Fixed #30585 -- Added {% translate %} and {% blocktranslate %} template tags.

This commit is contained in:
Mike Hansen 2019-06-21 09:41:01 -07:00 committed by Mariusz Felisiak
parent 70d95682b1
commit d291c72bf2
14 changed files with 346 additions and 175 deletions

View File

@ -98,7 +98,8 @@ class TranslateNode(Node):
class BlockTranslateNode(Node): class BlockTranslateNode(Node):
def __init__(self, extra_context, singular, plural=None, countervar=None, def __init__(self, extra_context, singular, plural=None, countervar=None,
counter=None, message_context=None, trimmed=False, asvar=None): counter=None, message_context=None, trimmed=False, asvar=None,
tag_name='blocktranslate'):
self.extra_context = extra_context self.extra_context = extra_context
self.singular = singular self.singular = singular
self.plural = plural self.plural = plural
@ -107,6 +108,7 @@ class BlockTranslateNode(Node):
self.message_context = message_context self.message_context = message_context
self.trimmed = trimmed self.trimmed = trimmed
self.asvar = asvar self.asvar = asvar
self.tag_name = tag_name
def render_token_list(self, tokens): def render_token_list(self, tokens):
result = [] result = []
@ -163,8 +165,8 @@ class BlockTranslateNode(Node):
if nested: if nested:
# Either string is malformed, or it's a bug # Either string is malformed, or it's a bug
raise TemplateSyntaxError( raise TemplateSyntaxError(
"'blocktrans' is unable to format string returned by gettext: %r using %r" '%r is unable to format string returned by gettext: %r '
% (result, data) 'using %r' % (self.tag_name, result, data)
) )
with translation.override(None): with translation.override(None):
result = self.render(context, nested=True) result = self.render(context, nested=True)
@ -313,6 +315,7 @@ def do_get_current_language_bidi(parser, token):
return GetCurrentLanguageBidiNode(args[2]) return GetCurrentLanguageBidiNode(args[2])
@register.tag("translate")
@register.tag("trans") @register.tag("trans")
def do_translate(parser, token): def do_translate(parser, token):
""" """
@ -406,6 +409,7 @@ def do_translate(parser, token):
return TranslateNode(message_string, noop, asvar, message_context) return TranslateNode(message_string, noop, asvar, message_context)
@register.tag("blocktranslate")
@register.tag("blocktrans") @register.tag("blocktrans")
def do_block_translate(parser, token): def do_block_translate(parser, token):
""" """
@ -513,19 +517,20 @@ def do_block_translate(parser, token):
break break
if countervar and counter: if countervar and counter:
if token.contents.strip() != 'plural': if token.contents.strip() != 'plural':
raise TemplateSyntaxError("'blocktrans' doesn't allow other block tags inside it") raise TemplateSyntaxError("%r doesn't allow other block tags inside it" % bits[0])
while parser.tokens: while parser.tokens:
token = parser.next_token() token = parser.next_token()
if token.token_type in (TokenType.VAR, TokenType.TEXT): if token.token_type in (TokenType.VAR, TokenType.TEXT):
plural.append(token) plural.append(token)
else: else:
break break
if token.contents.strip() != 'endblocktrans': end_tag_name = 'end%s' % bits[0]
raise TemplateSyntaxError("'blocktrans' doesn't allow other block tags (seen %r) inside it" % token.contents) if token.contents.strip() != end_tag_name:
raise TemplateSyntaxError("%r doesn't allow other block tags (seen %r) inside it" % (bits[0], token.contents))
return BlockTranslateNode(extra_context, singular, plural, countervar, return BlockTranslateNode(extra_context, singular, plural, countervar,
counter, message_context, trimmed=trimmed, counter, message_context, trimmed=trimmed,
asvar=asvar) asvar=asvar, tag_name=bits[0])
@register.tag @register.tag

View File

@ -19,15 +19,15 @@ def blankout(src, char):
context_re = _lazy_re_compile(r"""^\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?'))\s*""") context_re = _lazy_re_compile(r"""^\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?'))\s*""")
inline_re = _lazy_re_compile( inline_re = _lazy_re_compile(
# Match the trans 'some text' part # Match the trans/translate 'some text' part.
r"""^\s*trans\s+((?:"[^"]*?")|(?:'[^']*?'))""" r"""^\s*trans(?:late)?\s+((?:"[^"]*?")|(?:'[^']*?'))"""
# Match and ignore optional filters # Match and ignore optional filters
r"""(?:\s*\|\s*[^\s:]+(?::(?:[^\s'":]+|(?:"[^"]*?")|(?:'[^']*?')))?)*""" r"""(?:\s*\|\s*[^\s:]+(?::(?:[^\s'":]+|(?:"[^"]*?")|(?:'[^']*?')))?)*"""
# Match the optional context part # Match the optional context part
r"""(\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?')))?\s*""" r"""(\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?')))?\s*"""
) )
block_re = _lazy_re_compile(r"""^\s*blocktrans(\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?')))?(?:\s+|$)""") block_re = _lazy_re_compile(r"""^\s*blocktrans(?:late)?(\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?')))?(?:\s+|$)""")
endblock_re = _lazy_re_compile(r"""^\s*endblocktrans$""") endblock_re = _lazy_re_compile(r"""^\s*endblocktrans(?:late)?$""")
plural_re = _lazy_re_compile(r"""^\s*plural$""") plural_re = _lazy_re_compile(r"""^\s*plural$""")
constant_re = _lazy_re_compile(r"""_\(((?:".*?")|(?:'.*?'))\)""") constant_re = _lazy_re_compile(r"""_\(((?:".*?")|(?:'.*?'))\)""")

View File

@ -2695,14 +2695,14 @@ Therefore here is our new ``change_form.html`` :
{% load i18n admin_urls %} {% load i18n admin_urls %}
{% block object-tools-items %} {% block object-tools-items %}
<li> <li>
<a href="{% url opts|admin_urlname:'history' original.pk|admin_urlquote %}" class="historylink">{% trans "History" %}</a> <a href="{% url opts|admin_urlname:'history' original.pk|admin_urlquote %}" class="historylink">{% translate "History" %}</a>
</li> </li>
<li> <li>
<a href="mylink/" class="historylink">My Link</a> <a href="mylink/" class="historylink">My Link</a>
</li> </li>
{% if has_absolute_url %} {% if has_absolute_url %}
<li> <li>
<a href="{% url 'admin:view_on_site' content_type_id original.pk %}" class="viewsitelink">{% trans "View on site" %}</a> <a href="{% url 'admin:view_on_site' content_type_id original.pk %}" class="viewsitelink">{% translate "View on site" %}</a>
</li> </li>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -814,10 +814,10 @@ This would display as "It is the 4th of September".
You can also use the syntax ``{% now "Y" as current_year %}`` to store the You can also use the syntax ``{% now "Y" as current_year %}`` to store the
output (as a string) inside a variable. This is useful if you want to use output (as a string) inside a variable. This is useful if you want to use
``{% now %}`` inside a template tag like :ttag:`blocktrans` for example:: ``{% now %}`` inside a template tag like :ttag:`blocktranslate` for example::
{% now "Y" as current_year %} {% now "Y" as current_year %}
{% blocktrans %}Copyright {{ current_year }}{% endblocktrans %} {% blocktranslate %}Copyright {{ current_year }}{% endblocktranslate %}
.. templatetag:: regroup .. templatetag:: regroup
@ -1200,10 +1200,10 @@ image in the above example will be 88 pixels wide
(because 175/200 = .875; .875 * 100 = 87.5 which is rounded up to 88). (because 175/200 = .875; .875 * 100 = 87.5 which is rounded up to 88).
In some cases you might want to capture the result of ``widthratio`` in a In some cases you might want to capture the result of ``widthratio`` in a
variable. It can be useful, for instance, in a :ttag:`blocktrans` like this:: variable. It can be useful, for instance, in a :ttag:`blocktranslate` like this::
{% widthratio this_value max_value max_width as width %} {% widthratio this_value max_value max_width as width %}
{% blocktrans %}The width is: {{ width }}{% endblocktrans %} {% blocktranslate %}The width is: {{ width }}{% endblocktranslate %}
.. templatetag:: with .. templatetag:: with
@ -2023,7 +2023,7 @@ Example::
You have {{ num_cherries }} cherr{{ num_cherries|pluralize:"y,ies" }}. You have {{ num_cherries }} cherr{{ num_cherries|pluralize:"y,ies" }}.
.. note:: Use :ttag:`blocktrans` to pluralize translated strings. .. note:: Use :ttag:`blocktranslate` to pluralize translated strings.
.. templatefilter:: pprint .. templatefilter:: pprint

View File

@ -414,7 +414,7 @@ Here are some tips for working with inheritance:
tag ``as`` syntax can't be used inside the block. For example, this template tag ``as`` syntax can't be used inside the block. For example, this template
doesn't render anything:: doesn't render anything::
{% trans "Title" as title %} {% translate "Title" as title %}
{% block content %}{{ title }}{% endblock %} {% block content %}{{ title }}{% endblock %}
* For extra readability, you can optionally give a *name* to your * For extra readability, you can optionally give a *name* to your

View File

@ -260,7 +260,10 @@ Signals
Templates Templates
~~~~~~~~~ ~~~~~~~~~
* ... * The renamed :ttag:`translate` and :ttag:`blocktranslate` template tags are
introduced for internationalization in template code. The older :ttag:`trans`
and :ttag:`blocktrans` template tags aliases continue to work, and will be
retained for the foreseeable future.
Tests Tests
~~~~~ ~~~~~

View File

@ -682,7 +682,7 @@ templates to achieve the same result:
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
{% cache 600 welcome LANGUAGE_CODE %} {% cache 600 welcome LANGUAGE_CODE %}
{% trans "Welcome to example.com" %} {% translate "Welcome to example.com" %}
{% endcache %} {% endcache %}
The cache timeout can be a template variable, as long as the template variable The cache timeout can be a template variable, as long as the template variable

View File

@ -328,8 +328,8 @@ will appear in the ``.po`` file as:
msgid "May" msgid "May"
msgstr "" msgstr ""
Contextual markers are also supported by the :ttag:`trans` and Contextual markers are also supported by the :ttag:`translate` and
:ttag:`blocktrans` template tags. :ttag:`blocktranslate` template tags.
.. _lazy-translations: .. _lazy-translations:
@ -575,21 +575,22 @@ have already loaded the ``i18n`` tag.
unchanged. unchanged.
.. templatetag:: trans .. templatetag:: trans
.. templatetag:: translate
``trans`` template tag ``translate`` template tag
---------------------- --------------------------
The ``{% trans %}`` template tag translates either a constant string The ``{% translate %}`` template tag translates either a constant string
(enclosed in single or double quotes) or variable content:: (enclosed in single or double quotes) or variable content::
<title>{% trans "This is the title." %}</title> <title>{% translate "This is the title." %}</title>
<title>{% trans myvar %}</title> <title>{% translate myvar %}</title>
If the ``noop`` option is present, variable lookup still takes place but the If the ``noop`` option is present, variable lookup still takes place but the
translation is skipped. This is useful when "stubbing out" content that will translation is skipped. This is useful when "stubbing out" content that will
require translation in the future:: require translation in the future::
<title>{% trans "myvar" noop %}</title> <title>{% translate "myvar" noop %}</title>
Internally, inline translations use an Internally, inline translations use an
:func:`~django.utils.translation.gettext` call. :func:`~django.utils.translation.gettext` call.
@ -598,15 +599,14 @@ In case a template var (``myvar`` above) is passed to the tag, the tag will
first resolve such variable to a string at run-time and then look up that first resolve such variable to a string at run-time and then look up that
string in the message catalogs. string in the message catalogs.
It's not possible to mix a template variable inside a string within ``{% trans It's not possible to mix a template variable inside a string within
%}``. If your translations require strings with variables (placeholders), use ``{% translate %}``. If your translations require strings with variables
:ttag:`{% blocktrans %}<blocktrans>` instead. (placeholders), use :ttag:`{% blocktranslate %}<blocktranslate>` instead.
If you'd like to retrieve a translated string without displaying it, you can If you'd like to retrieve a translated string without displaying it, you can
use the following syntax:: use the following syntax::
{% trans "This is the title" as the_title %} {% translate "This is the title" as the_title %}
<title>{{ the_title }}</title> <title>{{ the_title }}</title>
<meta name="description" content="{{ the_title }}"> <meta name="description" content="{{ the_title }}">
@ -615,12 +615,12 @@ In practice you'll use this to get a string you can use in multiple places in a
template or so you can use the output as an argument for other template tags or template or so you can use the output as an argument for other template tags or
filters:: filters::
{% trans "starting point" as start %} {% translate "starting point" as start %}
{% trans "end point" as end %} {% translate "end point" as end %}
{% trans "La Grande Boucle" as race %} {% translate "La Grande Boucle" as race %}
<h1> <h1>
<a href="/" title="{% blocktrans %}Back to '{{ race }}' homepage{% endblocktrans %}">{{ race }}</a> <a href="/" title="{% blocktranslate %}Back to '{{ race }}' homepage{% endblocktranslate %}">{{ race }}</a>
</h1> </h1>
<p> <p>
{% for stage in tour_stages %} {% for stage in tour_stages %}
@ -628,50 +628,56 @@ filters::
{% endfor %} {% endfor %}
</p> </p>
``{% trans %}`` also supports :ref:`contextual markers<contextual-markers>` ``{% translate %}`` also supports :ref:`contextual markers<contextual-markers>`
using the ``context`` keyword: using the ``context`` keyword:
.. code-block:: html+django .. code-block:: html+django
{% trans "May" context "month name" %} {% translate "May" context "month name" %}
.. versionchanged:: 3.1
The ``trans`` tag was renamed to ``translate``. The ``trans``
tag is still supported as an alias for backwards compatibility.
.. templatetag:: blocktrans .. templatetag:: blocktrans
.. templatetag:: blocktranslate
``blocktrans`` template tag ``blocktranslate`` template tag
--------------------------- -------------------------------
Contrarily to the :ttag:`trans` tag, the ``blocktrans`` tag allows you to mark Contrarily to the :ttag:`translate` tag, the ``blocktranslate`` tag allows you
complex sentences consisting of literals and variable content for translation to mark complex sentences consisting of literals and variable content for
by making use of placeholders:: translation by making use of placeholders::
{% blocktrans %}This string will have {{ value }} inside.{% endblocktrans %} {% blocktranslate %}This string will have {{ value }} inside.{% endblocktranslate %}
To translate a template expression -- say, accessing object attributes or To translate a template expression -- say, accessing object attributes or
using template filters -- you need to bind the expression to a local variable using template filters -- you need to bind the expression to a local variable
for use within the translation block. Examples:: for use within the translation block. Examples::
{% blocktrans with amount=article.price %} {% blocktranslate with amount=article.price %}
That will cost $ {{ amount }}. That will cost $ {{ amount }}.
{% endblocktrans %} {% endblocktranslate %}
{% blocktrans with myvar=value|filter %} {% blocktranslate with myvar=value|filter %}
This will have {{ myvar }} inside. This will have {{ myvar }} inside.
{% endblocktrans %} {% endblocktranslate %}
You can use multiple expressions inside a single ``blocktrans`` tag:: You can use multiple expressions inside a single ``blocktranslate`` tag::
{% blocktrans with book_t=book|title author_t=author|title %} {% blocktranslate with book_t=book|title author_t=author|title %}
This is {{ book_t }} by {{ author_t }} This is {{ book_t }} by {{ author_t }}
{% endblocktrans %} {% endblocktranslate %}
.. note:: The previous more verbose format is still supported: .. note:: The previous more verbose format is still supported:
``{% blocktrans with book|title as book_t and author|title as author_t %}`` ``{% blocktranslate with book|title as book_t and author|title as author_t %}``
Other block tags (for example ``{% for %}`` or ``{% if %}``) are not allowed Other block tags (for example ``{% for %}`` or ``{% if %}``) are not allowed
inside a ``blocktrans`` tag. inside a ``blocktranslate`` tag.
If resolving one of the block arguments fails, ``blocktrans`` will fall back to If resolving one of the block arguments fails, ``blocktranslate`` will fall
the default language by deactivating the currently active language back to the default language by deactivating the currently active language
temporarily with the :func:`~django.utils.translation.deactivate_all` temporarily with the :func:`~django.utils.translation.deactivate_all`
function. function.
@ -681,43 +687,43 @@ This tag also provides for pluralization. To use it:
be the one used to select the right plural form. be the one used to select the right plural form.
* Specify both the singular and plural forms separating them with the * Specify both the singular and plural forms separating them with the
``{% plural %}`` tag within the ``{% blocktrans %}`` and ``{% plural %}`` tag within the ``{% blocktranslate %}`` and
``{% endblocktrans %}`` tags. ``{% endblocktranslate %}`` tags.
An example:: An example::
{% blocktrans count counter=list|length %} {% blocktranslate count counter=list|length %}
There is only one {{ name }} object. There is only one {{ name }} object.
{% plural %} {% plural %}
There are {{ counter }} {{ name }} objects. There are {{ counter }} {{ name }} objects.
{% endblocktrans %} {% endblocktranslate %}
A more complex example:: A more complex example::
{% blocktrans with amount=article.price count years=i.length %} {% blocktranslate with amount=article.price count years=i.length %}
That will cost $ {{ amount }} per year. That will cost $ {{ amount }} per year.
{% plural %} {% plural %}
That will cost $ {{ amount }} per {{ years }} years. That will cost $ {{ amount }} per {{ years }} years.
{% endblocktrans %} {% endblocktranslate %}
When you use both the pluralization feature and bind values to local variables When you use both the pluralization feature and bind values to local variables
in addition to the counter value, keep in mind that the ``blocktrans`` in addition to the counter value, keep in mind that the ``blocktranslate``
construct is internally converted to an ``ngettext`` call. This means the construct is internally converted to an ``ngettext`` call. This means the
same :ref:`notes regarding ngettext variables <pluralization-var-notes>` same :ref:`notes regarding ngettext variables <pluralization-var-notes>`
apply. apply.
Reverse URL lookups cannot be carried out within the ``blocktrans`` and should Reverse URL lookups cannot be carried out within the ``blocktranslate`` and
be retrieved (and stored) beforehand:: should be retrieved (and stored) beforehand::
{% url 'path.to.view' arg arg2 as the_url %} {% url 'path.to.view' arg arg2 as the_url %}
{% blocktrans %} {% blocktranslate %}
This is a URL: {{ the_url }} This is a URL: {{ the_url }}
{% endblocktrans %} {% endblocktranslate %}
If you'd like to retrieve a translated string without displaying it, you can If you'd like to retrieve a translated string without displaying it, you can
use the following syntax:: use the following syntax::
{% blocktrans asvar the_title %}The title is {{ title }}.{% endblocktrans %} {% blocktranslate asvar the_title %}The title is {{ title }}.{% endblocktranslate %}
<title>{{ the_title }}</title> <title>{{ the_title }}</title>
<meta name="description" content="{{ the_title }}"> <meta name="description" content="{{ the_title }}">
@ -725,32 +731,38 @@ In practice you'll use this to get a string you can use in multiple places in a
template or so you can use the output as an argument for other template tags or template or so you can use the output as an argument for other template tags or
filters. filters.
``{% blocktrans %}`` also supports :ref:`contextual ``{% blocktranslate %}`` also supports :ref:`contextual
markers<contextual-markers>` using the ``context`` keyword: markers<contextual-markers>` using the ``context`` keyword:
.. code-block:: html+django .. code-block:: html+django
{% blocktrans with name=user.username context "greeting" %}Hi {{ name }}{% endblocktrans %} {% blocktranslate with name=user.username context "greeting" %}Hi {{ name }}{% endblocktranslate %}
Another feature ``{% blocktrans %}`` supports is the ``trimmed`` option. This Another feature ``{% blocktranslate %}`` supports is the ``trimmed`` option.
option will remove newline characters from the beginning and the end of the This option will remove newline characters from the beginning and the end of
content of the ``{% blocktrans %}`` tag, replace any whitespace at the beginning the content of the ``{% blocktranslate %}`` tag, replace any whitespace at the
and end of a line and merge all lines into one using a space character to beginning and end of a line and merge all lines into one using a space
separate them. This is quite useful for indenting the content of a ``{% character to separate them. This is quite useful for indenting the content of a
blocktrans %}`` tag without having the indentation characters end up in the ``{% blocktranslate %}`` tag without having the indentation characters end up
corresponding entry in the PO file, which makes the translation process easier. in the corresponding entry in the PO file, which makes the translation process
easier.
For instance, the following ``{% blocktrans %}`` tag:: For instance, the following ``{% blocktranslate %}`` tag::
{% blocktrans trimmed %} {% blocktranslate trimmed %}
First sentence. First sentence.
Second paragraph. Second paragraph.
{% endblocktrans %} {% endblocktranslate %}
will result in the entry ``"First sentence. Second paragraph."`` in the PO file, will result in the entry ``"First sentence. Second paragraph."`` in the PO file,
compared to ``"\n First sentence.\n Second sentence.\n"``, if the ``trimmed`` compared to ``"\n First sentence.\n Second sentence.\n"``, if the ``trimmed``
option had not been specified. option had not been specified.
.. versionchanged:: 3.1
The ``blocktrans`` tag was renamed to ``blocktranslate``. The ``blocktrans``
tag is still supported as an alias for backwards compatibility.
String literals passed to tags and filters String literals passed to tags and filters
------------------------------------------ ------------------------------------------
@ -782,21 +794,21 @@ tag:
.. code-block:: html+django .. code-block:: html+django
{% comment %}Translators: View verb{% endcomment %} {% comment %}Translators: View verb{% endcomment %}
{% trans "View" %} {% translate "View" %}
{% comment %}Translators: Short intro blurb{% endcomment %} {% comment %}Translators: Short intro blurb{% endcomment %}
<p>{% blocktrans %}A multiline translatable <p>{% blocktranslate %}A multiline translatable
literal.{% endblocktrans %}</p> literal.{% endblocktranslate %}</p>
or with the ``{#`` ... ``#}`` :ref:`one-line comment constructs <template-comments>`: or with the ``{#`` ... ``#}`` :ref:`one-line comment constructs <template-comments>`:
.. code-block:: html+django .. code-block:: html+django
{# Translators: Label of a button that triggers search #} {# Translators: Label of a button that triggers search #}
<button type="submit">{% trans "Go" %}</button> <button type="submit">{% translate "Go" %}</button>
{# Translators: This is a text of the base template #} {# Translators: This is a text of the base template #}
{% blocktrans %}Ambiguous translatable block of text{% endblocktrans %} {% blocktranslate %}Ambiguous translatable block of text{% endblocktranslate %}
.. note:: Just for completeness, these are the corresponding fragments of the .. note:: Just for completeness, these are the corresponding fragments of the
resulting ``.po`` file: resulting ``.po`` file:
@ -841,12 +853,12 @@ If you want to select a language within a template, you can use the
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} --> <!-- Current language: {{ LANGUAGE_CODE }} -->
<p>{% trans "Welcome to our page" %}</p> <p>{% translate "Welcome to our page" %}</p>
{% language 'en' %} {% language 'en' %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} --> <!-- Current language: {{ LANGUAGE_CODE }} -->
<p>{% trans "Welcome to our page" %}</p> <p>{% translate "Welcome to our page" %}</p>
{% endlanguage %} {% endlanguage %}
While the first occurrence of "Welcome to our page" uses the current language, While the first occurrence of "Welcome to our page" uses the current language,
@ -1450,7 +1462,7 @@ template tag. It enables the given language in the enclosed template section:
{% get_available_languages as languages %} {% get_available_languages as languages %}
{% trans "View this category in:" %} {% translate "View this category in:" %}
{% for lang_code, lang_name in languages %} {% for lang_code, lang_name in languages %}
{% language lang_code %} {% language lang_code %}
<a href="{% url 'category' slug=category.slug %}">{{ lang_name }}</a> <a href="{% url 'category' slug=category.slug %}">{{ lang_name }}</a>

View File

@ -5,4 +5,4 @@ shouldn't create a .po file with duplicate `Plural-Forms` headers
{% endcomment %} {% endcomment %}
{% blocktrans count number=3 %}{{ number }} Bar{% plural %}{{ number }} Bars{% endblocktrans %} {% blocktrans count number=3 %}{{ number }} Bar{% plural %}{{ number }} Bars{% endblocktrans %}
{% trans 'First `trans`, then `blocktrans` with a plural' %} {% translate 'First `translate`, then `blocktranslate` with a plural' %}

View File

@ -85,9 +85,11 @@ continued here.{% endcomment %}
{% blocktrans context 'Special blocktrans context wrapped in single quotes' %}Translatable literal with context wrapped in single quotes{% endblocktrans %} {% blocktrans context 'Special blocktrans context wrapped in single quotes' %}Translatable literal with context wrapped in single quotes{% endblocktrans %}
{% blocktrans context "Special blocktrans context wrapped in double quotes" %}Translatable literal with context wrapped in double quotes{% endblocktrans %} {% blocktrans context "Special blocktrans context wrapped in double quotes" %}Translatable literal with context wrapped in double quotes{% endblocktrans %}
{% blocktranslate %}blocktranslate text{% endblocktranslate %}
{% translate "translate text" %}
{# BasicExtractorTests.test_blocktrans_trimmed #} {# BasicExtractorTests.test_blocktranslate_trimmed #}
{% blocktrans %} {% blocktranslate %}
Text with a few Text with a few
line breaks. line breaks.
{% endblocktrans %} {% endblocktrans %}
@ -98,10 +100,10 @@ continued here.{% endcomment %}
{% endblocktrans %} {% endblocktrans %}
{% trans "Get my line number" %} {% trans "Get my line number" %}
{% blocktrans trimmed count counter=mylist|length %} {% blocktranslate trimmed count counter=mylist|length %}
First `trans`, then `blocktrans` with a plural First `translate`, then `blocktranslate` with a plural
{% plural %} {% plural %}
Plural for a `trans` and `blocktrans` collision case Plural for a `translate` and `blocktranslate` collision case
{% endblocktrans %} {% endblocktranslate %}
{% trans "Non-breaking space :" %} {% trans "Non-breaking space :" %}

View File

@ -8,12 +8,13 @@ by using catalogs created from management commands.
Example: Example:
The string "Two %% Three %%%" renders differently using trans and blocktrans. The string "Two %% Three %%%" renders differently using translate and
This issue is difficult to debug, it could be a problem with extraction, blocktranslate. This issue is difficult to debug, it could be a problem with
interpolation, or both. extraction, interpolation, or both.
How this script helps: How this script helps:
* Add {% trans "Two %% Three %%%" %} and blocktrans equivalent to templates. * Add {% translate "Two %% Three %%%" %} and blocktranslate equivalent to
templates.
* Run this script. * Run this script.
* Test extraction - verify the new msgid in sampleproject's django.po. * Test extraction - verify the new msgid in sampleproject's django.po.
* Add a translation to sampleproject's django.po. * Add a translation to sampleproject's django.po.

View File

@ -182,7 +182,7 @@ class BasicExtractorTests(ExtractorTests):
po_contents = fp.read() po_contents = fp.read()
self.assertMsgId("Non-breaking space\u00a0:", po_contents) self.assertMsgId("Non-breaking space\u00a0:", po_contents)
def test_blocktrans_trimmed(self): def test_blocktranslate_trimmed(self):
management.call_command('makemessages', locale=[LOCALE], verbosity=0) management.call_command('makemessages', locale=[LOCALE], verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE)) self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE) as fp: with open(self.PO_FILE) as fp:
@ -256,6 +256,10 @@ class BasicExtractorTests(ExtractorTests):
self.assertIn('msgctxt "Special blocktrans context #4"', po_contents) self.assertIn('msgctxt "Special blocktrans context #4"', po_contents)
self.assertMsgId("Translatable literal #8d %(a)s", po_contents) self.assertMsgId("Translatable literal #8d %(a)s", po_contents)
# {% translate %} and {% blocktranslate %}
self.assertMsgId('translate text', po_contents)
self.assertMsgId('blocktranslate text', po_contents)
def test_context_in_single_quotes(self): def test_context_in_single_quotes(self):
management.call_command('makemessages', locale=[LOCALE], verbosity=0) management.call_command('makemessages', locale=[LOCALE], verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE)) self.assertTrue(os.path.exists(self.PO_FILE))
@ -528,7 +532,7 @@ class CopyPluralFormsExtractorTests(ExtractorTests):
found = re.findall(r'^(?P<value>"Plural-Forms.+?\\n")\s*$', po_contents, re.MULTILINE | re.DOTALL) found = re.findall(r'^(?P<value>"Plural-Forms.+?\\n")\s*$', po_contents, re.MULTILINE | re.DOTALL)
self.assertEqual(1, len(found)) self.assertEqual(1, len(found))
def test_trans_and_plural_blocktrans_collision(self): def test_translate_and_plural_blocktranslate_collision(self):
""" """
Ensures a correct workaround for the gettext bug when handling a literal Ensures a correct workaround for the gettext bug when handling a literal
found inside a {% trans %} tag and also in another file inside a found inside a {% trans %} tag and also in another file inside a
@ -539,8 +543,8 @@ class CopyPluralFormsExtractorTests(ExtractorTests):
with open(self.PO_FILE) as fp: with open(self.PO_FILE) as fp:
po_contents = fp.read() po_contents = fp.read()
self.assertNotIn("#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\\n", po_contents) self.assertNotIn("#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\\n", po_contents)
self.assertMsgId('First `trans`, then `blocktrans` with a plural', po_contents) self.assertMsgId('First `translate`, then `blocktranslate` with a plural', po_contents)
self.assertMsgIdPlural('Plural for a `trans` and `blocktrans` collision case', po_contents) self.assertMsgIdPlural('Plural for a `translate` and `blocktranslate` collision case', po_contents)
class NoWrapExtractorTests(ExtractorTests): class NoWrapExtractorTests(ExtractorTests):

View File

@ -1,4 +1,6 @@
import inspect
import os import os
from functools import partial, wraps
from asgiref.local import Local from asgiref.local import Local
@ -8,10 +10,39 @@ from django.utils import translation
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import trans_real from django.utils.translation import trans_real
from ...utils import setup from ...utils import setup as base_setup
from .base import MultipleLocaleActivationTestCase, extended_locale_paths, here from .base import MultipleLocaleActivationTestCase, extended_locale_paths, here
def setup(templates, *args, **kwargs):
blocktrans_setup = base_setup(templates, *args, **kwargs)
blocktranslate_setup = base_setup({
name: template.replace(
'{% blocktrans ', '{% blocktranslate '
).replace(
'{% endblocktrans %}', '{% endblocktranslate %}'
)
for name, template in templates.items()
})
tags = {
'blocktrans': blocktrans_setup,
'blocktranslate': blocktranslate_setup,
}
def decorator(func):
@wraps(func)
def inner(self, *args):
signature = inspect.signature(func)
for tag_name, setup_func in tags.items():
if 'tag_name' in signature.parameters:
setup_func(partial(func, tag_name=tag_name))(self)
else:
setup_func(func)(self)
return inner
return decorator
class I18nBlockTransTagTests(SimpleTestCase): class I18nBlockTransTagTests(SimpleTestCase):
libraries = {'i18n': 'django.templatetags.i18n'} libraries = {'i18n': 'django.templatetags.i18n'}
@ -76,8 +107,8 @@ class I18nBlockTransTagTests(SimpleTestCase):
'{% blocktrans with berta=anton|escape %}{{ berta }}{% endblocktrans %}'}) '{% blocktrans with berta=anton|escape %}{{ berta }}{% endblocktrans %}'})
def test_i18n17(self): def test_i18n17(self):
""" """
Escaping inside blocktrans and trans works as if it was directly in the Escaping inside blocktranslate and translate works as if it was
template. directly in the template.
""" """
output = self.engine.render_to_string('i18n17', {'anton': 'α & β'}) output = self.engine.render_to_string('i18n17', {'anton': 'α & β'})
self.assertEqual(output, 'α &amp; β') self.assertEqual(output, 'α &amp; β')
@ -224,8 +255,8 @@ class I18nBlockTransTagTests(SimpleTestCase):
self.assertEqual(output, '>Error: Seite nicht gefunden<') self.assertEqual(output, '>Error: Seite nicht gefunden<')
@setup({'template': '{% load i18n %}{% blocktrans asvar %}Yes{% endblocktrans %}'}) @setup({'template': '{% load i18n %}{% blocktrans asvar %}Yes{% endblocktrans %}'})
def test_blocktrans_syntax_error_missing_assignment(self): def test_blocktrans_syntax_error_missing_assignment(self, tag_name):
msg = "No argument provided to the 'blocktrans' tag for the asvar option." msg = "No argument provided to the '{}' tag for the asvar option.".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template') self.engine.render_to_string('template')
@ -235,14 +266,14 @@ class I18nBlockTransTagTests(SimpleTestCase):
self.assertEqual(output, '%s') self.assertEqual(output, '%s')
@setup({'template': '{% load i18n %}{% blocktrans %}{% block b %} {% endblock %}{% endblocktrans %}'}) @setup({'template': '{% load i18n %}{% blocktrans %}{% block b %} {% endblock %}{% endblocktrans %}'})
def test_with_block(self): def test_with_block(self, tag_name):
msg = "'blocktrans' doesn't allow other block tags (seen 'block b') inside it" msg = "'{}' doesn't allow other block tags (seen 'block b') inside it".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template') self.engine.render_to_string('template')
@setup({'template': '{% load i18n %}{% blocktrans %}{% for b in [1, 2, 3] %} {% endfor %}{% endblocktrans %}'}) @setup({'template': '{% load i18n %}{% blocktrans %}{% for b in [1, 2, 3] %} {% endfor %}{% endblocktrans %}'})
def test_with_for(self): def test_with_for(self, tag_name):
msg = "'blocktrans' doesn't allow other block tags (seen 'for b in [1, 2, 3]') inside it" msg = "'{}' doesn't allow other block tags (seen 'for b in [1, 2, 3]') inside it".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template') self.engine.render_to_string('template')
@ -252,14 +283,14 @@ class I18nBlockTransTagTests(SimpleTestCase):
self.engine.render_to_string('template', {'foo': 'bar'}) self.engine.render_to_string('template', {'foo': 'bar'})
@setup({'template': '{% load i18n %}{% blocktrans with %}{% endblocktrans %}'}) @setup({'template': '{% load i18n %}{% blocktrans with %}{% endblocktrans %}'})
def test_no_args_with(self): def test_no_args_with(self, tag_name):
msg = '"with" in \'blocktrans\' tag needs at least one keyword argument.' msg = '"with" in \'{}\' tag needs at least one keyword argument.'.format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template') self.engine.render_to_string('template')
@setup({'template': '{% load i18n %}{% blocktrans count a %}{% endblocktrans %}'}) @setup({'template': '{% load i18n %}{% blocktrans count a %}{% endblocktrans %}'})
def test_count(self): def test_count(self, tag_name):
msg = '"count" in \'blocktrans\' tag expected exactly one keyword argument.' msg = '"count" in \'{}\' tag expected exactly one keyword argument.'.format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template', {'a': [1, 2, 3]}) self.engine.render_to_string('template', {'a': [1, 2, 3]})
@ -268,13 +299,25 @@ class I18nBlockTransTagTests(SimpleTestCase):
'There is {{ count }} object. {% block a %} {% endblock %}' 'There is {{ count }} object. {% block a %} {% endblock %}'
'{% endblocktrans %}' '{% endblocktrans %}'
)}) )})
def test_plural_bad_syntax(self): def test_plural_bad_syntax(self, tag_name):
msg = "'blocktrans' doesn't allow other block tags inside it" msg = "'{}' doesn't allow other block tags inside it".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template', {'var': [1, 2, 3]}) self.engine.render_to_string('template', {'var': [1, 2, 3]})
class TranslationBlockTransTagTests(SimpleTestCase): class TranslationBlockTransTagTests(SimpleTestCase):
tag_name = 'blocktrans'
def get_template(self, template_string):
return Template(
template_string.replace(
'{{% blocktrans ',
'{{% {}'.format(self.tag_name)
).replace(
'{{% endblocktrans %}}',
'{{% end{} %}}'.format(self.tag_name)
)
)
@override_settings(LOCALE_PATHS=extended_locale_paths) @override_settings(LOCALE_PATHS=extended_locale_paths)
def test_template_tags_pgettext(self): def test_template_tags_pgettext(self):
@ -283,54 +326,58 @@ class TranslationBlockTransTagTests(SimpleTestCase):
trans_real._translations = {} trans_real._translations = {}
with translation.override('de'): with translation.override('de'):
# Nonexistent context # Nonexistent context
t = Template('{% load i18n %}{% blocktrans context "nonexistent" %}May{% endblocktrans %}') t = self.get_template('{% load i18n %}{% blocktrans context "nonexistent" %}May{% endblocktrans %}')
rendered = t.render(Context()) rendered = t.render(Context())
self.assertEqual(rendered, 'May') self.assertEqual(rendered, 'May')
# Existing context... using a literal # Existing context... using a literal
t = Template('{% load i18n %}{% blocktrans context "month name" %}May{% endblocktrans %}') t = self.get_template('{% load i18n %}{% blocktrans context "month name" %}May{% endblocktrans %}')
rendered = t.render(Context()) rendered = t.render(Context())
self.assertEqual(rendered, 'Mai') self.assertEqual(rendered, 'Mai')
t = Template('{% load i18n %}{% blocktrans context "verb" %}May{% endblocktrans %}') t = self.get_template('{% load i18n %}{% blocktrans context "verb" %}May{% endblocktrans %}')
rendered = t.render(Context()) rendered = t.render(Context())
self.assertEqual(rendered, 'Kann') self.assertEqual(rendered, 'Kann')
# Using a variable # Using a variable
t = Template('{% load i18n %}{% blocktrans context message_context %}May{% endblocktrans %}') t = self.get_template('{% load i18n %}{% blocktrans context message_context %}May{% endblocktrans %}')
rendered = t.render(Context({'message_context': 'month name'})) rendered = t.render(Context({'message_context': 'month name'}))
self.assertEqual(rendered, 'Mai') self.assertEqual(rendered, 'Mai')
t = Template('{% load i18n %}{% blocktrans context message_context %}May{% endblocktrans %}') t = self.get_template('{% load i18n %}{% blocktrans context message_context %}May{% endblocktrans %}')
rendered = t.render(Context({'message_context': 'verb'})) rendered = t.render(Context({'message_context': 'verb'}))
self.assertEqual(rendered, 'Kann') self.assertEqual(rendered, 'Kann')
# Using a filter # Using a filter
t = Template('{% load i18n %}{% blocktrans context message_context|lower %}May{% endblocktrans %}') t = self.get_template(
'{% load i18n %}{% blocktrans context message_context|lower %}May{% endblocktrans %}'
)
rendered = t.render(Context({'message_context': 'MONTH NAME'})) rendered = t.render(Context({'message_context': 'MONTH NAME'}))
self.assertEqual(rendered, 'Mai') self.assertEqual(rendered, 'Mai')
t = Template('{% load i18n %}{% blocktrans context message_context|lower %}May{% endblocktrans %}') t = self.get_template(
'{% load i18n %}{% blocktrans context message_context|lower %}May{% endblocktrans %}'
)
rendered = t.render(Context({'message_context': 'VERB'})) rendered = t.render(Context({'message_context': 'VERB'}))
self.assertEqual(rendered, 'Kann') self.assertEqual(rendered, 'Kann')
# Using 'count' # Using 'count'
t = Template( t = self.get_template(
'{% load i18n %}{% blocktrans count number=1 context "super search" %}' '{% load i18n %}{% blocktrans count number=1 context "super search" %}'
'{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}' '{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}'
) )
rendered = t.render(Context()) rendered = t.render(Context())
self.assertEqual(rendered, '1 Super-Ergebnis') self.assertEqual(rendered, '1 Super-Ergebnis')
t = Template( t = self.get_template(
'{% load i18n %}{% blocktrans count number=2 context "super search" %}{{ number }}' '{% load i18n %}{% blocktrans count number=2 context "super search" %}{{ number }}'
' super result{% plural %}{{ number }} super results{% endblocktrans %}' ' super result{% plural %}{{ number }} super results{% endblocktrans %}'
) )
rendered = t.render(Context()) rendered = t.render(Context())
self.assertEqual(rendered, '2 Super-Ergebnisse') self.assertEqual(rendered, '2 Super-Ergebnisse')
t = Template( t = self.get_template(
'{% load i18n %}{% blocktrans context "other super search" count number=1 %}' '{% load i18n %}{% blocktrans context "other super search" count number=1 %}'
'{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}' '{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}'
) )
rendered = t.render(Context()) rendered = t.render(Context())
self.assertEqual(rendered, '1 anderen Super-Ergebnis') self.assertEqual(rendered, '1 anderen Super-Ergebnis')
t = Template( t = self.get_template(
'{% load i18n %}{% blocktrans context "other super search" count number=2 %}' '{% load i18n %}{% blocktrans context "other super search" count number=2 %}'
'{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}' '{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}'
) )
@ -338,13 +385,13 @@ class TranslationBlockTransTagTests(SimpleTestCase):
self.assertEqual(rendered, '2 andere Super-Ergebnisse') self.assertEqual(rendered, '2 andere Super-Ergebnisse')
# Using 'with' # Using 'with'
t = Template( t = self.get_template(
'{% load i18n %}{% blocktrans with num_comments=5 context "comment count" %}' '{% load i18n %}{% blocktrans with num_comments=5 context "comment count" %}'
'There are {{ num_comments }} comments{% endblocktrans %}' 'There are {{ num_comments }} comments{% endblocktrans %}'
) )
rendered = t.render(Context()) rendered = t.render(Context())
self.assertEqual(rendered, 'Es gibt 5 Kommentare') self.assertEqual(rendered, 'Es gibt 5 Kommentare')
t = Template( t = self.get_template(
'{% load i18n %}{% blocktrans with num_comments=5 context "other comment count" %}' '{% load i18n %}{% blocktrans with num_comments=5 context "other comment count" %}'
'There are {{ num_comments }} comments{% endblocktrans %}' 'There are {{ num_comments }} comments{% endblocktrans %}'
) )
@ -352,19 +399,19 @@ class TranslationBlockTransTagTests(SimpleTestCase):
self.assertEqual(rendered, 'Andere: Es gibt 5 Kommentare') self.assertEqual(rendered, 'Andere: Es gibt 5 Kommentare')
# Using trimmed # Using trimmed
t = Template( t = self.get_template(
'{% load i18n %}{% blocktrans trimmed %}\n\nThere\n\t are 5 ' '{% load i18n %}{% blocktrans trimmed %}\n\nThere\n\t are 5 '
'\n\n comments\n{% endblocktrans %}' '\n\n comments\n{% endblocktrans %}'
) )
rendered = t.render(Context()) rendered = t.render(Context())
self.assertEqual(rendered, 'There are 5 comments') self.assertEqual(rendered, 'There are 5 comments')
t = Template( t = self.get_template(
'{% load i18n %}{% blocktrans with num_comments=5 context "comment count" trimmed %}\n\n' '{% load i18n %}{% blocktrans with num_comments=5 context "comment count" trimmed %}\n\n'
'There are \t\n \t {{ num_comments }} comments\n\n{% endblocktrans %}' 'There are \t\n \t {{ num_comments }} comments\n\n{% endblocktrans %}'
) )
rendered = t.render(Context()) rendered = t.render(Context())
self.assertEqual(rendered, 'Es gibt 5 Kommentare') self.assertEqual(rendered, 'Es gibt 5 Kommentare')
t = Template( t = self.get_template(
'{% load i18n %}{% blocktrans context "other super search" count number=2 trimmed %}\n' '{% load i18n %}{% blocktrans context "other super search" count number=2 trimmed %}\n'
'{{ number }} super \n result{% plural %}{{ number }} super results{% endblocktrans %}' '{{ number }} super \n result{% plural %}{{ number }} super results{% endblocktrans %}'
) )
@ -374,12 +421,14 @@ class TranslationBlockTransTagTests(SimpleTestCase):
# Misuses # Misuses
msg = "Unknown argument for 'blocktrans' tag: %r." msg = "Unknown argument for 'blocktrans' tag: %r."
with self.assertRaisesMessage(TemplateSyntaxError, msg % 'month="May"'): with self.assertRaisesMessage(TemplateSyntaxError, msg % 'month="May"'):
Template('{% load i18n %}{% blocktrans context with month="May" %}{{ month }}{% endblocktrans %}') self.get_template(
'{% load i18n %}{% blocktrans context with month="May" %}{{ month }}{% endblocktrans %}'
)
msg = '"context" in %r tag expected exactly one argument.' % 'blocktrans' msg = '"context" in %r tag expected exactly one argument.' % 'blocktrans'
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
Template('{% load i18n %}{% blocktrans context %}{% endblocktrans %}') self.get_template('{% load i18n %}{% blocktrans context %}{% endblocktrans %}')
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
Template( self.get_template(
'{% load i18n %}{% blocktrans count number=2 context %}' '{% load i18n %}{% blocktrans count number=2 context %}'
'{{ number }} super result{% plural %}{{ number }}' '{{ number }} super result{% plural %}{{ number }}'
' super results{% endblocktrans %}' ' super results{% endblocktrans %}'
@ -409,7 +458,23 @@ class TranslationBlockTransTagTests(SimpleTestCase):
self.assertEqual(rendered, 'My other name is James.') self.assertEqual(rendered, 'My other name is James.')
class TranslationBlockTranslationTagTests(TranslationBlockTransTagTests):
tag_name = 'blocktranslation'
class MultipleLocaleActivationBlockTransTests(MultipleLocaleActivationTestCase): class MultipleLocaleActivationBlockTransTests(MultipleLocaleActivationTestCase):
tag_name = 'blocktrans'
def get_template(self, template_string):
return Template(
template_string.replace(
'{{% blocktrans ',
'{{% {}'.format(self.tag_name)
).replace(
'{{% endblocktrans %}}',
'{{% end{} %}}'.format(self.tag_name)
)
)
def test_single_locale_activation(self): def test_single_locale_activation(self):
""" """
@ -418,35 +483,51 @@ class MultipleLocaleActivationBlockTransTests(MultipleLocaleActivationTestCase):
""" """
with translation.override('fr'): with translation.override('fr'):
self.assertEqual( self.assertEqual(
Template("{% load i18n %}{% blocktrans %}Yes{% endblocktrans %}").render(Context({})), self.get_template("{% load i18n %}{% blocktrans %}Yes{% endblocktrans %}").render(Context({})),
'Oui' 'Oui'
) )
def test_multiple_locale_btrans(self): def test_multiple_locale_btrans(self):
with translation.override('de'): with translation.override('de'):
t = Template("{% load i18n %}{% blocktrans %}No{% endblocktrans %}") t = self.get_template("{% load i18n %}{% blocktrans %}No{% endblocktrans %}")
with translation.override(self._old_language), translation.override('nl'): with translation.override(self._old_language), translation.override('nl'):
self.assertEqual(t.render(Context({})), 'Nee') self.assertEqual(t.render(Context({})), 'Nee')
def test_multiple_locale_deactivate_btrans(self): def test_multiple_locale_deactivate_btrans(self):
with translation.override('de', deactivate=True): with translation.override('de', deactivate=True):
t = Template("{% load i18n %}{% blocktrans %}No{% endblocktrans %}") t = self.get_template("{% load i18n %}{% blocktrans %}No{% endblocktrans %}")
with translation.override('nl'): with translation.override('nl'):
self.assertEqual(t.render(Context({})), 'Nee') self.assertEqual(t.render(Context({})), 'Nee')
def test_multiple_locale_direct_switch_btrans(self): def test_multiple_locale_direct_switch_btrans(self):
with translation.override('de'): with translation.override('de'):
t = Template("{% load i18n %}{% blocktrans %}No{% endblocktrans %}") t = self.get_template("{% load i18n %}{% blocktrans %}No{% endblocktrans %}")
with translation.override('nl'): with translation.override('nl'):
self.assertEqual(t.render(Context({})), 'Nee') self.assertEqual(t.render(Context({})), 'Nee')
class MultipleLocaleActivationBlockTranslationTests(MultipleLocaleActivationBlockTransTests):
tag_name = 'blocktranslation'
class MiscTests(SimpleTestCase): class MiscTests(SimpleTestCase):
tag_name = 'blocktranslate'
def get_template(self, template_string):
return Template(
template_string.replace(
'{{% blocktrans ',
'{{% {}'.format(self.tag_name)
).replace(
'{{% endblocktrans %}}',
'{{% end{} %}}'.format(self.tag_name)
)
)
@override_settings(LOCALE_PATHS=extended_locale_paths) @override_settings(LOCALE_PATHS=extended_locale_paths)
def test_percent_in_translatable_block(self): def test_percent_in_translatable_block(self):
t_sing = Template("{% load i18n %}{% blocktrans %}The result was {{ percent }}%{% endblocktrans %}") t_sing = self.get_template("{% load i18n %}{% blocktrans %}The result was {{ percent }}%{% endblocktrans %}")
t_plur = Template( t_plur = self.get_template(
"{% load i18n %}{% blocktrans count num as number %}" "{% load i18n %}{% blocktrans count num as number %}"
"{{ percent }}% represents {{ num }} object{% plural %}" "{{ percent }}% represents {{ num }} object{% plural %}"
"{{ percent }}% represents {{ num }} objects{% endblocktrans %}" "{{ percent }}% represents {{ num }} objects{% endblocktrans %}"
@ -457,13 +538,15 @@ class MiscTests(SimpleTestCase):
self.assertEqual(t_plur.render(Context({'percent': 42, 'num': 4})), '42% stellt 4 Objekte dar') self.assertEqual(t_plur.render(Context({'percent': 42, 'num': 4})), '42% stellt 4 Objekte dar')
@override_settings(LOCALE_PATHS=extended_locale_paths) @override_settings(LOCALE_PATHS=extended_locale_paths)
def test_percent_formatting_in_blocktrans(self): def test_percent_formatting_in_blocktranslate(self):
""" """
Python's %-formatting is properly escaped in blocktrans, singular, or Python's %-formatting is properly escaped in blocktranslate, singular,
plural. or plural.
""" """
t_sing = Template("{% load i18n %}{% blocktrans %}There are %(num_comments)s comments{% endblocktrans %}") t_sing = self.get_template(
t_plur = Template( "{% load i18n %}{% blocktrans %}There are %(num_comments)s comments{% endblocktrans %}"
)
t_plur = self.get_template(
"{% load i18n %}{% blocktrans count num as number %}" "{% load i18n %}{% blocktrans count num as number %}"
"%(percent)s% represents {{ num }} object{% plural %}" "%(percent)s% represents {{ num }} object{% plural %}"
"%(percent)s% represents {{ num }} objects{% endblocktrans %}" "%(percent)s% represents {{ num }} objects{% endblocktrans %}"
@ -473,3 +556,7 @@ class MiscTests(SimpleTestCase):
self.assertEqual(t_sing.render(Context({'num_comments': 42})), 'There are %(num_comments)s comments') self.assertEqual(t_sing.render(Context({'num_comments': 42})), 'There are %(num_comments)s comments')
self.assertEqual(t_plur.render(Context({'percent': 42, 'num': 1})), '%(percent)s% represents 1 object') self.assertEqual(t_plur.render(Context({'percent': 42, 'num': 1})), '%(percent)s% represents 1 object')
self.assertEqual(t_plur.render(Context({'percent': 42, 'num': 4})), '%(percent)s% represents 4 objects') self.assertEqual(t_plur.render(Context({'percent': 42, 'num': 4})), '%(percent)s% represents 4 objects')
class MiscBlockTranslationTests(MiscTests):
tag_name = 'blocktrans'

View File

@ -1,3 +1,6 @@
import inspect
from functools import partial, wraps
from asgiref.local import Local from asgiref.local import Local
from django.template import Context, Template, TemplateSyntaxError from django.template import Context, Template, TemplateSyntaxError
@ -7,10 +10,35 @@ from django.utils import translation
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import trans_real from django.utils.translation import trans_real
from ...utils import setup from ...utils import setup as base_setup
from .base import MultipleLocaleActivationTestCase, extended_locale_paths from .base import MultipleLocaleActivationTestCase, extended_locale_paths
def setup(templates, *args, **kwargs):
trans_setup = base_setup(templates, *args, **kwargs)
translate_setup = base_setup({
name: template.replace('{% trans ', '{% translate ')
for name, template in templates.items()
})
tags = {
'trans': trans_setup,
'translate': translate_setup,
}
def decorator(func):
@wraps(func)
def inner(self, *args):
signature = inspect.signature(func)
for tag_name, setup_func in tags.items():
if 'tag_name' in signature.parameters:
setup_func(partial(func, tag_name=tag_name))(self)
else:
setup_func(func)(self)
return inner
return decorator
class I18nTransTagTests(SimpleTestCase): class I18nTransTagTests(SimpleTestCase):
libraries = {'i18n': 'django.templatetags.i18n'} libraries = {'i18n': 'django.templatetags.i18n'}
@ -84,38 +112,38 @@ class I18nTransTagTests(SimpleTestCase):
self.assertEqual(output, 'Page not found') self.assertEqual(output, 'Page not found')
@setup({'template': '{% load i18n %}{% trans %}A}'}) @setup({'template': '{% load i18n %}{% trans %}A}'})
def test_syntax_error_no_arguments(self): def test_syntax_error_no_arguments(self, tag_name):
msg = "'trans' takes at least one argument" msg = "'{}' takes at least one argument".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template') self.engine.render_to_string('template')
@setup({'template': '{% load i18n %}{% trans "Yes" badoption %}'}) @setup({'template': '{% load i18n %}{% trans "Yes" badoption %}'})
def test_syntax_error_bad_option(self): def test_syntax_error_bad_option(self, tag_name):
msg = "Unknown argument for 'trans' tag: 'badoption'" msg = "Unknown argument for '{}' tag: 'badoption'".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template') self.engine.render_to_string('template')
@setup({'template': '{% load i18n %}{% trans "Yes" as %}'}) @setup({'template': '{% load i18n %}{% trans "Yes" as %}'})
def test_syntax_error_missing_assignment(self): def test_syntax_error_missing_assignment(self, tag_name):
msg = "No argument provided to the 'trans' tag for the as option." msg = "No argument provided to the '{}' tag for the as option.".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template') self.engine.render_to_string('template')
@setup({'template': '{% load i18n %}{% trans "Yes" as var context %}'}) @setup({'template': '{% load i18n %}{% trans "Yes" as var context %}'})
def test_syntax_error_missing_context(self): def test_syntax_error_missing_context(self, tag_name):
msg = "No argument provided to the 'trans' tag for the context option." msg = "No argument provided to the '{}' tag for the context option.".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template') self.engine.render_to_string('template')
@setup({'template': '{% load i18n %}{% trans "Yes" context as var %}'}) @setup({'template': '{% load i18n %}{% trans "Yes" context as var %}'})
def test_syntax_error_context_as(self): def test_syntax_error_context_as(self, tag_name):
msg = "Invalid argument 'as' provided to the 'trans' tag for the context option" msg = "Invalid argument 'as' provided to the '{}' tag for the context option".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template') self.engine.render_to_string('template')
@setup({'template': '{% load i18n %}{% trans "Yes" context noop %}'}) @setup({'template': '{% load i18n %}{% trans "Yes" context noop %}'})
def test_syntax_error_context_noop(self): def test_syntax_error_context_noop(self, tag_name):
msg = "Invalid argument 'noop' provided to the 'trans' tag for the context option" msg = "Invalid argument 'noop' provided to the '{}' tag for the context option".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template') self.engine.render_to_string('template')
@ -132,6 +160,15 @@ class I18nTransTagTests(SimpleTestCase):
class TranslationTransTagTests(SimpleTestCase): class TranslationTransTagTests(SimpleTestCase):
tag_name = 'trans'
def get_template(self, template_string):
return Template(
template_string.replace(
'{{% trans ',
'{{% {}'.format(self.tag_name)
)
)
@override_settings(LOCALE_PATHS=extended_locale_paths) @override_settings(LOCALE_PATHS=extended_locale_paths)
def test_template_tags_pgettext(self): def test_template_tags_pgettext(self):
@ -140,44 +177,57 @@ class TranslationTransTagTests(SimpleTestCase):
trans_real._translations = {} trans_real._translations = {}
with translation.override('de'): with translation.override('de'):
# Nonexistent context... # Nonexistent context...
t = Template('{% load i18n %}{% trans "May" context "nonexistent" %}') t = self.get_template('{% load i18n %}{% trans "May" context "nonexistent" %}')
rendered = t.render(Context()) rendered = t.render(Context())
self.assertEqual(rendered, 'May') self.assertEqual(rendered, 'May')
# Existing context... using a literal # Existing context... using a literal
t = Template('{% load i18n %}{% trans "May" context "month name" %}') t = self.get_template('{% load i18n %}{% trans "May" context "month name" %}')
rendered = t.render(Context()) rendered = t.render(Context())
self.assertEqual(rendered, 'Mai') self.assertEqual(rendered, 'Mai')
t = Template('{% load i18n %}{% trans "May" context "verb" %}') t = self.get_template('{% load i18n %}{% trans "May" context "verb" %}')
rendered = t.render(Context()) rendered = t.render(Context())
self.assertEqual(rendered, 'Kann') self.assertEqual(rendered, 'Kann')
# Using a variable # Using a variable
t = Template('{% load i18n %}{% trans "May" context message_context %}') t = self.get_template('{% load i18n %}{% trans "May" context message_context %}')
rendered = t.render(Context({'message_context': 'month name'})) rendered = t.render(Context({'message_context': 'month name'}))
self.assertEqual(rendered, 'Mai') self.assertEqual(rendered, 'Mai')
t = Template('{% load i18n %}{% trans "May" context message_context %}') t = self.get_template('{% load i18n %}{% trans "May" context message_context %}')
rendered = t.render(Context({'message_context': 'verb'})) rendered = t.render(Context({'message_context': 'verb'}))
self.assertEqual(rendered, 'Kann') self.assertEqual(rendered, 'Kann')
# Using a filter # Using a filter
t = Template('{% load i18n %}{% trans "May" context message_context|lower %}') t = self.get_template('{% load i18n %}{% trans "May" context message_context|lower %}')
rendered = t.render(Context({'message_context': 'MONTH NAME'})) rendered = t.render(Context({'message_context': 'MONTH NAME'}))
self.assertEqual(rendered, 'Mai') self.assertEqual(rendered, 'Mai')
t = Template('{% load i18n %}{% trans "May" context message_context|lower %}') t = self.get_template('{% load i18n %}{% trans "May" context message_context|lower %}')
rendered = t.render(Context({'message_context': 'VERB'})) rendered = t.render(Context({'message_context': 'VERB'}))
self.assertEqual(rendered, 'Kann') self.assertEqual(rendered, 'Kann')
# Using 'as' # Using 'as'
t = Template('{% load i18n %}{% trans "May" context "month name" as var %}Value: {{ var }}') t = self.get_template('{% load i18n %}{% trans "May" context "month name" as var %}Value: {{ var }}')
rendered = t.render(Context()) rendered = t.render(Context())
self.assertEqual(rendered, 'Value: Mai') self.assertEqual(rendered, 'Value: Mai')
t = Template('{% load i18n %}{% trans "May" as var context "verb" %}Value: {{ var }}') t = self.get_template('{% load i18n %}{% trans "May" as var context "verb" %}Value: {{ var }}')
rendered = t.render(Context()) rendered = t.render(Context())
self.assertEqual(rendered, 'Value: Kann') self.assertEqual(rendered, 'Value: Kann')
class TranslationTranslateTagTests(TranslationTransTagTests):
tag_name = 'translate'
class MultipleLocaleActivationTransTagTests(MultipleLocaleActivationTestCase): class MultipleLocaleActivationTransTagTests(MultipleLocaleActivationTestCase):
tag_name = 'trans'
def get_template(self, template_string):
return Template(
template_string.replace(
'{{% trans ',
'{{% {}'.format(self.tag_name)
)
)
def test_single_locale_activation(self): def test_single_locale_activation(self):
""" """
@ -185,27 +235,34 @@ class MultipleLocaleActivationTransTagTests(MultipleLocaleActivationTestCase):
constructs. constructs.
""" """
with translation.override('fr'): with translation.override('fr'):
self.assertEqual(Template("{% load i18n %}{% trans 'Yes' %}").render(Context({})), 'Oui') self.assertEqual(
self.get_template("{% load i18n %}{% trans 'Yes' %}").render(Context({})),
'Oui'
)
def test_multiple_locale_trans(self): def test_multiple_locale_trans(self):
with translation.override('de'): with translation.override('de'):
t = Template("{% load i18n %}{% trans 'No' %}") t = self.get_template("{% load i18n %}{% trans 'No' %}")
with translation.override(self._old_language), translation.override('nl'): with translation.override(self._old_language), translation.override('nl'):
self.assertEqual(t.render(Context({})), 'Nee') self.assertEqual(t.render(Context({})), 'Nee')
def test_multiple_locale_deactivate_trans(self): def test_multiple_locale_deactivate_trans(self):
with translation.override('de', deactivate=True): with translation.override('de', deactivate=True):
t = Template("{% load i18n %}{% trans 'No' %}") t = self.get_template("{% load i18n %}{% trans 'No' %}")
with translation.override('nl'): with translation.override('nl'):
self.assertEqual(t.render(Context({})), 'Nee') self.assertEqual(t.render(Context({})), 'Nee')
def test_multiple_locale_direct_switch_trans(self): def test_multiple_locale_direct_switch_trans(self):
with translation.override('de'): with translation.override('de'):
t = Template("{% load i18n %}{% trans 'No' %}") t = self.get_template("{% load i18n %}{% trans 'No' %}")
with translation.override('nl'): with translation.override('nl'):
self.assertEqual(t.render(Context({})), 'Nee') self.assertEqual(t.render(Context({})), 'Nee')
class MultipleLocaleActivationTranslateTagTests(MultipleLocaleActivationTransTagTests):
tag_name = 'translate'
class LocalizeNodeTests(SimpleTestCase): class LocalizeNodeTests(SimpleTestCase):
def test_repr(self): def test_repr(self):
node = LocalizeNode(nodelist=[], use_l10n=True) node = LocalizeNode(nodelist=[], use_l10n=True)