Skip to content

Commit

Permalink
it's now possible to register extensions after an environment
Browse files Browse the repository at this point in the history
was created.

--HG--
branch : trunk
  • Loading branch information
mitsuhiko committed May 29, 2010
1 parent b8892e7 commit ffaa2e7
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 16 deletions.
4 changes: 3 additions & 1 deletion CHANGES
Expand Up @@ -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
-------------
Expand Down
69 changes: 67 additions & 2 deletions docs/extensions.rst
Expand Up @@ -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.
Expand Down Expand Up @@ -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
--------------------
Expand Down
16 changes: 14 additions & 2 deletions docs/templates.rst
Expand Up @@ -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
~~~~~~~~~~~~~~~~~~~~
Expand Down
6 changes: 5 additions & 1 deletion jinja2/compiler.py
Expand Up @@ -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('(')
Expand Down
22 changes: 13 additions & 9 deletions jinja2/ext.py
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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')
Expand All @@ -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))

Expand Down
7 changes: 6 additions & 1 deletion jinja2/nodes.py
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions jinja2/testsuite/ext.py
Expand Up @@ -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):

Expand Down

0 comments on commit ffaa2e7

Please sign in to comment.