Projet

Général

Profil

Gestion traduction » Historique » Version 36

Gérald Schwartzmann, 11/01/2019 11:05

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 36 Gérald Schwartzmann
Exemples du projet creole :
23 36 Gérald Schwartzmann
* ./creole/eosfunc.py
24 36 Gérald Schwartzmann
* ./translation/fr/creole.po
25 36 Gérald Schwartzmann
26 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*).
27 27 Fabrice Barconnière
Le passage des *.po* aux *.mo* et inversement est possible par l'emploi des commandes *msgfmt* et *msgunfmt* réciproquement.
28 1 Benjamin Bohard
29 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.
30 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.
31 1 Benjamin Bohard
32 1 Benjamin Bohard
h2. Implémentation
33 1 Benjamin Bohard
34 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),
35 27 Fabrice Barconnière
# si les fichiers *.po* ont été mis à jour, les éditer,
36 27 Fabrice Barconnière
# compiler les fichiers *.mo* dans un répertoire installé à chaque construction de paquet (cible *Makefile* lancée automatiquement).
37 7 Benjamin Bohard
38 27 Fabrice Barconnière
Les utilitaires à disposition (voir également la documentation *gettext* au format texinfo, chapitre 9):
39 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 ;
40 27 Fabrice Barconnière
* *msginit* : création d'un catalogue *.po* pour une langue donnée, à partir d'un catalogue vide *.pot* ;
41 27 Fabrice Barconnière
* *msgfmt* : compilation d'un catalogue *.po* au format binaire *.mo* ;
42 27 Fabrice Barconnière
* *msgunfmt* : décompilation d'un catalogue au format binaire *.mo* en un catalogue *.po* ;
43 27 Fabrice Barconnière
* *msgmerge* : assemblage d'un catalogue avec traductions et d'un catalogue avec références mises à jour.
44 8 Benjamin Bohard
45 8 Benjamin Bohard
h3. Arborescence des projets
46 8 Benjamin Bohard
47 27 Fabrice Barconnière
Le fonctionnement de *gettext* nécessite de disposer, pour chaque langue, d'un fichier portant le même nom.
48 8 Benjamin Bohard
Sur le serveur, cela est rendu possible par l'organisation en arborescence avec un répertoire par langue, variante linguistique.
49 8 Benjamin Bohard
50 8 Benjamin Bohard
Dans le dépôt, on propose de distinguer les différentes langues en utilisant également une arborescence :
51 8 Benjamin Bohard
<pre>
52 8 Benjamin Bohard
|
53 20 Benjamin Bohard
`-- translation
54 8 Benjamin Bohard
    |-- creole.pot
55 8 Benjamin Bohard
    |-- en
56 8 Benjamin Bohard
    |   `-- creole.po
57 8 Benjamin Bohard
    `-- fr
58 8 Benjamin Bohard
        `-- creole.po
59 8 Benjamin Bohard
</pre>
60 8 Benjamin Bohard
Le niveau de répertoire LC_MESSAGES présent sur les serveurs n'est pas reproduit dans les dépôts.
61 8 Benjamin Bohard
62 27 Fabrice Barconnière
Au moment de la compilation, on recherchera tous les fichiers correspondant au motif *<notextile>translation/*/*.po</notextile>*.
63 8 Benjamin Bohard
64 28 Fabrice Barconnière
h3. Mise à jour des _.pot_ et des _.po_
65 8 Benjamin Bohard
66 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*.
67 1 Benjamin Bohard
68 28 Fabrice Barconnière
Ce script commence par créer (en écrasant si nécessaire) une version à jour du modèle *.pot*.
69 14 Benjamin Bohard
Il met ensuite à jour les fichiers de traduction existant déjà dans l'arborescence.
70 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.
71 14 Benjamin Bohard
Il lui suffit pour cela de recopier les chaînes à traduire dans les chaînes traduites.
72 14 Benjamin Bohard
Attention donc à bien utiliser la langue anglaise dans le corps du code pour les chaînes de caractères à traduire.
73 14 Benjamin Bohard
74 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.
75 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é).
76 28 Fabrice Barconnière
Ces informations seront fournies sous la forme du fichier texte *po_list* structuré comme suit :
77 8 Benjamin Bohard
<pre>
78 21 Benjamin Bohard
projet fichier1.py fichiers2*.py
79 1 Benjamin Bohard
</pre>
80 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).
81 11 Benjamin Bohard
82 28 Fabrice Barconnière
*Attention :* le langage défini, en dur, dans le script générant le *.pot* est *python*
83 8 Benjamin Bohard
84 28 Fabrice Barconnière
h3. Compilation des _.mo_
85 8 Benjamin Bohard
86 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.
87 28 Fabrice Barconnière
Les cibles ajoutées dans *eole.mk* seront tous les fichiers *.po* disponibles.
88 28 Fabrice Barconnière
La recette compilera les *.po* en *.mo* directement dans *$(DESTDIR)/usr/share/locale/…*
89 14 Benjamin Bohard
90 14 Benjamin Bohard
h2. Préparation des programmes à traduire
91 14 Benjamin Bohard
92 14 Benjamin Bohard
h3. Bibliothèques python
93 14 Benjamin Bohard
94 28 Fabrice Barconnière
Les bibliothèques python peuvent utiliser *gettext* pour afficher les chaînes de caractères dans une langue spécifique.
95 28 Fabrice Barconnière
Il faut utiliser les fonctions du module *gettext* auxquelles on passe toutes les chaînes de caractères à traduire.
96 14 Benjamin Bohard
Ces fonctions retournent les chaînes de caractères traduites.
97 14 Benjamin Bohard
98 28 Fabrice Barconnière
h4. modèle de fichier _i18n.py_
99 23 Benjamin Bohard
100 28 Fabrice Barconnière
h5. _i18n.py_ à l'échelle du module
101 14 Benjamin Bohard
102 14 Benjamin Bohard
<pre>
103 14 Benjamin Bohard
import gettext
104 14 Benjamin Bohard
import os
105 14 Benjamin Bohard
import sys
106 14 Benjamin Bohard
107 14 Benjamin Bohard
# Application Name
108 14 Benjamin Bohard
APP_NAME = 'creole'
109 14 Benjamin Bohard
110 14 Benjamin Bohard
# Traduction dir
111 14 Benjamin Bohard
APP_DIR = os.path.join(sys.prefix, 'share')
112 14 Benjamin Bohard
LOCALE_DIR = os.path.join(APP_DIR, 'locale')
113 14 Benjamin Bohard
114 14 Benjamin Bohard
mo_location = LOCALE_DIR
115 14 Benjamin Bohard
116 14 Benjamin Bohard
t = gettext.translation(APP_NAME, fallback=True)
117 14 Benjamin Bohard
118 14 Benjamin Bohard
_ = t.ugettext
119 14 Benjamin Bohard
</pre>
120 14 Benjamin Bohard
121 28 Fabrice Barconnière
La variable *LOCALE_DIR* pointe vers */usr/share/locale*.
122 14 Benjamin Bohard
123 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.
124 14 Benjamin Bohard
125 28 Fabrice Barconnière
_ est utilisé comme "alias" pour la fonction *gettext.translation(APP_NAME, "UTF-8").ugettext*.
126 14 Benjamin Bohard
_() sera appelée dans le code à chaque fois qu'une chaîne de caractère devra être traduite. 
127 14 Benjamin Bohard
128 14 Benjamin Bohard
L'utilisation de *ugettext* plutôt que *gettext* fait que la chaîne retournée sera de l' *unicode*.
129 14 Benjamin Bohard
130 28 Fabrice Barconnière
Dans le cas d'un fichier *i18n* dans chaque projet :
131 24 Benjamin Bohard
<pre>
132 24 Benjamin Bohard
from i18n import _
133 24 Benjamin Bohard
</pre>
134 22 Benjamin Bohard
135 22 Benjamin Bohard
h5. i18n à l'échelle du projet EOLE
136 22 Benjamin Bohard
137 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*.
138 22 Benjamin Bohard
<pre>
139 22 Benjamin Bohard
# -*- coding: UTF-8 -*-
140 22 Benjamin Bohard
# Copyright (C) 2014 Équipe EOLE.
141 22 Benjamin Bohard
#
142 22 Benjamin Bohard
"internationalisation utility"
143 22 Benjamin Bohard
import gettext
144 22 Benjamin Bohard
145 22 Benjamin Bohard
system_locale_dir = "/usr/share/locale"
146 22 Benjamin Bohard
147 22 Benjamin Bohard
class i18n(object):
148 22 Benjamin Bohard
    """callable version of gettext
149 22 Benjamin Bohard
    """
150 22 Benjamin Bohard
    def __init__(self, app_name):
151 22 Benjamin Bohard
        self.t = gettext.translation(app_name, localedir=system_locale_dir, fallback=True)
152 22 Benjamin Bohard
153 22 Benjamin Bohard
    def __call__(self, *args):
154 22 Benjamin Bohard
        # "polymorphism" rather than duck-typing to ease xgettext extraction work (keywordspec format)
155 22 Benjamin Bohard
        if len(args) == 1:
156 22 Benjamin Bohard
            return self.t.ugettext(*args)
157 22 Benjamin Bohard
        else:
158 22 Benjamin Bohard
            return self.t.ungettext(*args)
159 22 Benjamin Bohard
</pre>
160 22 Benjamin Bohard
161 32 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.
162 22 Benjamin Bohard
<pre>
163 22 Benjamin Bohard
from pyeole.i18n import i18n
164 22 Benjamin Bohard
_ = i18n('lib')
165 22 Benjamin Bohard
</pre>
166 22 Benjamin Bohard
167 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.
168 1 Benjamin Bohard
169 1 Benjamin Bohard
h4. exemple d'utilisation
170 1 Benjamin Bohard
171 15 Benjamin Bohard
h5. traduction d'une chaîne de caractères
172 15 Benjamin Bohard
173 15 Benjamin Bohard
<pre>
174 26 Fabrice Barconnière
# -*- coding: UTF-8 -*-
175 26 Fabrice Barconnière
"""
176 26 Fabrice Barconnière
Translation test file 1
177 26 Fabrice Barconnière
"""
178 26 Fabrice Barconnière
179 1 Benjamin Bohard
from pyeole.i18n import i18n
180 1 Benjamin Bohard
_ = i18n('project_name')
181 1 Benjamin Bohard
182 1 Benjamin Bohard
183 1 Benjamin Bohard
# chaîne non traduite
184 1 Benjamin Bohard
print u"some words"
185 1 Benjamin Bohard
186 1 Benjamin Bohard
#chaîne qui sera traduite 
187 1 Benjamin Bohard
#en fonction des paramètres linguistiques du contexte d'exécution de la bibliothèque
188 1 Benjamin Bohard
#et de la disponibilité de la traduction dans /usr/share/locale
189 1 Benjamin Bohard
print _(u"some words")
190 1 Benjamin Bohard
191 26 Fabrice Barconnière
#chaîne incluant des variations entre pluriel et singulier
192 26 Fabrice Barconnière
#appel valide uniquement avec la classe i18n (voir i18n à l'échelle du projet EOLE)
193 26 Fabrice Barconnière
num_phrase = 1|2
194 26 Fabrice Barconnière
print _(u"{0} phrase traduite", u"{0} phrases traduites", num_phrase).format(num_phrase)
195 15 Benjamin Bohard
</pre>
196 14 Benjamin Bohard
197 33 Fabrice Barconnière
*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*.
198 14 Benjamin Bohard
Il peut être nécessaire de l'encoder.
199 14 Benjamin Bohard
200 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.
201 14 Benjamin Bohard
202 28 Fabrice Barconnière
h2. Étapes de mise en oeuvre
203 24 Benjamin Bohard
204 27 Fabrice Barconnière
Le projet *eole-skeletor* intègre les fichiers nécessaires à la mise en oeuvre de la traduction.
205 27 Fabrice Barconnière
Pour cela, dans votre projet, il faudra reprendre ces fichiers de la façon suivante :
206 28 Fabrice Barconnière
* Vérifier que *eole.mk* soit à jour pour prendre en compte la compilation des fichiers de traduction ;
207 29 Fabrice Barconnière
* ajouter *po_update.sh* à la racine du projet ;
208 34 Fabrice Barconnière
* créer un fichier "*po_list*":https://dev-eole.ac-dijon.fr/projects/modules-eole/wiki/Gestion_traduction#Mise-%C3%A0-jour-des-pot-et-des-po à la racine du projet ;
209 28 Fabrice Barconnière
* utiliser la classe *i18n* de *pyeole* pour indiquer ce qu'il faut traduire, exemple : *_ = i18n('project_name')* ;
210 28 Fabrice Barconnière
* les messages en anglais à traduire apparaissent ainsi dans le code python : *_(u"English message")* ;
211 30 Fabrice Barconnière
* exécuter *po_update.sh*  pour créer ou actualiser l'arborescence de traduction des projets ;
212 35 Fabrice Barconnière
* effectuer la traduction en français dans les fichier *.po* du répertoire *translation/fr/* :
213 35 Fabrice Barconnière
** attention aux lignes contenant le mot clé *fuzzy*, il s'agit de proposition de traduction qu'il faut éventuellement corriger ;
214 35 Fabrice Barconnière
** une fois la traduction corrigée, supprimer le mot clé *fuzzy* (avec sa virgule) ;
215 29 Fabrice Barconnière
* ajouter le répertoire de traduction au projet ;
216 29 Fabrice Barconnière
* _*commiter*_ les changements ;