1 | diff --git a/django/template/base.py b/django/template/base.py
|
---|
2 | index 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):
|
---|
34 | diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py
|
---|
35 | index 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):
|
---|
215 | diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
|
---|
216 | index 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 = []
|
---|
306 | diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
|
---|
307 | index 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 |
|
---|
325 | diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt
|
---|
326 | index 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
|
---|
382 | diff --git a/tests/regressiontests/i18n/commands/extraction.py b/tests/regressiontests/i18n/commands/extraction.py
|
---|
383 | index 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 |
|
---|
422 | diff --git a/tests/regressiontests/i18n/commands/templates/test.html b/tests/regressiontests/i18n/commands/templates/test.html
|
---|
423 | index 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
|
---|
440 | diff --git a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo
|
---|
441 | index a1a93c83296f0fb97d500466a8df30338fdb137e..f825e3918b7fd74b7af37e55e704b260101367b7 100644
|
---|
442 | GIT binary patch
|
---|
443 | literal 1449
|
---|
444 | zcmbu9&2AGh5XTLKuYeFA2SA|86^fJyxy>e^R@<g(NmEgps8PG+0Ef!Sx|=P#-pKa0
|
---|
445 | zRN@7A01jL@ao`2GA)bK;K)`_q;qOgDLIJ4=OQWB?{?B;q%;d|}+$(}{5q2B)0(J%V
|
---|
446 | z8D<APK}Z{%1kZyC%yeK5>(`(Iz5y?RpTHULD_94wpCsfK_!O*x``|h7J-7|cN6^Nf
|
---|
447 | zK(L!&0#1N0!E4|<@DBI^JOzFO?}FdKW$^N8LT-Rt;C)bn*dlL1gePZU_B=c6cRTDo
|
---|
448 | zhT{SzXJIzB9dOIuA`c=pLJGycP+cKSNf!!KiM|pVD@_YUnZ_kelg_T_n&av;S>{Ge
|
---|
449 | zn=lr{>j?zZX=La=w*kjaBA!)rlQ_m<w40>T<e*oAa#(iu_TjEZ62mEp6KQ%>@>t|r
|
---|
450 | z{D33|KEvIA2s7~0DKes3aMkVQvNJnE;k3c`LF%|T-r@J}nhgE?!%LFFaE87Aio7RO
|
---|
451 | zC%ax-CHC>zs?Qz^rNczJ)ZgVYNT?Co==_H0C(5vv4nm(jNCVBh#HIQ6y2t8Da03r!
|
---|
452 | ze-M{e%1)V;E6lk^of)@Uy^D1jLbf6HLK}Icb6DA76^B+Fw>rbhRp^>r8|Eo4b;M1g
|
---|
453 | zT>6Mh8EV=Tp)VpjkDRbvjI!laD0h+t_{bpTfnc8CG4d94-WQ8eIF+biA>UeUHAa_m
|
---|
454 | zrpoy`ep(a*^A7sR*NEq4x*zdS&e3j<D=kcI%UfZyBb`lWM<~{i-NX-N;L_PnX!7kS
|
---|
455 | zRXk!ViHfyLWj|Z$T4jz7rnRD^lQrs;@<)nV8xf&ahlRh6!hdF{&((2sA`{}R(|G!c
|
---|
456 | z2n{VZ*Xv8o#&&zjd$?IbTWH!)f#}$0+vf_$jJLy|TpF4NVdpTR@orcfv<*6iw0KCC
|
---|
457 | zq>sjN>2dU9)yy+DNjGh+vSDtx>(6>0D2@oUu!X*n^)<);!f1|-k?-H5{<?#3YbYM_
|
---|
458 | E1CXVedjJ3c
|
---|
459 |
|
---|
460 | delta 266
|
---|
461 | zcmYj~&k8|76o-%Na!FaR@$X7v<tZ%KNJ-g9Y3kmh7|B&g$ur1CUcg%3fmg8bEWV-S
|
---|
462 | z*L>%jndY3iBu>umm9mdQVo-nqB;X7tcj&+a;*bk{-&L^9xsH>#iSxLJ&8#^d!UjzL
|
---|
463 | z(-O(zT3Dor8#qmycx+g*qGa$Eo54p+?=R+#8J|R{rpE#ES>)h`_Q~l?2cwc!Q{Qf@
|
---|
464 | f^`URM*PG>7<+d72)hl)V#c~p;;D7H+OKB}{^SvR9
|
---|
465 |
|
---|
466 | diff --git a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po
|
---|
467 | index 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
|
---|
499 | diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
|
---|
500 | index 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
|
---|