Un client distribuant son extension sur plusieurs marchés européens nous a signalé que la version allemande n’affichait presque aucune chaîne traduite, alors que le fichier de traduction existait bel et bien et semblait complet. Le coupable : plusieurs appels à __() dans le code utilisaient le textdomain de l’extension écrit en dur sous forme de variable concaténée, une pratique qui empêche les outils d’extraction de chaînes de les repérer, et qui empêche tout simplement WordPress de les charger correctement au moment de l’exécution.
Ce genre d’erreur ne casse rien de visible pour un développeur qui teste en français ou dans la langue par défaut du site : elle ne se révèle qu’au moment où un utilisateur change réellement de langue, souvent bien après la mise en production. C’est un candidat idéal pour un contrôle automatisé en CI plutôt qu’une vérification manuelle.
Les erreurs de textdomain les plus fréquentes
Le textdomain doit être une chaîne littérale, identique au slug déclaré dans l’en-tête du plugin, passée telle quelle à chaque appel de fonction de traduction. Trois erreurs reviennent régulièrement dans les audits :
// Erreur : textdomain construit dynamiquement, illisible par les outils d'extraction
__( 'Enregistrer les modifications', $this->textdomain );
// Erreur : textdomain qui ne correspond pas au slug déclaré dans l'en-tête du plugin
__( 'Enregistrer les modifications', 'mon-extension-ancien-nom' );
// Correct : chaîne littérale identique au slug du plugin
__( 'Enregistrer les modifications', 'mon-extension' );
La première erreur est la plus insidieuse : le code fonctionne parfaitement en local puisque la variable contient bien la bonne valeur à l’exécution, mais aucun outil d’extraction statique de chaînes ne peut résoudre une variable, ce qui vide le fichier .pot généré de ces chaînes.
Activer les sniffs i18n de WordPress Coding Standards
Le jeu de règles WordPress-Extra de PHPCS inclut des sniffs dédiés à l’internationalisation, regroupés sous WordPress.WP.I18n, qui détectent justement ce type de problème de façon statique, sans avoir besoin d’exécuter le code :
<?xml version="1.0"?>
<ruleset name="Mon Extension">
<rule ref="WordPress-Extra">
<exclude name="WordPress.Files.FileName" />
</rule>
<rule ref="WordPress.WP.I18n">
<properties>
<property name="text_domain" type="array">
<element value="mon-extension" />
</property>
</properties>
</rule>
</ruleset>
La propriété text_domain renseignée explicitement permet à PHPCS de signaler toute chaîne de traduction qui utiliserait un textdomain différent de celui déclaré, y compris un textdomain codé en dur mais simplement mal orthographié, l’erreur la plus fréquente sur les projets qui renomment leur extension en cours de vie.

Vérifier la cohérence du fichier .pot à chaque pull request
Un fichier .pot qui n’a pas été régénéré depuis plusieurs mois de développement actif devient rapidement obsolète : de nouvelles chaînes ajoutées au code n’y figurent pas, ce qui empêche les traducteurs de les traduire tant qu’ils ne s’appuient que sur ce fichier. La vérification consiste à régénérer le .pot dans le job de CI avec WP-CLI, puis à comparer son contenu à la version versionnée dans le dépôt :
wp i18n make-pot . languages/mon-extension.pot --exclude=vendor,node_modules
git diff --exit-code languages/mon-extension.pot
Si la commande git diff --exit-code détecte une différence, le job échoue et signale explicitement que le fichier .pot versionné n’est plus synchronisé avec le code, ce qui invite le contributeur à le régénérer et à l’inclure dans sa pull request avant la fusion.
Attention aux chaînes concaténées, invisibles pour les outils
Un autre piège classique, moins lié au textdomain qu’à la structure même de la chaîne : découper une phrase en plusieurs appels à __() concaténés rend la traduction impossible dans les langues dont l’ordre des mots diffère du français ou de l’anglais.
// À éviter : structure de phrase figée, intraduisible correctement
echo __( 'Vous avez', 'mon-extension' ) . ' ' . $nombre . ' ' . __( 'articles en attente', 'mon-extension' );
// Préférable : une seule chaîne avec espace réservé
printf(
/* translators: %d: nombre d'articles en attente */
esc_html__( 'Vous avez %d articles en attente', 'mon-extension' ),
$nombre
);
Le sniff WordPress.WP.I18n signale également l’absence de commentaire translators: devant un espace réservé, un commentaire pourtant essentiel pour qu’un traducteur comprenne ce que représente chaque %d ou %s sans avoir à lire le code source environnant.
Intégrer le contrôle complet dans le pipeline
- Exécuter PHPCS avec le jeu de règles i18n configuré sur chaque pull request touchant au code PHP
- Régénérer le
.potet comparer son contenu à la version versionnée, en échec bloquant si différence - Ajouter, en documentation interne courte, la liste des fonctions de traduction autorisées (
__(),_e(),_n(),esc_html__()) pour éviter que de nouveaux contributeurs réinventent un mécanisme maison
Une extension qui se targue d’être traduite en douze langues, mais dont un tiers des chaînes récentes n’apparaît même pas dans le fichier de traduction, ne tient pas vraiment cette promesse : le contrôle automatisé est ce qui la rend vraie dans la durée.
En résumé
L’internationalisation d’une extension WordPress se dégrade silencieusement si personne ne la contrôle activement : un textdomain mal écrit ou un .pot qui prend du retard ne cassent rien de visible pour l’équipe de développement elle-même. Les sniffs i18n de WordPress Coding Standards, couplés à une comparaison automatisée du fichier .pot à chaque pull request, transforment ce risque diffus en un contrôle explicite et bloquant, pour un coût de mise en place de quelques lignes de configuration.