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.
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.
First, let’s create django_i18n_example/locale
folder where we will store PO files.
Then, inside settings.py
file we should define:
MIDDLEWARE
which will take care of locale resolution. We need to add django.middleware.locale.LocaleMiddleware
after SessionMiddleware
but before CommonMiddleware
TEMPLATES
LANGUAGES
supported in the projectLOCALE_PATHS
. It should point to the previously created folderThe 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.
Django provides many Gettext methods:
gettext
– translates the simplest strings, without placeholdersngettext
– translates plural variations for the stringspgettext
– for translations that require additional context, which is the first parameternpgettext
– for plural translations with the contextgettext_noop
– this actually returns a string as-is, which you could use while structuring a new code sectionWe will go through the most common once, but the same principle applies to all of them.
Import gettext
in your Python code and just call it for the translatable string:
from django.utils.translation import gettext
simpleStringTranslated = gettext('Simple string')
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.
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.
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.
For the simple translations in the templates, it is enough to wrap the string into translate
tag:
{% load i18n %}
{% translate "Simple string" %}
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.
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.
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>
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.
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:
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.
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.
Like this article? Share it!
Aleksa is a Software Engineer at Localizely. Over the past few years, Aleksa has been working in the field of software localization. In his free time, he enjoys playing guitar and writing tech posts.
Subscribe to the Localizely blog newsletter for quality product content in your inbox.
Step into the world of easy localization