Internationaliser son application consiste à la préparer à être distribuée dans différentes langues. Lorsque l'on développe, c'est toujours utile et pratique de prévoir à long terme. Ce tutorial est destiné à être utilisé dans un programme en C, mais la partie théorique s'applique aux autres langages, et très peux de différences existent entre les différents langages, seul l'initialisation dans le programme changera.
Ce tutorial est à destination des utilisateurs de systèmes de type unix. N'ayant pas de windows à disposition et n'ayant aucune envi de programmer dessus, je vous laisses à vos recherches. Les méthodes ne différent pas complètement.
Pour internationaliser notre application, nous allons utiliser la bibliothèque GNU destinée à cela, gettext.
Cela permet de distribuer facilement une application dans différentes langues, sans la recompiler, et sans mettre de nombreuses conditions dans le code source. De plus, de nombreux outils sont disponibles pour les traducteurs non développeurs. gettext n'est pas limité au C, et peut être utilisé dans la plupart des langages de programmation, du moment qu'un port existe.
Au final, dans votre code source C;
se transformera en ceci:
Sommaire
- Comprendre le fonctionnement de gettext
- Installer gettext
- Les fichiers de localisation
- Dans le code C
- Utiliser gettext
- Conclusion
Comprendre le fonctionnement de gettext
Dans votre programme, au lieu de mettre directement en mémoire votre chaîne de caractères, vous appelez la fonction gettext avec pour paramètre une chaîne de caractères qui sert de clé.
Si il existe une traduction pour la langue de l'utilisateur et la clé, la traduction est retournée en lieu et place.
Si il n'en existe pas, c'est la clée qui est
retournée. Pour cette raison, on préférera plus nommer
ses clés en anglais, qu'en hexadécimal. D'ailleurs pour le
travail de traduction, c'est bien plus facile à comprendre.
La langue de l'utilisateur est déterminée à partir des paramètres régionaux de son ordinateur. Pour les langues, il s'agit de variables globales de la forme [language[_territory][.codeset].
Pour un français habitant en france, on aura donc une variable locale de type fr_FR.UTF-8, pour un anglais, on aura en_GB.UTF-8.
Si on se contente du choix automatique possible avec gettext, on en retiendra juste qu'il vaut mieux écrire en unicode. En effet, sur les systèmes modernes, l'unicode UTF-8 est désormais l'encodage par défaut.
De plus, pour un programme qui vise à être traduit dans de nombreuses langues, il vaut mieux avoir un encodage permettant d'utiliser tout les caractères disponibles. Après, rien ne vous empêche d'utiliser un autre encodage, mais ce ne sera pas détaillé dans ce tutorial.
Installer gettext
Si gettext n'est pas déjà installé, vous pouvez l'installer avec votre gestionnaire de paquets préféré, ou télécharger les sources et compiler manuellement (./configure && make && sudo make install). C'est ce que j'ai fait pour mon mac, et bien que la compilation soit étrangement longue, tout fonctionne à merveille.
Les fichiers de localisation
Il existe trois types de fichiers de localisation. Avec deux grandes familles, les fichiers compilés à destination du programme (d'extension .mo), et les fichiers textes lisibles par un être humain (.po et .pot).
Les fichiers pour humains sont couramment dans un dossier po à la base du programme. Le fichier .pot est nommé par le nom du programme, nom_programme.pot encore une fois par soucis de faire comme tout le monde. Les autres fichiers .po sont nommés par leur locale, pour faire simple. Ainsi pour la traduction française, on aura fr_FR.po, pour l'américaine, en_US.po.
Inutile d'indiquer l'encodage dans le nom du fichier. De même rien n'empêche de ne pas utiliser d'indication de région, on peut donc mettre un fichier fr.po qui sera à destinations de tout les utilisateurs francophones.
Vous générez le fichier .pot à partir d'une commande et du code C. C'est un des énormes avantages de gettext, une commande suffit à extraire toutes les clés de vos fichiers de code et à générer le fichier .pot. Le gain de temps est assuré.
Le fichier .pot sert de fichier de base pour les fichiers .po de traduction. Vous ne devez donc pas le modifier, ça risque de créer quelques problèmes, et ça ne sert à rien. C'est ce fichier que vous devez fournir aux traducteurs, à moins qu'ils ne préfèrent partir d'une traduction déjà réalisée dans une autre langue. Mais pour commencer, vous copier le fichier .po.
Pour les fichiers compilés, c'est un peu plus compliqué, mais
gettext est construit comme ça.
Tout est contenu dans locale/language/LC_MESSAGES/nom_programme.mo
.
Et il ira chercher les fichiers dans ces répertoires, ne
changez pas les noms. Par exemple, pour le fichier de
traduction en français, on aura un chemin du type
locale/fr/LC_MESSAGES/nom_programme.mo
.
gettext propose des outils pour compiler les fichiers .po en .mo (ou décompiler les fichiers .mo en .po, mais c'est moins utile).
Dans le code C
Nous allons aborder l'utilisation de gettext dans un langage en C à l'aide d'un programme très simple, à peine éloigné du Hello World.
Nous avons besoin de deux nouvelles bibliothèques:
#include <locale.h>
Un raccourcis très répandu consiste à appeler la fonction gettext par un underscore, permettant de ne pas surcharger visuellement le code. On ajoute donc cette ligne:
Ensuite, nous initialisons gettext (en premier dans le programme):
textdomain(NOM_PROGRAMME);
bindtextdomain(NOM_PROGRAMME, "locale");
Ici, le nom du programme est le même que celui que vous avez utilisé pour nommer vos fichiers, sinon vous risquez d'avoir des surprises.
Comme nous l'avons vu dans l'introduction, pour traduire une chaîne de caractères, nous appelons donc la fonction _(), ce qui donne: _("Hello\n") par exemple.
Voici le code d'un programme d'exemple, très simple:
#include <stdlib.h>
#include <libintl.h>
#include <locale.h>
#define _(string) gettext(string)
#define NOM_PROGRAMME "hello"
void init_gettext()
{
setlocale(LC_ALL, "");
textdomain(NOM_PROGRAMME);
bindtextdomain(NOM_PROGRAMME, "locale");
}
int main (int argc, char const* argv[])
{
init_gettext();
/* Liste des messages à traduire */
char *tab[] = {_("Hello"),_("Great"),_("Done"),_("Error"),_("Quit")};
/* Il faut les afficher maintenant */
int i;
for(i = 0; i < (sizeof tab / sizeof *tab); i += 1 )
{
printf("%s\n",tab[i]);
}
return 0;
}
Attention, une chose à ne pas faire est de remplacer:
// par:
char *tab[] = {"Hello","Great","Done","Error","Quit"};
et,
// par:
printf("%s\n",_(tab[i]));
En effet, deux problèmes se posent, le premier est qu'il est stocké deux fois plus de texte dans la ram (la clé, et la traduction). Le deuxième est que le parseur de gettext ne verra pas vos clés (il repère les appels à la fonction gettext et _), et ne les ajouteras pas automatiquement au fichier .pot .
Pour compiler votre programme, utilisez la classique commande cc main.c -Wall -lintl
Utiliser gettext
Le parseur de gettext qui crée le fichier .pot à partir des sources est xgettext.
Dans le dossier de votre application, il s'utilise comme cela:
xgettext main.c autre_fichier.c un_autre.c -o
po/nom_programme.pot --keyword=_ --from-code=utf-8
Vous donnez chaque fichier à analyser, vous indiquez où enregistrer le fichier .pot, que vous travaillez en unicode, et que vous avez rajouté le raccourcis underscore pour les appels de fonctions.
Pour le programme d'exemple, on utilise cette commande: xgettext main.c -o po/hello.pot --keyword=_ --from-code=utf-8
Vous copiez maintenant le fichier .pot en .po, par exemple: cp po/hello.pot po/fr.po
Vous pouvez modifier le fichier .po désormais. msgid est la clé et msgstr est la traduction.
Il est également indiqué où est rencontré chaque clée, pour
mieux se repérer par rapport au code lorsque le nom de la clé
est ambigu.
Pour traduire, un éditeur de texte fonctionne très bien, mais
il existe aussi des outils destinés à cela.
Voici par exemple poEdit:
Une fois votre traduction finie, vous devez la compiler dans le
bon dossier. Si celui si n'existe pas encore, il faut le créer:
mkdir -p locale/fr/LC_MESSAGES Puis exécuter
la commande msgfmt:
msgfmt po/fr.po -o
locale/fr/LC_MESSAGES/hello.mo
Vous pouvez maintenant lancer votre application, si vous avez
les bonnes locales, tout devrait fonctionner.
Pour changer vos locales, vous devez changer la définition de
la variable LC_ALL:
export LC_ALL="en_GB.UTF-8" par exemple.
Pour revenir comme avant, LC_ALL="C" est à
utiliser.
La commande locale affiche les différentes variables locales,
pour ne pas se perdre.
Lorsqu'il y a des changements dans votre code source, vous
devez ensuite régénérer les fichiers de traductions pour
prendre en compte les modifications. Pour cela, régénérez le
fichier .pot avec
xgettext comme plus haut.
Vous devez ensuite répercuter les modifications sur les
différents fichiers .po.
Vous pouvez utiliser un logiciel comme poEdit qui propose cette
fonctionnalité, ou utiliser la commande gettext
msgmerge.
Par exemple:
msgmerge fr.po hello.pot --verbose -o fr.po
Vous devez ensuite à nouveau compiler la locale avec msgfmt.
Conclusion
Tout ceci est un peu pénible au début, mais avec l'habitude, le gain de temps est évident. Je vous encourage à internationaliser vos programme, vous y gagnerez beaucoup au final. Et puis vos traductions ne sont pas dépendantes du langage utilisé, ou même du projet. C'est plus souple d'utilisation, rien ne vous empêche de réécrire votre application dans un autre langage ou même de combiner plusieurs langages de programmation en même temps.
Le 30/09/2010 à 20:32
Très intéressant
Vos explications sur le sujet sont éclairantes. Merci !