From: David Lord Date: Sat, 19 Oct 2019 14:30:39 +0000 (-0700) Subject: clean up i18n documentation X-Git-Tag: 2.11.0~37^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=50f181080aa327e9c3010bd34dd6ede4b7438517;p=thirdparty%2Fjinja.git clean up i18n documentation explain enabling newstyle organize newstyle advantages mention escaping percent signs for formatting mention marking format parameters as safe fix newstyle ref --- diff --git a/docs/extensions.rst b/docs/extensions.rst index b21b1113..15732841 100644 --- a/docs/extensions.rst +++ b/docs/extensions.rst @@ -27,14 +27,13 @@ i18n Extension **Import name:** ``jinja2.ext.i18n`` -The i18n extension can be used in combination with `gettext`_ or `babel`_. If -the i18n extension is enabled Jinja2 provides a ``trans`` statement that marks -the wrapped string as translatable and calls ``gettext``. +The i18n extension can be used in combination with `gettext`_ or +`Babel`_. When it's enabled, Jinja provides a ``trans`` statement that +marks a block as translatable and calls ``gettext``. -After enabling, dummy ``_`` function that forwards calls to ``gettext`` is added -to the environment globals. An internationalized application then has to -provide a ``gettext`` function and optionally an ``ngettext`` function into the -namespace, either globally or for each rendering. +After enabling, an application has to provide ``gettext`` and +``ngettext`` functions, either globally or when rendering. A ``_()`` +function is added as an alias to the ``gettext`` function. Environment Methods ~~~~~~~~~~~~~~~~~~~ @@ -44,36 +43,37 @@ additional methods: .. method:: jinja2.Environment.install_gettext_translations(translations, newstyle=False) - Installs a translation globally for that environment. The translations - 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. + Installs a translation globally for the environment. The + ``translations`` object must implement ``gettext`` and ``ngettext`` + (or ``ugettext`` and ``ungettext`` for Python 2). + :class:`gettext.NullTranslations`, :class:`gettext.GNUTranslations`, + and `Babel`_\s ``Translations`` are supported. - .. versionchanged:: 2.5 newstyle gettext added + .. versionchanged:: 2.5 Added new-style gettext support. .. 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. + Install no-op gettext functions. This is useful if you want to + prepare the application for internationalization but don't want to + implement the full system yet. - .. versionchanged:: 2.5 newstyle gettext added + .. versionchanged:: 2.5 Added new-style gettext support. .. 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. + Install the given ``gettext`` and ``ngettext`` callables into the + environment. They should behave exactly like + :func:`gettext.gettext` and :func:`gettext.ngettext` (or + ``ugettext`` and ``ungettext`` for Python 2). If ``newstyle`` is activated, the callables are wrapped to work like newstyle callables. See :ref:`newstyle-gettext` for more information. - .. versionadded:: 2.5 + .. versionadded:: 2.5 Added new-style gettext support. .. method:: jinja2.Environment.uninstall_gettext_translations() - Uninstall the translations again. + Uninstall the environment's globally installed translation. .. method:: jinja2.Environment.extract_translations(source) @@ -82,88 +82,102 @@ additional methods: For every string found this function yields a ``(lineno, function, message)`` tuple, where: - * ``lineno`` is the number of the line on which the string was found, - * ``function`` is the name of the ``gettext`` function used (if the - string was extracted from embedded Python code), and - * ``message`` is the string itself (a ``unicode`` object, or a tuple - of ``unicode`` objects for functions with multiple string arguments). + - ``lineno`` is the number of the line on which the string was + found. + - ``function`` is the name of the ``gettext`` function used (if + the string was extracted from embedded Python code). + - ``message`` is the string itself (``unicode`` on Python 2), or a + tuple of strings for functions with multiple arguments. - If `Babel`_ is installed, :ref:`the babel integration ` - can be used to extract strings for babel. + If `Babel`_ is installed, see :ref:`babel-integration` to extract + the strings. -For a web application that is available in multiple languages but gives all -the users the same language (for example a multilingual forum software -installed for a French community) may load the translations once and add the -translation methods to the environment at environment generation time:: +For a web application that is available in multiple languages but gives +all the users the same language (for example, multilingual forum +software installed for a French community), the translation may be +installed when the environment is created. + +.. code-block:: python translations = get_gettext_translations() - env = Environment(extensions=['jinja2.ext.i18n']) + env = Environment(extensions=["jinja2.ext.i18n"]) env.install_gettext_translations(translations) -The ``get_gettext_translations`` function would return the translator for the -current configuration. (For example by using ``gettext.find``) +The ``get_gettext_translations`` function would return the translator +for the current configuration, for example by using ``gettext.find``. -The usage of the ``i18n`` extension for template designers is covered as part -:ref:`of the template documentation `. +The usage of the ``i18n`` extension for template designers is covered in +:ref:`the template documentation `. .. _gettext: https://docs.python.org/3/library/gettext.html .. _Babel: http://babel.pocoo.org/ -.. _newstyle-gettext: Whitespace Trimming ~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2.10 -Linebreaks and surrounding whitespace can be automatically trimmed by enabling -the ``ext.i18n.trimmed`` :ref:`policy `. +Within ``{% trans %}`` blocks, it can be useful to trim line breaks and +whitespace so that the block of text looks like a simple string with +single spaces in the translation file. + +Linebreaks and surrounding whitespace can be automatically trimmed by +enabling the ``ext.i18n.trimmed`` :ref:`policy `. -Newstyle Gettext -~~~~~~~~~~~~~~~~ +.. _newstyle-gettext: + +New Style 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. +New style gettext calls are less to type, less error prone, and support +autoescaping better. -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 are some common differences between old and new calls: +You can use "new style" gettext calls by setting +``env.newstyle_gettext = True`` or passing ``newstyle=True`` to +``env.install_translations``. They are fully supported by the Babel +extraction tool, but might not work as expected with other extraction +tools. -standard gettext: +With standard ``gettext`` calls, string formatting is a separate step +done with the ``|format`` filter. This requires duplicating work for +``ngettext`` calls. -.. sourcecode:: html+jinja +.. sourcecode:: jinja - {{ gettext('Hello World!') }} - {{ gettext('Hello %(name)s!')|format(name='World') }} - {{ ngettext('%(num)d apple', '%(num)d apples', apples|count)|format( - num=apples|count - )}} + {{ gettext("Hello, World!") }} + {{ gettext("Hello, %(name)s!")|format(name=name) }} + {{ ngettext( + "%(num)d apple", "%(num)d apples", apples|count + )|format(num=apples|count) }} -newstyle gettext looks like this instead: +New style ``gettext`` make formatting part of the call, and behind the +scenes enforce more consistency. -.. sourcecode:: html+jinja +.. sourcecode:: jinja + + {{ gettext("Hello, World!") }} + {{ gettext("Hello, %(name)s!", name=name) }} + {{ ngettext("%(num)d apple", "%(num)d apples", apples|count) }} + +The advantages of newstyle gettext are: + +- There's no separate formatting step, you don't have to remember to + use the ``|format`` filter. +- Only named placeholders are allowed. This solves a common problem + translators face because positional placeholders can't switch + positions meaningfully. Named placeholders always carry semantic + information about what value goes where. +- String formatting is used even if no placeholders are used, which + makes all strings use a consistent format. Remember to escape any + raw percent signs as ``%%``, such as ``100%%``. +- The translated string is marked safe, formatting performs escaping + as needed. Mark a parameter as ``|safe`` if it has already been + escaped. - {{ gettext('Hello World!') }} - {{ gettext('Hello %(name)s!', name='World') }} - {{ ngettext('%(num)d apple', '%(num)d apples', apples|count) }} - -The advantages of newstyle gettext are 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 409751a3..b2ce4440 100644 --- a/docs/templates.rst +++ b/docs/templates.rst @@ -1569,33 +1569,43 @@ extensions not covered by this documentation; in which case there should be a separate document explaining said :ref:`extensions `. + .. _i18n-in-templates: i18n ~~~~ -If the i18n extension is enabled, it's possible to mark parts in the template -as translatable. To mark a section as translatable, you can use `trans`:: +If the :ref:`i18n-extension` is enabled, it's possible to mark text in +the template as translatable. To mark a section as translatable, use a +``trans`` block: + +.. code-block:: jinja + + {% trans %}Hello, {{ user }}!{% endtrans %} + +Inside the block, no statements are allowed, only text and simple +variable tags. + +Variable tags can only be a name, not attribute access, filters, or +other expressions. To use an expression, bind it to a name in the +``trans`` tag for use in the block. -

{% trans %}Hello {{ user }}!{% endtrans %}

+.. code-block:: jinja -To translate a template expression --- say, using template filters, or by just -accessing an attribute of an object --- you need to bind the expression to a -name for use within the translation block:: + {% trans user=user.username %}Hello, {{ user }}!{% endtrans %} -

{% trans user=user.username %}Hello {{ user }}!{% endtrans %}

+To bind more than one expression, separate each with a comma (``,``). -If you need to bind more than one expression inside a `trans` tag, separate -the pieces with a comma (``,``):: +.. code-block:: jinja {% trans book_title=book.title, author=author.name %} This is {{ book_title }} by {{ author }} {% endtrans %} -Inside trans tags no statements are allowed, only variable tags are. +To pluralize, specify both the singular and plural forms separated by +the ``pluralize`` tag. -To pluralize, specify both the singular and plural forms with the `pluralize` -tag, which appears between `trans` and `endtrans`:: +.. code-block:: jinja {% trans count=list|length %} There is {{ count }} {{ name }} object. @@ -1603,60 +1613,70 @@ tag, which appears between `trans` and `endtrans`:: There are {{ count }} {{ name }} objects. {% endtrans %} -By default, the first variable in a block is used to determine the correct -singular or plural form. If that doesn't work out, you can specify the name -which should be used for pluralizing by adding it as parameter to `pluralize`:: +By default, the first variable in a block is used to determine whether +to use singular or plural form. If that isn't correct, specify the +variable used for pluralizing as a parameter to ``pluralize``. + +.. code-block:: jinja {% trans ..., user_count=users|length %}... {% pluralize user_count %}...{% endtrans %} -When translating longer blocks of text, whitespace and linebreaks result in -rather ugly and error-prone translation strings. To avoid this, a trans block -can be marked as trimmed which will replace all linebreaks and the whitespace -surrounding them with a single space and remove leading/trailing whitespace:: +When translating blocks of text, whitespace and linebreaks result in +hard to read and error-prone translation strings. To avoid this, a trans +block can be marked as trimmed, which will replace all linebreaks and +the whitespace surrounding them with a single space and remove leading +and trailing whitespace. + +.. code-block:: jinja {% trans trimmed book_title=book.title %} This is {{ book_title }}. You should read it! {% endtrans %} -If trimming is enabled globally, the `notrimmed` modifier can be used to -disable it for a `trans` block. +This results in ``This is %(book_title)s. You should read it!`` in the +translation file. + +If trimming is enabled globally, the ``notrimmed`` modifier can be used +to disable it for a block. .. versionadded:: 2.10 - The `trimmed` and `notrimmed` modifiers have been added. + The ``trimmed`` and ``notrimmed`` modifiers have been added. -It's also possible to translate strings in expressions. For that purpose, -three functions exist: +It's possible to translate strings in expressions with these functions: -- `gettext`: translate a single string -- `ngettext`: translate a pluralizable string -- `_`: alias for `gettext` +- ``gettext``: translate a single string +- ``ngettext``: translate a pluralizable string +- ``_``: alias for ``gettext`` -For example, you can easily print a translated string like this:: +You can print a translated string like this: - {{ _('Hello World!') }} +.. code-block:: jinja -To use placeholders, use the `format` filter:: + {{ _("Hello, World!") }} - {{ _('Hello %(user)s!')|format(user=user.username) }} +To use placeholders, use the ``format`` filter. -For multiple placeholders, always use keyword arguments to `format`, -as other languages may not use the words in the same order. +.. code-block:: jinja -.. versionchanged:: 2.5 + {{ _("Hello, %(user)s!")|format(user=user.username) }} -If newstyle gettext calls are activated (:ref:`newstyle-gettext`), using -placeholders is a lot easier: +Always use keyword arguments to ``format``, as other languages may not +use the words in the same order. -.. sourcecode:: html+jinja +If :ref:`newstyle-gettext` calls are activated, using placeholders is +easier. Formatting is part of the ``gettext`` call instead of using the +``format`` filter. + +.. sourcecode:: 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 receives -the count as a `num` parameter in addition to the regular parameters. +The ``ngettext`` function's format string automatically receives the +count as a ``num`` parameter in addition to the given parameters. Expression Statement diff --git a/jinja2/ext.py b/jinja2/ext.py index 8488e63f..54b0f778 100644 --- a/jinja2/ext.py +++ b/jinja2/ext.py @@ -148,6 +148,9 @@ def _make_new_gettext(func): rv = __context.call(func, __string) if __context.eval_ctx.autoescape: rv = Markup(rv) + # Always treat as a format string, even if there are no + # variables. This makes translation strings more consistent + # and predictable. This requires escaping return rv % variables return gettext @@ -159,13 +162,14 @@ def _make_new_ngettext(func): rv = __context.call(func, __singular, __plural, __num) if __context.eval_ctx.autoescape: rv = Markup(rv) + # Always treat as a format string, see gettext comment above. return rv % variables return ngettext class InternationalizationExtension(Extension): """This extension adds gettext support to Jinja2.""" - tags = set(['trans']) + tags = {'trans'} # TODO: the i18n extension is currently reevaluating values in a few # situations. Take this example: