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
|
---|