Sorry, this blog is exclusively written in French!

Voyons comment traduire efficacement une application Django, en plusieurs étapes. Les contenus génériques des modèles et des vues, tout d'abord, en utilisant gettext. Puis les templates en eux-même. Et enfin, le contenu de la base de données que nous allons rendre adaptables aux différentes traductions grâce à django-multilingual-ng. Nous verrons également comment Django permet d'adapter la langue affichée en fonction des options de l'utilisateur. 

Configurer i18n

Le module générique de Django permettant la traduction s'appelle i18n, pour "Internationalisation". Dans votre fichier settings.py, ajouter / modifiez les lignes suivantes correspondant à vos différentes langues : 

from django.utils.translation import gettext_lazy as _
TIME_ZONE = 'Europe/Paris'
LANGUAGE_CODE = 'fr'
SITE_ID = 1USE_I18N = True
USE_L10N = True
LANGUAGES = (
    ('fr', _('Français')),
    ('en', _('Anglais')),
)
DEFAULT_LANGUAGE = 1

Puis, au niveau des middlewares et des context processors :

MIDDLEWARE_CLASSES = ( 
    [...] 
    'django.middleware.locale.LocaleMiddleware', 
)
TEMPLATE_CONTEXT_PROCESSORS = (
    [...]
    "django.core.context_processors.i18n",
)

Utiliser gettext au sein des fichiers .py

Premiers termes que nous allons traduire : les contenus de vos fichiers .py (modèles essentiellement). C'est déjà ce que nous avons fait dans l'étape précédente pour les langues disponibles : dès qu'un mot sera écrit dans un fichier .py (description d'un champ d'un modèle par exemple), vous allez utiliser gettext pour permettre ça traduction (en mode paresseux). Importez donc gettext dans tous vos fichiers .py et attribuez lui l'alias _ pour l'implémenter plus facilement : 

from django.utils.translation import gettext_lazy as _

Puis, pour chaque terme (ici la classe meta d'un model par exemple) : 

class Meta:
    verbose_name = _('Image')
    verbose_name_plural = _('Images')

Traduire les templates

Pour ce qui est des templates, où vous devriez avoir plus de termes à traduire, le fonctionnement est le même. Commencez par permettre la traduction au sein de chaque template grâce au template_tag :

{% load i18n %}

Et, pour chaque phrase à traduire : 

{% trans "Bienvenue sur mon site" %}

S'il s'agit d'un bloc de texte plus conséquent, vous pouvez utiliser : 

{% blocktrans %}
[...]
{% endblocktrans %}

Si vos blocs contiennent des variables, je vous invite à consulter la documentation bien fournie !

Générer les traductions

Maintenant que vous avez permis l'internationalisation de tous vos termes, vous allez pouvoir en saisir la traduction. Si vous êtes déjà familier avec le système de wordpress, sachez qu'il s'agit du même principe (qui ressemblait déjà jusque là). Des fichiers de traductions (.po) vont être générés, contenant chaque phrase que vous aurez à traduire dans toutes les langues sélectionnées. Une fois la traduction effectuée, Django compilera ces données dans un fichier .mo. 

Commencez par créer un nouveau répertoire à la racine de votre projet : locale/. C'est lui qui contiendra les différents fichiers de traductions. 

Vous devez ensuite installer gettext, si vous ne l'avez pas encore. Voici la marche à suivre sous Windows et MacOS. Pour générer les fichiers de traductions, tapez la commande suivante (à la racine de votre application) : 

django-admin.py makemessages --all

Un nouveau répertorie sera créé dans votre dossier locale/ par langue configurée. Editez LC_MESSAGES/django.po et traduisez chacun des termes... Puis, une fois la traduction effectuée, lancez la commande :

django-admin.py compilemessages

Changer de langue

Pour permettre à l'utilisateur de changer de langue, éditez votre fichier urls.py et ajoutez la règle suivante :

urlpatterns = patterns(
    [...],
    (r'^i18n/', include('django.conf.urls.i18n'))

Puis, dans un de vos templates, créez le formulaire suivant : 

<form action="/i18n/setlang" method="post">
{% csrf_token %}
<select name="language">
{% for lang in LANGUAGES %}
    <option value="{{ lang.0 }}">{{ lang.1 }}</option>
{% endfor %}
</select> 
</form> 

L'URL /i18n/setlang/ (que vous venez de configurer) permet de changer la langue courrante du site, qu'on lui passe en paramètre POST. La page est donc ré-actualisée... avec les templates traduits ! 

Notez que grâce au middleware Locale que vous avez inclus dans vos paramètres, Django détectera automatiquement les options du navigateur de l'utilisateur... et utilisera la bonne langue en conséquence !  

Traduire le contenu de votre BDD : django-multilingual-ng

Seulement, vous l'aurez remarqué, seuls les contenus "statiques" sont modifiés. Comment faire pour traduire dynamiquement le contenu de votre base de données ? Le nom d'un post et son contenu, par exemple ? C'est là qu'entre en jeu django-multilingual-ng. Il vous permettra d'ajouter dynamiquement une table de traduction pour chacun des objets à internationaliser. Attention, les opérations suivantes vous feront perdre vos données déjà présentes dans la BDD. Pensez à les sauvegarder !

Installer django-multilingual-ng

Téléchargez le package et installez-le comme un plug-in classique. Editez ensuite settings.py :

TEMPLATE_CONTEXT_PROCESSORS = (
    [...], 
    'multilingual.context_processors.multilingual',
)
INSTALLED_APPS = ( 
    [...], 
    'multilingual', 
)

Modifier les modèles et l'intertface d'administration

Editez maintenant vos models.py. Vous allez devoir choisir les champs qui auront besoin d'une traduction et les placer dans une sous-classe, de la façon suivante : 

from multilingual.translation import TranslationModel

class MyModel(models.Model):
    # Les champs qui ne seront pas traduits
    creation_date = models.DateTimeField()
    link = models.URLField(blank=True, null=True)

    # Les champs à traduire
    class Translation(TranslationModel):
        title = models.CharField(max_length=200)
        slug = models.CharField(max_length=200)
        description = models.TextField()
        overview = models.TextField()

Le champs compris dans cette classe Translation existeront donc pour chacune des langues configurées. Ici : title, slug, description et overview.

Modifiez également admin.py pour vous permettre de modifier ces champs dans l'interface d'administration. Vos classes d'admin devront juste hériter de "MultilingualModelAdmin" au lieu du traditionnel "ModelAdmin" :

from multilingual.admin import MultilingualModelAdmin

class MyModelAdmin(MultilingualModelAdmin):
    [...]
    use_prepopulated_fields = {'slug': ('title',)}

Attention, certaines méthodes ont été renomées dans le MultilingualModelAdmin. Par exemple, "prepolulated_fields" devient "use_prepopulated_fields". Notez également que vos classes "inlines" devront elles hériter de "MultilingualInlineAdmin", comme l'exemple suivant (couple classes Crea / Task) : 

from multilingual.admin import MultilingualModelAdmin, MultilingualInlineAdmin

class TaskInline(MultilingualInlineAdmin):
    model = Task
    use_prepopulated_fields = {'slug': ('name',)}

class CreaAdmin(MultilingualModelAdmin):
    list_display = ('title', 'slug', 'creation_date', 'link')
    search_field = ('title', 'body')
    date_hierarchy = 'creation_date'
    use_prepopulated_fields = {'slug': ('title',)}
    inlines = [ TaskInline]

Vous devez lancer un syncdb pour re-synchroniser votre base. Vous allez donc perdre toutes vos anciennes données. N'hésitez pas à les exporter pour pouvoir ensuite les ré-importer. Mais je vous conseille surtout de faire cette opération d'internationalisation avant le déploiement de votre application...

$ manage.py syncdb

Si vous explorez votre nouvelle base (via phpMyAdmin par exemple), vous comprendrez le fonctionnement de django-multilingual-ng avec la création de tables de traductions supplémentaires...

Rendez-vous maintenant dans l'interface d'administration. Vous pourrez constater que vos modèles possédant des champs traduisibles sont désormais modifiables grâce à un onglet pour chaque langue !

Accéder aux données traduites

Dans votre application, suivant la langue courante, les champs seront automatiquement traduits. Mais vous pouvez tout de même accéder aux traductions des autres langues. Par exemple, au sein d'un template, si la langue courante est le français :

{{ obj.title }} <!-- Le titre en français -->
{{ obj.title_fr }} <!-- Idem -->
{{ obj.title_en }} <!-- Le titre en anglais --> 

De même, dans vos Querysets (toujours avec le français en langue courante) :

# Les créations ordonnées par le titre français
objs = Crea.objects.order_by('title')

# Les créations ordonnées par le titre anglais
objs = Crea.objects.order_by('title_en')

Voilà pour l'utilisation de django-multilingual-ng. Vous pouvez également lire la documentation relativement peu fournie, ou simplement fouiller dans le code (ce qui est finalement le plus efficace)...

Optimisation

Une dernière chose à propose de l'optimisation. Si vous posséder la django-debug-toolbar (par exemple), vous allez constater que les requêtes SQL générés par django-multilingual-ng sont assez... généreuses. Effectivement, elles sont assez fournies en jointures (logique, avec les données réparties entre deux tables pour la traduction). N'hésitez pas, donc, à utiliser les moteurs de cache de Django pour décharger un peu votre base de données. Je reviendrai également dans un futur article sur une méthode de cache de fichiers HTML statiques (par langue) qui permettra un gain notable de performances.