Internationalisation : vous commettez probablement une erreur

Publié le
Mis à jour le

Auteur

Pierre Ripka

Cet article est une traduction de Internationalization: You’re probably doing it wrong, publié le 28 février 2012 par Ottopress.

Anecdote du jour : environ 37 % des téléchargements de WordPress concernent des versions localisées non anglaises.

En tant que créateur de plugin ou de thème, vous devriez donc considérer la locaalisation et l’internationalisation (L10N et I18N) comme étant quasiment indispensables.

Estimation du jour : d’après ma propre expérience sur ce sujet, à peu près, oh… tous les plugins et thèmes du catalogue sont, d’une certaine manière, mal conçus.

Oui mes amis, même mon code est coupable dans une certaine mesure.

C’est compréhensible. Lorsque vous codez, vous travaillez sur la fonctionnalité, pas sur la forme. Alors vous mettez des chaînes de caractères en vous disant « hey, ce n’est pas grave, je peux revenir et ajouter le I18N plus tard ». Parfois même, vous revenez plus tard et vous le faites.

Et vous savez quoi ? Vous continuez probablement à vous tromper. Je l’ai fait. Je le fais encore souvent.

Si vous vous trompez, c’est parce que l’utilisation du I18N n’est pas évidente. Il y a des astuces et des règles qui sortent de la logique classique de PHP.

Donc, voici les lois inviolables du I18N concernant les plugins et les thèmes WordPress.

Note : Ceci n’est pas un tutoriel à proprement parler. Vous êtes déjà censé traduire votre code d’une certaine façon, et avoir des connaissances de base sur le sujet. Ce que je vais vous montrer concerne des choses que vous faites déjà probablement, mais qu’il ne faut pas faire. Avec un peu de chance, vous allez prendre beaucoup de claques en lisant cet article, j’espère en tout cas vous faire ressentir ce que j’ai moi-même ressenti quand j’ai enfin compris.

Autre note : Ce sont des lois. Pas des suggestions. Vous ne pouvez pas les transgresser. Elles ne sont pas là pour créer le débat. Ce que je vais vous présenter aujourd’hui est attesté comme étant correct. Désolé, j’aime les bons arguments comme tout le monde, mais en défendant ces pratiques, vous vous faites simplement du mal.

Fonctions basiques du I18N

Tout d’abord, nous allons aborder rapidement les deux fonctions principales de traduction. Nous en verrons d’autres plus tard, ainsi que les lois qui s’y appliquent, mais voici celles que tout le monde devrait connaitre et qui permettent les exemples les plus simples.

La fonction de traduction de base est __(). C’est la fonction au double underscore. Elle reçoit une chaîne de caractères et la traduit, selon les paramètres de localisation, puis la retourne.

Il y a ensuite la fonction raccourcie _e(). Elle fait la même chose, mais affiche le résultat en echo au lieu de simplement le retourner.

Il y a plusieurs fonctions basées autour de celles-ci, comme esc_attr_e() par exemple. Ces fonctions se comportent toutes de manière identique à leurs homologues réunis. La fonction esc_attr_e() exécute d’abord la chaîne à travers __(), puis lui applique esc_attr(), puis l’affiche. Ces fonctions sont nommées d’une manière spécifique afin de travailler avec les outils de traduction existants. Toutes les lois suivantes s’appliquent à elles de la même façon.

Nous y sommes.

Première loi : tu n’utiliseras pas de variable PHP dans une fonction de traduction de chaîne

Ce code est évidemment incorrect, ou devrait l’être :

$string = __($string, 'plugin-domain');

Pourquoi ne faut-il jamais faire cela ? La traduction repose sur ce principe : les chaînes sont d’abord recherchées dans une table puis sont traduites. Cependant, cette liste de chaînes à traduire est créée de façon automatisée. Certains scripts scannent votre code PHP, sans l’exécuter, récupèrent toutes les __() qu’ils trouvent et s’en servent pour construire une liste de chaînes à traduire. Ces scripts de scan ne peuvent pas connaître la valeur de $string.

Cependant, c’est parfois plus subtil. Par exemple, ceci est également incorrect :

$string = __("You have $number tacos", 'plugin-domain');

La chaîne traduite ici ressemblera à « You have 12 tacos » mais le script de scan ne peut pas connaître la valeur de $number en avance, et il est impossible d’attendre de vos traducteurs qu’ils traduisent toutes les possibilités de ce que pourrait être $number.

En principe, les chaînes entre doubles quotes dans les fonctions de traductions sont toujours suspectes, et probablement incorrectes. Mais cette règle ne peut pas être absolue, car l’utilisation d’opérations de chaînes telles que 'You have '.$number.' tacos' est également incorrecte, pour la même raison.

Voici des erreurs qui font débat :

$string = __('You have 12 tacos', $plugin_domain);
$string = __('You have 12 tacos', PLUGIN_DOMAIN);

Il s’agit de deux cas d’une même erreur. Vous avez décidé que la répétition est mauvaise, ainsi vous définissez le domaine du plugin dynamiquement, et vous le référencez partout.

Mark Jaquith a détaillé sur son blog la raison pour laquelle cette méthode n’est pas bonne, mais je vais aussi adopter un principe général ici.

Je l’ai dit ci-dessus, et je vais le répéter : « cette liste de chaînes à traduire est créée de façon automatisée ». Quand je fais un script pour lire votre code et l’analyser, je n’exécute pas votre code. Je l’analyse. Et bien qu’en général, la création d’une liste de chaînes ne nécessite pas de connaître le textdomain de votre plugin, un cas plus compliqué peut survenir. Il y a des raisons légitimes pour vous demander d’indiquer votre domaine en texte brut et non pas sous forme de variable ou constante.

Pour les débutants, et si nous avions fait quelque chose comme créer un système où l’on puisse traduire nos chaînes directement sur le site wordpress.org ? Ou mettre au point un système où l’on pourrait recruter des traducteurs bénévoles pour traduire vos chaînes pour vous ? Ou faire en sorte que les gens puissent facilement télécharger des versions localisées de votre plugin, avec les bonnes traductions déjà incluses ?

Ce ne sont que quelques idées, mais pour chacune d’entre elles, ce textdomain doit être une chaîne de caractères brute. Pas une variable. Pas une constante.

Ce qu’il faut retenir : Dans toutes les fonctions de traduction, aucune variable PHP n’est autorisée dans les chaînes, pour aucune raison, jamais. Uniquement des chaînes brutes, entourées par des simples quotes.

Deuxième loi : tu traduiras toujours des phrases et non des mots

Voici une méthode souvent employée pour éviter d’utiliser des variables :

$string = __('You have ', 'plugin') . $number . __(' tacos', 'plugin-domain');

Non ! Mauvais codeur ! C’est mal !

L’anglais est une langue de mots. D’autres langues ne sont pas aussi flexibles. Dans certaines autres langues, le sujet vient en premier. Votre méthode ne fonctionnera pas dans ce cas, à moins que le traducteur ne mette « tacos » à la place de « you have » et vice-versa.

Voici la bonne méthode :

    
$string = sprintf( __('You have %d tacos', 'plugin-domain'), $number );

Le traducteur peut alors écrire l’équivalent dans sa langue, en laissant le %d au bon endroit. Notez que dans ce cas, le %d n’est pas une variable PHP mais un placeholder pour le nombre.

D’ailleurs, c’est le moment idéal pour introduire une nouvelle fonction permettant de gérer la pluralisation. Personne n’a « 1 tacos ». Donc nous pouvons écrire ceci :

$string = sprintf( _n('You have %d taco.', 'You have %d tacos.', $number, 'plugin-domain'), $number );

La fonction _n est une fonction de traduction qui récupère la première chaîne si $number vaut 1, ou la seconde si $number est supérieur à 1. Nous devons encore utiliser le sprintf pour remplacer le placeholder par le nombre réel, mais maintenant la pluralisation peut être traduite séparément, et dans le cadre de la phrase entière. Notez que le dernier argument de _n doit toujours être le textdomain du plugin.

Notez que certaines langues ont plus qu’un singulier un pluriel. Vous devrez parfois mettre en place des traitements spéciaux, mais cela devrait convenir la plupart du temps. Le polonais en particulier a des règles de pluralisation différentes pour 1, pour les nombres finissant par 2, 3 et 4, et pour les nombres finissant par 5-1 (excepté 1 lui-même). Pas de problème, _n peut gérer ces cas particuliers avec un traitement spécial de la pluralisation dans les fichiers de traduction, et vous n’aurez généralement pas à vous inquiéter tant que vous spécifierez la forme plurielle d’une manère propre, en utilisant la phrase entière.

Vous pouvez également noter que _n est la seule et unique fonction de traduction qui peut contenir une variable PHP. Et ceci parce que cette troisième variable sera toujours un nombre, et non une chaîne de caractères. Par conséquent, aucun script de scan qui crée la liste des chaînes à traduire n’a à se soucier de ce qu’elle est. Vous devez vous assurer que $number dans _n est toujours un nombre. La fonction ne va pas insérer le contenu de cette variable dans la chaîne, elle va choisir quelle chaîne utiliser en fonction de sa valeur.

Utiliser les placeholders peut être complexe, car parfois les éléments devront être inversés.

$string = sprintf( __('You have %d tacos and %d burritos', 'plugin-domain'), $taco_count, $burrito_count );

Que faire si une langue possède une condition étrange où il ne faudrait jamais mettre de tacos avant les burritos ? Avec cette méthode, nous ne pouvons rien faire. Le traducteur doit faire en sorte d’avoir le compteur de burrito en premier. Mais il ne peut pas, les placeholders sont définis tels que $taco_count est prévu pour être premier dans le sprintf. La solution :

$string = sprintf( __('You have %1$d tacos and %2$d burritos', 'plugin-domain'), $taco_count, $burrito_count );

Le %1$d est une forme alternative appelée « échange d’arguments » que permet PHP. Dans ce cas, le traducteur pourrait écrire correctement, mais mettre les burritos avant les tacos en mettant simplement %2$d avant %1$d dans la chaîne.

Notez que lorsque vous utilisez l’échange d’arguments, il devient très important d’avoir une chaîne entre simples quotes. Si %1$s est entre des doubles quotes, PHP va voir le $s et essayer de mettre votre variable $s là-dedans. Dans au moins un cas, cela a causé un problème de sécurité XSS accidentel.

Alors répétez après moi : « J’utiliserai toujours uniquement des chaînes entre simples quotes dans les fonctions I18N ». Très bien. Maintenant, vous êtes à nouveau en sécurité. Ceci devrait probablement être une loi, mais étant donné que l’utilisation des chaînes entre doubles quotes est sûre du moment que vous n’utilisez pas de variables PHP (ce qui romperait la première loi), je vous laisse plutôt méditer là-dessus 🙂

Troisième loi : tu lèveras l’ambiguïté en cas de besoin

Quand je dis « comment » (en anglais), suis-je en train de parler d’un commentaire sur mon site, ou de vous demander de commenter ? Qu’en est-il de « test » ? Ou même de « buffalo » ?

En anglais, certains mots et phrases peuvent avoir des sens différents selon le contexte. Dans d’autres langues, ces mêmes concepts peuvent être des mots ou phrases complètements différents. Pour aider les traducteurs, utilisez la fonction _x() dans ce cas de figure.

La fonction _x() est similaire à la fonction __(), mais possède une section commentaires où le contexte peut être spécifié.

$string = _x( 'Buffalo', 'an animal', 'plugin-domain' );
$string = _x( 'Buffalo', 'a city in New York', 'plugin-domain' );
$string = _x( 'Buffalo', 'a verb meaning to confuse somebody', 'plugin-domain' );

Bien que ces chaînes soient identiques, les traducteurs recevront des chaînes séparées, ainsi que l’explication pour chacune d’entre elles, et ils pourront les traduire en conséquence.

Et tout comme __() a _e pour un affichage immédiat, _x() a _ex() pour faire la même chose. A utiliser si besoin.

Enfin, cette dernière n’est pas une loi mais plutôt quelque chose qui m’agace. Vous êtes libre d’argumenter sur ce sujet si vous le souhaitez. 🙂

Premier agacement : tu ne mettras pas de balises HTML dans la chaîne traduite

$string = sprintf( __('<h3>You have %d tacos</h3>', 'plugin-domain'), $number );

Pourquoi voudriez-vous donner le pouvoir au traducteur d’effectuer des changements de balisage dans votre code ? Les balises devraient être éliminées de vos chaînes traduites aussi souvent que possible. Mettez-les plutôt en dehors de vos chaînes.

$string = '<h3>'.sprintf( __('You have %d tacos', 'plugin-domain'), $number ).'</h3>';

Notez que dans certains cas, cela reste tout à fait acceptable. Si vous ajoutez une emphase sur un mot spécifique, cette emphase peut être différente dans d’autres langues. Cela reste cependant plutôt rare, et parfois vous pouvez la retirer entièrement. Si je voulais un nombre de tacos en gras, j’utiliserais ceci :

$string = sprintf( __('You have %s tacos', 'plugin-domain'), '<strong>'.$number.'</strong>' );

Ou mieux, la version _n que j’ai décrit ci-dessus.

Conclusion

Comme je l’ai dit au début, nous avons tous fait ces erreurs. J’ai rompu toutes ces lois du I18N dans le passé (je sais que certains de mes plugins en portent encore les marques), avant de comprendre que j’avais tout faux. J’espère que vous avez repéré quelque chose que vous faisiez (ou faites actuellement) et avez compris pourquoi votre code est mauvais en lisant cet article. L’état du I18N dans les plugins et thèmes est assez déplorable, et c’est quelque chose que j’aimerais vraiment voir corrigé sur le long terme. Avec un peu de chance, cet article va y contribuer. 🙂

Avertissement : oui, j’ai écrit cet article en ayant faim.

Cet article est une traduction de Internationalization: You’re probably doing it wrong, publié le 28 février 2012 par Ottopress.

Source image : Flickr