vendredi 25 septembre 2026

À propos

Contact

Extensions

Deux traductions pour la même chaîne : cache et locale mal isolés

Un visiteur français voit soudain un bouton en anglais, sans raison apparente. Le coupable : un cache partagé qui ignore la langue de la requête en cours.

Par Clément Hadrot • 5 mai 2025 • 5 min de lecture • Aucun commentaire
Deux traductions pour la même chaîne : cache et locale mal isolés

Un site multi-boutique que nous maintenons servait ses pages produit dans trois langues différentes selon le domaine visité, chaque domaine correspondant à une locale WordPress distincte via une extension maison de résolution de site. Un client a signalé, de façon intermittente, un bouton « Ajouter au panier » affiché en anglais sur la version française du site, alors que le reste de la page s’affichait correctement en français.

Ce genre de bug intermittent est parmi les plus frustrants à reproduire, car il ne se manifeste pas à chaque chargement, seulement de façon aléatoire selon l’ordre des requêtes précédentes sur le serveur. La cause, une fois isolée, tenait à une seule fonction traduite via __(), dont le résultat était mis en cache sans tenir compte de la locale active au moment de l’appel.

Reproduire le bug

Le code incriminé ressemblait à ceci, une fonction utilitaire censée générer le texte du bouton d’ajout au panier, mémorisé en cache d’objet pour éviter de recalculer un texte prétendument coûteux à produire :

function bouton_texte_panier() {
    $texte_cache = wp_cache_get( 'bouton_texte_panier', 'boutique' );

    if ( false !== $texte_cache ) {
        return $texte_cache;
    }

    $texte = __( 'Ajouter au panier', 'boutique' );
    wp_cache_set( 'bouton_texte_panier', $texte, 'boutique', HOUR_IN_SECONDS );

    return $texte;
}

Sur un serveur qui traite des requêtes pour plusieurs domaines et donc plusieurs locales à la suite, sans redémarrage de processus entre elles, ce cache d’objet reste partagé entre toutes les requêtes qui utilisent le même groupe de cache, indépendamment de la locale active au moment de chaque appel. Si la première requête à écrire cette clé provient du domaine anglais, la clé bouton_texte_panier stocke « Add to cart », et toute requête suivante sur le domaine français la récupère telle quelle, sans jamais retraduire le texte.

Pourquoi ce bug reste intermittent

Le comportement dépend entièrement de l’ordre d’arrivée des requêtes et de la durée de vie du cache, ici une heure. Sur un serveur avec peu de trafic, la première requête après expiration détermine la langue servie à tous les visiteurs suivants pendant l’heure entière, quel que soit leur propre domaine, jusqu’à la prochaine expiration où le cycle recommence avec potentiellement une autre langue en tête.

L'essentiel à retenir : Un cache d'objet partagé peut servir la même clé pour deux locales différentes ; La clé de cache doit inclure la locale active, jamais l'inverse ; switch_to_locale change le contexte de traduction, pas automatiquement le cache

Ce comportement explique pourquoi les tests manuels effectués par le client, souvent depuis le même domaine et donc la même locale à chaque fois, n’avaient jamais révélé le problème : il fallait alterner entre domaines dans un intervalle rapproché pour le reproduire de façon fiable.

Le correctif : inclure la locale dans la clé de cache

La correction consiste à faire dépendre explicitement la clé de cache de la locale active au moment de l’appel, obtenue via determine_locale(), fonction qui reflète la locale réellement appliquée à la requête en cours, y compris après un éventuel switch_to_locale() :

function bouton_texte_panier() {
    $locale = determine_locale();
    $cle    = 'bouton_texte_panier_' . $locale;

    $texte_cache = wp_cache_get( $cle, 'boutique' );

    if ( false !== $texte_cache ) {
        return $texte_cache;
    }

    $texte = __( 'Ajouter au panier', 'boutique' );
    wp_cache_set( $cle, $texte, 'boutique', HOUR_IN_SECONDS );

    return $texte;
}

Avec cette clé désormais spécifique à chaque locale, deux domaines distincts ne se marchent plus dessus dans le même cache d’objet partagé, chacun conservant sa propre entrée indépendamment de l’ordre d’arrivée des requêtes.

Le cas particulier de switch_to_locale

Un piège proche concerne les extensions qui appellent switch_to_locale() pour générer un contenu dans une langue différente de celle de la requête courante, par exemple un e-mail de notification envoyé dans la langue préférée du destinataire plutôt que dans celle de l’administrateur connecté au moment de l’envoi. Si un cache est consulté pendant cette fenêtre sans inclure la locale temporaire dans sa clé, le même bug peut se manifester, cette fois entre le contexte d’origine et le contexte temporairement basculé, avant l’appel symétrique à restore_previous_locale().

Vérifier la correction

  • Vider le cache d’objet concerné avec wp cache flush avant de reproduire le test, pour repartir d’un état propre.
  • Effectuer deux requêtes consécutives et rapprochées sur deux domaines de langues différentes, en observant le texte du bouton à chaque fois.
  • Vérifier, avec un outil de cache persistant comme Redis ou Memcached en environnement de recette, que deux clés distinctes existent bien désormais, une par locale.

Une vérification que nous ajoutons systématiquement à notre revue de code sur les sites multilingues : toute clé de cache portant un texte traduit doit inclure la locale, sans exception, même quand le texte semble universel au premier regard.

En résumé

Un cache d’objet mal isolé par locale ne provoque pas d’erreur visible ni de plantage : il sert simplement, de façon silencieuse et intermittente, la mauvaise langue à des visiteurs qui n’ont rien fait pour la provoquer. Ce type de bug se détecte rarement en test manuel classique et nécessite de simuler explicitement l’alternance entre locales pour être reproduit de façon fiable avant mise en production.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi