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
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1import itertools
2import json
3import os
4import re
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
17LANGUAGE_QUERY_PARAMETER = "language"
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.
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
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}
88js_catalog_template = r"""
89{% autoescape off %}
90'use strict';
91{
92 const globals = this;
93 const django = globals.django || (globals.django = {});
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 %}
108 /* gettext library */
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 %}
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 };
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 };
137 django.gettext_noop = function(msgid) { return msgid; };
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 };
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 };
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 };
164 /* formatting library */
166 django.formats = {{ formats_str }};
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 };
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;
187 django.jsi18n_initialized = true;
188 }
189};
190{% endautoescape %}
191""" # NOQA
194class JavaScriptCatalog(View):
195 """
196 Return the selected language catalog as a JavaScript library.
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'.
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 """
207 domain = "djangojs"
208 packages = None
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)
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]
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
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
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
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
300 def get_context_data(self, **kwargs):
301 return {
302 "catalog": self.get_catalog(),
303 "formats": get_formats(),
304 "plural": self.get_plural(),
305 }
307 def render_to_response(self, context, **response_kwargs):
308 def indent(s):
309 return s.replace("\n", "\n ")
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 )
321 return HttpResponse(
322 template.render(Context(context)), 'text/javascript; charset="utf-8"'
323 )
326class JSONCatalog(JavaScriptCatalog):
327 """
328 Return the selected language catalog as a JSON object.
330 Receive the same parameters as JavaScriptCatalog and return a response
331 with a JSON object of the following format:
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 """
344 def render_to_response(self, context, **response_kwargs):
345 return JsonResponse(context)