mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
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"
|
||||
value = self.literal
|
||||
if self.translate:
|
||||
is_safe = isinstance(value, SafeData)
|
||||
msgid = value.replace('%', '%%')
|
||||
msgid = mark_safe(msgid) if is_safe else msgid
|
||||
if self.message_context:
|
||||
return pgettext_lazy(self.message_context, value)
|
||||
return pgettext_lazy(self.message_context, msgid)
|
||||
else:
|
||||
return ugettext_lazy(value)
|
||||
return ugettext_lazy(msgid)
|
||||
return value
|
||||
|
||||
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.defaulttags import token_kwargs
|
||||
from django.utils import six, translation
|
||||
from django.utils.safestring import SafeData, mark_safe
|
||||
|
||||
register = Library()
|
||||
|
||||
@ -86,6 +87,11 @@ class TranslateNode(Node):
|
||||
self.message_context.resolve(context))
|
||||
output = self.filter_expression.resolve(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:
|
||||
context[self.asvar] = value
|
||||
return ''
|
||||
|
@ -534,7 +534,6 @@ block_re = re.compile(r"""^\s*blocktrans(\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?
|
||||
endblock_re = re.compile(r"""^\s*endblocktrans$""")
|
||||
plural_re = re.compile(r"""^\s*plural$""")
|
||||
constant_re = re.compile(r"""_\(((?:".*?")|(?:'.*?'))\)""")
|
||||
one_percent_re = re.compile(r"""(?<!%)%(?!%)""")
|
||||
|
||||
|
||||
def templatize(src, origin=None):
|
||||
@ -631,7 +630,7 @@ def templatize(src, origin=None):
|
||||
else:
|
||||
singular.append('%%(%s)s' % t.contents)
|
||||
elif t.token_type == TOKEN_TEXT:
|
||||
contents = one_percent_re.sub('%%', t.contents)
|
||||
contents = t.contents.replace('%', '%%')
|
||||
if inplural:
|
||||
plural.append(contents)
|
||||
else:
|
||||
@ -667,7 +666,7 @@ def templatize(src, origin=None):
|
||||
g = g.strip('"')
|
||||
elif g[0] == "'":
|
||||
g = g.strip("'")
|
||||
g = one_percent_re.sub('%%', g)
|
||||
g = g.replace('%', '%%')
|
||||
if imatch.group(2):
|
||||
# A context is provided
|
||||
context_match = context_re.match(imatch.group(2))
|
||||
|
@ -940,6 +940,11 @@ Miscellaneous
|
||||
whitespace by default. This can be disabled by setting the new
|
||||
: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>`
|
||||
nor :class:`Context.current_app <django.template.Context>` are set, the
|
||||
:ttag:`url` template tag will now use the namespace of the current request.
|
||||
|
@ -17,55 +17,5 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
|
||||
#. Translators: Django template comment for translators
|
||||
#: templates/test.html:9
|
||||
#, 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"
|
||||
msgid "year"
|
||||
msgstr "année"
|
||||
|
@ -17,55 +17,5 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
|
||||
#. Translators: Django template comment for translators
|
||||
#: templates/test.html:9
|
||||
#, 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"
|
||||
msgid "hello world"
|
||||
msgstr "bok svijete"
|
||||
|
@ -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 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
|
||||
Some random comment
|
||||
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 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 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 %}
|
||||
@ -94,4 +79,4 @@ continued here.{% endcomment %}
|
||||
line breaks, this time
|
||||
should be trimmed.
|
||||
{% endblocktrans %}
|
||||
{% trans "I'm on line 97" %}
|
||||
{% trans "I'm on line 82" %}
|
||||
|
BIN
tests/i18n/sampleproject/locale/fr/LC_MESSAGES/django.mo
Normal file
BIN
tests/i18n/sampleproject/locale/fr/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
52
tests/i18n/sampleproject/locale/fr/LC_MESSAGES/django.po
Normal file
52
tests/i18n/sampleproject/locale/fr/LC_MESSAGES/django.po
Normal file
@ -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 %%%%"
|
12
tests/i18n/sampleproject/manage.py
Executable file
12
tests/i18n/sampleproject/manage.py
Executable file
@ -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
tests/i18n/sampleproject/sampleproject/__init__.py
Normal file
0
tests/i18n/sampleproject/sampleproject/__init__.py
Normal file
0
tests/i18n/sampleproject/sampleproject/settings.py
Normal file
0
tests/i18n/sampleproject/sampleproject/settings.py
Normal file
12
tests/i18n/sampleproject/templates/percents.html
Normal file
12
tests/i18n/sampleproject/templates/percents.html
Normal file
@ -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 %}
|
60
tests/i18n/sampleproject/update_catalogs.py
Executable file
60
tests/i18n/sampleproject/update_catalogs.py
Executable file
@ -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))
|
||||
|
||||
|
||||
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):
|
||||
|
||||
MO_FILE_HR = None
|
||||
|
@ -149,54 +149,22 @@ class BasicExtractorTests(ExtractorTests):
|
||||
self.assertTrue(os.path.exists(self.PO_FILE))
|
||||
with io.open(self.PO_FILE, 'r', encoding='utf-8') as fp:
|
||||
po_contents = fp.read()
|
||||
self.assertIn('#. Translators: This comment should 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: Two-line translator comment #1\n#. continued here.', 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: One-line translator comment #3', 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: 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: 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):
|
||||
os.chdir(self.test_dir)
|
||||
management.call_command('makemessages', locale=[LOCALE], verbosity=0)
|
||||
@ -208,8 +176,8 @@ class BasicExtractorTests(ExtractorTests):
|
||||
# should be trimmed
|
||||
self.assertMsgId("Again some text with a few line breaks, this time should be trimmed.", po_contents)
|
||||
# #21406 -- Should adjust for eaten line numbers
|
||||
self.assertMsgId("I'm on line 97", po_contents)
|
||||
self.assertLocationCommentPresent(self.PO_FILE, 97, 'templates', 'test.html')
|
||||
self.assertMsgId("I'm on line 82", po_contents)
|
||||
self.assertLocationCommentPresent(self.PO_FILE, 82, 'templates', 'test.html')
|
||||
|
||||
def test_force_en_us_locale(self):
|
||||
"""Value of locale-munging option used by the command is the right one"""
|
||||
|
157
tests/i18n/test_percents.py
Normal file
157
tests/i18n/test_percents.py
Normal file
@ -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."
|
||||
with self.assertRaisesMessage(TemplateSyntaxError, msg):
|
||||
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."
|
||||
with self.assertRaisesMessage(TemplateSyntaxError, msg):
|
||||
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
Block a user