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

View File

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

View File

@ -2695,14 +2695,14 @@ Therefore here is our new ``change_form.html`` :
{% load i18n admin_urls %}
{% block object-tools-items %}
<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>
<a href="mylink/" class="historylink">My Link</a>
</li>
{% if has_absolute_url %}
<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>
{% endif %}
{% 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
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 %}
{% blocktrans %}Copyright {{ current_year }}{% endblocktrans %}
{% blocktranslate %}Copyright {{ current_year }}{% endblocktranslate %}
.. 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).
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 %}
{% blocktrans %}The width is: {{ width }}{% endblocktrans %}
{% blocktranslate %}The width is: {{ width }}{% endblocktranslate %}
.. templatetag:: with
@ -2023,7 +2023,7 @@ Example::
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

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
doesn't render anything::
{% trans "Title" as title %}
{% translate "Title" as title %}
{% block content %}{{ title }}{% endblock %}
* For extra readability, you can optionally give a *name* to your

View File

@ -260,7 +260,10 @@ Signals
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
~~~~~

View File

@ -682,7 +682,7 @@ templates to achieve the same result:
{% get_current_language as LANGUAGE_CODE %}
{% cache 600 welcome LANGUAGE_CODE %}
{% trans "Welcome to example.com" %}
{% translate "Welcome to example.com" %}
{% endcache %}
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"
msgstr ""
Contextual markers are also supported by the :ttag:`trans` and
:ttag:`blocktrans` template tags.
Contextual markers are also supported by the :ttag:`translate` and
:ttag:`blocktranslate` template tags.
.. _lazy-translations:
@ -575,21 +575,22 @@ have already loaded the ``i18n`` tag.
unchanged.
.. 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::
<title>{% trans "This is the title." %}</title>
<title>{% trans myvar %}</title>
<title>{% translate "This is the title." %}</title>
<title>{% translate myvar %}</title>
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
require translation in the future::
<title>{% trans "myvar" noop %}</title>
<title>{% translate "myvar" noop %}</title>
Internally, inline translations use an
: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
string in the message catalogs.
It's not possible to mix a template variable inside a string within ``{% trans
%}``. If your translations require strings with variables (placeholders), use
:ttag:`{% blocktrans %}<blocktrans>` instead.
It's not possible to mix a template variable inside a string within
``{% translate %}``. If your translations require strings with variables
(placeholders), use :ttag:`{% blocktranslate %}<blocktranslate>` instead.
If you'd like to retrieve a translated string without displaying it, you can
use the following syntax::
{% trans "This is the title" as the_title %}
{% translate "This is the title" as the_title %}
<title>{{ the_title }}</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
filters::
{% trans "starting point" as start %}
{% trans "end point" as end %}
{% trans "La Grande Boucle" as race %}
{% translate "starting point" as start %}
{% translate "end point" as end %}
{% translate "La Grande Boucle" as race %}
<h1>
<a href="/" title="{% blocktrans %}Back to '{{ race }}' homepage{% endblocktrans %}">{{ race }}</a>
<a href="/" title="{% blocktranslate %}Back to '{{ race }}' homepage{% endblocktranslate %}">{{ race }}</a>
</h1>
<p>
{% for stage in tour_stages %}
@ -628,50 +628,56 @@ filters::
{% endfor %}
</p>
``{% trans %}`` also supports :ref:`contextual markers<contextual-markers>`
``{% translate %}`` also supports :ref:`contextual markers<contextual-markers>`
using the ``context`` keyword:
.. 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:: blocktranslate
``blocktrans`` template tag
---------------------------
``blocktranslate`` template tag
-------------------------------
Contrarily to the :ttag:`trans` tag, the ``blocktrans`` tag allows you to mark
complex sentences consisting of literals and variable content for translation
by making use of placeholders::
Contrarily to the :ttag:`translate` tag, the ``blocktranslate`` tag allows you
to mark complex sentences consisting of literals and variable content for
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
using template filters -- you need to bind the expression to a local variable
for use within the translation block. Examples::
{% blocktrans with amount=article.price %}
{% blocktranslate with amount=article.price %}
That will cost $ {{ amount }}.
{% endblocktrans %}
{% endblocktranslate %}
{% blocktrans with myvar=value|filter %}
{% blocktranslate with myvar=value|filter %}
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 }}
{% endblocktrans %}
{% endblocktranslate %}
.. 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
inside a ``blocktrans`` tag.
inside a ``blocktranslate`` tag.
If resolving one of the block arguments fails, ``blocktrans`` will fall back to
the default language by deactivating the currently active language
If resolving one of the block arguments fails, ``blocktranslate`` will fall
back to the default language by deactivating the currently active language
temporarily with the :func:`~django.utils.translation.deactivate_all`
function.
@ -681,43 +687,43 @@ This tag also provides for pluralization. To use it:
be the one used to select the right plural form.
* Specify both the singular and plural forms separating them with the
``{% plural %}`` tag within the ``{% blocktrans %}`` and
``{% endblocktrans %}`` tags.
``{% plural %}`` tag within the ``{% blocktranslate %}`` and
``{% endblocktranslate %}`` tags.
An example::
{% blocktrans count counter=list|length %}
{% blocktranslate count counter=list|length %}
There is only one {{ name }} object.
{% plural %}
There are {{ counter }} {{ name }} objects.
{% endblocktrans %}
{% endblocktranslate %}
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.
{% plural %}
That will cost $ {{ amount }} per {{ years }} years.
{% endblocktrans %}
{% endblocktranslate %}
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
same :ref:`notes regarding ngettext variables <pluralization-var-notes>`
apply.
Reverse URL lookups cannot be carried out within the ``blocktrans`` and should
be retrieved (and stored) beforehand::
Reverse URL lookups cannot be carried out within the ``blocktranslate`` and
should be retrieved (and stored) beforehand::
{% url 'path.to.view' arg arg2 as the_url %}
{% blocktrans %}
{% blocktranslate %}
This is a URL: {{ the_url }}
{% endblocktrans %}
{% endblocktranslate %}
If you'd like to retrieve a translated string without displaying it, you can
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>
<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
filters.
``{% blocktrans %}`` also supports :ref:`contextual
``{% blocktranslate %}`` also supports :ref:`contextual
markers<contextual-markers>` using the ``context`` keyword:
.. 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
option will remove newline characters from the beginning and the end of the
content of the ``{% blocktrans %}`` tag, replace any whitespace at the beginning
and end of a line and merge all lines into one using a space 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
corresponding entry in the PO file, which makes the translation process easier.
Another feature ``{% blocktranslate %}`` supports is the ``trimmed`` option.
This option will remove newline characters from the beginning and the end of
the content of the ``{% blocktranslate %}`` tag, replace any whitespace at the
beginning and end of a line and merge all lines into one using a space
character to separate them. This is quite useful for indenting the content of a
``{% blocktranslate %}`` tag without having the indentation characters end up
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.
Second paragraph.
{% endblocktrans %}
{% endblocktranslate %}
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``
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
------------------------------------------
@ -782,21 +794,21 @@ tag:
.. code-block:: html+django
{% comment %}Translators: View verb{% endcomment %}
{% trans "View" %}
{% translate "View" %}
{% comment %}Translators: Short intro blurb{% endcomment %}
<p>{% blocktrans %}A multiline translatable
literal.{% endblocktrans %}</p>
<p>{% blocktranslate %}A multiline translatable
literal.{% endblocktranslate %}</p>
or with the ``{#`` ... ``#}`` :ref:`one-line comment constructs <template-comments>`:
.. code-block:: html+django
{# 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 #}
{% 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
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 %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<p>{% trans "Welcome to our page" %}</p>
<p>{% translate "Welcome to our page" %}</p>
{% language 'en' %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<p>{% trans "Welcome to our page" %}</p>
<p>{% translate "Welcome to our page" %}</p>
{% endlanguage %}
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 %}
{% trans "View this category in:" %}
{% translate "View this category in:" %}
{% for lang_code, lang_name in languages %}
{% language lang_code %}
<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 %}
{% 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 double quotes" %}Translatable literal with context wrapped in double quotes{% endblocktrans %}
{% blocktranslate %}blocktranslate text{% endblocktranslate %}
{% translate "translate text" %}
{# BasicExtractorTests.test_blocktrans_trimmed #}
{% blocktrans %}
{# BasicExtractorTests.test_blocktranslate_trimmed #}
{% blocktranslate %}
Text with a few
line breaks.
{% endblocktrans %}
@ -98,10 +100,10 @@ continued here.{% endcomment %}
{% endblocktrans %}
{% trans "Get my line number" %}
{% blocktrans trimmed count counter=mylist|length %}
First `trans`, then `blocktrans` with a plural
{% blocktranslate trimmed count counter=mylist|length %}
First `translate`, then `blocktranslate` with a plural
{% plural %}
Plural for a `trans` and `blocktrans` collision case
{% endblocktrans %}
Plural for a `translate` and `blocktranslate` collision case
{% endblocktranslate %}
{% trans "Non-breaking space :" %}

View File

@ -8,12 +8,13 @@ by using catalogs created from management commands.
Example:
The string "Two %% Three %%%" renders differently using trans and blocktrans.
This issue is difficult to debug, it could be a problem with extraction,
interpolation, or both.
The string "Two %% Three %%%" renders differently using translate and
blocktranslate. This issue is difficult to debug, it could be a problem with
extraction, interpolation, or both.
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.
* Test extraction - verify the new msgid in 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()
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)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE) as fp:
@ -256,6 +256,10 @@ class BasicExtractorTests(ExtractorTests):
self.assertIn('msgctxt "Special blocktrans context #4"', 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):
management.call_command('makemessages', locale=[LOCALE], verbosity=0)
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)
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
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:
po_contents = fp.read()
self.assertNotIn("#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\\n", po_contents)
self.assertMsgId('First `trans`, then `blocktrans` with a plural', po_contents)
self.assertMsgIdPlural('Plural for a `trans` and `blocktrans` collision case', po_contents)
self.assertMsgId('First `translate`, then `blocktranslate` with a plural', po_contents)
self.assertMsgIdPlural('Plural for a `translate` and `blocktranslate` collision case', po_contents)
class NoWrapExtractorTests(ExtractorTests):

View File

@ -1,4 +1,6 @@
import inspect
import os
from functools import partial, wraps
from asgiref.local import Local
@ -8,10 +10,39 @@ from django.utils import translation
from django.utils.safestring import mark_safe
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
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):
libraries = {'i18n': 'django.templatetags.i18n'}
@ -76,8 +107,8 @@ class I18nBlockTransTagTests(SimpleTestCase):
'{% blocktrans with berta=anton|escape %}{{ berta }}{% endblocktrans %}'})
def test_i18n17(self):
"""
Escaping inside blocktrans and trans works as if it was directly in the
template.
Escaping inside blocktranslate and translate works as if it was
directly in the template.
"""
output = self.engine.render_to_string('i18n17', {'anton': 'α & β'})
self.assertEqual(output, 'α &amp; β')
@ -224,8 +255,8 @@ class I18nBlockTransTagTests(SimpleTestCase):
self.assertEqual(output, '>Error: Seite nicht gefunden<')
@setup({'template': '{% load i18n %}{% blocktrans asvar %}Yes{% endblocktrans %}'})
def test_blocktrans_syntax_error_missing_assignment(self):
msg = "No argument provided to the 'blocktrans' tag for the asvar option."
def test_blocktrans_syntax_error_missing_assignment(self, tag_name):
msg = "No argument provided to the '{}' tag for the asvar option.".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template')
@ -235,14 +266,14 @@ class I18nBlockTransTagTests(SimpleTestCase):
self.assertEqual(output, '%s')
@setup({'template': '{% load i18n %}{% blocktrans %}{% block b %} {% endblock %}{% endblocktrans %}'})
def test_with_block(self):
msg = "'blocktrans' doesn't allow other block tags (seen 'block b') inside it"
def test_with_block(self, tag_name):
msg = "'{}' doesn't allow other block tags (seen 'block b') inside it".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template')
@setup({'template': '{% load i18n %}{% blocktrans %}{% for b in [1, 2, 3] %} {% endfor %}{% endblocktrans %}'})
def test_with_for(self):
msg = "'blocktrans' doesn't allow other block tags (seen 'for b in [1, 2, 3]') inside it"
def test_with_for(self, tag_name):
msg = "'{}' doesn't allow other block tags (seen 'for b in [1, 2, 3]') inside it".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template')
@ -252,14 +283,14 @@ class I18nBlockTransTagTests(SimpleTestCase):
self.engine.render_to_string('template', {'foo': 'bar'})
@setup({'template': '{% load i18n %}{% blocktrans with %}{% endblocktrans %}'})
def test_no_args_with(self):
msg = '"with" in \'blocktrans\' tag needs at least one keyword argument.'
def test_no_args_with(self, tag_name):
msg = '"with" in \'{}\' tag needs at least one keyword argument.'.format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template')
@setup({'template': '{% load i18n %}{% blocktrans count a %}{% endblocktrans %}'})
def test_count(self):
msg = '"count" in \'blocktrans\' tag expected exactly one keyword argument.'
def test_count(self, tag_name):
msg = '"count" in \'{}\' tag expected exactly one keyword argument.'.format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template', {'a': [1, 2, 3]})
@ -268,13 +299,25 @@ class I18nBlockTransTagTests(SimpleTestCase):
'There is {{ count }} object. {% block a %} {% endblock %}'
'{% endblocktrans %}'
)})
def test_plural_bad_syntax(self):
msg = "'blocktrans' doesn't allow other block tags inside it"
def test_plural_bad_syntax(self, tag_name):
msg = "'{}' doesn't allow other block tags inside it".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template', {'var': [1, 2, 3]})
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)
def test_template_tags_pgettext(self):
@ -283,54 +326,58 @@ class TranslationBlockTransTagTests(SimpleTestCase):
trans_real._translations = {}
with translation.override('de'):
# 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())
self.assertEqual(rendered, 'May')
# 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())
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())
self.assertEqual(rendered, 'Kann')
# 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'}))
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'}))
self.assertEqual(rendered, 'Kann')
# 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'}))
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'}))
self.assertEqual(rendered, 'Kann')
# Using 'count'
t = Template(
t = self.get_template(
'{% load i18n %}{% blocktrans count number=1 context "super search" %}'
'{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}'
)
rendered = t.render(Context())
self.assertEqual(rendered, '1 Super-Ergebnis')
t = Template(
t = self.get_template(
'{% load i18n %}{% blocktrans count number=2 context "super search" %}{{ number }}'
' super result{% plural %}{{ number }} super results{% endblocktrans %}'
)
rendered = t.render(Context())
self.assertEqual(rendered, '2 Super-Ergebnisse')
t = Template(
t = self.get_template(
'{% load i18n %}{% blocktrans context "other super search" count number=1 %}'
'{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}'
)
rendered = t.render(Context())
self.assertEqual(rendered, '1 anderen Super-Ergebnis')
t = Template(
t = self.get_template(
'{% load i18n %}{% blocktrans context "other super search" count number=2 %}'
'{{ number }} super result{% plural %}{{ number }} super results{% endblocktrans %}'
)
@ -338,13 +385,13 @@ class TranslationBlockTransTagTests(SimpleTestCase):
self.assertEqual(rendered, '2 andere Super-Ergebnisse')
# Using 'with'
t = Template(
t = self.get_template(
'{% load i18n %}{% blocktrans with num_comments=5 context "comment count" %}'
'There are {{ num_comments }} comments{% endblocktrans %}'
)
rendered = t.render(Context())
self.assertEqual(rendered, 'Es gibt 5 Kommentare')
t = Template(
t = self.get_template(
'{% load i18n %}{% blocktrans with num_comments=5 context "other comment count" %}'
'There are {{ num_comments }} comments{% endblocktrans %}'
)
@ -352,19 +399,19 @@ class TranslationBlockTransTagTests(SimpleTestCase):
self.assertEqual(rendered, 'Andere: Es gibt 5 Kommentare')
# Using trimmed
t = Template(
t = self.get_template(
'{% load i18n %}{% blocktrans trimmed %}\n\nThere\n\t are 5 '
'\n\n comments\n{% endblocktrans %}'
)
rendered = t.render(Context())
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'
'There are \t\n \t {{ num_comments }} comments\n\n{% endblocktrans %}'
)
rendered = t.render(Context())
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'
'{{ number }} super \n result{% plural %}{{ number }} super results{% endblocktrans %}'
)
@ -374,12 +421,14 @@ class TranslationBlockTransTagTests(SimpleTestCase):
# Misuses
msg = "Unknown argument for 'blocktrans' tag: %r."
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'
with self.assertRaisesMessage(TemplateSyntaxError, msg):
Template('{% load i18n %}{% blocktrans context %}{% endblocktrans %}')
self.get_template('{% load i18n %}{% blocktrans context %}{% endblocktrans %}')
with self.assertRaisesMessage(TemplateSyntaxError, msg):
Template(
self.get_template(
'{% load i18n %}{% blocktrans count number=2 context %}'
'{{ number }} super result{% plural %}{{ number }}'
' super results{% endblocktrans %}'
@ -409,7 +458,23 @@ class TranslationBlockTransTagTests(SimpleTestCase):
self.assertEqual(rendered, 'My other name is James.')
class TranslationBlockTranslationTagTests(TranslationBlockTransTagTests):
tag_name = 'blocktranslation'
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):
"""
@ -418,35 +483,51 @@ class MultipleLocaleActivationBlockTransTests(MultipleLocaleActivationTestCase):
"""
with translation.override('fr'):
self.assertEqual(
Template("{% load i18n %}{% blocktrans %}Yes{% endblocktrans %}").render(Context({})),
self.get_template("{% load i18n %}{% blocktrans %}Yes{% endblocktrans %}").render(Context({})),
'Oui'
)
def test_multiple_locale_btrans(self):
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'):
self.assertEqual(t.render(Context({})), 'Nee')
def test_multiple_locale_deactivate_btrans(self):
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'):
self.assertEqual(t.render(Context({})), 'Nee')
def test_multiple_locale_direct_switch_btrans(self):
with translation.override('de'):
t = Template("{% load i18n %}{% blocktrans %}No{% endblocktrans %}")
t = self.get_template("{% load i18n %}{% blocktrans %}No{% endblocktrans %}")
with translation.override('nl'):
self.assertEqual(t.render(Context({})), 'Nee')
class MultipleLocaleActivationBlockTranslationTests(MultipleLocaleActivationBlockTransTests):
tag_name = 'blocktranslation'
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)
def test_percent_in_translatable_block(self):
t_sing = Template("{% load i18n %}{% blocktrans %}The result was {{ percent }}%{% endblocktrans %}")
t_plur = Template(
t_sing = self.get_template("{% load i18n %}{% blocktrans %}The result was {{ percent }}%{% endblocktrans %}")
t_plur = self.get_template(
"{% load i18n %}{% blocktrans count num as number %}"
"{{ percent }}% represents {{ num }} object{% plural %}"
"{{ 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')
@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
plural.
Python's %-formatting is properly escaped in blocktranslate, singular,
or plural.
"""
t_sing = Template("{% load i18n %}{% blocktrans %}There are %(num_comments)s comments{% endblocktrans %}")
t_plur = Template(
t_sing = self.get_template(
"{% load i18n %}{% blocktrans %}There are %(num_comments)s comments{% endblocktrans %}"
)
t_plur = self.get_template(
"{% load i18n %}{% blocktrans count num as number %}"
"%(percent)s% represents {{ num }} object{% plural %}"
"%(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_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')
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 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.translation import trans_real
from ...utils import setup
from ...utils import setup as base_setup
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):
libraries = {'i18n': 'django.templatetags.i18n'}
@ -84,38 +112,38 @@ class I18nTransTagTests(SimpleTestCase):
self.assertEqual(output, 'Page not found')
@setup({'template': '{% load i18n %}{% trans %}A}'})
def test_syntax_error_no_arguments(self):
msg = "'trans' takes at least one argument"
def test_syntax_error_no_arguments(self, tag_name):
msg = "'{}' takes at least one argument".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template')
@setup({'template': '{% load i18n %}{% trans "Yes" badoption %}'})
def test_syntax_error_bad_option(self):
msg = "Unknown argument for 'trans' tag: 'badoption'"
def test_syntax_error_bad_option(self, tag_name):
msg = "Unknown argument for '{}' tag: 'badoption'".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template')
@setup({'template': '{% load i18n %}{% trans "Yes" as %}'})
def test_syntax_error_missing_assignment(self):
msg = "No argument provided to the 'trans' tag for the as option."
def test_syntax_error_missing_assignment(self, tag_name):
msg = "No argument provided to the '{}' tag for the as option.".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template')
@setup({'template': '{% load i18n %}{% trans "Yes" as var context %}'})
def test_syntax_error_missing_context(self):
msg = "No argument provided to the 'trans' tag for the context option."
def test_syntax_error_missing_context(self, tag_name):
msg = "No argument provided to the '{}' tag for the context option.".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template')
@setup({'template': '{% load i18n %}{% trans "Yes" context as var %}'})
def test_syntax_error_context_as(self):
msg = "Invalid argument 'as' provided to the 'trans' tag for the context option"
def test_syntax_error_context_as(self, tag_name):
msg = "Invalid argument 'as' provided to the '{}' tag for the context option".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template')
@setup({'template': '{% load i18n %}{% trans "Yes" context noop %}'})
def test_syntax_error_context_noop(self):
msg = "Invalid argument 'noop' provided to the 'trans' tag for the context option"
def test_syntax_error_context_noop(self, tag_name):
msg = "Invalid argument 'noop' provided to the '{}' tag for the context option".format(tag_name)
with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.render_to_string('template')
@ -132,6 +160,15 @@ class I18nTransTagTests(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)
def test_template_tags_pgettext(self):
@ -140,44 +177,57 @@ class TranslationTransTagTests(SimpleTestCase):
trans_real._translations = {}
with translation.override('de'):
# Nonexistent context...
t = Template('{% load i18n %}{% trans "May" context "nonexistent" %}')
t = self.get_template('{% load i18n %}{% trans "May" context "nonexistent" %}')
rendered = t.render(Context())
self.assertEqual(rendered, 'May')
# 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())
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())
self.assertEqual(rendered, 'Kann')
# 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'}))
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'}))
self.assertEqual(rendered, 'Kann')
# 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'}))
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'}))
self.assertEqual(rendered, 'Kann')
# 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())
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())
self.assertEqual(rendered, 'Value: Kann')
class TranslationTranslateTagTests(TranslationTransTagTests):
tag_name = 'translate'
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):
"""
@ -185,27 +235,34 @@ class MultipleLocaleActivationTransTagTests(MultipleLocaleActivationTestCase):
constructs.
"""
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):
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'):
self.assertEqual(t.render(Context({})), 'Nee')
def test_multiple_locale_deactivate_trans(self):
with translation.override('de', deactivate=True):
t = Template("{% load i18n %}{% trans 'No' %}")
t = self.get_template("{% load i18n %}{% trans 'No' %}")
with translation.override('nl'):
self.assertEqual(t.render(Context({})), 'Nee')
def test_multiple_locale_direct_switch_trans(self):
with translation.override('de'):
t = Template("{% load i18n %}{% trans 'No' %}")
t = self.get_template("{% load i18n %}{% trans 'No' %}")
with translation.override('nl'):
self.assertEqual(t.render(Context({})), 'Nee')
class MultipleLocaleActivationTranslateTagTests(MultipleLocaleActivationTransTagTests):
tag_name = 'translate'
class LocalizeNodeTests(SimpleTestCase):
def test_repr(self):
node = LocalizeNode(nodelist=[], use_l10n=True)