Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/views/i18n.py: 24%

104 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1import itertools 

2import json 

3import os 

4import re 

5 

6from django.apps import apps 

7from django.conf import settings 

8from django.http import HttpResponse, HttpResponseRedirect, JsonResponse 

9from django.template import Context, Engine 

10from django.urls import translate_url 

11from django.utils.formats import get_format 

12from django.utils.http import url_has_allowed_host_and_scheme 

13from django.utils.translation import check_for_language, get_language 

14from django.utils.translation.trans_real import DjangoTranslation 

15from django.views.generic import View 

16 

17LANGUAGE_QUERY_PARAMETER = "language" 

18 

19 

20def set_language(request): 

21 """ 

22 Redirect to a given URL while setting the chosen language in the session 

23 (if enabled) and in a cookie. The URL and the language code need to be 

24 specified in the request parameters. 

25 

26 Since this view changes how the user will see the rest of the site, it must 

27 only be accessed as a POST request. If called as a GET request, it will 

28 redirect to the page in the request (the 'next' parameter) without changing 

29 any state. 

30 """ 

31 next_url = request.POST.get("next", request.GET.get("next")) 

32 if ( 

33 next_url or request.accepts("text/html") 

34 ) and not url_has_allowed_host_and_scheme( 

35 url=next_url, 

36 allowed_hosts={request.get_host()}, 

37 require_https=request.is_secure(), 

38 ): 

39 next_url = request.META.get("HTTP_REFERER") 

40 if not url_has_allowed_host_and_scheme( 

41 url=next_url, 

42 allowed_hosts={request.get_host()}, 

43 require_https=request.is_secure(), 

44 ): 

45 next_url = "/" 

46 response = HttpResponseRedirect(next_url) if next_url else HttpResponse(status=204) 

47 if request.method == "POST": 

48 lang_code = request.POST.get(LANGUAGE_QUERY_PARAMETER) 

49 if lang_code and check_for_language(lang_code): 

50 if next_url: 

51 next_trans = translate_url(next_url, lang_code) 

52 if next_trans != next_url: 

53 response = HttpResponseRedirect(next_trans) 

54 response.set_cookie( 

55 settings.LANGUAGE_COOKIE_NAME, 

56 lang_code, 

57 max_age=settings.LANGUAGE_COOKIE_AGE, 

58 path=settings.LANGUAGE_COOKIE_PATH, 

59 domain=settings.LANGUAGE_COOKIE_DOMAIN, 

60 secure=settings.LANGUAGE_COOKIE_SECURE, 

61 httponly=settings.LANGUAGE_COOKIE_HTTPONLY, 

62 samesite=settings.LANGUAGE_COOKIE_SAMESITE, 

63 ) 

64 return response 

65 

66 

67def get_formats(): 

68 """Return all formats strings required for i18n to work.""" 

69 FORMAT_SETTINGS = ( 

70 "DATE_FORMAT", 

71 "DATETIME_FORMAT", 

72 "TIME_FORMAT", 

73 "YEAR_MONTH_FORMAT", 

74 "MONTH_DAY_FORMAT", 

75 "SHORT_DATE_FORMAT", 

76 "SHORT_DATETIME_FORMAT", 

77 "FIRST_DAY_OF_WEEK", 

78 "DECIMAL_SEPARATOR", 

79 "THOUSAND_SEPARATOR", 

80 "NUMBER_GROUPING", 

81 "DATE_INPUT_FORMATS", 

82 "TIME_INPUT_FORMATS", 

83 "DATETIME_INPUT_FORMATS", 

84 ) 

85 return {attr: get_format(attr) for attr in FORMAT_SETTINGS} 

86 

87 

88js_catalog_template = r""" 

89{% autoescape off %} 

90'use strict'; 

91{ 

92 const globals = this; 

93 const django = globals.django || (globals.django = {}); 

94 

95 {% if plural %} 

96 django.pluralidx = function(n) { 

97 const v = {{ plural }}; 

98 if (typeof v === 'boolean') { 

99 return v ? 1 : 0; 

100 } else { 

101 return v; 

102 } 

103 }; 

104 {% else %} 

105 django.pluralidx = function(count) { return (count == 1) ? 0 : 1; }; 

106 {% endif %} 

107 

108 /* gettext library */ 

109 

110 django.catalog = django.catalog || {}; 

111 {% if catalog_str %} 

112 const newcatalog = {{ catalog_str }}; 

113 for (const key in newcatalog) { 

114 django.catalog[key] = newcatalog[key]; 

115 } 

116 {% endif %} 

117 

118 if (!django.jsi18n_initialized) { 

119 django.gettext = function(msgid) { 

120 const value = django.catalog[msgid]; 

121 if (typeof value === 'undefined') { 

122 return msgid; 

123 } else { 

124 return (typeof value === 'string') ? value : value[0]; 

125 } 

126 }; 

127 

128 django.ngettext = function(singular, plural, count) { 

129 const value = django.catalog[singular]; 

130 if (typeof value === 'undefined') { 

131 return (count == 1) ? singular : plural; 

132 } else { 

133 return value.constructor === Array ? value[django.pluralidx(count)] : value; 

134 } 

135 }; 

136 

137 django.gettext_noop = function(msgid) { return msgid; }; 

138 

139 django.pgettext = function(context, msgid) { 

140 let value = django.gettext(context + '\x04' + msgid); 

141 if (value.includes('\x04')) { 

142 value = msgid; 

143 } 

144 return value; 

145 }; 

146 

147 django.npgettext = function(context, singular, plural, count) { 

148 let value = django.ngettext(context + '\x04' + singular, context + '\x04' + plural, count); 

149 if (value.includes('\x04')) { 

150 value = django.ngettext(singular, plural, count); 

151 } 

152 return value; 

153 }; 

154 

155 django.interpolate = function(fmt, obj, named) { 

156 if (named) { 

157 return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])}); 

158 } else { 

159 return fmt.replace(/%s/g, function(match){return String(obj.shift())}); 

160 } 

161 }; 

162 

163 

164 /* formatting library */ 

165 

166 django.formats = {{ formats_str }}; 

167 

168 django.get_format = function(format_type) { 

169 const value = django.formats[format_type]; 

170 if (typeof value === 'undefined') { 

171 return format_type; 

172 } else { 

173 return value; 

174 } 

175 }; 

176 

177 /* add to global namespace */ 

178 globals.pluralidx = django.pluralidx; 

179 globals.gettext = django.gettext; 

180 globals.ngettext = django.ngettext; 

181 globals.gettext_noop = django.gettext_noop; 

182 globals.pgettext = django.pgettext; 

183 globals.npgettext = django.npgettext; 

184 globals.interpolate = django.interpolate; 

185 globals.get_format = django.get_format; 

186 

187 django.jsi18n_initialized = true; 

188 } 

189}; 

190{% endautoescape %} 

191""" # NOQA 

192 

193 

194class JavaScriptCatalog(View): 

195 """ 

196 Return the selected language catalog as a JavaScript library. 

197 

198 Receive the list of packages to check for translations in the `packages` 

199 kwarg either from the extra dictionary passed to the path() function or as 

200 a plus-sign delimited string from the request. Default is 'django.conf'. 

201 

202 You can override the gettext domain for this view, but usually you don't 

203 want to do that as JavaScript messages go to the djangojs domain. This 

204 might be needed if you deliver your JavaScript source from Django templates. 

205 """ 

206 

207 domain = "djangojs" 

208 packages = None 

209 

210 def get(self, request, *args, **kwargs): 

211 locale = get_language() 

212 domain = kwargs.get("domain", self.domain) 

213 # If packages are not provided, default to all installed packages, as 

214 # DjangoTranslation without localedirs harvests them all. 

215 packages = kwargs.get("packages", "") 

216 packages = packages.split("+") if packages else self.packages 

217 paths = self.get_paths(packages) if packages else None 

218 self.translation = DjangoTranslation(locale, domain=domain, localedirs=paths) 

219 context = self.get_context_data(**kwargs) 

220 return self.render_to_response(context) 

221 

222 def get_paths(self, packages): 

223 allowable_packages = { 

224 app_config.name: app_config for app_config in apps.get_app_configs() 

225 } 

226 app_configs = [ 

227 allowable_packages[p] for p in packages if p in allowable_packages 

228 ] 

229 if len(app_configs) < len(packages): 

230 excluded = [p for p in packages if p not in allowable_packages] 

231 raise ValueError( 

232 "Invalid package(s) provided to JavaScriptCatalog: %s" 

233 % ",".join(excluded) 

234 ) 

235 # paths of requested packages 

236 return [os.path.join(app.path, "locale") for app in app_configs] 

237 

238 @property 

239 def _num_plurals(self): 

240 """ 

241 Return the number of plurals for this catalog language, or 2 if no 

242 plural string is available. 

243 """ 

244 match = re.search(r"nplurals=\s*(\d+)", self._plural_string or "") 

245 if match: 

246 return int(match[1]) 

247 return 2 

248 

249 @property 

250 def _plural_string(self): 

251 """ 

252 Return the plural string (including nplurals) for this catalog language, 

253 or None if no plural string is available. 

254 """ 

255 if "" in self.translation._catalog: 

256 for line in self.translation._catalog[""].split("\n"): 

257 if line.startswith("Plural-Forms:"): 

258 return line.split(":", 1)[1].strip() 

259 return None 

260 

261 def get_plural(self): 

262 plural = self._plural_string 

263 if plural is not None: 

264 # This should be a compiled function of a typical plural-form: 

265 # Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : 

266 # n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; 

267 plural = [ 

268 el.strip() 

269 for el in plural.split(";") 

270 if el.strip().startswith("plural=") 

271 ][0].split("=", 1)[1] 

272 return plural 

273 

274 def get_catalog(self): 

275 pdict = {} 

276 num_plurals = self._num_plurals 

277 catalog = {} 

278 trans_cat = self.translation._catalog 

279 trans_fallback_cat = ( 

280 self.translation._fallback._catalog if self.translation._fallback else {} 

281 ) 

282 seen_keys = set() 

283 for key, value in itertools.chain( 

284 trans_cat.items(), trans_fallback_cat.items() 

285 ): 

286 if key == "" or key in seen_keys: 

287 continue 

288 if isinstance(key, str): 

289 catalog[key] = value 

290 elif isinstance(key, tuple): 

291 msgid, cnt = key 

292 pdict.setdefault(msgid, {})[cnt] = value 

293 else: 

294 raise TypeError(key) 

295 seen_keys.add(key) 

296 for k, v in pdict.items(): 

297 catalog[k] = [v.get(i, "") for i in range(num_plurals)] 

298 return catalog 

299 

300 def get_context_data(self, **kwargs): 

301 return { 

302 "catalog": self.get_catalog(), 

303 "formats": get_formats(), 

304 "plural": self.get_plural(), 

305 } 

306 

307 def render_to_response(self, context, **response_kwargs): 

308 def indent(s): 

309 return s.replace("\n", "\n ") 

310 

311 template = Engine().from_string(js_catalog_template) 

312 context["catalog_str"] = ( 

313 indent(json.dumps(context["catalog"], sort_keys=True, indent=2)) 

314 if context["catalog"] 

315 else None 

316 ) 

317 context["formats_str"] = indent( 

318 json.dumps(context["formats"], sort_keys=True, indent=2) 

319 ) 

320 

321 return HttpResponse( 

322 template.render(Context(context)), 'text/javascript; charset="utf-8"' 

323 ) 

324 

325 

326class JSONCatalog(JavaScriptCatalog): 

327 """ 

328 Return the selected language catalog as a JSON object. 

329 

330 Receive the same parameters as JavaScriptCatalog and return a response 

331 with a JSON object of the following format: 

332 

333 { 

334 "catalog": { 

335 # Translations catalog 

336 }, 

337 "formats": { 

338 # Language formats for date, time, etc. 

339 }, 

340 "plural": '...' # Expression for plural forms, or null. 

341 } 

342 """ 

343 

344 def render_to_response(self, context, **response_kwargs): 

345 return JsonResponse(context)