filters inside django trans tag : first translates then applies filters; unlike other tags

Viewed 251

I encountered a weird behavior from django trans tag. As you know whenever we use a filter inside a tag, the filter first applies and then gives the result as input to the tag. But this is in reverse order for trans tag.

Example:

Suppose I have this django.po file:

msgid "msgid-world"
msgstr "world"

msgid "msgid-"
msgstr "message"

Now see the result of these tags:

{% trans "msgid-"|add:"world" %}
result: messageworld  (first translate then concat)

Expected result: "world" (first concat then translate the key)

UPDATE:

My problem is about consistency. The behavior with other tags (like "if") is converse!

Moreover if I write a custom tag for myself like below:

@register.simple_tag
def customtrans(a):
    return _(str(a))

then it will behave like other tags. The result of this example (like the above example):

{% customtrans "msgid-"|add:"world" %}

will be "world".

This is the mysterious point I want to understand!

2 Answers

According to the jinja2 documentation

Variables can be modified by filters. Filters are separated from the variable by a pipe symbol (|) and may have optional arguments in parentheses. Multiple filters can be chained. The output of one filter is applied to the next

So it does what described:

  1. evaluates trans with the argument "msgid-"
  2. passes the result to the next filter and adds "world"

I suggest you're using blocktrans for your puropse

{% load i18n %}
{% blocktrans %}
    msgid-world
{% endblocktrans %}

Or you could use a with statement:

{% with variable="msgid-"|add:"world"}
    {% trans variable %}
{% endwith%}

Also check https://docs.djangoproject.com/en/3.0/topics/i18n/translation/ for a ton of options how you could solve your problem

Yes you are right, I analysed the django implementation of simple_tag and trans:

The important parts of the analysis are:

  1. Using simple_tag and trans, both eventually will have the same FilterExpression https://github.com/django/django/blob/e3d546a1d986f83d8698c32e13afd048b65d06eb/django/template/library.py#L123

    (Pdb) l
    119                     args, kwargs = parse_bits(
    120                         parser, bits, params, varargs, varkw, defaults,
    121                         kwonly, kwonly_defaults, takes_context, function_name,
    122                     )
    123  ->                 return SimpleNode(func, takes_context, args, kwargs, target_var)
    (Pdb) print(args)
    [<django.template.base.FilterExpression object at 0x1061e5cf8>]
    (Pdb) print(args[0].__dict__)
    {'token': '"msgid-"|add:"world"', 'filters': [(<function add at 0x10c2369d8>, [(False, 'world')])], 'var': 'msgid-'}
    
    (Pdb) l
    368         message_context = None
    369         seen = set()
    370         invalid_context = {'as', 'noop'}
    371
    372  ->     while remaining:
    373             option = remaining.pop(0)
    374             if option in seen:
    376                 raise TemplateSyntaxError(
    377                     "The '%s' option was specified more than once." % 
    (Pdb) message_string.__dict__
    {'token': '"msgid-"|add:"world"', 'filters': [(<function add at 0x1112a79d8>, [(False, 'world')])], 'var': 'msgid-'}
    
  2. But now the implementation differs

    While simple_tag is using SimpleNode, which first resolves the arguments (https://github.com/django/django/blob/e3d546a1d986f83d8698c32e13afd048b65d06eb/django/template/library.py#L191) before it get passed to your function, trans uses it's own TranslationNode (https://github.com/django/django/blob/e3d546a1d986f83d8698c32e13afd048b65d06eb/django/templatetags/i18n.py#L68) implementation, which behaves differently.

Consistency First this was interesting to dig into, since I was not aware of the different behavior.

In the end it's a implementation detail, which is based upon the author's taste. simple_tag is implemented that way and it's also described in the code https://github.com/django/django/commit/655f52491505932ef04264de2bce21a03f3a7cd0#diff-258b66ed22ebadbcfec255f802c95bdfR173-R176 - Maybe the doc string could be a bit more explicit.

With trans and blocktrans you basically can go both ways, pre populated or not.

You can still use tag instead of simple_tag and implement it differently.

I haven't found anything about a guideline where to follow jinja2-filters logic or not. This was a false assumption from my side.

Related