Chapitre 7.8 — Internationalisation (i18n)
⏱️ TL;DR — Internationaliser (i18n) = rendre un plugin/thème traduisible. Toute chaîne affichée passe par une fonction de traduction (
__(),_e(),esc_html__()…) avec votre text domain (le slug du plugin). WordPress génère ensuite un fichier.pot(modèle), traduit en.po/.mopar langue. Pour un plugin publié sur wp.org, les traductions sont même communautaires (translate.wordpress.org). Règle d’or : jamais de texte affiché en dur — toujours enveloppé dans une fonction i18n avec le bon domain.
🎯 Objectifs
- Envelopper les chaînes avec les bonnes fonctions i18n.
- Utiliser le text domain et le charger.
- Gérer variables, pluriels et contexte.
- Générer les fichiers de traduction.
1. Les fonctions de traduction
| Fonction | Rôle |
|---|---|
__( 'Texte', 'domain' ) | Retourne la traduction. |
_e( 'Texte', 'domain' ) | Affiche (echo) la traduction. |
esc_html__() / esc_html_e() | Traduit + échappe (à privilégier à l’affichage). |
esc_attr__() / esc_attr_e() | Traduit + échappe pour un attribut. |
_n( 'singulier', 'pluriel', $n, 'domain' ) | Pluriel. |
_x( 'Texte', 'contexte', 'domain' ) | Traduit avec contexte (désambiguïsation). |
echo esc_html__( 'Ajouter un livre', 'wpr-books' ); // affiche + échappe
$label = __( 'Genres', 'wpr-books' ); // récupère⚠️ Piège n°1 — le double emploi traduction/échappement. À l’affichage, préférez
esc_html_e()(traduit et échappe) plutôt que_e()seul : une traduction peut contenir du HTML inattendu. Sécurité (ch. 7.7) et i18n vont ensemble.
2. Le text domain
Le text domain est l’identifiant de vos traductions : il doit correspondre au slug du plugin (dossier) et à l’en-tête Text Domain: (ch. 7.1).
// En-tête du plugin : Text Domain: wpr-books / Domain Path: /languages
// Charger les traductions (sur init)
add_action( 'init', function () {
load_plugin_textdomain( 'wpr-books', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
} );⚠️ Piège n°2 — text domain incohérent ou variable. Le domain doit être une chaîne littérale identique partout (
'wpr-books'), jamais une variable ($domain) : les outils d’extraction (§4) lisent le code source statiquement. Un domain qui ne correspond pas au slug empêche le chargement des traductions (chaînes non traduites).
📚 Aller plus loin — Depuis WordPress 4.6, les plugins/thèmes hébergés sur wp.org chargent automatiquement leurs traductions depuis translate.wordpress.org (souvent,
load_plugin_textdomainn’est même plus nécessaire pour eux). Pour un plugin distribué hors wp.org, gardez l’appel explicite.
3. Variables, pluriels, contexte
Variables — n’insérez jamais une variable dans la chaîne à traduire ; utilisez des placeholders avec sprintf/printf :
// ✅ BON : le traducteur voit "%s livres trouvés" et peut réordonner
printf(
esc_html( _n( '%s livre trouvé', '%s livres trouvés', $count, 'wpr-books' ) ),
number_format_i18n( $count )
);
// ❌ MAUVAIS : chaîne cassée, intraduisible correctement
_e( 'Nombre : ' . $count . ' livres', 'wpr-books' );Pluriel — _n() gère les règles de pluriel (différentes selon les langues). Contexte — _x() désambiguïse un même mot (« Post » = publier vs article) : _x( 'Post', 'verbe : publier', 'wpr-books' ).
⚠️ Piège n°3 — la concaténation. Concaténer des morceaux de phrase (
__('Bonjour') . $nom . __('bienvenue')) est intraduisible proprement (l’ordre des mots varie selon la langue). Toujours une phrase complète avec placeholders (sprintf( __( 'Bonjour %s, bienvenue', 'domain' ), $nom )).
4. Générer les fichiers de traduction
.pot(Portable Object Template) : le modèle listant toutes vos chaînes. Généré avec WP-CLI :wp i18n make-pot . languages/wpr-books.pot..po: le fichier traduit par langue (wpr-books-fr_FR.po), édité avec Poedit ou GlotPress..mo: la version compilée (binaire) que WordPress charge.wp i18n make-mo languages/.
Convention de nommage : {textdomain}-{locale}.mo (ex. wpr-books-fr_FR.mo).
💡 Pour un dev React — C’est le même pipeline que i18next / react-intl : on extrait les clés du code (
make-pot≈ l’extracteur), on traduit par locale (.po≈ vos fichiers JSON de langue), on charge la bonne locale au runtime. La différence : le domain remplace le namespace, et l’extraction est statique (d’où l’exigence de chaînes et domain littéraux). Côté JS de vos blocs (Partie 8), WordPress fournit aussi@wordpress/i18n(__()en JavaScript) avec le même principe.
5. Checklist i18n
- Toute chaîne visible enveloppée dans une fonction i18n avec le bon domain littéral.
esc_html__/esc_html_eà l’affichage (i18n + échappement).- Placeholders (
%s,%d) +sprintf, jamais de concaténation. _n()pour les pluriels,_x()pour le contexte..potfourni ;Text Domain/Domain Pathrenseignés dans l’en-tête.
✏️ Exercices
- Réécrivez correctement :
_e( 'Il y a ' . $n . ' résultats', 'wpr-books' );. - Pourquoi le text domain ne doit-il jamais être une variable ?
- Quelle fonction utilisez-vous pour afficher une chaîne traduite et échappée ?
✅ Solution
printf(
esc_html( _n( 'Il y a %s résultat', 'Il y a %s résultats', $n, 'wpr-books' ) ),
number_format_i18n( $n )
);On utilise un placeholder (%s) + sprintf/printf, _n() pour le pluriel, et l’échappement.
2. Parce que l’extraction des chaînes (wp i18n make-pot) lit le code statiquement : un domain variable ne peut être identifié, et un domain incohérent empêche le chargement des traductions.
3. esc_html_e() (ou esc_html__() pour récupérer) : elle traduit et échappe le texte affiché.
🧠 Quiz de révision
1. Quelle fonction retourne une chaîne traduite ?
__( 'Texte', 'domain' ) (_e() l’affiche ; esc_html__/esc_html_e traduisent + échappent).
2. Qu’est-ce que le text domain ?
L’identifiant des traductions, égal au slug du plugin, présent dans chaque appel i18n et l’en-tête Text Domain:.
3. Comment insérer une variable dans une chaîne traduite ?
Avec un placeholder (%s, %d) et sprintf/printf — jamais par concaténation.
4. À quoi servent .pot, .po, .mo ?
.pot = modèle des chaînes ; .po = traduction par langue ; .mo = version compilée chargée par WordPress.
5. Quel outil génère le .pot ?
WP-CLI : wp i18n make-pot . languages/domain.pot.
Chapitre suivant : Tutoriel : le plugin wpr-books.