Jul 31

Handling i18n in Django projects

All of the websites that we develop rely on Django support for internalization (i18n). Also, these projects are made of external apps. In this series of articles I would explain problems that we meet managing translations both during development and deploying. We would try to give sensible solutions and workflow for them.

Given the following premises:

  • Some 3rd party apps do have translations for language that we need and some do not.
  • Translations for some apps maybe poor or just inadequate
  • We don't always have write access for some apps catalogs

Our goal is to offer website editor access for easily managing all important translations.

Example project setup

As a example project let say we want to build small website with FAQ section in Slovenian and Deutsch language. To manage questions and answers we want to use django-faq app. We would include Django Admin and Rosetta applications to allow administrator to easily manage content and translations on the website.

Our directory structure looks like this:

example.com/
  website/
    custom/
    settings.py
    urls.py
    requirements.txt

Note that we won't copy django-faq (or any other external apps) into our project directory, as it would made it harder to receive updates and bugfixes. Beside that we don't want it to clutter our source version control system with other projects. We would rather made it available on python-path by using pip, easy_install or symlinking it to site-packages directory.

Ideal workflow

Looking at the faq app we notice that it already have translations for Deutsch language but not for Slovenian language. Let's make a translation of django-faq application to Slovenian language, make a patch and submit it to the owner to include it.

We would also make translation catalogs for our website by running makemessages command.

$ ./manage.py makemessages -l sl -l de

This would create translation catalog in example.com/locale directory. You should create locale folder yourself, otherwise django-admin would raise an error.

Now, when website administrator visits Rosetta interface he can pick faq or website catalog and manage existing translations.

That is nice so far, but...

Non so ideal workflow

During development of this small website we noticed that template for submitting question is not exactly what we want. Text is not marked as translatable and besides that we want to add some explanation after the form. We copy this template into our project and edit it there. Location of new template should be example.com/website/templates/faq/submit_question.html and now, it looks like this:

{% extends "faq/base.html" %}
{% block title %}{{ block.super }}: {% trans "Add Question" %}{% endblock %}

{% block body %}
<h3>{% trans "Submit Question" %}</h3>
<p>{% trans "Use this form to submit a question (and optionally a corresponding answer) that you would like to see added to the FAQs on this site." %}</p>
<form action="." method="POST">
<table>
{{ form.as_table }}
</table>
<input type="submit" value="{% trans "Submit" %}"/>
</form>
<p>{% trans "Your e-mail would never be published. We would send you answer to this e-mail address. " %}</p>
{% endblock %}

Let's regenerate all catalogs:

$ ./manage.py makemessages -a

Wait, there is a problem - the website administrator would expect "Submit Question" and other terms from this template in faq catalog. Instead it would be in website catalog. Even worser case would be if the text in original template was marked for translation. It would produce this terms to appear in both catalogs and administrator would not know which one to update.

How can we solve this mess?

So far I found that the best solution for that problem is to have only one translation catalog for each language.

To produce one translation catalog for all apps in the website we need to have this apps available under the project directory so makemessage can detect them.

For the reasons stated before we don't want to copy 3rd party apps into our project directory, instead of that we would symlink it to extra-locales folder in our website directory.

$ mkdir website/extra-locales
$ ln -s PATH_WHERE_DJANGO_FAQ_IS_INSTALLED website/extra-locales/faq

Now we can use --symlinks or -s option to tell makemessages to follow symlinks to directories when examining source code and templates for translation strings (this option is new in Django 1.2).

./manage.py makemessages -s -a

This would made translations for both faq and website to appear in website catalog.

To exclude faq catalog in Rosetta interface configure ROSETTA_EXCLUDED_APPLICATIONS setting:

ROSETTA_EXCLUDED_APPLICATIONS = (
  'faq',
)

In next articles I plan to explore merging catalog files and handling catalogs in source code repository.

And let me know in the comments if that works for you or how your tame the i18n beast.