it's now possible to register extensions after an environment
authorArmin Ronacher <armin.ronacher@active-4.com>
Sat, 29 May 2010 18:57:16 +0000 (20:57 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sat, 29 May 2010 18:57:16 +0000 (20:57 +0200)
was created.

--HG--
branch : trunk

CHANGES
docs/extensions.rst
docs/templates.rst
jinja2/compiler.py
jinja2/ext.py
jinja2/nodes.py
jinja2/testsuite/ext.py

diff --git a/CHANGES b/CHANGES
index d17c2300fc1a4a0359839882228033cdb478b62f..3acfb96ea81fb37eb294ffea452d3fa8ea5393a0 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -10,7 +10,9 @@ Version 2.5
 - 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.
+  catalogs. (:ref:`newstyle-gettext`)
+- it's now possible to register extensions after an environment
+  was created.
 
 Version 2.4.1
 -------------
index 707b155596fbd130f9f4c8af0b973eadd73d223c..9115d0247319e4475e54c5bd7c0d6b4853fb1a79 100644 (file)
@@ -37,22 +37,41 @@ to the environment globals.  An internationalized application then has to
 provide at least an `gettext` and optoinally a `ngettext` function into the
 namespace.  Either globally or for each rendering.
 
+Environment Methods
+~~~~~~~~~~~~~~~~~~~
+
 After enabling of the extension the environment provides the following
 additional methods:
 
-.. method:: jinja2.Environment.install_gettext_translations(translations)
+.. method:: jinja2.Environment.install_gettext_translations(translations, newstyle=False)
 
     Installs a translation globally for that environment.  The tranlations
     object provided must implement at least `ugettext` and `ungettext`.
     The `gettext.NullTranslations` and `gettext.GNUTranslations` classes
     as well as `Babel`_\s `Translations` class are supported.
 
-.. method:: jinja2.Environment.install_null_translations()
+    .. versionchanged:: 2.5 newstyle gettext added
+
+.. method:: jinja2.Environment.install_null_translations(newstyle=False)
 
     Install dummy gettext functions.  This is useful if you want to prepare
     the application for internationalization but don't want to implement the
     full internationalization system yet.
 
+    .. versionchanged:: 2.5 newstyle gettext added
+
+.. method:: jinja2.Environment.install_gettext_callables(gettext, ngettext, newstyle=False)
+
+    Installs the given `gettext` and `ngettext` callables into the
+    environment as globals.  They are supposed to behave exactly like the
+    standard library's :func:`gettext.ugettext` and
+    :func:`gettext.ungettext` functions.
+
+    If `newstyle` is activated, the callables are wrapped to work like
+    newstyle callables.  See :ref:`newstyle-gettext` for more information.
+
+    .. versionadded:: 2.5
+
 .. method:: jinja2.Environment.uninstall_gettext_translations()
 
     Uninstall the translations again.
@@ -91,6 +110,52 @@ The usage of the `i18n` extension for template designers is covered as part
 .. _gettext: http://docs.python.org/dev/library/gettext
 .. _Babel: http://babel.edgewall.org/
 
+.. _newstyle-gettext:
+
+Newstyle Gettext
+~~~~~~~~~~~~~~~~
+
+.. versionadded:: 2.5
+
+Starting with version 2.5 you can use newstyle gettext calls.  These are
+inspired by trac's internal gettext functions and are fully supported by
+the babel extraction tool.  They might not work as expected by other
+extraction tools in case you are not using Babel's.
+
+What's the big difference between standard and newstyle gettext calls?  In
+general they are less to type and less error prone.  Also if they are used
+in an autoescaping environment they better support automatic escaping.
+Here some common differences between old and new calls:
+
+standard gettext:
+
+.. sourcecode:: html+jinja
+
+    {{ gettext('Hello World!') }}
+    {{ gettext('Hello %(name)s!')|format(name='World') }}
+    {{ ngettext('%(num)d apple', '%(num)d apples', apples|count)|format(
+        num=apples|count
+    )}}
+
+newstyle gettext looks like this instead:
+
+.. sourcecode:: html+jinja
+
+    {{ gettext('Hello World!') }}
+    {{ gettext('Hello %(name)s!', name='World') }}
+    {{ ngettext('%(num)d apple', '%(num)d apples', apples|count) }}
+
+The advantages of newstyle gettext is that you have less to type and that
+named placeholders become mandatory.  The latter sounds like a
+disadvantage but solves a lot of troubles translators are often facing
+when they are unable to switch the positions of two placeholder.  With
+newstyle gettext, all format strings look the same.
+
+Furthermore with newstyle gettext, string formatting is also used if no
+placeholders are used which makes all strings behave exactly the same.
+Last but not least are newstyle gettext calls able to properly mark
+strings for autoescaping which solves lots of escaping related issues many
+templates are experiencing over time when using autoescaping.
 
 Expression Statement
 --------------------
index 26234e01d88943135327f41d6dd4c356c7e115ea..02705c486a8081168855f30c67cffdafb819c343 100644 (file)
@@ -1239,12 +1239,24 @@ For example you can print a translated string easily this way::
 To use placeholders you can use the `format` filter::
 
     {{ _('Hello %(user)s!')|format(user=user.username) }}
-        or
-    {{ _('Hello %s')|format(user.username) }}
 
 For multiple placeholders always use keyword arguments to `format` as other
 languages may not use the words in the same order.
 
+.. versionchanged:: 2.5
+
+If newstyle gettext call are activated (:ref:`newstyle-gettext`), using
+placeholders is a lot easier:
+
+.. sourcecode:: html+jinja
+
+    {{ gettext('Hello World!') }}
+    {{ gettext('Hello %(name)s!', name='World') }}
+    {{ ngettext('%(num)d apple', '%(num)d apples', apples|count) }}
+
+Note that the `ngettext` function's format string automatically recieves
+the count as `num` parameter additionally to the regular parameters.
+
 
 Expression Statement
 ~~~~~~~~~~~~~~~~~~~~
index 1b01ed9af3d40f0853983ef0c79752686f490531..8660f8c05d52731608369c6ea0f0968481405858 100644 (file)
@@ -1390,7 +1390,11 @@ class CodeGenerator(NodeVisitor):
             self.write(repr(val))
 
     def visit_TemplateData(self, node, frame):
-        self.write(repr(node.as_const(frame.eval_ctx)))
+        try:
+            self.write(repr(node.as_const(frame.eval_ctx)))
+        except nodes.Impossible:
+            self.write('(context.eval_ctx.autoescape and Markup or identity)(%r)'
+                       % node.data)
 
     def visit_Tuple(self, node, frame):
         self.write('(')
index 42206577f7276dac0c75cb70506da649a8f112b6..48f04fc160ad2b999432da28cd9170414fe68a12 100644 (file)
@@ -130,7 +130,7 @@ def _gettext_alias(__context, *args, **kwargs):
 def _make_new_gettext(func):
     @contextfunction
     def gettext(__context, __string, **variables):
-        rv  = __context.call(func, __string)
+        rv = __context.call(func, __string)
         if __context.eval_ctx.autoescape:
             rv = Markup(rv)
         return rv % variables
@@ -275,18 +275,13 @@ class InternationalizationExtension(Extension):
             if var not in variables:
                 variables[var] = nodes.Name(var, 'load')
 
-        # no variables referenced?  no need to escape
-        if not referenced:
-            singular = singular.replace('%%', '%')
-            if plural:
-                plural = plural.replace('%%', '%')
-
         if not have_plural:
             plural_expr = None
         elif plural_expr is None:
             parser.fail('pluralize without variables', lineno)
 
-        node = self._make_node(singular, plural, variables, plural_expr)
+        node = self._make_node(singular, plural, variables, plural_expr,
+                               bool(referenced))
         node.set_lineno(lineno)
         return node
 
@@ -322,8 +317,16 @@ class InternationalizationExtension(Extension):
 
         return referenced, concat(buf)
 
-    def _make_node(self, singular, plural, variables, plural_expr):
+    def _make_node(self, singular, plural, variables, plural_expr,
+                   vars_referenced):
         """Generates a useful node from the data provided."""
+        # no variables referenced?  no need to escape for old style
+        # gettext invocations
+        if not vars_referenced and not self.environment.newstyle_gettext:
+            singular = singular.replace('%%', '%')
+            if plural:
+                plural = plural.replace('%%', '%')
+
         # singular only:
         if plural_expr is None:
             gettext = nodes.Name('gettext', 'load')
@@ -343,6 +346,7 @@ class InternationalizationExtension(Extension):
         # enough to handle the variable expansion and autoescape
         # handling itself
         if self.environment.newstyle_gettext:
+            print 'HERE'
             for key, value in variables.iteritems():
                 node.kwargs.append(nodes.Keyword(key, value))
 
index 8b5f89a4730fc643581c944f045d223c86534e53..6446c70eae8439c993de01359c03709a673246a7 100644 (file)
@@ -442,7 +442,10 @@ class TemplateData(Literal):
     fields = ('data',)
 
     def as_const(self, eval_ctx=None):
-        if get_eval_context(self, eval_ctx).autoescape:
+        eval_ctx = get_eval_context(self, eval_ctx)
+        if eval_ctx.volatile:
+            raise Impossible()
+        if eval_ctx.autoescape:
             return Markup(self.data)
         return self.data
 
@@ -839,6 +842,8 @@ class MarkSafeIfAutoescape(Expr):
 
     def as_const(self, eval_ctx=None):
         eval_ctx = get_eval_context(self, eval_ctx)
+        if eval_ctx.volatile:
+            raise Impossible()
         expr = self.expr.as_const(eval_ctx)
         if eval_ctx.autoescape:
             return Markup(expr)
index 13069732381541621e02de62a614d06f73cb6ef2..98e4161a80e59c94305125b9c3c6875c1d95a676 100644 (file)
@@ -311,6 +311,16 @@ class NewstyleInternationalizationTestCase(JinjaTestCase):
         assert tmpl.render(LANGUAGE='de', apples=1) == '1 Apfel'
         assert tmpl.render(LANGUAGE='de', apples=5) == u'5 Ã„pfel'
 
+    def test_autoescape_support(self):
+        env = Environment(extensions=['jinja2.ext.autoescape',
+                                      'jinja2.ext.i18n'])
+        env.install_gettext_callables(lambda x: u'<strong>Wert: %(name)s</strong>',
+                                      lambda s, p, n: s, newstyle=True)
+        t = env.from_string('{% autoescape ae %}{{ gettext("foo", name='
+                            '"<test>") }}{% endautoescape %}')
+        assert t.render(ae=True) == '<strong>Wert: &lt;test&gt;</strong>'
+        assert t.render(ae=False) == '<strong>Wert: <test></strong>'
+
 
 class AutoEscapeTestCase(JinjaTestCase):