Retrouvez cet article dans : Linux Magazine 84
Implémentez le support de l’internationalisation dans votre application et intégrez-la avec les auto-tools.
Introduction
Le sujet de l’internationalisation de projets a déjà été abordé dans les colonnes de Linux Magazine. Dans le numéro 10, vous participiez à la réalisation d’une application GNOME multilingue.
Mais c’est la dernière partie de l’introduction aux auto-tools du numéro 24 que nous allons reprendre ici, en nous focalisant sur l’internationalisation d’un projet avec les auto-tools, ce qui est le but de l’article.
L’internationalisation d’un programme sans utilisation des auto-tools est possible. Vous pouvez utiliser gettext, comme nous le verrons plus loin.
Cependant, ce serait à vous de coder certains mécanismes que nous ne décrirons pas ici. Pour cela, veuillez vous reporter à l’ouvrage C en action (O’Reilly) qui y consacre un chapitre entier. Nous préférerons ici ne pas nous priver des avantages que procurent les auto-tools.
Bonjour le monde
L’exemple que nous avons l’habitude de prendre, à savoir afficher " Bonjour le monde ", convient presque. En effet, nous pourrions l’utiliser tel quel et proposer une traduction dans d’autres langues dont l’anglais, langue dominante de notre civilisation.
Cependant, à cause de cette domination linguistique précisément, nous allons préférer un affichage natif en anglais et une traduction en français.
Cela a peu d’importance pour la traduction français/anglais, mais cela en revêt tout de suite plus lorsque vous devez traduire de la langue native de votre programme en une langue exotique.
Vous trouverez probablement plus de gens aptes à traduire de l’anglais vers le portugais ou le polonais que du français vers l’une de ces langues.
Hello World
Notre exemple parlera donc anglais contrairement aux articles précédents. Rassurez-vous, l’article a pour objet de le traduire ! Mais voici le code :
src/main.c
|
|
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char*argv[]) {
printf(“Hello World\n”);
exit(EXIT_SUCCESS);
} |
configure.ac
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
AC_PREREQ(2.59)
AC_INIT(bonjour_le_monde, 0.1, ymettier@libertysurf.fr)
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_HEADER([config.h])
# Checks for programs.
AC_PROG_CC
AC_PROG_MAKE_SET
# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([stdlib.h])
# Checks for library functions.
AC_CONFIG_FILES([
Makefile
src/Makefile
])
AC_OUTPUT |
Makefile.am
src/Makefile.am
|
|
bin_PROGRAMS=bonjour
bonjour_SOURCES=main.c |
Étape 1 : internationalisation des auto-fichiers
Ajout du support de gettext
La première chose à faire est d’exécuter
gettextize -c. Nous passons sur les répertoires créés et les nouveaux fichiers installés.
Vous noterez néanmoins que deux nouveaux répertoires apparaissent :
m4/ pour contenir des macros pour l’internationalisation avec les auto-tools et
po/ le répertoire dédié aux traductions.
À la fin de l’opération, vous pouvez lire ce genre de choses :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
Updating Makefile.am (backup is in Makefile.am~)
Updating configure.ac (backup is in configure.ac~)
Adding an entry to ChangeLog (backup is in ChangeLog~)
Please use AM_GNU_GETTEXT([external]) in order to cause autoconfiguration
to look for an external libintl.
Please create po/Makevars from the template in po/Makevars.template.
You can then remove po/Makevars.template.
Please fill po/POTFILES.in as described in the documentation.
Please run ‘aclocal -I m4’ to regenerate the aclocal.m4 file.
You need aclocal from GNU automake 1.5 (or newer) to do this.
Then run ‘autoconf’ to regenerate the configure file.
You will also need config.guess and config.sub, which you can get from the CVS
of the ‘config’ project at http://savannah.gnu.org/. The commands to fetch them
are
$ wget ‘http://savannah.gnu.org/cgi-bin/viewcvs/*checkout*/config/config/config.guess’
$ wget ‘http://savannah.gnu.org/cgi-bin/viewcvs/*checkout*/config/config/config.sub’
You might also want to copy the convenience header file gettext.h
from the /usr/share/gettext directory into your package.
It is a wrapper around <libintl.h> that implements the configure --disable-nls
option.
Press Return to acknowledge the previous 6 paragraphs. |
Nous allons revenir sur tout ce qui précède. D’abord, il nous est indiqué que les fichiers
Makefile.am et
configure.ac ont été modifiés. Vous pouvez les éditer et vous vous rendrez compte que
gettextize a ajouté le répertoire
po/ et tout ce qui s’y rapporte partout où cela était nécessaire.
Comme indiqué ensuite, vous pouvez ajouter
AM_GNU_GETTEXT([external]) dans votre fichier
configure.ac afin que votre script
configure effectue les tests nécessaires sur
gettext. La mention
external signifie que la bibliothèque
libintl n’est pas incluse et doit avoir été installée au préalable sur le système. Si vous tenez à inclure cette bibliothèque dans votre projet, vous avez la possibilité d’exécuter
gettextize -c --intl qui fera le nécessaire.
Vous devez ensuite créer le fichier
po/Makevars. Renommez le fichier
po/Makevars.template en
po/Makevars et le tour est joué. Il n’y a a priori rien à modifier dans ce fichier dans le cadre d’un projet simple.
Toujours dans le répertoire
po/, vous devez éditer le fichier
POTFILES.in afin d’y ajouter la liste des fichiers contenant des chaînes de caractères à traduire. Voici le contenu de ce fichier pour nous :
|
|
# List of source files which contain translatable strings.
src/main.c |
Dans le désordre, il est conseillé de copier le fichier
gettext.h de votre système avec vos sources. Vous pouvez l’installer dans le répertoire
src/ et modifier
Makefile.am en conséquence.
Vous en profiterez pour ajouter une ligne
#include <gettext.h> dans chaque fichier de sources contenant des chaînes à traduire. Mais nous reviendrons à cela plus loin, dans l’étape 2.
Maintenant, exécutez la commande
aclocal -I m4 comme indiqué plus haut. Lancez ensuite la suite classique
autoconf; autoheader; automake -a -c.
Vous remarquez qu’automake se charge lui-même d’installer les fichiers
config.sub et
config.guess. Vous n’avez donc pas besoin d’aller chercher ces fichiers à l’endroit indiqué.
|
|
$ automake -a -c
configure.ac:11: installing <code>./config.guess’
configure.ac:11: installing </code>./config.sub’ |
Lancez encore ceci pour tester que tout fonctionne :
./configure; make distcheck et la première étape sera achevée. Nous ne touchons maintenant plus aux fichiers de définitions pour les auto-tools.
Définition de LOCALEDIR
Dans les sources, nous allons avoir besoin du chemin contenant les locales. Celui-ci est généralement défini dans une macro du joyeux nom de
LOCALEDIR.
Partout où vous avez une fonction
main() (le répertoire
src/ dans notre cas), ajoutez ceci au fichier
Makefile.am. Cette macro n’est a priori pas utilisée ailleurs.
|
|
datadir = @datadir@
localedir = $(datadir)/locale
DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ |
Il pourrait exister une autre façon de faire, à savoir définir la macro dans le fichier
config.h. Pour cela, nous utiliserions
AC_DEFINE_UNQUOTED. Cependant, un simple
echo ${datadir} dans
configure.ac motre le problème :
En poussant un peu plus loin, avec par exemple
eval bonjour_localedir=${datadir}/locale, nous voyons qu’il vaut mieux abandonner la piste :
En fait, le script
configure ne définit la variable
${prefix} que très tard. En outre, il est plutôt déconseillé de forcer cette valeur plus tôt ou d’en choisir une soi-même. Mieux vaut se contenter de ce que propose
autoconf et définir la macro dans le fichier
Makefile.am, comme nous l’avons vu ci-dessus.
Étape 2 : internationalisation du code
Les fichiers sources
Dans cette étape, nous allons effectuer des modifications dans le code source. Dans chaque fichier de code, vous allez vous assurer que le fichier d’en-tête
config.h est inclus, et vous allez le faire suivre d’une inclusion de
gettext.h que nous avons recopié dans l’étape 1. Vous ajouterez de plus quelques définitions de macros qui permettront de rendre plus aisé le marquage des chaînes à traduire. Voici ce que cela peut donner, au début d’un fichier source :
|
|
#include <config.h>
#include <gettext.h>
#define _(String) gettext (String)
#define N_(String) String |
L’inclusion du fichier
config.h est importante car, grâce aux auto-tools, il contient des lignes de code nécessaires à
gettext.h. Par exemple, sans lui, vous pouvez avoir par la suite une erreur à cause d’un
LC_ALL manquant.
Ces trois macros servent, comme nous l’avons vu juste avant, au marquage des chaînes à traduire. En fait, le mécanisme de traduction passe par l’appel à la fonction
gettext() à laquelle vous indiquez la chaîne à traduire. Mais le nom de cette fonction est trop long à écrire si vous devez le faire pour chaque chaîne. Aussi, il est plus simple d’utiliser la macro
_() à la place.
Parfois, il n’est pas possible d’appeler
gettext() dans le code à l’endroit où est définie la chaîne. Voici un exemple :
|
|
char chaine[] = "Plouf !"; |
Ici, la chaîne est statique. Vous devez donc appeler
gettext() ailleurs. Par exemple, si par la suite vous avez
printf("%s\n", chaine);, vous mettrez à la place ceci :
printf("%s\n", gettext(chaine));. Cela devrait fonctionner tout aussi bien, avec l’inconvénient cependant de ne pas marquer la chaîne de caractères à traduire. C’est là que vous utilisez
N_() qui ne fait rien si ce n’est la marquer. Le programme
xgettext, qui réalise l’extraction des chaînes à traduire, saura la trouver grâce à cela.
main()
Le fichier source contenant
main() reçoit un traitement particulier supplémentaire. En effet, au début de la fonction principale, vous devez ajouter les trois lignes suivantes, telles quelles :
|
|
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE); |
Ces trois lignes de code initialisent tout le nécessaire pour l’internationalisation de votre code, aussi bien la traduction que la localisation. Les macros utilisées ici ne tombent pas du ciel. La première,
LC_ALL, est définie dans les en-têtes système.
PACKAGE n’est rien d’autre que le nom de notre paquet, défini dans
config.h. Quant à
LOCALEDIR, nous avons sué plus haut pour le définir dans le fichier
Makefile.am. C’est ici qu’il est utilisé.
Recompilation
Si vous n’avez pas fait d’erreur, l’exécution de
make s’effectue sans difficulté. Sinon, peut-être avez-vous oublié de définir une macro ?
Étape 3 : traduction des chaînes
La traduction des chaînes de caractères est, au niveau informatique, l’étape la plus simple probablement.
Nouvelle traduction
Démarrer une nouvelle traduction s’effectue en trois parties :
- Ajoutez ou complétez la macro LINGUAS dans le fichier configure.ac. Indiquez-y la langue de la nouvelle traduction, par exemple fr pour notre bonne vieille langue française.
- Dans le répertoire po/, trouvez le fichier portant le nom du projet et d’extension .pot. Faites-en une copie que vous appellerez xx.po où xx indique la langue. Une traduction française ira donc dans le fichier fr.po.
- Éditez le début du fichier de traduction pour y indiquer votre nom, la date de traduction, et d’autres choses comme le jeu de caractères choisi (par exemple ISO-8859-15 ou UTF-8).
Il ne vous reste ensuite plus qu’à traduire. Après les en-têtes, le fichier de traduction contient un certain nombre de séquences avec une chaîne issue des sources indiquée par
msgid suivie d’une traduction qui n’est initialement pas faite, précédée de
msgstr. Vous devez laisser les
msgid tels quels et traduire les
msgstr.
Compléter une traduction
Avant de compléter une traduction, vous devez vous assurer que le fichier d’extension
.po que vous voulez traduire est à jour. Pour cela, allez dans le répertoire
po/ et exécutez
make update-po. Vous pouvez maintenant vous mettre à traduire.
Vous remarquerez probablement, pour un projet un peu plus gros que le nôtre, des chaînes à traduire précédées d’un commentaire
# fuzzy. Cela signifie que la traduction est une traduction automatique. Il est très important de chercher tous ces commentaires afin de vérifier les traductions. Il est tout aussi important de supprimer ce commentaire lorsque vous avez vérifié (et parfois corrigé) la traduction. En effet, ce n’est pas la peine de vous donner du travail supplémentaire de vérification pour la prochaine fois que vous traduisez.
Quelques remarques sur la traduction
Lorsque vous traduisez, et que la chaîne à traduire est longue, vous pouvez couper la chaîne en fermant les guillemets et en les ouvrant à nouveau à la ligne suivante. Le résultat sera concaténé, exactement comme cela se fait en C. Ainsi,
et
sont équivalents. N’hésitez pas à en profiter pour plus de lisibilité.
Lors de la mise à jour des chaînes de caractères (réalisée avec
make update-po dans le répertoire
po/ ou avec
make distcheck), certains points de vos traductions sont vérifiés. En particulier, si la chaîne originale contient un retour à la ligne à sa fin, il faudra que votre traduction le contienne aussi. Avis aux programmeurs : le pluriel des mots est un sujet complexe.
Si le français respecte le même genre de règles qu’en anglais, avec un s au pluriel, ce n’est pas le cas dans toutes les langues, et cela commence à côté de chez nous. Les Italiens aiment remplacer la fin des mots par un i et non un s lorsqu’ils parlent au pluriel. De plus, la notion de pluriel n’est pas la même partout.
Encore plus près de chez, disons tout simplement chez nous en France, le pluriel se définit par un nombre strictement supérieur à un. De l’autre côté de la Manche, en Angleterre, le pluriel correspond à un nombre différent de un.
Le zéro est donc pluriel chez les anglais et singulier chez nous. Cette problématique est décrite dans le manuel de
gettext.
Le résultat
À titre d’illustration, voici tous les fichiers ayant subi des modifications par rapport aux fichiers initiaux, suite à l’internationalisation de notre code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
AC_PREREQ(2.59)
AC_INIT(bonjour_le_monde, 0.1, ymettier@libertysurf.fr)
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_HEADER([config.h])
# Checks for programs.
AC_PROG_CC
AC_PROG_MAKE_SET
AM_GNU_GETTEXT([external])
ALL_LINGUAS=fr
# Checks for libraries.
# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([stdlib.h])
# Checks for typedefs, structures, and compiler characteristics. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
configure.ac
AC_PREREQ(2.59)
AC_INIT(bonjour_le_monde, 0.1, ymettier@libertysurf.fr)
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_HEADER([config.h])
# Checks for programs.
AC_PROG_CC
AC_PROG_MAKE_SET
AM_GNU_GETTEXT([external])
ALL_LINGUAS=fr
# Checks for libraries.
# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([stdlib.h])
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_CONFIG_FILES([
po/Makefile.in
Makefile
src/Makefile
])
AC_OUTPUT |
Makefile.am
|
|
SUBDIRS= po src
ACLOCAL_AMFLAGS = -I m4
EXTRA_DIST = config.rpath mkinstalldirs m4/ChangeLog |
src/Makefile.am
|
|
datadir = @datadir@
localedir = $(datadir)/locale
DEFS = -DLOCALEDIR=\”$(localedir)\” @DEFS@
bin_PROGRAMS=bonjour
bonjour_SOURCES=main.c gettext.h |
src/main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <gettext.h>
#define _(String) gettext (String)
#define N_(String) String
int
main (int argc, char *argv[])
{
setlocale (LC_ALL, “”);
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
printf (_(“Hello World\n”));
exit (EXIT_SUCCESS);
} |
po/POTFILES.in
|
|
# List of source files which contain translatable strings.
src/main.c |
po/fr.po
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
# Translation for the bonjour project
# Copyright (C) YEAR Free Software Foundation, Inc.
# This file is distributed under the same license as the PACKAGE package.
# Yves Mettier <ymettier@libertysurf.fr>, 2006.
#
msgid “”
msgstr “”
“Project-Id-Version: bonjour_le_monde 0.1\n”
"Report-Msgid-Bugs-To: ymettier@libertysurf.fr\n"
"POT-Creation-Date: 2006-01-13 00:03+0100\n"
"PO-Revision-Date: 2006-01-09 17:29+GMT+1\n"
"Last-Translator: Yves Mettier <ymettier@libertysurf.fr>\n"
“Language-Team: Français <fr@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=ISO-8859-15\n"
"Content-Transfer-Encoding: 8bit\n"
#: src/main.c:14
#, c-format
msgid "Hello World\n"
msgstr “Bonjour le monde\n” |
Conclusion
Un programme ne parle pas encore français ? Votre projet se limite toujours à la langue française ? Vous avez maintenant de quoi faire pour le traduire. Pour un projet encore non internationalisé, écrivez vos chaînes en anglais pour toucher un public de traducteurs plus grand et, surtout, traduisez d’emblée en français. Cela vous permettra de vous rendre compte d’erreurs ou de difficultés de traduction que vous corrigerez immédiatement. Cela évitera à un traducteur contributeur dans une autre langue de se trouver face à des difficultés de base, qui peuvent le mener à l’abandon de sa contribution spontanée. Cela serait dommage. Alors, à vos dictionnaires bilingues !
Références
Retrouvez cet article dans : Linux Magazine 84