close Warning: HTML preview using PatchRenderer failed (ValueError: need more than 1 value to unpack)

Ticket #14806: 14806.template-tags-pgettext.4.diff

File 14806.template-tags-pgettext.4.diff, 29.8 KB (added by julien, 4 years ago)
Line 
1diff --git a/django/template/base.py b/django/template/base.py
2index 8001e93..3a2b740 100644
3--- a/django/template/base.py
4+++ b/django/template/base.py
5@@ -10,7 +10,7 @@ from django.utils.itercompat import is_iterable
6 from django.utils.text import (smart_split, unescape_string_literal,
7     get_text_list)
8 from django.utils.encoding import smart_unicode, force_unicode, smart_str
9-from django.utils.translation import ugettext_lazy
10+from django.utils.translation import ugettext_lazy, pgettext_lazy
11 from django.utils.safestring import (SafeData, EscapeData, mark_safe,
12     mark_for_escaping)
13 from django.utils.formats import localize
14@@ -673,6 +673,7 @@ class Variable(object):
15         self.literal = None
16         self.lookups = None
17         self.translate = False
18+        self.message_context = None
19 
20         try:
21             # First try to treat this variable as a number.
22@@ -720,7 +721,10 @@ class Variable(object):
23             # We're dealing with a literal, so it's already been "resolved"
24             value = self.literal
25         if self.translate:
26-            return ugettext_lazy(value)
27+            if self.message_context:
28+                return pgettext_lazy(self.message_context, value)
29+            else:
30+                return ugettext_lazy(value)
31         return value
32 
33     def __repr__(self):
34diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py
35index 669cdc9..dc3d93e 100644
36--- a/django/templatetags/i18n.py
37+++ b/django/templatetags/i18n.py
38@@ -70,15 +70,21 @@ class GetCurrentLanguageBidiNode(Node):
39 
40 
41 class TranslateNode(Node):
42-    def __init__(self, filter_expression, noop, asvar=None):
43+    def __init__(self, filter_expression, noop, asvar=None,
44+                 message_context=None):
45         self.noop = noop
46         self.asvar = asvar
47+        self.message_context = message_context
48         self.filter_expression = filter_expression
49         if isinstance(self.filter_expression.var, basestring):
50-            self.filter_expression.var = Variable(u"'%s'" % self.filter_expression.var)
51+            self.filter_expression.var = Variable(u"'%s'" %
52+                                                  self.filter_expression.var)
53 
54     def render(self, context):
55         self.filter_expression.var.translate = not self.noop
56+        if self.message_context:
57+            self.filter_expression.var.message_context = (
58+                self.message_context.resolve(context))
59         output = self.filter_expression.resolve(context)
60         value = _render_value_in_context(output, context)
61         if self.asvar:
62@@ -90,12 +96,13 @@ class TranslateNode(Node):
63 
64 class BlockTranslateNode(Node):
65     def __init__(self, extra_context, singular, plural=None, countervar=None,
66-            counter=None):
67+            counter=None, message_context=None):
68         self.extra_context = extra_context
69         self.singular = singular
70         self.plural = plural
71         self.countervar = countervar
72         self.counter = counter
73+        self.message_context = message_context
74 
75     def render_token_list(self, tokens):
76         result = []
77@@ -109,6 +116,10 @@ class BlockTranslateNode(Node):
78         return ''.join(result), vars
79 
80     def render(self, context):
81+        if self.message_context:
82+            message_context = self.message_context.resolve(context)
83+        else:
84+            message_context = None
85         tmp_context = {}
86         for var, val in self.extra_context.items():
87             tmp_context[var] = val.resolve(context)
88@@ -123,10 +134,17 @@ class BlockTranslateNode(Node):
89             context[self.countervar] = count
90             plural, plural_vars = self.render_token_list(self.plural)
91             plural = re.sub(u'%(?!\()', u'%%', plural)
92-            result = translation.ungettext(singular, plural, count)
93+            if message_context:
94+                result = translation.npgettext(message_context, singular,
95+                                               plural, count)
96+            else:
97+                result = translation.ungettext(singular, plural, count)
98             vars.extend(plural_vars)
99         else:
100-            result = translation.ugettext(singular)
101+            if message_context:
102+                result = translation.pgettext(message_context, singular)
103+            else:
104+                result = translation.ugettext(singular)
105         data = dict([(v, _render_value_in_context(context.get(v, ''), context)) for v in vars])
106         context.pop()
107         try:
108@@ -290,6 +308,17 @@ def do_translate(parser, token):
109     This will just try to translate the contents of
110     the variable ``variable``. Make sure that the string
111     in there is something that is in the .po file.
112+
113+    It is possible to store the translated string into a variable::
114+
115+        {% trans "this is a test" as var %}
116+        {{ var }}
117+
118+    Contextual translations are also supported::
119+
120+        {% trans "this is a test" context "greeting" %}
121+
122+    This is equivalent to calling pgettext instead of (u)gettext.
123     """
124     class TranslateParser(TokenParser):
125         def top(self):
126@@ -301,7 +330,6 @@ def do_translate(parser, token):
127             # backwards compatibility with existing uses of ``trans``
128             # where single quote use is supported.
129             if value[0] == "'":
130-                pos = None
131                 m = re.match("^'([^']+)'(\|.*$)", value)
132                 if m:
133                     value = '"%s"%s' % (m.group(1).replace('"','\\"'), m.group(2))
134@@ -310,19 +338,24 @@ def do_translate(parser, token):
135 
136             noop = False
137             asvar = None
138+            message_context = None
139 
140             while self.more():
141                 tag = self.tag()
142                 if tag == 'noop':
143                     noop = True
144+                elif tag == 'context':
145+                    message_context = parser.compile_filter(self.value())
146                 elif tag == 'as':
147                     asvar = self.tag()
148                 else:
149                     raise TemplateSyntaxError(
150-                        "only options for 'trans' are 'noop' and 'as VAR.")
151-            return (value, noop, asvar)
152-    value, noop, asvar = TranslateParser(token.contents).top()
153-    return TranslateNode(parser.compile_filter(value), noop, asvar)
154+                        "Only options for 'trans' are 'noop', " \
155+                        "'context \"xxx\"', and 'as VAR'.")
156+            return value, noop, asvar, message_context
157+    value, noop, asvar, message_context = TranslateParser(token.contents).top()
158+    return TranslateNode(parser.compile_filter(value), noop, asvar,
159+                         message_context)
160 
161 @register.tag("blocktrans")
162 def do_block_translate(parser, token):
163@@ -349,6 +382,15 @@ def do_block_translate(parser, token):
164 
165         {% blocktrans with foo|filter as bar and baz|filter as boo %}
166         {% blocktrans count var|length as count %}
167+
168+    Contextual translations are supported::
169+
170+        {% blocktrans with bar=foo|filter context "greeting" %}
171+            This is {{ bar }}.
172+        {% endblocktrans %}
173+
174+    This is equivalent to calling pgettext/npgettext instead of
175+    (u)gettext/(u)ngettext.
176     """
177     bits = token.split_contents()
178 
179@@ -369,6 +411,13 @@ def do_block_translate(parser, token):
180             if len(value) != 1:
181                 raise TemplateSyntaxError('"count" in %r tag expected exactly '
182                                           'one keyword argument.' % bits[0])
183+        elif option == "context":
184+            try:
185+                value = remaining_bits.pop(0)
186+                value = parser.compile_filter(value)
187+            except Exception:
188+                raise TemplateSyntaxError('"context" in %r tag expected '
189+                                          'exactly one argument.' % bits[0])
190         else:
191             raise TemplateSyntaxError('Unknown argument for %r tag: %r.' %
192                                       (bits[0], option))
193@@ -378,7 +427,11 @@ def do_block_translate(parser, token):
194         countervar, counter = options['count'].items()[0]
195     else:
196         countervar, counter = None, None
197-    extra_context = options.get('with', {})
198+    if 'context' in options:
199+        message_context = options['context']
200+    else:
201+        message_context = None
202+    extra_context = options.get('with', {})
203 
204     singular = []
205     plural = []
206@@ -401,7 +454,7 @@ def do_block_translate(parser, token):
207         raise TemplateSyntaxError("'blocktrans' doesn't allow other block tags (seen %r) inside it" % token.contents)
208 
209     return BlockTranslateNode(extra_context, singular, plural, countervar,
210-            counter)
211+            counter, message_context)
212 
213 @register.tag
214 def language(parser, token):
215diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
216index 71765e7..f43cfd9 100644
217--- a/django/utils/translation/trans_real.py
218+++ b/django/utils/translation/trans_real.py
219@@ -433,12 +433,14 @@ def blankout(src, char):
220     """
221     return dot_re.sub(char, src)
222 
223-inline_re = re.compile(r"""^\s*trans\s+((?:".*?")|(?:'.*?'))\s*""")
224-block_re = re.compile(r"""^\s*blocktrans(?:\s+|$)""")
225+context_re = re.compile(r"""^\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?'))\s*""")
226+inline_re = re.compile(r"""^\s*trans\s+((?:"[^"]*?")|(?:'[^']*?'))(\s+.*context\s+(?:"[^"]*?")|(?:'[^']*?'))?\s*""")
227+block_re = re.compile(r"""^\s*blocktrans(\s+.*context\s+(?:"[^"]*?")|(?:'[^']*?'))?(?:\s+|$)""")
228 endblock_re = re.compile(r"""^\s*endblocktrans$""")
229 plural_re = re.compile(r"""^\s*plural$""")
230 constant_re = re.compile(r"""_\(((?:".*?")|(?:'.*?'))\)""")
231 
232+
233 def templatize(src, origin=None):
234     """
235     Turns a Django template into something that is understood by xgettext. It
236@@ -448,6 +450,7 @@ def templatize(src, origin=None):
237     from django.template import (Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK,
238             TOKEN_COMMENT, TRANSLATOR_COMMENT_MARK)
239     out = StringIO()
240+    message_context = None
241     intrans = False
242     inplural = False
243     singular = []
244@@ -477,15 +480,22 @@ def templatize(src, origin=None):
245                 pluralmatch = plural_re.match(t.contents)
246                 if endbmatch:
247                     if inplural:
248-                        out.write(' ngettext(%r,%r,count) ' % (''.join(singular), ''.join(plural)))
249+                        if message_context:
250+                            out.write(' npgettext(%r, %r, %r,count) ' % (message_context, ''.join(singular), ''.join(plural)))
251+                        else:
252+                            out.write(' ngettext(%r, %r, count) ' % (''.join(singular), ''.join(plural)))
253                         for part in singular:
254                             out.write(blankout(part, 'S'))
255                         for part in plural:
256                             out.write(blankout(part, 'P'))
257                     else:
258-                        out.write(' gettext(%r) ' % ''.join(singular))
259+                        if message_context:
260+                            out.write(' pgettext(%r, %r) ' % (message_context, ''.join(singular)))
261+                        else:
262+                            out.write(' gettext(%r) ' % ''.join(singular))
263                         for part in singular:
264                             out.write(blankout(part, 'S'))
265+                    message_context = None
266                     intrans = False
267                     inplural = False
268                     singular = []
269@@ -515,12 +525,33 @@ def templatize(src, origin=None):
270                 cmatches = constant_re.findall(t.contents)
271                 if imatch:
272                     g = imatch.group(1)
273-                    if g[0] == '"': g = g.strip('"')
274-                    elif g[0] == "'": g = g.strip("'")
275-                    out.write(' gettext(%r) ' % g)
276+                    if g[0] == '"':
277+                        g = g.strip('"')
278+                    elif g[0] == "'":
279+                        g = g.strip("'")
280+                    if imatch.group(2):
281+                        # A context is provided
282+                        context_match = context_re.match(imatch.group(2))
283+                        message_context = context_match.group(1)
284+                        if message_context[0] == '"':
285+                            message_context = message_context.strip('"')
286+                        elif message_context[0] == "'":
287+                            message_context = message_context.strip("'")
288+                        out.write(' pgettext(%r, %r) ' % (message_context, g))
289+                        message_context = None
290+                    else:
291+                        out.write(' gettext(%r) ' % g)
292                 elif bmatch:
293                     for fmatch in constant_re.findall(t.contents):
294                         out.write(' _(%s) ' % fmatch)
295+                    if bmatch.group(1):
296+                        # A context is provided
297+                        context_match = context_re.match(bmatch.group(1))
298+                        message_context = context_match.group(1)
299+                        if message_context[0] == '"':
300+                            message_context = message_context.strip('"')
301+                        elif message_context[0] == "'":
302+                            message_context = message_context.strip("'")
303                     intrans = True
304                     inplural = False
305                     singular = []
306diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
307index 3e364fb..00acc65 100644
308--- a/docs/releases/1.4.txt
309+++ b/docs/releases/1.4.txt
310@@ -191,6 +191,14 @@ Additionally, it's now possible to define translatable URL patterns using
311 :ref:`url-internationalization` for more information about the language prefix
312 and how to internationalize URL patterns.
313 
314+Contextual translation support for ``{% trans %}`` and ``{% blocktrans %}``
315+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
316+
317+Django 1.3 introduced :ref:`contextual translation<contextual-markers>` support
318+in Python files via the ``pgettext`` function. This is now also supported by
319+the :ttag:`trans` and :ttag:`blocktrans` template tags using the new
320+``context`` keyword.
321+
322 Customizable ``SingleObjectMixin`` URLConf kwargs
323 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
324 
325diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt
326index b06c9bd..c8611ac 100644
327--- a/docs/topics/i18n/internationalization.txt
328+++ b/docs/topics/i18n/internationalization.txt
329@@ -266,6 +266,11 @@ will appear in the .po file as:
330     msgid "May"
331     msgstr ""
332 
333+.. versionadded:: 1.4
334+
335+Contextual markers are also supported by the :ttag:`trans` and
336+:ttag:`blocktrans` template tags.
337+
338 .. _lazy-translations:
339 
340 Lazy translation
341@@ -453,7 +458,7 @@ It's not possible to mix a template variable inside a string within ``{% trans
342 %}``. If your translations require strings with variables (placeholders), use
343 ``{% blocktrans %}`` instead.
344 
345-.. versionchanged:: 1.4
346+.. versionadded:: 1.4
347 
348 If you'd like to retrieve a translated string without displaying it, you can
349 use the following syntax::
350@@ -479,6 +484,15 @@ or should be used as arguments for other template tags or filters::
351     {% endfor %}
352     </p>
353 
354+.. versionadded:: 1.4
355+
356+``{% trans %}`` also supports :ref:`contextual markers<contextual-markers>`
357+using the ``context`` keyword:
358+
359+.. code-block:: html+django
360+
361+    {% trans "May" context "month name" %}
362+
363 .. templatetag:: blocktrans
364 
365 ``blocktrans`` template tag
366@@ -560,6 +574,15 @@ be retrieved (and stored) beforehand::
367     This is a URL: {{ the_url }}
368     {% endblocktrans %}
369 
370+.. versionadded:: 1.4
371+
372+``{% blocktrans %}`` also supports :ref:`contextual
373+markers<contextual-markers>` using the ``context`` keyword:
374+
375+.. code-block:: html+django
376+
377+    {% blocktrans with name=user.username context "greeting" %}Hi {{ name }}{% endblocktrans %}
378+
379 .. _template-translation-vars:
380 
381 Other tags
382diff --git a/tests/regressiontests/i18n/commands/extraction.py b/tests/regressiontests/i18n/commands/extraction.py
383index 7d7cdf7..c495772 100644
384--- a/tests/regressiontests/i18n/commands/extraction.py
385+++ b/tests/regressiontests/i18n/commands/extraction.py
386@@ -94,6 +94,35 @@ class BasicExtractorTests(ExtractorTests):
387             os.remove('./templates/template_with_error.html')
388             os.remove('./templates/template_with_error.html.py') # Waiting for #8536 to be fixed
389 
390+    def test_template_message_context_extractor(self):
391+        """
392+        Ensure that message contexts are correctly extracted for the
393+        {% trans %} and {% blocktrans %} template tags.
394+        Refs #14806.
395+        """
396+        os.chdir(self.test_dir)
397+        management.call_command('makemessages', locale=LOCALE, verbosity=0)
398+        self.assertTrue(os.path.exists(self.PO_FILE))
399+        po_contents = open(self.PO_FILE, 'r').read()
400+        # {% trans %}
401+        self.assertTrue('msgctxt "Special trans context #1"' in po_contents)
402+        self.assertTrue("Translatable literal #7a" in po_contents)
403+        self.assertTrue('msgctxt "Special trans context #2"' in po_contents)
404+        self.assertTrue("Translatable literal #7b" in po_contents)
405+        self.assertTrue('msgctxt "Special trans context #3"' in po_contents)
406+        self.assertTrue("Translatable literal #7c" in po_contents)
407+
408+        # {% blocktrans %}
409+        self.assertTrue('msgctxt "Special blocktrans context #1"' in po_contents)
410+        self.assertTrue("Translatable literal #8a" in po_contents)
411+        self.assertTrue('msgctxt "Special blocktrans context #2"' in po_contents)
412+        self.assertTrue("Translatable literal #8b-singular" in po_contents)
413+        self.assertTrue("Translatable literal #8b-plural" in po_contents)
414+        self.assertTrue('msgctxt "Special blocktrans context #3"' in po_contents)
415+        self.assertTrue("Translatable literal #8c-singular" in po_contents)
416+        self.assertTrue("Translatable literal #8c-plural" in po_contents)
417+        self.assertTrue('msgctxt "Special blocktrans context #4"' in po_contents)
418+        self.assertTrue("Translatable literal #8d" in po_contents)
419 
420 class JavascriptExtractorTests(ExtractorTests):
421 
422diff --git a/tests/regressiontests/i18n/commands/templates/test.html b/tests/regressiontests/i18n/commands/templates/test.html
423index b5d705c..24fc708 100644
424--- a/tests/regressiontests/i18n/commands/templates/test.html
425+++ b/tests/regressiontests/i18n/commands/templates/test.html
426@@ -57,3 +57,12 @@ continued here.{% endcomment %}
427 {% comment %}  Translators: Two-line translator comment #5 -- with non ASCII characters: áéíóúö
428 continued here.{% endcomment %}
429 {% trans "Translatable literal #6b" %}
430+
431+{% trans "Translatable literal #7a" context "Special trans context #1" %}
432+{% trans "Translatable literal #7b" as var context "Special trans context #2" %}
433+{% trans "Translatable literal #7c" context "Special trans context #3" as var %}
434+
435+{% blocktrans context "Special blocktrans context #1" %}Translatable literal #8a{% endblocktrans %}
436+{% blocktrans count 2 context "Special blocktrans context #2" %}Translatable literal #8b-singular{% plural %}Translatable literal #8b-plural{% endblocktrans %}
437+{% blocktrans context "Special blocktrans context #3" count 2 %}Translatable literal #8c-singular{% plural %}Translatable literal #8c-plural{% endblocktrans %}
438+{% blocktrans with a=1 context "Special blocktrans context #4" %}Translatable literal #8d {{ a }}{% endblocktrans %}
439\ No newline at end of file
440diff --git a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo
441index a1a93c83296f0fb97d500466a8df30338fdb137e..f825e3918b7fd74b7af37e55e704b260101367b7 100644
442GIT binary patch
443literal 1449
444zcmbu9&2AGh5XTLKuYeFA2SA|86^fJyxy>e^R@<g(NmEgps8PG+0Ef!Sx|=P#-pKa0
445zRN@7A01jL@ao`2GA)bK;K)`_q;qOgDLIJ4=OQWB?{?B;q%;d|}+$(}{5q2B)0(J%V
446z8D<APK}Z{%1kZyC%yeK5>(`(Iz5y?RpTHULD_94wpCsfK_!O*x``|h7J-7|cN6^Nf
447zK(L!&0#1N0!E4|<@DBI^JOzFO?}FdKW$^N8LT-Rt;C)bn*dlL1gePZU_B=c6cRTDo
448zhT{SzXJIzB9dOIuA`c=pLJGycP+cKSNf!!KiM|pVD@_YUnZ_kelg_T_n&av;S>{Ge
449zn=lr{>j?zZX=La=w*kjaBA!)rlQ_m<w40>T<e*oAa#(iu_TjEZ62mEp6KQ%>@>t|r
450z{D33|KEvIA2s7~0DKes3aMkVQvNJnE;k3c`LF%|T-r@J}nhgE?!%LFFaE87Aio7RO
451zC%ax-CHC>zs?Qz^rNczJ)ZgVYNT?Co==_H0C(5vv4nm(jNCVBh#HIQ6y2t8Da03r!
452ze-M{e%1)V;E6lk^of)@Uy^D1jLbf6HLK}Icb6DA76^B+Fw>rbhRp^>r8|Eo4b;M1g
453zT>6Mh8EV=Tp)VpjkDRbvjI!laD0h+t_{bpTfnc8CG4d94-WQ8eIF+biA>UeUHAa_m
454zrpoy`ep(a*^A7sR*NEq4x*zdS&e3j<D=kcI%UfZyBb`lWM<~{i-NX-N;L_PnX!7kS
455zRXk!ViHfyLWj|Z$T4jz7rnRD^lQrs;@<)nV8xf&ahlRh6!hdF{&((2sA`{}R(|G!c
456z2n{VZ*Xv8o#&&zjd$?IbTWH!)f#}$0+vf_$jJLy|TpF4NVdpTR@orcfv<*6iw0KCC
457zq>sjN>2dU9)yy+DNjGh+vSDtx>(6>0D2@oUu!X*n^)<);!f1|-k?-H5{<?#3YbYM_
458E1CXVedjJ3c
459
460delta 266
461zcmYj~&k8|76o-%Na!FaR@$X7v<tZ%KNJ-g9Y3kmh7|B&g$ur1CUcg%3fmg8bEWV-S
462z*L>%jndY3iBu>umm9mdQVo-nqB;X7tcj&+a;*bk{-&L^9xsH>#iSxLJ&8#^d!UjzL
463z(-O(zT3Dor8#qmycx+g*qGa$Eo54p+?=R+#8J|R{rpE#ES>)h`_Q~l?2cwc!Q{Qf@
464f^`URM*PG>7<+d72)hl)V#c~p;;D7H+OKB}{^SvR9
465
466diff --git a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po
467index 7226ad1..a471d38 100644
468--- a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po
469+++ b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po
470@@ -52,3 +52,27 @@ msgid "%(percent)s%% represents %(num)s object"
471 msgid_plural "%(percent)s%% represents %(num)s objects"
472 msgstr[0] "%(percent)s%% stellt %(num)s Objekt dar"
473 msgstr[1] "%(percent)s%% stellt %(num)s Objekte dar"
474+
475+#: models.py:17
476+msgctxt "super search"
477+msgid "%(number)s super result"
478+msgid_plural "%(number)s super results"
479+msgstr[0] "%(number)s Super-Ergebnis"
480+msgstr[1] "%(number)s Super-Ergebnisse"
481+
482+#: models.py:19
483+msgctxt "other super search"
484+msgid "%(number)s super result"
485+msgid_plural "%(number)s super results"
486+msgstr[0] "%(number)s anderen Super-Ergebnis"
487+msgstr[1] "%(number)s andere Super-Ergebnisse"
488+
489+#: models.py:21
490+msgctxt "comment count"
491+msgid "There are %(num_comments)s comments"
492+msgstr "Es gibt %(num_comments)s Kommentare"
493+
494+#: models.py:23
495+msgctxt "other comment count"
496+msgid "There are %(num_comments)s comments"
497+msgstr "Andere: Es gibt %(num_comments)s Kommentare"
498\ No newline at end of file
499diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
500index fc69d9c..10e9935 100644
501--- a/tests/regressiontests/i18n/tests.py
502+++ b/tests/regressiontests/i18n/tests.py
503@@ -9,6 +9,7 @@ from threading import local
504 
505 from django.conf import settings
506 from django.template import Template, Context
507+from django.template.base import TemplateSyntaxError
508 from django.test import TestCase, RequestFactory
509 from django.test.utils import override_settings
510 from django.utils import translation
511@@ -87,6 +88,126 @@ class TranslationTests(TestCase):
512                 self.assertEqual(pgettext("verb", "May"), u"Kann")
513                 self.assertEqual(npgettext("search", "%d result", "%d results", 4) % 4, u"4 Resultate")
514 
515+    def test_template_tags_pgettext(self):
516+        """
517+        Ensure that message contexts are taken into account the {% trans %} and
518+        {% blocktrans %} template tags.
519+        Refs #14806.
520+        """
521+        # Reset translation catalog to include other/locale/de
522+        extended_locale_paths = settings.LOCALE_PATHS + (
523+            os.path.join(here, 'other', 'locale'),
524+        )
525+        with self.settings(LOCALE_PATHS=extended_locale_paths):
526+            from django.utils.translation import trans_real
527+            trans_real._active = local()
528+            trans_real._translations = {}
529+            with translation.override('de'):
530+
531+                # {% trans %} -----------------------------------
532+
533+                # Inexisting context...
534+                t = Template('{% load i18n %}{% trans "May" context "unexisting" %}')
535+                rendered = t.render(Context())
536+                self.assertEqual(rendered, 'May')
537+
538+                # Existing context...
539+                # Using a literal
540+                t = Template('{% load i18n %}{% trans "May" context "month name" %}')
541+                rendered = t.render(Context())
542+                self.assertEqual(rendered, 'Mai')
543+                t = Template('{% load i18n %}{% trans "May" context "verb" %}')
544+                rendered = t.render(Context())
545+                self.assertEqual(rendered, 'Kann')
546+
547+                # Using a variable
548+                t = Template('{% load i18n %}{% trans "May" context message_context %}')
549+                rendered = t.render(Context({'message_context': 'month name'}))
550+                self.assertEqual(rendered, 'Mai')
551+                t = Template('{% load i18n %}{% trans "May" context message_context %}')
552+                rendered = t.render(Context({'message_context': 'verb'}))
553+                self.assertEqual(rendered, 'Kann')
554+
555+                # Using a filter
556+                t = Template('{% load i18n %}{% trans "May" context message_context|lower %}')
557+                rendered = t.render(Context({'message_context': 'MONTH NAME'}))
558+                self.assertEqual(rendered, 'Mai')
559+                t = Template('{% load i18n %}{% trans "May" context message_context|lower %}')
560+                rendered = t.render(Context({'message_context': 'VERB'}))
561+                self.assertEqual(rendered, 'Kann')
562+
563+                # Using 'as'
564+                t = Template('{% load i18n %}{% trans "May" context "month name" as var %}Value: {{ var }}')
565+                rendered = t.render(Context())
566+                self.assertEqual(rendered, 'Value: Mai')
567+                t = Template('{% load i18n %}{% trans "May" as var context "verb" %}Value: {{ var }}')
568+                rendered = t.render(Context())
569+                self.assertEqual(rendered, 'Value: Kann')
570+
571+                # Mis-uses
572+                self.assertRaises(TemplateSyntaxError, Template, '{% load i18n %}{% trans "May" context as var %}{{ var }}')
573+                self.assertRaises(TemplateSyntaxError, Template, '{% load i18n %}{% trans "May" as var context %}{{ var }}')
574+
575+                # {% blocktrans %} ------------------------------
576+
577+                # Inexisting context...
578+                t = Template('{% load i18n %}{% blocktrans context "unexisting" %}May{% endblocktrans %}')
579+                rendered = t.render(Context())
580+                self.assertEqual(rendered, 'May')
581+
582+                # Existing context...
583+                # Using a literal
584+                t = Template('{% load i18n %}{% blocktrans context "month name" %}May{% endblocktrans %}')
585+                rendered = t.render(Context())
586+                self.assertEqual(rendered, 'Mai')
587+                t = Template('{% load i18n %}{% blocktrans context "verb" %}May{% endblocktrans %}')
588+                rendered = t.render(Context())
589+                self.assertEqual(rendered, 'Kann')
590+
591+                # Using a variable
592+                t = Template('{% load i18n %}{% blocktrans context message_context %}May{% endblocktrans %}')
593+                rendered = t.render(Context({'message_context': 'month name'}))
594+                self.assertEqual(rendered, 'Mai')
595+                t = Template('{% load i18n %}{% blocktrans context message_context %}May{% endblocktrans %}')
596+                rendered = t.render(Context({'message_context': 'verb'}))
597+                self.assertEqual(rendered, 'Kann')
598+
599+                # Using a filter
600+                t = Template('{% load i18n %}{% blocktrans context message_context|lower %}May{% endblocktrans %}')
601+                rendered = t.render(Context({'message_context': 'MONTH NAME'}))
602+                self.assertEqual(rendered, 'Mai')
603+                t = Template('{% load i18n %}{% blocktrans context message_context|lower %}May{% endblocktrans %}')
604+                rendered = t.render(Context({'message_context': 'VERB'}))
605+                self.assertEqual(rendered, 'Kann')
606+
607+                # Using 'count'
608+                t = Template('{% load i18n %}{% blocktrans count number=1 context "super search" %}{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}')
609+                rendered = t.render(Context())
610+                self.assertEqual(rendered, '1 Super-Ergebnis')
611+                t = Template('{% load i18n %}{% blocktrans count number=2 context "super search" %}{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}')
612+                rendered = t.render(Context())
613+                self.assertEqual(rendered, '2 Super-Ergebnisse')
614+                t = Template('{% load i18n %}{% blocktrans context "other super search" count number=1 %}{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}')
615+                rendered = t.render(Context())
616+                self.assertEqual(rendered, '1 anderen Super-Ergebnis')
617+                t = Template('{% load i18n %}{% blocktrans context "other super search" count number=2 %}{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}')
618+                rendered = t.render(Context())
619+                self.assertEqual(rendered, '2 andere Super-Ergebnisse')
620+
621+                # Using 'with'
622+                t = Template('{% load i18n %}{% blocktrans with num_comments=5 context "comment count" %}There are {{ num_comments }} comments{% endblocktrans %}')
623+                rendered = t.render(Context())
624+                self.assertEqual(rendered, 'Es gibt 5 Kommentare')
625+                t = Template('{% load i18n %}{% blocktrans with num_comments=5 context "other comment count" %}There are {{ num_comments }} comments{% endblocktrans %}')
626+                rendered = t.render(Context())
627+                self.assertEqual(rendered, 'Andere: Es gibt 5 Kommentare')
628+
629+                # Mis-uses
630+                self.assertRaises(TemplateSyntaxError, Template, '{% load i18n %}{% blocktrans context with month="May" %}{{ month }}{% endblocktrans %}')
631+                self.assertRaises(TemplateSyntaxError, Template, '{% load i18n %}{% blocktrans context %}{% endblocktrans %}')
632+                self.assertRaises(TemplateSyntaxError, Template, '{% load i18n %}{% blocktrans count number=2 context %}{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}')
633+
634+
635     def test_string_concat(self):
636         """
637         unicode(string_concat(...)) should not raise a TypeError - #4796
Back to Top