From: Adrian Moennich Date: Fri, 17 Feb 2017 22:49:39 +0000 (+0100) Subject: Add support for `{% trans trimmed ... %}` X-Git-Tag: 2.10~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e605ff1a0bc304bb543f6867c0798e0480f31926;p=thirdparty%2Fjinja.git Add support for `{% trans trimmed ... %}` Same behavior as in Django: All linebreaks and the whitespace surrounding linebreaks are replaced with a single space. closes #504 --- diff --git a/CHANGES b/CHANGES index 81098b3e..b12dd4f5 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,9 @@ Version 2.10 - Added a `namespace` function that creates a special object which allows attribute assignment using the `set` tag. This can be used to carry data across scopes, e.g. from a loop body to code that comes after the loop. +- Added a `trimmed` modifier to `{% trans %}` to strip linebreaks and + surrounding whitespace. Also added a new policy to enable this for all + `trans` blocks. Version 2.9.6 ------------- diff --git a/docs/api.rst b/docs/api.rst index b983e8f0..fedc1c73 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -611,6 +611,13 @@ Example:: Keyword arguments to be passed to the dump function. The default is ``{'sort_keys': True}``. +.. _ext-i18n-trimmed: + +``ext.i18n.trimmed``: + If this is set to `True`, ``{% trans %}`` blocks of the + :ref:`i18n-extension` will always unify linebreaks and surrounding + whitespace as if the `trimmed` modifier was used. + Utilities --------- diff --git a/docs/extensions.rst b/docs/extensions.rst index cd093469..00adee9f 100644 --- a/docs/extensions.rst +++ b/docs/extensions.rst @@ -111,6 +111,15 @@ The usage of the `i18n` extension for template designers is covered as part .. _newstyle-gettext: +Whitespace Trimming +~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.10 + +Linebreaks and surrounding whitespace can be automatically trimmed by enabling +the ``ext.i18n.trimmed`` :ref:`policy `. + + Newstyle Gettext ~~~~~~~~~~~~~~~~ diff --git a/docs/templates.rst b/docs/templates.rst index 650b55e6..62852c4b 100644 --- a/docs/templates.rst +++ b/docs/templates.rst @@ -1492,6 +1492,22 @@ which should be used for pluralizing by adding it as parameter to `pluralize`:: {% trans ..., user_count=users|length %}... {% pluralize user_count %}...{% endtrans %} +When translating longer blocks of text, whitespace and linebreaks result in +rather ugly and error-prone translation strings. To avoid this, a trans block +can be marked as trimmed which will replace all linebreaks and the whitespace +surrounding them with a single space and remove leading/trailing whitespace:: + + {% trans trimmed book_title=book.title %} + This is {{ book_title }}. + You should read it! + {% endtrans %} + +If trimming is enabled globally, the `notrimmed` modifier can be used to +disable it for a `trans` block. + +.. versionadded:: 2.10 + The `trimmed` and `notrimmed` modifiers have been added. + It's also possible to translate strings in expressions. For that purpose, three functions exist: diff --git a/jinja2/defaults.py b/jinja2/defaults.py index 6970e888..7c93dec0 100644 --- a/jinja2/defaults.py +++ b/jinja2/defaults.py @@ -48,6 +48,7 @@ DEFAULT_POLICIES = { 'truncate.leeway': 5, 'json.dumps_function': None, 'json.dumps_kwargs': {'sort_keys': True}, + 'ext.i18n.trimmed': False, } diff --git a/jinja2/ext.py b/jinja2/ext.py index 75e1f3b6..0734a84f 100644 --- a/jinja2/ext.py +++ b/jinja2/ext.py @@ -10,6 +10,8 @@ :copyright: (c) 2017 by the Jinja Team. :license: BSD. """ +import re + from jinja2 import nodes from jinja2.defaults import BLOCK_START_STRING, \ BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \ @@ -223,6 +225,7 @@ class InternationalizationExtension(Extension): plural_expr = None plural_expr_assignment = None variables = {} + trimmed = None while parser.stream.current.type != 'block_end': if variables: parser.stream.expect('comma') @@ -241,6 +244,9 @@ class InternationalizationExtension(Extension): if parser.stream.current.type == 'assign': next(parser.stream) variables[name.value] = var = parser.parse_expression() + elif trimmed is None and name.value in ('trimmed', 'notrimmed'): + trimmed = name.value == 'trimmed' + continue else: variables[name.value] = var = nodes.Name(name.value, 'load') @@ -256,7 +262,7 @@ class InternationalizationExtension(Extension): parser.stream.expect('block_end') - plural = plural_names = None + plural = None have_plural = False referenced = set() @@ -297,6 +303,13 @@ class InternationalizationExtension(Extension): elif plural_expr is None: parser.fail('pluralize without variables', lineno) + if trimmed is None: + trimmed = self.environment.policies['ext.i18n.trimmed'] + if trimmed: + singular = self._trim_whitespace(singular) + if plural: + plural = self._trim_whitespace(plural) + node = self._make_node(singular, plural, variables, plural_expr, bool(referenced), num_called_num and have_plural) @@ -306,6 +319,9 @@ class InternationalizationExtension(Extension): else: return node + def _trim_whitespace(self, string, _ws_re=re.compile(r'\s*\n\s*')): + return _ws_re.sub(' ', string.strip()) + def _parse_block(self, parser, allow_pluralize): """Parse until the next block tag with a given name.""" referenced = [] @@ -583,6 +599,8 @@ def babel_extract(fileobj, keywords, comment_tags, options): auto_reload=False ) + if getbool(options, 'trimmed'): + environment.policies['ext.i18n.trimmed'] = True if getbool(options, 'newstyle_gettext'): environment.newstyle_gettext = True diff --git a/tests/test_ext.py b/tests/test_ext.py index 65a30cab..c3b028ff 100644 --- a/tests/test_ext.py +++ b/tests/test_ext.py @@ -91,6 +91,13 @@ i18n_env.globals.update({ 'gettext': gettext, 'ngettext': ngettext }) +i18n_env_trimmed = Environment(extensions=['jinja2.ext.i18n']) +i18n_env_trimmed.policies['ext.i18n.trimmed'] = True +i18n_env_trimmed.globals.update({ + '_': gettext, + 'gettext': gettext, + 'ngettext': ngettext +}) newstyle_i18n_env = Environment( loader=DictLoader(newstyle_i18n_templates), @@ -270,6 +277,36 @@ class TestInternationalization(object): tmpl = i18n_env.get_template('stringformat.html') assert tmpl.render(LANGUAGE='de', user_count=5) == 'Benutzer: 5' + def test_trimmed(self): + tmpl = i18n_env.from_string( + '{%- trans trimmed %} hello\n world {% endtrans -%}') + assert tmpl.render() == 'hello world' + + def test_trimmed_policy(self): + s = '{%- trans %} hello\n world {% endtrans -%}' + tmpl = i18n_env.from_string(s) + trimmed_tmpl = i18n_env_trimmed.from_string(s) + assert tmpl.render() == ' hello\n world ' + assert trimmed_tmpl.render() == 'hello world' + + def test_trimmed_policy_override(self): + tmpl = i18n_env_trimmed.from_string( + '{%- trans notrimmed %} hello\n world {% endtrans -%}') + assert tmpl.render() == ' hello\n world ' + + def test_trimmed_vars(self): + tmpl = i18n_env.from_string( + '{%- trans trimmed x="world" %} hello\n {{ x }} {% endtrans -%}') + assert tmpl.render() == 'hello world' + + def test_trimmed_varname_trimmed(self): + # unlikely variable name, but when used as a variable + # it should not enable trimming + tmpl = i18n_env.from_string( + '{%- trans trimmed = "world" %} hello\n {{ trimmed }} ' + '{% endtrans -%}') + assert tmpl.render() == ' hello\n world ' + def test_extract(self): from jinja2.ext import babel_extract source = BytesIO(''' @@ -284,6 +321,37 @@ class TestInternationalization(object): (4, 'ngettext', (u'%(users)s user', u'%(users)s users', None), []) ] + def test_extract_trimmed(self): + from jinja2.ext import babel_extract + source = BytesIO(''' + {{ gettext(' Hello \n World') }} + {% trans trimmed %} Hello \n World{% endtrans %} + {% trans trimmed %}{{ users }} \n user + {%- pluralize %}{{ users }} \n users{% endtrans %} + '''.encode('ascii')) # make python 3 happy + assert list(babel_extract(source, + ('gettext', 'ngettext', '_'), [], {})) == [ + (2, 'gettext', u' Hello \n World', []), + (4, 'gettext', u'Hello World', []), + (6, 'ngettext', (u'%(users)s user', u'%(users)s users', None), []) + ] + + def test_extract_trimmed_option(self): + from jinja2.ext import babel_extract + source = BytesIO(''' + {{ gettext(' Hello \n World') }} + {% trans %} Hello \n World{% endtrans %} + {% trans %}{{ users }} \n user + {%- pluralize %}{{ users }} \n users{% endtrans %} + '''.encode('ascii')) # make python 3 happy + opts = {'trimmed': 'true'} + assert list(babel_extract(source, + ('gettext', 'ngettext', '_'), [], opts)) == [ + (2, 'gettext', u' Hello \n World', []), + (4, 'gettext', u'Hello World', []), + (6, 'ngettext', (u'%(users)s user', u'%(users)s users', None), []) + ] + def test_comment_extract(self): from jinja2.ext import babel_extract source = BytesIO('''