In the previous post, we did a basic setup of our Django based project to help us on our path for becoming a multilingual Super Hero by setting up the internationalization and localization machinery. If you haven’t read it yet, I strongly encourage you to read that first.

In this post we’ll see how we can support multiple languages for both static and dynamic data.

Static String Translations

We use static strings in our model names, field names, error messages etc. We use them in many places including django templates, all our code files, including models.py Let’s see how we support translation in all those places.

Templates

Anything that should be translated in templates should be marked under trans or blocktrans.

To make the translation work, there is one more thing to take care of, and that is loading the internationalization i18ntag at top of every file that uses trans or blocktrans.

Let’s see it in some action in a dummy sign-up page made using templates.

<h1>Sign Up</h1>

<form>
    <label>Username</label>
    <input id='username' type='text'/>

<label>Password</label>
    <input id='password' type='password'/>
</form>

To enable translation and serve the sign up page with static content (the field name, the text in the heading), we’ll first load the translation tag and mark the content for translation like:

{% load i18n %}

<h1>{% trans 'Sign Up' %}</h1>

<form>
    <label>{% trans 'Username' %}</label>
    <input id='username' type='text'/>

<label>{% trans 'Password' %}</label>
    <input id='password' type='password'/>
</form>

Gotcha #2: You need to load i18n in every file, even if it extends another file that already has it loaded.

All *.py files

In all files where you need the text translated, use ugettext or ugettext_lazy like:

>>> from django.utils.translation import ugettext
>>> from django.utils.translation import activate, get_language

>>> activate("en")
>>> get_language()
'en'
>>> ugettext("Sign Up")
'Sign Up'

>>> activate("zh-cn")
>>> get_language()
'zh-cn'

>>> ugettext("Sign Up")
'注册'

Both ugettext and ugettext_lazy are Python objects that are evaluated to string at different time. The string representation depends on whatever language is activated.

Oh, wait! How do I decide which one to use?

Great Question!

  • Use ugettext to immediately get the translated version of the text. For example, in your views.py file.

  • Use ugettext_lazy to get a lazy object referencing the static string, for example, in your models.py file.

Gotcha #3: Try to use ugettext_lazy over ugettext as much as you can, in all cases other than the case mentioned above, which requires returning response in the API call, where an immediate translation is needed.

Generating translation files for static strings

Now that we’ve all the static data marked with trans tag, ugettext and ugettext_lazy methods, it’s time to generate the translation files and fill in the translations. For generating the django.po file for simplified Chinese, we’ll run the following inbuilt management command makemessages:

$ python manage.py makemessages -l 'zh_CN'

Gotcha #4: makemessages command accepts a flag i which excludes certain locations, like for the virtualenv, otherwise, django.po files are generated for each package in your virtualenv.

Gotcha #5: Notice that when you mention a language in settings, it is all in small letters like zh-cn but when you do it with makemessages command, the language part is in small letters and country part is in capital letters separated by an underscore rather than a hyphen like zh_CN. If you make a mistake here, you won’t see any error and your translation won’t work either, which can leave you baffled and probably pulling out your hair.

Now, if you observe in your Django project, you’ll have few directories & files generated automatically, like this:

myproject/
    myproject/
    templates/
        signup.html
    locale/
        zh_CN/
            LOCALE_MESSAGES/
                django.po

Now, let’s take a look at the django.po file that was created by makemessages command:

#: signup.html:3
msgid "Sign Up"
msgstr ""

#: signup.html:6
msgid "Username"
msgstr ""

#: signup.html:9
msgid "Password"
msgstr ""

Here msgid is the text marked for translation and msgstr in it’s translated form. You’ve to do this for all languages you want to support in their separate django.po files. Let’s fill this in with Simplified Chinese as:

#: signup.html:3
msgid "Sign Up"
msgstr "注册"

#: signup.html:6
msgid "Username"
msgstr "用户名"

#: signup.html:9
msgid "Password"
msgstr "密码"

Now, we’re ready to compile all these translations to make them available everywhere a string is marked to be translated.

Compiling the static translation messages

Once you’ve generated the django.po files, you can fill in the translations and then compile these *.po files to *.mo files to be used by Django.

$ python manage.py compilemessages

Gotcha #6: Your best bet, is to move your virtualenv out of your project. Because of compilemessages finding all the django.po files from the root of your Django project, it may start converting django.po files from external dependencies, which can cause a lot of pain in getting up and running with actual translation of your project.

Gotcha #7: After compiling the messages, you must restart your wsgi server, or else the translation won’t work ;)

Dynamic String Translations

Most of the data in our Django application is dynamic and user generated. We can employ two approaches in order to support translations:

  1. Enable the end-user to enter information in multiple languages.

  2. Translate the dynamic text with the use of third-party services like Transifex.

Let’s see the first approach in action.

Model Fields

The tricky part begins here. Now you want to support multilingual data in your database. For now, let’s assume that the user can input in just two languages. Say Chinese and English for simplicity.

We can use the django-modeltranslation package here which will create different columns for attributes that are marked for translation. The code is very similar to what you write for django-admin for any data model in your app.

So, let’s assume that we have a model named User in our dummy app which has the first_name and last_name marked for translation in the translation.py file

from modeltranslation.translator import register, TranslationOptions
from .models import User

@register
class UserTranslationOptions(TranslationOptions):
    fields = ('first_name', 'last_name', )

As soon as you do this in your translation file and run migrations, it will auto-magically create fields in your model. Here’s how the SQL dump would like:

$ ./manage.py sql users
BEGIN;
CREATE TABLE `users_user` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `first_name` varchar(255) NOT NULL,
    `first_name_zh_cn` varchar(255) NULL,
    `last_name` varchar(255) NULL,
    `last_name_zh_cn` varchar(255) NULL,
)
;
COMMIT;

Here, first_name is the default field that was defined on model, which tends to store and retrieve the value of first_name for the default language set in your Django app (English in this case). For each subsequent language, that your Django project supports, a new field with same name appears, suffixed with language code is created, like first_name_zh_cn for keeping Simplified Chinese version of the first_name field.

What if you don’t want to burden user with adding information in multiple languages?

You can use a third-party service such as transifex, to convert all the incoming text in different languages.

BONUS

Since you’re still reading this, here is a bonus on debugging translation issues for you.

Gotcha #8 With Django shell, you can quickly activate a particular language and check if the static data from a django.po file is being translated like:

from django.utils import translation

# Activate Simplified Chinese
translation.activate('zh-cn')

# Try to get the translation for the string `Sign Up`
translation.ugettext('Sign Up')

# This will return either the Chinese version (if available in the django.po file) or the English version as a fallback language.

Gotcha #9: Oh, but looks like the translation is working just for some string and not all of them. What should I do?

Check in your django.po file, and if the translation is marked as fuzzy, just remove that line, compile the messages again and watch the magic happen right before your eyes 😄.

Conclusion

Django’s translation support is indeed very powerful, but the initial setup becomes a bit of pain due to simple gotcha moments that can cause a lot of headaches (and sometimes pulling out your hair 😛)

The more early you support your project in different languages and write code correctly, the easier it will get in future to support multiple languages.

Oh, do you know something?

You just became a Multilingual Super Hero with your app supporting multiple languages Congrats!

Don’t forget to visit part 3 of the series where I’ll discuss about how you can include locale context in async tasks and much more.

Related articles: