Quand on développe un site web, il peut parfois être utile de le rendre disponible en plusieurs langues. Il est bien sûr possible de faire plusieurs fichiers différents pour autant de langues que l’on veut supporter, mais ce ne serait pas franchement très pratique. Heureusement, PHP nous simplifie grandement la tâche avec gettext.

GNU gettext, c’est une librairie disponible dans plusieurs langages et qui permet d’internationaliser des applications. Le principe est relativement simple : dans le programme, on modifie la façon de gérer les chaînes de caractères pour pouvoir dire qu’il faut internationaliser telle ou telle chaîne. Ensuite, on prépare un fichier contenant toutes les chaînes à traduire, et il n’y a plus qu’à les traduire pour que gettext sache par quoi modifier notre texte.

Comme vous vous en doutez, gettext existe en PHP, et c’est donc dans ce contexte que nous allons le voir ici. Vous allez d’ailleurs pouvoir constater que pour internationaliser une page web, il n’y a pas grand-chose à modifier.

Préparation des fichiers

Avant de voir la partie traduction et internationalisation à proprement parler, il nous faut préparer nos fichiers PHP pour dire à gettext quelles chaînes il doit traduire, dans quelle langue et surtout où chercher les fichiers de traduction.

Ce que nous allons faire ici doit être fait en tout début de fichier (c’est plus commode). Nous commençons par déclarer deux variables contenant respectivement la langue voulue et le domaine. Le domaine, c’est en quelque sorte un espace où l’on va ranger toutes les chaînes de caractères concernant une certaine catégorie. Pour de petits sites, le domaine n’a pas grand intérêt, mais imaginez de gros sites avec plein de pages et de catégories : en stockant toutes les chaînes de caractères au même endroit, on ne s’y retrouve plus, d’où l’intérêt des domaines qui permettent de mettre de l’ordre là-dedans.

Jusque-là c’est plutôt simple. La variable $lang contient la langue voulue. Personnellement, j’ai pour habitude d’écrire les chaînes originales en anglais pour les traduire après, mais rien ne vous empêche de faire le contraire. Le domaine peut être n’importe quoi.

Ensuite, il faut dire à PHP quelle langue nous parlons. Cela se fait avec deux fonctions qui vont indiquer la locale utilisée (le code dans la variable $lang) :

Il nous faut ensuite indiquer le domaine dans lequel chercher les fichiers de traduction. Là encore, deux fonctions seront nécessaires :

La fonction bindtextdomain() demande deux arguments : le premier est le nom du domaine et le second est le chemin vers le dossier où sont situés les fichiers de traduction. Pour plus de praticité, il est recommandé de les placer dans un dossier spécifique. La fonction textdomain() quant à elle indique que l’on souhaite utiliser le domaine indiqué.

La préparation des fichiers est presque terminée. En fait, la suite changera à chaque projet : il s’agit d’indiquer quelles chaînes doivent être traduites. Pour cela, il faut légèrement modifier notre façon d’afficher ces dernières.

Plusieurs façons d’afficher une chaîne

La méthode la plus simple pour afficher un texte traduit, c’est avec la fonction gettext(), dont _() est un alias beaucoup plus utilisé. Vous pouvez donc utiliser l’une ou l’autre sans distinction (enfin faites un choix quand même, c’est plus propre) :

Mais dans certains cas, gettext() ne suffira pas. Il faut en effet par moments savoir gérer le pluriel qui ne se comporte pas toujours de la même façon selon les langues. Par exemple, si vous souhaitez afficher le nombre de commentaires, il faudra faire la distinction entre « 1 commentaire » et « 3 commentaires« . On pourrait bien évidemment faire nos propres tests pour déterminer quelle chaîne afficher, mais gettext peut faire le boulot pour nous et ainsi choisir s’il faut traduire au pluriel ou non :

Si on utilise ici printf() plutôt qu’un simple echo, c’est pour l’argument %d qu’il propose et qui permet d’insérer un nombre, indiqué en deuxième argument. L’utilisation de ngettext() n’est par ailleurs pas très mystérieuse : on indique le texte du singulier, puis le texte du pluriel et enfin le nombre d’éléments, pour savoir si on doit utiliser la première ou la seconde forme.

Traduction !

Bien, nos fichiers sont prêts, mais pour le moment nous n’avons toujours qu’une seule langue disponible. Il est donc temps de passer à la traduction à proprement parler, et vous allez pouvoir constater que c’est probablement beaucoup plus simple que ce à quoi vous vous attendez…

Premièrement, il faut savoir que gettext ira chercher les chaînes traduites dans des fichiers *.mo. Il s’agit de fichiers binaires générés à partir de fichiers *.po. C’est dans ces fichiers *.po que tout le travail de traduction est fait : on y trouve les chaînes originales et, en-dessous, les chaînes traduites. Chaque langue aura donc son propre fichier *.po, et pour éviter de modifier un fichier de langue pour créer une autre langue, on a introduit les fichiers *.pot, qui vont servir de base à nos fichiers *.po : on y trouvera les chaînes à traduire, mais on ne les traduira jamais. Il nous faut donc maintenant créer tous ces fichiers.

xgettext

Nous avons préparé nos fichiers et nous savons donc quelles chaînes seront traduites : les fonctions sont uniques, on sait où chercher. Du coup, on se dit qu’un programme pourrait s’occuper d’aller chercher tout ça tout seul, et c’est exactement ce que fait xgettext. Ouvrez donc un terminal et lancez la commande :
xgettext index.php -o lang/monsite.pot

Bien évidemment, ça se change à souhaits : index.php est le chemin vers le fichier à traduire et après l’option -o vient un autre chemin, celui du fichier *.pot à générer. Ce fichier *.pot peut être utilisé tel quel, mais je vous recommande de le modifier légèrement (un simple éditeur de texte suffira).

Ainsi, vous y trouverez un header sous forme de gros commentaire. Ce header contient quelques passages en lettres capitales qu’il serait bon de modifier pour y voir apparaître les informations concernant votre projet. De même, la toute première chaîne contient plusieurs informations et il est utile d’y modifier le CHARSET (UTF-8 est une bonne idée).

msginit

Maintenant que nous avons notre *.pot, il faut générer autant de *.po que de langues à supporter. Pour cela, c’est assez simple, et ça passe par un autre programme : msginit, qui s’utilise comme suit.
msginit --locale=fr -i monsite.pot -o fr_FR/LC_MESSAGES/monsite.po

Je n’ai pas vraiment insisté là-dessus parce que ce n’est pas très important pour le fichier *.pot, mais le nom des fichiers a une importance : à la fin, le fichier *.mo devra posséder le nom du domaine de traduction que vous avez choisi plus haut. C’est pourquoi j’ai choisi les noms « monsite » depuis le début, il s’agissait simplement de rester cohérent.

La locale doit bien sûr être adaptée selon vos besoins. L’option -i attend, comme vous vous en doutez, le fichier *.pot sur lequel il faut se baser tandis que l’option -o est le chemin vers le fichier *.po à générer. Là, vous devriez vous demander pourquoi j’ai choisi un chemin aussi… pourri disons-le clairement.

En fait, nous n’avons pas vraiment parlé de l’endroit où gettext va chercher les fichiers. Il va bien sûr se baser sur le dossier que vous avez indiqué pendant la création du domaine (« ./lang » pour ma part). Cependant, ce dossier doit être découpé en plusieurs sous-dossiers qui représenteront autant de langues supportées. Dans ces sous-dossiers, il faudra créer un répertoire LC_MESSAGES et c’est là-dedans qu’il faudra mettre vos fichiers *.mo. Encore une fois, l’emplacement du *.po n’a pas d’importance, mais il s’agit d’être cohérent.

Petite pause

Si ce n’est pas vous qui traduisez les phrases, envoyez les fichiers *.po au traducteur et prenez donc une petite pause, car vous n’avez rien à faire en attendant.

Dans le fichier *.po, on trouve une succession de petits blocs. La plupart seront sous la forme d’un couple (msgid, msgstr). Comme vous pouvez le deviner, msgid est la chaîne à traduire et ne doit pas être modifiée. Par contre, msgstr est par défaut vide et doit donc être remplie avec la traduction de la chaîne correspondante. Et c’est tout.

Les choses se compliquent légèrement avec le pluriel si vous en avez utilisé : on trouvera msgid pour la forme singulière et msgid_plural pour la forme plurielle, les deux faisant référence aux chaînes originales. Viennent ensuite les traduction, dans le même ordre. Pas très compliqué en fait, mais si vous ne traduisez pas vous-même, pensez bien à indiquer au traducteur qu’il ne doit en aucun cas modifier les « %d » et autres tags dont vous vous servez.

D’ailleurs, en parlant de ça, une option utile de xgettext est -c : ajoutez-la et tous les commentaires situés juste au-dessus d’une chaîne à traduire apparaîtront dans le fichier *.pot (et donc dans les *.po).
xgettext index.php -o lang/monsite.pot -c
Elle peut s’avérer pratique pour prévenir les traducteurs qu’ils doivent laisser certains tags tels qu’ils sont.

msgfmt

Nous avons bientôt fini. Vous avez les fichiers *.po contenant les traductions, reste maintenant à les traduire en binaires pour que gettext puisse les utiliser. Cette opération peut être faite de façon très simple :
msgfmt monsite.po -o monsite.mo

Et c’est tout ! Normalement, vous devriez voir vos chaînes dans la langue choisie dans la variable $lang créée tout au début. Il ne vous reste plus qu’à rendre cette variable un poil plus dynamique : dans l’état, cette traduction ne sert strictement à rien si vous ne détectez pas automatiquement la langue de l’utilisateur, ou si vous ne lui laissez pas une option permettant de la choisir lui-même.

Je veux rajouter une phrase sans devoir tout recommencer

Votre site évoluera sûrement au cours du temps, en proposant toujours plus de contenu. Ainsi, vous pourriez être amené à rajouter une phrase à traduire. Sauf qu’avec ce qu’on a vu ici, vous n’avez pas d’autre choix que de tout recommencer, en reprenant la traduction depuis le début. C’est moche.

Heureusement, gettext et compagnie ont tout prévu ! Comme d’habitude, on commence avec xgettext :
xgettext -j index.php -o monsite.pot

Ici, c’est l’option -j qui change tout : elle a pour charge de ne pas écraser le contenu du fichier monsite.pot qui existe déjà et ajoutera à la suite les nouvelles chaînes. Il y a cependant un petit bémol : une chaîne modifiée apparaîtra comme nouvelle et sera donc ajoutée à la fin, tandis que l’ancienne chaîne, devenue inutile, conservera sa place.

On continue avec msginit, comme toujours :
msginit --locale=fr -i monsite.pot -o fr_FR/LC_MESSAGES/monnouveausite.po

Ici, rien ne change, excepté que je n’ai pas choisi d’écraser l’ancien fichier *.po, ce qui est très important. Contentez-vous ensuite de ne traduire que les nouvelles chaînes en laissant vides les anciennes.

Avant de formater tout ça en binaire, nous allons fusionner l’ancien fichier *.po et le nouveau : les traductions seront alors toutes au même endroit, qu’elles soient anciennes ou nouvelles.
msgmerge monsite.po monnouveausite.po -o monsite.po

L’ordre est important ici. À la sortie, j’écrase l’ancien fichier *.po : vous n’êtes pas obligés de le faire, mais il sera devenu inutile (comme le nouveau d’ailleurs).

La suite rejoint ce que vous connaissez : un coup de msgfmt et le tour est joué.

Pfiou, ce fût long, mais on aura fait le tour de tout ce dont nous avons besoin de base pour traduire un site web. Si vous avez besoin d’options supplémentaires, n’oubliez pas le manuel de chaque commande qui est assez fourni. Nous aurons d’ailleurs l’occasion de revenir sur une option de xgettext bien particulière lors d’un prochain tutoriel sur la traduction d’un plugin ou thème WordPress.

Pour finir, notez que tout ici utilise un simple éditeur de texte, mais il existe des programmes, comme poEdit par exemple, qui permettent la traduction dans un environnement fenêtré, pour ceux qui préfèrent.