[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$ftQVwmvDrlkGXRTOUb-Rfyo6y70Wf_XgKlDldRLFtxvQ":3,"$fSa3LVnafgmf9tE63wyFBYWqM4GhftG-TgzZ8DAGcSgc":25},{"_id":4,"slug":5,"__v":6,"author":7,"body":8,"canonical":9,"category":10,"createdAt":11,"date":12,"description":13,"htmlContent":14,"image":15,"imageAlt":15,"readingTime":16,"tags":17,"title":23,"updatedAt":24},"6a1029dfaa6b273b0c39be84","what-multilingual-regions-reveal-about-the-future-of-ai-search",0,"Equipe Seogard","Un site e-commerce catalan de 8 000 pages produit voit son trafic organique chuter de 23% en trois mois — sans aucun changement technique, sans pénalité manuelle, sans mise à jour d'algorithme identifiée. La cause : les AI Overviews de Google citent systématiquement les pages espagnoles (es) au lieu des pages catalanes (ca), parce que le modèle de langue sous-jacent ne distingue pas correctement les deux langues. Ce n'est pas un bug isolé. C'est un problème structurel qui touche toutes les régions multilingues, et qui redéfinit les règles du SEO technique pour l'ère de la recherche IA.\n\n## Le problème fondamental : la language identification dans les LLMs\n\nLes moteurs de recherche traditionnels s'appuient sur des signaux explicites pour déterminer la langue d'une page : la balise `lang`, les annotations `hreflang`, la locale détectée via l'IP ou les préférences navigateur. Ces signaux sont structurés, déclaratifs, et le crawler les respecte (en théorie).\n\nLes LLMs fonctionnent différemment. Quand un modèle comme Gemini génère une AI Overview, il effectue un processus de retrieval-augmented generation (RAG) qui implique une étape de compréhension du contenu source. Cette compréhension passe par le tokenizer du modèle, qui transforme le texte en tokens avant tout traitement. Et c'est là que le problème commence.\n\n### Le biais du tokenizer sur les langues proches\n\nLe catalan et l'espagnol partagent environ 85% de leur vocabulaire courant. Pour un tokenizer entraîné majoritairement sur des corpus anglais, espagnols et français, le catalan est sous-représenté. Le modèle n'a pas assez de données d'entraînement pour distinguer fiablement les deux langues dans des contextes courts (descriptions produit, FAQ, extraits de 200-300 mots).\n\nLe résultat concret : quand un utilisateur effectue une recherche en catalan, l'AI Overview peut puiser dans des sources espagnoles jugées \"sémantiquement équivalentes\" par le modèle, même si une source catalane parfaitement pertinente existe et est indexée.\n\nCe problème ne se limite pas au catalan. Il affecte :\n- Le galicien vs. le portugais\n- Le luxembourgeois vs. l'allemand\n- Le bokmål vs. le nynorsk (Norvège)\n- Le serbe latinisé vs. le croate\n- Le hindi vs. l'ourdou en script latin\n\nToute paire de langues avec une proximité lexicale élevée est vulnérable.\n\n### Pourquoi hreflang ne suffit plus\n\nL'annotation `hreflang` indique à Googlebot quelle version servir à quel utilisateur. Mais dans le pipeline RAG d'une AI Overview, le contenu est déjà extrait et indexé dans un embedding vectoriel. L'annotation hreflang n'est pas un signal dans l'espace vectoriel — c'est une métadonnée de serving, pas de retrieval.\n\nDit autrement : hreflang dit \"sers cette page aux utilisateurs catalans\", mais le LLM qui construit l'AI Overview ne \"sert\" pas — il \"cite\". Et sa décision de citation repose sur la proximité sémantique dans l'espace d'embeddings, pas sur les annotations HTML.\n\n## Audit technique : détecter les confusions de langue sur votre site\n\nAvant de corriger, il faut diagnostiquer. Voici une méthodologie pour identifier si votre site multilingue est affecté par des erreurs de language identification dans les résultats IA.\n\n### Étape 1 : extraire les AI Overviews par langue\n\nUtilisez un script qui interroge les SERPs pour vos mots-clés principaux dans chaque langue cible, puis compare les sources citées dans les AI Overviews avec la langue attendue.\n\n```python\nimport requests\nfrom bs4 import BeautifulSoup\nimport json\n\n# Configuration pour un site catalan/espagnol\nKEYWORDS = {\n    \"ca\": [\"sabates de cuir\", \"roba de muntanya\", \"motxilles impermeables\"],\n    \"es\": [\"zapatos de cuero\", \"ropa de montaña\", \"mochilas impermeables\"]\n}\n\nDOMAIN = \"botiga-outdoor.cat\"\n\ndef check_aio_citations(keyword: str, lang: str, gl: str = \"ES\") -> dict:\n    \"\"\"\n    Vérifie si les citations d'AI Overview matchent la langue attendue.\n    Nécessite un proxy SERP API (SerpAPI, ValueSERP, etc.)\n    \"\"\"\n    params = {\n        \"q\": keyword,\n        \"hl\": lang,\n        \"gl\": gl,\n        \"engine\": \"google\",\n        \"api_key\": \"VOTRE_CLE_API\"\n    }\n    \n    response = requests.get(\"https://serpapi.com/search\", params=params)\n    data = response.json()\n    \n    ai_overview = data.get(\"ai_overview\", {})\n    citations = ai_overview.get(\"citations\", [])\n    \n    mismatches = []\n    for citation in citations:\n        url = citation.get(\"link\", \"\")\n        # Détecter la langue de la page citée via le path ou subdomain\n        if DOMAIN in url:\n            page_lang = detect_lang_from_url(url)\n            if page_lang != lang:\n                mismatches.append({\n                    \"keyword\": keyword,\n                    \"expected_lang\": lang,\n                    \"cited_url\": url,\n                    \"cited_lang\": page_lang\n                })\n    \n    return {\n        \"keyword\": keyword,\n        \"lang\": lang,\n        \"total_citations\": len(citations),\n        \"own_citations\": len([c for c in citations if DOMAIN in c.get(\"link\", \"\")]),\n        \"lang_mismatches\": mismatches\n    }\n\ndef detect_lang_from_url(url: str) -> str:\n    \"\"\"Détecte la langue à partir de la structure URL\"\"\"\n    if \"/ca/\" in url or url.startswith(\"https://ca.\"):\n        return \"ca\"\n    elif \"/es/\" in url or url.startswith(\"https://es.\"):\n        return \"es\"\n    return \"unknown\"\n\n# Exécution\nresults = []\nfor lang, kws in KEYWORDS.items():\n    for kw in kws:\n        result = check_aio_citations(kw, lang)\n        results.append(result)\n        if result[\"lang_mismatches\"]:\n            print(f\"⚠ MISMATCH: '{kw}' ({lang}) → {len(result['lang_mismatches'])} citations en mauvaise langue\")\n```\n\nCe script est un point de départ. En production, vous voudrez l'exécuter de manière hebdomadaire et stocker les résultats pour suivre l'évolution. Un outil de monitoring comme Seogard peut automatiser cette détection en suivant les régressions de citations dans les AI Overviews par marché linguistique.\n\n### Étape 2 : vérifier la cohérence des signaux de langue\n\nCrawlez votre site avec Screaming Frog en filtrant les incohérences entre l'attribut `lang`, le contenu réel de la page, et les annotations hreflang. Configurez un custom extraction :\n\nDans Screaming Frog → Configuration → Custom → Extraction :\n- Extraction 1 : `//html/@lang` (XPath) — récupère l'attribut lang déclaré\n- Extraction 2 : `//meta[@http-equiv='content-language']/@content` (XPath) — récupère le content-language s'il existe\n- Extraction 3 : `//link[@rel='alternate'][@hreflang]/@hreflang` (XPath) — récupère tous les hreflang déclarés\n\nExportez en CSV et croisez avec la détection de langue réelle du contenu. Un script rapide avec la bibliothèque `langdetect` de Python sur le texte extrait révèle les pages où le contenu ne correspond pas à la langue déclarée :\n\n```python\nfrom langdetect import detect, DetectorFactory\nimport csv\n\n# Reproductibilité des résultats\nDetectorFactory.seed = 0\n\ndef audit_language_consistency(crawl_export: str) -> list:\n    \"\"\"\n    Croise la langue déclarée (html lang) avec la langue détectée\n    dans le contenu textuel extrait par Screaming Frog.\n    \"\"\"\n    issues = []\n    \n    with open(crawl_export, \"r\", encoding=\"utf-8\") as f:\n        reader = csv.DictReader(f)\n        for row in reader:\n            url = row.get(\"Address\", \"\")\n            declared_lang = row.get(\"Lang 1\", \"\").strip().lower()  # custom extraction\n            body_text = row.get(\"Body Text 1\", \"\")  # si extrait\n            \n            if not body_text or len(body_text) \u003C 100:\n                continue\n            \n            try:\n                detected_lang = detect(body_text)\n            except Exception:\n                detected_lang = \"unknown\"\n            \n            # Normaliser : langdetect retourne 'ca' pour catalan, 'es' pour espagnol\n            # Mais attention : langdetect confond souvent ca/es sur des textes courts\n            if declared_lang and detected_lang != declared_lang:\n                # Vérifier si c'est une confusion connue (ca↔es, gl↔pt, etc.)\n                confused_pairs = [(\"ca\", \"es\"), (\"gl\", \"pt\"), (\"bs\", \"hr\"), (\"no\", \"da\")]\n                is_confused_pair = any(\n                    {declared_lang, detected_lang} == set(pair) \n                    for pair in confused_pairs\n                )\n                \n                issues.append({\n                    \"url\": url,\n                    \"declared\": declared_lang,\n                    \"detected\": detected_lang,\n                    \"is_confused_pair\": is_confused_pair,\n                    \"text_length\": len(body_text),\n                    \"severity\": \"HIGH\" if is_confused_pair else \"MEDIUM\"\n                })\n    \n    return issues\n\n# Utilisation\nissues = audit_language_consistency(\"screaming_frog_export.csv\")\nhigh_severity = [i for i in issues if i[\"severity\"] == \"HIGH\"]\nprint(f\"{len(high_severity)} pages avec confusion de langue à haut risque\")\n```\n\nLe point clé : si `langdetect` confond la langue de vos pages, les LLMs le font probablement aussi. Ces pages sont les premières candidates à une optimisation des signaux de langue.\n\n## Renforcer les signaux de langue au-delà de hreflang\n\nPuisque les signaux déclaratifs (hreflang, `lang`) ne sont pas suffisants dans le pipeline RAG, il faut renforcer les signaux implicites — ceux que le LLM peut capter pendant l'extraction et l'embedding du contenu.\n\n### Stratégie 1 : densifier les marqueurs linguistiques distinctifs\n\nChaque langue a des structures grammaticales et des mots-outils qui la distinguent de ses langues sœurs. En catalan, les articles \"el/la/els/les\", les pronoms faibles (\"em\", \"et\", \"en\", \"hi\"), et les suffixes verbaux (\"-ar\", \"-er\", \"-ir\" vs. \"-ar\", \"-er\", \"-ir\" en espagnol mais avec des conjugaisons différentes) sont des signaux distinctifs.\n\nL'idée n'est pas de sur-optimiser le texte — c'est de s'assurer que le contenu utilise naturellement les formes propres à la langue cible, plutôt que des calques de la langue dominante. En pratique, cela signifie :\n\n- Éviter le \"català light\" (catalan truffé d'hispanismes) dans les descriptions produit\n- Utiliser les pronoms faibles catalans là où un locuteur natif les utiliserait\n- Privilégier le vocabulaire spécifiquement catalan quand il existe (ex: \"ordinador\" vs. \"computadora\")\n\n### Stratégie 2 : structured data avec indication de langue explicite\n\nLe JSON-LD permet de spécifier la langue du contenu de manière granulaire via la propriété `inLanguage`. Ce signal est lisible par les crawlers traditionnels ET par les systèmes RAG qui parsent le structured data.\n\n```html\n\u003Cscript type=\"application/ld+json\">\n{\n  \"@context\": \"https://schema.org\",\n  \"@type\": \"Product\",\n  \"name\": \"Motxilla impermeable de muntanya 45L\",\n  \"description\": \"Motxilla lleugera amb teixit impermeable de 3 capes. Dissenyada per a rutes d'alta muntanya als Pirineus catalans.\",\n  \"inLanguage\": \"ca\",\n  \"brand\": {\n    \"@type\": \"Brand\",\n    \"name\": \"Botiga Outdoor\"\n  },\n  \"offers\": {\n    \"@type\": \"Offer\",\n    \"priceCurrency\": \"EUR\",\n    \"price\": \"129.95\",\n    \"availability\": \"https://schema.org/InStock\",\n    \"areaServed\": {\n      \"@type\": \"AdministrativeArea\",\n      \"name\": \"Catalunya\",\n      \"inDefinedTermSet\": \"NUTS\",\n      \"termCode\": \"ES51\"\n    }\n  },\n  \"isPartOf\": {\n    \"@type\": \"WebPage\",\n    \"inLanguage\": \"ca\",\n    \"url\": \"https://botiga-outdoor.cat/ca/motxilles/impermeable-45l\"\n  }\n}\n\u003C/script>\n```\n\nTrois points techniques à noter dans ce markup :\n1. `inLanguage: \"ca\"` est déclaré à la fois sur le Product et sur la WebPage — redondance intentionnelle pour maximiser la captation du signal\n2. `areaServed` avec le code NUTS (ES51 = Catalunya) crée une association géographique explicite qui aide à la désambiguïsation\n3. La description produit utilise des termes spécifiquement catalans (\"lleugera\", \"teixit\", \"Pirineus catalans\") qui renforcent le signal linguistique implicite\n\n### Stratégie 3 : en-têtes HTTP Content-Language\n\nCe signal est souvent oublié alors qu'il est lu avant même le parsing HTML. Configurez-le au niveau du serveur.\n\nPour Nginx, avec une architecture en sous-répertoires (`/ca/`, `/es/`) :\n\n```nginx\nserver {\n    listen 443 ssl;\n    server_name botiga-outdoor.cat;\n\n    # Langue par défaut\n    set $content_lang \"ca\";\n\n    # Détection basée sur le path\n    location ~ ^/es/ {\n        set $content_lang \"es\";\n    }\n    location ~ ^/fr/ {\n        set $content_lang \"fr\";\n    }\n\n    # Appliquer le header à toutes les réponses HTML\n    location / {\n        # Ne pas ajouter Content-Language sur les assets statiques\n        if ($uri ~* \\.(css|js|png|jpg|svg|woff2)$) {\n            break;\n        }\n        add_header Content-Language $content_lang always;\n        \n        # Vary header pour que les CDN cachent par langue\n        add_header Vary \"Accept-Language\" always;\n        \n        proxy_pass http://backend;\n    }\n\n    # Pages spécifiques : forcer le catalan sur la homepage\n    location = / {\n        add_header Content-Language \"ca\" always;\n        add_header Vary \"Accept-Language\" always;\n        proxy_pass http://backend;\n    }\n}\n```\n\nL'en-tête `Content-Language` est spécifié dans la [documentation MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Language) comme un signal destiné aux systèmes de traitement automatique, pas uniquement aux navigateurs. C'est exactement ce dont les crawlers IA ont besoin.\n\n## Scénario réel : migration multilingue et effondrement des citations IA\n\nPrenons un cas réaliste. BotigaraDeco est un e-commerce catalan de décoration intérieure : 12 400 pages produit, 850 pages catégorie, 320 pages éditoriales. Le site existe en catalan (ca) et espagnol (es), avec une architecture en sous-répertoires.\n\n**Situation initiale (janvier 2026) :**\n- 68% du trafic organique vient de requêtes en catalan\n- Le site apparaît dans les AI Overviews pour 142 requêtes catalanes (mesuré via un suivi SERP hebdomadaire)\n- Les citations AI pointent vers la version `/ca/` dans 89% des cas\n\n**Événement déclencheur (février 2026) :**\nL'équipe technique migre le frontend de Nuxt 2 vers Nuxt 3. Pendant la migration, un changement dans la configuration SSR modifie la façon dont la page est rendue : le composant `\u003Chtml lang=\"ca\">` est maintenant injecté côté client au lieu d'être présent dans le HTML initial.\n\n**Impact mesuré sur 8 semaines :**\n- Les citations AI en catalan chutent de 142 à 51 requêtes (-64%)\n- Les citations vers `/es/` pour des requêtes catalanes passent de 11% à 47%\n- Le trafic organique catalan baisse de 23% (mesuré dans GA4 en segmentant par paramètre `hl=ca` dans les referrers Search)\n- Le trafic espagnol reste stable — le problème est asymétrique\n\n**Diagnostic :** le SSR de Nuxt 3 générait le `\u003Chtml>` initial sans attribut `lang`, qui n'était ajouté qu'après hydration côté client. GoogleBot et les systèmes RAG qui crawlent le HTML pré-hydration voyaient une page sans signal de langue explicite. Combined avec le contenu textuel ambiguë (noms de produits internationaux, descriptions courtes), le modèle classifiait ces pages comme \"espagnol\" par défaut — la langue dominante de la région selon les données d'entraînement.\n\n**Correction :**\n\nDans `nuxt.config.ts`, forcer la langue dans le rendu SSR :\n\n```typescript\n// nuxt.config.ts\nexport default defineNuxtConfig({\n  app: {\n    head: {\n      htmlAttrs: {\n        // Langue par défaut, sera overridée par le middleware\n        lang: 'ca'\n      }\n    }\n  },\n  \n  // Plugin SSR pour injecter la langue dynamiquement\n  nitro: {\n    plugins: ['~/server/plugins/lang-header.ts']\n  }\n})\n```\n\n```typescript\n// server/plugins/lang-header.ts\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook('render:html', (html, { event }) => {\n    const path = event.path || ''\n    \n    let lang = 'ca' // défaut catalan\n    if (path.startsWith('/es/') || path.startsWith('/es?')) {\n      lang = 'es'\n    } else if (path.startsWith('/fr/')) {\n      lang = 'fr'\n    }\n    \n    // Injecter lang dans la balise html AVANT l'hydration client\n    html.htmlAttrs = html.htmlAttrs || []\n    html.htmlAttrs.push(`lang=\"${lang}\"`)\n    \n    // Ajouter le header HTTP également\n    event.node.res.setHeader('Content-Language', lang)\n  })\n})\n```\n\nAprès déploiement de la correction et re-crawl (forcé via l'API d'indexation de Search Console pour les 200 pages les plus critiques), les citations AI sont revenues au niveau initial en 4 semaines.\n\nCe scénario illustre un problème que les [audits SEO techniques classiques](/blog/the-tech-seo-audit-for-the-ai-search-era-how-to-maximize-your-ai-visibility-via-sejournal-jetoctopus) ne couvrent pas encore : la cohérence des signaux de langue dans le HTML pré-hydration n'est testée ni par Lighthouse, ni par la plupart des outils de crawl qui exécutent JavaScript.\n\n## L'impact structurel sur les AI Overviews et la GEO\n\nCe que révèle le cas catalan dépasse la simple correction technique. C'est un signal d'un problème structurel dans la façon dont les moteurs IA traitent les contenus multilingues.\n\n### Le biais de la langue dominante\n\nLes LLMs ont un biais statistique en faveur des langues pour lesquelles ils disposent de plus de données d'entraînement. L'espagnol dispose d'un corpus web environ 15 fois plus large que le catalan. Ce ratio crée un prior bayésien implicite : quand le modèle hésite entre \"c'est du catalan\" et \"c'est de l'espagnol\", il penche vers l'espagnol par défaut.\n\nEn termes de SEO, cela signifie que les pages dans des langues minoritaires doivent fournir des signaux de langue **plus forts** que les pages dans des langues dominantes pour obtenir la même attribution. L'asymétrie est structurelle et ne sera pas corrigée par un simple update d'algorithme.\n\n### La fragmentation des citations\n\nLes [frameworks de mesure GEO](/blog/the-5-layer-framework-for-measuring-geo-performance) doivent intégrer la dimension linguistique. Quand une AI Overview cite une source en espagnol pour répondre à une requête catalane, trois choses se produisent :\n\n1. **L'utilisateur catalan reçoit une réponse dans une langue qu'il n'a pas demandée** — dégradation de l'expérience utilisateur que Google dit vouloir éviter\n2. **Le site catalan perd la citation** — même si son contenu est pertinent et mieux adapté\n3. **Les métriques de visibilité AI sont faussées** — le site apparaît moins visible en catalan alors que le problème est côté moteur, pas côté contenu\n\nCe phénomène est documenté de façon plus large dans la problématique de la [propagation d'information incorrecte dans l'IA de recherche](/blog/how-negative-information-spreads-from-wikipedia-into-ai-search) : quand le système RAG sélectionne la mauvaise source, il propage une erreur en cascade.\n\n### Le consensus gap linguistique\n\nLe concept de [consensus gap](/blog/the-consensus-gap-via-sejournal-kevin-indig) prend une dimension nouvelle en contexte multilingue. Si les sources catalanes et espagnoles disent la même chose mais avec des nuances culturelles ou terminologiques, le LLM peut percevoir un désaccord artificiel et soit omettre la citation, soit créer une synthèse confuse.\n\nExemple concret : pour la requête \"millors rutes senderisme Pirineus\" (meilleures randonnées Pyrénées), les sources catalanes mentionnent des noms de lieux en catalan (Aigüestortes, Cadí-Moixeró) tandis que les sources espagnoles utilisent parfois les noms castillans. Le LLM peut traiter ces noms comme des entités différentes, créant un consensus gap artificiel qui dilue la visibilité de toutes les sources.\n\n## Stratégie de monitoring continu pour les sites multilingues\n\nLa détection ponctuelle ne suffit pas. Les confusions de langue dans les résultats IA sont dynamiques — elles changent avec chaque mise à jour du modèle, chaque re-crawl, chaque modification du contenu.\n\n### Métriques à suivre\n\nAjoutez ces KPIs à votre [tableau de bord de mesure de la visibilité IA](/blog/how-to-measure-ai-search-current-kpis-you-need-to-know-webinar-via-sejournal-hethr-campbell) :\n\n- **Language Match Rate** : % des citations AI qui pointent vers la bonne version linguistique de votre site, par langue cible\n- **Cross-Language Cannibalization** : nombre de requêtes où la version es apparaît dans les AI Overviews à la place de la version ca (ou inversement)\n- **Citation Language Drift** : évolution du Language Match Rate sur le temps — un drift négatif indique un problème de crawl ou de mise à jour du modèle\n\n### Vérifier le rendu SSR par langue\n\nUn test simple mais critique : vérifier que chaque version linguistique retourne les bons signaux dans le HTML initial, sans exécution JavaScript.\n\n```bash\n# Vérifier le HTML SSR pour la version catalane\ncurl -s -o /dev/null -D - \"https://botiga-outdoor.cat/ca/motxilles/\" \\\n  -H \"User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1)\" \\\n  | grep -i \"content-language\"\n\n# Extraire l'attribut lang du HTML pré-hydration\ncurl -s \"https://botiga-outdoor.cat/ca/motxilles/\" \\\n  -H \"User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1)\" \\\n  | grep -oP '\u003Chtml[^>]*lang=\"[^\"]*\"' | head -1\n\n# Comparer avec la version espagnole\ncurl -s \"https://botiga-outdoor.cat/es/mochilas/\" \\\n  -H \"User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1)\" \\\n  | grep -oP '\u003Chtml[^>]*lang=\"[^\"]*\"' | head -1\n\n# Vérifier le hreflang dans le HTML (pas dans le sitemap uniquement)\ncurl -s \"https://botiga-outdoor.cat/ca/motxilles/\" \\\n  -H \"User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1)\" \\\n  | grep -oP 'hreflang=\"[^\"]*\"' | sort -u\n```\n\nExécutez ces vérifications après chaque déploiement. Si le `lang` disparaît ou si le `Content-Language` ne correspond plus au path, vous avez une régression critique — le type exact de régression que Seogard détecte automatiquement en comparant le HTML rendu entre deux crawls.\n\n### Anticiper les prochaines mises à jour\n\nLe [core update de mai 2026](/blog/google-begins-rolling-out-may-2026-core-update-via-sejournal-mattgsouthern) est en cours de déploiement au moment de la publication de cet article. Historiquement, les core updates modifient les poids relatifs des signaux utilisés dans le retrieval. Pour les sites multilingues, chaque core update est un risque de voir les seuils de language identification bouger.\n\nLa stratégie défensive : maintenir un snapshot hebdomadaire de vos citations AI par langue, et corréler toute variation avec les dates de rollout des updates. Si votre Language Match Rate chute de plus de 5 points pendant un update, c'est un signal que les critères de sélection de langue dans le RAG ont changé — et que votre contenu a besoin de signaux de langue plus forts.\n\n## Au-delà du catalan : ce que ça change pour tout le SEO multilingue\n\nLe cas catalan est un canari dans la mine. Aujourd'hui, ce sont les langues minoritaires qui souffrent. Demain, à mesure que les AI Overviews s'étendent et que [les agents IA deviennent des visiteurs réguliers du web](/blog/google-agent-the-web-s-new-visitor-just-got-an-identity-via-sejournal-slobodanmanic), le problème touchera des marchés plus larges.\n\nLes sites opérant en français canadien vs. français européen, en portugais brésilien vs. européen, ou même en anglais britannique vs. américain, verront des phénomènes similaires — plus subtils, mais avec un impact commercial réel sur la pertinence des réponses IA pour leur audience.\n\nLa recommandation technique est claire : ne traitez plus la langue comme un signal déclaratif passif. Traitez-la comme un signal actif qui doit être renforcé à chaque couche — HTML, HTTP headers, structured data, contenu textuel, et monitoring continu. Les sites qui maîtrisent cette stack multilingue complète seront ceux que les LLMs citent correctement. Les autres seront invisibles dans les réponses IA, remplacés par la version linguistique dominante de leur marché.","https://seogard.io/blog/what-multilingual-regions-reveal-about-the-future-of-ai-search","Actualités SEO","2026-05-22T10:03:11.214Z","2026-05-22","Catalan search behavior reveals how language identification errors reshape AI rankings and citations. Technical deep-dive with hreflang, SSR, and monitoring strategies.","\u003Cp>Un site e-commerce catalan de 8 000 pages produit voit son trafic organique chuter de 23% en trois mois — sans aucun changement technique, sans pénalité manuelle, sans mise à jour d'algorithme identifiée. La cause : les AI Overviews de Google citent systématiquement les pages espagnoles (es) au lieu des pages catalanes (ca), parce que le modèle de langue sous-jacent ne distingue pas correctement les deux langues. Ce n'est pas un bug isolé. C'est un problème structurel qui touche toutes les régions multilingues, et qui redéfinit les règles du SEO technique pour l'ère de la recherche IA.\u003C/p>\n\u003Ch2>Le problème fondamental : la language identification dans les LLMs\u003C/h2>\n\u003Cp>Les moteurs de recherche traditionnels s'appuient sur des signaux explicites pour déterminer la langue d'une page : la balise \u003Ccode>lang\u003C/code>, les annotations \u003Ccode>hreflang\u003C/code>, la locale détectée via l'IP ou les préférences navigateur. Ces signaux sont structurés, déclaratifs, et le crawler les respecte (en théorie).\u003C/p>\n\u003Cp>Les LLMs fonctionnent différemment. Quand un modèle comme Gemini génère une AI Overview, il effectue un processus de retrieval-augmented generation (RAG) qui implique une étape de compréhension du contenu source. Cette compréhension passe par le tokenizer du modèle, qui transforme le texte en tokens avant tout traitement. Et c'est là que le problème commence.\u003C/p>\n\u003Ch3>Le biais du tokenizer sur les langues proches\u003C/h3>\n\u003Cp>Le catalan et l'espagnol partagent environ 85% de leur vocabulaire courant. Pour un tokenizer entraîné majoritairement sur des corpus anglais, espagnols et français, le catalan est sous-représenté. Le modèle n'a pas assez de données d'entraînement pour distinguer fiablement les deux langues dans des contextes courts (descriptions produit, FAQ, extraits de 200-300 mots).\u003C/p>\n\u003Cp>Le résultat concret : quand un utilisateur effectue une recherche en catalan, l'AI Overview peut puiser dans des sources espagnoles jugées \"sémantiquement équivalentes\" par le modèle, même si une source catalane parfaitement pertinente existe et est indexée.\u003C/p>\n\u003Cp>Ce problème ne se limite pas au catalan. Il affecte :\u003C/p>\n\u003Cul>\n\u003Cli>Le galicien vs. le portugais\u003C/li>\n\u003Cli>Le luxembourgeois vs. l'allemand\u003C/li>\n\u003Cli>Le bokmål vs. le nynorsk (Norvège)\u003C/li>\n\u003Cli>Le serbe latinisé vs. le croate\u003C/li>\n\u003Cli>Le hindi vs. l'ourdou en script latin\u003C/li>\n\u003C/ul>\n\u003Cp>Toute paire de langues avec une proximité lexicale élevée est vulnérable.\u003C/p>\n\u003Ch3>Pourquoi hreflang ne suffit plus\u003C/h3>\n\u003Cp>L'annotation \u003Ccode>hreflang\u003C/code> indique à Googlebot quelle version servir à quel utilisateur. Mais dans le pipeline RAG d'une AI Overview, le contenu est déjà extrait et indexé dans un embedding vectoriel. L'annotation hreflang n'est pas un signal dans l'espace vectoriel — c'est une métadonnée de serving, pas de retrieval.\u003C/p>\n\u003Cp>Dit autrement : hreflang dit \"sers cette page aux utilisateurs catalans\", mais le LLM qui construit l'AI Overview ne \"sert\" pas — il \"cite\". Et sa décision de citation repose sur la proximité sémantique dans l'espace d'embeddings, pas sur les annotations HTML.\u003C/p>\n\u003Ch2>Audit technique : détecter les confusions de langue sur votre site\u003C/h2>\n\u003Cp>Avant de corriger, il faut diagnostiquer. Voici une méthodologie pour identifier si votre site multilingue est affecté par des erreurs de language identification dans les résultats IA.\u003C/p>\n\u003Ch3>Étape 1 : extraire les AI Overviews par langue\u003C/h3>\n\u003Cp>Utilisez un script qui interroge les SERPs pour vos mots-clés principaux dans chaque langue cible, puis compare les sources citées dans les AI Overviews avec la langue attendue.\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> requests\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">from\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> bs4 \u003C/span>\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> BeautifulSoup\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> json\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Configuration pour un site catalan/espagnol\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">KEYWORDS\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"ca\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"sabates de cuir\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"roba de muntanya\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"motxilles impermeables\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"es\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"zapatos de cuero\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"ropa de montaña\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"mochilas impermeables\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">DOMAIN\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"botiga-outdoor.cat\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C/span>\u003Cspan style=\"color:#B392F0\"> check_aio_citations\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(keyword: \u003C/span>\u003Cspan style=\"color:#79B8FF\">str\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, lang: \u003C/span>\u003Cspan style=\"color:#79B8FF\">str\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, gl: \u003C/span>\u003Cspan style=\"color:#79B8FF\">str\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"ES\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) -> \u003C/span>\u003Cspan style=\"color:#79B8FF\">dict\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"\"\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    Vérifie si les citations d'AI Overview matchent la langue attendue.\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    Nécessite un proxy SERP API (SerpAPI, ValueSERP, etc.)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"\"\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    params \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        \"q\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: keyword,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        \"hl\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: lang,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        \"gl\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: gl,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        \"engine\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"google\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        \"api_key\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"VOTRE_CLE_API\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    response \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> requests.get(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"https://serpapi.com/search\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">params\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">params)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    data \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> response.json()\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    ai_overview \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> data.get(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"ai_overview\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, {})\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    citations \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ai_overview.get(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"citations\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, [])\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    mismatches \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> []\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> citation \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> citations:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        url \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> citation.get(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"link\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        # Détecter la langue de la page citée via le path ou subdomain\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        if\u003C/span>\u003Cspan style=\"color:#79B8FF\"> DOMAIN\u003C/span>\u003Cspan style=\"color:#F97583\"> in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> url:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">            page_lang \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> detect_lang_from_url(url)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> page_lang \u003C/span>\u003Cspan style=\"color:#F97583\">!=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> lang:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">                mismatches.append({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                    \"keyword\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: keyword,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                    \"expected_lang\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: lang,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                    \"cited_url\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: url,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                    \"cited_lang\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: page_lang\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">                })\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        \"keyword\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: keyword,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        \"lang\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: lang,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        \"total_citations\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(citations),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        \"own_citations\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">([c \u003C/span>\u003Cspan style=\"color:#F97583\">for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> c \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> citations \u003C/span>\u003Cspan style=\"color:#F97583\">if\u003C/span>\u003Cspan style=\"color:#79B8FF\"> DOMAIN\u003C/span>\u003Cspan style=\"color:#F97583\"> in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> c.get(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"link\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)]),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        \"lang_mismatches\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: mismatches\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C/span>\u003Cspan style=\"color:#B392F0\"> detect_lang_from_url\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(url: \u003C/span>\u003Cspan style=\"color:#79B8FF\">str\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) -> \u003C/span>\u003Cspan style=\"color:#79B8FF\">str\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"\"\"Détecte la langue à partir de la structure URL\"\"\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"/ca/\"\u003C/span>\u003Cspan style=\"color:#F97583\"> in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> url \u003C/span>\u003Cspan style=\"color:#F97583\">or\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> url.startswith(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"https://ca.\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">):\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        return\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"ca\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    elif\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"/es/\"\u003C/span>\u003Cspan style=\"color:#F97583\"> in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> url \u003C/span>\u003Cspan style=\"color:#F97583\">or\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> url.startswith(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"https://es.\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">):\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        return\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"es\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"unknown\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Exécution\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">results \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> []\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> lang, kws \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#79B8FF\"> KEYWORDS\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.items():\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> kw \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> kws:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        result \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> check_aio_citations(kw, lang)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        results.append(result)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> result[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"lang_mismatches\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">            print\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#F97583\">f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"⚠ MISMATCH: '\u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">kw\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">' (\u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">lang\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">) → \u003C/span>\u003Cspan style=\"color:#79B8FF\">{len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(result[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'lang_mismatches'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">])\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> citations en mauvaise langue\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Ce script est un point de départ. En production, vous voudrez l'exécuter de manière hebdomadaire et stocker les résultats pour suivre l'évolution. Un outil de monitoring comme Seogard peut automatiser cette détection en suivant les régressions de citations dans les AI Overviews par marché linguistique.\u003C/p>\n\u003Ch3>Étape 2 : vérifier la cohérence des signaux de langue\u003C/h3>\n\u003Cp>Crawlez votre site avec Screaming Frog en filtrant les incohérences entre l'attribut \u003Ccode>lang\u003C/code>, le contenu réel de la page, et les annotations hreflang. Configurez un custom extraction :\u003C/p>\n\u003Cp>Dans Screaming Frog → Configuration → Custom → Extraction :\u003C/p>\n\u003Cul>\n\u003Cli>Extraction 1 : \u003Ccode>//html/@lang\u003C/code> (XPath) — récupère l'attribut lang déclaré\u003C/li>\n\u003Cli>Extraction 2 : \u003Ccode>//meta[@http-equiv='content-language']/@content\u003C/code> (XPath) — récupère le content-language s'il existe\u003C/li>\n\u003Cli>Extraction 3 : \u003Ccode>//link[@rel='alternate'][@hreflang]/@hreflang\u003C/code> (XPath) — récupère tous les hreflang déclarés\u003C/li>\n\u003C/ul>\n\u003Cp>Exportez en CSV et croisez avec la détection de langue réelle du contenu. Un script rapide avec la bibliothèque \u003Ccode>langdetect\u003C/code> de Python sur le texte extrait révèle les pages où le contenu ne correspond pas à la langue déclarée :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">from\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> langdetect \u003C/span>\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> detect, DetectorFactory\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> csv\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Reproductibilité des résultats\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">DetectorFactory.seed \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 0\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C/span>\u003Cspan style=\"color:#B392F0\"> audit_language_consistency\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(crawl_export: \u003C/span>\u003Cspan style=\"color:#79B8FF\">str\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) -> \u003C/span>\u003Cspan style=\"color:#79B8FF\">list\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"\"\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    Croise la langue déclarée (html lang) avec la langue détectée\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    dans le contenu textuel extrait par Screaming Frog.\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"\"\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    issues \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> []\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    with\u003C/span>\u003Cspan style=\"color:#79B8FF\"> open\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(crawl_export, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"r\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">encoding\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"utf-8\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) \u003C/span>\u003Cspan style=\"color:#F97583\">as\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> f:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        reader \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> csv.DictReader(f)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> row \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> reader:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">            url \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> row.get(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Address\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">            declared_lang \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> row.get(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Lang 1\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">).strip().lower()  \u003C/span>\u003Cspan style=\"color:#6A737D\"># custom extraction\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">            body_text \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> row.get(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Body Text 1\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)  \u003C/span>\u003Cspan style=\"color:#6A737D\"># si extrait\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">            \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            if\u003C/span>\u003Cspan style=\"color:#F97583\"> not\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> body_text \u003C/span>\u003Cspan style=\"color:#F97583\">or\u003C/span>\u003Cspan style=\"color:#79B8FF\"> len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(body_text) \u003C/span>\u003Cspan style=\"color:#F97583\">&#x3C;\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 100\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">                continue\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">            \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            try\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">                detected_lang \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> detect(body_text)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            except\u003C/span>\u003Cspan style=\"color:#79B8FF\"> Exception\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">                detected_lang \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"unknown\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">            \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">            # Normaliser : langdetect retourne 'ca' pour catalan, 'es' pour espagnol\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">            # Mais attention : langdetect confond souvent ca/es sur des textes courts\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> declared_lang \u003C/span>\u003Cspan style=\"color:#F97583\">and\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> detected_lang \u003C/span>\u003Cspan style=\"color:#F97583\">!=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> declared_lang:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">                # Vérifier si c'est une confusion connue (ca↔es, gl↔pt, etc.)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">                confused_pairs \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> [(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"ca\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"es\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">), (\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"gl\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"pt\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">), (\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"bs\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"hr\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">), (\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"no\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"da\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">                is_confused_pair \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\"> any\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">                    {declared_lang, detected_lang} \u003C/span>\u003Cspan style=\"color:#F97583\">==\u003C/span>\u003Cspan style=\"color:#79B8FF\"> set\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(pair) \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">                    for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> pair \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> confused_pairs\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">                )\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">                \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">                issues.append({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                    \"url\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: url,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                    \"declared\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: declared_lang,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                    \"detected\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: detected_lang,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                    \"is_confused_pair\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: is_confused_pair,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                    \"text_length\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(body_text),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                    \"severity\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"HIGH\"\u003C/span>\u003Cspan style=\"color:#F97583\"> if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> is_confused_pair \u003C/span>\u003Cspan style=\"color:#F97583\">else\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"MEDIUM\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">                })\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> issues\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Utilisation\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">issues \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> audit_language_consistency(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"screaming_frog_export.csv\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">high_severity \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> [i \u003C/span>\u003Cspan style=\"color:#F97583\">for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> i \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> issues \u003C/span>\u003Cspan style=\"color:#F97583\">if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> i[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"severity\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">==\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"HIGH\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">print\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#F97583\">f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#79B8FF\">{len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(high_severity)\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> pages avec confusion de langue à haut risque\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Le point clé : si \u003Ccode>langdetect\u003C/code> confond la langue de vos pages, les LLMs le font probablement aussi. Ces pages sont les premières candidates à une optimisation des signaux de langue.\u003C/p>\n\u003Ch2>Renforcer les signaux de langue au-delà de hreflang\u003C/h2>\n\u003Cp>Puisque les signaux déclaratifs (hreflang, \u003Ccode>lang\u003C/code>) ne sont pas suffisants dans le pipeline RAG, il faut renforcer les signaux implicites — ceux que le LLM peut capter pendant l'extraction et l'embedding du contenu.\u003C/p>\n\u003Ch3>Stratégie 1 : densifier les marqueurs linguistiques distinctifs\u003C/h3>\n\u003Cp>Chaque langue a des structures grammaticales et des mots-outils qui la distinguent de ses langues sœurs. En catalan, les articles \"el/la/els/les\", les pronoms faibles (\"em\", \"et\", \"en\", \"hi\"), et les suffixes verbaux (\"-ar\", \"-er\", \"-ir\" vs. \"-ar\", \"-er\", \"-ir\" en espagnol mais avec des conjugaisons différentes) sont des signaux distinctifs.\u003C/p>\n\u003Cp>L'idée n'est pas de sur-optimiser le texte — c'est de s'assurer que le contenu utilise naturellement les formes propres à la langue cible, plutôt que des calques de la langue dominante. En pratique, cela signifie :\u003C/p>\n\u003Cul>\n\u003Cli>Éviter le \"català light\" (catalan truffé d'hispanismes) dans les descriptions produit\u003C/li>\n\u003Cli>Utiliser les pronoms faibles catalans là où un locuteur natif les utiliserait\u003C/li>\n\u003Cli>Privilégier le vocabulaire spécifiquement catalan quand il existe (ex: \"ordinador\" vs. \"computadora\")\u003C/li>\n\u003C/ul>\n\u003Ch3>Stratégie 2 : structured data avec indication de langue explicite\u003C/h3>\n\u003Cp>Le JSON-LD permet de spécifier la langue du contenu de manière granulaire via la propriété \u003Ccode>inLanguage\u003C/code>. Ce signal est lisible par les crawlers traditionnels ET par les systèmes RAG qui parsent le structured data.\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">&#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">script\u003C/span>\u003Cspan style=\"color:#B392F0\"> type\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"application/ld+json\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">{\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  \"@context\": \"https://schema.org\",\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  \"@type\": \"Product\",\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  \"name\": \"Motxilla impermeable de muntanya 45L\",\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  \"description\": \"Motxilla lleugera amb teixit impermeable de 3 capes. Dissenyada per a rutes d'alta muntanya als Pirineus catalans.\",\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  \"inLanguage\": \"ca\",\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  \"brand\": {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \"@type\": \"Brand\",\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \"name\": \"Botiga Outdoor\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  \"offers\": {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \"@type\": \"Offer\",\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \"priceCurrency\": \"EUR\",\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \"price\": \"129.95\",\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \"availability\": \"https://schema.org/InStock\",\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \"areaServed\": {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      \"@type\": \"AdministrativeArea\",\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      \"name\": \"Catalunya\",\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      \"inDefinedTermSet\": \"NUTS\",\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      \"termCode\": \"ES51\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  \"isPartOf\": {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \"@type\": \"WebPage\",\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \"inLanguage\": \"ca\",\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \"url\": \"https://botiga-outdoor.cat/ca/motxilles/impermeable-45l\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">script\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Trois points techniques à noter dans ce markup :\u003C/p>\n\u003Col>\n\u003Cli>\u003Ccode>inLanguage: \"ca\"\u003C/code> est déclaré à la fois sur le Product et sur la WebPage — redondance intentionnelle pour maximiser la captation du signal\u003C/li>\n\u003Cli>\u003Ccode>areaServed\u003C/code> avec le code NUTS (ES51 = Catalunya) crée une association géographique explicite qui aide à la désambiguïsation\u003C/li>\n\u003Cli>La description produit utilise des termes spécifiquement catalans (\"lleugera\", \"teixit\", \"Pirineus catalans\") qui renforcent le signal linguistique implicite\u003C/li>\n\u003C/ol>\n\u003Ch3>Stratégie 3 : en-têtes HTTP Content-Language\u003C/h3>\n\u003Cp>Ce signal est souvent oublié alors qu'il est lu avant même le parsing HTML. Configurez-le au niveau du serveur.\u003C/p>\n\u003Cp>Pour Nginx, avec une architecture en sous-répertoires (\u003Ccode>/ca/\u003C/code>, \u003Ccode>/es/\u003C/code>) :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">server\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    listen \u003C/span>\u003Cspan style=\"color:#79B8FF\">443\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ssl;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    server_name \u003C/span>\u003Cspan style=\"color:#E1E4E8\">botiga-outdoor.cat;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # Langue par défaut\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    set \u003C/span>\u003Cspan style=\"color:#E1E4E8\">$content_lang \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"ca\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # Détection basée sur le path\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    location\u003C/span>\u003Cspan style=\"color:#F97583\"> ~\u003C/span>\u003Cspan style=\"color:#DBEDFF\"> ^/es/ \u003C/span>\u003Cspan style=\"color:#E1E4E8\">{\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        set \u003C/span>\u003Cspan style=\"color:#E1E4E8\">$content_lang \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"es\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    location\u003C/span>\u003Cspan style=\"color:#F97583\"> ~\u003C/span>\u003Cspan style=\"color:#DBEDFF\"> ^/fr/ \u003C/span>\u003Cspan style=\"color:#E1E4E8\">{\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        set \u003C/span>\u003Cspan style=\"color:#E1E4E8\">$content_lang \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"fr\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # Appliquer le header à toutes les réponses HTML\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    location\u003C/span>\u003Cspan style=\"color:#B392F0\"> / \u003C/span>\u003Cspan style=\"color:#E1E4E8\">{\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        # Ne pas ajouter Content-Language sur les assets statiques\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ($uri \u003C/span>\u003Cspan style=\"color:#F97583\">~* \u003C/span>\u003Cspan style=\"color:#E1E4E8\">\\.(css|js|png|jpg|svg|woff2)$) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            break\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        add_header \u003C/span>\u003Cspan style=\"color:#E1E4E8\">Content-Language $content_lang always;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        # Vary header pour que les CDN cachent par langue\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        add_header \u003C/span>\u003Cspan style=\"color:#E1E4E8\">Vary \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Accept-Language\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> always;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        proxy_pass \u003C/span>\u003Cspan style=\"color:#E1E4E8\">http://backend;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # Pages spécifiques : forcer le catalan sur la homepage\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    location\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#DBEDFF\"> / \u003C/span>\u003Cspan style=\"color:#E1E4E8\">{\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        add_header \u003C/span>\u003Cspan style=\"color:#E1E4E8\">Content-Language \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"ca\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> always;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        add_header \u003C/span>\u003Cspan style=\"color:#E1E4E8\">Vary \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Accept-Language\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> always;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        proxy_pass \u003C/span>\u003Cspan style=\"color:#E1E4E8\">http://backend;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>L'en-tête \u003Ccode>Content-Language\u003C/code> est spécifié dans la \u003Ca href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Language\">documentation MDN\u003C/a> comme un signal destiné aux systèmes de traitement automatique, pas uniquement aux navigateurs. C'est exactement ce dont les crawlers IA ont besoin.\u003C/p>\n\u003Ch2>Scénario réel : migration multilingue et effondrement des citations IA\u003C/h2>\n\u003Cp>Prenons un cas réaliste. BotigaraDeco est un e-commerce catalan de décoration intérieure : 12 400 pages produit, 850 pages catégorie, 320 pages éditoriales. Le site existe en catalan (ca) et espagnol (es), avec une architecture en sous-répertoires.\u003C/p>\n\u003Cp>\u003Cstrong>Situation initiale (janvier 2026) :\u003C/strong>\u003C/p>\n\u003Cul>\n\u003Cli>68% du trafic organique vient de requêtes en catalan\u003C/li>\n\u003Cli>Le site apparaît dans les AI Overviews pour 142 requêtes catalanes (mesuré via un suivi SERP hebdomadaire)\u003C/li>\n\u003Cli>Les citations AI pointent vers la version \u003Ccode>/ca/\u003C/code> dans 89% des cas\u003C/li>\n\u003C/ul>\n\u003Cp>\u003Cstrong>Événement déclencheur (février 2026) :\u003C/strong>\nL'équipe technique migre le frontend de Nuxt 2 vers Nuxt 3. Pendant la migration, un changement dans la configuration SSR modifie la façon dont la page est rendue : le composant \u003Ccode>&#x3C;html lang=\"ca\">\u003C/code> est maintenant injecté côté client au lieu d'être présent dans le HTML initial.\u003C/p>\n\u003Cp>\u003Cstrong>Impact mesuré sur 8 semaines :\u003C/strong>\u003C/p>\n\u003Cul>\n\u003Cli>Les citations AI en catalan chutent de 142 à 51 requêtes (-64%)\u003C/li>\n\u003Cli>Les citations vers \u003Ccode>/es/\u003C/code> pour des requêtes catalanes passent de 11% à 47%\u003C/li>\n\u003Cli>Le trafic organique catalan baisse de 23% (mesuré dans GA4 en segmentant par paramètre \u003Ccode>hl=ca\u003C/code> dans les referrers Search)\u003C/li>\n\u003Cli>Le trafic espagnol reste stable — le problème est asymétrique\u003C/li>\n\u003C/ul>\n\u003Cp>\u003Cstrong>Diagnostic :\u003C/strong> le SSR de Nuxt 3 générait le \u003Ccode>&#x3C;html>\u003C/code> initial sans attribut \u003Ccode>lang\u003C/code>, qui n'était ajouté qu'après hydration côté client. GoogleBot et les systèmes RAG qui crawlent le HTML pré-hydration voyaient une page sans signal de langue explicite. Combined avec le contenu textuel ambiguë (noms de produits internationaux, descriptions courtes), le modèle classifiait ces pages comme \"espagnol\" par défaut — la langue dominante de la région selon les données d'entraînement.\u003C/p>\n\u003Cp>\u003Cstrong>Correction :\u003C/strong>\u003C/p>\n\u003Cp>Dans \u003Ccode>nuxt.config.ts\u003C/code>, forcer la langue dans le rendu SSR :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// nuxt.config.ts\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">export\u003C/span>\u003Cspan style=\"color:#F97583\"> default\u003C/span>\u003Cspan style=\"color:#B392F0\"> defineNuxtConfig\u003C/span>\u003Cspan style=\"color:#E1E4E8\">({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  app: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    head: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      htmlAttrs: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        // Langue par défaut, sera overridée par le middleware\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        lang: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ca'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">  // Plugin SSR pour injecter la langue dynamiquement\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  nitro: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    plugins: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'~/server/plugins/lang-header.ts'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">})\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// server/plugins/lang-header.ts\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">export\u003C/span>\u003Cspan style=\"color:#F97583\"> default\u003C/span>\u003Cspan style=\"color:#B392F0\"> defineNitroPlugin\u003C/span>\u003Cspan style=\"color:#E1E4E8\">((\u003C/span>\u003Cspan style=\"color:#FFAB70\">nitroApp\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) \u003C/span>\u003Cspan style=\"color:#F97583\">=>\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  nitroApp.hooks.\u003C/span>\u003Cspan style=\"color:#B392F0\">hook\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'render:html'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, (\u003C/span>\u003Cspan style=\"color:#FFAB70\">html\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { \u003C/span>\u003Cspan style=\"color:#FFAB70\">event\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }) \u003C/span>\u003Cspan style=\"color:#F97583\">=>\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> path\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> event.path \u003C/span>\u003Cspan style=\"color:#F97583\">||\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> ''\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    let\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> lang \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'ca'\u003C/span>\u003Cspan style=\"color:#6A737D\"> // défaut catalan\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (path.\u003C/span>\u003Cspan style=\"color:#B392F0\">startsWith\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'/es/'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) \u003C/span>\u003Cspan style=\"color:#F97583\">||\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> path.\u003C/span>\u003Cspan style=\"color:#B392F0\">startsWith\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'/es?'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      lang \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'es'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    } \u003C/span>\u003Cspan style=\"color:#F97583\">else\u003C/span>\u003Cspan style=\"color:#F97583\"> if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (path.\u003C/span>\u003Cspan style=\"color:#B392F0\">startsWith\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'/fr/'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      lang \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'fr'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    // Injecter lang dans la balise html AVANT l'hydration client\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    html.htmlAttrs \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> html.htmlAttrs \u003C/span>\u003Cspan style=\"color:#F97583\">||\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> []\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    html.htmlAttrs.\u003C/span>\u003Cspan style=\"color:#B392F0\">push\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">`lang=\"${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">lang\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}\"`\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    // Ajouter le header HTTP également\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    event.node.res.\u003C/span>\u003Cspan style=\"color:#B392F0\">setHeader\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Content-Language'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, lang)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  })\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">})\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Après déploiement de la correction et re-crawl (forcé via l'API d'indexation de Search Console pour les 200 pages les plus critiques), les citations AI sont revenues au niveau initial en 4 semaines.\u003C/p>\n\u003Cp>Ce scénario illustre un problème que les \u003Ca href=\"/blog/the-tech-seo-audit-for-the-ai-search-era-how-to-maximize-your-ai-visibility-via-sejournal-jetoctopus\">audits SEO techniques classiques\u003C/a> ne couvrent pas encore : la cohérence des signaux de langue dans le HTML pré-hydration n'est testée ni par Lighthouse, ni par la plupart des outils de crawl qui exécutent JavaScript.\u003C/p>\n\u003Ch2>L'impact structurel sur les AI Overviews et la GEO\u003C/h2>\n\u003Cp>Ce que révèle le cas catalan dépasse la simple correction technique. C'est un signal d'un problème structurel dans la façon dont les moteurs IA traitent les contenus multilingues.\u003C/p>\n\u003Ch3>Le biais de la langue dominante\u003C/h3>\n\u003Cp>Les LLMs ont un biais statistique en faveur des langues pour lesquelles ils disposent de plus de données d'entraînement. L'espagnol dispose d'un corpus web environ 15 fois plus large que le catalan. Ce ratio crée un prior bayésien implicite : quand le modèle hésite entre \"c'est du catalan\" et \"c'est de l'espagnol\", il penche vers l'espagnol par défaut.\u003C/p>\n\u003Cp>En termes de SEO, cela signifie que les pages dans des langues minoritaires doivent fournir des signaux de langue \u003Cstrong>plus forts\u003C/strong> que les pages dans des langues dominantes pour obtenir la même attribution. L'asymétrie est structurelle et ne sera pas corrigée par un simple update d'algorithme.\u003C/p>\n\u003Ch3>La fragmentation des citations\u003C/h3>\n\u003Cp>Les \u003Ca href=\"/blog/the-5-layer-framework-for-measuring-geo-performance\">frameworks de mesure GEO\u003C/a> doivent intégrer la dimension linguistique. Quand une AI Overview cite une source en espagnol pour répondre à une requête catalane, trois choses se produisent :\u003C/p>\n\u003Col>\n\u003Cli>\u003Cstrong>L'utilisateur catalan reçoit une réponse dans une langue qu'il n'a pas demandée\u003C/strong> — dégradation de l'expérience utilisateur que Google dit vouloir éviter\u003C/li>\n\u003Cli>\u003Cstrong>Le site catalan perd la citation\u003C/strong> — même si son contenu est pertinent et mieux adapté\u003C/li>\n\u003Cli>\u003Cstrong>Les métriques de visibilité AI sont faussées\u003C/strong> — le site apparaît moins visible en catalan alors que le problème est côté moteur, pas côté contenu\u003C/li>\n\u003C/ol>\n\u003Cp>Ce phénomène est documenté de façon plus large dans la problématique de la \u003Ca href=\"/blog/how-negative-information-spreads-from-wikipedia-into-ai-search\">propagation d'information incorrecte dans l'IA de recherche\u003C/a> : quand le système RAG sélectionne la mauvaise source, il propage une erreur en cascade.\u003C/p>\n\u003Ch3>Le consensus gap linguistique\u003C/h3>\n\u003Cp>Le concept de \u003Ca href=\"/blog/the-consensus-gap-via-sejournal-kevin-indig\">consensus gap\u003C/a> prend une dimension nouvelle en contexte multilingue. Si les sources catalanes et espagnoles disent la même chose mais avec des nuances culturelles ou terminologiques, le LLM peut percevoir un désaccord artificiel et soit omettre la citation, soit créer une synthèse confuse.\u003C/p>\n\u003Cp>Exemple concret : pour la requête \"millors rutes senderisme Pirineus\" (meilleures randonnées Pyrénées), les sources catalanes mentionnent des noms de lieux en catalan (Aigüestortes, Cadí-Moixeró) tandis que les sources espagnoles utilisent parfois les noms castillans. Le LLM peut traiter ces noms comme des entités différentes, créant un consensus gap artificiel qui dilue la visibilité de toutes les sources.\u003C/p>\n\u003Ch2>Stratégie de monitoring continu pour les sites multilingues\u003C/h2>\n\u003Cp>La détection ponctuelle ne suffit pas. Les confusions de langue dans les résultats IA sont dynamiques — elles changent avec chaque mise à jour du modèle, chaque re-crawl, chaque modification du contenu.\u003C/p>\n\u003Ch3>Métriques à suivre\u003C/h3>\n\u003Cp>Ajoutez ces KPIs à votre \u003Ca href=\"/blog/how-to-measure-ai-search-current-kpis-you-need-to-know-webinar-via-sejournal-hethr-campbell\">tableau de bord de mesure de la visibilité IA\u003C/a> :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Language Match Rate\u003C/strong> : % des citations AI qui pointent vers la bonne version linguistique de votre site, par langue cible\u003C/li>\n\u003Cli>\u003Cstrong>Cross-Language Cannibalization\u003C/strong> : nombre de requêtes où la version es apparaît dans les AI Overviews à la place de la version ca (ou inversement)\u003C/li>\n\u003Cli>\u003Cstrong>Citation Language Drift\u003C/strong> : évolution du Language Match Rate sur le temps — un drift négatif indique un problème de crawl ou de mise à jour du modèle\u003C/li>\n\u003C/ul>\n\u003Ch3>Vérifier le rendu SSR par langue\u003C/h3>\n\u003Cp>Un test simple mais critique : vérifier que chaque version linguistique retourne les bons signaux dans le HTML initial, sans exécution JavaScript.\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Vérifier le HTML SSR pour la version catalane\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">curl\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -s\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -o\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> /dev/null\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -D\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> -\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"https://botiga-outdoor.cat/ca/motxilles/\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  -H\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1)\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -i\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"content-language\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Extraire l'attribut lang du HTML pré-hydration\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">curl\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -s\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"https://botiga-outdoor.cat/ca/motxilles/\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  -H\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1)\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -oP\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '&#x3C;html[^>]*lang=\"[^\"]*\"'\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#B392F0\"> head\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -1\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Comparer avec la version espagnole\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">curl\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -s\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"https://botiga-outdoor.cat/es/mochilas/\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  -H\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1)\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -oP\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '&#x3C;html[^>]*lang=\"[^\"]*\"'\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#B392F0\"> head\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -1\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Vérifier le hreflang dans le HTML (pas dans le sitemap uniquement)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">curl\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -s\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"https://botiga-outdoor.cat/ca/motxilles/\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  -H\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1)\"\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -oP\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'hreflang=\"[^\"]*\"'\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#B392F0\"> sort\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -u\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Exécutez ces vérifications après chaque déploiement. Si le \u003Ccode>lang\u003C/code> disparaît ou si le \u003Ccode>Content-Language\u003C/code> ne correspond plus au path, vous avez une régression critique — le type exact de régression que Seogard détecte automatiquement en comparant le HTML rendu entre deux crawls.\u003C/p>\n\u003Ch3>Anticiper les prochaines mises à jour\u003C/h3>\n\u003Cp>Le \u003Ca href=\"/blog/google-begins-rolling-out-may-2026-core-update-via-sejournal-mattgsouthern\">core update de mai 2026\u003C/a> est en cours de déploiement au moment de la publication de cet article. Historiquement, les core updates modifient les poids relatifs des signaux utilisés dans le retrieval. Pour les sites multilingues, chaque core update est un risque de voir les seuils de language identification bouger.\u003C/p>\n\u003Cp>La stratégie défensive : maintenir un snapshot hebdomadaire de vos citations AI par langue, et corréler toute variation avec les dates de rollout des updates. Si votre Language Match Rate chute de plus de 5 points pendant un update, c'est un signal que les critères de sélection de langue dans le RAG ont changé — et que votre contenu a besoin de signaux de langue plus forts.\u003C/p>\n\u003Ch2>Au-delà du catalan : ce que ça change pour tout le SEO multilingue\u003C/h2>\n\u003Cp>Le cas catalan est un canari dans la mine. Aujourd'hui, ce sont les langues minoritaires qui souffrent. Demain, à mesure que les AI Overviews s'étendent et que \u003Ca href=\"/blog/google-agent-the-web-s-new-visitor-just-got-an-identity-via-sejournal-slobodanmanic\">les agents IA deviennent des visiteurs réguliers du web\u003C/a>, le problème touchera des marchés plus larges.\u003C/p>\n\u003Cp>Les sites opérant en français canadien vs. français européen, en portugais brésilien vs. européen, ou même en anglais britannique vs. américain, verront des phénomènes similaires — plus subtils, mais avec un impact commercial réel sur la pertinence des réponses IA pour leur audience.\u003C/p>\n\u003Cp>La recommandation technique est claire : ne traitez plus la langue comme un signal déclaratif passif. Traitez-la comme un signal actif qui doit être renforcé à chaque couche — HTML, HTTP headers, structured data, contenu textuel, et monitoring continu. Les sites qui maîtrisent cette stack multilingue complète seront ceux que les LLMs citent correctement. Les autres seront invisibles dans les réponses IA, remplacés par la version linguistique dominante de leur marché.\u003C/p>",null,12,[18,19,20,21,22],"multilingual SEO","AI search","hreflang","language identification","GEO","Multilingual regions expose AI search's language ID crisis","Fri May 22 2026 10:03:11 GMT+0000 (Coordinated Universal Time)",[26,40,54],{"_id":27,"slug":28,"__v":6,"author":7,"canonical":29,"category":10,"createdAt":30,"date":12,"description":31,"image":15,"imageAlt":15,"readingTime":16,"tags":32,"title":38,"updatedAt":39},"6a0ff1a4aa6b273b0c0b58f7","google-may-2026-core-update-rolling-out-now","https://seogard.io/blog/google-may-2026-core-update-rolling-out-now","2026-05-22T06:03:16.605Z","Deuxième core update de 2026 : ce qui change, comment diagnostiquer l'impact sur vos pages, et les actions techniques à mener pendant le rollout.",[33,34,35,36,37],"google","core update","2026","SEO technique","rolling update","Google May 2026 Core Update : analyse technique et plan d'action","Fri May 22 2026 06:03:16 GMT+0000 (Coordinated Universal Time)",{"_id":41,"slug":42,"__v":6,"author":7,"canonical":43,"category":10,"createdAt":44,"date":12,"description":45,"image":15,"imageAlt":15,"readingTime":16,"tags":46,"title":52,"updatedAt":53},"6a107024aa6b273b0c73b9c9","google-launches-core-update-amid-i-o-ai-search-overhaul-seo-pulse-via-sejournal-mattgsouthern","https://seogard.io/blog/google-launches-core-update-amid-i-o-ai-search-overhaul-seo-pulse-via-sejournal-mattgsouthern","2026-05-22T15:03:00.080Z","Analyse technique du core update mai 2026, du redesign AI Search annoncé à Google I/O, et des signaux contradictoires sur llms.txt. Actions concrètes pour les SEO.",[47,48,49,50,51,36],"google core update","AI Search","Google I/O 2026","AI Mode","llms.txt","Core Update mai 2026 et refonte AI Search : analyse technique","Fri May 22 2026 15:03:00 GMT+0000 (Coordinated Universal Time)",{"_id":55,"slug":56,"__v":6,"author":7,"canonical":57,"category":10,"createdAt":58,"date":12,"description":59,"image":15,"imageAlt":15,"readingTime":16,"tags":60,"title":64,"updatedAt":65},"6a109a64aa6b273b0c9696bb","what-makes-a-brand-machine-readable-in-ai-search","https://seogard.io/blog/what-makes-a-brand-machine-readable-in-ai-search","2026-05-22T18:03:16.018Z","19 audits révèlent le même problème : l'expertise existe mais l'IA ne la lit pas. Voici comment rendre votre marque machine-readable pour l'AI search.",[19,61,62,63,22],"structured data","brand visibility","machine-readable","Brand machine-readable : rendre votre marque lisible par l'IA","Fri May 22 2026 18:03:16 GMT+0000 (Coordinated Universal Time)"]