Comment traduire correctement un thème ou un plugin WordPress ?
Cet article est basé sur d’Otto on WordPress, “Internationalization, you are probably doing it wrong“, comme sa source son texte est donc sous licence CC BY SA.
Le fait du jour : environ 37% des downloads de WordPress concernent des versions dans d’autres langues que l’anglais.
Donc, en tant qu’auteur de thème ou de plugin, vous devriez vous préoccuper de localisation et d’internationalisation (L10N and I18N).
Or la très grande majorité des plugins et des thèmes présentent des erreurs. I18n n’est pas super-simple, il y a des choses à prendre en compte qui ne relèvent pas du php.
C’est ce que cet article revoit. Ce n’est pas un tuto complet sur la traduction de thèmes, mais une explication sur la bonne utilisation des fonctions de traductions.
Les fonctions I18N de base
La fonction de base pour identifier une chaine à traduire est __(). Cette fonction “double underscore” prend une chaine, et la traduit, selon le paramètre du “domaine de texte”.
Il existe pour un raccourci pour echo __() qui est _e().
Il existe d’autres fonctions, comme esc_attr_e(). Elles se comportent de la même façon que les fonctions correspondantes en php. Ainsi esc_attr_e() traduit d’abord la chaine avec __() puis transfère le résultat à esc_attr() puis exécute un “echo”.
Commandement n°1 : Les variables php dans les chaines à traduire jamais n’utiliseras.
Par exemple, ce code est mauvais :
$string = __($string, 'plugin-domain');
La raison pour laquelle il ne faut jamais faire ça relève du mécanisme de traduction, qui va chercher les chaines générées, avant l’affichage, dans une table, et ensuite les traduit. Il ne trouvera jamais dans le code généré la variable $string, mais sa valeur… et cette valeur ne sera pas dans les listes de chaines traduites.
Parfois, l’erreur est un peu plus subtile :
$string = __("You have $number tacos", 'plugin-domain');
La chaine traduite devrait être quelque chose comme “Vous avez 12 tacos” (ou 36), mais encore une fois la routine qui scanne les chaine ne peut faire le lien entre $number et 12, et de son côté le traducteur ne va pas faire 999 exemplaires de la même chaine….
Et exactement pour la même raison, la phrase ‘You have ‘.$number.’ tacos’ ne doit pas être utilisée.
Dans le même genre, il ne faut pas faire ça :
$string = __('You have 12 tacos', $plugin_domain); $string = __('You have 12 tacos', PLUGIN_DOMAIN);
Deux exemples de la même erreur. Si en général il est mauvais de se répéter, il ne faut pas définir le “text domain” dans une variable ou une constante, puis se référer à cette dernière. (Pour savoir pourquoi, le post détaillé de Mark Jaquith peut vous éclairer). Néanmoins, c’est le même type d’erreur que celle des tacos.
Comme déjà expliqué, “la liste de chaînes à traduire est construite par un processus automatisé“. Quand on écrit un code pour lire une page de code et la parser, on n’exécute pas le code, on le parse. Et si le processus simple de construire une liste de chaine ne nécessite pas (toujours*) de connaître le domaine de texte, il peut y avoire des cas plus complexe, où cette variable est nécessaire.
Par exemple, si dans l’avenir on créait un système qui permettrait de traduire les thèmes directement sur wordpress.org ? Ou un système ou des volontaires pourraient venir traduire des plugin ? Ou un système où les gens pourraient venir télécharger des versions localisées d’un plugin ? Ou un système qui s’appuierait sur les traductions déjà réalisées pour en proposer d’autres ? Dans tous ces cas de figure, on aurait besoin que le text-domaine soit indiqué par une chaîne, et pas par une variable ou une constante.
Donc pour résumer : à l’intérieur des fonctions de traduction, on ne doit trouver, pour quelque raison que ce soit, aucune variable PHP.
Commandement n°2 : Des phrases toujours tu traduiras, jamais des mots
Souvent, les gens essayent de contourner le commandement n°1 de cette façon :
$string = __('You have ', 'plugin') . $number . __(' tacos', 'plugin-domain');
Affreux, affreux, affreux !
Si l’anglais est un langage très souple dans sa structure (mais quand même, avec un ordre naturel), d’autres langues ne sont pas aussi souples. En cassant la phrase en petits morceaux, il devient impossible pour le traducteur de respecter l’ordre des mots dans la traduction (et je pense notamment à l’allemand, avec ses verbes en bout de phrase).
Voici la bonne façon de faire :
$string = sprintf( __('You have %d tacos', 'plugin-domain'), $number );
Le traducteur peut alors écrire la phrase correctement, dans la structure de sa propre langue, en positionnant le %d à la bonne place. Et non, le %d n’est pas une variable php, c’est un argument ! Subtile différence ^^
C’est d’ailleurs le bon moment d’introduire une nouvelle fonction, qui permet de gérer les pluriels. “1 commentaires” ou “1 commentaire(s)” sont tous les deux erronés. Mais on peut écrire ceci :
$string = sprintf( _n('You have %d taco.', 'You have %d tacos.', $number, 'plugin-domain'), $number );
La fonction _n() a 4 paramètres :
- chaine 1
- chaine 2
- nombre
- text-domain
_n() utilisera la chaine 1 si la valeur de $number est 1 (singulier) ou la chaine 2 si il est supérieur à 1 (pluriel). On rajoute sprintf selon la logique vue précédemment, avec l’argument.
_n() est aussi capable de gérer les langues complexes, comme l’arabe ou le polonais, où les formes de pluriel varient selon le “nombre” (en arabe, on a le singulier, le duel et le pluriel, en polonais ça semble extrêmement compliqué…). Ces “pluralisations” comme on les appelle, seront gérées dans les fichiers de traductions.
_n() est en même temps l’exception qui confirme la règle, avec une variable php, mais….
- cette variable est isolée
- c’est toujours un chiffre, donc on n’a pas besoin de la traduire
- à l’intérieur de la chaine, elle est remplacée par le pointeur
On peut aussi utiliser les arguments dans des phrases plus complexes. Par exemple :
$string = sprintf( __('You have %d tacos and %d burritos', 'plugin-domain'), $taco_count, $burrito_count );
Que ferait-on dans une langue qui aurait comme règle de toujours placer les burritos avant les tacos ? Il faut donc identifier séparément chaque pointeur :
$string = sprintf( __('You have %1$d tacos and %2$d burritos', 'plugin-domain'), $taco_count, $burrito_count );
La %1$d permet ce qui s’appelle l’échange d’aguments en PHP. Et avec cela le traducteur peut écrire la phrase correctement, en assignant chaque argument au bon mot, quelque soit l’ordre.
Attention : en utilisant cela, il est essentiel d’utiliser les guillements simples. En mettant des guillements doubles, “%1$s” , PHP essaiera d’utiliser la valeur de la variable $s. Au moins une fois, cela a généré un problème de sécurité, involontairement.
Donc la règle est “J’utiliserai toujours les guillemets simples pour encadrer les chaines à traduire dans les functions I18n”.
Commandement n° 3 : quand nécessaire, les amiguïtés évitera
Particulièrement en anglais, mais dans toutes les langues, il existe des homographes, c’est à dire des mots qui s’écrivent de façon identique, mais avec un sens différent. Ainsi la “mine d’or”, la “petite mine”, et la “mine du crayon”. Ce qui peut sembler clair et sans ambiguïté sur un site peut être totalement confus quand cela se retrouve dans une liste de chaines à traduites.
La fonction _x() est là pour cela. Elle donne le même résultat que __(), mais elle permet d’introduire en troisième argument une explication à l’attention du traducteur. Dans WordPress, elle est utilisée pour les exemples de déclarations de Custom Post Types ou Custom Taxonomies (les labels)
$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' );
Et de la même façon, _ex() est le pendant de _e().
Enfin une dernière recommandation, qui n’est pas un commandement, mais qui est importante.[Ndt Lumière de Lune : étant donné la façon dont elle est formulée, pour moi c’est un commandement).
Commandement n° 4 : du marquage HTML inutilement ne traduira
$string = sprintf( __('<h3>You have %d tacos</h3>', 'plugin-domain'), $number );
est à éviter, selon Otto au profit de :
$string = '<h3>'.sprintf( __('You have %d tacos', 'plugin-domain'), $number ).'</h3>';
Pour moi ce n’est pas mieux. La bonne façon de faire serait :
$string = sprintf( __('You have %d tacos', 'plugin-domain'), $number );
et dans le thème
<h3>.<?php echo $string ;?></h3> [dans le template][NdT Lumière de Lune, et particulièrement dans les constructions de widget…. laissez moi libre de mon marquage sémantique, boudiou !]
Il peut cependant être justifié, dans certains cas, d’inclure ce marquage, notamment si vous voulez mettre en exergue un seul mot au sein d’une phrase (rappel “Des phrases entières tu traduiras”). Dans ce cas, on peut faire ainsi:
$string = sprintf( __('You have %d tacos', 'plugin-domain'), '<strong>'.$number.'</strong>' );
ou mieux encore, utiliser _x() de la même façon.
Au final on obtient combien de tacos et combien de burritos ? :)
Ne pas oublier de tester à plusieurs reprises le thème avec la traduction et de demander à des collègues et amis si ils ne trouvent pas de fautes !
Gael
Euh… %z avec du piment ? :D
Toujours tester, oui… indispensable !
Merci pour cette adaptation en français de l’article original. Par contre, c’est dommage qu’il n’y ai pas de fonctions bien adapté en natif sur WordPress pour gérer la gestion de la ponctuation qui est différente selon les langues. Par exemple, en français il faut mettre un espace avec le symbole “2 points” mais pas en anglais.
pour moi cela fait partie de la traduction. J’avais fait un article assez complet sur ce genres de choses, et comment les gérer.
http://lumlune/traduire-theme-wordpress,2011,04
On peut aussi passer par des plugins de typos, en fonction de la façon dont le contenu multilingue est géré.
Je me suis pris la tête pendant 2 jours à traduire WP Customer reviews, et en même temps j’ai du chercher pour fixer les bugs du plugin.
Par contre comme c’était la première vois que je traduisait un plugin, et que le plugin d’origine avait tout en “hardcodé” je n’ai fait que remplacer les textes, j’aurai du utiliser des variables en effet…
Je pense que je vais devoir refaire une deuxième phase pour du nettoyage, votre article m’a inspiré pour faire ça proprement !!
Vous pouvez le voir en action sur ma page :
et au passage vous pourrez récuperer un lien dofollow :) c’est le cadeau du jour.