Permalink
Browse files

it's now possible to register extensions after an environment

was created.

--HG--
branch : trunk
  • Loading branch information...
1 parent b8892e7 commit ffaa2e7908d5fc0d06a73b42d3222d59df27d0b3 @mitsuhiko mitsuhiko committed May 29, 2010
Showing with 118 additions and 16 deletions.
  1. +3 −1 CHANGES
  2. +67 −2 docs/extensions.rst
  3. +14 −2 docs/templates.rst
  4. +5 −1 jinja2/compiler.py
  5. +13 −9 jinja2/ext.py
  6. +6 −1 jinja2/nodes.py
  7. +10 −0 jinja2/testsuite/ext.py
View
4 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
-------------
View
69 docs/extensions.rst
@@ -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
--------------------
View
16 docs/templates.rst
@@ -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
~~~~~~~~~~~~~~~~~~~~
View
6 jinja2/compiler.py
@@ -1390,7 +1390,11 @@ def visit_Const(self, node, frame):
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('(')
View
22 jinja2/ext.py
@@ -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 @@ def parse(self, parser):
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 @@ def _parse_block(self, parser, allow_pluralize):
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 @@ def _make_node(self, singular, plural, variables, plural_expr):
# 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))
View
7 jinja2/nodes.py
@@ -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)
View
10 jinja2/testsuite/ext.py
@@ -311,6 +311,16 @@ def test_newstyle_plural(self):
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):

0 comments on commit ffaa2e7

Please sign in to comment.