diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index e05b5613a5..918d1842ee 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -529,7 +529,14 @@ def blankout(src, char): context_re = re.compile(r"""^\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?'))\s*""") -inline_re = re.compile(r"""^\s*trans\s+((?:"[^"]*?")|(?:'[^']*?'))(\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?')))?\s*""") +inline_re = re.compile( + # Match the trans 'some text' part + r"""^\s*trans\s+((?:"[^"]*?")|(?:'[^']*?'))""" + # Match and ignore optional filters + r"""(?:\s*\|\s*[^\s:]+(?::(?:[^\s'":]+|(?:"[^"]*?")|(?:'[^']*?')))?)*""" + # Match the optional context part + r"""(\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?')))?\s*""" +) block_re = re.compile(r"""^\s*blocktrans(\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?')))?(?:\s+|$)""") endblock_re = re.compile(r"""^\s*endblocktrans$""") plural_re = re.compile(r"""^\s*plural$""") diff --git a/tests/i18n/commands/templates/test.html b/tests/i18n/commands/templates/test.html index ad64dd741f..dde42d3fdd 100644 --- a/tests/i18n/commands/templates/test.html +++ b/tests/i18n/commands/templates/test.html @@ -58,6 +58,18 @@ continued here.{% endcomment %} {% trans "Translatable literal #7b" as var context "Special trans context #2" %} {% trans "Translatable literal #7c" context "Special trans context #3" as var %} +{% trans "Translatable literal #7.1a" | upper context "context #7.1a" %} +{% trans "Translatable literal #7.1b" |upper as var context "context #7.1b" %} +{% trans "Translatable literal #7.1c"| upper context "context #7.1c" as var %} + +{% trans "Translatable literal #7.1d"|add:" foo" context "context #7.1d" %} +{% trans "Translatable literal #7.1e"|add:' ûè本' as var context "context #7.1e" %} +{% with foo=" foo" %} + {% trans "Translatable literal #7.1f"|add:foo context "context #7.1f" as var %} +{% endwith %} +{% trans "Translatable literal #7.1g"|add:2 context "context #7.1g" as var %} +{% trans "Translatable literal #7.1h" | add:"foo" | add:2 context "context #7.1h" as var %} + {% blocktrans context "Special blocktrans context #1" %}Translatable literal #8a{% endblocktrans %} {% blocktrans count 2 context "Special blocktrans context #2" %}Translatable literal #8b-singular{% plural %}Translatable literal #8b-plural{% endblocktrans %} {% blocktrans context "Special blocktrans context #3" count 2 %}Translatable literal #8c-singular{% plural %}Translatable literal #8c-plural{% endblocktrans %} @@ -79,7 +91,7 @@ continued here.{% endcomment %} line breaks, this time should be trimmed. {% endblocktrans %} -{% trans "I'm on line 82" %} +{% trans "Get my line number" %} {% blocktrans trimmed count counter=mylist|length %} First `trans`, then `blocktrans` with a plural diff --git a/tests/i18n/test_extraction.py b/tests/i18n/test_extraction.py index 9a665ec0de..4a612c546b 100644 --- a/tests/i18n/test_extraction.py +++ b/tests/i18n/test_extraction.py @@ -102,15 +102,28 @@ class ExtractorTests(SimpleTestCase): # #: path/to/file.html:123 cwd_prefix = '' parts = ['#: '] - parts.append(os.path.join(cwd_prefix, *comment_parts)) + + path = os.path.join(cwd_prefix, *comment_parts) + parts.append(path) + + if isinstance(line_number, six.string_types): + line_number = self._get_token_line_number(path, line_number) if line_number is not None: parts.append(':%d' % line_number) + needle = ''.join(parts) if assert_presence: return self.assertIn(needle, po_contents, '"%s" not found in final .po file.' % needle) else: return self.assertNotIn(needle, po_contents, '"%s" shouldn\'t be in final .po file.' % needle) + def _get_token_line_number(self, path, token): + with open(path) as f: + for line, content in enumerate(f, 1): + if token in force_text(content): + return line + self.fail("The token '%s' could not be found in %s, please check the test config" % (token, path)) + def assertLocationCommentPresent(self, po_filename, line_number, *comment_parts): """ self.assertLocationCommentPresent('django.po', 42, 'dirA', 'dirB', 'foo.py') @@ -121,7 +134,11 @@ class ExtractorTests(SimpleTestCase): (or `#: .\dirA\dirB\foo.py:42` on Windows) - None can be passed for the line_number argument to skip checking of the :42 suffix part. + None can be passed for the line_number argument to skip checking of + the :42 suffix part. + A string token can also be pased as line_number, in which case it + will be searched in the template, and its line number will be used. + A msgid is a suitable candidate. """ return self._assertPoLocComment(True, po_filename, line_number, *comment_parts) @@ -179,8 +196,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 82", po_contents) - self.assertLocationCommentPresent(self.PO_FILE, 82, 'templates', 'test.html') + self.assertMsgId("Get my line number", po_contents) + self.assertLocationCommentPresent(self.PO_FILE, 'Get my line number', 'templates', 'test.html') def test_force_en_us_locale(self): """Value of locale-munging option used by the command is the right one""" @@ -235,6 +252,11 @@ class BasicExtractorTests(ExtractorTests): self.assertIn('msgctxt "Special trans context #3"', po_contents) self.assertMsgId("Translatable literal #7c", po_contents) + # {% trans %} with a filter + for minor_part in 'abcdefgh': # Iterate from #7.1a to #7.1h template markers + self.assertIn('msgctxt "context #7.1{}"'.format(minor_part), po_contents) + self.assertMsgId('Translatable literal #7.1{}'.format(minor_part), po_contents) + # {% blocktrans %} self.assertIn('msgctxt "Special blocktrans context #1"', po_contents) self.assertMsgId("Translatable literal #8a", po_contents) @@ -578,7 +600,7 @@ class LocationCommentsTests(ExtractorTests): management.call_command('makemessages', locale=[LOCALE], verbosity=0, no_location=False) self.assertTrue(os.path.exists(self.PO_FILE)) # #16903 -- Standard comment with source file relative path should be present - self.assertLocationCommentPresent(self.PO_FILE, 55, 'templates', 'test.html') + self.assertLocationCommentPresent(self.PO_FILE, 'Translatable literal #6b', 'templates', 'test.html') # #21208 -- Leaky paths in comments on Windows e.g. #: path\to\file.html.py:123 self.assertLocationCommentNotPresent(self.PO_FILE, None, 'templates', 'test.html.py')