WordPress nous fournit plusieurs outils bien pratiques pour rendre nos développements traduisibles. On en a déjà parlé dans l’article Internationaliser son thème ou extension WordPress. J’en parle aussi plus en détails dans le WPCookBook.

Traduire ses développements reste relativement simple tant qu’on reste dans le domaine du PHP. Il suffit de mettre nos chaines dans les fonctions __(), _e() et ses soeurs, et nos fichiers .po et .pot vont, s’ils sont bien configurés, les détecter quand on va rafraichir le code source dans PoEdit.

Ensuite, il suffit de bien paramétrer son entête d’extension ou de thème, avec le bon Text Domain et Domain Path, et utiliser la fonction load_plugin_textdomain() ou load_theme_textdomain() pour charger nos traductions.

Mais ce process se complique un tout petit peu pour le JavaScript, et une étape supplémentaire est nécessaire, et avec les nouveaux outils dont nous disposons depuis WordPress 5.0, je pense que réviser un peu comment fonctionnent les traductions dans WordPress vaut le coup !

Traduire son extension : les bases

Tout d’abord, révisons les bases de l’internationalisation. Pour ça, on va créer une petite extension pour illustrer le propos. L’extension ne sert à rien du tout, si ce n’est exposer des chaines de caractères à traduire.

Dans une installation fraiche de WordPress, naviguez jusqu’à votre dossier wp-content/plugins/ et créez un dossier js-i18n/ et un fichier js-i18n.php à l’intérieur. Dans ce fichier, mettez l’entête suivante :

<?php
/**
 * Plugin Name: Javascript i18n
 * Description: Examples of how to localize JavaScript
 * 
 * Text Domain: js-i18n
 * Domain Path: languages
 * Version:     1.0
 */

Rien de compliqué. On crée une petite extension. Vous pouvez l’activer de suite. Dans les réglages de mon installation de WordPress, j’ai pour l’instant laissé la locale par défaut qui est en_US.

Ensuite, on va simplement créer un petit code court qui va afficher trois boutons, et ces boutons vont afficher un message au clic. Dans notre fichier js-i18n.php, ajoutez ceci :

add_shortcode( 'js_i18n_button', 'js_i18n_button' );
/**
 * Adds a shortcode that prints three button, to hook our JS messages.
 */
function js_i18n_button( $atts = array(), $content = null, $tag = 'js_i18n_button' ){
    ob_start();
    ?>
        <p><?php esc_html_e( 'Click a button to trigger a message.', 'js-i18n' ); ?></p>
        <div class="i18n-button">
            <button id="message-1" class="message-trigger">Message 1</button>
            <button id="message-2" class="message-trigger">Message 2</button>
            <button id="message-3" class="message-trigger">Message 3</button>
        </div>
    <?php
    wp_enqueue_script( 'js-i18n' );
    return ob_get_clean();
}

On affiche simplement un message d’instruction et trois boutons. Notez que le message d’instruction est affiché à l’aide de la fonction esc_html_e(), et le text domain correspondant est fourni, ce qui permettra de le traduire, comme expliqué dans l’article Internationaliser son thème ou extension WordPress.

Aussi, on charge un script js-i18n à l’aide de wp_enqueue_script(). Il nous faut donc créer ce script. Le petit script va afficher le message qui va bien quand on clique sur un bouton. Dans un nouveau dossier js/, ajoutez un petit fichier js-i18n.js avec ce code :

const buttons = document.querySelectorAll('.message-trigger');
const messages = {
    'message-1': 'Hello !',  
    'message-2': 'Hi !', 
    'message-3': 'How are you ?'
};

buttons.forEach(button=>{
    button.addEventListener('click', event => {
        alert(messages[button.id]);
    });
});

Dans ce script, on définit trois messages dans un objet. Chaque clé correspond à l’identifiant d’un des boutons, et la valeur correspond au message que l’on va afficher. Puis on écoute l’événement click sur chaque boutons pour afficher le message correspondant.

Il ne faut pas oublier d’enregistrer le script !

add_action( 'wp_enqueue_scripts', 'js_i18n_scripts' );
/**
 * Enqueues a basic script
 */
function js_i18n_scripts(){
    wp_register_script( 'js-i18n', plugin_dir_url( __FILE__ ) . 'js/js-i18n.js', array(), null, true );
}

Installez le code court [js_i18n_button] sur une nouvelle page pour voir le résultat.

Traduire le JavaScript dans WordPress - code court mis en place.
Notre extension est en place et fonctionne.

Voilà pour la mise en place. Je vous avait prévenu que l’extension n’allait servir à rien, ok ?

On va procéder étape par étape, pour que vous compreniez bien le processus et pour que ce soit plus clair pour les lecteurs qui n’ont jamais traduit une extension.

Créez un fichier .pot

Pour traduire notre extension, il faut d’abord créer un fichier .pot, qui va lister les chaines traduisibles de notre extension. On peut le faire facilement avec une seule commande en utilisant wp-cli. Créez un dossier languages/ dans le dossier de l’extension, et dans un terminal, naviguez vers le dossier racine de votre extension, et entrez la commande suivante.

wp i18n make-pot . languages/js-i18n.pot

Cette commande va créer un fichier js-i18n.pot dans le dossier languages/, en scannant tous les fichiers à la racine et dans les sous dossiers ( la source est « . » ), pour y trouver les chaines à traduire dans les fonctions __() et ses consoeurs.

Si wp-cli n’est pas installé sur votre machine, je vous le recommande vivement. Vous pouvez toujours générer votre .pot manuellement ou à l’aide du Blank-WordPress-Pot, mais vous allez être embêté plus tard pour traduire le JavaScript, et wp-cli offre tellement d’autre outils utiles, ce serait dommage de s’en priver. Pour installer wp-cli, suivez ces instructions d’installation.

Nous avons un .pot, on peut donc créer notre traduction. Pour l’exemple, j’utiliserai PoEdit. Il suffit d’ouvrir le fichier .pot dans PoEdit, de cliquer sur « Créer une nouvelle traduction », de choisir la langue (pour moi le français) puis de traduire les chaines et de sauvegarder le tout dans le fichier js-i18n-fr_FR.po.

C’est très important de respecter la convention et de nommer votre fichier ${slug}-${locale}, où $slug est le nom de votre extension, et $locale la langue de la traduction.

Traduire le JavaScript dans WordPress - traduction dans PoEdit
Notre traduction est faite, et le nom du fichier est le bon.

Maintenant, il nous reste à charger notre traduction. Dans le fichier bootstrap de l’extension js-i18n.php, ajoutez :

add_action( 'plugins_loaded', 'js_i18n_load_textdomain' );
/**
 * Load translations
 */
function js_i18n_load_textdomain() {
    load_plugin_textdomain( 'js-i18n', FALSE, basename( dirname( __FILE__ ) ) . '/languages' );
}

Ici, on utilise load_plugin_textdomain() pour charger les traductions correspondantes au Text Domain js-i18n, et on lui dit d’aller les chercher dans notre dossier languages/.

Si vous passez votre site en français et que vous retournez sur le devant du site, vous pourrez constater que notre message d’instruction est traduit, mais pas les messages déclenchés via JavaScript.

Traduire le JavaScript dans WordPress - messages non traduits
Un seul de nos messages est traduit.

C’est tout à fait normal. Seul notre message généré en PHP dans le code court est affiché à l’aide d’une fonction de traduction, et nos messages déclenchés en JavaScript sont en dur dans notre fichier .js. Aussi, vous remarquerez que ces messages n’apparaissent pas dans notre .po.

Nos traductions sont bien chargées. Il faut maintenant s’occuper de notre JavaScript.

Old school : wp_localize_script()

Pour utiliser des chaines traduisibles dans les scripts chargés par WordPress avant la version 5.0, on utilisait la fonction wp_localize_script( string $handle, string $object_name, array $l10n ).

La fonction prends trois paramètres : $handle correspond au script que l’on veut localiser, $object_name est le nom que l’on va donner à l’objet contenant nos messages, et $l10n est un tableau contenant nos messages.

L’idée avec cette fonction est de créer via PHP un tableau de chaines traduisibles, que l’on va passer ensuite à notre JavaScript. Ainsi, il n’y aura plus aucune chaine en dur dans notre script. Elles viennent toutes du serveur.

Ajustez la fonction qui charge notre script comme ceci :

add_action( 'wp_enqueue_scripts', 'js_i18n_scripts' );
/**
 * Enqueues a basic script
 */
function js_i18n_scripts(){
    wp_register_script( 'js-i18n', plugin_dir_url( __FILE__ ) . 'js/js-i18n.js', array(), null, true );
    wp_localize_script( 'js-i18n', 'messages',  array(
        'message-1' => __( 'Hello !', 'js-i18n' ),
        'message-2' => __( 'Hi !', 'js-i18n' ),
        'message-3' => __( 'How are you ?', 'js-i18n' ),
    ) );
}

Grâce à wp_localize_script(), on passe un objet messages à notre script js-i18n, contenant un tableau avec les trois messages déclenchés sur les boutons. Evidemment, on passe ces messages dans la fonction __() pour les rendre traduisibles.

Notre script js-i18n.js n’a plus besoin de la référence aux trois messages en dur, car il va les recevoir de WordPress. On peut donc le simplifier.

const buttons = document.querySelectorAll('.message-trigger');

buttons.forEach(button=>{
    button.addEventListener('click', event => {
        alert(messages[button.id]);
    });
});

Maintenant, on peut régénérer notre .pot et notre .po, pour qu’ils prennent en compte nos nouveaux messages. Procédez de la même manière que précédemment, en utilisant wp i18n make-pot . languages/js-i18n.pot pour générer un .pot tout frais, puis PoEdit pour créer la traduction. Pensez bien à la nommer js-i18n-fr_FR.po.

Traduire le JavaScript dans WordPress - messages traduits via wp_localize_script
Nos messages sont bien traduits

Tout fonctionne correctement. On déclare nos chaines traduisibles en PHP, et on les envoie à notre JavaScript. Pratique.

New school : les chaines traduisibles directement dans le JavaScript

Depuis WordPress 5.0 et Gutenberg, on dispose de pleins de nouveaux outils JavaScript qui vont nous simplifier la tâche. Parmi eux, se trouve un petit package JavaScript wp-i18n, qui nous permet d’utiliser les fonctions __(), _e() et compagnie directement dans nos scripts ! Si si !

Pour ce faire, on va déclarer ce package en dépendance de notre script. Ajustez notre appel à wp_register_script() comme ceci :

add_action( 'wp_enqueue_scripts', 'js_i18n_scripts' );
/**
 * Enqueues a basic script
 */
function js_i18n_scripts(){
    wp_register_script( 'js-i18n', plugin_dir_url( __FILE__ ) . 'js/js-i18n.js', array( 'wp-i18n' ), null, true );
}

Vous pouvez commenter ou supprimer l’appel à wp_localize_script(). Nous n’en avons plus besoin.

Maintenant, on peut ajuster notre script pour utiliser les fonctions __(), _x(), etc…

const { __ } = wp.i18n;

const buttons = document.querySelectorAll('.message-trigger');

const messages = {
    'message-1': __('Hello !', 'js-i18n'), 
    'message-2': __('Hi !', 'js-i18n'),
    'message-3': __('How are you ?', 'js-i18n')
};

buttons.forEach(button=>{
    button.addEventListener('click', event => {
        alert(messages[button.id]);
    });
});

On extrait la fonction __() du package wp-i18n déclaré en dépendance, puis on recrée notre objet messages, mais cette fois-ci, en utilisant la fonction extraite pour déclarer les chaines traduisibles.

Il ne nous reste plus qu’a régénérer (encore !) nos fichiers .pot et .po (car ils croient que nos messages sont dans notre fichier PHP alors qu’ils sont dans le JavaScript), et à charger nos traductions.

Si vous recréez votre fichier .pot à l’aide de la commande wp i18n make-pot . languages/js-i18n.pot , et ouvrez ce fichier dans votre éditeur de code, vous pourrez voir que la source de la chaine à changé. Par exemple :

#: js/js-i18n.js:8
msgid "How are you ?"
msgstr ""

Il trouve bien cette chaine dans notre fichier JS, et plus dans notre fichier PHP. On peut recréer nos traductions, en ouvrant ce fichier dans PoEdit et en recréant rapidement une traduction française. N’oubliez pas de sauvegarder le fichier sous le nom js-i18n-fr_FR.po.

Une fois vos traductions crées, vous constaterez que le message d’instructions généré via PHP est traduit normalement, mais les messages de notre script ne le sont toujours pas. C’est parce que le package wp-i18n et la fonction JavaScript __() ont besoin d’un fichier JSON contenant nos traductions. Il ne peut pas manipuler de .po malheureusement.

On va donc créer un fichier JSON qui va contenir nos traductions. On peut le faire automatiquement avec un peu de magie wp-cli. Dans un terminal, entrez la commande suivante :

wp i18n make-json languages/ --no-purge

La commande va scanner tous les fichiers de traductions du dossier languages/ et va y extraire toutes les chaines présentes dans les fichiers JavaScript pour créer un fichier JSON formatté comme il faut pour que wp-i18n et ses fonctions le comprennent. Le flag --no-purge indique de ne pas supprimer les chaines du .po original.

Maintenant, on va dire à WordPress où chercher les traductions de notre fichier JavaScript. Ajustez la fonction qui charge notre script comme ceci :

add_action( 'wp_enqueue_scripts', 'js_i18n_scripts' );
/**
 * Enqueues a basic script
 */
function js_i18n_scripts(){
    wp_register_script( 'js-i18n', plugin_dir_url( __FILE__ ) . 'js/js-i18n.js', array( 'wp-i18n' ), null, true );
    wp_set_script_translations( 'js-i18n', 'js-i18n', plugin_dir_path( __FILE__ ) . 'languages/'  );
}

La fonction wp_set_script_translation( string $handle, string $domain = 'default', string $path = null ) prends trois paramètres : $handle correspond au script à traduire, $domain au text domain de notre extension, et $path indique à WordPress où trouver les traductions. Ce dernier paramètre est optionnel et s’il n’est pas fourni, il les cherchera chez https://translate.wordpress.org. Donc ici, on dit à WordPress d’aller chercher les traductions du script js-i18n d’abord dans le dossier /languages de notre extension, tout simplement.

Traduire le JavaScript dans WordPress - messages traduits via wp-i18n
Nos messages sont bien traduits !

Et voilà ! Tout fonctionne correctement et vous pouvez maintenant utiliser directement __() dans votre JavaScript !

Pour résumer

Il y a deux moyens de traduire le JavaScript dans WordPress.

Soit on utilise wp_localize_script() au moment où on déclare/charge notre script, et on lui passe un tableau de chaines traduisibles. On garde donc toutes nos chaines dans notre code PHP, et on génère notre fichier .pot et .po normalement. Les chaines traduites sont récupérées et passées à notre script. Notre script fait donc référence aux chaines qu’on lui a passées, mais n’en contient aucune lui-même.

Soit on utilise les fonctions __(), _e() et ses amies fournies par le package wp-i18n, directement dans notre script. Ces dernières fonctionnent exactement comme les fonctions PHP du même nom. Il faut pour cela déclarer le package wp-i18n comme dépendance de notre script (exactement comme vous le feriez pour jQuery), puis extraire nos traductions de notre fichier .po pour les mettre dans un fichier JSON à l’aide de wp-cli, et enfin dire à WordPress où trouver nos traductions à l’aide de set_script_translation().

En terme de workflow, c’est exactement la même chose, à une commande près : on doit utiliser wp i18n make-json en plus. Mais quel confort de pouvoir utiliser __() et _e() directement dans notre JavaScript !

wp_localize_script() reste très utile, car il permet de passer d’autres données à notre script, comme par exemple des URLs générées en PHP, ou des options récupérées à l’aide de get_option().

J’espère que cet article vous a aidé ! N’hésitez pas à vous abonner à ma newsletter et à jeter un oeil au WPCookBook pour des dizaines d’autres tutoriels pratiques de ce type !

Bonne lecture et à bientôt !