# Internationaliser correctement une extension WordPress : le guide i18n

> Text domain, fonctions __() et esc_html__(), fichiers .pot et traductions différées : tout ce qu'il faut savoir pour rendre une extension traduisible sans erreur.

- Auteur : Clément Hadrot
- Publié le : 2021-10-12
- Mis à jour le : 2021-10-12
- Catégorie : Extensions
- URL : https://wpmoderne.dev.wordpress-developpement.fr/extensions/i18n-internationaliser-extension/

## L’essentiel

- Le text domain doit être une chaîne littérale, jamais une variable
- load_plugin_textdomain devenu inutile pour WordPress.org depuis 4.6
- esc_html__ combine sécurité et traduction en un appel

WordPress équipe plus de 40 % des sites web dans le monde, avec une proportion significative d'installations en dehors des pays anglophones. Une extension qui n'est pas correctement internationalisée exclut d'emblée une partie substantielle de son public potentiel, ou pire, affiche un mélange incohérent de texte traduit et de texte resté en anglais.

Cet article détaille la mise en place de l'internationalisation (i18n) dans une extension, des fonctions de traduction aux fichiers `.pot`, en passant par les pièges qui empêchent WordPress.org de générer correctement les traductions automatiques.

## Le text domain : une chaîne littérale, pas une variable

Chaque chaîne traduisible est associée à un « text domain », identifiant unique de l'extension qui doit correspondre exactement au slug du dossier de l'extension sur WordPress.org.

```
/**
 * Plugin Name: Mon Extension Acme
 * Text Domain: acme-mon-extension
 * Domain Path: /languages
 */

echo esc_html__( 'Réglages enregistrés avec succès.', 'acme-mon-extension' );
```

Un piège fréquent et facile à commettre : passer une variable ou une constante comme text domain plutôt qu'une chaîne littérale directement écrite dans le code.

```
// À NE JAMAIS FAIRE
define( 'ACME_TEXT_DOMAIN', 'acme-mon-extension' );
echo __( 'Réglages enregistrés.', ACME_TEXT_DOMAIN );

// Toujours écrire la chaîne en dur
echo __( 'Réglages enregistrés.', 'acme-mon-extension' );
```

La raison est purement technique : l'outil `WP-CLI i18n make-pot` ainsi que les outils de scan de WordPress.org analysent le code source de façon statique, sans l'exécuter. Une constante ou une variable ne peut pas être résolue par cette analyse statique, et toutes les chaînes concernées sont alors invisibles pour la génération automatique du fichier de traduction, cassant silencieusement toute la chaîne de traduction pour cette extension.

## Choisir la bonne fonction selon le contexte

| Fonction | Usage |
| --- | --- |
| __() | Retourne la chaîne traduite, à assigner à une variable |
| _e() | Affiche directement la chaîne traduite (echo implicite) |
| esc_html__() | Traduit puis échappe pour un affichage HTML sûr |
| esc_attr__() | Traduit puis échappe pour un attribut HTML |
| _n() | Gère le pluriel selon une quantité |
| _x() | Ajoute un contexte pour désambiguïser un même mot |

```
// Pluriel correctement géré
printf(
    esc_html( _n( '%d produit en stock', '%d produits en stock', $quantite, 'acme-mon-extension' ) ),
    $quantite
);

// Contexte pour désambiguïser
esc_html_x( 'Poste', 'poste de travail', 'acme-mon-extension' );
esc_html_x( 'Poste', 'envoi postal', 'acme-mon-extension' );
```

Le mot « Poste » en français illustre bien l'intérêt de `_x()` : sans contexte, un traducteur vers une autre langue ne peut pas deviner s'il s'agit d'un poste de travail ou d'un envoi postal, et risque une traduction erronée sans que rien ne l'alerte.

> L'essentiel à retenir : Le text domain doit être une chaîne littérale, jamais une variable ; load_plugin_textdomain devenu inutile pour WordPress.org depuis 4.6 ; esc_html__ combine sécurité et traduction en un appel

## Chaînes avec variables : toujours via printf ou sprintf

Concaténer une traduction avec une variable casse la structure grammaticale dans les langues où l'ordre des mots diffère du français ou de l'anglais.

```
// À éviter : structure figée qui ne s'adapte pas à toutes les langues
echo esc_html__( 'Bonjour', 'acme-mon-extension' ) . ' ' . esc_html( $prenom );

// Préférable : la traduction contrôle la position de la variable
printf(
    /* translators: %s : prénom de l'utilisateur */
    esc_html__( 'Bonjour %s', 'acme-mon-extension' ),
    esc_html( $prenom )
);
```

Le commentaire `/* translators: ... */` juste avant l'appel n'est pas facultatif dans une extension sérieuse : il apparaît directement dans l'interface de traduction de WordPress.org (GlotPress) et aide les traducteurs à comprendre ce que représente chaque paramètre `%s` ou `%d`, en particulier lorsqu'il y en a plusieurs dans la même chaîne.

## Charger les traductions : un besoin qui a disparu depuis 2016

Pendant des années, la documentation recommandait un appel explicite à `load_plugin_textdomain()` dans le hook `init` ou `plugins_loaded`. Depuis WordPress 4.6 (2016), ce chargement est automatique pour les extensions hébergées sur WordPress.org, à condition que le `Text Domain` et le `Domain Path` soient correctement déclarés dans l'en-tête du plugin. L'appel manuel reste nécessaire uniquement pour une extension distribuée en dehors de WordPress.org (vente directe, marketplace privé), car WordPress ne connaît alors pas l'emplacement des fichiers de traduction sans cette indication explicite.

```
// Nécessaire seulement pour une extension hors WordPress.org
add_action( 'plugins_loaded', function () {
    load_plugin_textdomain(
        'acme-mon-extension',
        false,
        dirname( plugin_basename( __FILE__ ) ) . '/languages'
    );
} );
```

## Générer le fichier .pot avec WP-CLI

Le fichier `.pot` (Portable Object Template) recense toutes les chaînes traduisibles extraites du code. WP-CLI simplifie sa génération, plus fiable qu'un scan manuel :

```
wp i18n make-pot . languages/acme-mon-extension.pot
```

Cette commande détecte aussi, en avertissement, les cas de text domain non littéral évoqués plus haut, ce qui en fait un bon outil de vérification avant chaque publication d'une nouvelle version.

> Sur nos extensions distribuées, un contrôle systématique avant chaque mise à jour : lancer `wp i18n make-pot` et vérifier qu'aucun avertissement n'apparaît dans la console. C'est un filet de sécurité qui coûte dix secondes et évite des traductions cassées côté utilisateur final.

## En résumé

Internationaliser une extension correctement repose sur trois disciplines simples mais non négociables : un text domain toujours écrit en dur, la fonction de traduction adaptée au contexte d'affichage (échappement compris), et des variables toujours insérées via `printf` avec un commentaire pour les traducteurs. Depuis 2016, WordPress se charge du reste pour toute extension correctement hébergée sur WordPress.org.
