Gestion traduction » Historique » Version 28
Fabrice Barconnière, 13/11/2014 08:47
| 1 | 1 | Benjamin Bohard | h1. Gestion traduction |
|---|---|---|---|
| 2 | 1 | Benjamin Bohard | |
| 3 | 1 | Benjamin Bohard | h2. Besoins |
| 4 | 1 | Benjamin Bohard | |
| 5 | 1 | Benjamin Bohard | Pouvoir mettre à jour les fichiers de traduction distribués dans les paquets. |
| 6 | 1 | Benjamin Bohard | |
| 7 | 2 | Benjamin Bohard | Idéalement, il faut : |
| 8 | 27 | Fabrice Barconnière | * un mécanisme permettant de mettre à jour les fichiers *.po* pour chaque langue déjà traduite et le fichier *.pot* pouvant servir de point de départ à la traduction dans une nouvelle langue ; |
| 9 | 6 | Benjamin Bohard | * un mécanisme permettant d'être sûr que la version compilée installée est bien à jour. |
| 10 | 2 | Benjamin Bohard | |
| 11 | 1 | Benjamin Bohard | h2. Procédures |
| 12 | 1 | Benjamin Bohard | |
| 13 | 27 | Fabrice Barconnière | Les fichiers de traduction sont installés sous forme compilée (*.mo*). |
| 14 | 1 | Benjamin Bohard | Ces fichiers compilés ne sont pas éditables directement. |
| 15 | 27 | Fabrice Barconnière | Ils sont générés à partir de fichiers texte (*.po*). |
| 16 | 1 | Benjamin Bohard | |
| 17 | 1 | Benjamin Bohard | La mise à jour des fichiers de traduction est faite en trois étapes : |
| 18 | 27 | Fabrice Barconnière | # extraction des chaînes de caractères à traduire dans un ensemble de fichiers (création ou mise à jour des fichiers *.po*), |
| 19 | 27 | Fabrice Barconnière | # édition des fichiers *.po*, |
| 20 | 27 | Fabrice Barconnière | # compilation des fichiers *.mo* à partir des fichiers *.po*. |
| 21 | 1 | Benjamin Bohard | |
| 22 | 27 | Fabrice Barconnière | L'extraction des chaînes de caractères à traduire est possible par l'emploi de la commande xgettext (également conseillée par l'auteur de *pygettext* depuis qu'elle gère le code *python*). |
| 23 | 27 | Fabrice Barconnière | Le passage des *.po* aux *.mo* et inversement est possible par l'emploi des commandes *msgfmt* et *msgunfmt* réciproquement. |
| 24 | 1 | Benjamin Bohard | |
| 25 | 27 | Fabrice Barconnière | Les fichiers *.po* et *.mo* sont redondants et il n'est pas nécessaire de conserver les deux formats dans les dépôts. |
| 26 | 27 | Fabrice Barconnière | Les fichiers *.mo* ne sont utiles que pour l'exécution des programmes traduits et les fichiers *.po* sont plus facilement exploitables dans un contexte de dépôt git. |
| 27 | 1 | Benjamin Bohard | |
| 28 | 1 | Benjamin Bohard | h2. Implémentation |
| 29 | 1 | Benjamin Bohard | |
| 30 | 27 | Fabrice Barconnière | # si nécessaire, mettre à jour tous les fichiers *.po* déjà créés et écraser le fichier *.pot* servant à créer le point de départ des nouvelles traductions (script lancé par le développeur après changement du code contenant les chaînes à traduire), |
| 31 | 27 | Fabrice Barconnière | # si les fichiers *.po* ont été mis à jour, les éditer, |
| 32 | 27 | Fabrice Barconnière | # compiler les fichiers *.mo* dans un répertoire installé à chaque construction de paquet (cible *Makefile* lancée automatiquement). |
| 33 | 7 | Benjamin Bohard | |
| 34 | 27 | Fabrice Barconnière | Les utilitaires à disposition (voir également la documentation *gettext* au format texinfo, chapitre 9): |
| 35 | 27 | Fabrice Barconnière | * *xgettext* : extraction de chaînes de caractères et création d'un catalogue vide ou ajout des chaînes à un catalogue déjà existant ; |
| 36 | 27 | Fabrice Barconnière | * *msginit* : création d'un catalogue *.po* pour une langue donnée, à partir d'un catalogue vide *.pot* ; |
| 37 | 27 | Fabrice Barconnière | * *msgfmt* : compilation d'un catalogue *.po* au format binaire *.mo* ; |
| 38 | 27 | Fabrice Barconnière | * *msgunfmt* : décompilation d'un catalogue au format binaire *.mo* en un catalogue *.po* ; |
| 39 | 27 | Fabrice Barconnière | * *msgmerge* : assemblage d'un catalogue avec traductions et d'un catalogue avec références mises à jour. |
| 40 | 8 | Benjamin Bohard | |
| 41 | 8 | Benjamin Bohard | h3. Arborescence des projets |
| 42 | 8 | Benjamin Bohard | |
| 43 | 27 | Fabrice Barconnière | Le fonctionnement de *gettext* nécessite de disposer, pour chaque langue, d'un fichier portant le même nom. |
| 44 | 8 | Benjamin Bohard | Sur le serveur, cela est rendu possible par l'organisation en arborescence avec un répertoire par langue, variante linguistique. |
| 45 | 8 | Benjamin Bohard | |
| 46 | 8 | Benjamin Bohard | Dans le dépôt, on propose de distinguer les différentes langues en utilisant également une arborescence : |
| 47 | 8 | Benjamin Bohard | <pre> |
| 48 | 8 | Benjamin Bohard | | |
| 49 | 20 | Benjamin Bohard | `-- translation |
| 50 | 8 | Benjamin Bohard | |-- creole.pot |
| 51 | 8 | Benjamin Bohard | |-- en |
| 52 | 8 | Benjamin Bohard | | `-- creole.po |
| 53 | 8 | Benjamin Bohard | `-- fr |
| 54 | 8 | Benjamin Bohard | `-- creole.po |
| 55 | 8 | Benjamin Bohard | </pre> |
| 56 | 8 | Benjamin Bohard | Le niveau de répertoire LC_MESSAGES présent sur les serveurs n'est pas reproduit dans les dépôts. |
| 57 | 8 | Benjamin Bohard | |
| 58 | 27 | Fabrice Barconnière | Au moment de la compilation, on recherchera tous les fichiers correspondant au motif *<notextile>translation/*/*.po</notextile>*. |
| 59 | 8 | Benjamin Bohard | |
| 60 | 28 | Fabrice Barconnière | h3. Mise à jour des _.pot_ et des _.po_ |
| 61 | 8 | Benjamin Bohard | |
| 62 | 28 | Fabrice Barconnière | La mise à jour des fichiers *.pot* et *.po* est une opération lancée si besoin au moyen du script *po_update.sh*. |
| 63 | 1 | Benjamin Bohard | |
| 64 | 28 | Fabrice Barconnière | Ce script commence par créer (en écrasant si nécessaire) une version à jour du modèle *.pot*. |
| 65 | 14 | Benjamin Bohard | Il met ensuite à jour les fichiers de traduction existant déjà dans l'arborescence. |
| 66 | 14 | Benjamin Bohard | Il gére le cas particulier du fichier de traduction en langue anglaise en le créant si il n'existe pas. |
| 67 | 14 | Benjamin Bohard | Il lui suffit pour cela de recopier les chaînes à traduire dans les chaînes traduites. |
| 68 | 14 | Benjamin Bohard | Attention donc à bien utiliser la langue anglaise dans le corps du code pour les chaînes de caractères à traduire. |
| 69 | 14 | Benjamin Bohard | |
| 70 | 28 | Fabrice Barconnière | Les informations nécessaires pour générer le *.pot* sont un nom de projet (domaine, également utilisé pour le nom de paquet) et les répertoires ou fichiers dans lesquels chercher les chaînes de caractères. |
| 71 | 28 | Fabrice Barconnière | Ces informations doivent être fournies en paramètres au script assurant la génération du *.pot* et la mise à jour des *.po* (en cas de chaîne vide, le message d'origine est utilisé). |
| 72 | 28 | Fabrice Barconnière | Ces informations seront fournies sous la forme du fichier texte *po_list* structuré comme suit : |
| 73 | 8 | Benjamin Bohard | <pre> |
| 74 | 21 | Benjamin Bohard | projet fichier1.py fichiers2*.py |
| 75 | 1 | Benjamin Bohard | </pre> |
| 76 | 12 | Benjamin Bohard | Le premier mot est le nom du projet, les autres mots de la ligne, des motifs désignant les fichiers (et non les répertoires). |
| 77 | 11 | Benjamin Bohard | |
| 78 | 28 | Fabrice Barconnière | *Attention :* le langage défini, en dur, dans le script générant le *.pot* est *python* |
| 79 | 8 | Benjamin Bohard | |
| 80 | 28 | Fabrice Barconnière | h3. Compilation des _.mo_ |
| 81 | 8 | Benjamin Bohard | |
| 82 | 28 | Fabrice Barconnière | La compilation des *.mo* sera faite à la compilation des paquets : le *.mo* ne sera pas présent dans le dépôt. |
| 83 | 28 | Fabrice Barconnière | Les cibles ajoutées dans *eole.mk* seront tous les fichiers *.po* disponibles. |
| 84 | 28 | Fabrice Barconnière | La recette compilera les *.po* en *.mo* directement dans *$(DESTDIR)/usr/share/locale/…* |
| 85 | 14 | Benjamin Bohard | |
| 86 | 14 | Benjamin Bohard | h2. Préparation des programmes à traduire |
| 87 | 14 | Benjamin Bohard | |
| 88 | 14 | Benjamin Bohard | h3. Bibliothèques python |
| 89 | 14 | Benjamin Bohard | |
| 90 | 28 | Fabrice Barconnière | Les bibliothèques python peuvent utiliser *gettext* pour afficher les chaînes de caractères dans une langue spécifique. |
| 91 | 28 | Fabrice Barconnière | Il faut utiliser les fonctions du module *gettext* auxquelles on passe toutes les chaînes de caractères à traduire. |
| 92 | 14 | Benjamin Bohard | Ces fonctions retournent les chaînes de caractères traduites. |
| 93 | 14 | Benjamin Bohard | |
| 94 | 28 | Fabrice Barconnière | h4. modèle de fichier _i18n.py_ |
| 95 | 23 | Benjamin Bohard | |
| 96 | 28 | Fabrice Barconnière | h5. _i18n.py_ à l'échelle du module |
| 97 | 14 | Benjamin Bohard | |
| 98 | 14 | Benjamin Bohard | <pre> |
| 99 | 14 | Benjamin Bohard | import gettext |
| 100 | 14 | Benjamin Bohard | import os |
| 101 | 14 | Benjamin Bohard | import sys |
| 102 | 14 | Benjamin Bohard | |
| 103 | 14 | Benjamin Bohard | # Application Name |
| 104 | 14 | Benjamin Bohard | APP_NAME = 'creole' |
| 105 | 14 | Benjamin Bohard | |
| 106 | 14 | Benjamin Bohard | # Traduction dir |
| 107 | 14 | Benjamin Bohard | APP_DIR = os.path.join(sys.prefix, 'share') |
| 108 | 14 | Benjamin Bohard | LOCALE_DIR = os.path.join(APP_DIR, 'locale') |
| 109 | 14 | Benjamin Bohard | |
| 110 | 14 | Benjamin Bohard | mo_location = LOCALE_DIR |
| 111 | 14 | Benjamin Bohard | |
| 112 | 14 | Benjamin Bohard | t = gettext.translation(APP_NAME, fallback=True) |
| 113 | 14 | Benjamin Bohard | |
| 114 | 14 | Benjamin Bohard | _ = t.ugettext |
| 115 | 14 | Benjamin Bohard | </pre> |
| 116 | 14 | Benjamin Bohard | |
| 117 | 28 | Fabrice Barconnière | La variable *LOCALE_DIR* pointe vers */usr/share/locale*. |
| 118 | 14 | Benjamin Bohard | |
| 119 | 28 | Fabrice Barconnière | La variable *APP_NAME* définit le nom du domaine, soit le nom permettant d'associer le bon fichier *.mo* à la bibliothèque. |
| 120 | 14 | Benjamin Bohard | |
| 121 | 28 | Fabrice Barconnière | _ est utilisé comme "alias" pour la fonction *gettext.translation(APP_NAME, "UTF-8").ugettext*. |
| 122 | 14 | Benjamin Bohard | _() sera appelée dans le code à chaque fois qu'une chaîne de caractère devra être traduite. |
| 123 | 14 | Benjamin Bohard | |
| 124 | 14 | Benjamin Bohard | L'utilisation de *ugettext* plutôt que *gettext* fait que la chaîne retournée sera de l' *unicode*. |
| 125 | 14 | Benjamin Bohard | |
| 126 | 28 | Fabrice Barconnière | Dans le cas d'un fichier *i18n* dans chaque projet : |
| 127 | 24 | Benjamin Bohard | <pre> |
| 128 | 24 | Benjamin Bohard | from i18n import _ |
| 129 | 24 | Benjamin Bohard | </pre> |
| 130 | 22 | Benjamin Bohard | |
| 131 | 22 | Benjamin Bohard | h5. i18n à l'échelle du projet EOLE |
| 132 | 22 | Benjamin Bohard | |
| 133 | 28 | Fabrice Barconnière | Pour limiter le nombre de fichiers *i18n.py*, il est possible d'utiliser une fonction générique apportée par *pyeole*. |
| 134 | 22 | Benjamin Bohard | <pre> |
| 135 | 22 | Benjamin Bohard | # -*- coding: UTF-8 -*- |
| 136 | 22 | Benjamin Bohard | # Copyright (C) 2014 Équipe EOLE. |
| 137 | 22 | Benjamin Bohard | # |
| 138 | 22 | Benjamin Bohard | "internationalisation utility" |
| 139 | 22 | Benjamin Bohard | import gettext |
| 140 | 22 | Benjamin Bohard | |
| 141 | 22 | Benjamin Bohard | system_locale_dir = "/usr/share/locale" |
| 142 | 22 | Benjamin Bohard | |
| 143 | 22 | Benjamin Bohard | class i18n(object): |
| 144 | 22 | Benjamin Bohard | """callable version of gettext |
| 145 | 22 | Benjamin Bohard | """ |
| 146 | 22 | Benjamin Bohard | def __init__(self, app_name): |
| 147 | 22 | Benjamin Bohard | self.t = gettext.translation(app_name, localedir=system_locale_dir, fallback=True) |
| 148 | 22 | Benjamin Bohard | |
| 149 | 22 | Benjamin Bohard | def __call__(self, *args): |
| 150 | 22 | Benjamin Bohard | # "polymorphism" rather than duck-typing to ease xgettext extraction work (keywordspec format) |
| 151 | 22 | Benjamin Bohard | if len(args) == 1: |
| 152 | 22 | Benjamin Bohard | return self.t.ugettext(*args) |
| 153 | 22 | Benjamin Bohard | else: |
| 154 | 22 | Benjamin Bohard | return self.t.ungettext(*args) |
| 155 | 22 | Benjamin Bohard | </pre> |
| 156 | 22 | Benjamin Bohard | |
| 157 | 28 | Fabrice Barconnière | L'appel dans les bibliothèques se fait en deux temps : import de la classe *i18n* et assignation de la variable _ pour disposer d'un appel peu intrusif. |
| 158 | 22 | Benjamin Bohard | <pre> |
| 159 | 22 | Benjamin Bohard | from pyeole.i18n import i18n |
| 160 | 22 | Benjamin Bohard | _ = i18n('lib') |
| 161 | 22 | Benjamin Bohard | </pre> |
| 162 | 22 | Benjamin Bohard | |
| 163 | 28 | Fabrice Barconnière | Le contructeur de la classe *i18n* prend en argument le nom du projet utilisé pour nommer le fichier *.po* et identifier le domaine de l'application. |
| 164 | 1 | Benjamin Bohard | |
| 165 | 1 | Benjamin Bohard | h4. exemple d'utilisation |
| 166 | 1 | Benjamin Bohard | |
| 167 | 15 | Benjamin Bohard | h5. traduction d'une chaîne de caractères |
| 168 | 15 | Benjamin Bohard | |
| 169 | 15 | Benjamin Bohard | <pre> |
| 170 | 26 | Fabrice Barconnière | # -*- coding: UTF-8 -*- |
| 171 | 26 | Fabrice Barconnière | """ |
| 172 | 26 | Fabrice Barconnière | Translation test file 1 |
| 173 | 26 | Fabrice Barconnière | """ |
| 174 | 26 | Fabrice Barconnière | |
| 175 | 1 | Benjamin Bohard | from pyeole.i18n import i18n |
| 176 | 1 | Benjamin Bohard | _ = i18n('project_name') |
| 177 | 1 | Benjamin Bohard | |
| 178 | 1 | Benjamin Bohard | |
| 179 | 1 | Benjamin Bohard | # chaîne non traduite |
| 180 | 1 | Benjamin Bohard | print u"some words" |
| 181 | 1 | Benjamin Bohard | |
| 182 | 1 | Benjamin Bohard | #chaîne qui sera traduite |
| 183 | 1 | Benjamin Bohard | #en fonction des paramètres linguistiques du contexte d'exécution de la bibliothèque |
| 184 | 1 | Benjamin Bohard | #et de la disponibilité de la traduction dans /usr/share/locale |
| 185 | 1 | Benjamin Bohard | print _(u"some words") |
| 186 | 1 | Benjamin Bohard | |
| 187 | 26 | Fabrice Barconnière | #chaîne incluant des variations entre pluriel et singulier |
| 188 | 26 | Fabrice Barconnière | #appel valide uniquement avec la classe i18n (voir i18n à l'échelle du projet EOLE) |
| 189 | 26 | Fabrice Barconnière | num_phrase = 1|2 |
| 190 | 26 | Fabrice Barconnière | print _(u"{0} phrase traduite", u"{0} phrases traduites", num_phrase).format(num_phrase) |
| 191 | 15 | Benjamin Bohard | </pre> |
| 192 | 14 | Benjamin Bohard | |
| 193 | 14 | Benjamin Bohard | *Attention :* toujours garder à l'esprit que la chaîne retournée par la fonction _() est de l'unicode si la fonction ugettext a été utilisée dans le fichier i18n.py. |
| 194 | 14 | Benjamin Bohard | Il peut être nécessaire de l'encoder. |
| 195 | 14 | Benjamin Bohard | |
| 196 | 14 | Benjamin Bohard | Par convention, on utilisera la langue anglaise dans le code et on fournira la traduction française dans les fichiers de traduction. |
| 197 | 14 | Benjamin Bohard | |
| 198 | 28 | Fabrice Barconnière | h2. Étapes de mise en oeuvre |
| 199 | 24 | Benjamin Bohard | |
| 200 | 27 | Fabrice Barconnière | Le projet *eole-skeletor* intègre les fichiers nécessaires à la mise en oeuvre de la traduction. |
| 201 | 27 | Fabrice Barconnière | Pour cela, dans votre projet, il faudra reprendre ces fichiers de la façon suivante : |
| 202 | 28 | Fabrice Barconnière | * Vérifier que *eole.mk* soit à jour pour prendre en compte la compilation des fichiers de traduction ; |
| 203 | 28 | Fabrice Barconnière | * copier *po_update.sh* à la racine du projet ; |
| 204 | 28 | Fabrice Barconnière | * créer un fichier *po_list* à la racine du projet ; |
| 205 | 28 | Fabrice Barconnière | * utiliser la classe *i18n* de *pyeole* pour indiquer ce qu'il faut traduire, exemple : *_ = i18n('project_name')* ; |
| 206 | 28 | Fabrice Barconnière | * les messages en anglais à traduire apparaissent ainsi dans le code python : *_(u"English message")* ; |
| 207 | 28 | Fabrice Barconnière | * exécuter *po_update.sh* pour créer l'arborescence de traduction des projets ; |
| 208 | 28 | Fabrice Barconnière | * effectuer la traduction en français dans les fichier *.po* du répertoire *translation/fr/* |