Chapitre 18 sur 20

Créer un site multilingue

Laisser un commentaire

Lorsqu’un site prévoit de s’adresser aux internautes de différentes régions, il est avantageux qu’il soit disponible dans plusieurs langues. On parle alors de localisation car, au delà de la traduction, d’autres éléments sont à prendre en compte : devises, unités de mesure…

Dans le cas le plus simple, certaines marques ou certains sites possèdent une installation par pays/région. De ce fait, chaque installation ne gère qu’une seule langue et région. Dans ce cas là, il n’y a rien à faire.

Dans le ca contraire, WordPress n’offre pas nativement l’ensemble de ces fonctionnalités. Cependant, comme toujours, de nombreux plugins peuvent nous venir en aide.

Traduction des thèmes et plugins

WordPress n’est pas nativement prévu pour gérer plusieurs langues à la fois. En revanche, il est conçu dès l’origine pour être utilisé dans n’importe quelle langue. Ainsi, le core et la plupart des thèmes et plugins sont disponibles dans de très nombreuses langues.

Dans le processus de localisation, on distingue en réalité deux étapes :

De ce fait, la première étape dans nos thèmes et plugins est de ne pas écrire du texte en dur s’il est destiné à être traduit. Par exemple, pour un site qui n’a pas vocation à être traduit, vous avez peut-être un template de page 404 qui ressemble à ceci.

<?php get_header(); ?>

<main id="post-404">
    <h1>Pas non trouvée</h1>
    <p>Retourner à la <a href="/">page d'accueil</a> ?</p>
</main>

<?php get_footer(); ?>

Cette page ne sera pas traduisible car ici, nous voyons clairement que du texte en français est directement intégré avec le code du template. Pour rendre l’i18n possible, WordPress dispose de fonctions basées sur l’API gettext de PHP.

Les deux fonctions les plus utilisées sont __('string to translate') et _e('string to translate'). __ retourne la traduction correspondante si elle est trouvée, la chaîne passée en argument dans le cas contraire. _e est un raccourcis qui, au lieu de retourner la traduction, fait directement un echo de cette dernière.

Anglais par convention

En général, par convention, les chaînes de traduction de référence sont en anglais. Cependant, rien ne vous empêche d’utiliser une autre langue. Cela peut être sensé si vous envisagez de localiser votre site dans un second temps.

En l’absence de traduction, les fonctions de traductions retourneront les chaînes dans la langue du site et vous n’aurez qu’à ajouter les fichiers de traductions plus tard pour que votre site soit disponible dans une autre langue.

Sachez cependant que certains caractères multi-bytes peuvent poser problème. Aussi, j’évite cette pratique. Soit le site n’a pas vocation à être traduit, auquel cas je ne m’encombre d’aucune de ces fonctions. Soit il a vocation à être internationalisé (immédiatement ou plus tard), auquel cas je renseigne mes textes en anglais dans le code et je créé un fichier de traduction pour la langue requise.

Ces deux fonctions, comme presque toutes les fonctions de traduction, prennent optionnellement ce que l’on appelle un text domain ou domaine de recherche. Il s’agit du scope de traduction : chaque thème ou plugin peut déclarer son domaine.

<?php get_header(); ?>

<main id="post-404">
    <h1><?php _e('Page not found', 'steroids'); ?></h1>
    <h2>
        <!-- L'url de la home varie selon la languev (/, /fr/ /es/ par ex.) -->
        <a href="<?= home_url() ?>"><?= __('Return home?', 'steroids') ?></a>
    </h2>

</main>

<?php get_footer(); ?>

Vous pourrez vouloir utiliser pour votre thème celui de WordPress ou d’un plugin pour un une traduction donnée et déclarer le votre pour d’autres traductions. Par exemple, il est possible que vous utilisiez certaines traductions de WooCommerce directement dans votre thème. Cela vous évite de traduire à nouveaux des choses qui l’ont déjà été. Cependant, certaines traductions ne vous conviendront pas ou ne seront pas disponibles ailleurs.

<!-- Le header du site peut proposer un lien pour accéder à "mon compte" -->
<!-- Vous savez dans ce cas que WooCommerce est forcément présent -->
<a href="<?= wc_get_page_permalink('myaccount') ?>">
    <?= __('My account', 'woocommerce') ?>
</a>

Ce n’est pas forcément une bonne pratique de se reposer sur des domaines externes si vous développez un thème ou un plugin pour le public. En effet, ça risque de ne pas fonctionner dans tous les cas : il faut que les plugins en question soient présents par exemple.

Déclarer un text domain

Avant de déclarer un text domain, il faut s’assurer que votre thème ou plugin dispose des fichiers de traductions. Ces fichiers sont au nombre de trois :

On commence naturellement par générer le .POT. Pour cela, on s’épaule d’un outil spécifique qui va scanner l’ensemble du code à la recherche de chaînes traduisibles. Le logiciel de référence à cet usage est Poedit. Alternativement, vous pouvez utiliser Loco Translate qui offre les mêmes fonctionnalités sous la forme d’un plugin WordPress.

Sachez que Loco propose aussi un éditeur en ligne. Vous ne pourrez cependant pas générer de fichier .POT de cette manière. WordPress est capable de fonctionner sans .POT mais ce n’est pas standard et de ce fait, ce n’est pas une pratique recommandée.

Lorsque votre fichier .POT est généré, vous pouvez procéder aux traductions à proprement parler. C’est cette partie qui relève à proprement parler de la localisation. Elle ne concerne pas forcément le développeur. Le .POT étant un standard, vous pourriez transmettre ce dernier à différents traducteurs et récupérer les traductions dans différentes locales sous la forme de fichiers .mo et .po.

Une fois que vous avez à minima généré votre fichier .POT, il faut le placer dans un répertoire qui contiendra tous les fichiers de traduction. Qu’il s’agisse d’un thème ou d’un plugin, le nom du répertoire est arbitraire mais il est courant de l’appeler languages/. Ce répertoire est évidemment contenu dans celui du thème ou du plugin.

Charger un text domain dans un plugin

Pour les plugins, le text domain doit forcément correspondre au slug du plugin. Ainsi, si votre plugin s’appelle “My Plugin”, le répertoire racine du plugin s’appellera my-plugin et cela sera aussi le nom du text domain.

Vous le déclarerez simplement dans l’en-tête du plugin conjointement au répertoire des traductions comme dans l’exemple ci-dessous.

/*
 * Plugin Name: My Plugin
 * Author: Plugin Author
 * Text Domain: my-plugin
 * Domain Path: /languages
 */

Voilà, tout est prêt !

Charger un text domain dans un thème

Dans le cas d’un thème, vous disposez d’un peu plus de liberté. Vous êtes libres dans le choix du text domain. On le déclarera dans le functions.php avec la fonction load_theme_textdomain.

load_theme_textdomain('steroids', get_template_directory() . '/languages');

En complément, vous pouvez déclarer le text domain dans le fichier de styles du thème. Cela permet de récupérer les traductions même lorsque le thème n’est pas activé (pour la description du thème par exemple). L’utilité est moindre, c’est pourquoi il s’agit d’une déclaration optionnelle.

/*
* Theme Name: My Theme
* Author: Theme Author
* Text Domain: my-theme
* Domain Path: /languages
*/

Sachez par ailleurs que lorsque vous placez vos fichiers de traductions .mo et .po dans le répertoire des traductions, leur nommage est strict. Leurs noms doivent correspondre aux locales qu’ils représentent.

# Exemple
fr_FR --> Français de France
fr_CA --> Français du Québec

Chaînes variables et complexes

Vous connaissez les deux principales fonctions de traduction et savez comment créer et déclarer les fichiers de traduction. Cela représente déjà la grande majorité des cas.

Cependant, vous aurez des cas plus complexes. Comment traduire une chaîne qui contient une variable ? Par exemple : “Il vous reste 300 points de fidélité”. “300” ne peut pas faire partie de la chaîne car il sera différent à chaque fois. On utilise pour cela printf.

printf(__('You still have %d bonus points', 'steroids'), $points);

Dans certains cas, on se retrouve avec plusieurs variables pour la même chaîne. Étant donné que dans son utilisation classique, le passage des arguments de sprintf est positionnel, il est risqué de l’utiliser de la sorte car leur place peut varier d’une langue à l’autre (exemple le plus courant : les dates et les adresses).

printf__('It\'s %1$s the %2$s', 'steroids'), $month, $day);

printf supporte aussi les arguments numérotés. De la sorte, l’ordre des arguments dans la chaîne traduite n’a aucune incidence.

Une autre problématique assez courante concerne la gestion des pluriels. Si l’on prend un exemple courant : les commentaires. Lorsqu’il n’y a qu’un seul commentaire, “commentaire” est au singulier tandis qu’il est évidemment au pluriel dans les autres cas. On utilise dans ce cas _n.

printf(
    _n('There are %d comment', 'There are %d comments', $comment_counts, 'steroids'),
    $comment_counts
);

_n utilise la première chaîne si le troisième argument est inférieur à 2 et la seconde chaîne dans le cas contraire.

Enfin, certains cas sont ambigus car un mot peut avoir deux sens dans une langue alors qu’il devra être traduit par un tout autre mot dans une autre. Pour ces cas là, on peut ajouter du contexte. C’est la fonction _x qui se charge de cette tâche.

_x('film', 'movie', 'steroids'); // ici film dans le sens d'un film que l'on regarde
_x('film', 'record', 'steroids'); // ici il s'agit du verbe filmer
_x('film', 'camera', 'steroids'); // pellicule

On voit avec l’exemple précédent que le même mot est utilisé en anglais pour trois termes qui seraient différents en français. Il n’y a pas vraiment de norme concernant la rédaction du contexte.

Souvent en anglais les termes sont les mêmes pour le nom et le verbe, on pourrait donc se contenter de préciser (noun et verb) tandis que les cas requérant plus de précisions pourront avoir plusieurs mots comme contexte. Par ailleurs, de la même manière que _e fait directement un echo de la traduction, il existe la fonction _ex.

Formater les nombres

Les nombres peuvent sembler facile de prime abord : en effet, “10” reste 10, quel que soit la langue. Néanmoins, “10 000” s’écrira aussi parfois souvent 10,000 dans le monde anglo-saxon.

Et si nous considérons en plus les décimaux, cela se complexifie d’avantage : 123,45 vs 123.45 vs 123,450 vs 123.450 ? La majorité du monde francophone utilise la version du système international : nous n’utilisons (optionnellement) que les espaces pour séparer les milliers. De ce fait, que le séparateur des décimales soit la virgule (SI francophone) ou la point (SI anglophone), 123,45 et 123.45 se lisent sans ambiguïté.

Pour parer à ces vicariantes, WordPress met à disposition number_format_i18n afin de formater un nombre dans son format adapté à la locale courante.

Formater les dates

Les différences de formatage des dates est tellement connu que le problème semble plus simple. Dans WordPress, les dates issues des fonctions the_date et the_time sont automatiquement ajustées en fonction de la locale utilisée.

On peut toutefois forcer un format particulier en passant en premier argument un masque comme pour les fonctions PHP. En outre, il est aussi possible de déclarer une date arbitraire en passant un timestamp à date_i18n, cette dernière sera automatiquement localisée.

Formater les prix

On ajoute ici encore un peu de complexité car, en plus du séparateur des milliers et des décimales, il faut ici placer la devise (et optionnellement la convertir !).

Ici, cela va être extrêmement simple, on va dans nos templates passer le prix brut sous forme de float à wc_price. Le formatage et l’éventuelle conversion de devise seront gérée directement par WooCommerce et le plugin de traduction.

Dans le cas où vous n’utilisez pas WooCommerce… il faut vous référer à la documentation du plugin que vous utilisez car il n’y a pas de façon standard de procéder.

Vous savez presque tout ce qu’il y a à savoir sur la préparation des fichiers des thèmes et plugins. Il existe quelques autres fonctions de WordPress offrant des fonctions de traduction dans des cas particuliers.

Traduction du JavaScript

Le plus simple est d’éviter de placer du texte qu’il à traduire dans le JavaScript. S’il y a des éléments d’interactions dont le texte géré par le JavaScript, je préfère placer les différents texte dans des data-attributes directement dans le HTML.

De cette manière, les traductions peuvent se gérer depuis PHP et le JS n’a plus qu’à les récupérer avant usage. Dans le cas où il est impératif de gérer ces traduction depuis le JS, sachez que WordPress offre depuis sa version 5.0 un équivalent JavaScript de l’ensemble des fonctions de traduction vues avec PHP.

Traduire le contenu

Votre thème et vos plugins sont prêts. En l’état, ils sont compatibles avec un site WordPress configuré dans n’importe quelle langue. Cependant, ce que nous voulons ici c’est pouvoir changer la langue du site dynamiquement. Pour cela, au delà du code, il faut traduire tout le contenu : textes, images, url…

Plusieurs plugins sont spécialisés dans la traduction. Le plus connu d’entre eux (car c’est un des premier à être sortis) est WPML. Je vous le dis tout de suite, fuyez ! Ce plugin n’est d’après mon expérience pas très stable, les updates sont souvent compliquées et il ralentit considérablement votre site.

Je ne les ai évidemment pas tous testé, mais je peux recommander Polylang sans hésitation. Le plugin permet de facilement traduire l’ensemble du contenu (textes, images, url, custom posts, taxonomies…) de manière SEO friendly, il repose sur les fonctions WordPress natives (pas de tables customs etc) et n’utilise pas de shortcode pour préserver les performances.

Ce que je trouve important est qu’il est simple d’utilisation : l’interface et claire et il fait le job sans être une usine à gaz. De plus, de fabrication française, le support est disponible quand vous en avez besoin.

Gestion des langues dans Polylang
L'interface est simple et puissante

Il existe une version gratuite, une version “Pro” et un pack spécifique pour WooCommerce. La version Pro offre des options supplémentaires dans la gestion des url, s’intègre avec ACF, permet d’activer ou désactiver des langues selon les contenus et offre le support de l’API REST.

Quant à la version WooCommerce, elle permet de traduire les fiches produits (même variables), les emails ainsi que tous les éléments de WC. Par ailleurs, certains plugins WooCommerce sont aussi nativement supportés.

Traduction avec Polylang
Il suffit d'un clic pour traduire une page dans une autre langue

On ne s’en rend en général pas compte jusqu’à être confronté au problème, mais il y a en plus des chaînes contenues dans les templates et du contenu déclaré par l’utilisateur, de nombreuses chaînes déclarées par WordPress, le thème et les plugins qui sont stockées dans la base de données. Heureusement, Polylang permet aussi de traduire ces éléments.

Traduction des strings avec Polylang
Polylang indique clairement d'où proviennent les chaînes

En outre, si vous souhaitez customiser certains comportements pour votre thème ou vos plugins, vous pouvez vous référer à la documentation, laquelle liste les fonctions, filtres et constantes disponibles.

Gérer les devises

Polylang ne gère pas le multi-devise. De ce fait, si vous souhaitez proposer la vente dans plusieurs devises sur votre site, on utilisera un plugin dédié à cela.

Le plugin le plus complet est sans conteste WooCommerce Multi Currency. Il vous permet de renseigner autant de devises que vous le souhaitez et de gérer les conversions manuellement (vous définnissez les prix dans les différentes devises) ou au cours actuel.

Le plugin permet en outre de gérer les règles de formatage, mais aussi les arrondis pour conserver une stratégie commerciale pour les prix sous la dizaine par exemple (9,99, 99,99…). Enfin, vous pouvez choisir d’offrir le paiement dans plusieurs devises (et sélectionner les moyens de paiement disponibles pour chaque devise) ou de reconvertir dans votre devise lors du paiement (les prix en devises étrangères ne sont alors qu’indicatifs).

Sachez qu’il existe aussi une version gratuite de ce plugin. Elle se limite à deux devises, ne permet pas de régler le formatage ni de choisir les devises lors du checkout et les passerelles de paiement disponibles.

Gérer les unités

De la même manière que pour les prix, les unités spécifiées au seins de vos contenus devront être gérées directement avec le contenu. Cependant, si vous utilisez des unités de taille et de poids sur vos fiches produits par exemple, WooCommerce fournit deux fonctions permettant de les convertir automatiquement. Il s’agit de wc_get_dimension et wc_get_weight.

// Convertit 12cm en pouces
wc_get_dimension(11, 'in', 'cm');

// Convertit 75kg en livres
wc_get_weight(75, 'lbs', 'kg');

Le troisième argument est optionnel pour les deux fonctions. S’il est omis, l’unité de départ sera celle par défaut du site. Cette fois vous êtes parés pour affronter la globalisation de votre site !

Commentaires

Rejoignez la discussion !

Vous pouvez utiliser Markdown pour les liens [ancre de lien](url), la mise en *italique* et en **gras**. Enfin pour le code, vous pouvez utiliser la syntaxe `inline` et la syntaxe bloc

```
ceci est un bloc
de code
```