{"id":133350,"date":"2020-01-30T16:40:13","date_gmt":"2020-01-30T15:40:13","guid":{"rendered":"https:\/\/phrase.com\/blog\/posts\/detection-des-parametres-regionaux-dun-utilisateur-dans-une-application-web\/"},"modified":"2026-02-26T16:50:13","modified_gmt":"2026-02-26T15:50:13","slug":"detecting-a-users-locale","status":"publish","type":"post","link":"https:\/\/phrase.com\/fr\/blog\/posts\/detecting-a-users-locale\/","title":{"rendered":"D\u00e9tecter les param\u00e8tres r\u00e9gionaux d&rsquo;un utilisateur dans une application web"},"content":{"rendered":"<p>Que nous d\u00e9veloppions un simple blog ou une application monopage (SPA) sophistiqu\u00e9e et moderne, il arrive souvent qu&rsquo;en r\u00e9fl\u00e9chissant \u00e0 l&rsquo;<a href=\"https:\/\/phrase.com\/fr\/blog\/posts\/i18n-a-simple-definition\/\">internationalisation<\/a> d&rsquo;une application web, nous soyons confront\u00e9s \u00e0 une question importante\u00a0: comment d\u00e9tecter la pr\u00e9f\u00e9rence linguistique d&rsquo;un utilisateur\u00a0? C&rsquo;est important, car nous voulons toujours offrir la meilleure exp\u00e9rience utilisateur. Or, si l&rsquo;utilisateur a d\u00e9fini un ensemble de langues pr\u00e9f\u00e9r\u00e9es dans son navigateur, nous voulons faire de notre mieux pour lui proposer notre contenu dans ces langues pr\u00e9f\u00e9r\u00e9es.<br \/>\nDans cet article, nous allons passer en revue trois fa\u00e7ons diff\u00e9rentes de d\u00e9tecter les param\u00e8tres r\u00e9gionaux d&rsquo;un utilisateur : via l&rsquo;objet <code>navigator.languages<\/code> du navigateur (c\u00f4t\u00e9 client), via l&rsquo;en-t\u00eate HTTP <code>Accept-Language<\/code> (c\u00f4t\u00e9 serveur) et enfin via la g\u00e9olocalisation en utilisant l&rsquo;adresse IP de l&rsquo;utilisateur (c\u00f4t\u00e9 serveur).<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_81 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Overview<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/phrase.com\/fr\/blog\/posts\/detecting-a-users-locale\/#cote-client-objet-navigatorlanguages\" >C\u00f4t\u00e9 client : objet navigator.languages<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/phrase.com\/fr\/blog\/posts\/detecting-a-users-locale\/#cote-serveur-en-tete-http-accept-language\" >C\u00f4t\u00e9 Serveur\u00a0: en-t\u00eate HTTP Accept-Language<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/phrase.com\/fr\/blog\/posts\/detecting-a-users-locale\/#cote-serveur-geolocalisation-a-laide-de-ladresse-ip\" >C\u00f4t\u00e9 serveur : g\u00e9olocalisation \u00e0 l&rsquo;aide de l\u2019adresse IP<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/phrase.com\/fr\/blog\/posts\/detecting-a-users-locale\/#utilisation-de-maxmind-pour-la-geolocalisation\" >Utilisation de MaxMind pour la g\u00e9olocalisation<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/phrase.com\/fr\/blog\/posts\/detecting-a-users-locale\/#package-de-conversion-des-codes-pays-en-parametres-regionaux-de-peter-kahl\" >Package de conversion des codes pays en param\u00e8tres r\u00e9gionaux de Peter Kahl<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/phrase.com\/fr\/blog\/posts\/detecting-a-users-locale\/#classe-de-detection-des-parametres-regionaux-de-ladresse-ip\" >Classe de d\u00e9tection des param\u00e8tres r\u00e9gionaux de l&rsquo;adresse IP<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/phrase.com\/fr\/blog\/posts\/detecting-a-users-locale\/#cote-serveur-detection-des-parametres-regionaux-en-cascade\" >C\u00f4t\u00e9 serveur : d\u00e9tection des param\u00e8tres r\u00e9gionaux en cascade<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"cote-client-objet-navigatorlanguages\"><\/span>C\u00f4t\u00e9 client : objet <code>navigator.languages<\/code><span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Les navigateurs modernes fournissent un objet <code>navigator.languages<\/code> que nous pouvons utiliser pour obtenir toutes les langues pr\u00e9f\u00e9r\u00e9es que l&rsquo;utilisateur a d\u00e9finies dans son navigateur.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-9669 size-large\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-firefox-browser-settings-1024x531.png\" alt=\"Objet navigator.languages du navigateur pour les param\u00e8tres de langue de la page web | Phrase\" width=\"1024\" height=\"531\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-firefox-browser-settings-1024x531.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-firefox-browser-settings-300x155.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-firefox-browser-settings-768x398.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-firefox-browser-settings.png 1320w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p style=\"text-align: center\"><em>Param\u00e8tres de langue dans Firefox<\/em><\/p>\n<p>\u00c9tant donn\u00e9 les param\u00e8tres ci-dessus, si nous devions ouvrir la console Firefox et v\u00e9rifier la valeur de <code>navigator.languages<\/code>, nous obtiendrions ce qui suit :<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"wp-image-9671 size-full aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-navigator-languages.png\" alt=\"Valeur de l&apos;objet navigator.languages de Firefox | Phrase\" width=\"440\" height=\"80\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-navigator-languages.png 440w, https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-navigator-languages-300x55.png 300w\" sizes=\"auto, (max-width: 440px) 100vw, 440px\" \/><\/p>\n<p style=\"text-align: center\"><em>Les codes des param\u00e8tres r\u00e9gionaux correspondent \u00e0 ceux de nos param\u00e8tres de navigateur<\/em><\/p>\n<p><code>navigator.languages<\/code> est <a href=\"https:\/\/caniuse.com\/#search=navigator%20languages\">disponible dans tous les navigateurs web modernes<\/a> et est g\u00e9n\u00e9ralement fiable. Nous allons \u00e9crire une fonction <a href=\"https:\/\/phrase.com\/fr\/blog\/posts\/step-step-guide-javascript-localization\/\">JavaScript<\/a> r\u00e9utilisable qui nous indique la ou les langues pr\u00e9f\u00e9r\u00e9es de l&rsquo;utilisateur actuel.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">function getBrowserLocales(options = {}) {\n  const defaultOptions = {\n    languageCodeOnly: false,\n  };\n  const opt = {\n    ...defaultOptions,\n    ...options,\n  };\n  const browserLocales =\n    navigator.languages === undefined\n      ? [navigator.language]\n      : navigator.languages;\n  if (!browserLocales) {\n    return undefined;\n  }\n  return browserLocales.map(locale =&gt; {\n    const trimmedLocale = locale.trim();\n    return opt.languageCodeOnly\n      ? trimmedLocale.split(\/-|_\/)[0]\n      : trimmedLocale;\n  });\n}<\/pre>\n<p><code>getBrowserLocales()<\/code> v\u00e9rifie la liste <code>navigator.languages<\/code>, en revenant \u00e0 <code>navigator.language<\/code> si aucune liste n&rsquo;est disponible. Il convient de noter que dans certains navigateurs, comme Chrome, <code>navigator.language<\/code> sera la langue de l\u2019<em>IU<\/em>, qui est probablement la langue utilis\u00e9e pour le <em>syst\u00e8me d\u2019exploitation<\/em>. C&rsquo;est diff\u00e9rent de <code>navigator.languages<\/code>, qui contient les langues pr\u00e9f\u00e9r\u00e9es d\u00e9finies par l&rsquo;utilisateur dans le <em>navigateur<\/em> lui-m\u00eame.<\/p>\n<p>\u270b\ud83c\udffd <em>Avertissement \u00bb<\/em> Si vous prenez en charge Internet Explorer, vous devrez utiliser les propri\u00e9t\u00e9s <code>navigator.userLanguage<\/code> et <code>navigaor.browserLanguage<\/code>. Bien s\u00fbr, vous devrez \u00e9galement remplacer toutes les instances de <code>const<\/code> par <code>var<\/code> dans le code ci-dessus.<\/p>\n<p>Notre fonction propose \u00e9galement une option pratique <code>languageCodeOnly<\/code>, qui supprime les codes de pays des param\u00e8tres r\u00e9gionaux avant de les renvoyer. Cela peut \u00eatre utile lorsque notre application ne g\u00e8re pas vraiment les nuances r\u00e9gionales d&rsquo;une langue, par exemple, si nous n&rsquo;avons qu&rsquo;une seule version du contenu en anglais.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-9672 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-language-code-only.png\" alt=\"languageCodeOnly: true permet d&apos;obtenir les langues sans les codes pays | Phrase\" width=\"658\" height=\"158\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-language-code-only.png 658w, https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-language-code-only-300x72.png 300w\" sizes=\"auto, (max-width: 658px) 100vw, 658px\" \/><\/p>\n<p style=\"text-align: center\"><em>languageCodeOnly: true permet d&rsquo;obtenir les langues sans les codes pays<\/em><\/p>\n<h2><span class=\"ez-toc-section\" id=\"cote-serveur-en-tete-http-accept-language\"><\/span>C\u00f4t\u00e9 Serveur\u00a0: en-t\u00eate HTTP <code>Accept-Language<\/code><span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Si l&rsquo;utilisateur d\u00e9finit ses pr\u00e9f\u00e9rences de langue dans un navigateur moderne, le navigateur enverra, \u00e0 son tour, un en-t\u00eate HTTP qui transmet ces pr\u00e9f\u00e9rences de langue au serveur avec chaque requ\u00eate. C&rsquo;est l&rsquo;en-t\u00eate <code>Accept-Language<\/code> et il ressemble souvent \u00e0 ceci : <code>Accept-Language: en-CA,ar-EG;q=0.5<\/code>.<br \/>\nL&rsquo;en-t\u00eate liste les langues pr\u00e9f\u00e9r\u00e9es de l&rsquo;utilisateur, avec une pond\u00e9ration d\u00e9finie par une valeur <code>q<\/code> attribu\u00e9e \u00e0 chacune. Lorsqu&rsquo;une valeur explicite de <code>q<\/code> n&rsquo;est pas sp\u00e9cifi\u00e9e, une valeur par d\u00e9faut de <code>1.0<\/code> est utilis\u00e9e. Ainsi, dans la valeur d&rsquo;en-t\u00eate ci-dessus, le client indique que l&rsquo;utilisateur pr\u00e9f\u00e8re l&rsquo;anglais canadien (avec une pond\u00e9ration <code>q = 1.0<\/code>), puis l&rsquo;arabe \u00e9gyptien (avec une pond\u00e9ration <code>q = 0.5<\/code>).<br \/>\nNous pouvons utiliser cet en-t\u00eate HTTP standard pour d\u00e9terminer les param\u00e8tres r\u00e9gionaux pr\u00e9f\u00e9r\u00e9s de l&rsquo;utilisateur. Pour ce faire, nous allons \u00e9crire une classe appel\u00e9e <code>HttpAcceptLanguageHeaderLocaleDetector<\/code>. Nous utiliserons PHP ici, mais vous pouvez utiliser le langage de votre choix ; l&rsquo;en-t\u00eate <code>Accept-Language<\/code> devrait \u00eatre le m\u00eame (ou similaire) dans tous les environnements.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\">&lt;?php\nclass HttpAcceptLanguageHeaderLocaleDetector\n{\n  const HTTP_ACCEPT_LANGUAGE_HEADER_KEY = 'HTTP_ACCEPT_LANGUAGE';\n  public static function detect()\n  {\n    $httpAcceptLanguageHeader = static::getHttpAcceptLanguageHeader();\n    if ($httpAcceptLanguageHeader == null) {\n      return [];\n    }\n    $locales = static::getWeightedLocales($httpAcceptLanguageHeader);\n    $sortedLocales = static::sortLocalesByWeight($locales);\n    return array_map(function ($weightedLocale) {\n      return $weightedLocale['locale'];\n    }, $sortedLocales);\n  }\n  private static function getHttpAcceptLanguageHeader()\n  {\n    if (isset($_SERVER[static::HTTP_ACCEPT_LANGUAGE_HEADER_KEY])) {\n      return trim($_SERVER['HTTP_ACCEPT_LANGUAGE']);\n    } else {\n      return null;\n    }\n  }\n  private static function getWeightedLocales($httpAcceptLanguageHeader)\n  {\n    if (strlen($httpAcceptLanguageHeader) == 0) {\n      return [];\n    }\n    $weightedLocales = [];\n    \/\/ We break up the string 'en-CA,ar-EG;q=0.5' along the commas,\n    \/\/ and iterate over the resulting array of individual locales. Once\n    \/\/ we're done, $weightedLocales should look like\n    \/\/ [['locale' =&gt; 'en-CA', 'q' =&gt; 1.0], ['locale' =&gt; 'ar-EG', 'q' =&gt; 0.5]]\n    foreach (explode(',', $httpAcceptLanguageHeader) as $locale) {\n      \/\/ separate the locale key (\"ar-EG\") from its weight (\"q=0.5\")\n      $localeParts = explode(';', $locale);\n      $weightedLocale = ['locale' =&gt; $localeParts[0]];\n      if (count($localeParts) == 2) {\n        \/\/ explicit weight e.g. 'q=0.5'\n        $weightParts = explode('=', $localeParts[1]);\n        \/\/ grab the '0.5' bit and parse it to a float\n        $weightedLocale['q'] = floatval($weightParts[1]);\n      } else {\n        \/\/ no weight given in string, ie. implicit weight of 'q=1.0'\n        $weightedLocale['q'] = 1.0;\n      }\n      $weightedLocales[] = $weightedLocale;\n    }\n    return $weightedLocales;\n  }\n  \/**\n   * Sort by high to low `q` value\n   *\/\n  private static function sortLocalesByWeight($locales)\n  {\n    usort($locales, function ($a, $b) {\n      \/\/ usort will cast float values that we return here into integers,\n      \/\/ which can mess up our sorting. So instead of subtracting the `q`,\n      \/\/ values and returning the difference, we compare the `q` values and\n      \/\/ explicitly return integer values.\n      if ($a['q'] == $b['q']) {\n        return 0;\n      }\n      if ($a['q'] &gt; $b['q']) {\n        return -1;\n      }\n      return 1;\n    });\n    return $locales;\n  }\n}<\/pre>\n<p>Ce long bout de code n&rsquo;est en fait pas tr\u00e8s compliqu\u00e9. Dans la seule m\u00e9thode publique, <code>detect()<\/code>, notre classe fait ce qui suit\u00a0:<\/p>\n<ol>\n<li>Elle obtient la valeur brute de la cha\u00eene de l\u2019en-t\u00eate <code>Accept-Language<\/code>, par exemple <code>\"en-CA,ar-EG;q=0.5\"<\/code><\/li>\n<li>Elle utilise la m\u00e9thode d&rsquo;aide <code>getWeightedLocales()<\/code> pour analyser la cha\u00eene d&rsquo;en-t\u00eate sous forme d&rsquo;une liste qui ressemble \u00e0 <code>[['locale' =&gt; 'en-CA', 'q' =&gt; 1.0], ['locale' =&gt; 'ar-EG', 'q' =&gt; 0.5]]<\/code>.<\/li>\n<li>Elle utilise la m\u00e9thode d&rsquo;aide <code>sortLocalesByWeight()<\/code> pour trier la liste ci-dessus de la valeur <code>q<\/code> la plus \u00e9lev\u00e9e \u00e0 la plus basse.<\/li>\n<li>Elle extrait les valeurs <code>locale<\/code> de la liste tri\u00e9e, renvoyant une liste qui ressemble \u00e0 <code>['en-CA', 'ar-EG']<\/code>.<\/li>\n<\/ol>\n<p>Nous pouvons maintenant utiliser notre nouvelle classe pour obtenir une liste exploitable de codes de param\u00e8tres r\u00e9gionaux bas\u00e9s sur l&rsquo;en-t\u00eate HTTP <code>Accept-Language<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\">&lt;?php\n$locales = HttpAcceptLanguageHeaderLocaleDetector::detect();\n\/\/ =&gt; ['en-CA', 'ar-EG']<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"cote-serveur-geolocalisation-a-laide-de-ladresse-ip\"><\/span>C\u00f4t\u00e9 serveur : g\u00e9olocalisation \u00e0 l&rsquo;aide de l\u2019adresse IP<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Parfois, l&rsquo;en-t\u00eate <code>Accept-Language<\/code> ne sera pas pr\u00e9sent dans les requ\u00eates \u00e0 notre serveur. Dans ces cas-l\u00e0, nous pourrions vouloir utiliser l&rsquo;adresse IP de l&rsquo;utilisateur pour d\u00e9terminer le pays de l&rsquo;utilisateur et en d\u00e9duire les param\u00e8tres r\u00e9gionaux ou la langue de ce pays.<\/p>\n<p>\u270b\ud83c\udffd <em>Avertissement \u00bb<\/em> La g\u00e9olocalisation doit \u00eatre utilis\u00e9e en dernier recours pour d\u00e9tecter les param\u00e8tres r\u00e9gionaux de l&rsquo;utilisateur, car elle peut souvent conduire \u00e0 une d\u00e9termination incorrecte des param\u00e8tres r\u00e9gionaux. Par exemple, si nous voyons que notre utilisateur vient du Canada, devons-nous supposer que sa langue pr\u00e9f\u00e9r\u00e9e est l&rsquo;anglais ou le fran\u00e7ais ? Les deux sont des langues officielles, largement utilis\u00e9es dans le pays. Bien s\u00fbr, l&rsquo;utilisateur pourrait appartenir \u00e0 une minorit\u00e9 arabophone ou \u00eatre un visiteur hispanophone.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"utilisation-de-maxmind-pour-la-geolocalisation\"><\/span>Utilisation de MaxMind pour la g\u00e9olocalisation<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Pour d\u00e9terminer le pays de l&rsquo;utilisateur \u00e0 l&rsquo;aide de l&rsquo;adresse IP de la requ\u00eate, nous utiliserons l&rsquo;API PHP de MaxMind et la base de donn\u00e9es de g\u00e9olocalisation de MaxMind. MaxMind est une entreprise qui propose quelques produits li\u00e9s aux adresses IP, et parmi eux, deux qui nous int\u00e9ressent ici\u00a0:<\/p>\n<ul>\n<li><a href=\"https:\/\/www.maxmind.com\/en\/geoip-databases\">Les bases de donn\u00e9es GeoIP2<\/a>. Il s&rsquo;agit des bases de donn\u00e9es de g\u00e9olocalisation commerciales de MaxMind et elles offrent une faible latence et fonctionnent sur abonnement. Vous voudrez peut-\u00eatre passer \u00e0 une \u00e9dition sup\u00e9rieure si vous souhaitez des bases de donn\u00e9es plus \u00e0 jour ou plus rapides.<\/li>\n<li><a href=\"https:\/\/dev.maxmind.com\/geoip\/\">Les bases de donn\u00e9es GeoLite2<\/a>. Il s&rsquo;agit des bases de donn\u00e9es de g\u00e9olocalisation gratuites de MaxMind et, bien qu&rsquo;elles soient apparemment moins pr\u00e9cises que leurs homologues commerciaux, elles sont plus que suffisantes pour d\u00e9marrer. Nous utiliserons ici une base de donn\u00e9es GeoLite2. Notez que vous devrez cr\u00e9diter Maxmind sur votre <a href=\"https:\/\/phrase.com\/fr\/blog\/posts\/how-translate-web-page\/\">page web publique<\/a> et faire un lien vers leur site si vous utilisez l&rsquo;une de leurs bases de donn\u00e9es gratuites.<\/li>\n<\/ul>\n<p>Pour installer la base de donn\u00e9es, il vous suffit de <a href=\"https:\/\/www.maxmind.com\/en\/geolite2\/signup\">souscrire<\/a> 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&rsquo;est fait, vous devriez arriver sur la page <em>Account Summary<\/em>.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-9673 size-large\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-maxmind-download-link-1024x193.png\" alt=\"T\u00e9l\u00e9charger les bases de donn\u00e9es MaxMind | Phrase\" width=\"1024\" height=\"193\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-maxmind-download-link-1024x193.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-maxmind-download-link-300x57.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-maxmind-download-link-768x145.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-maxmind-download-link.png 1474w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p style=\"text-align: center\"><em>Cliquez sur le lien Download Databases sur la page Account Summary<\/em><\/p>\n<p>Cela vous m\u00e8nera \u00e0 une page avec la liste des bases de donn\u00e9es GeoLite2 gratuites. R\u00e9cup\u00e9rez la base de donn\u00e9es des <em>codes pays binaires<\/em>.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-9676 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-maxmind-databases-1.png\" alt=\"Base de donn\u00e9es MaxMind des codes pays binaires | Phrase\" width=\"729\" height=\"752\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-maxmind-databases-1.png 729w, https:\/\/phrase.com\/wp-content\/uploads\/2020\/01\/detectinguserlocale202001-maxmind-databases-1-291x300.png 291w\" sizes=\"auto, (max-width: 729px) 100vw, 729px\" \/><\/p>\n<p style=\"text-align: center\"><em>Nous avons besoin de la base de donn\u00e9es des codes pays binaires<\/em><\/p>\n<p>Placez le fichier que vous avez t\u00e9l\u00e9charg\u00e9 quelque part dans votre projet.<br \/>\nNous aurons \u00e9galement besoin de l&rsquo;API PHP MaxMind pour travailler avec la base de donn\u00e9es. Nous pouvons l&rsquo;installer avec <a href=\"https:\/\/getcomposer.org\/\">Composer<\/a>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">composer require geoip2\/geoip2:~2.0<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"package-de-conversion-des-codes-pays-en-parametres-regionaux-de-peter-kahl\"><\/span>Package de conversion des codes pays en param\u00e8tres r\u00e9gionaux de Peter Kahl<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Nous aurons besoin d\u2019un package suppl\u00e9mentaire avant de passer \u00e0 notre code. Pour d\u00e9terminer les <em>param\u00e8tres r\u00e9gionaux<\/em> ou les langues d&rsquo;un pays, nous allons utiliser le package <code>de conversion des codes pays en param\u00e8tres r\u00e9gionaux<\/code> de Peter Kahl. Nous pouvons \u00e9galement l&rsquo;installer en utilisant Composer.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">composer require peterkahl\/country-to-locale<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"classe-de-detection-des-parametres-regionaux-de-ladresse-ip\"><\/span>Classe de d\u00e9tection des param\u00e8tres r\u00e9gionaux de l&rsquo;adresse IP<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Avec cette configuration, nous pouvons acc\u00e9der \u00e0 notre propre classe, <code>IpAddressLocaleDetector<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\">&lt;?php\nrequire '..\/vendor\/autoload.php';\nuse GeoIp2\\Database\\Reader;\nuse peterkahl\\locale\\locale;\nclass IpAddressLocaleDetector\n{\n  const MAX_MIND_DB_FILEPATH =\n    __DIR__ . '\/GeoLite2-Country_20200121\/GeoLite2-Country.mmdb';\n  private static $maxMindDbReader;\n  public static function detect()\n  {\n    $ipAddress = static::getIpAddress();\n    try {\n      $record = static::getMaxMindDbReader()-&gt;country($ipAddress);\n      $locales = locale::country2locale($record-&gt;country-&gt;isoCode);\n      $normalizedLocales = str_replace('_', '-', $locales);\n      return explode(',', $normalizedLocales);\n    } catch (Exception $ex) {\n      return null;\n    }\n  }\n  private static function getIpAddress()\n  {\n    return $_SERVER['REMOTE_ADDR'];\n  }\n  private static function getMaxMindDbReader()\n  {\n    if (static::$maxMindDbReader == null) {\n      static::$maxMindDbReader = new Reader(static::MAX_MIND_DB_FILEPATH);\n    }\n    return static::$maxMindDbReader;\n  }\n}<\/pre>\n<p>Notre classe est relativement facile \u00e0 comprendre. Tout comme <code>HttpAcceptLanguageHeaderLocaleDetector<\/code>, elle a une m\u00e9thode publique, <code>detect()<\/code>, qui fait ce qui suit :<\/p>\n<ol>\n<li>Elle obtient l&rsquo;adresse IP de la requ\u00eate \u00e0 partir de la liste globale <code>$_SERVER<\/code>.<\/li>\n<li>Cette adresse IP est transmise \u00e0 la m\u00e9thode <code>country<\/code> du <code>lecteur<\/code> de la base de donn\u00e9es MaxMind, qui tente de g\u00e9olocaliser un pays sur la base de l&rsquo;adresse IP.<\/li>\n<li>Elle utilise <code>locale::country2locale()<\/code> de Peter Kahl pour obtenir les langues du pays concern\u00e9.<\/li>\n<li>Elle normalise les param\u00e8tres r\u00e9gionaux acquis, de sorte que <code>\"en_CA,ar_EG\"<\/code> devienne <code>\"en-CA,ar-EG\"<\/code>.<\/li>\n<li>Elle retourne les param\u00e8tres r\u00e9gionaux normalis\u00e9s sous forme d&rsquo;une liste, par exemple <code>[\"en-CA\", \"ar-EG\"]<\/code>.<\/li>\n<\/ol>\n<p>\ud83d\udcd6 <em>Pour aller plus loin \u00bb<\/em> Le <code>lecteur<\/code> de MaxMind a beaucoup d&rsquo;autres m\u00e9thodes. Consultez la <a href=\"https:\/\/maxmind.github.io\/GeoIP2-php\/\">documentation officielle de l&rsquo;API<\/a> si vous souhaitez en savoir plus sur les informations disponibles dans les bases de donn\u00e9es MaxMind.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"cote-serveur-detection-des-parametres-regionaux-en-cascade\"><\/span>C\u00f4t\u00e9 serveur : d\u00e9tection des param\u00e8tres r\u00e9gionaux en cascade<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>\u00c9tant donn\u00e9 les deux strat\u00e9gies de d\u00e9tection c\u00f4t\u00e9 serveur que nous avons pr\u00e9sent\u00e9es ci-dessus, nous pouvons \u00e9crire une petite fonction <code>detect_user_locales()<\/code> qui tentera d&rsquo;abord la strat\u00e9gie bas\u00e9e sur l&rsquo;en-t\u00eate HTTP.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\">&lt;?php\nrequire '.\/HttpAcceptLanguageHeaderLocaleDetector.php';\nrequire '.\/IpAddressLocaleDetector.php';\nfunction detect_user_locales()\n{\n  $locales = HttpAcceptLanguageHeaderLocaleDetector::detect();\n  if (count($locales) == 0) {\n    $locales = IPAddressLocaleDetector::detect();\n  }\n  if (count($locales) == 0) {\n    \/\/ fall back on some default locale, English in this case\n    $locales = ['en'];\n  }\n  return $locales;\n}<\/pre>\n<p>Si la d\u00e9tection via l&rsquo;en-t\u00eate HTTP \u00e9choue, <code>detect_user_locales()<\/code> tentera une d\u00e9tection de la g\u00e9olocalisation via l&rsquo;IP. Si cette tentative ne porte pas ses fruits, la fonction reviendra \u00e0 des param\u00e8tres r\u00e9gionaux par d\u00e9faut.<br \/>\nSi elle est g\u00e9r\u00e9e avec soin, la d\u00e9tection des param\u00e8tres r\u00e9gionaux de l&rsquo;utilisateur peut aider \u00e0 offrir une meilleure exp\u00e9rience utilisateur dans nos applications web. Heureusement, l&rsquo;objet <code>navigator.languages<\/code> et l&rsquo;en-t\u00eate HTTP <code>Accept-Language<\/code> sont disponibles pour r\u00e9duire nos t\u00e2tonnements en mati\u00e8re de d\u00e9tection des param\u00e8tres r\u00e9gionaux.<\/p>\n<p>Si vous et votre \u00e9quipe travaillez sur une application web internationalis\u00e9e, d\u00e9couvrez Phrase, une plateforme d&rsquo;internationalisation professionnelle adapt\u00e9e aux d\u00e9veloppeurs. CLI et API flexibles, synchronisation des traductions avec GitHub et int\u00e9gration Bitbucket, <a href=\"https:\/\/phrase.com\/fr\/blog\/posts\/machine-translation\/\">traductions over-the-air<\/a>\u00a0: Phrase couvre tous vos besoins en mati\u00e8re d&rsquo;internationalisation, afin que vous puissiez vous concentrer sur votre c\u0153ur de m\u00e9tier.<\/p>\n<p>D\u00e9couvrez toutes les <a href=\"https:\/\/phrase.com\/fr\/roles\/developers\/\">fonctionnalit\u00e9s Phrase pens\u00e9es pour les d\u00e9veloppeurs<\/a> et voyez par vous-m\u00eame comment notre plateforme peut vous aider \u00e0 rationaliser vos flux de travaux de localisation logicielle.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>L&rsquo;un des probl\u00e8mes les plus courants dans le d\u00e9veloppement d&rsquo;applications web est la d\u00e9tection des param\u00e8tres r\u00e9gionaux d&rsquo;un utilisateur. Voici comment les d\u00e9tecter correctement.<\/p>\n","protected":false},"author":41,"featured_media":2612,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"post-refresh-updated","format":"standard","meta":{"_acf_changed":false,"_stopmodifiedupdate":false,"_modified_date":"","_searchwp_excluded":"","episode_type":"","audio_file":"","podmotor_file_id":"","podmotor_episode_id":"","cover_image":"","cover_image_id":"","duration":"","filesize":"","filesize_raw":"","date_recorded":"","explicit":"","block":"","itunes_episode_number":"","itunes_title":"","itunes_season_number":"","itunes_episode_type":"","footnotes":""},"categories":[2459],"class_list":["post-133350","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-localisation-de-logiciels-fr"],"acf":[],"_links":{"self":[{"href":"https:\/\/phrase.com\/fr\/wp-json\/wp\/v2\/posts\/133350","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/phrase.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/phrase.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/phrase.com\/fr\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/phrase.com\/fr\/wp-json\/wp\/v2\/comments?post=133350"}],"version-history":[{"count":4,"href":"https:\/\/phrase.com\/fr\/wp-json\/wp\/v2\/posts\/133350\/revisions"}],"predecessor-version":[{"id":136282,"href":"https:\/\/phrase.com\/fr\/wp-json\/wp\/v2\/posts\/133350\/revisions\/136282"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/phrase.com\/fr\/wp-json\/wp\/v2\/media\/2612"}],"wp:attachment":[{"href":"https:\/\/phrase.com\/fr\/wp-json\/wp\/v2\/media?parent=133350"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/phrase.com\/fr\/wp-json\/wp\/v2\/categories?post=133350"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}