From 26698bc85123935d5926a87a45cae25efe683639 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Wed, 19 Oct 2011 04:59:47 +0000 Subject: [PATCH] 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 --- django/template/base.py | 8 +- django/templatetags/i18n.py | 77 +++++++++-- django/utils/translation/trans_real.py | 45 ++++++- docs/releases/1.4.txt | 8 ++ docs/topics/i18n/internationalization.txt | 25 +++- .../i18n/commands/extraction.py | 29 +++++ .../i18n/commands/templates/test.html | 9 ++ .../other/locale/de/LC_MESSAGES/django.mo | Bin 922 -> 1449 bytes .../other/locale/de/LC_MESSAGES/django.po | 24 ++++ tests/regressiontests/i18n/tests.py | 121 ++++++++++++++++++ 10 files changed, 324 insertions(+), 22 deletions(-) diff --git a/django/template/base.py b/django/template/base.py index 3e0c14aa03f..0fafb2d1824 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 669cdc911a2..dc3d93e90f2 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 71765e74e5e..f43cfd9705a 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 = [] diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index 3e364fbe237..00acc65922f 100644 --- a/docs/releases/1.4.txt +++ b/docs/releases/1.4.txt @@ -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` 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt index b06c9bdfdb7..c8611acff8e 100644 --- a/docs/topics/i18n/internationalization.txt +++ b/docs/topics/i18n/internationalization.txt @@ -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 %}

+.. versionadded:: 1.4 + +``{% trans %}`` also supports :ref:`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` using the ``context`` keyword: + +.. code-block:: html+django + + {% blocktrans with name=user.username context "greeting" %}Hi {{ name }}{% endblocktrans %} + .. _template-translation-vars: Other tags diff --git a/tests/regressiontests/i18n/commands/extraction.py b/tests/regressiontests/i18n/commands/extraction.py index 7d7cdf7485b..c49577221e5 100644 --- a/tests/regressiontests/i18n/commands/extraction.py +++ b/tests/regressiontests/i18n/commands/extraction.py @@ -94,6 +94,35 @@ class BasicExtractorTests(ExtractorTests): os.remove('./templates/template_with_error.html') os.remove('./templates/template_with_error.html.py') # Waiting for #8536 to be fixed + def test_template_message_context_extractor(self): + """ + Ensure that message contexts are correctly extracted for the + {% trans %} and {% blocktrans %} template tags. + Refs #14806. + """ + os.chdir(self.test_dir) + management.call_command('makemessages', locale=LOCALE, verbosity=0) + self.assertTrue(os.path.exists(self.PO_FILE)) + po_contents = open(self.PO_FILE, 'r').read() + # {% trans %} + self.assertTrue('msgctxt "Special trans context #1"' in po_contents) + self.assertTrue("Translatable literal #7a" in po_contents) + self.assertTrue('msgctxt "Special trans context #2"' in po_contents) + self.assertTrue("Translatable literal #7b" in po_contents) + self.assertTrue('msgctxt "Special trans context #3"' in po_contents) + self.assertTrue("Translatable literal #7c" in po_contents) + + # {% blocktrans %} + self.assertTrue('msgctxt "Special blocktrans context #1"' in po_contents) + self.assertTrue("Translatable literal #8a" in po_contents) + self.assertTrue('msgctxt "Special blocktrans context #2"' in po_contents) + self.assertTrue("Translatable literal #8b-singular" in po_contents) + self.assertTrue("Translatable literal #8b-plural" in po_contents) + self.assertTrue('msgctxt "Special blocktrans context #3"' in po_contents) + self.assertTrue("Translatable literal #8c-singular" in po_contents) + self.assertTrue("Translatable literal #8c-plural" in po_contents) + self.assertTrue('msgctxt "Special blocktrans context #4"' in po_contents) + self.assertTrue("Translatable literal #8d" in po_contents) class JavascriptExtractorTests(ExtractorTests): diff --git a/tests/regressiontests/i18n/commands/templates/test.html b/tests/regressiontests/i18n/commands/templates/test.html index b5d705c1327..24fc7086210 100644 --- a/tests/regressiontests/i18n/commands/templates/test.html +++ b/tests/regressiontests/i18n/commands/templates/test.html @@ -57,3 +57,12 @@ continued here.{% endcomment %} {% comment %} Translators: Two-line translator comment #5 -- with non ASCII characters: áéíóúö continued here.{% endcomment %} {% trans "Translatable literal #6b" %} + +{% trans "Translatable literal #7a" context "Special trans context #1" %} +{% trans "Translatable literal #7b" as var context "Special trans context #2" %} +{% trans "Translatable literal #7c" context "Special trans context #3" as var %} + +{% blocktrans context "Special blocktrans context #1" %}Translatable literal #8a{% endblocktrans %} +{% blocktrans count 2 context "Special blocktrans context #2" %}Translatable literal #8b-singular{% plural %}Translatable literal #8b-plural{% endblocktrans %} +{% blocktrans context "Special blocktrans context #3" count 2 %}Translatable literal #8c-singular{% plural %}Translatable literal #8c-plural{% endblocktrans %} +{% blocktrans with a=1 context "Special blocktrans context #4" %}Translatable literal #8d {{ a }}{% endblocktrans %} \ No newline at end of file diff --git a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo index a1a93c83296f0fb97d500466a8df30338fdb137e..f825e3918b7fd74b7af37e55e704b260101367b7 100644 GIT binary patch literal 1449 zcmbu9&2AGh5XTLKuYeFA2SA|86^fJyxy>e^R@(`(Iz5y?RpTHULD_94wpCsfK_!O*x``|h7J-7|cN6^Nf zK(L!&0#1N0!E4|<@DBI^JOzFO?}FdKW$^N8LT-Rt;C)bn*dlL1gePZU_B=c6cRTDo zhT{SzXJIzB9dOIuA`c=pLJGycP+cKSNf!!KiM|pVD@_YUnZ_kelg_T_n&av;S>{Ge zn=lr{>j?zZX=La=w*kjaBA!)rlQ_mT@>t|r z{D33|KEvIA2s7~0DKes3aMkVQvNJnE;k3c`LF%|T-r@J}nhgE?!%LFFaE87Aio7RO zC%ax-CHC>zs?Qz^rNczJ)ZgVYNT?Co==_H0C(5vv4nm(jNCVBh#HIQ6y2t8Da03r! ze-M{e%1)V;E6lk^of)@Uy^D1jLbf6HLK}Icb6DA76^B+Fw>rbhRp^>r8|Eo4b;M1g zT>6Mh8EV=Tp)VpjkDRbvjI!laD0h+t_{bpTfnc8CG4d94-WQ8eIF+biA>UeUHAa_m zrpoy`ep(a*^A7sR*NEq4x*zdS&e3jsjN>2dU9)yy+DNjGh+vSDtx>(6>0D2@oUu!X*n^)<);!f1|-k?-H5{%jndY3iBu>umm9mdQVo-nqB;X7tcj&+a;*bk{-&L^9xsH>#iSxLJ&8#^d!UjzL z(-O(zT3Dor8#qmycx+g*qGa$Eo54p+?=R+#8J|R{rpE#ES>)h`_Q~l?2cwc!Q{Qf@ f^`URM*PG>7<+d72)hl)V#c~p;;D7H+OKB}{^SvR9 diff --git a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po index 7226ad19433..a471d3814b1 100644 --- a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po +++ b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po @@ -52,3 +52,27 @@ msgid "%(percent)s%% represents %(num)s object" msgid_plural "%(percent)s%% represents %(num)s objects" msgstr[0] "%(percent)s%% stellt %(num)s Objekt dar" msgstr[1] "%(percent)s%% stellt %(num)s Objekte dar" + +#: models.py:17 +msgctxt "super search" +msgid "%(number)s super result" +msgid_plural "%(number)s super results" +msgstr[0] "%(number)s Super-Ergebnis" +msgstr[1] "%(number)s Super-Ergebnisse" + +#: models.py:19 +msgctxt "other super search" +msgid "%(number)s super result" +msgid_plural "%(number)s super results" +msgstr[0] "%(number)s anderen Super-Ergebnis" +msgstr[1] "%(number)s andere Super-Ergebnisse" + +#: models.py:21 +msgctxt "comment count" +msgid "There are %(num_comments)s comments" +msgstr "Es gibt %(num_comments)s Kommentare" + +#: models.py:23 +msgctxt "other comment count" +msgid "There are %(num_comments)s comments" +msgstr "Andere: Es gibt %(num_comments)s Kommentare" \ No newline at end of file diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py index 17a96ed4336..bfed0ec882d 100644 --- a/tests/regressiontests/i18n/tests.py +++ b/tests/regressiontests/i18n/tests.py @@ -9,6 +9,7 @@ from threading import local from django.conf import settings from django.template import Template, Context +from django.template.base import TemplateSyntaxError from django.test import TestCase, RequestFactory from django.test.utils import override_settings from django.utils import translation @@ -96,6 +97,126 @@ class TranslationTests(TestCase): self.assertEqual(pgettext("verb", "May"), u"Kann") self.assertEqual(npgettext("search", "%d result", "%d results", 4) % 4, u"4 Resultate") + def test_template_tags_pgettext(self): + """ + Ensure that message contexts are taken into account the {% trans %} and + {% blocktrans %} template tags. + Refs #14806. + """ + # Reset translation catalog to include other/locale/de + extended_locale_paths = settings.LOCALE_PATHS + ( + os.path.join(here, 'other', 'locale'), + ) + with self.settings(LOCALE_PATHS=extended_locale_paths): + from django.utils.translation import trans_real + trans_real._active = local() + trans_real._translations = {} + with translation.override('de'): + + # {% trans %} ----------------------------------- + + # Inexisting context... + t = Template('{% load i18n %}{% trans "May" context "unexisting" %}') + rendered = t.render(Context()) + self.assertEqual(rendered, 'May') + + # Existing context... + # Using a literal + t = Template('{% load i18n %}{% trans "May" context "month name" %}') + rendered = t.render(Context()) + self.assertEqual(rendered, 'Mai') + t = Template('{% load i18n %}{% trans "May" context "verb" %}') + rendered = t.render(Context()) + self.assertEqual(rendered, 'Kann') + + # Using a variable + t = Template('{% load i18n %}{% trans "May" context message_context %}') + rendered = t.render(Context({'message_context': 'month name'})) + self.assertEqual(rendered, 'Mai') + t = Template('{% load i18n %}{% trans "May" context message_context %}') + rendered = t.render(Context({'message_context': 'verb'})) + self.assertEqual(rendered, 'Kann') + + # Using a filter + t = Template('{% load i18n %}{% trans "May" context message_context|lower %}') + rendered = t.render(Context({'message_context': 'MONTH NAME'})) + self.assertEqual(rendered, 'Mai') + t = Template('{% load i18n %}{% trans "May" context message_context|lower %}') + rendered = t.render(Context({'message_context': 'VERB'})) + self.assertEqual(rendered, 'Kann') + + # Using 'as' + t = Template('{% load i18n %}{% trans "May" context "month name" as var %}Value: {{ var }}') + rendered = t.render(Context()) + self.assertEqual(rendered, 'Value: Mai') + t = Template('{% load i18n %}{% trans "May" as var context "verb" %}Value: {{ var }}') + rendered = t.render(Context()) + self.assertEqual(rendered, 'Value: Kann') + + # Mis-uses + self.assertRaises(TemplateSyntaxError, Template, '{% load i18n %}{% trans "May" context as var %}{{ var }}') + self.assertRaises(TemplateSyntaxError, Template, '{% load i18n %}{% trans "May" as var context %}{{ var }}') + + # {% blocktrans %} ------------------------------ + + # Inexisting context... + t = Template('{% load i18n %}{% blocktrans context "unexisting" %}May{% endblocktrans %}') + rendered = t.render(Context()) + self.assertEqual(rendered, 'May') + + # Existing context... + # Using a literal + t = Template('{% load i18n %}{% blocktrans context "month name" %}May{% endblocktrans %}') + rendered = t.render(Context()) + self.assertEqual(rendered, 'Mai') + t = Template('{% load i18n %}{% blocktrans context "verb" %}May{% endblocktrans %}') + rendered = t.render(Context()) + self.assertEqual(rendered, 'Kann') + + # Using a variable + t = Template('{% load i18n %}{% blocktrans context message_context %}May{% endblocktrans %}') + rendered = t.render(Context({'message_context': 'month name'})) + self.assertEqual(rendered, 'Mai') + t = Template('{% load i18n %}{% blocktrans context message_context %}May{% endblocktrans %}') + rendered = t.render(Context({'message_context': 'verb'})) + self.assertEqual(rendered, 'Kann') + + # Using a filter + t = Template('{% load i18n %}{% blocktrans context message_context|lower %}May{% endblocktrans %}') + rendered = t.render(Context({'message_context': 'MONTH NAME'})) + self.assertEqual(rendered, 'Mai') + t = Template('{% load i18n %}{% blocktrans context message_context|lower %}May{% endblocktrans %}') + rendered = t.render(Context({'message_context': 'VERB'})) + self.assertEqual(rendered, 'Kann') + + # Using 'count' + t = Template('{% load i18n %}{% blocktrans count number=1 context "super search" %}{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}') + rendered = t.render(Context()) + self.assertEqual(rendered, '1 Super-Ergebnis') + t = Template('{% load i18n %}{% blocktrans count number=2 context "super search" %}{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}') + rendered = t.render(Context()) + self.assertEqual(rendered, '2 Super-Ergebnisse') + t = Template('{% load i18n %}{% blocktrans context "other super search" count number=1 %}{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}') + rendered = t.render(Context()) + self.assertEqual(rendered, '1 anderen Super-Ergebnis') + t = Template('{% load i18n %}{% blocktrans context "other super search" count number=2 %}{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}') + rendered = t.render(Context()) + self.assertEqual(rendered, '2 andere Super-Ergebnisse') + + # Using 'with' + t = Template('{% load i18n %}{% blocktrans with num_comments=5 context "comment count" %}There are {{ num_comments }} comments{% endblocktrans %}') + rendered = t.render(Context()) + self.assertEqual(rendered, 'Es gibt 5 Kommentare') + t = Template('{% load i18n %}{% blocktrans with num_comments=5 context "other comment count" %}There are {{ num_comments }} comments{% endblocktrans %}') + rendered = t.render(Context()) + self.assertEqual(rendered, 'Andere: Es gibt 5 Kommentare') + + # Mis-uses + self.assertRaises(TemplateSyntaxError, Template, '{% load i18n %}{% blocktrans context with month="May" %}{{ month }}{% endblocktrans %}') + self.assertRaises(TemplateSyntaxError, Template, '{% load i18n %}{% blocktrans context %}{% endblocktrans %}') + self.assertRaises(TemplateSyntaxError, Template, '{% load i18n %}{% blocktrans count number=2 context %}{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}') + + def test_string_concat(self): """ unicode(string_concat(...)) should not raise a TypeError - #4796