From 4da90349e80324fb5ab5d79a42c3a45d4993fd5e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 29 May 2010 17:35:10 +0200 Subject: [PATCH] Tip is now 2.5. Started work on newstyle gettext translations. --HG-- branch : trunk --- CHANGES | 9 ++++-- jinja2/compiler.py | 5 ++++ jinja2/ext.py | 71 +++++++++++++++++++++++++++++++++++++--------- jinja2/nodes.py | 16 +++++++++++ jinja2/runtime.py | 5 +++- 5 files changed, 88 insertions(+), 18 deletions(-) diff --git a/CHANGES b/CHANGES index bbe3bd3..d17c230 100644 --- a/CHANGES +++ b/CHANGES @@ -1,13 +1,16 @@ Jinja2 Changelog ================ -Version 2.4.2 -------------- -(bugfix release, release date to be announced) +Version 2.5 +----------- +(codename Incoherence, relased on May 29th 2010) - improved the sort filter (should have worked like this for a long time) by adding support for case insensitive searches. - fixed a bug for getattribute constant folding. +- support for newstyle gettext translations which result in a + nicer in-template user interface and more consistent + catalogs. Version 2.4.1 ------------- diff --git a/jinja2/compiler.py b/jinja2/compiler.py index 0d608a3..1b01ed9 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -1582,6 +1582,11 @@ class CodeGenerator(NodeVisitor): self.visit(node.expr, frame) self.write(')') + def visit_MarkSafeIfAutoescape(self, node, frame): + self.write('(context.eval_ctx.autoescape and Markup or identity)(') + self.visit(node.expr, frame) + self.write(')') + def visit_EnvironmentAttribute(self, node, frame): self.write('environment.' + node.name) diff --git a/jinja2/ext.py b/jinja2/ext.py index 63ce408..e6bef2f 100644 --- a/jinja2/ext.py +++ b/jinja2/ext.py @@ -123,8 +123,29 @@ class Extension(object): @contextfunction -def _gettext_alias(context, string): - return context.resolve('gettext')(string) +def _gettext_alias(__context, *args, **kwargs): + return __context.resolve('gettext')(*args, **kwargs) + + +def _make_new_gettext(func): + @contextfunction + def gettext(__context, __string, **variables): + rv = func(__string) + if __context.eval_ctx.autoescape: + rv = Markup(rv) + return rv % variables + return gettext + + +def _make_new_ngettext(func): + @contextfunction + def ngettext(__context, __singular, __plural, num, **variables): + variables.setdefault('num', num) + rv = func(__singular, __plural, num) + if __context.eval_ctx.autoescape: + rv = Markup(rv) + return rv % variables + return ngettext class InternationalizationExtension(Extension): @@ -144,23 +165,37 @@ class InternationalizationExtension(Extension): environment.extend( install_gettext_translations=self._install, install_null_translations=self._install_null, + install_gettext_callables=self._install_callables, uninstall_gettext_translations=self._uninstall, - extract_translations=self._extract + extract_translations=self._extract, + newstyle_gettext=False ) - def _install(self, translations): + def _install(self, translations, newstyle=None): gettext = getattr(translations, 'ugettext', None) if gettext is None: gettext = translations.gettext ngettext = getattr(translations, 'ungettext', None) if ngettext is None: ngettext = translations.ngettext - self.environment.globals.update(gettext=gettext, ngettext=ngettext) + self._install_callables(gettext, ngettext, newstyle) - def _install_null(self): + def _install_null(self, newstyle=None): + self._install_callables( + lambda x: x, + lambda s, p, n: (n != 1 and (p,) or (s,))[0], + newstyle + ) + + def _install_callables(self, gettext, ngettext, newstyle=None): + if newstyle is not None: + self.environment.newstyle_gettext = newstyle + if self.environment.newstyle_gettext: + gettext = _make_new_gettext(gettext) + ngettext = _make_new_ngettext(ngettext) self.environment.globals.update( - gettext=lambda x: x, - ngettext=lambda s, p, n: (n != 1 and (p,) or (s,))[0] + gettext=gettext, + ngettext=ngettext ) def _uninstall(self, translations): @@ -310,13 +345,21 @@ class InternationalizationExtension(Extension): plural_expr ], [], None, None) - # mark the return value as safe if we are in an - # environment with autoescaping turned on - if self.environment.autoescape: - node = nodes.MarkSafe(node) + # in case newstyle gettext is used, the method is powerful + # enough to handle the variable expansion and autoescape + # handling itself + if self.environment.newstyle_gettext: + if variables is None: + variables = nodes.Dict([]) + node.kwargs = variables - if variables: - node = nodes.Mod(node, variables) + # otherwise do that here + else: + # mark the return value as safe if we are in an + # environment with autoescaping turned on + node = nodes.MarkSafeIfAutoescape(node) + if variables: + node = nodes.Mod(node, variables) return nodes.Output([node]) diff --git a/jinja2/nodes.py b/jinja2/nodes.py index 11247d8..8b5f89a 100644 --- a/jinja2/nodes.py +++ b/jinja2/nodes.py @@ -829,6 +829,22 @@ class MarkSafe(Expr): return Markup(self.expr.as_const(eval_ctx)) +class MarkSafeIfAutoescape(Expr): + """Mark the wrapped expression as safe (wrap it as `Markup`) but + only if autoescaping is active. + + .. versionadded:: 2.5 + """ + fields = ('expr',) + + def as_const(self, eval_ctx=None): + eval_ctx = get_eval_context(self, eval_ctx) + expr = self.expr.as_const(eval_ctx) + if eval_ctx.autoescape: + return Markup(expr) + return expr + + class ContextReference(Expr): """Returns the current template context. It can be used like a :class:`Name` node, with a ``'load'`` ctx and will return the diff --git a/jinja2/runtime.py b/jinja2/runtime.py index a89812f..00b823e 100644 --- a/jinja2/runtime.py +++ b/jinja2/runtime.py @@ -20,7 +20,7 @@ from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \ # these variables are exported to the template runtime __all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup', 'TemplateRuntimeError', 'missing', 'concat', 'escape', - 'markup_join', 'unicode_join', 'to_string', + 'markup_join', 'unicode_join', 'to_string', 'identity', 'TemplateNotFound'] #: the name of the function that is used to convert something into @@ -28,6 +28,9 @@ __all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup', #: code can take advantage of it. to_string = unicode +#: the identity function. Useful for certain things in the environment +identity = lambda x: x + def markup_join(seq): """Concatenation that escapes if necessary and converts to unicode.""" -- 2.26.2