Translating models, views and templates into multiple languages

Django offers excellent support for multilingual sites. In this tutorial we will show how to translate templates into multiple languages. We will focus on translating text that is somewhere in your model, your forms, your views or your templates. Multilingual sites obviously also have page content (articles etc.) in several languages; this is the subject of another tutorial.

July 8, 2020, 8:05 a.m.
Themes: Multiple languages

The first thing to do is tell Django via the settings that we will be using multiple languages. Let's say you have English as your primary language and you also want French and Dutch; add the following to your settings (or check that it's there):

LANGUAGE_CODE = 'en'

USE_I18N = True

LANGUAGES = [
    ('en', 'English'),
    ('fr', 'Français'),
    ('nl', 'Nederlands'),
]

You also need to add LocaleMiddleware. Add it to your MIDDLEWARE parameter, after SessionMiddleware and CacheMiddleware (if present) and before CommonMiddleware:

'django.middleware.locale.LocaleMiddleware',

Now we use the i18n system for creating different urls for different languages, by adding the i18n_patterns function in your project urls.py file before all included urls that we want to have in multiple languages. For example, in previous tutorials we have included urls for an authentication app; if we want these available in multiple languages, then we put the following in our urls.py:

urlpatterns += i18n_patterns(
    path('accounts/', include('allauth.urls')),
    path('accounts/', include('userauth.urls')),
)

Note: if you also use Wagtail, do not put the Wagtail urls inside the i18n_patterns function; we will manage translated Wagtail pages in another manner. Check out the urls.py file on Github.

You can check that the prefixes are working by going to a url in your project with the given prefix. Let's say we have a url /accounts/login/, then go to /fr/accounts/login/, and this should render the template. Of course the template is still in English; Django will not do that for us, so let's start doing this ourselves. Create a directory named locale in your project directory and tell Django about this by adding the following to your settings:

LOCALE_PATHS = (os.path.join(BASE_DIR, 'locale'),)

Now we are ready to use Django's makemessages command. This command uses the gettext toolset, so you have to have this installed on your computer. Then run:

django-admin makemessages -l fr

or whichever language you would like to translate. All strings that are marked for translation in your models, forms, views, templates etc. are then found and listed in a text file with the .po extension in the locale directory. The .po file will also generate strings for applications that you have installed, in our case allauth. You can avoid that by using the -i ignore flag to exclude files from the env directory:

django-admin makemessages -l fr -i env

Marking strings for translation is done in two ways. In your .py files (models, forms, views etc.) use gettext or gettext_lazy. In your templates use {% trans %} or {% blocktrans %}. In our previous tutorials we have done both consistently for all 'human readable texts'.

You have to manually translate all strings in the .po file. As an example, say that on line 11 in our models.py file of our userauth app we have the string "Date of Birth", then an entry in the .po file would be:

#: userauth/models.py:11
msgid "Date of Birth"
msgstr ""

Adding the French translation would change this to:

#: userauth/models.py:11
msgid "Date of Birth"
msgstr "Date de naissance"

If a string is too long, Django will split it in the following way:

#: userauth/templates/userauth/account/email_confirm.html:17
msgid ""
"Please confirm that <a href=\"mailto:%(email)s\">%(email)s</a> is an e-mail "
"address for user %(user_display)s."
msgstr ""

Our msgstr translation would then read:

msgstr ""
"Veuillez confirmer que <a href=\"mailto:%(email)s\">%(email)s</a> est une adresse "
"e-mail pour l'utilisateur %(user_display)s."

As in this example, copy everything that is not text literally, such as html tags and variable names. When Django is unsure, it will add a line with the word fuzzy, so that you can judge yourself how to deal with that. When all translations are done, we're ready to compile the .po files:

django-admin compilemessages --locale fr

Note: when you use a social account to log in, you have to add the callback urls with the language prefixes, otherwise the social account provider will not be able to find the page.

After logging in, we are redirected to the profile page /accounts/profile/, but this url has no prefix, so we will end up on this page in the default language, which is not what we want. In order to repair this, put the following in the settings file; this will resolve the urls via urls.py including language code:

from django.urls import reverse_lazy

LOGIN_URL = reverse_lazy('account_login')
LOGIN_REDIRECT_URL = reverse_lazy('account_profile')

Whenever new strings are added to your .py or .html files, or existing strings are changed, rerun the makemessages command, edit the .po files and rerun the compilemessages command.

As mentioned in the beginning, read on if you want to translate the content of your webpages as well.

Comment on this article (sign in first or confirm by name and email below)