Commit 26698bc8 authored by Julien Phalip's avatar Julien Phalip
Browse files

Fixed #14806 -- Added support for contextual translations to the `trans` and...

Fixed #14806 -- Added support for contextual translations to the `trans` and `blocktrans` template tags. Thanks to jtiai for the report.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17015 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent 358e5a80
Loading
Loading
Loading
Loading
+6 −2
Original line number Diff line number Diff line
@@ -12,7 +12,7 @@ from django.utils.itercompat import is_iterable
from django.utils.text import (smart_split, unescape_string_literal,
    get_text_list)
from django.utils.encoding import smart_unicode, force_unicode, smart_str
from django.utils.translation import ugettext_lazy
from django.utils.translation import ugettext_lazy, pgettext_lazy
from django.utils.safestring import (SafeData, EscapeData, mark_safe,
    mark_for_escaping)
from django.utils.formats import localize
@@ -675,6 +675,7 @@ class Variable(object):
        self.literal = None
        self.lookups = None
        self.translate = False
        self.message_context = None

        try:
            # First try to treat this variable as a number.
@@ -722,6 +723,9 @@ class Variable(object):
            # We're dealing with a literal, so it's already been "resolved"
            value = self.literal
        if self.translate:
            if self.message_context:
                return pgettext_lazy(self.message_context, value)
            else:
                return ugettext_lazy(value)
        return value

+65 −12
Original line number Diff line number Diff line
@@ -70,15 +70,21 @@ class GetCurrentLanguageBidiNode(Node):


class TranslateNode(Node):
    def __init__(self, filter_expression, noop, asvar=None):
    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, basestring):
            self.filter_expression.var = Variable(u"'%s'" % self.filter_expression.var)
            self.filter_expression.var = Variable(u"'%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)
        if self.asvar:
@@ -90,12 +96,13 @@ class TranslateNode(Node):

class BlockTranslateNode(Node):
    def __init__(self, extra_context, singular, plural=None, countervar=None,
            counter=None):
            counter=None, message_context=None):
        self.extra_context = extra_context
        self.singular = singular
        self.plural = plural
        self.countervar = countervar
        self.counter = counter
        self.message_context = message_context

    def render_token_list(self, tokens):
        result = []
@@ -109,6 +116,10 @@ class BlockTranslateNode(Node):
        return ''.join(result), vars

    def render(self, context):
        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)
@@ -123,8 +134,15 @@ class BlockTranslateNode(Node):
            context[self.countervar] = count
            plural, plural_vars = self.render_token_list(self.plural)
            plural = re.sub(u'%(?!\()', u'%%', plural)
            if message_context:
                result = translation.npgettext(message_context, singular,
                                               plural, count)
            else:
                result = translation.ungettext(singular, plural, count)
            vars.extend(plural_vars)
        else:
            if message_context:
                result = translation.pgettext(message_context, singular)
            else:
                result = translation.ugettext(singular)
        data = dict([(v, _render_value_in_context(context.get(v, ''), context)) for v in vars])
@@ -290,6 +308,17 @@ def do_translate(parser, token):
    This will just try 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.
    """
    class TranslateParser(TokenParser):
        def top(self):
@@ -301,7 +330,6 @@ def do_translate(parser, token):
            # backwards compatibility with existing uses of ``trans``
            # where single quote use is supported.
            if value[0] == "'":
                pos = None
                m = re.match("^'([^']+)'(\|.*$)", value)
                if m:
                    value = '"%s"%s' % (m.group(1).replace('"','\\"'), m.group(2))
@@ -310,19 +338,24 @@ def do_translate(parser, token):

            noop = False
            asvar = None
            message_context = None

            while self.more():
                tag = self.tag()
                if tag == 'noop':
                    noop = True
                elif tag == 'context':
                    message_context = parser.compile_filter(self.value())
                elif tag == 'as':
                    asvar = self.tag()
                else:
                    raise TemplateSyntaxError(
                        "only options for 'trans' are 'noop' and 'as VAR.")
            return (value, noop, asvar)
    value, noop, asvar = TranslateParser(token.contents).top()
    return TranslateNode(parser.compile_filter(value), noop, asvar)
                        "Only options for 'trans' are 'noop', " \
                        "'context \"xxx\"', and 'as VAR'.")
            return value, noop, asvar, message_context
    value, noop, asvar, message_context = TranslateParser(token.contents).top()
    return TranslateNode(parser.compile_filter(value), noop, asvar,
                         message_context)

@register.tag("blocktrans")
def do_block_translate(parser, token):
@@ -349,6 +382,15 @@ def do_block_translate(parser, token):

        {% blocktrans with foo|filter as bar and baz|filter as boo %}
        {% blocktrans count var|length as count %}

    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()

@@ -369,6 +411,13 @@ def do_block_translate(parser, token):
            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])
        else:
            raise TemplateSyntaxError('Unknown argument for %r tag: %r.' %
                                      (bits[0], option))
@@ -378,6 +427,10 @@ def do_block_translate(parser, token):
        countervar, counter = options['count'].items()[0]
    else:
        countervar, counter = None, None
    if 'context' in options:
        message_context = options['context']
    else:
        message_context = None
    extra_context = options.get('with', {})

    singular = []
@@ -401,7 +454,7 @@ def do_block_translate(parser, token):
        raise TemplateSyntaxError("'blocktrans' doesn't allow other block tags (seen %r) inside it" % token.contents)

    return BlockTranslateNode(extra_context, singular, plural, countervar,
            counter)
            counter, message_context)

@register.tag
def language(parser, token):
+38 −7
Original line number Diff line number Diff line
@@ -433,12 +433,14 @@ def blankout(src, char):
    """
    return dot_re.sub(char, src)

inline_re = re.compile(r"""^\s*trans\s+((?:".*?")|(?:'.*?'))\s*""")
block_re = re.compile(r"""^\s*blocktrans(?:\s+|$)""")
context_re = re.compile(r"""^\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?'))\s*""")
inline_re = re.compile(r"""^\s*trans\s+((?:"[^"]*?")|(?:'[^']*?'))(\s+.*context\s+(?:"[^"]*?")|(?:'[^']*?'))?\s*""")
block_re = re.compile(r"""^\s*blocktrans(\s+.*context\s+(?:"[^"]*?")|(?:'[^']*?'))?(?:\s+|$)""")
endblock_re = re.compile(r"""^\s*endblocktrans$""")
plural_re = re.compile(r"""^\s*plural$""")
constant_re = re.compile(r"""_\(((?:".*?")|(?:'.*?'))\)""")


def templatize(src, origin=None):
    """
    Turns a Django template into something that is understood by xgettext. It
@@ -448,6 +450,7 @@ def templatize(src, origin=None):
    from django.template import (Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK,
            TOKEN_COMMENT, TRANSLATOR_COMMENT_MARK)
    out = StringIO()
    message_context = None
    intrans = False
    inplural = False
    singular = []
@@ -477,15 +480,22 @@ def templatize(src, origin=None):
                pluralmatch = plural_re.match(t.contents)
                if endbmatch:
                    if inplural:
                        if message_context:
                            out.write(' npgettext(%r, %r, %r,count) ' % (message_context, ''.join(singular), ''.join(plural)))
                        else:
                            out.write(' ngettext(%r, %r, count) ' % (''.join(singular), ''.join(plural)))
                        for part in singular:
                            out.write(blankout(part, 'S'))
                        for part in plural:
                            out.write(blankout(part, 'P'))
                    else:
                        if message_context:
                            out.write(' pgettext(%r, %r) ' % (message_context, ''.join(singular)))
                        else:
                            out.write(' gettext(%r) ' % ''.join(singular))
                        for part in singular:
                            out.write(blankout(part, 'S'))
                    message_context = None
                    intrans = False
                    inplural = False
                    singular = []
@@ -515,12 +525,33 @@ def templatize(src, origin=None):
                cmatches = constant_re.findall(t.contents)
                if imatch:
                    g = imatch.group(1)
                    if g[0] == '"': g = g.strip('"')
                    elif g[0] == "'": g = g.strip("'")
                    if g[0] == '"':
                        g = g.strip('"')
                    elif g[0] == "'":
                        g = g.strip("'")
                    if imatch.group(2):
                        # A context is provided
                        context_match = context_re.match(imatch.group(2))
                        message_context = context_match.group(1)
                        if message_context[0] == '"':
                            message_context = message_context.strip('"')
                        elif message_context[0] == "'":
                            message_context = message_context.strip("'")
                        out.write(' pgettext(%r, %r) ' % (message_context, g))
                        message_context = None
                    else:
                        out.write(' gettext(%r) ' % g)
                elif bmatch:
                    for fmatch in constant_re.findall(t.contents):
                        out.write(' _(%s) ' % fmatch)
                    if bmatch.group(1):
                        # A context is provided
                        context_match = context_re.match(bmatch.group(1))
                        message_context = context_match.group(1)
                        if message_context[0] == '"':
                            message_context = message_context.strip('"')
                        elif message_context[0] == "'":
                            message_context = message_context.strip("'")
                    intrans = True
                    inplural = False
                    singular = []
+8 −0
Original line number Diff line number Diff line
@@ -191,6 +191,14 @@ Additionally, it's now possible to define translatable URL patterns using
:ref:`url-internationalization` for more information about the language prefix
and how to internationalize URL patterns.

Contextual translation support for ``{% trans %}`` and ``{% blocktrans %}``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Django 1.3 introduced :ref:`contextual translation<contextual-markers>` support
in Python files via the ``pgettext`` function. This is now also supported by
the :ttag:`trans` and :ttag:`blocktrans` template tags using the new
``context`` keyword.

Customizable ``SingleObjectMixin`` URLConf kwargs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+24 −1
Original line number Diff line number Diff line
@@ -266,6 +266,11 @@ will appear in the .po file as:
    msgid "May"
    msgstr ""

.. versionadded:: 1.4

Contextual markers are also supported by the :ttag:`trans` and
:ttag:`blocktrans` template tags.

.. _lazy-translations:

Lazy translation
@@ -453,7 +458,7 @@ It's not possible to mix a template variable inside a string within ``{% trans
%}``. If your translations require strings with variables (placeholders), use
``{% blocktrans %}`` instead.

.. versionchanged:: 1.4
.. versionadded:: 1.4

If you'd like to retrieve a translated string without displaying it, you can
use the following syntax::
@@ -479,6 +484,15 @@ or should be used as arguments for other template tags or filters::
    {% endfor %}
    </p>

.. versionadded:: 1.4

``{% trans %}`` also supports :ref:`contextual markers<contextual-markers>`
using the ``context`` keyword:

.. code-block:: html+django

    {% trans "May" context "month name" %}

.. templatetag:: blocktrans

``blocktrans`` template tag
@@ -560,6 +574,15 @@ be retrieved (and stored) beforehand::
    This is a URL: {{ the_url }}
    {% endblocktrans %}

.. versionadded:: 1.4

``{% blocktrans %}`` also supports :ref:`contextual
markers<contextual-markers>` using the ``context`` keyword:

.. code-block:: html+django

    {% blocktrans with name=user.username context "greeting" %}Hi {{ name }}{% endblocktrans %}

.. _template-translation-vars:

Other tags
Loading