Le 23 avril 2025, un ticket de support signale qu’un même visiteur, avec les mêmes réglages de navigateur, se voit parfois présenter le site en anglais et parfois en allemand, sans schéma apparent. Aucune erreur PHP dans les logs, aucun message visible pour l’utilisateur — juste un comportement incohérent, difficile à reproduire à la demande puisqu’il ne se manifestait pas à chaque visite.
L’origine du problème tenait à un détail rarement documenté : deux extensions actives sur le site interprétaient chacune l’en-tête Accept-Language envoyé par le navigateur, mais selon des règles de priorité légèrement différentes, aboutissant parfois à des décisions contradictoires sur la langue à afficher.
Ce que contient réellement l’en-tête Accept-Language
L’en-tête HTTP Accept-Language, envoyé automatiquement par le navigateur à chaque requête, liste les langues préférées de l’utilisateur avec un poids qualitatif optionnel, comme le documente la spécification du MDN Web Docs sur Accept-Language :
Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
Cette chaîne signifie : l’allemand d’Allemagne est préféré sans réserve, l’allemand générique vient juste après avec un poids de 0,9, puis l’anglais américain à 0,8 et l’anglais générique à 0,7. Le visiteur de ce ticket avait exactement ce profil de navigateur — un ordinateur configuré en allemand, avec l’anglais en langue secondaire.

Diagnostic : deux logiques de parsing différentes
La première extension active sur le site, dédiée à la redirection automatique de langue, retenait la première langue de la liste sans tenir compte du poids qualitatif : elle lisait simplement de-DE et redirigeait vers la version allemande. La seconde extension, un module de détection de langue plus récent installé pour un besoin distinct (adaptation du format de devise), appliquait un tri correct par poids qualitatif, mais ne retenait que les deux premières langues de la liste et ignorait purement les poids inférieurs à 0,8 — ce qui, sur certains profils de navigateur avec une syntaxe légèrement différente, produisait un résultat différent de la première extension.
// Extension 1 : lecture naïve, premier élément de la liste
$langue = explode( ',', $_SERVER['HTTP_ACCEPT_LANGUAGE'] )[0];
// Extension 2 : tri par poids, mais tronqué aux deux premiers résultats
$langues = parse_accept_language_header( $_SERVER['HTTP_ACCEPT_LANGUAGE'] );
$langue = array_slice( $langues, 0, 2 );
Les deux extensions agissaient sur des hooks différents, à des moments différents du cycle de requête, ce qui expliquait le caractère apparemment aléatoire du bug : selon l’ordre de priorité des hooks WordPress ce jour-là (modifié par une mise à jour récente de l’une des deux extensions), c’était tantôt l’une, tantôt l’autre qui déterminait la langue affichée en dernier.
Correctif : une seule source de vérité pour la détection
La solution retenue n’a pas consisté à corriger le parsing de chacune des deux extensions, mais à désactiver la détection automatique de langue de la seconde (celle dédiée à la devise), en la configurant pour se caler explicitement sur la langue déjà déterminée par la première extension, via un filtre exposé à cet effet :
add_filter( 'devise_module_locale_override', function () {
return determine_locale();
} );
Cette approche garantit qu’une seule logique de détection décide réellement de la langue affichée, tandis que les modules annexes se contentent de suivre cette décision plutôt que de reparser l’en-tête de leur côté avec leurs propres règles.
Prévention : centraliser la détection dès la conception
- Vérifier, avant d’activer une nouvelle extension sensible à la langue, si elle propose sa propre détection automatique ou si elle peut suivre une décision existante
- Documenter clairement, dans les réglages internes du projet, quelle extension fait autorité sur la détection de langue
- Tester avec plusieurs profils de navigateur aux en-têtes
Accept-Languagevolontairement complexes (plusieurs langues, poids qualitatifs variés) avant toute mise en production
Deux mécanismes de détection de langue actifs sur le même site ne s’additionnent jamais proprement : l’un des deux doit céder la priorité à l’autre, sinon c’est l’ordre d’exécution des hooks qui tranchera au hasard.
En résumé
Ce conflit n’avait rien d’une anomalie rare de WordPress : c’était la conséquence directe de deux extensions parsant la même information brute selon des règles subtilement différentes. La correction n’a pas nécessité de toucher au cœur du site, seulement d’établir clairement quelle logique fait autorité et de faire suivre les autres modules cette même décision plutôt que de la recalculer chacun de leur côté.