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