Internationalisation d’un composant

Plone est multilingue. Cette partie explique comment rendre votre composant disponible en plusieurs langues.

Internationalisation et localisation

Nous distinguerons l’internationalisation (i18n), qui consiste à utiliser l’API de production de textes dans le code Python et les templates, de la localisation (l10n), qui consiste à fournir la traduction des labels produits lors de durant l’internationalisation.

A faire

Placer des liens sur les grandes lignes de gettext (fichiers .pot, .po, .mo)

Internationaliser un composant

Connaissances requises

Nous recommandons aux lecteurs de prendre préalablement connaissance des mécanismes standard d’internationalisation des applications, basé sur le standard “GNU-gettext” http://fr.wikipedia.org/wiki/Gettext

Les amateurs de détails pourront se reporter à la documentation complète en anglais ici : http://www.gnu.org/software/gettext/manual/gettext.html

Notion de domaine

Plone et plus généralement Zope (puisque nous parlons d’internationalisation Zope) sont des écosystèmes générant des centaines de composants d’extension sur l’utilité desquels nous ne reviendrons pas ici. Un intégrateur est susceptible de mixer nombre parmi ces composants.

Ceci risque donc d’amener nombre de conflits de labels, le même label pouvant être traduit différemment selon l’emploi qui en est fait.

Pour éviter ceci, le standard “gettext” définit la notion de “domaine” qui est un espace de nommage pour les traductions de labels. Ainsi le label “foo” peut être traduit différemment dans autant de domaines distincts.

Dès lors qu’un composant propose une interface utilisateurs comportant du texte, il doit déclarer son domaine et le répertoire racine des traductions (traditionnellement, le répertoire locales/ de votre egg). Vous allez donc ajouter au fichier configure.zcml principal de votre composant les ligne suivantes :

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:i18n="http://namespaces.zope.org/i18n"
    i18n_domain="AgileKnowledgeBase">
    ...
    <i18n:registerTranslations directory="locales" />
    ...
</configure>

Code Python

Vous placez ensuite dans le __init__.py principal de votre composant les lignes suivantes :

from zope.i18nmessageid import MessageFactory
MyMessageFactory = MessageFactory('AgileKnowledgeBase')

Écrit en C

Afin d’optimiser les performances, les primitives des MessageFactory sont écrites en C.

Lorsque vous voulez placer un texte susceptible d’être traduit dans votre code, vous devez procéder ainsi.

from Products.AgileKnowledgeBase import MyMessageFactory as _
# ...
def foo(bar):
    message = _(u'my_message', default=u"Some words")
    # ...

Quelques explications :

  • Le symbole _ est un identificateur Python légal. Comme nous le verrons par la suite, i18ndude cherche dans tout le code Python de votre composant l’utilisation de cette fonction pour élaborer les modèles de dictionnaires (fichier *.pot).
  • my_message est le label de traduction qui figurera, après traitement par i18ndude dans le modèle de dictionnaire *.pot de votre composant.
  • Some words est le texte par défaut, en anglais, du label my_message qui sera fourni aux visiteurs du site en l’absence de traduction spécifique pour son langage. Bien que, comme la syntaxe l’indique, le paramètre nommé default n’est pas obligatoire (votre code ne plantera pas) son utilisation est fortement recommandée.

Messages avec variables

Il est parfois nécessaire d’introduire des parties variables dans les messages à traduire. Par exemple : “Ce dossier contient 5 image(s)”. Utilisez pour ceci le paramètre nommé mapping ayant pour valeur un dictionnaire (ou objet quelconque ayant le même protocole) fournissant toutes les clés prévues par les emplacements réservés aux variables dans le message. Ces emplacements variables sont marqués dans le message sous la forme ${clé}.

def bar(value):
    # ...
    msg_map = {'count': value}
    message = _(u'images_in_folder',
                default=u"This folder has ${count} images",
                mapping=msg_map)

Templates ZPT

A faire

expliquer le marquage des templates

De la même façon que le code Python, les templates ZPT peuvent être internationalisée.

Le marquage d’internationalisation est effectué en utilisant l’espace de nommage XML i18n.

<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:tal="http://xml.zope.org/namespaces/tal"
   xmlns:metal="http://xml.zope.org/namespaces/metal"
   xmlns:i18n="http://xml.zope.org/namespaces/i18n"
   >
   ...
</html>

i18n:domain : Déclaration du domaine

Pour marquer le domaine de traduction d’une template (ou d’un élément quelconque figurant dans une template) la déclaration préalable de ce dernier est nécessaire dans un élément englobant.

...
<div i18n:domain="machin">
  <!-- Les textes marqués pour traductions utiliseront les
       messages des dictionnaires du domaine "machin"
  -->
  ...
   <table i18n:domain="truc">
      <!-- Les textes marqués de ce tableau utiliseront par
           contre les dictionnaires du domaine "truc"
      -->
      ...
   </table>
</div>

i18n:translate : Traduction du contenu d’un élément HTML

Le contenu de cette <div> sera traduit selon la clé label_truc du domaine de traduction courant.

...
<div i18n:translate="label_truc">Some stuff</div>
...

Le contenu de cette <div> sera traduit selon la clé Some stuff du domaine de traduction courant. En effet, lorsque l’attribut i18n:translate est vide, le contenu de la balise est utilisé comme clé de traduction.

...
<div i18n:translate="">Some stuff</div>
...

Ceci permet de traduire des textes dont le contenu ne peut être connu qu’au moment de la publication de la template.

...
<!-- On traduit le titre lorsque c'est possible -->
<span i18n:translate="" tal:content="context/title_or_id">Ze title</span>

i18n:attributes : Traduction des attributs d’un élément

Les attributs title et alt sont traduits selon la clé fournie par leurs valeurs (statique ou calculée)

...
<img title="img_title" alt=""
     tal:attributes="alt image/title"
     i18n:attributes="alt title" />
...

i18n:name placer des données variables dans une traduction.

Le problème consiste à traduire des constructions du type :

A faire

exemple avec i18n:name

...
<p>Hi</p>

Note

Utilisez tout le temps i18n:translate=”“ dans vos templates, même si vous appelez une fonction qui retourne un objet Message (une chaine commencant par _( dans votre code Python). Bien que la traduction marcherait sans le préciser avec le moteur de template d’origine, cela ne marcherait pas avec le moteur de template alternatif Chameleon qui peut être utilisé dans Plone 4.

Localiser un composant

Créez un environnement avec i18ndude installé :

$ mkvirtualenv i18n
$ easy_install i18ndude

Attention

N’installez jamais i18ndude dans l’environnement Python standard ou l’environnement virtuel spécifique à un projet mais bien dans un environnement virtuel dédié.

Dans Products/AgileKnowledgeBase créez un script update-l10n.sh :

$ i18ndude rebuild-pot --pot i18n/kb-plone.pot --create plone profiles
$ i18ndude filter i18n/kb-plone.pot \
    ~/.buildout/eggs/plone.app.locales-3.3.1-py2.4.egg/plone/app/locales/i18n/plone.pot \
     > i18n/kb-plone.pot_
$ mv i18n/kb-plone.pot_ i18n/kb-plone.pot
$ i18ndude sync --pot i18n/kb-plone.pot i18n/kb-plone-*.po

$ i18ndude rebuild-pot --pot locales/AgileKnowledgeBase.pot --create AgileKnowledgeBase .
$ i18ndude sync --pot locales/AgileKnowledgeBase.pot locales/*/LC_MESSAGES/AgileKnowledgeBase.po

Exécutez i18ndude -h pour savoir à quoi servent les différentes commandes.

Installez le package Ubuntu gettext qui contient notamment la commande msginit. Installez également poedit (ou kbabel) pour traduire facilement :

$ sudo apt-get install gettext poedit

Exécutez le script update-l10n.sh, pour cela ajoutez le mode exécution :

$ chmod u+x update-l10n.sh
$ ./update-l10n.sh

La première exécution du script échouera pour les commandes i18ndude sync car il n’y a encore aucune traduction.

Vous allez donc maintenant initialiser la traduction française :

$ msginit -i i18n/kb-plone.pot -o i18n/kb-plone-fr.po

La commande msginit fait une copie du fichier pot en fichier po et remplie certains headers comme la date et le traducteur.

Ouvrez ensuite kb-plone-fr.po pour finir de remplir les headers. Remplacez notamment PACKAGE par AgileKnowledgeBase, supprimez VERSION et surtout modifiez ces headers :

"Language-Code: fr\n"
"Language-Name: French\n"

Vous pouvez maintenant passer à la traduction :

$ poedit i18n/kb-plone-fr.po

Vous utiliserez le dossier i18n uniquement pour le domaine plone.

Pour le domaine de votre produit, vous utiliserez le nouveau dossier locales qui est le standard gettext.

Pour le domaine AgileKnowledgeBase, il vous faut tout d’abord créer le dossier :

$ mkdir -p locales/fr/LC_MESSAGES

Générez le fichier AgileKnowledgeBase.pot avec le script :

$ ./update-l10n.sh

Ensuite créez le fichier de traduction :

$ msginit -i locales/AgileKnowledgeBase.pot -o locales/fr/LC_MESSAGES/AgileKnowledgeBase.po

éditez les headers du fichier po comme ceci :

"Language-Code: fr\n"
"Language-Name: French\n"
"Domain: AgileKnowledgeBase\n"

Traduisez ensuite avec poedit.

Pour que vos traductions soient prises au démarrage de l’instance, vous devez également avoir la directive i18n:registerTranslations dans votre configure.zcml, par exemple :

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:grok="http://namespaces.zope.org/grok"
    xmlns:i18n="http://namespaces.zope.org/i18n"
    i18n_domain="AgileKnowledgeBase">

    <i18n:registerTranslations directory="locales" />

    <grok:grok package="." />

</configure>

Exercice

Traduction du template de mail créé précédemment.