# General Translation Knowledge Base: Comment détecter la langue du navigateur d’un utilisateur ?
URL: https://generaltranslation.com/fr/kb/detect-user-language.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Lisez la liste ordonnée des langues du navigateur (navigator.languages en JavaScript ou l’en-tête Accept-Language côté serveur), triée par valeur q, puis mettez-la en correspondance avec vos paramètres régionaux pris en charge, en prévoyant une solution de secours. Safari et la navigation privée de Chrome n’indiquent qu’une seule langue, et la géolocalisation par IP révèle l’emplacement, pas la langue.
Category: Internationalisation
Last updated: 2026-10-20
Authors: Kevin Wu, Jackie Chen
Published: 2026-10-20

Le navigateur détient déjà la réponse : une liste ordonnée des langues que l’utilisateur a définies dans son navigateur ou son système d’exploitation. Tout l’enjeu consiste à lire correctement cette liste, à la mettre en correspondance avec les paramètres régionaux pour lesquels vous disposez de traductions, et à savoir quand un signal plus fort doit l’emporter.

## D&#39;où vient la préférence linguistique d&#39;un utilisateur ?

Cinq signaux peuvent vous indiquer la langue à afficher. Seuls certains d&#39;entre eux concernent réellement la langue.

| Signal                            | Où le lire                                  | Ce qu&#39;il indique                             | Fiabilité                                                                       | À utiliser pour                                                    |
| --------------------------------- | ------------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `navigator.languages`             | JavaScript du navigateur                    | Préférences linguistiques, par ordre de priorité | Bonne, mais certains navigateurs n&#39;exposent qu&#39;une seule langue         | Pages rendues côté client lors d&#39;une première visite           |
| En-tête `Accept-Language`         | Chaque requête HTTP                         | La même liste, pondérée par des valeurs q        | Bonne, mais l&#39;utilisateur en déplacement n&#39;a souvent pas la main dessus | Pages server-rendered lors d&#39;une première visite               |
| Paramètre régional dans l&#39;URL | Le chemin de la requête, par exemple `/fr/` | La version demandée par le visiteur              | Élevée                                                                          | Pages publiques et partageables                                    |
| Préférence enregistrée            | Un cookie ou un paramètre de compte         | Un choix fait précédemment par l&#39;utilisateur | Élevée                                                                          | Visites ultérieures                                                |
| Géolocalisation IP                | Un service de géolocalisation               | Localisation approximative                       | Pas un signal linguistique                                                      | Valeurs par défaut régionales, comme la devise, mais pas la langue |

### Dans le navigateur : `navigator.language` et `navigator.languages`

[`navigator.languages`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/languages) renvoie les langues préférées de l&#39;utilisateur sous forme de balises BCP 47, par ordre de préférence décroissant, et [`navigator.language`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/language) correspond au premier élément de ce tableau :

```js
navigator.language;  // "en-US"
navigator.languages; // ["en-US", "zh-CN", "ja-JP"]
```

Lorsque l&#39;utilisateur modifie ses préférences, le navigateur déclenche un événement `languagechange` sur `window`. Pour limiter le fingerprinting (empreinte numérique du navigateur), Safari ne transmet toujours qu&#39;une seule langue, tout comme Chrome en navigation privée : partez donc du principe que la liste peut être incomplète par rapport aux préférences réelles de l&#39;utilisateur.

### Côté serveur : l&#39;en-tête `Accept-Language`

Les navigateurs transmettent ces mêmes préférences à chaque requête. La [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-accept-language) définit cet en-tête comme une liste de plages de langues séparées par des virgules, chacune pouvant être assortie d&#39;une valeur de qualité (`q`) facultative comprise entre 0 et 1. En l&#39;absence de `q`, la valeur est 1, et `q=0` signifie « non acceptable ». Voici l&#39;exemple donné par la RFC elle-même :

```http
Accept-Language: da, en-gb;q=0.8, en;q=0.7
```

Autrement dit : « Je préfère le danois, mais j&#39;accepte l&#39;anglais britannique et les autres variantes de l&#39;anglais. » Le caractère générique `*` [représente](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Language) toute langue non listée. Les navigateurs ajoutent aussi des solutions de repli limitées à la langue seule : selon [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/languages), Chrome et Safari envoient `en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7` pour un utilisateur dont `navigator.languages` vaut `["en-US", "zh-CN"]`. Triez les entrées par `q`, car la RFC indique qu&#39;on « ne peut pas se fier » à l&#39;ordre de la liste.

L&#39;en-tête n&#39;est qu&#39;une indication. MDN précise qu&#39;un serveur « ne devrait jamais remplacer un choix de langue explicite de l&#39;utilisateur » et souligne que l&#39;en-tête échappe souvent au contrôle de l&#39;utilisateur, par exemple lorsqu&#39;il est en voyage. Si vous mettez en cache des pages sélectionnées d&#39;après l&#39;en-tête, ajoutez [`Vary: Accept-Language`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Vary) pour éviter qu&#39;un cache ne serve à un visiteur la langue du précédent.

### Pourquoi la géolocalisation IP est le mauvais indicateur

Un lieu n&#39;est pas une langue. Comme le [souligne](https://www.w3.org/International/questions/qa-link-lang) le W3C, « de nombreux pays ont plus d&#39;une langue officielle », et les voyageurs, les expatriés ou les utilisateurs de VPN lisent une langue que leur adresse IP ne permet pas de deviner. Google est catégorique : « N&#39;utilisez pas l&#39;analyse de l&#39;adresse IP pour adapter votre contenu », notamment parce que « le robot d&#39;exploration Googlebot provient généralement des États-Unis », selon ses [recommandations sur les sites multirégionaux](https://developers.google.com/search/docs/specialty/international/managing-multi-regional-sites). Une page qui choisit sa langue en fonction de l&#39;adresse IP ne montre qu&#39;une seule version aux robots d&#39;exploration et masque toutes les autres. La position géographique peut néanmoins servir à choisir une devise ou une région de livraison par défaut pertinente ; veillez simplement à dissocier cette décision de celle de la langue.

## Faire correspondre les préférences à vos paramètres régionaux

Un utilisateur qui demande `pt-PT` sur un site proposant `pt-BR` a besoin d’une règle de repli. Il existe deux algorithmes, dont les [API `Intl`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) reprennent les noms :

* **Lookup** suit la [RFC 4647](https://www.rfc-editor.org/rfc/rfc4647.html#section-3.4) : chaque tag préféré est essayé tour à tour, en retirant les sous-balises en partant de la fin (`zh-Hant-CN`, puis `zh-Hant`, puis `zh`) jusqu’à obtenir une correspondance ; à défaut, la valeur par défaut est renvoyée.
* **Best fit** permet à l’implémentation de renvoyer un paramètre régional « au moins aussi adapté, voire davantage » que celui que renverrait lookup, par exemple une variante régionale voisine.

La différence se manifeste avec les variantes régionales. Avec [`@formatjs/intl-localematcher`](https://www.npmjs.com/package/@formatjs/intl-localematcher) 0.9.0, appliqué à `['en', 'fr', 'de', 'pt-BR']` avec `en` par défaut :

| Demandé       | Lookup | Best fit |
| ------------- | ------ | -------- |
| `pt-PT`       | `en`   | `pt-BR`  |
| `en-GB`       | `en`   | `en`     |
| `fr-CA`, `de` | `fr`   | `fr`     |
| `zh-TW`       | `en`   | `en`     |

Lookup est prévisible et facile à expliquer. Best fit sert à davantage de personnes une langue qu’elles comprennent ; quant à savoir si un lecteur portugais préfère le portugais brésilien à l’anglais, c’est une décision produit. Testez donc les deux avec votre liste de paramètres régionaux.

## Exemple : négociation côté serveur

Ce serveur Node.js vérifie d&#39;abord l&#39;URL, puis un cookie enregistré, et enfin l&#39;en-tête. Il utilise [`negotiator`](https://www.npmjs.com/package/negotiator) 1.1.0 pour analyser `Accept-Language` et en trier les valeurs par facteur q, puis `@formatjs/intl-localematcher` pour faire correspondre le résultat avec l&#39;algorithme « best fit ».

```bash
npm install negotiator @formatjs/intl-localematcher
```

```js title="server.mjs"
import { createServer } from 'node:http';
import Negotiator from 'negotiator';
import { match } from '@formatjs/intl-localematcher';

const SUPPORTED = ['en', 'fr', 'de', 'pt-BR'];
const DEFAULT_LOCALE = 'en';

// Écarter « * » et les étiquettes mal formées : match() lève une exception RangeError sur celles-ci.
function isValidTag(tag) {
  try {
    Intl.getCanonicalLocales(tag);
    return true;
  } catch {
    return false;
  }
}

function pickLocale(req) {
  // 1. Un paramètre régional dans le chemin de l'URL, comme /fr/pricing
  const { pathname } = new URL(req.url, 'http://localhost');
  const fromPath = pathname.split('/')[1];
  if (SUPPORTED.includes(fromPath)) return { locale: fromPath, source: 'url' };

  // 2. Un choix enregistré lors d'une visite précédente
  const fromCookie = /(?:^|;\s*)locale=([^;]+)/.exec(req.headers.cookie ?? '')?.[1];
  if (SUPPORTED.includes(fromCookie)) return { locale: fromCookie, source: 'cookie' };

  // 3. Accept-Language, trié par valeur q, puis mis en correspondance avec vos paramètres régionaux
  const requested = new Negotiator(req).languages().filter(isValidTag);
  if (requested.length === 0) return { locale: DEFAULT_LOCALE, source: 'default' };
  return { locale: match(requested, SUPPORTED, DEFAULT_LOCALE), source: 'header' };
}

createServer((req, res) => {
  const { locale, source } = pickLocale(req);
  res.setHeader('Content-Language', locale);
  res.setHeader('Vary', 'Accept-Language, Cookie');
  res.end(`${locale} (from ${source})\n`);
}).listen(3000);
```

Lancez-le avec `node server.mjs`, puis envoyez des requêtes avec `curl -H "Accept-Language: ..." localhost:3000/`. Résultat sous Node.js 24 :

```text
da, en-gb;q=0.8, en;q=0.7                      -> en (from header)
pt-PT,pt;q=0.9,en;q=0.8                        -> pt-BR (from header)
fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5   -> fr (from header)
ja, de;q=0.5                                   -> de (from header)
en;q=0.5, de                                   -> de (from header)
*                                              -> en (from default)
(no header)                                    -> en (from default)
fr, with cookie locale=de                      -> de (from cookie)
/fr/pricing, with cookie locale=de             -> fr (from url)
```

Le filtre `isValidTag` est essentiel : sans lui, un en-tête contenant `*` fait lever à `match()` l&#39;erreur `RangeError: Incorrect locale information provided`, ce qui ferait planter la requête.

## Exemple : détection dans le navigateur

Sur une page rendue côté client, vous pouvez appliquer le même principe à `navigator.languages`. Cette version implémente une recherche RFC 4647 simple, sans aucune dépendance :

```js title="detect-locale.js"
const SUPPORTED = ['en', 'fr', 'de', 'pt-BR'];
const DEFAULT_LOCALE = 'en';

// Recherche : tester chaque balise préférée, en retirant les sous-balises en partant de la fin.
function lookup(requested, supported, fallback) {
  const lower = supported.map((tag) => tag.toLowerCase());
  for (const tag of requested) {
    const parts = tag.split('-');
    while (parts.length) {
      const i = lower.indexOf(parts.join('-').toLowerCase());
      if (i !== -1) return supported[i];
      parts.pop();
    }
  }
  return fallback;
}

function preferredLocale(nav) {
  const requested = nav.languages?.length ? nav.languages : [nav.language];
  return lookup(requested, SUPPORTED, DEFAULT_LOCALE);
}

// Dans le navigateur : preferredLocale(navigator)
```

Résultat obtenu avec des exemples de listes de préférences, exécuté sous Node.js 24 avec un objet `navigator` simulé :

```text
["fr-CA","en-US"] -> fr
["pt-BR","pt"]    -> pt-BR
["pt-PT","en"]    -> en
["ja-JP"]         -> en
["de-AT","de"]    -> de
```

Vérifiez s&#39;il existe un choix enregistré avant d&#39;appeler `preferredLocale`, et servez-vous du résultat comme point de départ : affichez le contenu dans cette langue et proposez le sélecteur de langue. Ne vous fondez pas sur ce résultat pour effectuer une redirection.

## Quel signal doit l&#39;emporter ?

La détection ne fournit qu&#39;une estimation lors de la première visite. Lorsque les signaux divergent, appliquez l&#39;ordre suivant :

1. **L&#39;URL.** Un lien `/de/` partagé affiche l&#39;allemand, et une langue choisie dans le sélecteur mène à l&#39;URL de cette langue.
2. **Le choix enregistré.** Un cookie ou un paramètre de compte. Il détermine vers où une racine neutre redirige l&#39;utilisateur ou ce qu&#39;il convient de lui suggérer, mais ne réécrit jamais une URL localisée.
3. **L&#39;en-tête `Accept-Language`**, ou `navigator.languages` côté client. À utiliser uniquement en l&#39;absence de choix enregistré, et seulement à la racine ou sous forme de suggestion.
4. **Les paramètres régionaux par défaut.** La solution de secours lorsque rien ne correspond.

Le W3C recommande d&#39;utiliser l&#39;en-tête comme [point de départ](https://www.w3.org/International/questions/qa-accept-lang-locales), de permettre aux utilisateurs de changer de langue et de stocker le résultat « dans une base de données ou un cookie pour les visites ultérieures ». Les redirections, la mise en cache et le SEO liés à cet ordre sont abordés dans [routage selon les paramètres régionaux et SEO](/kb/locale-routing-seo), et le sélecteur lui-même dans [créer un sélecteur de langue](/kb/language-switcher).

## Comment gt-next, gt-react et gt-node résolvent le paramètre régional

Dans `gt-next` de General Translation, [`createNextMiddleware`](/docs/react/nextjs/reference/functions/create-next-middleware) résout le paramètre régional de chaque requête dans l&#39;ordre suivant : un paramètre régional que le visiteur vient de sélectionner (suivi par un cookie de réinitialisation), le paramètre régional de l&#39;URL (par exemple `/es/about`), le cookie de paramètre régional enregistré, un cookie de paramètre régional de référent provenant de la route client précédente, l&#39;en-tête `Accept-Language` et enfin votre `defaultLocale`. Par défaut, le résultat est enregistré dans le cookie `generaltranslation.locale`, que vous pouvez renommer via [`headersAndCookies`](/docs/react/nextjs/config#headers-and-cookies). Définissez [`ignoreBrowserLocales`](/docs/react/nextjs/config#ignore-browser-locales) pour ignorer complètement l&#39;en-tête.

Lorsqu&#39;un paramètre régional demandé n&#39;est pas pris en charge, les bibliothèques choisissent d&#39;abord la meilleure correspondance prise en charge parmi les préférences du navigateur, puis un paramètre régional de la même langue (`en-GB` pour `en-US`), et enfin celui par défaut. Récupérez le résultat avec [`useLocale`](/docs/react/reference/hooks/use-locale), ou avec [`getLocale`](/docs/react/nextjs/reference/functions/get-locale) depuis `gt-next/server` dans du code asynchrone.

Dans une application monopage React, [`initializeGTSPA`](/docs/react/reference/config#initialize-spa) de `gt-react` résout le paramètre régional à partir des cookies et du navigateur avant le premier rendu. Sur un serveur Node.js, [`getRequestLocale`](/docs/node/reference/functions/get-request-locale) de `gt-node` négocie `Accept-Language` en fonction des locales passées à [`initializeGT`](/docs/node/reference/functions/initialize-gt). Avec `en`, `fr`, `de` et `pt-BR` configurés, la fonction a renvoyé `fr` pour `fr-CH, fr;q=0.9, en;q=0.8` et `pt-BR` pour `pt-PT,pt;q=0.9`. Ajoutez en amont une vérification du cookie ou du chemin, comme le montre le [guide de détection du paramètre régional de gt-node](/docs/node/guides/detecting-locale).

## Points clés

* `navigator.languages` et `Accept-Language` transmettent les mêmes préférences, dans le même ordre ; Safari et la navigation privée de Chrome n&#39;exposent qu&#39;une seule langue.
* Analysez les valeurs q (de 0 à 1, 1 par défaut), triez les langues selon ces valeurs, puis écartez `*` et les tags mal formés avant la mise en correspondance ; `@formatjs/intl-localematcher` lève une exception sur `*`.
* L&#39;algorithme lookup associe `pt-PT` à votre paramètre régional par défaut si vous ne disposez que de `pt-BR` ; l&#39;algorithme best fit l&#39;associe à `pt-BR`. Faites ce choix en connaissance de cause.
* Résolvez la langue dans cet ordre : URL, choix enregistré, `Accept-Language`, puis valeur par défaut. La géolocalisation IP n&#39;est jamais un indicateur de langue.
* Après la vérification d&#39;un paramètre régional tout juste sélectionné, `gt-next` vérifie l&#39;URL, le cookie `generaltranslation.locale`, `Accept-Language`, puis `defaultLocale`.

## Related questions

- [Redirections linguistiques et SEO : URL, cookie ou Accept-Language ?](https://generaltranslation.com/fr/kb/locale-routing-seo.mdx): Laissez l’URL déterminer la langue de chaque page que vous souhaitez faire indexer, car Googlebot n’envoie aucun en-tête Accept-Language et explore principalement depuis des adresses américaines. Ne vous servez du choix enregistré dans un cookie, puis d’Accept-Language, que pour décider vers où redirige une racine neutre comme /, avec une redirection temporaire 302 ou 307.
- [Bonnes pratiques pour le sélecteur de langue : UX, SEO et accessibilité](https://generaltranslation.com/fr/kb/language-switcher.mdx): Affichez chaque langue sous son propre nom, comme Deutsch ou 日本語, et bannissez les drapeaux, qui représentent des pays. Faites pointer chaque option vers la même page dans cette langue grâce à un lien explorable par les robots, mémorisez le choix dans un cookie et ne redirigez jamais automatiquement une URL localisée.
- [Qu’est-ce que hreflang et comment le configurer ?](https://generaltranslation.com/fr/kb/what-is-hreflang.mdx): Hreflang indique à Google quelles URL correspondent aux versions linguistiques ou régionales d’une même page, afin qu’il affiche la bonne dans les résultats de recherche. Ajoutez-le à l’aide de balises link HTML, d’en-têtes HTTP Link ou d’un plan de site, veillez à ce que chaque lien soit réciproque et utilisez des codes valides comme en-GB, et non en-UK.
- [Comment rendre un site web multilingue ?](https://generaltranslation.com/fr/kb/make-website-multilingual.mdx): Choisissez où stocker les traductions (dans un proxy, un plugin de CMS, un constructeur de sites ou votre propre code), puis attribuez à chaque langue sa propre URL, comme le recommande Google. Traduisez les pages entières ainsi que les titres et les méta-descriptions, reliez les versions avec hreflang et proposez un sélecteur de langue plutôt que de rediriger automatiquement les visiteurs.

## Previous and next questions

- Previous question: [Bonnes pratiques pour le sélecteur de langue : UX, SEO et accessibilité](https://generaltranslation.com/fr/kb/language-switcher.mdx)
- Next question: [Comment localiser une app iOS avec les String Catalogs ?](https://generaltranslation.com/fr/kb/localize-ios-app.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/fr/kb/sitemap.md) for every article.
