Tip is now 2.5. Started work on newstyle gettext translations.
authorArmin Ronacher <armin.ronacher@active-4.com>
Sat, 29 May 2010 15:35:10 +0000 (17:35 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sat, 29 May 2010 15:35:10 +0000 (17:35 +0200)
--HG--
branch : trunk

CHANGES
jinja2/compiler.py
jinja2/ext.py
jinja2/nodes.py
jinja2/runtime.py

diff --git a/CHANGES b/CHANGES
index bbe3bd35ffe251cb391b7d76b402cbd71bbff492..d17c2300fc1a4a0359839882228033cdb478b62f 100644 (file)
--- 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
 -------------
index 0d608a36e7247935e731df425d6625f73304e5a0..1b01ed9af3d40f0853983ef0c79752686f490531 100644 (file)
@@ -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)
 
index 63ce408125aecc45c05050305d719a2ad07be730..e6bef2f551fa086a00c12c84d64ae93d00d21175 100644 (file)
@@ -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])
 
 
index 11247d83cc21fc6efd6ac0a4743ad2cc2afd169b..8b5f89a4730fc643581c944f045d223c86534e53 100644 (file)
@@ -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
index a89812ff223ef3f4b9fbaa16d0ee3e3e7138a378..00b823e3e45dbfa0c4c5307ea6584e85e43b1b5a 100644 (file)
@@ -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."""