Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/templatetags/i18n.py: 19%
290 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
1from decimal import Decimal
3from django.conf import settings
4from django.template import Library, Node, TemplateSyntaxError, Variable
5from django.template.base import TokenType, render_value_in_context
6from django.template.defaulttags import token_kwargs
7from django.utils import translation
8from django.utils.safestring import SafeData, mark_safe
10register = Library()
13class GetAvailableLanguagesNode(Node):
14 def __init__(self, variable):
15 self.variable = variable
17 def render(self, context):
18 context[self.variable] = [
19 (k, translation.gettext(v)) for k, v in settings.LANGUAGES
20 ]
21 return ""
24class GetLanguageInfoNode(Node):
25 def __init__(self, lang_code, variable):
26 self.lang_code = lang_code
27 self.variable = variable
29 def render(self, context):
30 lang_code = self.lang_code.resolve(context)
31 context[self.variable] = translation.get_language_info(lang_code)
32 return ""
35class GetLanguageInfoListNode(Node):
36 def __init__(self, languages, variable):
37 self.languages = languages
38 self.variable = variable
40 def get_language_info(self, language):
41 # ``language`` is either a language code string or a sequence
42 # with the language code as its first item
43 if len(language[0]) > 1:
44 return translation.get_language_info(language[0])
45 else:
46 return translation.get_language_info(str(language))
48 def render(self, context):
49 langs = self.languages.resolve(context)
50 context[self.variable] = [self.get_language_info(lang) for lang in langs]
51 return ""
54class GetCurrentLanguageNode(Node):
55 def __init__(self, variable):
56 self.variable = variable
58 def render(self, context):
59 context[self.variable] = translation.get_language()
60 return ""
63class GetCurrentLanguageBidiNode(Node):
64 def __init__(self, variable):
65 self.variable = variable
67 def render(self, context):
68 context[self.variable] = translation.get_language_bidi()
69 return ""
72class TranslateNode(Node):
73 child_nodelists = ()
75 def __init__(self, filter_expression, noop, asvar=None, message_context=None):
76 self.noop = noop
77 self.asvar = asvar
78 self.message_context = message_context
79 self.filter_expression = filter_expression
80 if isinstance(self.filter_expression.var, str):
81 self.filter_expression.var = Variable("'%s'" % self.filter_expression.var)
83 def render(self, context):
84 self.filter_expression.var.translate = not self.noop
85 if self.message_context:
86 self.filter_expression.var.message_context = self.message_context.resolve(
87 context
88 )
89 output = self.filter_expression.resolve(context)
90 value = render_value_in_context(output, context)
91 # Restore percent signs. Percent signs in template text are doubled
92 # so they are not interpreted as string format flags.
93 is_safe = isinstance(value, SafeData)
94 value = value.replace("%%", "%")
95 value = mark_safe(value) if is_safe else value
96 if self.asvar:
97 context[self.asvar] = value
98 return ""
99 else:
100 return value
103class BlockTranslateNode(Node):
104 def __init__(
105 self,
106 extra_context,
107 singular,
108 plural=None,
109 countervar=None,
110 counter=None,
111 message_context=None,
112 trimmed=False,
113 asvar=None,
114 tag_name="blocktranslate",
115 ):
116 self.extra_context = extra_context
117 self.singular = singular
118 self.plural = plural
119 self.countervar = countervar
120 self.counter = counter
121 self.message_context = message_context
122 self.trimmed = trimmed
123 self.asvar = asvar
124 self.tag_name = tag_name
126 def __repr__(self):
127 return (
128 f"<{self.__class__.__qualname__}: "
129 f"extra_context={self.extra_context!r} "
130 f"singular={self.singular!r} plural={self.plural!r}>"
131 )
133 def render_token_list(self, tokens):
134 result = []
135 vars = []
136 for token in tokens:
137 if token.token_type == TokenType.TEXT:
138 result.append(token.contents.replace("%", "%%"))
139 elif token.token_type == TokenType.VAR:
140 result.append("%%(%s)s" % token.contents)
141 vars.append(token.contents)
142 msg = "".join(result)
143 if self.trimmed:
144 msg = translation.trim_whitespace(msg)
145 return msg, vars
147 def render(self, context, nested=False):
148 if self.message_context:
149 message_context = self.message_context.resolve(context)
150 else:
151 message_context = None
152 # Update() works like a push(), so corresponding context.pop() is at
153 # the end of function
154 context.update(
155 {var: val.resolve(context) for var, val in self.extra_context.items()}
156 )
157 singular, vars = self.render_token_list(self.singular)
158 if self.plural and self.countervar and self.counter:
159 count = self.counter.resolve(context)
160 if not isinstance(count, (Decimal, float, int)):
161 raise TemplateSyntaxError(
162 "%r argument to %r tag must be a number."
163 % (self.countervar, self.tag_name)
164 )
165 context[self.countervar] = count
166 plural, plural_vars = self.render_token_list(self.plural)
167 if message_context:
168 result = translation.npgettext(message_context, singular, plural, count)
169 else:
170 result = translation.ngettext(singular, plural, count)
171 vars.extend(plural_vars)
172 else:
173 if message_context:
174 result = translation.pgettext(message_context, singular)
175 else:
176 result = translation.gettext(singular)
177 default_value = context.template.engine.string_if_invalid
179 def render_value(key):
180 if key in context:
181 val = context[key]
182 else:
183 val = default_value % key if "%s" in default_value else default_value
184 return render_value_in_context(val, context)
186 data = {v: render_value(v) for v in vars}
187 context.pop()
188 try:
189 result = result % data
190 except (KeyError, ValueError):
191 if nested:
192 # Either string is malformed, or it's a bug
193 raise TemplateSyntaxError(
194 "%r is unable to format string returned by gettext: %r "
195 "using %r" % (self.tag_name, result, data)
196 )
197 with translation.override(None):
198 result = self.render(context, nested=True)
199 if self.asvar:
200 context[self.asvar] = result
201 return ""
202 else:
203 return result
206class LanguageNode(Node):
207 def __init__(self, nodelist, language):
208 self.nodelist = nodelist
209 self.language = language
211 def render(self, context):
212 with translation.override(self.language.resolve(context)):
213 output = self.nodelist.render(context)
214 return output
217@register.tag("get_available_languages")
218def do_get_available_languages(parser, token):
219 """
220 Store a list of available languages in the context.
222 Usage::
224 {% get_available_languages as languages %}
225 {% for language in languages %}
226 ...
227 {% endfor %}
229 This puts settings.LANGUAGES into the named variable.
230 """
231 # token.split_contents() isn't useful here because this tag doesn't accept
232 # variable as arguments.
233 args = token.contents.split()
234 if len(args) != 3 or args[1] != "as":
235 raise TemplateSyntaxError(
236 "'get_available_languages' requires 'as variable' (got %r)" % args
237 )
238 return GetAvailableLanguagesNode(args[2])
241@register.tag("get_language_info")
242def do_get_language_info(parser, token):
243 """
244 Store the language information dictionary for the given language code in a
245 context variable.
247 Usage::
249 {% get_language_info for LANGUAGE_CODE as l %}
250 {{ l.code }}
251 {{ l.name }}
252 {{ l.name_translated }}
253 {{ l.name_local }}
254 {{ l.bidi|yesno:"bi-directional,uni-directional" }}
255 """
256 args = token.split_contents()
257 if len(args) != 5 or args[1] != "for" or args[3] != "as":
258 raise TemplateSyntaxError(
259 "'%s' requires 'for string as variable' (got %r)" % (args[0], args[1:])
260 )
261 return GetLanguageInfoNode(parser.compile_filter(args[2]), args[4])
264@register.tag("get_language_info_list")
265def do_get_language_info_list(parser, token):
266 """
267 Store a list of language information dictionaries for the given language
268 codes in a context variable. The language codes can be specified either as
269 a list of strings or a settings.LANGUAGES style list (or any sequence of
270 sequences whose first items are language codes).
272 Usage::
274 {% get_language_info_list for LANGUAGES as langs %}
275 {% for l in langs %}
276 {{ l.code }}
277 {{ l.name }}
278 {{ l.name_translated }}
279 {{ l.name_local }}
280 {{ l.bidi|yesno:"bi-directional,uni-directional" }}
281 {% endfor %}
282 """
283 args = token.split_contents()
284 if len(args) != 5 or args[1] != "for" or args[3] != "as":
285 raise TemplateSyntaxError(
286 "'%s' requires 'for sequence as variable' (got %r)" % (args[0], args[1:])
287 )
288 return GetLanguageInfoListNode(parser.compile_filter(args[2]), args[4])
291@register.filter
292def language_name(lang_code):
293 return translation.get_language_info(lang_code)["name"]
296@register.filter
297def language_name_translated(lang_code):
298 english_name = translation.get_language_info(lang_code)["name"]
299 return translation.gettext(english_name)
302@register.filter
303def language_name_local(lang_code):
304 return translation.get_language_info(lang_code)["name_local"]
307@register.filter
308def language_bidi(lang_code):
309 return translation.get_language_info(lang_code)["bidi"]
312@register.tag("get_current_language")
313def do_get_current_language(parser, token):
314 """
315 Store the current language in the context.
317 Usage::
319 {% get_current_language as language %}
321 This fetches the currently active language and puts its value into the
322 ``language`` context variable.
323 """
324 # token.split_contents() isn't useful here because this tag doesn't accept
325 # variable as arguments.
326 args = token.contents.split()
327 if len(args) != 3 or args[1] != "as":
328 raise TemplateSyntaxError(
329 "'get_current_language' requires 'as variable' (got %r)" % args
330 )
331 return GetCurrentLanguageNode(args[2])
334@register.tag("get_current_language_bidi")
335def do_get_current_language_bidi(parser, token):
336 """
337 Store the current language layout in the context.
339 Usage::
341 {% get_current_language_bidi as bidi %}
343 This fetches the currently active language's layout and puts its value into
344 the ``bidi`` context variable. True indicates right-to-left layout,
345 otherwise left-to-right.
346 """
347 # token.split_contents() isn't useful here because this tag doesn't accept
348 # variable as arguments.
349 args = token.contents.split()
350 if len(args) != 3 or args[1] != "as":
351 raise TemplateSyntaxError(
352 "'get_current_language_bidi' requires 'as variable' (got %r)" % args
353 )
354 return GetCurrentLanguageBidiNode(args[2])
357@register.tag("translate")
358@register.tag("trans")
359def do_translate(parser, token):
360 """
361 Mark a string for translation and translate the string for the current
362 language.
364 Usage::
366 {% translate "this is a test" %}
368 This marks the string for translation so it will be pulled out by
369 makemessages into the .po files and runs the string through the translation
370 engine.
372 There is a second form::
374 {% translate "this is a test" noop %}
376 This marks the string for translation, but returns the string unchanged.
377 Use it when you need to store values into forms that should be translated
378 later on.
380 You can use variables instead of constant strings
381 to translate stuff you marked somewhere else::
383 {% translate variable %}
385 This tries to translate the contents of the variable ``variable``. Make
386 sure that the string in there is something that is in the .po file.
388 It is possible to store the translated string into a variable::
390 {% translate "this is a test" as var %}
391 {{ var }}
393 Contextual translations are also supported::
395 {% translate "this is a test" context "greeting" %}
397 This is equivalent to calling pgettext instead of (u)gettext.
398 """
399 bits = token.split_contents()
400 if len(bits) < 2:
401 raise TemplateSyntaxError("'%s' takes at least one argument" % bits[0])
402 message_string = parser.compile_filter(bits[1])
403 remaining = bits[2:]
405 noop = False
406 asvar = None
407 message_context = None
408 seen = set()
409 invalid_context = {"as", "noop"}
411 while remaining:
412 option = remaining.pop(0)
413 if option in seen:
414 raise TemplateSyntaxError(
415 "The '%s' option was specified more than once." % option,
416 )
417 elif option == "noop":
418 noop = True
419 elif option == "context":
420 try:
421 value = remaining.pop(0)
422 except IndexError:
423 raise TemplateSyntaxError(
424 "No argument provided to the '%s' tag for the context option."
425 % bits[0]
426 )
427 if value in invalid_context:
428 raise TemplateSyntaxError(
429 "Invalid argument '%s' provided to the '%s' tag for the context "
430 "option" % (value, bits[0]),
431 )
432 message_context = parser.compile_filter(value)
433 elif option == "as":
434 try:
435 value = remaining.pop(0)
436 except IndexError:
437 raise TemplateSyntaxError(
438 "No argument provided to the '%s' tag for the as option." % bits[0]
439 )
440 asvar = value
441 else:
442 raise TemplateSyntaxError(
443 "Unknown argument for '%s' tag: '%s'. The only options "
444 "available are 'noop', 'context' \"xxx\", and 'as VAR'."
445 % (
446 bits[0],
447 option,
448 )
449 )
450 seen.add(option)
452 return TranslateNode(message_string, noop, asvar, message_context)
455@register.tag("blocktranslate")
456@register.tag("blocktrans")
457def do_block_translate(parser, token):
458 """
459 Translate a block of text with parameters.
461 Usage::
463 {% blocktranslate with bar=foo|filter boo=baz|filter %}
464 This is {{ bar }} and {{ boo }}.
465 {% endblocktranslate %}
467 Additionally, this supports pluralization::
469 {% blocktranslate count count=var|length %}
470 There is {{ count }} object.
471 {% plural %}
472 There are {{ count }} objects.
473 {% endblocktranslate %}
475 This is much like ngettext, only in template syntax.
477 The "var as value" legacy format is still supported::
479 {% blocktranslate with foo|filter as bar and baz|filter as boo %}
480 {% blocktranslate count var|length as count %}
482 The translated string can be stored in a variable using `asvar`::
484 {% blocktranslate with bar=foo|filter boo=baz|filter asvar var %}
485 This is {{ bar }} and {{ boo }}.
486 {% endblocktranslate %}
487 {{ var }}
489 Contextual translations are supported::
491 {% blocktranslate with bar=foo|filter context "greeting" %}
492 This is {{ bar }}.
493 {% endblocktranslate %}
495 This is equivalent to calling pgettext/npgettext instead of
496 (u)gettext/(u)ngettext.
497 """
498 bits = token.split_contents()
500 options = {}
501 remaining_bits = bits[1:]
502 asvar = None
503 while remaining_bits:
504 option = remaining_bits.pop(0)
505 if option in options:
506 raise TemplateSyntaxError(
507 "The %r option was specified more than once." % option
508 )
509 if option == "with":
510 value = token_kwargs(remaining_bits, parser, support_legacy=True)
511 if not value:
512 raise TemplateSyntaxError(
513 '"with" in %r tag needs at least one keyword argument.' % bits[0]
514 )
515 elif option == "count":
516 value = token_kwargs(remaining_bits, parser, support_legacy=True)
517 if len(value) != 1:
518 raise TemplateSyntaxError(
519 '"count" in %r tag expected exactly '
520 "one keyword argument." % bits[0]
521 )
522 elif option == "context":
523 try:
524 value = remaining_bits.pop(0)
525 value = parser.compile_filter(value)
526 except Exception:
527 raise TemplateSyntaxError(
528 '"context" in %r tag expected exactly one argument.' % bits[0]
529 )
530 elif option == "trimmed":
531 value = True
532 elif option == "asvar":
533 try:
534 value = remaining_bits.pop(0)
535 except IndexError:
536 raise TemplateSyntaxError(
537 "No argument provided to the '%s' tag for the asvar option."
538 % bits[0]
539 )
540 asvar = value
541 else:
542 raise TemplateSyntaxError(
543 "Unknown argument for %r tag: %r." % (bits[0], option)
544 )
545 options[option] = value
547 if "count" in options:
548 countervar, counter = next(iter(options["count"].items()))
549 else:
550 countervar, counter = None, None
551 if "context" in options:
552 message_context = options["context"]
553 else:
554 message_context = None
555 extra_context = options.get("with", {})
557 trimmed = options.get("trimmed", False)
559 singular = []
560 plural = []
561 while parser.tokens:
562 token = parser.next_token()
563 if token.token_type in (TokenType.VAR, TokenType.TEXT):
564 singular.append(token)
565 else:
566 break
567 if countervar and counter:
568 if token.contents.strip() != "plural":
569 raise TemplateSyntaxError(
570 "%r doesn't allow other block tags inside it" % bits[0]
571 )
572 while parser.tokens:
573 token = parser.next_token()
574 if token.token_type in (TokenType.VAR, TokenType.TEXT):
575 plural.append(token)
576 else:
577 break
578 end_tag_name = "end%s" % bits[0]
579 if token.contents.strip() != end_tag_name:
580 raise TemplateSyntaxError(
581 "%r doesn't allow other block tags (seen %r) inside it"
582 % (bits[0], token.contents)
583 )
585 return BlockTranslateNode(
586 extra_context,
587 singular,
588 plural,
589 countervar,
590 counter,
591 message_context,
592 trimmed=trimmed,
593 asvar=asvar,
594 tag_name=bits[0],
595 )
598@register.tag
599def language(parser, token):
600 """
601 Enable the given language just for this block.
603 Usage::
605 {% language "de" %}
606 This is {{ bar }} and {{ boo }}.
607 {% endlanguage %}
608 """
609 bits = token.split_contents()
610 if len(bits) != 2:
611 raise TemplateSyntaxError("'%s' takes one argument (language)" % bits[0])
612 language = parser.compile_filter(bits[1])
613 nodelist = parser.parse(("endlanguage",))
614 parser.delete_first_token()
615 return LanguageNode(nodelist, language)