Internationaliser votre application PHP avec gettext
Développer une application, c'est bien. La distribuer où l'utiliser, c'est mieux. Cependant, tout le monde ne parle pas votre langue, d'où l'intérêt de l'internationaliser (et paf, une introduction toute pourrie \o/). Dans ce billet, je vous présenterai donc comment j'utilise gettext dans CodingTeam, afin de traduire l'application.
Traduire une application se fait généralement à l'aide d'outils dédiés comme gettext. Vous écrivez votre code en mettant tous vos textes traduisibles d'une certaine manière et il va aller les chercher pour en faire un fichier modèle (pot), qui servira ensuite à générer des fichiers de langue (po).En PHP et comme dans beaucoup d'autres langages, il y a déjà quelque chose de tout préparé, ce qui permet de l'utiliser directement, sans rien installer d'autre.
Ma façon d'utiliser gettext est inspirée de la lecture de bon nombre d'articles sur le net. En gros, j'utilise ce code pour charger gettext :
setlocale(LC_ALL, $lang.'.UTF-8');
bindtextdomain('nomdedomaine', 'i18n');
textdomain('nomdedomaine');
bind_textdomain_codeset('nomdedomaine', 'UTF-8');Dans cet exemple, $lang doit valoir quelque chose comme en_GB ou fr_FR et nomdedomaine devrait être le nom de votre application.
Dans le répertoire i18n, il devrait se trouver un dossier fr_FR contenant un autre dossier LC_MESSAGES qui accueillera le binaire produit par gettext (mo).
De même, on peut faire en sorte que la langue soit enregistrée par l'utilisateur, donnée dans l'URL, lue depuis l'en-tête adéquate... Voilà une manière assez rapide^Wmoche et ne respectant pas les spécifications, de se baser sur la langue du navigateur :
$langlist = array('fr' => 'fr_FR', 'en' => 'en_GB');
$accept_language = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
foreach ($accept_language as $value)
{
$choice = mb_substr($value, 0, 2);
if (array_key_exists($choice, $langlist))
{
$lang = $langlist[$choice];
break;
}
}
if (empty($lang))
$lang = 'en_GB';À noter qu'il n'y a pas besoin de générer de .po/.mo pour la langue dans laquelle vous écrivez vos textes au sein de votre application vu qu'ils sont chargés par défaut si gettext n'arrive pas à trouver les traductions qui vont bien.
Pour créer le modèle, j'utilise ce script, qui est bien sûr adaptable. Dès que j'ai créé le .pot, je peux lancer cet autre script qui va me créer le fichier de traduction pour la langue que je désire. Une fois que j'ai traduit toutes les chaines, il me reste à lancer ce dernier script pour générer le binaire !
C'est là qu'on arrive au point relativement intéressant de ce billet (l'avant-ici a des airs de déjà-vu). J'ai toujours utilisé la fonction _() ou gettext() couplée à sprintf() afin de remplacer des éléments par d'autres. Exemple, si je veux traduire « Toto a mangé 4 bananes », j'écris : sprintf(_('Toto a mangé %d bananes'), 4); jusqu'au jour où je me suis rendu compte que si je voulais insérer plusieurs éléments dans la chaine tout en les nommant (afin de pouvoir changer leur ordre), j'allais au devant d'une syntaxe affreuse, repoussante,
%2$s, %1$s ! Mais qui est allé pondre cette horreur ?
Nostalgique de la façon bien plus propre de remplacer des occurrences dans des chaines en Python, j'ai donc écrit cette fonction, que je vous partage. Et j'espère qu'elle vous sera utile. En tout cas, c'est bien plus facile de ne pas se tromper, le traducteur a en face de lui quelque chose qui a une signification et ça ne fait pas tâche.
/**
* Translate a string and add arguments
*
* Gets the translated string from gettext and add arguments in it with
* the Python way instead of the *ugly* PHP way (sprintf).
*
* @param $str
* The string to be translated.
* @param $args
* All arguments to be added.
* @return
* The translated and formatted string.
*/
function i18n($str, $args=array())
{
// Get the translated string
$str = gettext($str);
// Add arguments to the string if exist
if (count($args) > 0)
// Fetching all arguments
foreach ($args as $key => $value)
{
// Decimal value
if (is_numeric($value))
$type = 'd';
// String value
elseif (is_string($value))
$type = 's';
// Replace
$str = str_replace('%('.$key.')'.$type, $value, $str);
}
// Return the translated string with arguments
return $str;
}
Matt.Rixx
# | 3 commentaire(s) - Trolleur égaré | lundi 9 juin 2008, 10:12
En gros ca donne ca ?
echo i18n('Le %noms a mangé %nbd bananes', array('nom' => monsieur, 'nb' => 4));
Et comment gérer les pluriels? Si j'envoie 1 à 'nb', ca marquera "1 bananes"... y'a pas un moyen de gérer ca ?
xbright
# | 303 commentaire(s) - Vrai trolleur | lundi 9 juin 2008, 12:32
Ça donnerait ça, plutôt :
echo i18n('Le %(nom)s a mangé %(nb)d bananes', array('nom' => monsieur, 'nb' => 4));
Pour les pluriels, on utilise pas gettext, mais ngettext et je n'ai rien fait pour supporter les pluriels avec ma fonction. Rien n'empêche de se démerder pour n'avoir qu'un seul élément à insérer dans la chaine que tu veux mettre au pluriel et faire un bon vieux sprintf/ngettext O:-)
Grummfy
# | 9 commentaire(s) - Trolleur égaré | lundi 9 juin 2008, 16:22
Hello,
pas mal du tout,
petite question qui à rien 'avoir pourquoi i18n lorsque l'on parle d'internationalisation?
Thesa
# | 3 commentaire(s) - Trolleur égaré | lundi 9 juin 2008, 16:37
@ Grummfy :
Parce que internationalisation, c'est un i, suivit de 18 lettres, suivit d'un n. Il s'agit de l'étape d'écriture / modification du programme pour permettre des traductions ultérieures. De même, la localisation (le fait d'effectuer la traduction/adaptation pour une langue/un pays précis) s'abrège en l10n.
Grummfy
# | 9 commentaire(s) - Trolleur égaré | lundi 9 juin 2008, 19:26
lol, je me demandait toujours pourquoi.
Je dormirait vraiment moins bêtes tonight.
Merci
Fil des commentaires de ce billet
Ajouter un commentaire