Coverage for src / rendercv / schema / models / locale / english_locale.py: 100%

29 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-25 13:49 +0000

1import functools 

2from typing import Annotated, Literal 

3 

4import annotated_types as at 

5import pydantic 

6 

7from ..base import BaseModelWithoutExtraKeys 

8 

9 

10class Phrases(BaseModelWithoutExtraKeys): 

11 degree_with_area: str = pydantic.Field( 

12 default="DEGREE in AREA", 

13 description=( 

14 "Template for combining degree and area in education entries." 

15 " Available placeholders: DEGREE, AREA." 

16 " The default value is `DEGREE in AREA`." 

17 ), 

18 ) 

19 

20 

21class EnglishLocale(BaseModelWithoutExtraKeys): 

22 language: Literal["english"] = pydantic.Field( 

23 default="english", 

24 description="The language for your CV. The default value is `english`.", 

25 ) 

26 last_updated: str = pydantic.Field( 

27 default="Last updated in", 

28 description=( 

29 'Translation of "Last updated in". The default value is `Last updated in`.' 

30 ), 

31 ) 

32 month: str = pydantic.Field( 

33 default="month", 

34 description='Translation of "month" (singular). The default value is `month`.', 

35 ) 

36 months: str = pydantic.Field( 

37 default="months", 

38 description='Translation of "months" (plural). The default value is `months`.', 

39 ) 

40 year: str = pydantic.Field( 

41 default="year", 

42 description='Translation of "year" (singular). The default value is `year`.', 

43 ) 

44 years: str = pydantic.Field( 

45 default="years", 

46 description='Translation of "years" (plural). The default value is `years`.', 

47 ) 

48 present: str = pydantic.Field( 

49 default="present", 

50 description=( 

51 'Translation of "present" for ongoing dates. The default value is' 

52 " `present`." 

53 ), 

54 ) 

55 phrases: Phrases = pydantic.Field( 

56 default_factory=Phrases, 

57 description="Locale-specific phrases used in entry templates as placeholders.", 

58 ) 

59 # From https://web.library.yale.edu/cataloging/months 

60 month_abbreviations: Annotated[list[str], at.Len(min_length=12, max_length=12)] = ( 

61 pydantic.Field( 

62 default=[ 

63 "Jan", 

64 "Feb", 

65 "Mar", 

66 "Apr", 

67 "May", 

68 "June", 

69 "July", 

70 "Aug", 

71 "Sept", 

72 "Oct", 

73 "Nov", 

74 "Dec", 

75 ], 

76 description="Month abbreviations (Jan-Dec).", 

77 ) 

78 ) 

79 month_names: Annotated[list[str], at.Len(min_length=12, max_length=12)] = ( 

80 pydantic.Field( 

81 default=[ 

82 "January", 

83 "February", 

84 "March", 

85 "April", 

86 "May", 

87 "June", 

88 "July", 

89 "August", 

90 "September", 

91 "October", 

92 "November", 

93 "December", 

94 ], 

95 description="Full month names (January-December).", 

96 ) 

97 ) 

98 

99 @functools.cached_property 

100 def language_iso_639_1(self) -> str: 

101 """Get ISO 639-1 two-letter language code for locale. 

102 

103 Why: 

104 Typst's text element requires ISO 639-1/2/3 language codes for the 

105 lang parameter. This enables proper hyphenation, smart quotes, and 

106 accessibility (screen readers use it for voice selection). HTML export 

107 also uses it for lang attribute. 

108 

109 Returns: 

110 Two-letter ISO 639-1 language code for Typst and HTML. 

111 """ 

112 return { 

113 "danish": "da", 

114 "dutch": "nl", 

115 "english": "en", 

116 "french": "fr", 

117 "german": "de", 

118 "hindi": "hi", 

119 "italian": "it", 

120 "indonesian": "id", 

121 "japanese": "ja", 

122 "korean": "ko", 

123 "indonesia": "id", 

124 "mandarin_chinese": "zh", 

125 "norwegian_bokmål": "nb", 

126 "norwegian_nynorsk": "nn", 

127 "portuguese": "pt", 

128 "russian": "ru", 

129 "spanish": "es", 

130 "turkish": "tr", 

131 "arabic": "ar", 

132 "hebrew": "he", 

133 "persian": "fa", 

134 "vietnamese": "vi", 

135 "hungarian": "hu", 

136 }[self.language] 

137 

138 @functools.cached_property 

139 def flag_emoji(self) -> str: 

140 """Get flag emoji for the locale's primary country. 

141 

142 Why: 

143 Flag emojis are displayed next to locale names in the UI. Deriving 

144 flags here keeps the mapping in one place alongside the ISO language 

145 codes. 

146 

147 Returns: 

148 Flag emoji string (e.g., "🇬🇧" for English). 

149 """ 

150 country = { 

151 "arabic": "SA", 

152 "danish": "DK", 

153 "dutch": "NL", 

154 "english": "GB", 

155 "french": "FR", 

156 "german": "DE", 

157 "hebrew": "IL", 

158 "hindi": "IN", 

159 "hungarian": "HU", 

160 "indonesian": "ID", 

161 "italian": "IT", 

162 "japanese": "JP", 

163 "korean": "KR", 

164 "mandarin_chinese": "CN", 

165 "norwegian_bokmål": "NO", 

166 "norwegian_nynorsk": "NO", 

167 "persian": "IR", 

168 "portuguese": "PT", 

169 "russian": "RU", 

170 "spanish": "ES", 

171 "turkish": "TR", 

172 "vietnamese": "VN", 

173 }[self.language] 

174 return "".join(chr(0x1F1E6 + ord(c) - ord("A")) for c in country) 

175 

176 @functools.cached_property 

177 def is_rtl(self) -> bool: 

178 """Check if language uses right-to-left text direction. 

179 

180 Returns: 

181 True if language is RTL (Arabic, Hebrew, Persian, Urdu, etc.) 

182 """ 

183 rtl_languages = {"arabic", "hebrew", "persian"} 

184 return self.language in rtl_languages