Fixed #5849 -- Strip whitespace from blocktrans

Add the trimmed option to the blocktrans tag to trim any newlines and
whitespace from its content.

This allows the developer to indent the blocktrans tag without adding
new lines and whitespace to the msgid in the PO file.

Thanks to mpessas for the initial patch and Dmitri Fedortchenko for the
report.
This commit is contained in:
Bouke Haarsma 2013-11-02 20:01:17 +01:00 committed by Anssi Kääriäinen
parent 30203a0dea
commit 7a7c789d5a
8 changed files with 104 additions and 10 deletions

View File

@ -97,14 +97,16 @@ class TranslateNode(Node):
class BlockTranslateNode(Node): class BlockTranslateNode(Node):
def __init__(self, extra_context, singular, plural=None, countervar=None, def __init__(self, extra_context, singular, plural=None, countervar=None,
counter=None, message_context=None): counter=None, message_context=None, trimmed=False):
self.extra_context = extra_context self.extra_context = extra_context
self.singular = singular self.singular = singular
self.plural = plural self.plural = plural
self.countervar = countervar self.countervar = countervar
self.counter = counter self.counter = counter
self.message_context = message_context self.message_context = message_context
self.trimmed = trimmed
def render_token_list(self, tokens): def render_token_list(self, tokens):
result = [] result = []
@ -115,7 +117,10 @@ class BlockTranslateNode(Node):
elif token.token_type == TOKEN_VAR: elif token.token_type == TOKEN_VAR:
result.append('%%(%s)s' % token.contents) result.append('%%(%s)s' % token.contents)
vars.append(token.contents) vars.append(token.contents)
return ''.join(result), vars msg = ''.join(result)
if self.trimmed:
msg = translation.trim_whitespace(msg)
return msg, vars
def render(self, context, nested=False): def render(self, context, nested=False):
if self.message_context: if self.message_context:
@ -438,6 +443,8 @@ def do_block_translate(parser, token):
'"context" in %r tag expected ' '"context" in %r tag expected '
'exactly one argument.') % bits[0] 'exactly one argument.') % bits[0]
six.reraise(TemplateSyntaxError, TemplateSyntaxError(msg), sys.exc_info()[2]) six.reraise(TemplateSyntaxError, TemplateSyntaxError(msg), sys.exc_info()[2])
elif option == "trimmed":
value = True
else: else:
raise TemplateSyntaxError('Unknown argument for %r tag: %r.' % raise TemplateSyntaxError('Unknown argument for %r tag: %r.' %
(bits[0], option)) (bits[0], option))
@ -453,6 +460,8 @@ def do_block_translate(parser, token):
message_context = None message_context = None
extra_context = options.get('with', {}) extra_context = options.get('with', {})
trimmed = options.get("trimmed", False)
singular = [] singular = []
plural = [] plural = []
while parser.tokens: while parser.tokens:
@ -474,7 +483,7 @@ def do_block_translate(parser, token):
raise TemplateSyntaxError("'blocktrans' doesn't allow other block tags (seen %r) inside it" % token.contents) raise TemplateSyntaxError("'blocktrans' doesn't allow other block tags (seen %r) inside it" % token.contents)
return BlockTranslateNode(extra_context, singular, plural, countervar, return BlockTranslateNode(extra_context, singular, plural, countervar,
counter, message_context) counter, message_context, trimmed=trimmed)
@register.tag @register.tag

View File

@ -2,7 +2,7 @@
Internationalization support. Internationalization support.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
import re
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.functional import lazy from django.utils.functional import lazy
from django.utils import six from django.utils import six
@ -218,3 +218,9 @@ def get_language_info(lang_code):
return LANG_INFO[generic_lang_code] return LANG_INFO[generic_lang_code]
except KeyError: except KeyError:
raise KeyError("Unknown language code %s and %s." % (lang_code, generic_lang_code)) raise KeyError("Unknown language code %s and %s." % (lang_code, generic_lang_code))
trim_whitespace_re = re.compile('\s*\n\s*')
def trim_whitespace(s):
return trim_whitespace_re.sub(' ', s.strip())

View File

@ -19,7 +19,7 @@ from django.utils._os import upath
from django.utils.safestring import mark_safe, SafeData from django.utils.safestring import mark_safe, SafeData
from django.utils import six from django.utils import six
from django.utils.six import StringIO from django.utils.six import StringIO
from django.utils.translation import TranslatorCommentWarning from django.utils.translation import TranslatorCommentWarning, trim_whitespace
# Translations are cached in a dictionary for every language+app tuple. # Translations are cached in a dictionary for every language+app tuple.
@ -530,6 +530,7 @@ def blankout(src, char):
""" """
return dot_re.sub(char, src) return dot_re.sub(char, src)
context_re = re.compile(r"""^\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?'))\s*""") 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(r"""^\s*trans\s+((?:"[^"]*?")|(?:'[^']*?'))(\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?')))?\s*""")
block_re = re.compile(r"""^\s*blocktrans(\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?')))?(?:\s+|$)""") block_re = re.compile(r"""^\s*blocktrans(\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?')))?(?:\s+|$)""")
@ -553,6 +554,7 @@ def templatize(src, origin=None):
message_context = None message_context = None
intrans = False intrans = False
inplural = False inplural = False
trimmed = False
singular = [] singular = []
plural = [] plural = []
incomment = False incomment = False
@ -582,20 +584,29 @@ def templatize(src, origin=None):
endbmatch = endblock_re.match(t.contents) endbmatch = endblock_re.match(t.contents)
pluralmatch = plural_re.match(t.contents) pluralmatch = plural_re.match(t.contents)
if endbmatch: if endbmatch:
if trimmed:
singular = trim_whitespace(''.join(singular))
else:
singular = ''.join(singular)
if inplural: if inplural:
if message_context: if trimmed:
out.write(' npgettext(%r, %r, %r,count) ' % (message_context, ''.join(singular), ''.join(plural))) plural = trim_whitespace(''.join(plural))
else: else:
out.write(' ngettext(%r, %r, count) ' % (''.join(singular), ''.join(plural))) plural = ''.join(plural)
if message_context:
out.write(' npgettext(%r, %r, %r,count) ' % (message_context, singular, plural))
else:
out.write(' ngettext(%r, %r, count) ' % (singular, plural))
for part in singular: for part in singular:
out.write(blankout(part, 'S')) out.write(blankout(part, 'S'))
for part in plural: for part in plural:
out.write(blankout(part, 'P')) out.write(blankout(part, 'P'))
else: else:
if message_context: if message_context:
out.write(' pgettext(%r, %r) ' % (message_context, ''.join(singular))) out.write(' pgettext(%r, %r) ' % (message_context, singular))
else: else:
out.write(' gettext(%r) ' % ''.join(singular)) out.write(' gettext(%r) ' % singular)
for part in singular: for part in singular:
out.write(blankout(part, 'S')) out.write(blankout(part, 'S'))
message_context = None message_context = None
@ -678,6 +689,7 @@ def templatize(src, origin=None):
message_context = message_context.strip("'") message_context = message_context.strip("'")
intrans = True intrans = True
inplural = False inplural = False
trimmed = 'trimmed' in t.split_contents()
singular = [] singular = []
plural = [] plural = []
elif cmatches: elif cmatches:

View File

@ -342,6 +342,15 @@ Internationalization
still read from in 1.7. Sessions will be migrated to the new ``_language`` still read from in 1.7. Sessions will be migrated to the new ``_language``
key as they are written. key as they are written.
* The :ttag:`blocktrans` now supports a ``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.
Management Commands Management Commands
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^

View File

@ -674,6 +674,30 @@ markers<contextual-markers>` using the ``context`` keyword:
{% blocktrans with name=user.username context "greeting" %}Hi {{ name }}{% endblocktrans %} {% blocktrans with name=user.username context "greeting" %}Hi {{ name }}{% endblocktrans %}
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.
For instance, the following ``{% blocktrans %}`` tag::
{% blocktrans trimmed %}
First sentence.
Second paragraph.
{% endblocktrans %}
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:: 1.7
The ``trimmed`` option was added.
String literals passed to tags and filters String literals passed to tags and filters
------------------------------------------ ------------------------------------------

View File

@ -82,3 +82,15 @@ continued here.{% endcomment %}
{% 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 %}
{% blocktrans context "Special blocktrans context wrapped in double quotes" %}Translatable literal with context wrapped in double quotes{% endblocktrans %} {% blocktrans context "Special blocktrans context wrapped in double quotes" %}Translatable literal with context wrapped in double quotes{% endblocktrans %}
{# BasicExtractorTests.test_blocktrans_trimmed #}
{% blocktrans %}
Text with a few
line breaks.
{% endblocktrans %}
{% blocktrans trimmed %}
Again some text with a few
line breaks, this time
should be trimmed.
{% endblocktrans %}

View File

@ -121,6 +121,17 @@ class BasicExtractorTests(ExtractorTests):
self.assertMsgId('I think that 100%% is more that 50%% of %(obj)s.', 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) 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)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
po_contents = force_text(fp.read())
# should not be trimmed
self.assertNotMsgId('Text with a few line breaks.', po_contents)
# should be trimmed
self.assertMsgId("Again some text with a few line breaks, this time should be trimmed.", po_contents)
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"""
from django.core.management.commands.makemessages import Command from django.core.management.commands.makemessages import Command

View File

@ -245,6 +245,17 @@ class TranslationTests(TransRealMixin, TestCase):
rendered = t.render(Context()) rendered = t.render(Context())
self.assertEqual(rendered, 'Andere: Es gibt 5 Kommentare') self.assertEqual(rendered, 'Andere: Es gibt 5 Kommentare')
# Using trimmed
t = 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('{% load i18n %}{% blocktrans with num_comments=5 context "comment count" trimmed %}\n\nThere are \t\n \t {{ num_comments }} comments\n\n{% endblocktrans %}')
rendered = t.render(Context())
self.assertEqual(rendered, 'Es gibt 5 Kommentare')
t = Template('{% load i18n %}{% blocktrans context "other super search" count number=2 trimmed %}\n{{ number }} super \n result{% plural %}{{ number }} super results{% endblocktrans %}')
rendered = t.render(Context())
self.assertEqual(rendered, '2 andere Super-Ergebnisse')
# Mis-uses # Mis-uses
self.assertRaises(TemplateSyntaxError, Template, '{% load i18n %}{% blocktrans context with month="May" %}{{ month }}{% endblocktrans %}') self.assertRaises(TemplateSyntaxError, Template, '{% load i18n %}{% blocktrans context with month="May" %}{{ month }}{% endblocktrans %}')
self.assertRaises(TemplateSyntaxError, Template, '{% load i18n %}{% blocktrans context %}{% endblocktrans %}') self.assertRaises(TemplateSyntaxError, Template, '{% load i18n %}{% blocktrans context %}{% endblocktrans %}')