mirror of https://github.com/django/django.git
Fixed #24257 -- Corrected i18n handling of percent signs.
Refactored tests to use a sample project. Updated extraction: * Removed special handling of single percent signs. * When extracting messages from template text, doubled all percent signs so they are not interpreted by gettext as string format flags. All strings extracted by gettext, if containing a percent sign, will now be labeled "#, python-format". Updated translation: * Used "%%" for "%" in template text before calling gettext. * Updated {% trans %} rendering to restore "%" from "%%".
This commit is contained in:
parent
d772d812cf
commit
b7508896fb
|
@ -825,10 +825,13 @@ class Variable(object):
|
||||||
# We're dealing with a literal, so it's already been "resolved"
|
# We're dealing with a literal, so it's already been "resolved"
|
||||||
value = self.literal
|
value = self.literal
|
||||||
if self.translate:
|
if self.translate:
|
||||||
|
is_safe = isinstance(value, SafeData)
|
||||||
|
msgid = value.replace('%', '%%')
|
||||||
|
msgid = mark_safe(msgid) if is_safe else msgid
|
||||||
if self.message_context:
|
if self.message_context:
|
||||||
return pgettext_lazy(self.message_context, value)
|
return pgettext_lazy(self.message_context, msgid)
|
||||||
else:
|
else:
|
||||||
return ugettext_lazy(value)
|
return ugettext_lazy(msgid)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|
|
@ -7,6 +7,7 @@ from django.template import Library, Node, TemplateSyntaxError, Variable
|
||||||
from django.template.base import TOKEN_TEXT, TOKEN_VAR, render_value_in_context
|
from django.template.base import TOKEN_TEXT, TOKEN_VAR, render_value_in_context
|
||||||
from django.template.defaulttags import token_kwargs
|
from django.template.defaulttags import token_kwargs
|
||||||
from django.utils import six, translation
|
from django.utils import six, translation
|
||||||
|
from django.utils.safestring import SafeData, mark_safe
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
|
@ -86,6 +87,11 @@ class TranslateNode(Node):
|
||||||
self.message_context.resolve(context))
|
self.message_context.resolve(context))
|
||||||
output = self.filter_expression.resolve(context)
|
output = self.filter_expression.resolve(context)
|
||||||
value = render_value_in_context(output, context)
|
value = render_value_in_context(output, context)
|
||||||
|
# Restore percent signs. Percent signs in template text are doubled
|
||||||
|
# so they are not interpreted as string format flags.
|
||||||
|
is_safe = isinstance(value, SafeData)
|
||||||
|
value = value.replace('%%', '%')
|
||||||
|
value = mark_safe(value) if is_safe else value
|
||||||
if self.asvar:
|
if self.asvar:
|
||||||
context[self.asvar] = value
|
context[self.asvar] = value
|
||||||
return ''
|
return ''
|
||||||
|
|
|
@ -534,7 +534,6 @@ block_re = re.compile(r"""^\s*blocktrans(\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?
|
||||||
endblock_re = re.compile(r"""^\s*endblocktrans$""")
|
endblock_re = re.compile(r"""^\s*endblocktrans$""")
|
||||||
plural_re = re.compile(r"""^\s*plural$""")
|
plural_re = re.compile(r"""^\s*plural$""")
|
||||||
constant_re = re.compile(r"""_\(((?:".*?")|(?:'.*?'))\)""")
|
constant_re = re.compile(r"""_\(((?:".*?")|(?:'.*?'))\)""")
|
||||||
one_percent_re = re.compile(r"""(?<!%)%(?!%)""")
|
|
||||||
|
|
||||||
|
|
||||||
def templatize(src, origin=None):
|
def templatize(src, origin=None):
|
||||||
|
@ -631,7 +630,7 @@ def templatize(src, origin=None):
|
||||||
else:
|
else:
|
||||||
singular.append('%%(%s)s' % t.contents)
|
singular.append('%%(%s)s' % t.contents)
|
||||||
elif t.token_type == TOKEN_TEXT:
|
elif t.token_type == TOKEN_TEXT:
|
||||||
contents = one_percent_re.sub('%%', t.contents)
|
contents = t.contents.replace('%', '%%')
|
||||||
if inplural:
|
if inplural:
|
||||||
plural.append(contents)
|
plural.append(contents)
|
||||||
else:
|
else:
|
||||||
|
@ -667,7 +666,7 @@ def templatize(src, origin=None):
|
||||||
g = g.strip('"')
|
g = g.strip('"')
|
||||||
elif g[0] == "'":
|
elif g[0] == "'":
|
||||||
g = g.strip("'")
|
g = g.strip("'")
|
||||||
g = one_percent_re.sub('%%', g)
|
g = g.replace('%', '%%')
|
||||||
if imatch.group(2):
|
if imatch.group(2):
|
||||||
# A context is provided
|
# A context is provided
|
||||||
context_match = context_re.match(imatch.group(2))
|
context_match = context_re.match(imatch.group(2))
|
||||||
|
|
|
@ -940,6 +940,11 @@ Miscellaneous
|
||||||
whitespace by default. This can be disabled by setting the new
|
whitespace by default. This can be disabled by setting the new
|
||||||
:attr:`~django.forms.CharField.strip` argument to ``False``.
|
:attr:`~django.forms.CharField.strip` argument to ``False``.
|
||||||
|
|
||||||
|
* Template text that is translated and uses two or more consecutive percent
|
||||||
|
signs, e.g. ``"%%"``, may have a new `msgid` after ``makemessages`` is run
|
||||||
|
(most likely the translation will be marked fuzzy). The new ``msgid`` will be
|
||||||
|
marked ``"#, python-format"``.
|
||||||
|
|
||||||
* If neither :attr:`request.current_app <django.http.HttpRequest.current_app>`
|
* If neither :attr:`request.current_app <django.http.HttpRequest.current_app>`
|
||||||
nor :class:`Context.current_app <django.template.Context>` are set, the
|
nor :class:`Context.current_app <django.template.Context>` are set, the
|
||||||
:ttag:`url` template tag will now use the namespace of the current request.
|
:ttag:`url` template tag will now use the namespace of the current request.
|
||||||
|
|
|
@ -17,55 +17,5 @@ msgstr ""
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||||
|
|
||||||
#. Translators: Django template comment for translators
|
msgid "year"
|
||||||
#: templates/test.html:9
|
msgstr "année"
|
||||||
#, python-format
|
|
||||||
msgid "I think that 100%% is more that 50%% of anything."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/test.html:10
|
|
||||||
#, python-format
|
|
||||||
msgid "I think that 100%% is more that 50%% of %(obj)s."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/test.html:70
|
|
||||||
#, python-format
|
|
||||||
msgid "Literal with a percent symbol at the end %%"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/test.html:71
|
|
||||||
#, python-format
|
|
||||||
msgid "Literal with a percent %% symbol in the middle"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/test.html:72
|
|
||||||
#, python-format
|
|
||||||
msgid "Completed 50%% of all the tasks"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/test.html:73
|
|
||||||
#, python-format
|
|
||||||
msgctxt "ctx0"
|
|
||||||
msgid "Completed 99%% of all the tasks"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/test.html:74
|
|
||||||
#, python-format
|
|
||||||
msgid "Shouldn't double escape this sequence: %% (two percent signs)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/test.html:75
|
|
||||||
#, python-format
|
|
||||||
msgctxt "ctx1"
|
|
||||||
msgid "Shouldn't double escape this sequence %% either"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/test.html:76
|
|
||||||
#, python-format
|
|
||||||
msgid "Looks like a str fmt spec %%s but shouldn't be interpreted as such"
|
|
||||||
msgstr "Translation of the above string"
|
|
||||||
|
|
||||||
#: templates/test.html:77
|
|
||||||
#, python-format
|
|
||||||
msgid "Looks like a str fmt spec %% o but shouldn't be interpreted as such"
|
|
||||||
msgstr "Translation contains %% for the above string"
|
|
||||||
|
|
|
@ -17,55 +17,5 @@ msgstr ""
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||||
|
|
||||||
#. Translators: Django template comment for translators
|
msgid "hello world"
|
||||||
#: templates/test.html:9
|
msgstr "bok svijete"
|
||||||
#, python-format
|
|
||||||
msgid "I think that 100%% is more that 50%% of anything."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/test.html:10
|
|
||||||
#, python-format
|
|
||||||
msgid "I think that 100%% is more that 50%% of %(obj)s."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/test.html:70
|
|
||||||
#, python-format
|
|
||||||
msgid "Literal with a percent symbol at the end %%"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/test.html:71
|
|
||||||
#, python-format
|
|
||||||
msgid "Literal with a percent %% symbol in the middle"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/test.html:72
|
|
||||||
#, python-format
|
|
||||||
msgid "Completed 50%% of all the tasks"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/test.html:73
|
|
||||||
#, python-format
|
|
||||||
msgctxt "ctx0"
|
|
||||||
msgid "Completed 99%% of all the tasks"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/test.html:74
|
|
||||||
#, python-format
|
|
||||||
msgid "Shouldn't double escape this sequence: %% (two percent signs)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/test.html:75
|
|
||||||
#, python-format
|
|
||||||
msgctxt "ctx1"
|
|
||||||
msgid "Shouldn't double escape this sequence %% either"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/test.html:76
|
|
||||||
#, python-format
|
|
||||||
msgid "Looks like a str fmt spec %%s but shouldn't be interpreted as such"
|
|
||||||
msgstr "Translation of the above string"
|
|
||||||
|
|
||||||
#: templates/test.html:77
|
|
||||||
#, python-format
|
|
||||||
msgid "Looks like a str fmt spec %% o but shouldn't be interpreted as such"
|
|
||||||
msgstr "Translation contains %% for the above string"
|
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2011-12-04 04:59-0600\n"
|
|
||||||
"PO-Revision-Date: 2011-12-10 20:29-0300\n"
|
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
||||||
"Language: it\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Completed 50%% of all the tasks"
|
|
||||||
msgstr "IT translation of Completed 50%% of all the tasks"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Looks like a str fmt spec %%s but shouldn't be interpreted as such"
|
|
||||||
msgstr "Translation of the above string"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Looks like a str fmt spec %% o but shouldn't be interpreted as such"
|
|
||||||
msgstr "IT translation contains %% for the above string"
|
|
|
@ -5,10 +5,6 @@ string's meaning unveiled
|
||||||
{% trans "This literal should be included." %}
|
{% trans "This literal should be included." %}
|
||||||
{% trans "This literal should also be included wrapped or not wrapped depending on the use of the --no-wrap option." %}
|
{% trans "This literal should also be included wrapped or not wrapped depending on the use of the --no-wrap option." %}
|
||||||
|
|
||||||
{# Translators: Django template comment for translators #}
|
|
||||||
<p>{% blocktrans %}I think that 100% is more that 50% of anything.{% endblocktrans %}</p>
|
|
||||||
{% blocktrans with 'txt' as obj %}I think that 100% is more that 50% of {{ obj }}.{% endblocktrans %}
|
|
||||||
|
|
||||||
{% comment %}Some random comment
|
{% comment %}Some random comment
|
||||||
Some random comment
|
Some random comment
|
||||||
Translators: One-line translator comment #1
|
Translators: One-line translator comment #1
|
||||||
|
@ -67,17 +63,6 @@ continued here.{% endcomment %}
|
||||||
{% blocktrans context "Special blocktrans context #3" count 2 %}Translatable literal #8c-singular{% plural %}Translatable literal #8c-plural{% endblocktrans %}
|
{% blocktrans context "Special blocktrans context #3" count 2 %}Translatable literal #8c-singular{% plural %}Translatable literal #8c-plural{% endblocktrans %}
|
||||||
{% blocktrans with a=1 context "Special blocktrans context #4" %}Translatable literal #8d {{ a }}{% endblocktrans %}
|
{% blocktrans with a=1 context "Special blocktrans context #4" %}Translatable literal #8d {{ a }}{% endblocktrans %}
|
||||||
|
|
||||||
{% blocktrans with a=1 %}Blocktrans extraction shouldn't double escape this: %%, a={{ a }}{% endblocktrans %}
|
|
||||||
|
|
||||||
{% trans "Literal with a percent symbol at the end %" %}
|
|
||||||
{% trans "Literal with a percent % symbol in the middle" %}
|
|
||||||
{% trans "Completed 50% of all the tasks" %}
|
|
||||||
{% trans "Completed 99% of all the tasks" context "ctx0" %}
|
|
||||||
{% trans "Shouldn't double escape this sequence: %% (two percent signs)" %}
|
|
||||||
{% trans "Shouldn't double escape this sequence %% either" context "ctx1" %}
|
|
||||||
{% trans "Looks like a str fmt spec %s but shouldn't be interpreted as such" %}
|
|
||||||
{% trans "Looks like a str fmt spec % o but shouldn't be interpreted as such" %}
|
|
||||||
|
|
||||||
{% trans "Translatable literal with context wrapped in single quotes" context 'Context wrapped in single quotes' as var %}
|
{% trans "Translatable literal with context wrapped in single quotes" context 'Context wrapped in single quotes' as var %}
|
||||||
{% trans "Translatable literal with context wrapped in double quotes" context "Context wrapped in double quotes" as var %}
|
{% trans "Translatable literal with context wrapped in double quotes" context "Context wrapped in double quotes" as var %}
|
||||||
{% 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 %}
|
||||||
|
@ -94,4 +79,4 @@ continued here.{% endcomment %}
|
||||||
line breaks, this time
|
line breaks, this time
|
||||||
should be trimmed.
|
should be trimmed.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% trans "I'm on line 97" %}
|
{% trans "I'm on line 82" %}
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,52 @@
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
|
||||||
|
#: templates/percents.html:3
|
||||||
|
#, python-format
|
||||||
|
msgid "Literal with a percent symbol at the end %%"
|
||||||
|
msgstr "Littérale avec un symbole de pour cent à la fin %%"
|
||||||
|
|
||||||
|
#: templates/percents.html:4
|
||||||
|
#, python-format
|
||||||
|
msgid "Literal with a percent %% symbol in the middle"
|
||||||
|
msgstr "Pour cent littérale %% avec un symbole au milieu"
|
||||||
|
|
||||||
|
#: templates/percents.html:6
|
||||||
|
#, python-format
|
||||||
|
msgid "It is 100%%"
|
||||||
|
msgstr "Il est de 100%%"
|
||||||
|
|
||||||
|
#: templates/percents.html:7
|
||||||
|
#, python-format
|
||||||
|
msgctxt "female"
|
||||||
|
msgid "It is 100%%"
|
||||||
|
msgstr "Elle est de 100%%"
|
||||||
|
|
||||||
|
#: templates/percents.html:8
|
||||||
|
#, python-format
|
||||||
|
msgid "Looks like a str fmt spec %%s but should not be interpreted as such"
|
||||||
|
msgstr ""
|
||||||
|
"On dirait un spec str fmt %%s mais ne devrait pas être interprété comme plus "
|
||||||
|
"disponible"
|
||||||
|
|
||||||
|
#: templates/percents.html:9
|
||||||
|
#, python-format
|
||||||
|
msgid "Looks like a str fmt spec %% o but should not be interpreted as such"
|
||||||
|
msgstr ""
|
||||||
|
"On dirait un spec str fmt %% o mais ne devrait pas être interprété comme "
|
||||||
|
"plus disponible"
|
||||||
|
|
||||||
|
#: templates/percents.html:11
|
||||||
|
#, python-format
|
||||||
|
msgid "1 percent sign %%, 2 percent signs %%%%, 3 percent signs %%%%%%"
|
||||||
|
msgstr ""
|
||||||
|
"1 %% signe pour cent, signes %%%% 2 pour cent, trois signes de pourcentage %%"
|
||||||
|
"%%%%"
|
||||||
|
|
||||||
|
#: templates/percents.html:12
|
||||||
|
#, python-format
|
||||||
|
msgid "%(name)s says: 1 percent sign %%, 2 percent signs %%%%"
|
||||||
|
msgstr "%(name)s dit: 1 pour cent signe %%, deux signes de pourcentage %%%%"
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.append(os.path.abspath(os.path.join('..', '..', '..')))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sampleproject.settings")
|
||||||
|
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
|
||||||
|
execute_from_command_line(sys.argv)
|
|
@ -0,0 +1,12 @@
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% trans "Literal with a percent symbol at the end %" %}
|
||||||
|
{% trans "Literal with a percent % symbol in the middle" %}
|
||||||
|
|
||||||
|
{% trans "It is 100%" %}
|
||||||
|
{% trans "It is 100%" context "female" %}
|
||||||
|
{% trans "Looks like a str fmt spec %s but should not be interpreted as such" %}
|
||||||
|
{% trans "Looks like a str fmt spec % o but should not be interpreted as such" %}
|
||||||
|
|
||||||
|
{% trans "1 percent sign %, 2 percent signs %%, 3 percent signs %%%" %}
|
||||||
|
{% blocktrans with name="Simon" %}{{name}} says: 1 percent sign %, 2 percent signs %%{% endblocktrans %}
|
|
@ -0,0 +1,60 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
"""
|
||||||
|
Helper script to update sampleproject's translation catalogs.
|
||||||
|
|
||||||
|
When a bug has been identified related to i18n, this helps capture the issue
|
||||||
|
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.
|
||||||
|
|
||||||
|
How this script helps:
|
||||||
|
* Add {% trans "Two %% Three %%%" %} and blocktrans 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.
|
||||||
|
* Run this script.
|
||||||
|
* Test interpolation - verify templatetag rendering, test each in a template
|
||||||
|
that is rendered using an activated language from sampleproject's locale.
|
||||||
|
* Tests should fail, issue captured.
|
||||||
|
* Fix issue.
|
||||||
|
* Run this script.
|
||||||
|
* Tests all pass.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
proj_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
sys.path.append(os.path.abspath(os.path.join(proj_dir, '..', '..', '..')))
|
||||||
|
|
||||||
|
|
||||||
|
def update_translation_catalogs():
|
||||||
|
"""Run makemessages and compilemessages in sampleproject."""
|
||||||
|
from django.core.management import call_command
|
||||||
|
|
||||||
|
prev_cwd = os.getcwd()
|
||||||
|
|
||||||
|
os.chdir(proj_dir)
|
||||||
|
call_command('makemessages')
|
||||||
|
call_command('compilemessages')
|
||||||
|
|
||||||
|
# keep the diff friendly - remove 'POT-Creation-Date'
|
||||||
|
pofile = os.path.join(proj_dir, 'locale', 'fr', 'LC_MESSAGES', 'django.po')
|
||||||
|
|
||||||
|
with open(pofile) as f:
|
||||||
|
content = f.read()
|
||||||
|
content = re.sub(r'^"POT-Creation-Date.+$\s', '', content, flags=re.MULTILINE)
|
||||||
|
with open(pofile, 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
os.chdir(prev_cwd)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
update_translation_catalogs()
|
|
@ -81,31 +81,6 @@ class PoFileContentsTests(MessageCompilationTests):
|
||||||
self.assertTrue(os.path.exists(self.MO_FILE))
|
self.assertTrue(os.path.exists(self.MO_FILE))
|
||||||
|
|
||||||
|
|
||||||
class PercentRenderingTests(MessageCompilationTests):
|
|
||||||
# Ticket #11240 -- Testing rendering doesn't belong here but we are trying
|
|
||||||
# to keep tests for all the stack together
|
|
||||||
|
|
||||||
LOCALE = 'it'
|
|
||||||
MO_FILE = 'locale/%s/LC_MESSAGES/django.mo' % LOCALE
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(PercentRenderingTests, self).setUp()
|
|
||||||
self.addCleanup(os.unlink, os.path.join(self.test_dir, self.MO_FILE))
|
|
||||||
|
|
||||||
def test_percent_symbol_escaping(self):
|
|
||||||
with override_settings(LOCALE_PATHS=[os.path.join(self.test_dir, 'locale')]):
|
|
||||||
from django.template import Template, Context
|
|
||||||
call_command('compilemessages', locale=[self.LOCALE], stdout=StringIO())
|
|
||||||
with translation.override(self.LOCALE):
|
|
||||||
t = Template('{% load i18n %}{% trans "Looks like a str fmt spec %% o but shouldn\'t be interpreted as such" %}')
|
|
||||||
rendered = t.render(Context({}))
|
|
||||||
self.assertEqual(rendered, 'IT translation contains %% for the above string')
|
|
||||||
|
|
||||||
t = Template('{% load i18n %}{% trans "Completed 50%% of all the tasks" %}')
|
|
||||||
rendered = t.render(Context({}))
|
|
||||||
self.assertEqual(rendered, 'IT translation of Completed 50%% of all the tasks')
|
|
||||||
|
|
||||||
|
|
||||||
class MultipleLocaleCompilationTests(MessageCompilationTests):
|
class MultipleLocaleCompilationTests(MessageCompilationTests):
|
||||||
|
|
||||||
MO_FILE_HR = None
|
MO_FILE_HR = None
|
||||||
|
|
|
@ -149,54 +149,22 @@ class BasicExtractorTests(ExtractorTests):
|
||||||
self.assertTrue(os.path.exists(self.PO_FILE))
|
self.assertTrue(os.path.exists(self.PO_FILE))
|
||||||
with io.open(self.PO_FILE, 'r', encoding='utf-8') as fp:
|
with io.open(self.PO_FILE, 'r', encoding='utf-8') as fp:
|
||||||
po_contents = fp.read()
|
po_contents = fp.read()
|
||||||
self.assertIn('#. Translators: This comment should be extracted', po_contents)
|
|
||||||
self.assertNotIn('This comment should not be extracted', po_contents)
|
self.assertNotIn('This comment should not be extracted', po_contents)
|
||||||
# Comments in templates
|
|
||||||
self.assertIn('#. Translators: Django template comment for translators', po_contents)
|
|
||||||
self.assertIn("#. Translators: Django comment block for translators\n#. string's meaning unveiled", po_contents)
|
|
||||||
|
|
||||||
|
# Comments in templates
|
||||||
|
self.assertIn('#. Translators: This comment should be extracted', po_contents)
|
||||||
|
self.assertIn("#. Translators: Django comment block for translators\n#. string's meaning unveiled", po_contents)
|
||||||
self.assertIn('#. Translators: One-line translator comment #1', po_contents)
|
self.assertIn('#. Translators: One-line translator comment #1', po_contents)
|
||||||
self.assertIn('#. Translators: Two-line translator comment #1\n#. continued here.', po_contents)
|
self.assertIn('#. Translators: Two-line translator comment #1\n#. continued here.', po_contents)
|
||||||
|
|
||||||
self.assertIn('#. Translators: One-line translator comment #2', po_contents)
|
self.assertIn('#. Translators: One-line translator comment #2', po_contents)
|
||||||
self.assertIn('#. Translators: Two-line translator comment #2\n#. continued here.', po_contents)
|
self.assertIn('#. Translators: Two-line translator comment #2\n#. continued here.', po_contents)
|
||||||
|
|
||||||
self.assertIn('#. Translators: One-line translator comment #3', po_contents)
|
self.assertIn('#. Translators: One-line translator comment #3', po_contents)
|
||||||
self.assertIn('#. Translators: Two-line translator comment #3\n#. continued here.', po_contents)
|
self.assertIn('#. Translators: Two-line translator comment #3\n#. continued here.', po_contents)
|
||||||
|
|
||||||
self.assertIn('#. Translators: One-line translator comment #4', po_contents)
|
self.assertIn('#. Translators: One-line translator comment #4', po_contents)
|
||||||
self.assertIn('#. Translators: Two-line translator comment #4\n#. continued here.', po_contents)
|
self.assertIn('#. Translators: Two-line translator comment #4\n#. continued here.', po_contents)
|
||||||
|
|
||||||
self.assertIn('#. Translators: One-line translator comment #5 -- with non ASCII characters: áéíóúö', po_contents)
|
self.assertIn('#. Translators: One-line translator comment #5 -- with non ASCII characters: áéíóúö', po_contents)
|
||||||
self.assertIn('#. Translators: Two-line translator comment #5 -- with non ASCII characters: áéíóúö\n#. continued here.', po_contents)
|
self.assertIn('#. Translators: Two-line translator comment #5 -- with non ASCII characters: áéíóúö\n#. continued here.', po_contents)
|
||||||
|
|
||||||
def test_templatize_trans_tag(self):
|
|
||||||
# ticket #11240
|
|
||||||
os.chdir(self.test_dir)
|
|
||||||
management.call_command('makemessages', locale=[LOCALE], verbosity=0)
|
|
||||||
self.assertTrue(os.path.exists(self.PO_FILE))
|
|
||||||
with open(self.PO_FILE, 'r') as fp:
|
|
||||||
po_contents = force_text(fp.read())
|
|
||||||
self.assertMsgId('Literal with a percent symbol at the end %%', po_contents)
|
|
||||||
self.assertMsgId('Literal with a percent %% symbol in the middle', po_contents)
|
|
||||||
self.assertMsgId('Completed 50%% of all the tasks', po_contents)
|
|
||||||
self.assertMsgId('Completed 99%% of all the tasks', po_contents)
|
|
||||||
self.assertMsgId("Shouldn't double escape this sequence: %% (two percent signs)", po_contents)
|
|
||||||
self.assertMsgId("Shouldn't double escape this sequence %% either", po_contents)
|
|
||||||
self.assertMsgId("Looks like a str fmt spec %%s but shouldn't be interpreted as such", po_contents)
|
|
||||||
self.assertMsgId("Looks like a str fmt spec %% o but shouldn't be interpreted as such", po_contents)
|
|
||||||
|
|
||||||
def test_templatize_blocktrans_tag(self):
|
|
||||||
# ticket #11966
|
|
||||||
os.chdir(self.test_dir)
|
|
||||||
management.call_command('makemessages', locale=[LOCALE], verbosity=0)
|
|
||||||
self.assertTrue(os.path.exists(self.PO_FILE))
|
|
||||||
with open(self.PO_FILE, 'r') as fp:
|
|
||||||
po_contents = force_text(fp.read())
|
|
||||||
self.assertMsgId('I think that 100%% is more that 50%% of anything.', po_contents)
|
|
||||||
self.assertMsgId('I think that 100%% is more that 50%% of %(obj)s.', po_contents)
|
|
||||||
self.assertMsgId("Blocktrans extraction shouldn't double escape this: %%, a=%(a)s", po_contents)
|
|
||||||
|
|
||||||
def test_blocktrans_trimmed(self):
|
def test_blocktrans_trimmed(self):
|
||||||
os.chdir(self.test_dir)
|
os.chdir(self.test_dir)
|
||||||
management.call_command('makemessages', locale=[LOCALE], verbosity=0)
|
management.call_command('makemessages', locale=[LOCALE], verbosity=0)
|
||||||
|
@ -208,8 +176,8 @@ class BasicExtractorTests(ExtractorTests):
|
||||||
# should be trimmed
|
# should be trimmed
|
||||||
self.assertMsgId("Again some text with a few line breaks, this time should be trimmed.", po_contents)
|
self.assertMsgId("Again some text with a few line breaks, this time should be trimmed.", po_contents)
|
||||||
# #21406 -- Should adjust for eaten line numbers
|
# #21406 -- Should adjust for eaten line numbers
|
||||||
self.assertMsgId("I'm on line 97", po_contents)
|
self.assertMsgId("I'm on line 82", po_contents)
|
||||||
self.assertLocationCommentPresent(self.PO_FILE, 97, 'templates', 'test.html')
|
self.assertLocationCommentPresent(self.PO_FILE, 82, 'templates', 'test.html')
|
||||||
|
|
||||||
def test_force_en_us_locale(self):
|
def test_force_en_us_locale(self):
|
||||||
"""Value of locale-munging option used by the command is the right one"""
|
"""Value of locale-munging option used by the command is the right one"""
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.template import Context, Template
|
||||||
|
from django.test import SimpleTestCase, override_settings
|
||||||
|
from django.utils._os import upath
|
||||||
|
from django.utils.encoding import force_text
|
||||||
|
from django.utils.translation import activate, get_language, trans_real
|
||||||
|
|
||||||
|
from .test_extraction import ExtractorTests
|
||||||
|
|
||||||
|
SAMPLEPROJECT_DIR = os.path.join(os.path.dirname(os.path.abspath(upath(__file__))), 'sampleproject')
|
||||||
|
SAMPLEPROJECT_LOCALE = os.path.join(SAMPLEPROJECT_DIR, 'locale')
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(LOCALE_PATHS=[SAMPLEPROJECT_LOCALE])
|
||||||
|
class FrenchTestCase(SimpleTestCase):
|
||||||
|
"""Tests using the French translations of the sampleproject."""
|
||||||
|
|
||||||
|
PO_FILE = os.path.join(SAMPLEPROJECT_LOCALE, 'fr', 'LC_MESSAGES', 'django.po')
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self._language = get_language()
|
||||||
|
self._translations = trans_real._translations
|
||||||
|
activate('fr')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
trans_real._translations = self._translations
|
||||||
|
activate(self._language)
|
||||||
|
|
||||||
|
|
||||||
|
class ExtractingStringsWithPercentSigns(FrenchTestCase, ExtractorTests):
|
||||||
|
"""
|
||||||
|
Tests the extracted string found in the gettext catalog.
|
||||||
|
|
||||||
|
Ensures that percent signs are python formatted.
|
||||||
|
|
||||||
|
These tests should all have an analogous translation tests below, ensuring
|
||||||
|
the python formatting does not persist through to a rendered template.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(ExtractingStringsWithPercentSigns, self).setUp()
|
||||||
|
with open(self.PO_FILE, 'r') as fp:
|
||||||
|
self.po_contents = force_text(fp.read())
|
||||||
|
|
||||||
|
def test_trans_tag_with_percent_symbol_at_the_end(self):
|
||||||
|
self.assertMsgId('Literal with a percent symbol at the end %%', self.po_contents)
|
||||||
|
|
||||||
|
def test_trans_tag_with_percent_symbol_in_the_middle(self):
|
||||||
|
self.assertMsgId('Literal with a percent %% symbol in the middle', self.po_contents)
|
||||||
|
self.assertMsgId('It is 100%%', self.po_contents)
|
||||||
|
|
||||||
|
def test_trans_tag_with_string_that_look_like_fmt_spec(self):
|
||||||
|
self.assertMsgId('Looks like a str fmt spec %%s but should not be interpreted as such', self.po_contents)
|
||||||
|
self.assertMsgId('Looks like a str fmt spec %% o but should not be interpreted as such', self.po_contents)
|
||||||
|
|
||||||
|
def test_adds_python_format_to_all_percent_signs(self):
|
||||||
|
self.assertMsgId('1 percent sign %%, 2 percent signs %%%%, 3 percent signs %%%%%%', self.po_contents)
|
||||||
|
self.assertMsgId('%(name)s says: 1 percent sign %%, 2 percent signs %%%%', self.po_contents)
|
||||||
|
|
||||||
|
|
||||||
|
class RenderingTemplatesWithPercentSigns(FrenchTestCase):
|
||||||
|
"""
|
||||||
|
Test rendering of templates that use percent signs.
|
||||||
|
|
||||||
|
Ensures both trans and blocktrans tags behave consistently.
|
||||||
|
|
||||||
|
Refs #11240, #11966, #24257
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_translates_with_a_percent_symbol_at_the_end(self):
|
||||||
|
expected = 'Littérale avec un symbole de pour cent à la fin %'
|
||||||
|
|
||||||
|
trans_tpl = Template('{% load i18n %}{% trans "Literal with a percent symbol at the end %" %}')
|
||||||
|
self.assertEqual(trans_tpl.render(Context({})), expected)
|
||||||
|
|
||||||
|
block_tpl = Template(
|
||||||
|
'{% load i18n %}{% blocktrans %}Literal with a percent symbol at '
|
||||||
|
'the end %{% endblocktrans %}'
|
||||||
|
)
|
||||||
|
self.assertEqual(block_tpl.render(Context({})), expected)
|
||||||
|
|
||||||
|
def test_translates_with_percent_symbol_in_the_middle(self):
|
||||||
|
expected = 'Pour cent littérale % avec un symbole au milieu'
|
||||||
|
|
||||||
|
trans_tpl = Template('{% load i18n %}{% trans "Literal with a percent % symbol in the middle" %}')
|
||||||
|
self.assertEqual(trans_tpl.render(Context({})), expected)
|
||||||
|
|
||||||
|
block_tpl = Template(
|
||||||
|
'{% load i18n %}{% blocktrans %}Literal with a percent % symbol '
|
||||||
|
'in the middle{% endblocktrans %}'
|
||||||
|
)
|
||||||
|
self.assertEqual(block_tpl.render(Context({})), expected)
|
||||||
|
|
||||||
|
def test_translates_with_percent_symbol_using_context(self):
|
||||||
|
trans_tpl = Template('{% load i18n %}{% trans "It is 100%" %}')
|
||||||
|
self.assertEqual(trans_tpl.render(Context({})), 'Il est de 100%')
|
||||||
|
trans_tpl = Template('{% load i18n %}{% trans "It is 100%" context "female" %}')
|
||||||
|
self.assertEqual(trans_tpl.render(Context({})), 'Elle est de 100%')
|
||||||
|
|
||||||
|
block_tpl = Template('{% load i18n %}{% blocktrans %}It is 100%{% endblocktrans %}')
|
||||||
|
self.assertEqual(block_tpl.render(Context({})), 'Il est de 100%')
|
||||||
|
block_tpl = Template('{% load i18n %}{% blocktrans context "female" %}It is 100%{% endblocktrans %}')
|
||||||
|
self.assertEqual(block_tpl.render(Context({})), 'Elle est de 100%')
|
||||||
|
|
||||||
|
def test_translates_with_string_that_look_like_fmt_spec_with_trans(self):
|
||||||
|
# tests "%s"
|
||||||
|
expected = ('On dirait un spec str fmt %s mais ne devrait pas être interprété comme plus disponible')
|
||||||
|
trans_tpl = Template(
|
||||||
|
'{% load i18n %}{% trans "Looks like a str fmt spec %s but '
|
||||||
|
'should not be interpreted as such" %}'
|
||||||
|
)
|
||||||
|
self.assertEqual(trans_tpl.render(Context({})), expected)
|
||||||
|
block_tpl = Template(
|
||||||
|
'{% load i18n %}{% blocktrans %}Looks like a str fmt spec %s but '
|
||||||
|
'should not be interpreted as such{% endblocktrans %}'
|
||||||
|
)
|
||||||
|
self.assertEqual(block_tpl.render(Context({})), expected)
|
||||||
|
|
||||||
|
# tests "% o"
|
||||||
|
expected = ('On dirait un spec str fmt % o mais ne devrait pas être interprété comme plus disponible')
|
||||||
|
trans_tpl = Template(
|
||||||
|
'{% load i18n %}{% trans "Looks like a str fmt spec % o but should not be '
|
||||||
|
'interpreted as such" %}'
|
||||||
|
)
|
||||||
|
self.assertEqual(trans_tpl.render(Context({})), expected)
|
||||||
|
block_tpl = Template(
|
||||||
|
'{% load i18n %}{% blocktrans %}Looks like a str fmt spec % o but should not be '
|
||||||
|
'interpreted as such{% endblocktrans %}'
|
||||||
|
)
|
||||||
|
self.assertEqual(block_tpl.render(Context({})), expected)
|
||||||
|
|
||||||
|
def test_translates_multiple_percent_signs(self):
|
||||||
|
expected = ('1 % signe pour cent, signes %% 2 pour cent, trois signes de pourcentage %%%')
|
||||||
|
|
||||||
|
trans_tpl = Template(
|
||||||
|
'{% load i18n %}{% trans "1 percent sign %, 2 percent signs %%, '
|
||||||
|
'3 percent signs %%%" %}'
|
||||||
|
)
|
||||||
|
self.assertEqual(trans_tpl.render(Context({})), expected)
|
||||||
|
block_tpl = Template(
|
||||||
|
'{% load i18n %}{% blocktrans %}1 percent sign %, 2 percent signs '
|
||||||
|
'%%, 3 percent signs %%%{% endblocktrans %}'
|
||||||
|
)
|
||||||
|
self.assertEqual(block_tpl.render(Context({})), expected)
|
||||||
|
|
||||||
|
block_tpl = Template(
|
||||||
|
'{% load i18n %}{% blocktrans %}{{name}} says: 1 percent sign %, '
|
||||||
|
'2 percent signs %%{% endblocktrans %}'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
block_tpl.render(Context({"name": "Django"})),
|
||||||
|
'Django dit: 1 pour cent signe %, deux signes de pourcentage %%'
|
||||||
|
)
|
|
@ -323,3 +323,12 @@ class BasicSyntaxTests(SimpleTestCase):
|
||||||
msg = "Unclosed tag 'if'. Looking for one of: elif, else, endif."
|
msg = "Unclosed tag 'if'. Looking for one of: elif, else, endif."
|
||||||
with self.assertRaisesMessage(TemplateSyntaxError, msg):
|
with self.assertRaisesMessage(TemplateSyntaxError, msg):
|
||||||
self.engine.render_to_string('template')
|
self.engine.render_to_string('template')
|
||||||
|
|
||||||
|
@setup({'tpl-str': '%s', 'tpl-percent': '%%', 'tpl-weird-percent': '% %s'})
|
||||||
|
def test_ignores_strings_that_look_like_format_interpolation(self):
|
||||||
|
output = self.engine.render_to_string('tpl-str')
|
||||||
|
self.assertEqual(output, '%s')
|
||||||
|
output = self.engine.render_to_string('tpl-percent')
|
||||||
|
self.assertEqual(output, '%%')
|
||||||
|
output = self.engine.render_to_string('tpl-weird-percent')
|
||||||
|
self.assertEqual(output, '% %s')
|
||||||
|
|
|
@ -511,3 +511,13 @@ class I18nTagTests(SimpleTestCase):
|
||||||
msg = "The 'noop' option was specified more than once."
|
msg = "The 'noop' option was specified more than once."
|
||||||
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 "%s" %}'})
|
||||||
|
def test_trans_tag_using_a_string_that_looks_like_str_fmt(self):
|
||||||
|
output = self.engine.render_to_string('template')
|
||||||
|
self.assertEqual(output, '%s')
|
||||||
|
|
||||||
|
@setup({'template': '{% load i18n %}{% blocktrans %}%s{% endblocktrans %}'})
|
||||||
|
def test_blocktrans_tag_using_a_string_that_looks_like_str_fmt(self):
|
||||||
|
output = self.engine.render_to_string('template')
|
||||||
|
self.assertEqual(output, '%s')
|
||||||
|
|
Loading…
Reference in New Issue