WPML a la réputation d’être une boîte noire. On installe l’extension, on active quelques langues, et des versions traduites de nos pages apparaissent comme par enchantement dans le tableau de bord. Beaucoup de développeurs s’arrêtent là, sans jamais regarder ce qui se passe réellement dans la base de données. C’est une erreur : le jour où un client demande pourquoi telle page ne se traduit pas, ou pourquoi un import massif casse les liens entre langues, il faut savoir où chercher.
Cet article détaille l’architecture concrète de WPML : comment il stocke les traductions, comment il relie les contenus entre eux, et où se trouvent les chaînes qui n’appartiennent pas au contenu principal (options de thème, textes de widgets, libellés d’extensions). L’objectif n’est pas de remplacer la documentation officielle, mais de donner les repères qui manquent souvent aux développeurs qui découvrent l’extension sur un projet existant.
Un post par langue, reliés par une table pivot
Contrairement à une idée reçue, WPML ne stocke pas une page en plusieurs langues dans une seule ligne de wp_posts. Chaque traduction est un post WordPress complet et autonome, avec son propre ID, son propre post_content, ses propres métadonnées. Une page d’accueil traduite en anglais et en espagnol correspond donc à trois lignes distinctes dans wp_posts : l’original et deux traductions.
Le lien entre ces trois posts est assuré par la table icl_translations, ajoutée par WPML lors de son activation. Chaque ligne y associe un element_id (l’ID du post), un element_type (post_page, post_product, etc.), une langue, et un trid commun à toutes les traductions d’un même groupe. C’est ce trid qui permet à WPML de savoir que trois posts différents sont en réalité « la même page » dans trois langues.
Pourquoi cette approche plutôt qu’un champ multilingue
On pourrait imaginer un système où une seule ligne de wp_posts contiendrait un champ sérialisé avec les trois versions du texte. C’est d’ailleurs l’approche retenue par certains plugins de traduction plus légers. WPML a fait le choix inverse pour une raison simple : conserver la compatibilité maximale avec l’écosystème WordPress. Les extensions tierces, les thèmes, les requêtes WP_Query personnalisées continuent de fonctionner sans adaptation, puisque chaque traduction reste un post normal, indexable, éditable et interrogeable comme n’importe quel autre.
Cette solidité a un coût : la duplication. Un site en cinq langues avec cinq cents articles peut afficher jusqu’à deux mille cinq cents lignes de wp_posts pour ce seul type de contenu, sans compter les révisions. C’est un point à garder en tête lors du dimensionnement d’un hébergement, surtout si le client ajoute une langue en cours de vie du site.
La table icl_translations en détail
Voici les colonnes qui comptent vraiment lorsqu’on doit déboguer un problème de traduction manquante ou mal liée :
translation_id: identifiant propre à la ligne WPML.element_type: type précis de l’élément (post, taxonomie, menu…).element_id: ID de l’élément dans sa table native (wp_posts,wp_terms…).trid: identifiant du groupe de traduction, partagé entre toutes les langues d’un même contenu.language_code: la langue de cette ligne précise.source_language_code: la langue d’origine à partir de laquelle la traduction a été créée.

Un cas fréquent en support : un post existe bien en anglais et en français, mais WPML affiche « pas de traduction » sur le tableau de traduction. Neuf fois sur dix, la ligne correspondante dans icl_translations a été supprimée ou porte un trid incohérent, souvent après un import via un outil qui ne respecte pas les métadonnées WPML (migration brute de base, duplication manuelle d’un post via un plugin generique). La correction passe alors soit par l’interface de gestion des traductions de WPML, soit par une requête SQL ciblée pour recréer la liaison — à faire uniquement après sauvegarde complète de la base.
String Translation : traduire ce qui n’est pas un post
Le contenu éditorial n’est qu’une partie du site. Le nom du thème affiche des libellés codés en dur ou passés par __(), les widgets contiennent du texte libre, les options de thème (slogan, texte du bouton d’appel à l’action) ne passent pas toujours par le système de traduction de contenu. C’est là qu’intervient le module String Translation de WPML, alimenté par la table icl_strings et son pendant icl_string_translations.
Concrètement, WPML scanne les chaînes correctement internationalisées avec les fonctions de l’API i18n de WordPress (__(), _e(), esc_html__()) et les rend disponibles dans l’interface WPML → String Translation. Une chaîne mal internationalisée dans le thème — un texte écrit directement dans le HTML sans passer par ces fonctions — n’apparaîtra jamais dans cette liste, quel que soit l’effort de configuration. C’est la cause numéro un des « ce bouton ne se traduit pas » remontés par les clients.
Enregistrer manuellement une chaîne non détectée
Pour les textes générés dynamiquement (par exemple un texte construit dans une fonction PHP personnalisée), il faut enregistrer la chaîne explicitement :
<?php
$texte = 'Livraison gratuite dès 50 € d\'achat';
do_action( 'wpml_register_single_string', 'mon-theme', 'Bandeau livraison', $texte );
$texte_traduit = apply_filters( 'wpml_translate_single_string', $texte, 'mon-theme', 'Bandeau livraison' );
echo esc_html( $texte_traduit );
Cette paire d’actions et de filtres (wpml_register_single_string pour l’enregistrement, wpml_translate_single_string pour la récupération traduite) est la porte d’entrée la plus fiable pour intégrer un développement sur mesure au système WPML, sans dépendre de la détection automatique.
Les pièges classiques d’une architecture méconnue
Trois situations reviennent régulièrement en maintenance :
- Un développeur duplique un post via une requête SQL directe pour gagner du temps : le nouveau post n’a pas de ligne dans
icl_translationset n’est jamais reconnu comme traduction. - Une extension tierce crée ses propres types de contenu sans déclarer d’
element_typecompatible : WPML ne peut pas proposer de traduction pour ce contenu tant qu’il n’est pas explicitement enregistré viaicl_taxonomy_addou l’API idoine. - Une migration de base de données via un simple export/import SQL casse les
tridsi les ID de post changent entre l’environnement source et l’environnement cible, sans passer par un outil qui préserve les relations (WPML propose ses propres outils d’export pour ce cas).
Sur nos projets, la première chose que nous vérifions avant toute intervention sur un site WPML existant, c’est l’état de la table
icl_translations: un simpleSELECT COUNT(*)comparé au nombre de posts publiés donne déjà une idée de la cohérence du système.
En résumé
WPML repose sur un principe simple mais rigide : chaque traduction est un post autonome, relié aux autres par une table pivot. Cette architecture garantit une compatibilité maximale avec l’écosystème WordPress, au prix d’une duplication de données qu’il faut anticiper dès le dimensionnement du projet. Comprendre le rôle de icl_translations et du module String Translation permet de diagnostiquer en quelques minutes des problèmes qui, sans ces repères, peuvent transformer une simple traduction manquante en session de débogage de plusieurs heures.