Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/django/templatetags/i18n.py: 21%
291 statements
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
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, SafeString, 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.is_var = True
82 self.filter_expression.var = Variable("'%s'" % self.filter_expression.var)
84 def render(self, context):
85 self.filter_expression.var.translate = not self.noop
86 if self.message_context:
87 self.filter_expression.var.message_context = self.message_context.resolve(
88 context
89 )
90 output = self.filter_expression.resolve(context)
91 value = render_value_in_context(output, context)
92 # Restore percent signs. Percent signs in template text are doubled
93 # so they are not interpreted as string format flags.
94 is_safe = isinstance(value, SafeData)
95 value = value.replace("%%", "%")
96 value = mark_safe(value) if is_safe else value
97 if self.asvar:
98 context[self.asvar] = value
99 return ""
100 else:
101 return value
104class BlockTranslateNode(Node):
105 def __init__(
106 self,
107 extra_context,
108 singular,
109 plural=None,
110 countervar=None,
111 counter=None,
112 message_context=None,
113 trimmed=False,
114 asvar=None,
115 tag_name="blocktranslate",
116 ):
117 self.extra_context = extra_context
118 self.singular = singular
119 self.plural = plural
120 self.countervar = countervar
121 self.counter = counter
122 self.message_context = message_context
123 self.trimmed = trimmed
124 self.asvar = asvar
125 self.tag_name = tag_name
127 def __repr__(self):
128 return (
129 f"<{self.__class__.__qualname__}: "
130 f"extra_context={self.extra_context!r} "
131 f"singular={self.singular!r} plural={self.plural!r}>"
132 )
134 def render_token_list(self, tokens):
135 result = []
136 vars = []
137 for token in tokens:
138 if token.token_type == TokenType.TEXT:
139 result.append(token.contents.replace("%", "%%"))
140 elif token.token_type == TokenType.VAR:
141 result.append("%%(%s)s" % token.contents)
142 vars.append(token.contents)
143 msg = "".join(result)
144 if self.trimmed:
145 msg = translation.trim_whitespace(msg)
146 return msg, vars
148 def render(self, context, nested=False):
149 if self.message_context:
150 message_context = self.message_context.resolve(context)
151 else:
152 message_context = None
153 # Update() works like a push(), so corresponding context.pop() is at
154 # the end of function
155 context.update(
156 {var: val.resolve(context) for var, val in self.extra_context.items()}
157 )
158 singular, vars = self.render_token_list(self.singular)
159 if self.plural and self.countervar and self.counter:
160 count = self.counter.resolve(context)
161 if not isinstance(count, (Decimal, float, int)):
162 raise TemplateSyntaxError(
163 "%r argument to %r tag must be a number."
164 % (self.countervar, self.tag_name)
165 )
166 context[self.countervar] = count
167 plural, plural_vars = self.render_token_list(self.plural)
168 if message_context:
169 result = translation.npgettext(message_context, singular, plural, count)
170 else:
171 result = translation.ngettext(singular, plural, count)
172 vars.extend(plural_vars)
173 else:
174 if message_context:
175 result = translation.pgettext(message_context, singular)
176 else:
177 result = translation.gettext(singular)
178 default_value = context.template.engine.string_if_invalid
180 def render_value(key):
181 if key in context:
182 val = context[key]
183 else:
184 val = default_value % key if "%s" in default_value else default_value
185 return render_value_in_context(val, context)
187 data = {v: render_value(v) for v in vars}
188 context.pop()
189 try:
190 result %= data
191 except (KeyError, ValueError):
192 if nested:
193 # Either string is malformed, or it's a bug
194 raise TemplateSyntaxError(
195 "%r is unable to format string returned by gettext: %r "
196 "using %r" % (self.tag_name, result, data)
197 )
198 with translation.override(None):
199 result = self.render(context, nested=True)
200 if self.asvar:
201 context[self.asvar] = SafeString(result)
202 return ""
203 else:
204 return result
207class LanguageNode(Node):
208 def __init__(self, nodelist, language):
209 self.nodelist = nodelist
210 self.language = language
212 def render(self, context):
213 with translation.override(self.language.resolve(context)):
214 output = self.nodelist.render(context)
215 return output
218@register.tag("get_available_languages")
219def do_get_available_languages(parser, token):
220 """
221 Store a list of available languages in the context.
223 Usage::
225 {% get_available_languages as languages %}
226 {% for language in languages %}
227 ...
228 {% endfor %}
230 This puts settings.LANGUAGES into the named variable.
231 """
232 # token.split_contents() isn't useful here because this tag doesn't accept
233 # variable as arguments.
234 args = token.contents.split()
235 if len(args) != 3 or args[1] != "as":
236 raise TemplateSyntaxError(
237 "'get_available_languages' requires 'as variable' (got %r)" % args
238 )
239 return GetAvailableLanguagesNode(args[2])
242@register.tag("get_language_info")
243def do_get_language_info(parser, token):
244 """
245 Store the language information dictionary for the given language code in a
246 context variable.
248 Usage::
250 {% get_language_info for LANGUAGE_CODE as l %}
251 {{ l.code }}
252 {{ l.name }}
253 {{ l.name_translated }}
254 {{ l.name_local }}
255 {{ l.bidi|yesno:"bi-directional,uni-directional" }}
256 """
257 args = token.split_contents()
258 if len(args) != 5 or args[1] != "for" or args[3] != "as":
259 raise TemplateSyntaxError(
260 "'%s' requires 'for string as variable' (got %r)" % (args[0], args[1:])
261 )
262 return GetLanguageInfoNode(parser.compile_filter(args[2]), args[4])
265@register.tag("get_language_info_list")
266def do_get_language_info_list(parser, token):
267 """
268 Store a list of language information dictionaries for the given language
269 codes in a context variable. The language codes can be specified either as
270 a list of strings or a settings.LANGUAGES style list (or any sequence of
271 sequences whose first items are language codes).
273 Usage::
275 {% get_language_info_list for LANGUAGES as langs %}
276 {% for l in langs %}
277 {{ l.code }}
278 {{ l.name }}
279 {{ l.name_translated }}
280 {{ l.name_local }}
281 {{ l.bidi|yesno:"bi-directional,uni-directional" }}
282 {% endfor %}
283 """
284 args = token.split_contents()
285 if len(args) != 5 or args[1] != "for" or args[3] != "as":
286 raise TemplateSyntaxError(
287 "'%s' requires 'for sequence as variable' (got %r)" % (args[0], args[1:])
288 )
289 return GetLanguageInfoListNode(parser.compile_filter(args[2]), args[4])
292@register.filter
293def language_name(lang_code):
294 return translation.get_language_info(lang_code)["name"]
297@register.filter
298def language_name_translated(lang_code):
299 english_name = translation.get_language_info(lang_code)["name"]
300 return translation.gettext(english_name)
303@register.filter
304def language_name_local(lang_code):
305 return translation.get_language_info(lang_code)["name_local"]
308@register.filter
309def language_bidi(lang_code):
310 return translation.get_language_info(lang_code)["bidi"]
313@register.tag("get_current_language")
314def do_get_current_language(parser, token):
315 """
316 Store the current language in the context.
318 Usage::
320 {% get_current_language as language %}
322 This fetches the currently active language and puts its value into the
323 ``language`` context variable.
324 """
325 # token.split_contents() isn't useful here because this tag doesn't accept
326 # variable as arguments.
327 args = token.contents.split()
328 if len(args) != 3 or args[1] != "as":
329 raise TemplateSyntaxError(
330 "'get_current_language' requires 'as variable' (got %r)" % args
331 )
332 return GetCurrentLanguageNode(args[2])
335@register.tag("get_current_language_bidi")
336def do_get_current_language_bidi(parser, token):
337 """
338 Store the current language layout in the context.
340 Usage::
342 {% get_current_language_bidi as bidi %}
344 This fetches the currently active language's layout and puts its value into
345 the ``bidi`` context variable. True indicates right-to-left layout,
346 otherwise left-to-right.
347 """
348 # token.split_contents() isn't useful here because this tag doesn't accept
349 # variable as arguments.
350 args = token.contents.split()
351 if len(args) != 3 or args[1] != "as":
352 raise TemplateSyntaxError(
353 "'get_current_language_bidi' requires 'as variable' (got %r)" % args
354 )
355 return GetCurrentLanguageBidiNode(args[2])
358@register.tag("translate")
359@register.tag("trans")
360def do_translate(parser, token):
361 """
362 Mark a string for translation and translate the string for the current
363 language.
365 Usage::
367 {% translate "this is a test" %}
369 This marks the string for translation so it will be pulled out by
370 makemessages into the .po files and runs the string through the translation
371 engine.
373 There is a second form::
375 {% translate "this is a test" noop %}
377 This marks the string for translation, but returns the string unchanged.
378 Use it when you need to store values into forms that should be translated
379 later on.
381 You can use variables instead of constant strings
382 to translate stuff you marked somewhere else::
384 {% translate variable %}
386 This tries to translate the contents of the variable ``variable``. Make
387 sure that the string in there is something that is in the .po file.
389 It is possible to store the translated string into a variable::
391 {% translate "this is a test" as var %}
392 {{ var }}
394 Contextual translations are also supported::
396 {% translate "this is a test" context "greeting" %}
398 This is equivalent to calling pgettext instead of (u)gettext.
399 """
400 bits = token.split_contents()
401 if len(bits) < 2:
402 raise TemplateSyntaxError("'%s' takes at least one argument" % bits[0])
403 message_string = parser.compile_filter(bits[1])
404 remaining = bits[2:]
406 noop = False
407 asvar = None
408 message_context = None
409 seen = set()
410 invalid_context = {"as", "noop"}
412 while remaining:
413 option = remaining.pop(0)
414 if option in seen:
415 raise TemplateSyntaxError(
416 "The '%s' option was specified more than once." % option,
417 )
418 elif option == "noop":
419 noop = True
420 elif option == "context":
421 try:
422 value = remaining.pop(0)
423 except IndexError:
424 raise TemplateSyntaxError(
425 "No argument provided to the '%s' tag for the context option."
426 % bits[0]
427 )
428 if value in invalid_context:
429 raise TemplateSyntaxError(
430 "Invalid argument '%s' provided to the '%s' tag for the context "
431 "option" % (value, bits[0]),
432 )
433 message_context = parser.compile_filter(value)
434 elif option == "as":
435 try:
436 value = remaining.pop(0)
437 except IndexError:
438 raise TemplateSyntaxError(
439 "No argument provided to the '%s' tag for the as option." % bits[0]
440 )
441 asvar = value
442 else:
443 raise TemplateSyntaxError(
444 "Unknown argument for '%s' tag: '%s'. The only options "
445 "available are 'noop', 'context' \"xxx\", and 'as VAR'."
446 % (
447 bits[0],
448 option,
449 )
450 )
451 seen.add(option)
453 return TranslateNode(message_string, noop, asvar, message_context)
456@register.tag("blocktranslate")
457@register.tag("blocktrans")
458def do_block_translate(parser, token):
459 """
460 Translate a block of text with parameters.
462 Usage::
464 {% blocktranslate with bar=foo|filter boo=baz|filter %}
465 This is {{ bar }} and {{ boo }}.
466 {% endblocktranslate %}
468 Additionally, this supports pluralization::
470 {% blocktranslate count count=var|length %}
471 There is {{ count }} object.
472 {% plural %}
473 There are {{ count }} objects.
474 {% endblocktranslate %}
476 This is much like ngettext, only in template syntax.
478 The "var as value" legacy format is still supported::
480 {% blocktranslate with foo|filter as bar and baz|filter as boo %}
481 {% blocktranslate count var|length as count %}
483 The translated string can be stored in a variable using `asvar`::
485 {% blocktranslate with bar=foo|filter boo=baz|filter asvar var %}
486 This is {{ bar }} and {{ boo }}.
487 {% endblocktranslate %}
488 {{ var }}
490 Contextual translations are supported::
492 {% blocktranslate with bar=foo|filter context "greeting" %}
493 This is {{ bar }}.
494 {% endblocktranslate %}
496 This is equivalent to calling pgettext/npgettext instead of
497 (u)gettext/(u)ngettext.
498 """
499 bits = token.split_contents()
501 options = {}
502 remaining_bits = bits[1:]
503 asvar = None
504 while remaining_bits:
505 option = remaining_bits.pop(0)
506 if option in options:
507 raise TemplateSyntaxError(
508 "The %r option was specified more than once." % option
509 )
510 if option == "with":
511 value = token_kwargs(remaining_bits, parser, support_legacy=True)
512 if not value:
513 raise TemplateSyntaxError(
514 '"with" in %r tag needs at least one keyword argument.' % bits[0]
515 )
516 elif option == "count":
517 value = token_kwargs(remaining_bits, parser, support_legacy=True)
518 if len(value) != 1:
519 raise TemplateSyntaxError(
520 '"count" in %r tag expected exactly '
521 "one keyword argument." % bits[0]
522 )
523 elif option == "context":
524 try:
525 value = remaining_bits.pop(0)
526 value = parser.compile_filter(value)
527 except Exception:
528 raise TemplateSyntaxError(
529 '"context" in %r tag expected exactly one argument.' % bits[0]
530 )
531 elif option == "trimmed":
532 value = True
533 elif option == "asvar":
534 try:
535 value = remaining_bits.pop(0)
536 except IndexError:
537 raise TemplateSyntaxError(
538 "No argument provided to the '%s' tag for the asvar option."
539 % bits[0]
540 )
541 asvar = value
542 else:
543 raise TemplateSyntaxError(
544 "Unknown argument for %r tag: %r." % (bits[0], option)
545 )
546 options[option] = value
548 if "count" in options:
549 countervar, counter = next(iter(options["count"].items()))
550 else:
551 countervar, counter = None, None
552 if "context" in options:
553 message_context = options["context"]
554 else:
555 message_context = None
556 extra_context = options.get("with", {})
558 trimmed = options.get("trimmed", False)
560 singular = []
561 plural = []
562 while parser.tokens:
563 token = parser.next_token()
564 if token.token_type in (TokenType.VAR, TokenType.TEXT):
565 singular.append(token)
566 else:
567 break
568 if countervar and counter:
569 if token.contents.strip() != "plural":
570 raise TemplateSyntaxError(
571 "%r doesn't allow other block tags inside it" % bits[0]
572 )
573 while parser.tokens:
574 token = parser.next_token()
575 if token.token_type in (TokenType.VAR, TokenType.TEXT):
576 plural.append(token)
577 else:
578 break
579 end_tag_name = "end%s" % bits[0]
580 if token.contents.strip() != end_tag_name:
581 raise TemplateSyntaxError(
582 "%r doesn't allow other block tags (seen %r) inside it"
583 % (bits[0], token.contents)
584 )
586 return BlockTranslateNode(
587 extra_context,
588 singular,
589 plural,
590 countervar,
591 counter,
592 message_context,
593 trimmed=trimmed,
594 asvar=asvar,
595 tag_name=bits[0],
596 )
599@register.tag
600def language(parser, token):
601 """
602 Enable the given language just for this block.
604 Usage::
606 {% language "de" %}
607 This is {{ bar }} and {{ boo }}.
608 {% endlanguage %}
609 """
610 bits = token.split_contents()
611 if len(bits) != 2:
612 raise TemplateSyntaxError("'%s' takes one argument (language)" % bits[0])
613 language = parser.compile_filter(bits[1])
614 nodelist = parser.parse(("endlanguage",))
615 parser.delete_first_token()
616 return LanguageNode(nodelist, language)