wp post list --post_type=page --format=csv : cette commande, lancée après une première migration de contenu Joomla vers WordPress, a révélé un problème que le script de migration initial n’avait pas anticipé : toutes les pages étaient bien présentes, mais aucune n’était reliée à sa traduction dans l’autre langue.
Joomla multilingue associe les contenus traduits via une table interne, #__associations, qui relie des identifiants de contenu à une clé de groupe partagée. Polylang, de son côté, stocke ses associations dans une taxonomie cachée, post_translations, dont le format diffère complètement. Une migration de contenu qui ignore cette différence produit un site bilingue où chaque langue existe, mais où plus aucune page ne pointe vers son équivalent traduit.
Étape 1 : exporter la cartographie des associations Joomla
Avant tout import, il faut extraire depuis la base Joomla la correspondance entre chaque contenu et son groupe de traduction, en conservant l’identifiant original de chaque article :
SELECT a.id AS joomla_id, a.language, ag.key AS groupe_traduction
FROM jos_content a
JOIN jos_associations ag ON ag.id = a.id AND ag.context = 'com_content.item'
ORDER BY ag.key;
Ce résultat, exporté en CSV, devient la table de correspondance de référence pour toute la suite de la migration : chaque ligne indique quel identifiant Joomla appartient à quel groupe de traduction, et dans quelle langue.
Étape 2 : importer le contenu brut sans les associations
Le contenu lui-même s’importe d’abord sans se préoccuper des liens de traduction, via un script s’appuyant sur wp_insert_post(), en conservant systématiquement l’identifiant Joomla d’origine dans un champ personnalisé temporaire :
update_post_meta( $nouvel_id_wp, '_joomla_id_origine', $joomla_id );
Cette étape intermédiaire est volontairement séparée de la reconstruction des associations : mélanger les deux opérations dans un seul script rend le débogage beaucoup plus difficile en cas d’erreur partielle sur un lot d’articles.

Étape 3 : reconstruire les associations dans Polylang
Une fois tout le contenu importé, un second script parcourt la table de correspondance exportée à l’étape 1 et appelle la fonction officielle de Polylang pour lier les traductions entre elles :
foreach ( $groupes_traduction as $groupe => $ids_par_langue ) {
pll_save_post_translations( $ids_par_langue );
// $ids_par_langue est un tableau du type array( 'fr' => 123, 'en' => 456 )
}
La fonction pll_save_post_translations() est la voie officiellement documentée par Polylang pour créer ces liens par programmation, plutôt que de manipuler directement la taxonomie post_translations, dont la structure interne n’est pas garantie stable d’une version à l’autre de l’extension.
Étape 4 : vérifier l’intégrité des associations
Un contrôle final compare le nombre de groupes de traduction Joomla d’origine au nombre de relations effectivement créées dans Polylang, afin de repérer d’éventuels groupes orphelins, souvent causés par des contenus supprimés côté Joomla mais restés présents dans la table d’associations :
- compter les groupes de traduction dans l’export Joomla d’origine
- compter les paires liées effectivement présentes dans Polylang après import
- lister les écarts et vérifier manuellement chaque cas isolé avant de considérer la migration terminée
Sur la migration décrite ici, ce contrôle a permis d’identifier une trentaine de groupes orphelins sur plusieurs centaines, tous liés à des articles Joomla archivés depuis longtemps mais jamais réellement supprimés de la table d’associations. Leur traitement manuel, un par un, a représenté la dernière étape avant de considérer le catalogue traduit comme fiable.
Ne jamais manipuler directement les tables internes d’une extension comme Polylang : les fonctions publiques comme pll_save_post_translations existent précisément pour absorber les changements de structure interne d’une version à l’autre.
En résumé
La difficulté d’une migration Joomla vers Polylang ne réside pas dans le transfert du contenu lui-même, mais dans la reconstruction fidèle des liens entre langues, deux systèmes qui modélisent cette relation de façon complètement différente. Séparer clairement l’import du contenu et la reconstruction des associations, avec une table de correspondance intermédiaire, évite de perdre silencieusement ces liens au passage.