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 yourviews.py
file. -
Use
ugettext_lazy
to get a lazy object referencing the static string, for example, in yourmodels.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:
-
Enable the end-user to enter information in multiple languages.
-
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.