{"id":133276,"date":"2020-01-30T16:40:13","date_gmt":"2020-01-30T15:40:13","guid":{"rendered":"https:\/\/phrase.com\/blog\/posts\/erkennung-deiner-sprache-und-region-in-einer-web-app\/"},"modified":"2026-03-06T17:22:36","modified_gmt":"2026-03-06T16:22:36","slug":"detecting-a-users-locale","status":"publish","type":"post","link":"https:\/\/phrase.com\/de\/blog\/posts\/detecting-a-users-locale\/","title":{"rendered":"Erkennung deiner Sprache und Region in einer Web-App"},"content":{"rendered":"<p>Egal, ob wir einen einfachen Blog oder eine ausgekl\u00fcgelte, moderne Single-Page-Anwendung (SPA) entwickeln \u2013 wenn wir <a href=\"https:\/\/phrase.com\/de\/blog\/posts\/i18n-a-simple-definition\/\">i18n<\/a> in einer Webanwendung einsetzen, taucht oft eine wichtige Frage auf: Wie ermitteln wir die Sprachpr\u00e4ferenz der User? Eine wichtige Frage: Schlie\u00dflich wollen wir immer die beste User-Erfahrung bieten. Wenn du in deinem Browser eine Reihe bevorzugter Sprachen festgelegt hast, wollen wir unser Bestes geben, um unsere Inhalte in diesen bevorzugten Sprachen anzuzeigen.<br \/>\nIn diesem Artikel schauen wir uns drei verschiedene M\u00f6glichkeiten an, wie man die Locale eines Users erkennen kann: \u00fcber das <code>navigator.languages<\/code>-Objekt des Browsers (auf dem Client), \u00fcber den <code>Accept-Language<\/code> HTTP-Header (auf dem Server) und \u00fcber Geolokalisierung mit der IP-Adresse (auf dem Server).<\/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\/de\/blog\/posts\/detecting-a-users-locale\/#client-seitig-das-objekt-navigatorlanguages\" >Client-seitig: Das Objekt 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\/de\/blog\/posts\/detecting-a-users-locale\/#server-seitig-der-accept-language-http-header\" >Server-seitig: Der Accept-Language HTTP-Header<\/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\/de\/blog\/posts\/detecting-a-users-locale\/#server-seitig-geolokalisierung-nach-ip-adresse\" >Server-seitig: Geolokalisierung nach IP-Adresse<\/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\/de\/blog\/posts\/detecting-a-users-locale\/#verwendung-von-maxmind-fuer-geolokation\" >Verwendung von MaxMind f\u00fcr Geolokation<\/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\/de\/blog\/posts\/detecting-a-users-locale\/#peter-kahls-country-to-locale-paket\" >Peter Kahls Country-to-Locale-Paket<\/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\/de\/blog\/posts\/detecting-a-users-locale\/#ip-adress-lokalisierungs-klasse\" >IP-Adress-Lokalisierungs-Klasse<\/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\/de\/blog\/posts\/detecting-a-users-locale\/#serverseitig-kaskadierende-spracherkennung\" >Serverseitig: Kaskadierende Spracherkennung<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"client-seitig-das-objekt-navigatorlanguages\"><\/span>Client-seitig: Das Objekt <code>navigator.languages<\/code><span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Moderne Browser bieten ein <code>navigator.languages<\/code>-Objekt, das wir verwenden k\u00f6nnen, um alle bevorzugten Sprachen abzurufen, die du in deinem Browser festgelegt hast.<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=\" navigator.languages - Objekt im Browser, f\u00fcr die Spracheinstellungen der Website | 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>Die Spracheinstellungen in Firefox<\/em><\/p>\n<p>Angesichts der obigen Einstellungen w\u00fcrden wir, wenn wir die Firefox-Konsole \u00f6ffnen und den Wert <code>navigator.languages<\/code> \u00fcberpr\u00fcfen, Folgendes erhalten:<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=\"Firefox navigator.languages-Objektwert | 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>Die Codes f\u00fcr die Gebietsschemata (Locales, oder vereinfacht: Sprachen) stimmen mit denen in unseren Browsereinstellungen \u00fcberein<\/em><\/p>\n<p><code>navigator.languages<\/code> ist <a href=\"https:\/\/caniuse.com\/#search=navigator%20languages\">in allen modernen Webbrowsern verf\u00fcgbar<\/a> und kann in der Regel bedenkenlos verwendet werden. Lass uns also eine wiederverwendbare <a href=\"https:\/\/phrase.com\/de\/blog\/posts\/step-step-guide-javascript-localization\/\">JavaScript<\/a>-Funktion schreiben, die uns zeigt, welche Sprache(n) der aktuelle Nutzer bevorzugt.<\/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> \u00fcberpr\u00fcft das <code>navigator.languages<\/code>-Array und greift auf <code>navigator.language<\/code> zur\u00fcck, wenn das Array nicht verf\u00fcgbar ist. Es ist erw\u00e4hnenswert, dass in einigen Browsern wie Chrome <code>navigator.language<\/code> die <em>UI-Sprache<\/em> sein wird, die wahrscheinlich die Sprache ist, auf die das <em>Betriebssystem<\/em> eingestellt ist. Das ist anders als <code>navigator.languages<\/code>, was die vom User festgelegten bevorzugten Sprachen im <em>Browser<\/em> selbst enth\u00e4lt.<\/p>\n<p>\u270b\ud83c\udffd <em>Hinweis \u00bb<\/em> Wenn du Internet Explorer unterst\u00fctzt, musst du die Eigenschaften <code>navigator.userLanguage<\/code> und <code>navigator.browserLanguage<\/code> verwenden. Nat\u00fcrlich musst du auch alle Vorkommen von <code>const<\/code> im obigen Code durch <code>var<\/code> ersetzen.<\/p>\n<p>Unsere Funktion hat auch eine praktische <code>languageCodeOnly<\/code>-Option, mit der du die L\u00e4ndercodes von Sprachen entfernen kannst, bevor sie zur\u00fcckgesandt werden. Das kann praktisch sein, wenn unsere App die regionalen Nuancen einer Sprache nicht wirklich ber\u00fccksichtigt, z.\u00a0B. wenn wir nur eine Version von englischen Inhalten haben.<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=\"Mit languageCodeOnly: true erhalten wir die Sprachen ohne L\u00e4nder | 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>Mit languageCodeOnly: true, bekommen wir die Sprachen ohne L\u00e4ndercodes<\/em><\/p>\n<h2><span class=\"ez-toc-section\" id=\"server-seitig-der-accept-language-http-header\"><\/span>Server-seitig: Der <code>Accept-Language<\/code> HTTP-Header<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Wenn du deine Spracheinstellungen in einem modernen Browser festlegst, sendet der Browser im Gegenzug einen HTTP-Header, der diese Spracheinstellungen bei jeder Anfrage an den Server \u00fcbermittelt. Dies ist der <code>Accept-Language<\/code> Header, und er sieht meistens so aus: <code>Accept-Language: en-CA,ar-EG;q=0.5<\/code>.<br \/>\nIm Header siehst du deine bevorzugten Sprachen, jeweils mit einer Gewichtung, die durch einen <code>q<\/code> Wert festgelegt ist. Wenn kein expliziter <code>q<\/code>-Wert angegeben ist, wird der Standardwert <code>1.0<\/code> angenommen. Im obigen Header-Wert gibt der Client an, dass du kanadisches Englisch bevorzugst (mit einer Gewichtung von <code>q = 1.0<\/code>), gefolgt von \u00e4gyptischem Arabisch (mit einer Gewichtung von <code>q = 0.5<\/code>).<br \/>\nWir k\u00f6nnen diesen standardm\u00e4\u00dfigen HTTP-Header verwenden, um die bevorzugten Spracheinstellungen des Users zu bestimmen. Lass uns eine Klasse namens <code>HttpAcceptLanguageHeaderLocaleDetector<\/code> schreiben, die das erledigt. Wir verwenden hier PHP, aber du kannst jede Sprache verwenden, die du m\u00f6chtest; der <code>Accept-Language<\/code> Header sollte in allen Umgebungen gleich (oder \u00e4hnlich genug) sein.<\/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    \/\/ Wir zerlegen die Zeichenfolge 'en-CA,ar-EG;q=0.5' an den Kommas,\n    \/\/ und iterieren \u00fcber das resultierende Array von einzelnen Locales. \/\/ Sobald\n    wir fertig sind, sollte $weightedLocales so aussehen:\n    \/\/ [['locale' =&gt; 'en-CA', 'q' =&gt; 1.0], ['locale' =&gt; 'ar-EG', 'q' =&gt; 0.5]]\n    foreach (explode(',', $httpAcceptLanguageHeader) as $locale) {\n      \/\/ trennt den Sprachen-Key (\"ar-EG\") von seiner Gewichtung (\"q=0.5\")\n      $localeParts = explode(';', $locale);\n      $weightedLocale = ['locale' =&gt; $localeParts[0]];\n      if (count($localeParts) == 2) {\n        \/\/ explizite Gewichtung z. B. 'q=0.5'\n        $weightParts = explode('=', $localeParts[1]);\n        \/\/ hol dir den '0.5'-Teil und wandle ihn in eine Flie\u00dfkommazahl um\n        $weightedLocale['q'] = floatval($weightParts[1]);\n      } else {\n        \/\/ keine Gewichtung in der Zeichenfolge angegeben, d.h. implizite Gewichtung von 'q=1.0'\n        $weightedLocale['q'] = 1.0;\n      }\n      $weightedLocales[] = $weightedLocale;\n    }\n    return $weightedLocales;\n  }\n  \/**\n   * Sortiere nach hohem bis niedrigem `q`-Wert\n   *\/\n  private static function sortLocalesByWeight($locales)\n  {\n    usort($locales, function ($a, $b) {\n      \/\/ usort wird die Float-Werte, die wir hier zur\u00fccksenden, in Ganzzahlen umwandeln,\n      \/\/ was unsere Sortierung durcheinanderbringen kann. Statt also den `q`-Wert zu subtrahieren,\n      \/\/ und die Differenz zur\u00fcckzugeben, vergleichen wir die `q`-Werte und\n      \/\/ senden explizit die Ganzzahlen zur\u00fcck.\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>Dieser lange Code ist eigentlich nicht sehr kompliziert. In der einzigen \u00f6ffentlichen Methode <code>detect()<\/code> f\u00fchrt unsere Klasse Folgendes aus:<\/p>\n<ol>\n<li>Hole den rohen Zeichenfolgenwert des <code>Accept-Language<\/code> Headers, z. B. <code>\"en-CA,ar-EG;q=0.5\"<\/code><\/li>\n<li>Verwende die Hilfsmethode <code>getWeightedLocales()<\/code>, um die Header-Zeichenfolge in ein Array zu parsen, das wie <code>[['locale' =&gt; 'en-CA', 'q' =&gt; 1.0], ['locale' =&gt; 'ar-EG', 'q' =&gt; 0.5]]<\/code> aussieht.<\/li>\n<li>Verwende die Hilfsmethode <code>sortLocalesByWeight()<\/code>, um das obige Array von h\u00f6chstem zu niedrigstem <code>q<\/code>-Wert zu sortieren.<\/li>\n<li>Ziehe die <code>locale<\/code>-Werte aus dem sortierten Array und sende ein Array zur\u00fcck, das so aussieht: <code>['en-CA', 'ar-EG']<\/code>.<\/li>\n<\/ol>\n<p>Jetzt kannst du unsere neue Klasse verwenden, um ein sch\u00f6nes, verwendbares Array von Locale-Codes zu erhalten, basierend auf dem <code>Accept-Language<\/code> HTTP-Header.<\/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=\"server-seitig-geolokalisierung-nach-ip-adresse\"><\/span>Server-seitig: Geolokalisierung nach IP-Adresse<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Manchmal fehlt der <code>Accept-Language<\/code>-Header bei Anfragen an unseren Server. In diesen F\u00e4llen k\u00f6nnen wir die IP-Adresse des Users verwenden, um sein oder ihr Land zu bestimmen und daraus die Sprache oder Spracheinstellung abzuleiten.<\/p>\n<p>\u270b\ud83c\udffd <em>Vorsicht \u00bb<\/em> Geolokalisierung sollte nur als letzte M\u00f6glichkeit genutzt werden, um eine Spracheinstellung zu erkennen, da sie oft zu einer falschen Sprache f\u00fchren kann. Wenn wir z.\u00a0B. sehen, dass du aus Kanada kommst, nehmen wir dann an, dass deine bevorzugte Sprache Englisch oder Franz\u00f6sisch ist? Beide sind offizielle Landessprachen und weit verbreitet. Und nat\u00fcrlich k\u00f6nntest du einer arabischsprachigen Minderheit angeh\u00f6ren oder ein spanischsprachiger Besucher sein &#8230;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"verwendung-von-maxmind-fuer-geolokation\"><\/span>Verwendung von MaxMind f\u00fcr Geolokation<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Um das Land des User anhand der IP-Adresse der Anfrage zu bestimmen, nutzen wir die MaxMind PHP API und die MaxMind GeoIP-Datenbank. MaxMind ist ein Unternehmen, das einige IP-bezogene Produkte anbietet. Dazu geh\u00f6ren zwei Produkte, die f\u00fcr uns hier interessant sind:<\/p>\n<ul>\n<li><a href=\"https:\/\/www.maxmind.com\/en\/geoip-databases\">Die GeoIP2-Datenbanken<\/a> sind die kommerziellen Geolokalisierungs-Datenbanken von MaxMind, die latenzarm und abonnementbasiert sind. Vielleicht solltest du auf diese upgraden, wenn du aktuellere oder schnellere Datenbanken willst.<\/li>\n<li><a href=\"https:\/\/dev.maxmind.com\/geoip\/\">Die GeoLite2-Datenbanken<\/a> sind die kostenlosen Geolokalisierungs-Datenbanken von MaxMind, und obwohl sie laut Berichten weniger genau sind als ihre kommerziellen Pendants, reichen sie f\u00fcr den Anfang mehr als aus. Wir werden hier eine GeoLite2-Datenbank verwenden. Beachte, dass du Maxmind auf deiner <a href=\"https:\/\/phrase.com\/de\/blog\/posts\/how-translate-web-page\/\">\u00f6ffentlichen Webseite<\/a> erw\u00e4hnen und auf deren Seite verlinken musst, wenn du eine ihrer kostenlosen Datenbanken verwendest.<\/li>\n<\/ul>\n<p>Um die Datenbank zu installieren, melde dich einfach f\u00fcr <a href=\"https:\/\/www.maxmind.com\/en\/geolite2\/signup\">ein kostenloses MaxMind-Konto<\/a> an. Du erh\u00e4ltst eine E-Mail mit einem Anmeldelink. Folge dem Link und melde dich an. Sobald du das getan hast, solltest du auf deiner Seite <em>Kontozusammenfassung<\/em> landen.<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=\"MaxMind Datenbanken herunterladen | 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>Klick auf den Link Datenbanken herunterladen auf der Kontozusammenfassungsseite<\/em><\/p>\n<p>Das bringt dich zu einer Seite mit der Liste der kostenlosen GeoLite2-Datenbanken. Hol dir die <em>L\u00e4nder-Bin\u00e4rdatenbank<\/em> von dort.<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=\"MaxMind L\u00e4nderdatenbank (Bin\u00e4rformat) | 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>Wir ben\u00f6tigen die bin\u00e4re L\u00e4nderdatenbank f\u00fcr unsere Zwecke<\/em><\/p>\n<p>Lege die Datei, die du heruntergeladen hast, irgendwo in deinem Projekt ab.<br \/>\nDu brauchst au\u00dferdem die MaxMind PHP API, um mit der Datenbank zu arbeiten. Wir k\u00f6nnen das mit <a href=\"https:\/\/getcomposer.org\/\">Composer<\/a> installieren.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">composer require geoip2\/geoip2:~2.0<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"peter-kahls-country-to-locale-paket\"><\/span>Peter Kahls Country-to-Locale-Paket<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Wir ben\u00f6tigen noch ein weiteres Paket, bevor wir zu unserem Code kommen. Um die <em>Locales<\/em> oder Sprachen eines Landes zu bestimmen, verwenden wir Peter Kahls <code>Country-to-Locale<\/code>-Paket. Wir k\u00f6nnen es auch mit Composer installieren.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">composer require peterkahl\/country-to-locale<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"ip-adress-lokalisierungs-klasse\"><\/span>IP-Adress-Lokalisierungs-Klasse<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Mit unserem Setup kommen wir nun zu unserer eigenen Klasse <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); \/\/ Ruft das Land f\u00fcr die angegebene IP-Adresse ab.\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>Unsere Klasse ist relativ einfach. \u00c4hnlich wie <code>HttpAcceptLanguageHeaderLocaleDetector<\/code> hat sie eine \u00f6ffentliche Methode, <code>detect()<\/code>, die Folgendes tut:<\/p>\n<ol>\n<li>Holt die IP-Adresse der Anfrage aus dem globalen <code>$_SERVER<\/code>-Array.<\/li>\n<li>\u00dcbergibt diese IP-Adresse an die Methode <code>country<\/code> von <code>Reader<\/code> in der MaxMind-Datenbank, die versucht, anhand der IP-Adresse ein Land zu ermitteln.<\/li>\n<li>Verwendet Peter Kahls <code>locale::country2locale()<\/code>, um die Sprachen eines bestimmten Landes zu ermitteln.<\/li>\n<li>Normalisiert die \u00fcbernommenen Locales (Sprachen), sodass <code>\"en_CA,ar_EG\"<\/code> zu <code>\"en-CA,ar-EG\"<\/code> wird.<\/li>\n<li>Gibt die normalisierten Locales als Array wieder, z.\\u00a0B. <code>[\"en-CA\", \"ar-EG\"]<\/code>.<\/li>\n<\/ol>\n<p>\ud83d\udcd6 <em>Mehr erfahren \u00bb<\/em> Der MaxMind <code>Reader<\/code> hat viele weitere Methoden. Sieh dir die <a href=\"https:\/\/maxmind.github.io\/GeoIP2-php\/\">offizielle API-Dokumentation<\/a> an, wenn du etwas tiefer in die Informationen eintauchen m\u00f6chtest, die in den MaxMind-Datenbanken verf\u00fcgbar sind.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"serverseitig-kaskadierende-spracherkennung\"><\/span>Serverseitig: Kaskadierende Spracherkennung<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Angesichts der beiden serverseitigen Erkennungsstrategien, die wir oben behandelt haben, k\u00f6nnen wir eine kleine <code>detect_user_locales()<\/code>-Funktion schreiben, die zuerst die HTTP-Header-Strategie versucht.<\/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    \/\/ auf eine Standardsprache zur\u00fcckgreifen, in diesem Fall Englisch\n    $locales = ['en'];\n  }\n  return $locales;\n}<\/pre>\n<p>Wenn die HTTP-Header-Erkennung fehlschl\u00e4gt, versucht <code>detect_user_locales()<\/code>, die IP-Geolokalisierung zu verwenden. Wenn letzteres keine Ergebnisse bringt, wird die Funktion auf eine Standardsprache zur\u00fcckgreifen.<br \/>\nWenn du es richtig machst, kann das Erkennen der Sprache des Users dabei helfen, ein besseres User-Erlebnis in unseren Web-Apps zu schaffen. Gott sei Dank sind das Objekt <code>navigator.languages<\/code> und der <code>Accept-Language<\/code> HTTP-Header verf\u00fcgbar, um unsere Vermutungen bei der Erkennung der Spracheinstellungen zu reduzieren.<\/p>\n<p>Wenn du und dein Team an einer internationalisierten Webanwendung arbeiten, schau dir Phrase f\u00fcr eine professionelle, entwicklerfreundliche i18n-Plattform an. Mit einer flexiblen CLI und API, \u00dcbersetzungs-Synchronisierung mit GitHub- und Bitbucket-Integration, <a href=\"https:\/\/phrase.com\/de\/blog\/posts\/machine-translation\/\">Over-the-Air (OTA)-\u00dcbersetzungen<\/a> und vielem mehr bist du mit Phrase in Sachen i18n bestens aufgestellt, sodass du dich auf deine Gesch\u00e4ftslogik konzentrieren kannst.<\/p>\n<p>Schau dir alle <a href=\"https:\/\/phrase.com\/de\/roles\/developers\/\">Phrase-Funktionen f\u00fcr Entwickler<\/a> an und sieh selbst, wie sie deine Software-Lokalisierungs-Workflows optimieren k\u00f6nnen.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Eines der h\u00e4ufigsten Probleme bei der Entwicklung von Webanwendungen besteht darin, die Sprache und Region eines Users zu erkennen. So machst du es richtig.<\/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":[46],"class_list":["post-133276","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-software-lokalisierung"],"acf":[],"_links":{"self":[{"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/posts\/133276","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/comments?post=133276"}],"version-history":[{"count":4,"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/posts\/133276\/revisions"}],"predecessor-version":[{"id":136330,"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/posts\/133276\/revisions\/136330"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/media\/2612"}],"wp:attachment":[{"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/media?parent=133276"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/categories?post=133276"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}