123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551 |
- from django.conf import settings
- from django.template import Library, Node, TemplateSyntaxError, Variable
- from django.template.base import TokenType, render_value_in_context
- from django.template.defaulttags import token_kwargs
- from django.utils import translation
- from django.utils.safestring import SafeData, mark_safe
-
- register = Library()
-
-
- class GetAvailableLanguagesNode(Node):
- def __init__(self, variable):
- self.variable = variable
-
- def render(self, context):
- context[self.variable] = [(k, translation.gettext(v)) for k, v in settings.LANGUAGES]
- return ''
-
-
- class GetLanguageInfoNode(Node):
- def __init__(self, lang_code, variable):
- self.lang_code = lang_code
- self.variable = variable
-
- def render(self, context):
- lang_code = self.lang_code.resolve(context)
- context[self.variable] = translation.get_language_info(lang_code)
- return ''
-
-
- class GetLanguageInfoListNode(Node):
- def __init__(self, languages, variable):
- self.languages = languages
- self.variable = variable
-
- def get_language_info(self, language):
- # ``language`` is either a language code string or a sequence
- # with the language code as its first item
- if len(language[0]) > 1:
- return translation.get_language_info(language[0])
- else:
- return translation.get_language_info(str(language))
-
- def render(self, context):
- langs = self.languages.resolve(context)
- context[self.variable] = [self.get_language_info(lang) for lang in langs]
- return ''
-
-
- class GetCurrentLanguageNode(Node):
- def __init__(self, variable):
- self.variable = variable
-
- def render(self, context):
- context[self.variable] = translation.get_language()
- return ''
-
-
- class GetCurrentLanguageBidiNode(Node):
- def __init__(self, variable):
- self.variable = variable
-
- def render(self, context):
- context[self.variable] = translation.get_language_bidi()
- return ''
-
-
- class TranslateNode(Node):
- def __init__(self, filter_expression, noop, asvar=None,
- message_context=None):
- self.noop = noop
- self.asvar = asvar
- self.message_context = message_context
- self.filter_expression = filter_expression
- if isinstance(self.filter_expression.var, str):
- self.filter_expression.var = Variable("'%s'" %
- self.filter_expression.var)
-
- def render(self, context):
- self.filter_expression.var.translate = not self.noop
- if self.message_context:
- self.filter_expression.var.message_context = (
- self.message_context.resolve(context))
- output = self.filter_expression.resolve(context)
- value = render_value_in_context(output, context)
- # Restore percent signs. Percent signs in template text are doubled
- # so they are not interpreted as string format flags.
- is_safe = isinstance(value, SafeData)
- value = value.replace('%%', '%')
- value = mark_safe(value) if is_safe else value
- if self.asvar:
- context[self.asvar] = value
- return ''
- else:
- return value
-
-
- class BlockTranslateNode(Node):
-
- def __init__(self, extra_context, singular, plural=None, countervar=None,
- counter=None, message_context=None, trimmed=False, asvar=None):
- self.extra_context = extra_context
- self.singular = singular
- self.plural = plural
- self.countervar = countervar
- self.counter = counter
- self.message_context = message_context
- self.trimmed = trimmed
- self.asvar = asvar
-
- def render_token_list(self, tokens):
- result = []
- vars = []
- for token in tokens:
- if token.token_type == TokenType.TEXT:
- result.append(token.contents.replace('%', '%%'))
- elif token.token_type == TokenType.VAR:
- result.append('%%(%s)s' % token.contents)
- vars.append(token.contents)
- msg = ''.join(result)
- if self.trimmed:
- msg = translation.trim_whitespace(msg)
- return msg, vars
-
- def render(self, context, nested=False):
- if self.message_context:
- message_context = self.message_context.resolve(context)
- else:
- message_context = None
- tmp_context = {}
- for var, val in self.extra_context.items():
- tmp_context[var] = val.resolve(context)
- # Update() works like a push(), so corresponding context.pop() is at
- # the end of function
- context.update(tmp_context)
- singular, vars = self.render_token_list(self.singular)
- if self.plural and self.countervar and self.counter:
- count = self.counter.resolve(context)
- context[self.countervar] = count
- plural, plural_vars = self.render_token_list(self.plural)
- if message_context:
- result = translation.npgettext(message_context, singular,
- plural, count)
- else:
- result = translation.ngettext(singular, plural, count)
- vars.extend(plural_vars)
- else:
- if message_context:
- result = translation.pgettext(message_context, singular)
- else:
- result = translation.gettext(singular)
- default_value = context.template.engine.string_if_invalid
-
- def render_value(key):
- if key in context:
- val = context[key]
- else:
- val = default_value % key if '%s' in default_value else default_value
- return render_value_in_context(val, context)
-
- data = {v: render_value(v) for v in vars}
- context.pop()
- try:
- result = result % data
- except (KeyError, ValueError):
- if nested:
- # Either string is malformed, or it's a bug
- raise TemplateSyntaxError(
- "'blocktrans' is unable to format string returned by gettext: %r using %r"
- % (result, data)
- )
- with translation.override(None):
- result = self.render(context, nested=True)
- if self.asvar:
- context[self.asvar] = result
- return ''
- else:
- return result
-
-
- class LanguageNode(Node):
- def __init__(self, nodelist, language):
- self.nodelist = nodelist
- self.language = language
-
- def render(self, context):
- with translation.override(self.language.resolve(context)):
- output = self.nodelist.render(context)
- return output
-
-
- @register.tag("get_available_languages")
- def do_get_available_languages(parser, token):
- """
- Store a list of available languages in the context.
-
- Usage::
-
- {% get_available_languages as languages %}
- {% for language in languages %}
- ...
- {% endfor %}
-
- This puts settings.LANGUAGES into the named variable.
- """
- # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
- args = token.contents.split()
- if len(args) != 3 or args[1] != 'as':
- raise TemplateSyntaxError("'get_available_languages' requires 'as variable' (got %r)" % args)
- return GetAvailableLanguagesNode(args[2])
-
-
- @register.tag("get_language_info")
- def do_get_language_info(parser, token):
- """
- Store the language information dictionary for the given language code in a
- context variable.
-
- Usage::
-
- {% get_language_info for LANGUAGE_CODE as l %}
- {{ l.code }}
- {{ l.name }}
- {{ l.name_translated }}
- {{ l.name_local }}
- {{ l.bidi|yesno:"bi-directional,uni-directional" }}
- """
- args = token.split_contents()
- if len(args) != 5 or args[1] != 'for' or args[3] != 'as':
- raise TemplateSyntaxError("'%s' requires 'for string as variable' (got %r)" % (args[0], args[1:]))
- return GetLanguageInfoNode(parser.compile_filter(args[2]), args[4])
-
-
- @register.tag("get_language_info_list")
- def do_get_language_info_list(parser, token):
- """
- Store a list of language information dictionaries for the given language
- codes in a context variable. The language codes can be specified either as
- a list of strings or a settings.LANGUAGES style list (or any sequence of
- sequences whose first items are language codes).
-
- Usage::
-
- {% get_language_info_list for LANGUAGES as langs %}
- {% for l in langs %}
- {{ l.code }}
- {{ l.name }}
- {{ l.name_translated }}
- {{ l.name_local }}
- {{ l.bidi|yesno:"bi-directional,uni-directional" }}
- {% endfor %}
- """
- args = token.split_contents()
- if len(args) != 5 or args[1] != 'for' or args[3] != 'as':
- raise TemplateSyntaxError("'%s' requires 'for sequence as variable' (got %r)" % (args[0], args[1:]))
- return GetLanguageInfoListNode(parser.compile_filter(args[2]), args[4])
-
-
- @register.filter
- def language_name(lang_code):
- return translation.get_language_info(lang_code)['name']
-
-
- @register.filter
- def language_name_translated(lang_code):
- english_name = translation.get_language_info(lang_code)['name']
- return translation.gettext(english_name)
-
-
- @register.filter
- def language_name_local(lang_code):
- return translation.get_language_info(lang_code)['name_local']
-
-
- @register.filter
- def language_bidi(lang_code):
- return translation.get_language_info(lang_code)['bidi']
-
-
- @register.tag("get_current_language")
- def do_get_current_language(parser, token):
- """
- Store the current language in the context.
-
- Usage::
-
- {% get_current_language as language %}
-
- This fetches the currently active language and puts its value into the
- ``language`` context variable.
- """
- # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
- args = token.contents.split()
- if len(args) != 3 or args[1] != 'as':
- raise TemplateSyntaxError("'get_current_language' requires 'as variable' (got %r)" % args)
- return GetCurrentLanguageNode(args[2])
-
-
- @register.tag("get_current_language_bidi")
- def do_get_current_language_bidi(parser, token):
- """
- Store the current language layout in the context.
-
- Usage::
-
- {% get_current_language_bidi as bidi %}
-
- This fetches the currently active language's layout and puts its value into
- the ``bidi`` context variable. True indicates right-to-left layout,
- otherwise left-to-right.
- """
- # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
- args = token.contents.split()
- if len(args) != 3 or args[1] != 'as':
- raise TemplateSyntaxError("'get_current_language_bidi' requires 'as variable' (got %r)" % args)
- return GetCurrentLanguageBidiNode(args[2])
-
-
- @register.tag("trans")
- def do_translate(parser, token):
- """
- Mark a string for translation and translate the string for the current
- language.
-
- Usage::
-
- {% trans "this is a test" %}
-
- This marks the string for translation so it will be pulled out by
- makemessages into the .po files and runs the string through the translation
- engine.
-
- There is a second form::
-
- {% trans "this is a test" noop %}
-
- This marks the string for translation, but returns the string unchanged.
- Use it when you need to store values into forms that should be translated
- later on.
-
- You can use variables instead of constant strings
- to translate stuff you marked somewhere else::
-
- {% trans variable %}
-
- This tries to translate the contents of the variable ``variable``. Make
- sure that the string in there is something that is in the .po file.
-
- It is possible to store the translated string into a variable::
-
- {% trans "this is a test" as var %}
- {{ var }}
-
- Contextual translations are also supported::
-
- {% trans "this is a test" context "greeting" %}
-
- This is equivalent to calling pgettext instead of (u)gettext.
- """
- bits = token.split_contents()
- if len(bits) < 2:
- raise TemplateSyntaxError("'%s' takes at least one argument" % bits[0])
- message_string = parser.compile_filter(bits[1])
- remaining = bits[2:]
-
- noop = False
- asvar = None
- message_context = None
- seen = set()
- invalid_context = {'as', 'noop'}
-
- while remaining:
- option = remaining.pop(0)
- if option in seen:
- raise TemplateSyntaxError(
- "The '%s' option was specified more than once." % option,
- )
- elif option == 'noop':
- noop = True
- elif option == 'context':
- try:
- value = remaining.pop(0)
- except IndexError:
- raise TemplateSyntaxError(
- "No argument provided to the '%s' tag for the context option." % bits[0]
- )
- if value in invalid_context:
- raise TemplateSyntaxError(
- "Invalid argument '%s' provided to the '%s' tag for the context option" % (value, bits[0]),
- )
- message_context = parser.compile_filter(value)
- elif option == 'as':
- try:
- value = remaining.pop(0)
- except IndexError:
- raise TemplateSyntaxError(
- "No argument provided to the '%s' tag for the as option." % bits[0]
- )
- asvar = value
- else:
- raise TemplateSyntaxError(
- "Unknown argument for '%s' tag: '%s'. The only options "
- "available are 'noop', 'context' \"xxx\", and 'as VAR'." % (
- bits[0], option,
- )
- )
- seen.add(option)
-
- return TranslateNode(message_string, noop, asvar, message_context)
-
-
- @register.tag("blocktrans")
- def do_block_translate(parser, token):
- """
- Translate a block of text with parameters.
-
- Usage::
-
- {% blocktrans with bar=foo|filter boo=baz|filter %}
- This is {{ bar }} and {{ boo }}.
- {% endblocktrans %}
-
- Additionally, this supports pluralization::
-
- {% blocktrans count count=var|length %}
- There is {{ count }} object.
- {% plural %}
- There are {{ count }} objects.
- {% endblocktrans %}
-
- This is much like ngettext, only in template syntax.
-
- The "var as value" legacy format is still supported::
-
- {% blocktrans with foo|filter as bar and baz|filter as boo %}
- {% blocktrans count var|length as count %}
-
- The translated string can be stored in a variable using `asvar`::
-
- {% blocktrans with bar=foo|filter boo=baz|filter asvar var %}
- This is {{ bar }} and {{ boo }}.
- {% endblocktrans %}
- {{ var }}
-
- Contextual translations are supported::
-
- {% blocktrans with bar=foo|filter context "greeting" %}
- This is {{ bar }}.
- {% endblocktrans %}
-
- This is equivalent to calling pgettext/npgettext instead of
- (u)gettext/(u)ngettext.
- """
- bits = token.split_contents()
-
- options = {}
- remaining_bits = bits[1:]
- asvar = None
- while remaining_bits:
- option = remaining_bits.pop(0)
- if option in options:
- raise TemplateSyntaxError('The %r option was specified more '
- 'than once.' % option)
- if option == 'with':
- value = token_kwargs(remaining_bits, parser, support_legacy=True)
- if not value:
- raise TemplateSyntaxError('"with" in %r tag needs at least '
- 'one keyword argument.' % bits[0])
- elif option == 'count':
- value = token_kwargs(remaining_bits, parser, support_legacy=True)
- if len(value) != 1:
- raise TemplateSyntaxError('"count" in %r tag expected exactly '
- 'one keyword argument.' % bits[0])
- elif option == "context":
- try:
- value = remaining_bits.pop(0)
- value = parser.compile_filter(value)
- except Exception:
- raise TemplateSyntaxError(
- '"context" in %r tag expected exactly one argument.' % bits[0]
- )
- elif option == "trimmed":
- value = True
- elif option == "asvar":
- try:
- value = remaining_bits.pop(0)
- except IndexError:
- raise TemplateSyntaxError(
- "No argument provided to the '%s' tag for the asvar option." % bits[0]
- )
- asvar = value
- else:
- raise TemplateSyntaxError('Unknown argument for %r tag: %r.' %
- (bits[0], option))
- options[option] = value
-
- if 'count' in options:
- countervar, counter = next(iter(options['count'].items()))
- else:
- countervar, counter = None, None
- if 'context' in options:
- message_context = options['context']
- else:
- message_context = None
- extra_context = options.get('with', {})
-
- trimmed = options.get("trimmed", False)
-
- singular = []
- plural = []
- while parser.tokens:
- token = parser.next_token()
- if token.token_type in (TokenType.VAR, TokenType.TEXT):
- singular.append(token)
- else:
- break
- if countervar and counter:
- if token.contents.strip() != 'plural':
- raise TemplateSyntaxError("'blocktrans' doesn't allow other block tags inside it")
- while parser.tokens:
- token = parser.next_token()
- if token.token_type in (TokenType.VAR, TokenType.TEXT):
- plural.append(token)
- else:
- break
- if token.contents.strip() != 'endblocktrans':
- raise TemplateSyntaxError("'blocktrans' doesn't allow other block tags (seen %r) inside it" % token.contents)
-
- return BlockTranslateNode(extra_context, singular, plural, countervar,
- counter, message_context, trimmed=trimmed,
- asvar=asvar)
-
-
- @register.tag
- def language(parser, token):
- """
- Enable the given language just for this block.
-
- Usage::
-
- {% language "de" %}
- This is {{ bar }} and {{ boo }}.
- {% endlanguage %}
- """
- bits = token.split_contents()
- if len(bits) != 2:
- raise TemplateSyntaxError("'%s' takes one argument (language)" % bits[0])
- language = parser.compile_filter(bits[1])
- nodelist = parser.parse(('endlanguage',))
- parser.delete_first_token()
- return LanguageNode(nodelist, language)
|