djangorequestdjango-admindjango-messagesdjango-translated-fields

ugettext_lazy - app1 passing django messages to app2. Messages do not get translated in app2, strings get translated in app1


I am managing two django apps built by third parts.

One of these app manages a UI (app-ui), while the other one is a backend app (app-api).

Now, app-api uses django messages.
The default language is english, and the author made transaltions in italian.
The translations are collected in the file app-api/apps/sub_app1/locale/it/LC_MESSAGES/django.po.

The django function used to mark the translated messages is ugettext_lazy

from django.utils.translation import ugettext_lazy as _

Furthermore, app-api has other apps. One of these is sub_app. This one has models, defined in sub_app1/models.py together with some data validations.

Among those ones there is this one:

if client.status != ACTIVE:
            raise ValidationError(_(f'customer {customer.name} not active'))

When the user navigates app-ui, it fires requests towards app-api, and if the above condition is met, the ValidationError is raised, and the error message 'customer {customer.name} not active' is passed to app-ui, which shows it at the top of the window.

In order to make the messages translated, I have

etc etc...

#. Translators: This message appears on the home page only
# apps/sub_app1/model.py:123
msgid "customer {customer.name} not active"
msgstr "cliente {customer.name} non abilitato"

etc etc...

and I obtained every marked string appearing successfully translated in the web pages of app-api.

However, the strings that are printed as django messages in app-ui do not get translated, they are still in english, even if they are marked the same way as the other strings.

What is the problem, and how can I fix it?

What I have tryed

In order to simulate the behaviour of app-ui, I have tried to send requests to app-api via Postman.
I have indicated the preferred language in which I would like to get the messages via the requests header key 'Accept-Language'.

So, in the header I have added the key-value pair

'Accept-Language': 'it-IT'  # also tryed with 'Accept-Language': 'it'

but the response still returns the message in english

{
    "detail": "customer John Doe not active",
    "status_code": 400
}

I am trying to understand how/(from where) does django get the indication of the language desired by the client.


Solution

  • Explanation

    Formatting strings by using Formatted String Literals spoils the django built-in gear for translation, because the strings are formatted before gettext_lazy looks them up as msgids.

    So the string gets its placeholders substituted with variable values, then it is passed to gettext_lazy, which will look up in django.po msgids for the string containing the variables values, finding no match.

    Examples

    Example:

    f'customer {customer.name} not active'
    

    is formatted as

    "customer John Doe not active"
    

    and then looked up by gettext_lazy in django.po, which expects to find it among the msgids.

    In order to avoid this, we want the strings to be substituted with variables values after gettext_lazy have looked them up, and substitued them with their translation.

    We can achieve this by formatting the strings by using the string format method

    Example:

    "customer {} not active".format(customer.name)
    

    is first looked up by gettext_lazy as

    "customer {} not active"
    

    found in django.po among the msgids, and so translated into

    "cliente {} non abilitato"
    

    and only afterwards, it is formatted into

    "cliente John Doe non abilitato"
    

    Fixing the case study

    So I have fixed the problem by

    #1 adding in the header of every request of app-ui towards app-ui, the key-value pair

    'Accept-Language': 'it-IT'  # also tryed with 'Accept-Language': 'it'
    

    #2 substituting

    if client.status != ACTIVE:
                raise ValidationError(_(f'customer {customer.name} not active'))
    

    with

    if client.status != ACTIVE:
                raise ValidationError(_("customer {} not active").format(customer.name))
    

    NOTE

    it is not

    if client.status != ACTIVE:
                raise ValidationError(_("customer {} not active".format({customer.name})))
    

    (with .format after the closing quote),
    it is

    if client.status != ACTIVE:
                raise ValidationError(_("customer {} not active").format(customer.name))
    

    (with .format after the closing parenthesis of gettext_lazy)

    because we want to format the messages only after they are translated, and not before.