diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py index 30eb6b5f76..5576beb4a3 100644 --- a/django/templatetags/i18n.py +++ b/django/templatetags/i18n.py @@ -110,13 +110,13 @@ class BlockTranslateNode(Node): vars = [] for token in tokens: if token.token_type == TOKEN_TEXT: - result.append(token.contents) + result.append(token.contents.replace('%', '%%')) elif token.token_type == TOKEN_VAR: result.append('%%(%s)s' % token.contents) vars.append(token.contents) return ''.join(result), vars - def render(self, context): + def render(self, context, nested=False): if self.message_context: message_context = self.message_context.resolve(context) else: @@ -128,13 +128,10 @@ class BlockTranslateNode(Node): # the end of function context.update(tmp_context) singular, vars = self.render_token_list(self.singular) - # Escape all isolated '%' - singular = re.sub('%(?!\()', '%%', singular) if self.plural and self.countervar and self.counter: count = self.counter.resolve(context) context[self.countervar] = count plural, plural_vars = self.render_token_list(self.plural) - plural = re.sub('%(?!\()', '%%', plural) if message_context: result = translation.npgettext(message_context, singular, plural, count) @@ -151,8 +148,12 @@ class BlockTranslateNode(Node): try: result = result % data except (KeyError, ValueError): + if nested: + # Either string is malformed, or it's a bug + raise TemplateSyntaxError("'blocktrans' is unable to format " + "string returned by gettext: %r using %r" % (result, data)) with translation.override(None): - result = self.render(context) + result = self.render(context, nested=True) return result diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py index 1346383f3d..4054f85ef0 100644 --- a/tests/regressiontests/i18n/tests.py +++ b/tests/regressiontests/i18n/tests.py @@ -269,7 +269,6 @@ class TranslationTests(TestCase): (%(person)s is translated as %(personne)s in fr.po) Refs #16516. """ - from django.template import Template, Context with translation.override('fr'): t = Template('{% load i18n %}{% blocktrans %}My name is {{ person }}.{% endblocktrans %}') rendered = t.render(Context({'person': 'James'})) @@ -282,7 +281,6 @@ class TranslationTests(TestCase): (%(person) misses a 's' in fr.po, causing the string formatting to fail) Refs #18393. """ - from django.template import Template, Context with translation.override('fr'): t = Template('{% load i18n %}{% blocktrans %}My other name is {{ person }}.{% endblocktrans %}') rendered = t.render(Context({'person': 'James'})) @@ -821,6 +819,20 @@ class MiscTests(TestCase): self.assertEqual(t_plur.render(Context({'percent': 42, 'num': 1})), '42% stellt 1 Objekt dar') self.assertEqual(t_plur.render(Context({'percent': 42, 'num': 4})), '42% stellt 4 Objekte dar') + @override_settings(LOCALE_PATHS=extended_locale_paths) + def test_percent_formatting_in_blocktrans(self): + """ + Test that using Python's %-formatting is properly escaped in blocktrans, + singular or plural + """ + t_sing = Template("{% load i18n %}{% blocktrans %}There are %(num_comments)s comments{% endblocktrans %}") + t_plur = Template("{% load i18n %}{% blocktrans count num as number %}%(percent)s% represents {{ num }} object{% plural %}%(percent)s% represents {{ num }} objects{% endblocktrans %}") + with translation.override('de'): + # Strings won't get translated as they don't match after escaping % + self.assertEqual(t_sing.render(Context({'num_comments': 42})), 'There are %(num_comments)s comments') + self.assertEqual(t_plur.render(Context({'percent': 42, 'num': 1})), '%(percent)s% represents 1 object') + self.assertEqual(t_plur.render(Context({'percent': 42, 'num': 4})), '%(percent)s% represents 4 objects') + class ResolutionOrderI18NTests(TestCase):