April 09, 2021coding

Django internationalization (i18n) tutorial

You are working on a new or existing Django project, and you want to make it multi-lingual? Probably you wonder what you need to adapt in your source code in order to achieve that. Also, there might be some features provided by Django that you are not aware of but might help you on that road. In this article, we will go through how to internationalize (i18n) your Django project and how to streamline localization content management.

We will show how to translate the Django app inside python code, templates, and Javascript code. If you don’t need translations in your Javascript code, you can skip those steps.

Create new Django project

If you don’t have an existing Django project, create a new one with the following CLI command:

django-admin startproject django_i18n_example

It generates a minimal set of project files in the given directory.

Now execute a default database migration with python manage.py migrate to make the server session work.

Set up i18n for Django

First, let’s create django_i18n_example/locale folder where we will store PO files.

Then, inside settings.py file we should define:

  • a middleware using MIDDLEWARE which will take care of locale resolution. We need to add django.middleware.locale.LocaleMiddleware after SessionMiddleware but before CommonMiddleware
  • a directory to our folder with templates within TEMPLATES
  • a list of LANGUAGES supported in the project
  • where our PO files will be located using LOCALE_PATHS. It should point to the previously created folder

The remaining relevant i18n settings should be configured by default.

Our settings.py file should now contain this:

from os.path import join

MIDDLEWARE = [
    ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    # Order is important here
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...
]

TEMPLATES = [
    {
        ...
        'DIRS': [
            join(BASE_DIR, 'django_i18n_example', 'templates'),
        ],
        ...
    }
]

LANGUAGES = (
    ('en-us', 'English (US)'),
    ('de', 'Deutsche'),
    ('ar', 'عربى'),
)

# Default locale
LANGUAGE_CODE = 'en-us'

LOCALE_PATHS = [
    join(BASE_DIR, 'django_i18n_example', 'locale'),
]

# Turned on by default
USE_I18N = True

# Turned on by default
USE_L10N = True

# Set by default
STATIC_URL = '/static/'

# Enable our static JS file serving
STATICFILES_DIRS = (
    join(BASE_DIR, 'django_i18n_example', "static"),
)

We also need to update urls.py file. We need to add a predefined URL for locale change, make URLs for our views localized, and also load translations into Javascript (if there is a need for that).

from django.urls import path, include
from django.conf.urls.i18n import i18n_patterns
from django.views.i18n import JavaScriptCatalog
from . import views

urlpatterns = [
    ...
    # Needed for locale change
    path('i18n/', include('django.conf.urls.i18n')),
]
urlpatterns += i18n_patterns(
    # Put translatable views here
    path('', views.index),
    # Needed for translations in Javascript
    path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
)

Now it remains to install GNU Gettext tool. Gettext is used by Django under the hood for all commands around the PO files that we will discuss below.

Translations in Python code

Django provides many Gettext methods:

  • gettext – translates the simplest strings, without placeholders
  • ngettext – translates plural variations for the strings
  • pgettext – for translations that require additional context, which is the first parameter
  • npgettext – for plural translations with the context
  • gettext_noop – this actually returns a string as-is, which you could use while structuring a new code section

We will go through the most common once, but the same principle applies to all of them.

Simple translation

Import gettext in your Python code and just call it for the translatable string:

from django.utils.translation import gettext

simpleStringTranslated = gettext('Simple string')

Plural

We should not manually implement plural conditions in Python code, since plurals have different rules among different languages.
Use plural values in the following way:

from django.utils.translation import ngettext

pluralStringTranslated = ngettext(
    'There is %(counter)s object',
    'There are %(counter)s objects',
    objectsCount
  ) % {
    'count': objectsCount
  }

We suggest using named variables whenever possible. It helps you to not depend on variables order since the order might be changed in some translations.

Lazy translation in Django

Sometimes we need to define the string but want to postpone its translation till we really need it. Primarily when defining a string at module load time that is actually not used at that place. In such cases, we should use “_lazy” suffix to gettext methods, for instance gettext_lazy. A common use case is when defining translations for models or model forms.

You can see the full source code for the Django view here.

Translations in Django templates

Django templates allow you the same functions as in Python code, just in different forms of tags.
It is required to put the line {% load i18n %} on top of the template in order to use such tags.

translate tag

For the simple translations in the templates, it is enough to wrap the string into translate tag:

{% load i18n %}

{% translate "Simple string" %}

blocktranslate tag

Tag blocktranslate allows us to apply a bit complex translations. Translations that contain plural variations or placeholders.

{% load i18n %}

{% blocktranslate count counter=objectsCount %}
    There is {{ counter }} object
{% plural %}
    There are {{ counter }} objects
{% endblocktranslate %}

{% blocktranslate with amount=price %}
    The price is ${{amount}}.
{% endblocktranslate %}

Django 3.1 still keeps blocktrans tag from older Django versions, but as an alias for blocktranslate.

You can see the full source code for the Django template here.

Translations in Javascript

Sometimes you need to manipulate a web page with custom Javascript code and to show translated messages to the user.

Django tries to mimics Gettext functions inside Javascript as well.
Your template needs to include Django’s Javascript catalog that contains translations, and of course, include our JS script that uses those translations:

<!-- Load translations into Javascript -->
<script src="{% url 'javascript-catalog' %}"></script>

<!-- Load our JS script that uses translations -->
{% load static %}
<script src="{% static 'django_i18n_example/index.js' %}"></script>

Now you can translate strings in Javascript code:

const simpleString = gettext('Simple string');

const pluralFormat = ngettext(
    'There is %(count)s object',
    'There are %(count)s objects',
    objectsCount
);
const pluralString = interpolate(pluralFormat, { count: objectsCount }, true);

You can see the full source code for the Javascript file here.

Locale selection

We already included backend logic from Django that changes the locale for the session.
So it is left to implement a simple logic inside template code that triggers the locale change:

{% get_current_language_bidi as LANGUAGE_BIDI %}
{% get_current_language as LANGUAGE_CODE %}
<form action="{% url 'set_language' %}" method="post">{% csrf_token %}
    <input name="next" type="hidden" value="/">
    <select name="language" onchange="this.form.submit()">
        {% get_available_languages as LANGUAGES %}
        {% get_language_info_list for LANGUAGES as languages %}
        {% for language in languages %}
            <option value="{{ language.code }}" {%iflanguage.code ==LANGUAGE_CODE%}selected{%endif%}>
                {{ language.name_local }} ({{ language.code }})
            </option>
        {% endfor %}
    </select>
</form>

Create translation files

Now that we added translatable strings to our code, we can use a Django command to extract those strings into PO files.
Following CLI command extracts strings for the English(US) language:

# Extracts strings from python code and templates
django-admin makemessages -l en-us

# Extracts strings from JS scripts
django-admin makemessages -d djangojs -l en-us

It creates a PO file locale/en-us/LC_MESSAGES/django.po with strings from Python code and templates and a PO file locale/en-us/LC_MESSAGES/djangojs.po with strings from JS scripts.

The final step is to translate them to all remaining languages we support in the project.

Manage Django translations

We have extracted translations into the PO file, and now need to localize them into all remaining locales.
Localization can be very cumbersome and error-prone work if we do it manually. We will show how to streamline the localization process using the Localizely platform:

  1. Upload PO file to Localizely
  2. Translate strings – by your team members, using Machine Translation, professional translators, or some other way
  3. Download PO files from Localizely

Upload and download you can do in a few ways: manually using Localizely web console, or automate the process via API or Github integration.

When we place translated PO files into Django project, it is left to compile them into MO files that Django loads:

django-admin compilemessages

Now we have a project that is localized into all needed languages.

We can run the Django project with the CLI command python manage.py runserver and access the app via http://localhost:8000 in a browser.

Conclusion

In this Django i18n Example, we configured a simple multilingual Django application – we translated strings inside Python code, inside templates, and also inside Javascript code.

The implementation of this Django i18n tutorial can be found in the GitHub project.

Enjoying the read?

Subscribe to the Localizely blog newsletter for quality product content in your inbox.

Related

Flutter localization: step-by-step
March 29, 2022
In “Coding
Unicode
December 02, 2021
In “Coding
Copyrights 2022 © Localizely