1 | diff --git a/django/template/base.py b/django/template/base.py |
---|
2 | index 8001e93..3129886 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.trans_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.trans_context: |
---|
28 | + return pgettext_lazy(self.trans_context, value) |
---|
29 | + else: |
---|
30 | + return ugettext_lazy(value) |
---|
31 | return value |
---|
32 | |
---|
33 | def __repr__(self): |
---|
34 | @@ -814,11 +818,10 @@ class NodeList(list): |
---|
35 | bits = [] |
---|
36 | for node in self: |
---|
37 | if isinstance(node, Node): |
---|
38 | - bit = self.render_node(node, context) |
---|
39 | + bits.append(self.render_node(node, context)) |
---|
40 | else: |
---|
41 | - bit = node |
---|
42 | - bits.append(force_unicode(bit)) |
---|
43 | - return mark_safe(u''.join(bits)) |
---|
44 | + bits.append(node) |
---|
45 | + return mark_safe(''.join([force_unicode(b) for b in bits])) |
---|
46 | |
---|
47 | def get_nodes_by_type(self, nodetype): |
---|
48 | "Return a list of all nodes of the given type" |
---|
49 | diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py |
---|
50 | index 669cdc9..4f64316 100644 |
---|
51 | --- a/django/templatetags/i18n.py |
---|
52 | +++ b/django/templatetags/i18n.py |
---|
53 | @@ -70,15 +70,21 @@ class GetCurrentLanguageBidiNode(Node): |
---|
54 | |
---|
55 | |
---|
56 | class TranslateNode(Node): |
---|
57 | - def __init__(self, filter_expression, noop, asvar=None): |
---|
58 | + def __init__(self, filter_expression, noop, asvar=None, |
---|
59 | + trans_context=None): |
---|
60 | self.noop = noop |
---|
61 | self.asvar = asvar |
---|
62 | + self.trans_context = trans_context |
---|
63 | self.filter_expression = filter_expression |
---|
64 | if isinstance(self.filter_expression.var, basestring): |
---|
65 | - self.filter_expression.var = Variable(u"'%s'" % self.filter_expression.var) |
---|
66 | + self.filter_expression.var = Variable(u"'%s'" % |
---|
67 | + self.filter_expression.var) |
---|
68 | |
---|
69 | def render(self, context): |
---|
70 | self.filter_expression.var.translate = not self.noop |
---|
71 | + if self.trans_context: |
---|
72 | + self.filter_expression.var.trans_context = ( |
---|
73 | + self.trans_context.resolve(context)) |
---|
74 | output = self.filter_expression.resolve(context) |
---|
75 | value = _render_value_in_context(output, context) |
---|
76 | if self.asvar: |
---|
77 | @@ -90,12 +96,13 @@ class TranslateNode(Node): |
---|
78 | |
---|
79 | class BlockTranslateNode(Node): |
---|
80 | def __init__(self, extra_context, singular, plural=None, countervar=None, |
---|
81 | - counter=None): |
---|
82 | + counter=None, trans_context=None): |
---|
83 | self.extra_context = extra_context |
---|
84 | self.singular = singular |
---|
85 | self.plural = plural |
---|
86 | self.countervar = countervar |
---|
87 | self.counter = counter |
---|
88 | + self.trans_context = trans_context |
---|
89 | |
---|
90 | def render_token_list(self, tokens): |
---|
91 | result = [] |
---|
92 | @@ -109,6 +116,10 @@ class BlockTranslateNode(Node): |
---|
93 | return ''.join(result), vars |
---|
94 | |
---|
95 | def render(self, context): |
---|
96 | + if self.trans_context: |
---|
97 | + trans_context = self.trans_context.resolve(context) |
---|
98 | + else: |
---|
99 | + trans_context = None |
---|
100 | tmp_context = {} |
---|
101 | for var, val in self.extra_context.items(): |
---|
102 | tmp_context[var] = val.resolve(context) |
---|
103 | @@ -123,10 +134,17 @@ class BlockTranslateNode(Node): |
---|
104 | context[self.countervar] = count |
---|
105 | plural, plural_vars = self.render_token_list(self.plural) |
---|
106 | plural = re.sub(u'%(?!\()', u'%%', plural) |
---|
107 | - result = translation.ungettext(singular, plural, count) |
---|
108 | + if trans_context: |
---|
109 | + result = translation.npgettext(trans_context, singular, plural, |
---|
110 | + count) |
---|
111 | + else: |
---|
112 | + result = translation.ungettext(singular, plural, count) |
---|
113 | vars.extend(plural_vars) |
---|
114 | else: |
---|
115 | - result = translation.ugettext(singular) |
---|
116 | + if trans_context: |
---|
117 | + result = translation.pgettext(trans_context, singular) |
---|
118 | + else: |
---|
119 | + result = translation.ugettext(singular) |
---|
120 | data = dict([(v, _render_value_in_context(context.get(v, ''), context)) for v in vars]) |
---|
121 | context.pop() |
---|
122 | try: |
---|
123 | @@ -301,7 +319,6 @@ def do_translate(parser, token): |
---|
124 | # backwards compatibility with existing uses of ``trans`` |
---|
125 | # where single quote use is supported. |
---|
126 | if value[0] == "'": |
---|
127 | - pos = None |
---|
128 | m = re.match("^'([^']+)'(\|.*$)", value) |
---|
129 | if m: |
---|
130 | value = '"%s"%s' % (m.group(1).replace('"','\\"'), m.group(2)) |
---|
131 | @@ -310,19 +327,23 @@ def do_translate(parser, token): |
---|
132 | |
---|
133 | noop = False |
---|
134 | asvar = None |
---|
135 | + trans_context = None |
---|
136 | |
---|
137 | while self.more(): |
---|
138 | tag = self.tag() |
---|
139 | if tag == 'noop': |
---|
140 | noop = True |
---|
141 | + elif tag == 'context': |
---|
142 | + trans_context = parser.compile_filter(self.tag()) |
---|
143 | elif tag == 'as': |
---|
144 | asvar = self.tag() |
---|
145 | else: |
---|
146 | raise TemplateSyntaxError( |
---|
147 | "only options for 'trans' are 'noop' and 'as VAR.") |
---|
148 | - return (value, noop, asvar) |
---|
149 | - value, noop, asvar = TranslateParser(token.contents).top() |
---|
150 | - return TranslateNode(parser.compile_filter(value), noop, asvar) |
---|
151 | + return value, noop, asvar, trans_context |
---|
152 | + value, noop, asvar, trans_context = TranslateParser(token.contents).top() |
---|
153 | + return TranslateNode(parser.compile_filter(value), noop, asvar, |
---|
154 | + trans_context) |
---|
155 | |
---|
156 | @register.tag("blocktrans") |
---|
157 | def do_block_translate(parser, token): |
---|
158 | @@ -369,6 +390,13 @@ def do_block_translate(parser, token): |
---|
159 | if len(value) != 1: |
---|
160 | raise TemplateSyntaxError('"count" in %r tag expected exactly ' |
---|
161 | 'one keyword argument.' % bits[0]) |
---|
162 | + elif option == "context": |
---|
163 | + try: |
---|
164 | + value = remaining_bits.pop(0) |
---|
165 | + value = parser.compile_filter(value) |
---|
166 | + except Exception: |
---|
167 | + raise TemplateSyntaxError('"context" in %r tag expected ' |
---|
168 | + 'exactly one argument.' % bits[0]) |
---|
169 | else: |
---|
170 | raise TemplateSyntaxError('Unknown argument for %r tag: %r.' % |
---|
171 | (bits[0], option)) |
---|
172 | @@ -378,7 +406,11 @@ def do_block_translate(parser, token): |
---|
173 | countervar, counter = options['count'].items()[0] |
---|
174 | else: |
---|
175 | countervar, counter = None, None |
---|
176 | - extra_context = options.get('with', {}) |
---|
177 | + if 'context' in options: |
---|
178 | + trans_context = options['context'] |
---|
179 | + else: |
---|
180 | + trans_context = None |
---|
181 | + extra_context = options.get('with', {}) |
---|
182 | |
---|
183 | singular = [] |
---|
184 | plural = [] |
---|
185 | @@ -401,7 +433,7 @@ def do_block_translate(parser, token): |
---|
186 | raise TemplateSyntaxError("'blocktrans' doesn't allow other block tags (seen %r) inside it" % token.contents) |
---|
187 | |
---|
188 | return BlockTranslateNode(extra_context, singular, plural, countervar, |
---|
189 | - counter) |
---|
190 | + counter, trans_context) |
---|
191 | |
---|
192 | @register.tag |
---|
193 | def language(parser, token): |
---|
194 | diff --git a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo |
---|
195 | index a1a93c83296f0fb97d500466a8df30338fdb137e..9ab2f4672535fe38d864ec37cbd7815cac8db453 100644 |
---|
196 | GIT binary patch |
---|
197 | delta 554 |
---|
198 | zcmaKnu}T9$5QZnoX^dbBMbSj$5KofA(n@TMR$2%?fLxNr3&vG;bCt+7d4V7{zJ{F$ |
---|
199 | z;zL;KD;Qru{5J70N_5$Ozq#4jf97uUkA>uQ*1HkbG+IV&G=m<HTYYqh-cbSVWQKJI |
---|
200 | z(Br!Wi%`QFyn<!;1h?S}oPy=7$T9I6bbj5O$dX7R&jc$Z-ryW8;z}s1a2Kw_MR*D~ |
---|
201 | zVHdLHo&!IbKyFPAF*xZlXzB*eBlmO8)4h|Ra-mE^=}y%KLGPo~>#O9v6Di}{m>b`! |
---|
202 | z&@>`1sOb2tu1wW_<&Cm&yTkLbc`NC=rk_@(5qJMPFO0Xoi!$@s+IPQDH<eM^KXPsx |
---|
203 | jd*(#db<2)UG_3elVAb_yuCz5W_}>OUZ>*C2P;2=F0$+a) |
---|
204 | |
---|
205 | delta 225 |
---|
206 | zcmdnbIg7pio)F7a1|Z-7Vi_Qg0b*_-o&&@nZ~}+}fcPX3a{{pxBSf7FkY)k$9f33- |
---|
207 | zkah#o(m*;CNQ2BO1k&+9Tm{4+{kNDH7$kwTHZuc*9FTSe(!xM(3@Jbc5HVB$2?-!w |
---|
208 | z1EfI)PX*FIy>I{$1DXK_Y(NsEhaHGPma$HJJ7MxcMw!jhOgv1Jb66x;y%Y2D7y!I` |
---|
209 | B6te&T |
---|
210 | |
---|
211 | diff --git a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po |
---|
212 | index 7226ad1..9bf79ce 100644 |
---|
213 | --- a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po |
---|
214 | +++ b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po |
---|
215 | @@ -52,3 +52,18 @@ msgid "%(percent)s%% represents %(num)s object" |
---|
216 | msgid_plural "%(percent)s%% represents %(num)s objects" |
---|
217 | msgstr[0] "%(percent)s%% stellt %(num)s Objekt dar" |
---|
218 | msgstr[1] "%(percent)s%% stellt %(num)s Objekte dar" |
---|
219 | + |
---|
220 | +#: models.py:17 |
---|
221 | +msgctxt "super search" |
---|
222 | +msgid "%(number)s super result" |
---|
223 | +msgid_plural "%(number)s super results" |
---|
224 | +msgstr[0] "%(number)s Super-Ergebnis" |
---|
225 | +msgstr[1] "%(number)s Super-Ergebnisse" |
---|
226 | + |
---|
227 | +#: models.py:19 |
---|
228 | +msgctxt "other super search" |
---|
229 | +msgid "%(number)s super result" |
---|
230 | +msgid_plural "%(number)s super results" |
---|
231 | +msgstr[0] "%(number)s anderen Super-Ergebnis" |
---|
232 | +msgstr[1] "%(number)s andere Super-Ergebnisse" |
---|
233 | + |
---|
234 | diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py |
---|
235 | index fc69d9c..7b0e285 100644 |
---|
236 | --- a/tests/regressiontests/i18n/tests.py |
---|
237 | +++ b/tests/regressiontests/i18n/tests.py |
---|
238 | @@ -9,6 +9,7 @@ from threading import local |
---|
239 | |
---|
240 | from django.conf import settings |
---|
241 | from django.template import Template, Context |
---|
242 | +from django.template.base import TemplateSyntaxError |
---|
243 | from django.test import TestCase, RequestFactory |
---|
244 | from django.test.utils import override_settings |
---|
245 | from django.utils import translation |
---|
246 | @@ -87,6 +88,69 @@ class TranslationTests(TestCase): |
---|
247 | self.assertEqual(pgettext("verb", "May"), u"Kann") |
---|
248 | self.assertEqual(npgettext("search", "%d result", "%d results", 4) % 4, u"4 Resultate") |
---|
249 | |
---|
250 | + def test_template_tags_pgettext(self): |
---|
251 | + # Reset translation catalog to include other/locale/de |
---|
252 | + extended_locale_paths = settings.LOCALE_PATHS + ( |
---|
253 | + os.path.join(here, 'other', 'locale'), |
---|
254 | + ) |
---|
255 | + with self.settings(LOCALE_PATHS=extended_locale_paths): |
---|
256 | + from django.utils.translation import trans_real |
---|
257 | + trans_real._active = local() |
---|
258 | + trans_real._translations = {} |
---|
259 | + with translation.override('de'): |
---|
260 | + |
---|
261 | + # Blocktrans --------- |
---|
262 | + |
---|
263 | + # Inexisting context... |
---|
264 | + t = Template('{% load i18n %}{% blocktrans context "unexisting" %}May{% endblocktrans %}') |
---|
265 | + rendered = t.render(Context()) |
---|
266 | + self.assertEqual(rendered, 'May') |
---|
267 | + |
---|
268 | + # Existing context... |
---|
269 | + # Using a literal |
---|
270 | + t = Template('{% load i18n %}{% blocktrans context "month name" %}May{% endblocktrans %}') |
---|
271 | + rendered = t.render(Context()) |
---|
272 | + self.assertEqual(rendered, 'Mai') |
---|
273 | + t = Template('{% load i18n %}{% blocktrans context "verb" %}May{% endblocktrans %}') |
---|
274 | + rendered = t.render(Context()) |
---|
275 | + self.assertEqual(rendered, 'Kann') |
---|
276 | + |
---|
277 | + # Using a variable |
---|
278 | + t = Template('{% load i18n %}{% blocktrans context trans_context %}May{% endblocktrans %}') |
---|
279 | + rendered = t.render(Context({'trans_context': 'month name'})) |
---|
280 | + self.assertEqual(rendered, 'Mai') |
---|
281 | + t = Template('{% load i18n %}{% blocktrans context trans_context %}May{% endblocktrans %}') |
---|
282 | + rendered = t.render(Context({'trans_context': 'verb'})) |
---|
283 | + self.assertEqual(rendered, 'Kann') |
---|
284 | + |
---|
285 | + # Using a filter |
---|
286 | + t = Template('{% load i18n %}{% blocktrans context trans_context|lower %}May{% endblocktrans %}') |
---|
287 | + rendered = t.render(Context({'trans_context': 'MONTH NAME'})) |
---|
288 | + self.assertEqual(rendered, 'Mai') |
---|
289 | + t = Template('{% load i18n %}{% blocktrans context trans_context|lower %}May{% endblocktrans %}') |
---|
290 | + rendered = t.render(Context({'trans_context': 'VERB'})) |
---|
291 | + self.assertEqual(rendered, 'Kann') |
---|
292 | + |
---|
293 | + # Using 'count' |
---|
294 | + t = Template('{% load i18n %}{% blocktrans count number=1 context "super search" %}{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}') |
---|
295 | + rendered = t.render(Context()) |
---|
296 | + self.assertEqual(rendered, '1 Super-Ergebnis') |
---|
297 | + t = Template('{% load i18n %}{% blocktrans count number=2 context "super search" %}{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}') |
---|
298 | + rendered = t.render(Context()) |
---|
299 | + self.assertEqual(rendered, '2 Super-Ergebnisse') |
---|
300 | + t = Template('{% load i18n %}{% blocktrans count number=1 context "other super search" %}{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}') |
---|
301 | + rendered = t.render(Context()) |
---|
302 | + self.assertEqual(rendered, '1 anderen Super-Ergebnis') |
---|
303 | + t = Template('{% load i18n %}{% blocktrans count number=2 context "other super search" %}{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}') |
---|
304 | + rendered = t.render(Context()) |
---|
305 | + self.assertEqual(rendered, '2 andere Super-Ergebnisse') |
---|
306 | + |
---|
307 | + # Mis-uses |
---|
308 | + self.assertRaises(TemplateSyntaxError, Template, '{% load i18n %}{% blocktrans context with month="May" %}{{ month }}{% endblocktrans %}') |
---|
309 | + self.assertRaises(TemplateSyntaxError, Template, '{% load i18n %}{% blocktrans context %}{% endblocktrans %}') |
---|
310 | + self.assertRaises(TemplateSyntaxError, Template, '{% load i18n %}{% blocktrans count number=2 context %}{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}') |
---|
311 | + |
---|
312 | + |
---|
313 | def test_string_concat(self): |
---|
314 | """ |
---|
315 | unicode(string_concat(...)) should not raise a TypeError - #4796 |
---|