diff options
| author | Julien Phalip <jphalip@gmail.com> | 2011-10-19 04:59:47 +0000 |
|---|---|---|
| committer | Julien Phalip <jphalip@gmail.com> | 2011-10-19 04:59:47 +0000 |
| commit | 26698bc85123935d5926a87a45cae25efe683639 (patch) | |
| tree | 6932998940436b4362a8a6c8d167aca1e647d5cb /django | |
| parent | 358e5a8031be14efe33dc62e821408c2a6a8e85a (diff) | |
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
Diffstat (limited to 'django')
| -rw-r--r-- | django/template/base.py | 8 | ||||
| -rw-r--r-- | django/templatetags/i18n.py | 77 | ||||
| -rw-r--r-- | django/utils/translation/trans_real.py | 45 |
3 files changed, 109 insertions, 21 deletions
diff --git a/django/template/base.py b/django/template/base.py index 3e0c14aa03..0fafb2d182 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -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,7 +723,10 @@ class Variable(object): # We're dealing with a literal, so it's already been "resolved" value = self.literal if self.translate: - return ugettext_lazy(value) + if self.message_context: + return pgettext_lazy(self.message_context, value) + else: + return ugettext_lazy(value) return value def __repr__(self): diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py index 669cdc911a..dc3d93e90f 100644 --- a/django/templatetags/i18n.py +++ b/django/templatetags/i18n.py @@ -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,10 +134,17 @@ class BlockTranslateNode(Node): context[self.countervar] = count plural, plural_vars = self.render_token_list(self.plural) plural = re.sub(u'%(?!\()', u'%%', plural) - result = translation.ungettext(singular, plural, count) + if message_context: + result = translation.npgettext(message_context, singular, + plural, count) + else: + result = translation.ungettext(singular, plural, count) vars.extend(plural_vars) else: - result = translation.ugettext(singular) + 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]) context.pop() try: @@ -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,7 +427,11 @@ def do_block_translate(parser, token): countervar, counter = options['count'].items()[0] else: countervar, counter = None, None - extra_context = options.get('with', {}) + if 'context' in options: + message_context = options['context'] + else: + message_context = None + extra_context = options.get('with', {}) singular = [] plural = [] @@ -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): diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index 71765e74e5..f43cfd9705 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -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: - out.write(' ngettext(%r,%r,count) ' % (''.join(singular), ''.join(plural))) + 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: - out.write(' gettext(%r) ' % ''.join(singular)) + 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("'") - out.write(' gettext(%r) ' % g) + 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 = [] |
