{% 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:
: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, \
plural_expr = None
plural_expr_assignment = None
variables = {}
+ trimmed = None
while parser.stream.current.type != 'block_end':
if variables:
parser.stream.expect('comma')
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')
parser.stream.expect('block_end')
- plural = plural_names = None
+ plural = None
have_plural = False
referenced = set()
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)
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 = []
auto_reload=False
)
+ if getbool(options, 'trimmed'):
+ environment.policies['ext.i18n.trimmed'] = True
if getbool(options, 'newstyle_gettext'):
environment.newstyle_gettext = True
'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),
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('''
(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('''