mardi 18 septembre 2012

Localisation en Python avec gettext pour les nuls

Pas plus tard qu'hier, je me suis intéressé à la localisation du code Python en utilisant gettext.

Force m'est de constater que pour le développeur moyen que je suis la doc officielle Python n'est pas très explicite. En tout cas, en lisant le début de la doc, j'étais un peu perdu.

Alors pour les noobs comme moi, voici le tutorial définitif pour faire de la localisation en Python. Let's go.

gettext c'est quoi

Je vous laisse chercher sur Google. A priori c'est un truc libre (GNU) pour faire de la localisation. Le principe est d'utiliser les commandes xgettext (comprenez récupère mon texte) et msgfmt (comprenez formate mes messages). Je passe sur l'absence de cohérence dans les noms des utilitaires...

Le principe

C'est simple, vous vous démerdez pour identifier des chaînes à localiser dans votre code source. Vous mettez votre code en entrée de xgettext pour générer un fichier .po. Vous éditez le .po pour traduire vos messages (c'est là que vous vous dites ah mais c'est vrai, la localisation, c'est un travail chiant !). In fine, vous transformez votre .po en .mo grâce à msgfmt qui décidément porte mal son nom.

Exemple

La localisation se fait en Python grâce au module gettext.

Contrairement à ce que je pensais au départ, cela ne signifie pas que gettext devient une dépendance de chaque module contenant du texte à traduire : il suffit de l'initialiser une fois dans votre module principal pour disposer des fonctions dans le namespace de toute votre application.

Ici, je vais vous proposer de mettre les chaînes à traduire dans un module dédié. Je ne sais pas s'il y a des bonnes pratiques dans le domaine... Le fait est que je ne veux pas être embéter par des dépendances dans des tests unitaires.

Pour la suite, j'imagine que vous êtes sur un Linux avec un python 2.6+ et les outils xgettext et msgfmt.

Commencez par créer le module messages.py comme suit :

import gettext
gettext.install('messages', './locale')

JAVA = _("It's the blue Java, it's the java that we dance!")

Explications :

Comme dit plus haut, vous importez le module gettext qui vous apporte les fonctionnalités de localisation. Vous appelez ensuite la fonction gettext.install qui prend en paramètre ici un nom de domaine de traduction (messages) et un répertoire dans lequel gettext ira chercher vos fichiers de traduction (les .mo). Une fois fait, vous avez automagiquement accès à la fonction _().

Cette fonction doit être exécutée avec vos chaines de caractères en argument. Elle a 2 utilités :

  • Elle fait effectivement la traduction de la chaîne en utilisant le fichier de traduction trouvé correspondant à la langue du système.
  • Elle permet à xgettext d'identifier les chaînes à traduire et de générer le fichier .po.

Passons à la suite.

Créez un module translator.py et mettez dedans :

import messages

if __name__ == '__main__':
    print("Test...")
    print(messages.JAVA)

Le module utilise un message que nous voulons internationaliser. J'en profite pour ajouter un argument supplémentaire sur le fait d'utiliser un module séparé pour les messages à traduire : je ne m'imagine pas me trimballer la fonction _() dans du code réalisant mes fonctionnalités.

Ceci étant dit, nous pouvons à ce moment de l'histoire tenter un lancement :

$ python translator.py
Test...
It's the blue Java, it's the java that we dance!

Extasions nous sur le fait qu'en l'absence de toute traduction, _() nous renvoie la chaîne non traduite.

Créons maintenant notre fichier de traduction :

$ mkdir -p locale/fr_FR/LC_MESSAGES
$ xgettext -o locale/fr_FR/LC_MESSAGES/messages.po messages.py

Ici locale est le répertoire dans lequel vous avez déclaré que vous alliez chercher vos traductions. fr_FR/LC_MESSAGES est un répertoire dans lequel gettext ira automatiquement chercher les traductions dans le paramétrage de langue fr_FR.

Notez que le .po n'a a priori pas besoin d'être ici car ce n'est pas lui qui sera utilisé. Il n'a même pas besoin d'être nommé comme ça mais on va conserver la convention car sinon c'est l'embrouille. Concernant le nom du fichier, messages est le nom passé à la fonction gettext.install (bon OK, en fait, j'ai tout embrouillé tout seul...).

Bref. Ouvrez le fichier avec votre éditeur préférez et modifiez le rapidement pour arriver à ça :

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-08-28 22:34+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: messages.py:7
msgid "It's the blue Java, it's the java that we dance!"
msgstr "C'est la Java bleue, c'est la Java que l'on danse!"

Donnez une valeur de charset (j'ai mis UTF-8 qui est par défaut sous Linux, vous mettrez un iso-8859-1 sous Windows). Pour chaque msgid, écrivez une traduction dans msgstr.

Dernière étape, produire le .mo qui sera utilisable.

$ cd locale/fr_FR/LC_MESSAGES/
$ msgfmt -o messages.mo messages.po

Vérifiez que vous êtes en français et lancez à présent votre script :

$ echo $LANG
fr_FR.UTF-8

$ python translator.py 
Test...
C'est la Java bleue, c'est la Java que l'on danse!

C'est si beau et simple que j'ai eu envie de pleurer la première fois (T-T).

Résumé et éléments pour aller plus loin

Contrairement à ce que je pensais au départ, la localisation du code Python en utilisant gettext n'est pas si terrible... une fois qu'on sait comment faire. Alors ne vous en privez pas : pensez à la traduction de votre application le plus tôt possible afin d'espérer la rendre accessible au plus grand nombre.

Pour aller plus loin, regardez le contenu du module gettext qui propose une API qui peut être intéressante.

En lisant la doc jusqu'au bout, je m'aperçois qu'il y avait des exemples pas trop mal foutus... Allez donc voir !