Que nous développions un simple blog ou une application monopage (SPA) sophistiquée et moderne, il arrive souvent qu’en réfléchissant à l’internationalisation d’une application web, nous soyons confrontés à une question importante : comment détecter la préférence linguistique d’un utilisateur ? C’est important, car nous voulons toujours offrir la meilleure expérience utilisateur. Or, si l’utilisateur a défini un ensemble de langues préférées dans son navigateur, nous voulons faire de notre mieux pour lui proposer notre contenu dans ces langues préférées.
Dans cet article, nous allons passer en revue trois façons différentes de détecter les paramètres régionaux d’un utilisateur : via l’objet navigator.languages du navigateur (côté client), via l’en-tête HTTP Accept-Language (côté serveur) et enfin via la géolocalisation en utilisant l’adresse IP de l’utilisateur (côté serveur).
Côté client : objet navigator.languages
Les navigateurs modernes fournissent un objet navigator.languages que nous pouvons utiliser pour obtenir toutes les langues préférées que l’utilisateur a définies dans son navigateur.

Paramètres de langue dans Firefox
Étant donné les paramètres ci-dessus, si nous devions ouvrir la console Firefox et vérifier la valeur de navigator.languages, nous obtiendrions ce qui suit :

Les codes des paramètres régionaux correspondent à ceux de nos paramètres de navigateur
navigator.languages est disponible dans tous les navigateurs web modernes et est généralement fiable. Nous allons écrire une fonction JavaScript réutilisable qui nous indique la ou les langues préférées de l’utilisateur actuel.
function getBrowserLocales(options = {}) {
const defaultOptions = {
languageCodeOnly: false,
};
const opt = {
...defaultOptions,
...options,
};
const browserLocales =
navigator.languages === undefined
? [navigator.language]
: navigator.languages;
if (!browserLocales) {
return undefined;
}
return browserLocales.map(locale => {
const trimmedLocale = locale.trim();
return opt.languageCodeOnly
? trimmedLocale.split(/-|_/)[0]
: trimmedLocale;
});
}
getBrowserLocales() vérifie la liste navigator.languages, en revenant à navigator.language si aucune liste n’est disponible. Il convient de noter que dans certains navigateurs, comme Chrome, navigator.language sera la langue de l’IU, qui est probablement la langue utilisée pour le système d’exploitation. C’est différent de navigator.languages, qui contient les langues préférées définies par l’utilisateur dans le navigateur lui-même.
✋🏽 Avertissement » Si vous prenez en charge Internet Explorer, vous devrez utiliser les propriétés navigator.userLanguage et navigaor.browserLanguage. Bien sûr, vous devrez également remplacer toutes les instances de const par var dans le code ci-dessus.
Notre fonction propose également une option pratique languageCodeOnly, qui supprime les codes de pays des paramètres régionaux avant de les renvoyer. Cela peut être utile lorsque notre application ne gère pas vraiment les nuances régionales d’une langue, par exemple, si nous n’avons qu’une seule version du contenu en anglais.

languageCodeOnly: true permet d’obtenir les langues sans les codes pays
Côté Serveur : en-tête HTTP Accept-Language
Si l’utilisateur définit ses préférences de langue dans un navigateur moderne, le navigateur enverra, à son tour, un en-tête HTTP qui transmet ces préférences de langue au serveur avec chaque requête. C’est l’en-tête Accept-Language et il ressemble souvent à ceci : Accept-Language: en-CA,ar-EG;q=0.5.
L’en-tête liste les langues préférées de l’utilisateur, avec une pondération définie par une valeur q attribuée à chacune. Lorsqu’une valeur explicite de q n’est pas spécifiée, une valeur par défaut de 1.0 est utilisée. Ainsi, dans la valeur d’en-tête ci-dessus, le client indique que l’utilisateur préfère l’anglais canadien (avec une pondération q = 1.0), puis l’arabe égyptien (avec une pondération q = 0.5).
Nous pouvons utiliser cet en-tête HTTP standard pour déterminer les paramètres régionaux préférés de l’utilisateur. Pour ce faire, nous allons écrire une classe appelée HttpAcceptLanguageHeaderLocaleDetector. Nous utiliserons PHP ici, mais vous pouvez utiliser le langage de votre choix ; l’en-tête Accept-Language devrait être le même (ou similaire) dans tous les environnements.
<?php
class HttpAcceptLanguageHeaderLocaleDetector
{
const HTTP_ACCEPT_LANGUAGE_HEADER_KEY = 'HTTP_ACCEPT_LANGUAGE';
public static function detect()
{
$httpAcceptLanguageHeader = static::getHttpAcceptLanguageHeader();
if ($httpAcceptLanguageHeader == null) {
return [];
}
$locales = static::getWeightedLocales($httpAcceptLanguageHeader);
$sortedLocales = static::sortLocalesByWeight($locales);
return array_map(function ($weightedLocale) {
return $weightedLocale['locale'];
}, $sortedLocales);
}
private static function getHttpAcceptLanguageHeader()
{
if (isset($_SERVER[static::HTTP_ACCEPT_LANGUAGE_HEADER_KEY])) {
return trim($_SERVER['HTTP_ACCEPT_LANGUAGE']);
} else {
return null;
}
}
private static function getWeightedLocales($httpAcceptLanguageHeader)
{
if (strlen($httpAcceptLanguageHeader) == 0) {
return [];
}
$weightedLocales = [];
// We break up the string 'en-CA,ar-EG;q=0.5' along the commas,
// and iterate over the resulting array of individual locales. Once
// we're done, $weightedLocales should look like
// [['locale' => 'en-CA', 'q' => 1.0], ['locale' => 'ar-EG', 'q' => 0.5]]
foreach (explode(',', $httpAcceptLanguageHeader) as $locale) {
// separate the locale key ("ar-EG") from its weight ("q=0.5")
$localeParts = explode(';', $locale);
$weightedLocale = ['locale' => $localeParts[0]];
if (count($localeParts) == 2) {
// explicit weight e.g. 'q=0.5'
$weightParts = explode('=', $localeParts[1]);
// grab the '0.5' bit and parse it to a float
$weightedLocale['q'] = floatval($weightParts[1]);
} else {
// no weight given in string, ie. implicit weight of 'q=1.0'
$weightedLocale['q'] = 1.0;
}
$weightedLocales[] = $weightedLocale;
}
return $weightedLocales;
}
/**
* Sort by high to low `q` value
*/
private static function sortLocalesByWeight($locales)
{
usort($locales, function ($a, $b) {
// usort will cast float values that we return here into integers,
// which can mess up our sorting. So instead of subtracting the `q`,
// values and returning the difference, we compare the `q` values and
// explicitly return integer values.
if ($a['q'] == $b['q']) {
return 0;
}
if ($a['q'] > $b['q']) {
return -1;
}
return 1;
});
return $locales;
}
}
Ce long bout de code n’est en fait pas très compliqué. Dans la seule méthode publique, detect(), notre classe fait ce qui suit :
- Elle obtient la valeur brute de la chaîne de l’en-tête
Accept-Language, par exemple"en-CA,ar-EG;q=0.5" - Elle utilise la méthode d’aide
getWeightedLocales()pour analyser la chaîne d’en-tête sous forme d’une liste qui ressemble à[['locale' => 'en-CA', 'q' => 1.0], ['locale' => 'ar-EG', 'q' => 0.5]]. - Elle utilise la méthode d’aide
sortLocalesByWeight()pour trier la liste ci-dessus de la valeurqla plus élevée à la plus basse. - Elle extrait les valeurs
localede la liste triée, renvoyant une liste qui ressemble à['en-CA', 'ar-EG'].
Nous pouvons maintenant utiliser notre nouvelle classe pour obtenir une liste exploitable de codes de paramètres régionaux basés sur l’en-tête HTTP Accept-Language.
<?php $locales = HttpAcceptLanguageHeaderLocaleDetector::detect(); // => ['en-CA', 'ar-EG']
Côté serveur : géolocalisation à l’aide de l’adresse IP
Parfois, l’en-tête Accept-Language ne sera pas présent dans les requêtes à notre serveur. Dans ces cas-là, nous pourrions vouloir utiliser l’adresse IP de l’utilisateur pour déterminer le pays de l’utilisateur et en déduire les paramètres régionaux ou la langue de ce pays.
✋🏽 Avertissement » La géolocalisation doit être utilisée en dernier recours pour détecter les paramètres régionaux de l’utilisateur, car elle peut souvent conduire à une détermination incorrecte des paramètres régionaux. Par exemple, si nous voyons que notre utilisateur vient du Canada, devons-nous supposer que sa langue préférée est l’anglais ou le français ? Les deux sont des langues officielles, largement utilisées dans le pays. Bien sûr, l’utilisateur pourrait appartenir à une minorité arabophone ou être un visiteur hispanophone.
Utilisation de MaxMind pour la géolocalisation
Pour déterminer le pays de l’utilisateur à l’aide de l’adresse IP de la requête, nous utiliserons l’API PHP de MaxMind et la base de données de géolocalisation de MaxMind. MaxMind est une entreprise qui propose quelques produits liés aux adresses IP, et parmi eux, deux qui nous intéressent ici :
- Les bases de données GeoIP2. Il s’agit des bases de données de géolocalisation commerciales de MaxMind et elles offrent une faible latence et fonctionnent sur abonnement. Vous voudrez peut-être passer à une édition supérieure si vous souhaitez des bases de données plus à jour ou plus rapides.
- Les bases de données GeoLite2. Il s’agit des bases de données de géolocalisation gratuites de MaxMind et, bien qu’elles soient apparemment moins précises que leurs homologues commerciaux, elles sont plus que suffisantes pour démarrer. Nous utiliserons ici une base de données GeoLite2. Notez que vous devrez créditer Maxmind sur votre page web publique et faire un lien vers leur site si vous utilisez l’une de leurs bases de données gratuites.
Pour installer la base de données, il vous suffit de souscrire un compte MaxMind gratuit. Vous recevrez un e-mail avec un lien pour vous connecter. Suivez le lien et connectez-vous. Une fois que c’est fait, vous devriez arriver sur la page Account Summary.

Cliquez sur le lien Download Databases sur la page Account Summary
Cela vous mènera à une page avec la liste des bases de données GeoLite2 gratuites. Récupérez la base de données des codes pays binaires.

Nous avons besoin de la base de données des codes pays binaires
Placez le fichier que vous avez téléchargé quelque part dans votre projet.
Nous aurons également besoin de l’API PHP MaxMind pour travailler avec la base de données. Nous pouvons l’installer avec Composer.
composer require geoip2/geoip2:~2.0
Package de conversion des codes pays en paramètres régionaux de Peter Kahl
Nous aurons besoin d’un package supplémentaire avant de passer à notre code. Pour déterminer les paramètres régionaux ou les langues d’un pays, nous allons utiliser le package de conversion des codes pays en paramètres régionaux de Peter Kahl. Nous pouvons également l’installer en utilisant Composer.
composer require peterkahl/country-to-locale
Classe de détection des paramètres régionaux de l’adresse IP
Avec cette configuration, nous pouvons accéder à notre propre classe, IpAddressLocaleDetector.
<?php
require '../vendor/autoload.php';
use GeoIp2\Database\Reader;
use peterkahl\locale\locale;
class IpAddressLocaleDetector
{
const MAX_MIND_DB_FILEPATH =
__DIR__ . '/GeoLite2-Country_20200121/GeoLite2-Country.mmdb';
private static $maxMindDbReader;
public static function detect()
{
$ipAddress = static::getIpAddress();
try {
$record = static::getMaxMindDbReader()->country($ipAddress);
$locales = locale::country2locale($record->country->isoCode);
$normalizedLocales = str_replace('_', '-', $locales);
return explode(',', $normalizedLocales);
} catch (Exception $ex) {
return null;
}
}
private static function getIpAddress()
{
return $_SERVER['REMOTE_ADDR'];
}
private static function getMaxMindDbReader()
{
if (static::$maxMindDbReader == null) {
static::$maxMindDbReader = new Reader(static::MAX_MIND_DB_FILEPATH);
}
return static::$maxMindDbReader;
}
}
Notre classe est relativement facile à comprendre. Tout comme HttpAcceptLanguageHeaderLocaleDetector, elle a une méthode publique, detect(), qui fait ce qui suit :
- Elle obtient l’adresse IP de la requête à partir de la liste globale
$_SERVER. - Cette adresse IP est transmise à la méthode
countrydulecteurde la base de données MaxMind, qui tente de géolocaliser un pays sur la base de l’adresse IP. - Elle utilise
locale::country2locale()de Peter Kahl pour obtenir les langues du pays concerné. - Elle normalise les paramètres régionaux acquis, de sorte que
"en_CA,ar_EG"devienne"en-CA,ar-EG". - Elle retourne les paramètres régionaux normalisés sous forme d’une liste, par exemple
["en-CA", "ar-EG"].
📖 Pour aller plus loin » Le lecteur de MaxMind a beaucoup d’autres méthodes. Consultez la documentation officielle de l’API si vous souhaitez en savoir plus sur les informations disponibles dans les bases de données MaxMind.
Côté serveur : détection des paramètres régionaux en cascade
Étant donné les deux stratégies de détection côté serveur que nous avons présentées ci-dessus, nous pouvons écrire une petite fonction detect_user_locales() qui tentera d’abord la stratégie basée sur l’en-tête HTTP.
<?php
require './HttpAcceptLanguageHeaderLocaleDetector.php';
require './IpAddressLocaleDetector.php';
function detect_user_locales()
{
$locales = HttpAcceptLanguageHeaderLocaleDetector::detect();
if (count($locales) == 0) {
$locales = IPAddressLocaleDetector::detect();
}
if (count($locales) == 0) {
// fall back on some default locale, English in this case
$locales = ['en'];
}
return $locales;
}
Si la détection via l’en-tête HTTP échoue, detect_user_locales() tentera une détection de la géolocalisation via l’IP. Si cette tentative ne porte pas ses fruits, la fonction reviendra à des paramètres régionaux par défaut.
Si elle est gérée avec soin, la détection des paramètres régionaux de l’utilisateur peut aider à offrir une meilleure expérience utilisateur dans nos applications web. Heureusement, l’objet navigator.languages et l’en-tête HTTP Accept-Language sont disponibles pour réduire nos tâtonnements en matière de détection des paramètres régionaux.
Si vous et votre équipe travaillez sur une application web internationalisée, découvrez Phrase, une plateforme d’internationalisation professionnelle adaptée aux développeurs. CLI et API flexibles, synchronisation des traductions avec GitHub et intégration Bitbucket, traductions over-the-air : Phrase couvre tous vos besoins en matière d’internationalisation, afin que vous puissiez vous concentrer sur votre cœur de métier.
Découvrez toutes les fonctionnalités Phrase pensées pour les développeurs et voyez par vous-même comment notre plateforme peut vous aider à rationaliser vos flux de travaux de localisation logicielle.


