Django

Code

root/django/trunk/django/templatetags/i18n.py

Revision 7261, 8.8 kB (checked in by mtredinnick, 10 months ago)

Fixed #4539 -- Fixed a subtle context resolving bug in the i18n template tag.
Excellent debugging from permonik@mesias.brnonet.cz.

  • Property svn:eol-style set to native
Line 
1 import re
2
3 from django.template import Node, Variable, VariableNode
4 from django.template import TemplateSyntaxError, TokenParser, Library
5 from django.template import TOKEN_TEXT, TOKEN_VAR
6 from django.utils import translation
7 from django.utils.encoding import force_unicode
8
9 register = Library()
10
11 class GetAvailableLanguagesNode(Node):
12     def __init__(self, variable):
13         self.variable = variable
14
15     def render(self, context):
16         from django.conf import settings
17         context[self.variable] = [(k, translation.ugettext(v)) for k, v in settings.LANGUAGES]
18         return ''
19
20 class GetCurrentLanguageNode(Node):
21     def __init__(self, variable):
22         self.variable = variable
23
24     def render(self, context):
25         context[self.variable] = translation.get_language()
26         return ''
27
28 class GetCurrentLanguageBidiNode(Node):
29     def __init__(self, variable):
30         self.variable = variable
31
32     def render(self, context):
33         context[self.variable] = translation.get_language_bidi()
34         return ''
35
36 class TranslateNode(Node):
37     def __init__(self, value, noop):
38         self.value = Variable(value)
39         self.noop = noop
40
41     def render(self, context):
42         value = self.value.resolve(context)
43         if self.noop:
44             return value
45         else:
46             return translation.ugettext(value)
47
48 class BlockTranslateNode(Node):
49     def __init__(self, extra_context, singular, plural=None, countervar=None,
50             counter=None):
51         self.extra_context = extra_context
52         self.singular = singular
53         self.plural = plural
54         self.countervar = countervar
55         self.counter = counter
56
57     def render_token_list(self, tokens):
58         result = []
59         vars = []
60         for token in tokens:
61             if token.token_type == TOKEN_TEXT:
62                 result.append(token.contents)
63             elif token.token_type == TOKEN_VAR:
64                 result.append(u'%%(%s)s' % token.contents)
65                 vars.append(token.contents)
66         return ''.join(result), vars
67
68     def render(self, context):
69         tmp_context = {}
70         for var, val in self.extra_context.items():
71             tmp_context[var] = val.render(context)
72         # Update() works like a push(), so corresponding context.pop() is at
73         # the end of function
74         context.update(tmp_context)
75         singular, vars = self.render_token_list(self.singular)
76         if self.plural and self.countervar and self.counter:
77             count = self.counter.resolve(context)
78             context[self.countervar] = count
79             plural, vars = self.render_token_list(self.plural)
80             result = translation.ungettext(singular, plural, count)
81         else:
82             result = translation.ugettext(singular)
83         # Escape all isolated '%' before substituting in the context.
84         result = re.sub(u'%(?!\()', u'%%', result)
85         data = dict([(v, force_unicode(context[v])) for v in vars])
86         context.pop()
87         return result % data
88
89 def do_get_available_languages(parser, token):
90     """
91     This will store a list of available languages
92     in the context.
93
94     Usage::
95
96         {% get_available_languages as languages %}
97         {% for language in languages %}
98         ...
99         {% endfor %}
100
101     This will just pull the LANGUAGES setting from
102     your setting file (or the default settings) and
103     put it into the named variable.
104     """
105     args = token.contents.split()
106     if len(args) != 3 or args[1] != 'as':
107         raise TemplateSyntaxError, "'get_available_languages' requires 'as variable' (got %r)" % args
108     return GetAvailableLanguagesNode(args[2])
109
110 def do_get_current_language(parser, token):
111     """
112     This will store the current language in the context.
113
114     Usage::
115
116         {% get_current_language as language %}
117
118     This will fetch the currently active language and
119     put it's value into the ``language`` context
120     variable.
121     """
122     args = token.contents.split()
123     if len(args) != 3 or args[1] != 'as':
124         raise TemplateSyntaxError, "'get_current_language' requires 'as variable' (got %r)" % args
125     return GetCurrentLanguageNode(args[2])
126
127 def do_get_current_language_bidi(parser, token):
128     """
129     This will store the current language layout in the context.
130
131     Usage::
132
133         {% get_current_language_bidi as bidi %}
134
135     This will fetch the currently active language's layout and
136     put it's value into the ``bidi`` context variable.
137     True indicates right-to-left layout, otherwise left-to-right
138     """
139     args = token.contents.split()
140     if len(args) != 3 or args[1] != 'as':
141         raise TemplateSyntaxError, "'get_current_language_bidi' requires 'as variable' (got %r)" % args
142     return GetCurrentLanguageBidiNode(args[2])
143
144 def do_translate(parser, token):
145     """
146     This will mark a string for translation and will
147     translate the string for the current language.
148
149     Usage::
150
151         {% trans "this is a test" %}
152
153     This will mark the string for translation so it will
154     be pulled out by mark-messages.py into the .po files
155     and will run the string through the translation engine.
156
157     There is a second form::
158
159         {% trans "this is a test" noop %}
160
161     This will only mark for translation, but will return
162     the string unchanged. Use it when you need to store
163     values into forms that should be translated later on.
164
165     You can use variables instead of constant strings
166     to translate stuff you marked somewhere else::
167
168         {% trans variable %}
169
170     This will just try to translate the contents of
171     the variable ``variable``. Make sure that the string
172     in there is something that is in the .po file.
173     """
174     class TranslateParser(TokenParser):
175         def top(self):
176             value = self.value()
177             if self.more():
178                 if self.tag() == 'noop':
179                     noop = True
180                 else:
181                     raise TemplateSyntaxError, "only option for 'trans' is 'noop'"
182             else:
183                 noop = False
184             return (value, noop)
185     value, noop = TranslateParser(token.contents).top()
186     return TranslateNode(value, noop)
187
188 def do_block_translate(parser, token):
189     """
190     This will translate a block of text with parameters.
191
192     Usage::
193
194         {% blocktrans with foo|filter as bar and baz|filter as boo %}
195         This is {{ bar }} and {{ boo }}.
196         {% endblocktrans %}
197
198     Additionally, this supports pluralization::
199
200         {% blocktrans count var|length as count %}
201         There is {{ count }} object.
202         {% plural %}
203         There are {{ count }} objects.
204         {% endblocktrans %}
205
206     This is much like ngettext, only in template syntax.
207     """
208     class BlockTranslateParser(TokenParser):
209         def top(self):
210             countervar = None
211             counter = None
212             extra_context = {}
213             while self.more():
214                 tag = self.tag()
215                 if tag == 'with' or tag == 'and':
216                     value = self.value()
217                     if self.tag() != 'as':
218                         raise TemplateSyntaxError, "variable bindings in 'blocktrans' must be 'with value as variable'"
219                     extra_context[self.tag()] = VariableNode(
220                             parser.compile_filter(value))
221                 elif tag == 'count':
222                     counter = parser.compile_filter(self.value())
223                     if self.tag() != 'as':
224                         raise TemplateSyntaxError, "counter specification in 'blocktrans' must be 'count value as variable'"
225                     countervar = self.tag()
226                 else:
227                     raise TemplateSyntaxError, "unknown subtag %s for 'blocktrans' found" % tag
228             return (countervar, counter, extra_context)
229
230     countervar, counter, extra_context = BlockTranslateParser(token.contents).top()
231
232     singular = []
233     plural = []
234     while parser.tokens:
235         token = parser.next_token()
236         if token.token_type in (TOKEN_VAR, TOKEN_TEXT):
237             singular.append(token)
238         else:
239             break
240     if countervar and counter:
241         if token.contents.strip() != 'plural':
242             raise TemplateSyntaxError, "'blocktrans' doesn't allow other block tags inside it"
243         while parser.tokens:
244             token = parser.next_token()
245             if token.token_type in (TOKEN_VAR, TOKEN_TEXT):
246                 plural.append(token)
247             else:
248                 break
249     if token.contents.strip() != 'endblocktrans':
250         raise TemplateSyntaxError, "'blocktrans' doesn't allow other block tags (seen %r) inside it" % token.contents
251
252     return BlockTranslateNode(extra_context, singular, plural, countervar,
253             counter)
254
255 register.tag('get_available_languages', do_get_available_languages)
256 register.tag('get_current_language', do_get_current_language)
257 register.tag('get_current_language_bidi', do_get_current_language_bidi)
258 register.tag('trans', do_translate)
259 register.tag('blocktrans', do_block_translate)
Note: See TracBrowser for help on using the browser.