If you haven’t read through Part 1 and Part 2 of the series to become a Multilingual Super Hero in Django, I highly encourage you to do that before reading this part of the series. This is the final part of the series where we’ll be focusing on the following:

  • Passing context from the request-response cycle to async tasks, so that they can retain the language context depending on user request.

  • Switching languages for certain parts of the template.

  • Supporting multiple languages in Django Templates.

Let’s look into each of the topics:

Passing Context To Async Tasks

We’ve already seen how Django’s request-response cycle works. We can hook using LocaleMiddleware to extract language information from the request and set up the appropriate language in Content-Language header for the client to know the language the response was sent in. But we never thought about async tasks. In order to understand this in a better way, let’s take an example.

We want to send an email whenever a company is registered on our site, but we also want to ensure to use the correct language in the template that was being used while executing the registration logic. So, if someone accessed the registration page in the Chinese language, we want to ensure that the email that they receive is also in the Chinese language.

Let’s have a quick look at this code snippet:

@app.task()
def send_company_registration_email_task(company_id, lang_code='zh-cn'):
    send_company_registration_email(company_id, lang_code=lang_code)

Here we have a celery task named send_company_registration_email_task accepting an argument for the language code to be used. In this case, since our primary users are Chinese, we default the language code to zh-cn which stands for Simplified Chinese.

This celery task calls the service to send the company registration email and passes the language code to the service function. Next, we’ll have a look at our service function:

from django.urls.base import translate_url
from django.utils.translation import activate
from my_project.services import send_mail


def send_company_registration_email(
        company_id, template_name='email/company_register_mail.tpl',
        lang_code='zh-cn'):
    if not settings.COMPANY_REGISTRATION_EMAIL_RECIPIENT:
        logger.info('Setting \'COMPANY_REGISTRATION_EMAIL_RECIPIENT\' not configured')
        return
    activate(lang_code)
    ctx = {
        'company_admin_url': translate_url(
            reverse('company-onboarding-decision', args=(company_id, )), lang_code),
    }

    # This is custom method and is different from Django's default send_mail
    return send_mail(from_email=settings.DEFAULT_FROM_EMAIL,
                     recipient_list=[email[1] for email in settings.COMPANY_REGISTRATION_EMAIL_RECIPIENT],
                     template_name=template_name,
context=ctx)

The first thing that we do in our service function is activate the particular language in the current celery task context. We do this in Django with the function which can be found in django.utils.translation

Next, we set up the context for our template rendering process. The send_mail service function will take care of rendering and the trans tags inside the template will make sure that the static text will be translated to the language set in the context. Optionally here, we need to also send some links in the email body. Particularly we want to give to the company admin the ability to explore the company onboarding decision page which shows them the accepted or refused status on the page.

The trick here is that if the user is getting an email in Chinese and they open another page on the website through that link, it should be in the same language as in the email.

If the content is targeted at a Chinese user, we cannot show them a page in English or German. Our first preference will always be Chinese.

In Django, we pass the lang_code to the translate_url function available in django.urls.base so that the actual link that will be rendered in the template would contain the language information as shown above.

Notice here that the lang_code is passed as an argument to our send_company_registration_email service function. By default, all the links in this application will be in Chinese as we have a default value of zh-cn for the lang_code parameter. After all the things are in place regarding the setting of the language context, we just trigger the utility function send_email which would trigger actual emails through a celery task.

Switching Language in templates for certain parts of the template

Sometimes there is a need to override certain parts of a template to display the text in that language. For example, when the user is visiting your website for the first time, you’d like to display a greeting to them. If this website is for Chinese users, the default language is maybe Chinese — but we’d also like to display the message in English.

The first and foremost thing to do here is to load the internationalization tag at the top of the template.

{% load i18n %}

{# Current language: {{ LANGUAGE_CODE }} #}
{% get_current_language as LANGUAGE_CODE %}

<p>{% trans "Welcome to our page" %}</p>

{% language 'en' %}
    {# Current language: {{ LANGUAGE_CODE }} #}
    {% get_current_language as LANGUAGE_CODE %}
    <p>{% trans "Welcome to our page" %}</p>
{% endlanguage %}

Next, we use the get_current_language function to pick up the language set in the current thread context during the rendering of the template. We then present the sentence “Welcome to our page” in Chinese (in case the template is rendered with the language set as Chinese) and the second block of code ensures that we override the language to ’ en’ and then display the same message. We use the trans tag for the static string translations as we’ve seen in the previous blog posts.

Supporting multiple languages in Django Templates

You might have already come across sites that showcase a menu icon giving the ability to change the language of the website. This menu item is supposed to display all the languages that the current website supports. Here we’ll see how we can accomplish this task in Django templates.

{% load i18n %}

{% get_available_languages as languages %}

{% trans "View this category in:" %}
{% for lang_code, lang_name in languages %}
    {% language lang_code %}
    <a href="{% url 'category' slug=category.slug %}">{{ lang_name }}</a>
    {% endlanguage %}
{% endfor %}

Consider this code snippet where we use the get_available_languages function to get all the available languages from the LANGUAGES setting that we had a look in Part 1 of the series.

languages is now a dictionary and we iterate over it to get the language code (lang_code) and language name (lang_name).

For each language, we then override the context of the page as we did previously and mention the name of the language in the hyperlinks that will be rendered.

Now that we’ve expanded our arsenal for supporting multilingual applications through Django, we’ve become a Multilingual SuperHero in Django.

Although there is more to explore about multilingual applications, we’ve tried to cover almost everything that a developer will use most of the time. This shouldn’t restrict you to just this tutorial and I highly encourage you to explore the Django documentation as it provides numerous utility methods that can help in accomplishing almost any task in your Django application. The trick is to keep expanding your arsenal and always be a Hero. 😉

This brings us to the end of the series of Becoming a Multilingual SuperHero in Django. If you’ve any questions/thoughts/suggestions, please let me know.

Originally published at https://fueled.com on June 13, 2019.

Related articles: