From ffaa2e7908d5fc0d06a73b42d3222d59df27d0b3 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 29 May 2010 20:57:16 +0200 Subject: [PATCH] it's now possible to register extensions after an environment was created. --HG-- branch : trunk --- CHANGES | 4 ++- docs/extensions.rst | 69 +++++++++++++++++++++++++++++++++++++++-- docs/templates.rst | 16 ++++++++-- jinja2/compiler.py | 6 +++- jinja2/ext.py | 22 +++++++------ jinja2/nodes.py | 7 ++++- jinja2/testsuite/ext.py | 10 ++++++ 7 files changed, 118 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index d17c230..3acfb96 100644 --- 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 ------------- diff --git a/docs/extensions.rst b/docs/extensions.rst index 707b155..9115d02 100644 --- a/docs/extensions.rst +++ b/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 -------------------- diff --git a/docs/templates.rst b/docs/templates.rst index 26234e0..02705c4 100644 --- a/docs/templates.rst +++ b/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 ~~~~~~~~~~~~~~~~~~~~ diff --git a/jinja2/compiler.py b/jinja2/compiler.py index 1b01ed9..8660f8c 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -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('(') diff --git a/jinja2/ext.py b/jinja2/ext.py index 4220657..48f04fc 100644 --- a/jinja2/ext.py +++ b/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 @@ 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)) diff --git a/jinja2/nodes.py b/jinja2/nodes.py index 8b5f89a..6446c70 100644 --- a/jinja2/nodes.py +++ b/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) diff --git a/jinja2/testsuite/ext.py b/jinja2/testsuite/ext.py index 1306973..98e4161 100644 --- a/jinja2/testsuite/ext.py +++ b/jinja2/testsuite/ext.py @@ -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'Wert: %(name)s', + lambda s, p, n: s, newstyle=True) + t = env.from_string('{% autoescape ae %}{{ gettext("foo", name=' + '"") }}{% endautoescape %}') + assert t.render(ae=True) == 'Wert: <test>' + assert t.render(ae=False) == 'Wert: ' + class AutoEscapeTestCase(JinjaTestCase): -- 2.26.2