Réaliser un thème multilingue avec WPML

Publié le 3 décembre 2013 Les thèmes de WordPress
Willy Bahuaud

Lorsque l’on souhaite réaliser un site multilingue sur WordPress, on a le choix entre quelques extensions : qTranslate, Xili language, Multilingual Press… et WPML. Cette dernière se détache vraiment du lot.

Ce plugin, actuellement en version 3.0.1, existe depuis 2009, bénéficie d’une large communauté et d’un support réactif.
Sa licence complète coûte 79$, un prix tout à fait raisonnable et qui s’amorti rapidement au fil des projets.

S’il est testé et approuvé, il faut tout de même comprendre son fonctionnement pour concevoir des thèmes compatibles.

Cet article va vous donner les bases nécessaires pour appréhender la bête et éviter les écueils…

wpml wordpress
Concevoir un thème WordPress multilingue

Fonctionnement de WPML et respect des standards

Le plugin, lors de son installation, va créer quelques tables dans votre base de données. L’une d’entre elle (*icl_translations) va lui servir à stocker les correspondances entre les articles, taxonomies, et éléments des différents langages. Par exemple, on y trouvera ce genre d’informations :

La page d’ID 90 est la version anglaise de la page d’ID 4, qui elle est française, et appartient au groupe de traduction 122

Lors de chaque requête WordPress, WPML va contrôler dans cette table que l’objet demandé correspond bien à la langue actuelle, ou bien trouver sa correspondance. Ensuite il utilisera des hooks, tels que posts_where, pour filtrer le résultat de la query et ne conserver que les contenus de la langue active.

Un des rares points noirs de WPML est d’ailleurs l’utilisation de cette table, puisque l’interroger augmente considérablement le nombre de requêtes. Il est donc vivement conseillé d’utiliser un système de cache avec ce plugin.

Faire de vraies requêtes

Une partie des problèmes que vous rencontrerez avec WPML sont probablement dûs à des requêtes « exotiques ».

L’extension utilise des hooks pour modifier le comportement de WordPress ; utiliser la classe WP_Query est le meilleur moyen de garantir son bon fonctionnement (les objets retournés seront bien filtrés). Vous pourrez utiliser les ids des éléments, ou les slugs des post types, mais jamais les noms (qui seront traduits).

Donc bien évidemment, il faut bannir les requêtes SQL pour chopper des contenus (LOL). Il faut aussi éviter d’utiliser get_post() et get_posts().

Par défaut, la fonction get_posts récupère tous les contenus, sans appliquer de filtres. Si vous avez WPML d’activé et que vous utilisez cette fonction, vous récupérerez non seulement l’article souhaité, mais également toutes ces traductions.

A mon avis, on peut très bien se passer de get_posts, et lui préférer l’emploie d’une vraie WP_Query. Cependant, si vous n’arrivez pas à vous passer de get_posts, sachez qu’il faut simplement lui passer l’argument suppress_filters à false pour que le plugin puisse faire son job.

Une structure logique de template

D’autres cas où le plugin risque de vous lâcher se présenteront si la structure de vos templates est bizarre.

Utiliser la template hierarchy, c’est du solide. Certains thèmes premiums utilisent des templates de page personnalisés pour afficher des archives de post type. Sur ceux-là WPML pourra se perdre… De même si vos boucles imbriquent d’autres boucles…

Si votre thème est logique, l’extension n’aura aucun mal à se greffer.

Les fonctions utiles de WPML

Ce plugin de traduction mets à disposition des développeurs quelques « template tags » afin de simplifier l’intégration de WPML dans leurs thèmes.
Je vais vous expliquer le fonctionnement des plus utiles de ses fonctions.

Pensez juste que WPML ne sera pas forcément installé lors de l’activation de votre thème ; il sera donc nécessaire de tester leur support via :

function exists
<?php
if( function_exists( 'icl_get_languages' ) ) { 
    //... Do stuff
}

Icl_object_id : récupérer l’ID d’un contenu traduit

Parmi les fonctions indispensables de WPML, celle qui me semble la plus importante est icl_object_id. Elle permet de récupérer l’ID d’un article selon sa langue.

Il faut l’utiliser dans deux cas de figures:

  • si vous connaissez l’ID d’un contenu dans la langue par défaut, et que vous avez besoin de récupérer l’ID de sa traduction
  • si vous avez accès à l’ID d’un contenu dans la langue courante et que vous souhaiter retrouver l’ID du post dans une certaine langue.

Cette fonction accepte jusqu’à 4 arguments, dans l’ordre :

  1. l’id du contenu (obligatoire)
  2. le type de contenu, si vous ne le connaissez pas vous pourrez probablement le récupérer dans la global $post (obligatoire)
  3. un argument booléen (false par défaut) indiquant s’il faut retourner l’ID de l’élément dans la langue par défaut, si la traduction n’existe pas
  4. langue souhaitée (facultatif) le code langue (deux lettres) de l’ID du contenu que vous souhaitez récupérer

L’utilité de cette fonction est de faire des liens localisé vers les contenus. Elle évite de faire un get_permalink sur un icl_object_id. Personnellement, je préfère être maître de mes liens et je l’emploie rarement.

Cela ne veut pas dire qu’elle ne vous sera pas utile. Voici ses paramètres :

  1. l’id du contenu sur lequel vous souhaitez diriger le lien
  2. le type de contenu ciblé (page / post / category / tag)
  3. le texte du lien, il peut contenir du HTML
  4. un tableau d’arguments, qui seront ajoutés en get à l’URL du lien
  5. l’ancre du lien, si vous avez besoin de le faire pointer vers un élément précis de la page de destination
  6. un booléen pour indiquer si l’on souhaite imprimer le lien, ou juste le retourner (true par défaut)
  7. un booléen (true par défaut) pour retourner le contenu original si la traduction est manquante

Icl_get_languages : une liste des langues disponibles

WPML fournit d’office un sélecteur de langue que l’on peut afficher grâce à la fonction do_action(‘language_switcher’). Il peut être grossièrement personnalisé via l’administration, mais ne s’adaptera pas forcément à votre maquette.

J’ai donc pris l’habitude de concevoir mon propre language switcher en récupérant les informations nécessaires via la fonction icl_get_languages. Cette fonction retourne un tableau PHP des traductions disponibles pour le contenu courant.

Elle accepte un tableau en argument, qui peut contenir les clefs suivantes :

  • skip_missing (booléen) indique s’il faut tout de même faire apparaître les langues où le contenu n’est pas traduit
  • orderby (id / code / name / custom) sert à choisir de quelle manière vont être ordonné les langues
  • order (asc ou desc) en corrélation avec orderby, il permet de déterminé un ordre d’affichage croissant ou décroissant
  • link_empty_to (str) si skip_missing est défini sur false, permet de choisir le lien de substitution (par défaut, il renverra vers la home)

Cette fonction renvoie donc un tableau qui prend la forme suivante :

Array
(
 [0] => Array
  (
   [id] => 1
   [active] => 1
   [native_name] => English
   [missing] => 0
   [translated_name] => English
   [language_code] => en
   [country_flag_url] => http://example.com/wpmlpath/res/flags/en.png
   [url] => http://example.com/about
  )

 [1] => Array
  (
   [id] => 4
   [active] => 0
   [native_name] => Français
   [missing] => 0
   [translated_name] => French
   [language_code] => fr
   [country_flag_url] => http://example.com/wpmlpath/res/flags/fr.png
   [url] => http://example.com/fr/a-propos
  )

 [2] => Array
  (
   [id] => 27
   [active] => 0
   [native_name] => Italiano
   [missing] => 0
   [translated_name] => Italian
   [language_code] => it
   [country_flag_url] => http://example.com/wpmlpath/res/flags/it.png
   [url] => http://example.com/it/circa
  )
)

Voici un exemple pour réaliser un sélecteur de langue personnalisé :

Custom language switcher
<?php
$languages = icl_get_languages('skip_missing=1');
foreach( $languages as $l )
  if( $l['active'] ) 
  	echo '<div class="flag"><img src="' . $l['country_flag_url'] . '" alt="voir l\'article en ' . $l['translated_name'] . '"></div>';
foreach( $languages as $l )
  if( ! $l['active'] ) 
  	echo '<a href="' . esc_url( $l['url'] ) . '" class="flag"><img src="' . $l['country_flag_url'] . '" alt="voir l\'article en ' . $l['translated_name'] . '"></a>';

Supprimer les CSS et JS inutiles

WPML charge systématiquement des fichiers css et javascript en front, pour permettre l’utilisation de certains composants (via shortcodes).
Si vous pensez ne pas en avoir l’usage, vous pouvez désactiver l’enqueue de ces éléments, en assignant la valeur true à certaines constantes.

Il suffit d’ajouter ces lignes dans le fichier wp-config.php :

  • define( ‘ICL_DONT_LOAD_NAVIGATION_CSS’, true ) permet de désactiver l’enqueue des styles du breadcrumb de l’extension
  • define( ‘ICL_DONT_LOAD_LANGUAGE_SELECTOR_CSS’, true ) désactive le chargement des styles du language switcher par défaut
  • define( ‘ICL_DONT_LOAD_LANGUAGES_JS’, true ) empêche le chargement du javascript du language switcher (dropdown)

Les informations sur la langue courante

Le plugin met à disposition quelques constantes qui permettront d’accéder aux informations de la langue active.

  • ICL_LANGUAGE_CODE renverra le format de la langue au format iso 639-1 (deux lettres)
  • ICL_LANGUAGE_NAME permet d’obtenir le nom de la langue courante, dans son propre language (français, par exemple)
  • ICL_LANGUAGE_NAME_EN retourne le nom de la langue, en anglais

Traductions des contenus statiques

WPML offre une interface simple pour gérer vos contenus dynamiques, comme vos contenus statiques. Cependant, il sera tout de même nécessaire d’utiliser les fonctions de localisation habituelles sur les « mots en dur » de votre thème.

Les différentes fonctions de traduction

Voici un petit récapitulatif de ces fonctions…

__( ‘chaine-a-traduire’, ‘domaine’ )

C’est la plus basique des fonctions de traduction. Elle ne prend que deux paramètres : la chaîne de texte à traduire, puis le domaine de traduction. Elle retournera votre texte traduit, tout simplement.

Le domaine de traduction désigne le fichier po qui gère les traductions de vos templates. Par défaut je vous incite à le nommer comme votre thème.

Il peut ne pas être renseigné si la traduction du texte est déjà disponible dans le fichier po de WordPress

_e( ‘chaine-a-traduire’, ‘domaine’ )

Cette fonction fait exactement la même chose que la précédente, à un détail prêt : la texte retourné est imprimé directement. Il n’y a donc pas besoin de faire de echo.

_x( ‘chaine-a-traduire’, ‘contexte’, ‘domaine’ )

Celle-ci fonctionne comme la fonction __(), mais permet de contextualiser la chaine à traduire.

Un texte peut avoir besoin d’être traduit de plusieurs façons différentes selon le contexte de la phrase. C’est à cela que sert le deuxième paramètre. Par exemple la chaîne title peut être traduit avec des mots différents selon qu’elle désigne le titre d’un article, ou bien celui d’une image. le contexte permet de cibler ces versions.

_n( ‘chaine-singuliere-a-traduire’, ‘chaine-pluriel-a-traduire’, quantite, ‘domaine’ )

Certains textes sont susceptibles de s’accorder en nombre avec un élément extérieur à la chaîne. L’exemple le plus fréquemment rencontré est celui des commentaires : s’il y a un seul commentaire, on aura besoin d’écrire « commentaire« , alors que s’il y en a plusieurs ce sera « commentaires« .

Le paramètre quantité doit être un nombre entier.  Les deux premiers arguments désignent respectivement la chaîne à traduire, au singulier et au pluriel.

wp_sprintf( __( ‘je suis %s’, ‘domaine’ ) , $nom )

Parfois, vous aurez besoin de placer des variables à l’intérieur de textes statiques. La fonction wp_sprintf est faite pour ça. Elle prend au moins deux paramètres :

  1. un texte, ou une fonction de texte à traduire (c’est ce qui nous intéresse ici). Cette chaîne doit contenir au minimum un signe (%s, %d) qui sera remplacé par les autres paramètres de wp_sprintf
  2. les variables qui viendront, dans l’ordre, remplacer les signes dans le texte traduit

Les signes peuvent être :

  • %s une simple chaîne de caractère
  • %d un nombre décimal
  • %f un nombre à virgule
  • %1$s, %2$s, %3$s… cette notation permet de modifier l’ordre des paramètres dans la chaîne (par exemple, %2$s retournera le troisième argument de la fonction)

En fait, wp_sprintf est un simple portage WordPress de la fonction sprintf de PHP, la différence est que la première peut être hookée. C’est pratique ;-P

À partir du moment où tous vos textes statiques sont contenus dans ces fonctions, votre thème pourra être intégralement traduit.

Il y a deux possibilités pour ça :

  • soit utiliser un fichier po, généré à partir d’une librairie pot, en utilisant le logiciel poedit. C’est une méthode stable mais pas forcément simple à prendre en main au début.
  • soit utiliser l’extension fournit par WPML : WPML String Translations

Pour la deuxième méthode, il faut activer ce bébé-plugin dans l’administration de WordPress, puis se rendre dans l’onglet « Localisation du thème et des modules d’extension » dans l’onglet WPML.

Puis dans la section « Chaînes du thème » il faut cliquer sur « analyser le thème des chaînes » (oui je sais, c’est pas très français comme phrase ^^). Votre thème va ensuite être scanné et les textes traductibles vont être indexés puis mis à disposition dans l’onglet « traduction de chaîne » du menu de WPML.

Rendez-vous y, puis sélectionner votre domaine dans la liste déroulante. Vous pouvez maintenant traduire ce que vous souhaitez. N’oubliez pas de cocher « traduction achevée » pour chaque expression traduite.

Vos textes en dur sont maintenant traduits en front office.

Traduire les slugs des Post types

Jusqu’à très récemment, il était très laborieux de traduire le slugs des types de contenu personnalisés. La seule astuce viable était celle de ScreenFeed.

Mais depuis les dernières versions c’est maintenant possible. Tout se passe dans la déclaration du post type.

Traduire slug post types
<?php
register_post_type( 'book', array(
    'public' => true
    'has_archive' => true,
    'slug' => _x( 'livres', 'URL slug', 'domain' )
) );

Le deuxième paramètre doit rester tel quel. Après avoir traduit le slug via le menu de traduction de chaîne, il ne faudra pas oublier de rafraîchir vos permaliens.

Hooker les textes en option

Certains thèmes, assez mal conçus, vous proposerons d’enregistrer du contenu textuel dans des options de WordPress. Le problème est que les textes saisis ici ne pourront être traduits.

Il y a tout de même une méthode : hooker la récupération de ces options. Cette méthode désactive, par contre, l’édition de ces chaînes via l’administration de WordPress, mais il faut savoir ce que l’on veut…

Traduire une option
<?php
add_filter( 'option_option_a_traduire', 'my_option_a_traduire' );
function my_option_a_traduire( $text ) {
	return __( 'le nouveau texte', 'domain' );
}

Traduire les autres contenus

Pour tous les autres contenus du site, l’utilisation du plugin est relativement instinctive.

Dans les pages de listes, vous verrez des drapeaux qui vous permettront de créer les différentes versions d’un même contenu. Vous aurez également la possibilité de régler les détails de traduction via des meta-box, dans les pages d’édition de ces pages (langue, article original, localisation des fichiers attachés…).

liste d'article WPML
Vue de la liste d’articles

Choix des contenus à traduire

Par défaut, seuls vos articles et vos pages pourront être traduits. Si vous souhaitez versionner vos catégories, tags, post types et autres taxonomies, il faudra vous rendre dans le menu « gestion de traduction » puis dans l’onglet « Configuration du contenu multilingue » (pour activer cet écran, il faut activer le mini-plugin « WPML Translation Management« .

Vous constaterez que cette page est assez longue. C’est normal…

Ici vous pouvez configurer quels sont les contenus qui vont être traduits, mais aussi quelles meta-données pourront l’être, ou bien quels contenus seront synchronisés (copiés automatiquement).

Il faut donc s’assurer ici que les metadonnées de WordPress SEO puissent être traduites, afin de garantir un référencement optimal.

Depuis la version 3, on peut aussi traduire les taxonomies en passant directement par le sous-menu « Traduction de taxonomie« …

Traduction des menus

Le système de traduction des menus est assez simple à utiliser. Chaque menu se voit affublé d’une langue et d’une boite « ce menu est une traduction de…« . Lorsque l’on commence à traduire les menus, il n’est pas rare que des bugs les fassent sauter vers d’autres langages. Il faut garder un oeil dessus donc ;-)

WPML  associe les menus entre eux, indépendamment des « thèmes locations ». On peut donc utiliser la fonction wp_nav_menu avec l’attribut theme_location ou l’attribut menu (mais la bonne pratique est d’utiliser uniquement theme_location de façon que l’utilisateur puisse le changer sans toucher le thème).

Un petit détail reste perturbant ici : les éléments que l’on peut déposer dans les menus traduits s’affichent dans cette langue. Il faut donc comprendre le néerlandais pour concevoir un menu néerlandais

Pour palier à ce problème, voici ces quelques lignes de code :

Translate menu items
add_filter( 'wp_setup_nav_menu_item', 'wpml_menus' );
function wpml_menus( $menu_item ) {
	if( function_exists( 'icl_object_id' ) && ICL_LANGUAGE_CODE != 'fr' ) {
		if( $menu_item->type == 'post_type' ) {
			$menu_item_title = get_the_title( icl_object_id( $menu_item->ID, $menu_item->post_type, true, 'fr' ) );
			if( $menu_item_title )
				$menu_item->post_title = $menu_item->post_title . ' (' . esc_html( $menu_item_title ) . ')';
		}elseif( $menu_item->type == 'taxonomy' && ! isset( $menu_item->post_title ) ) {
			$menu_item_term = get_term( icl_object_id( $menu_item->term_id, $menu_item->object, true, 'fr' ), $menu_item->object );
			if( $menu_item_term )
				$menu_item->title = $menu_item->title . ' (' . esc_html( $menu_item_term->name ) . ')';
		}
	}
	return $menu_item;
}

Vous pouvez les déposer dans un mu-plugin, ou bien dans le functions.php de votre thème. Grâce au hook wp_setup_nav_menu_item, on vient modifier l’affichage des post type et taxonomie dans les boîtes d’ajout d’élément de menu, et l’on y adjoint la traduction française.

On s’y retrouve tout de même mieux ;-)

Extensions compatibles

WPML est un des plugins premiums les plus utilisés. Il est donc relativement bien supporté par la plupart des extensions WordPress. Vous trouverez d’ailleurs sur le site une liste non-exhaustives des plugins compatibles.

Par contre je sais qu’il rencontre des problèmes avec « redirections », qui met en place automatiquement des redirections 301 vers les contenus originaux. Je vous déconseille donc d’utiliser les deux simultanément (et c’est dommage car ce plugin est bien pratique pour le référencement).


Multilingual WordPress

Voilà, vous avez maintenant davantage de cartes en main pour aborder et utiliser l’extension WPML. N’hésitez pas à partager vos exemples de réalisations !

Willy Bahuaud

Développeur freelance, spécialiste de WordPress

8 Commentaires

Julio Potier Le 03 décembre 2013 à 10h08

Willy, l’homme qui dormait 7 mn par jour.
hahahaha 3 articles, 3 monstres, 3 guides, 3 favoris. OMG j’utilise WPML et… ha non en fait j’utilisais pas :|
Merci pour ce gros tuto, je vais mettre de suite les constantes en place !
Et qu’ajouter sinon « a relire » encore et toujours.
ps : pour wp_sprintf() on a maintenant %l (L minuscule) qui permet de passer un array de chaines pour les transformer en « item1, item2, item3, et item4 » avec le « et » pour le dernier item, et traduit etc
voilà :

Greg Le 03 décembre 2013 à 16h00

Encore toi ?! ;)

J’utilise aussi WPML parfois pour mes clients, mais ça tu t’en doutais. Le truc qui m’a toujours fait ch*** c’est le code langage en iso 639-1 (fr) alors que WordPress utilise une autre norme (fr_FR).
Damn, rien que pour retrouver la correspondance ça coute 5 requêtes o_O (oui 5 à cause du « coefficient multiplicateur de requêtes » de WPML -_-‘).

Question concernant register_post_type() :
« Le deuxième paramètre doit rester tel quel. »
Le 2ème param de quoi, de register_post_type() ou du _x() ? Si c’est celui de register_post_type() alors là ça pose problème puisque si je ne peux pas toucher à ‘has_archive’ ça veut dire que si WPML n’est pas installé j’ai plus le droit de personnaliser le slug de l’archive ? o.O
Bon ok, avec le param ‘slug’ tu peux traduire le slug de l’archive apparemment, mais qu’en est-il de celui de la single ? :s (merci pour le lien au passage, je ne me souvenais même plus avoir fait cet article :D).

Merci pour l’article :)

Willy Bahuaud Le 03 décembre 2013 à 16h31

@Julio Merci merci :-) mais en fait je dors bien grassement ^^
Oui je sais que tu n’es pas trop amené à utiliser WPML. Si à l’occasion tu as besoin, j’espère que l’article pourra te filer une ou deux astuces.

Et merci pour sprintf !! Je ne savais pas :-D

@Greg oui, encore moins :-P
Alors la norme iso 639-1, je n’ai jamais compris pourquoi WPML fait ça non plus -__- il eut-été tellement plus simple d’utiliser get_locale()… mais bon… soulignons déjà qu’il y a du progrès dans leurs dernières versions (plus de notices en front, un truc de malade).

Pour le register_post_type, je parlais du deuxième argument de _x() : URL slug.
Quant au slug de la single, il se cale sur celui de l’archive :-)

Je suis tombé sur ton article juste ce mois-ci (Merci au passage pour ton blog, encore, c’est une mine d’or), et c’était l’occasion d’en parler. Ils ont mis pas mal de temps à régler ce détail de slug, mais je n’ai plus rencontré ce genre de soucis depuis :-)

Greg Le 03 décembre 2013 à 16h37

« Quant au slug de la single, il se cale sur celui de l’archive :-) »
Ha pas bon, on ne peut pas les différencier alors :(

    Willy Bahuaud Le 03 décembre 2013 à 17h21

    @Greg j’avoue que je n’ai jamais eu ce genre de besoin… et à mon avis WPML ne permet pas d’aller jusque là (et donc il faudra alors user de ta méthode)

Christiane Lagacé Le 09 décembre 2013 à 21h24

Cet article répond tout à fait à ce que je cherchais. Je suis déjà en processus de tester Stella, Multisite Languate Switcher et Bilingual Linker. J’ajoute sans tarder WPML à ma liste.

Merci !

Christiane Lagacé

Grégory Le 09 juin 2014 à 11h07

Bonjour,
merci pour ces quelques astuces pour WPML. Ce plugin est incontestablement le plus efficace que j’ai utilisé pour créer des sites multilingues.

En revanche, pour migrer ma DB locale vers le serveur de production, j’ai régulièrement un problème qui corrompt toute ma configuration WMPL… Il perd la table de traduction et je me retrouve donc avec tout mon contenu dans ma langue par défaut. N’ayant aucune envie de relier manuellement mes contenus ENFR je me suis renseigné sur le support de WPML.

Le point positif, c’est qu’ils répondent plutot rapidement. En revanche, leur procédure standard pour palier à ce bug de migration est plutot bizarre (Ils demandent un accès à notre ftp et à l’admin de WP).

En insistant un peu, ils m’ont fournit document décrivant la procédure (artisanale) à suivre pour régler le soucis de migration de la DB.

Malgré ce petit soucis, je pense que ce plugin est un bon investissement !

David Lesprit Le 21 juillet 2014 à 13h56

Bonjour, j’ai une petite question en ce qui concerne l’utilisation de WPML : si on se trouve dans le cas d’utilisation d’un child-theme comment ca se passe? Par defaut, WPML va chercher un fichier dans le theme parent… Or si l’on souhaite utiliser un fichier propre au theme enfant, WPML semble l’ignorer….. Il serait bien pratique pourtant de pouvoir dissocier les chaines a traduire du theme enfant de celles du theme parent… Y’a t’il une astuce que j’ignore?

Gros merci pour le taf que represente ce site, c’est juste une mine d’or :)

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *