diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py index cec35fb48e0..a2b7818eb57 100644 --- a/django/templatetags/i18n.py +++ b/django/templatetags/i18n.py @@ -1,9 +1,10 @@ import re -from django.template import Node, Variable +from django.template import Node, Variable, VariableNode from django.template import TemplateSyntaxError, TokenParser, Library from django.template import TOKEN_TEXT, TOKEN_VAR from django.utils import translation +from django.utils.encoding import force_unicode register = Library() @@ -45,7 +46,8 @@ class TranslateNode(Node): return translation.ugettext(value) class BlockTranslateNode(Node): - def __init__(self, extra_context, singular, plural=None, countervar=None, counter=None): + def __init__(self, extra_context, singular, plural=None, countervar=None, + counter=None): self.extra_context = extra_context self.singular = singular self.plural = plural @@ -54,29 +56,32 @@ class BlockTranslateNode(Node): def render_token_list(self, tokens): result = [] + vars = [] for token in tokens: if token.token_type == TOKEN_TEXT: result.append(token.contents) elif token.token_type == TOKEN_VAR: - result.append('%%(%s)s' % token.contents) - return ''.join(result) + result.append(u'%%(%s)s' % token.contents) + vars.append(token.contents) + return ''.join(result), vars def render(self, context): context.push() - for var,val in self.extra_context.items(): - context[var] = val.resolve(context) - singular = self.render_token_list(self.singular) + for var, val in self.extra_context.items(): + context[var] = val.render(context) + singular, vars = self.render_token_list(self.singular) if self.plural and self.countervar and self.counter: count = self.counter.resolve(context) context[self.countervar] = count - plural = self.render_token_list(self.plural) + plural = self.render_token_list(self.plural)[0] result = translation.ungettext(singular, plural, count) else: result = translation.ugettext(singular) # Escape all isolated '%' before substituting in the context. - result = re.sub('%(?!\()', '%%', result) % context + result = re.sub(u'%(?!\()', u'%%', result) + data = dict([(v, force_unicode(context[v])) for v in vars]) context.pop() - return result + return result % data def do_get_available_languages(parser, token): """ @@ -198,7 +203,6 @@ def do_block_translate(parser, token): This is much like ngettext, only in template syntax. """ class BlockTranslateParser(TokenParser): - def top(self): countervar = None counter = None @@ -209,7 +213,8 @@ def do_block_translate(parser, token): value = self.value() if self.tag() != 'as': raise TemplateSyntaxError, "variable bindings in 'blocktrans' must be 'with value as variable'" - extra_context[self.tag()] = parser.compile_filter(value) + extra_context[self.tag()] = VariableNode( + parser.compile_filter(value)) elif tag == 'count': counter = parser.compile_filter(self.value()) if self.tag() != 'as': @@ -241,7 +246,8 @@ def do_block_translate(parser, token): if token.contents.strip() != 'endblocktrans': raise TemplateSyntaxError, "'blocktrans' doesn't allow other block tags (seen %r) inside it" % token.contents - return BlockTranslateNode(extra_context, singular, plural, countervar, counter) + return BlockTranslateNode(extra_context, singular, plural, countervar, + counter) register.tag('get_available_languages', do_get_available_languages) register.tag('get_current_language', do_get_current_language) diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 7eb4ae06b06..6f6667b6b0d 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -705,10 +705,10 @@ class Templates(unittest.TestCase): 'i18n02': ('{% load i18n %}{% trans "xxxyyyxxx" %}', {}, "xxxyyyxxx"), # simple translation of a variable - 'i18n03': ('{% load i18n %}{% blocktrans %}{{ anton }}{% endblocktrans %}', {'anton': 'xxxyyyxxx'}, "xxxyyyxxx"), + 'i18n03': ('{% load i18n %}{% blocktrans %}{{ anton }}{% endblocktrans %}', {'anton': '\xc3\x85'}, u"Å"), # simple translation of a variable and filter - 'i18n04': ('{% load i18n %}{% blocktrans with anton|lower as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'XXXYYYXXX'}, "xxxyyyxxx"), + 'i18n04': ('{% load i18n %}{% blocktrans with anton|lower as berta %}{{ berta }}{% endblocktrans %}', {'anton': '\xc3\x85'}, u'å'), # simple translation of a string with interpolation 'i18n05': ('{% load i18n %}{% blocktrans %}xxx{{ anton }}xxx{% endblocktrans %}', {'anton': 'yyy'}, "xxxyyyxxx"), @@ -740,6 +740,11 @@ class Templates(unittest.TestCase): 'i18n15': ('{{ absent|default:_("Password") }}', {'LANGUAGE_CODE': 'de', 'absent': ""}, 'Passwort'), 'i18n16': ('{{ _("<") }}', {'LANGUAGE_CODE': 'de'}, '<'), + # Escaping inside blocktrans works as if it was directly in the + # template. + 'i18n17': ('{% load i18n %}{% blocktrans with anton|escape as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α & β'), + 'i18n18': ('{% load i18n %}{% blocktrans with anton|force_escape as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α & β'), + ### HANDLING OF TEMPLATE_STRING_IF_INVALID ################################### 'invalidstr01': ('{{ var|default:"Foo" }}', {}, ('Foo','INVALID')),