Created
January 15, 2018 05:56
-
-
Save SimonEast/4817d8c48cf69164a850fe655d1737b0 to your computer and use it in GitHub Desktop.
PHP: Choosing the closest matching language from Accept-Language header
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* So you have a list of languages your site supports | |
* and want to automatically assign the user to the closest | |
* one. | |
* | |
* Usage: | |
* | |
* LanguageDetect::findBestMatch(['en', 'fr', 'gr']); | |
* LanguageDetect::findBestMatchOrFallback(['en', 'fr', 'gr']); | |
* | |
* The above will automatically parse the browser's Accept-Language header. | |
* You can alternatively pass a second parameter if you want to supply another | |
* string or array instead of looking in the header. | |
*/ | |
class LanguageDetect { | |
/** | |
* Provide a list of supported languages and this function | |
* will scan the list supported by the user's browser and | |
* return the closest language the user supports. | |
* | |
* False if no match is found. Use findBestMatchOrFallback() | |
* if you want to automatically default to the first language. | |
* | |
* @param $supportedLanguages - array of supported languages (use lowercase) | |
* @param $userLanguages - leave null to use $_SERVER['HTTP_ACCEPT_LANGUAGE'] | |
* Otherwise pass a string like 'en,fr,cn' or 'en,fr;q=0.5' | |
*/ | |
public static function findBestMatch($supportedLanguages, $userLanguages = null) | |
{ | |
if (empty($userLanguages)) | |
$userLanguages = @$_SERVER['HTTP_ACCEPT_LANGUAGE']; | |
if (!is_array($userLanguages)) | |
$userLanguages = self::languageStringToArray($userLanguages); | |
// Find exact matches | |
foreach ($userLanguages as $l) { | |
if (in_array($l, $supportedLanguages)) | |
return $l; | |
} | |
// No exact match? Let's look for partial matches ('en-us' == 'en') | |
// Note: currently will NOT find matches when supportedLanguages is more specific ('en-us') | |
foreach ($userLanguages as $l) { | |
list($l) = explode('-', $l); | |
if (in_array($l, $supportedLanguages)) | |
return $l; | |
} | |
// No match at all | |
return false; | |
} | |
public static function findBestMatchOrFallback($supportedLanguages, $userLanguages = null) | |
{ | |
$bestMatch = self::findBestMatch($supportedLanguages, $userLanguages); | |
if (!empty($bestMatch)) | |
return $bestMatch; | |
// No result? Return first supported language as a fallback | |
return $supportedLanguages[0]; | |
} | |
/** | |
* Converts a comma-separated string of accepted languages (with an | |
* optional priority) into an array of languages, ordered by priority | |
* | |
* @param $languageString - eg. 'en-us,en;q=0.8,fr;q=0.5' | |
* @return an array of languages, lowercased, ordered by priority, e.g. ['en-us','en','fr'] | |
*/ | |
public static function languageStringToArray($languageString) | |
{ | |
$languages = explode(',', trim($languageString)); | |
$result = []; | |
foreach ($languages as $language) { | |
if (preg_match('/(\*|[a-zA-Z0-9]{1,8}(?:-[a-zA-Z0-9]{1,8})*)(?:\s*;\s*q\s*=\s*(0(?:\.\d{0,3})|1(?:\.0{0,3})))?/', trim($language), $match)) { | |
// $match[1] is the language, $match[2] is the priority (if present) | |
if (!isset($match[2])) { | |
$priority = '1.0'; | |
} else { | |
$priority = (string) floatval($match[2]); | |
} | |
$result[strtolower($match[1])] = $priority; | |
} | |
} | |
// Sort by priority, descending | |
arsort($result); | |
// Return only the keys, discarding the priorities | |
return array_keys($result); | |
} | |
} | |
// TESTS | |
// print_r(LanguageDetect::languageStringToArray('en-us,en;q=1,fr;q=0.5')); | |
// assert(LanguageDetect::findBestMatch(['en','cn'], 'en-us,en;q=0.8,fr;q=0.5') == 'en'); | |
// assert(LanguageDetect::findBestMatch(['cn'], 'en-us,en;q=0.8,fr;q=0.5') == null); | |
// assert(LanguageDetect::findBestMatch(['en','en-us','cn'], 'en-us,en;q=0.8,fr;q=0.5') == 'en-us'); | |
// assert(LanguageDetect::findBestMatch(['fr','en','en-us','cn'], 'en-us,en;q=0.8,fr;q=0.5') == 'en-us'); | |
// assert(LanguageDetect::findBestMatch(['fr','cn'], 'en-us,en;q=0.8,fr;q=0.5') == 'fr'); | |
// assert(LanguageDetect::findBestMatch(['en'], 'en-us;q=0.8') == 'en'); | |
// assert(LanguageDetect::findBestMatch(['en'], 'fr,en-us;q=0.8') == 'en'); | |
// assert(LanguageDetect::findBestMatch(['en-us','en'], 'fr,gr,en,en-us;q=0.8') == 'en'); | |
// assert(LanguageDetect::findBestMatch(['en-us','en'], 'fr,gr,en-us,en;q=0.8') == 'en-us'); | |
// assert(LanguageDetect::findBestMatch(['en-us','en'], 'fr,gr,en;q=0.8,en-us') == 'en-us'); | |
// assert(LanguageDetect::findBestMatch(['en-us','en'], 'fr,gr,en-us;q=0.8,en') == 'en'); | |
// assert(LanguageDetect::findBestMatchOrFallback(['en-us','en'], 'fr,gr,en-us;q=0.8,en') == 'en'); | |
// assert(LanguageDetect::findBestMatchOrFallback(['xa','xb'], 'fr,gr,en-us;q=0.8,en') == 'xa'); | |
// assert(LanguageDetect::findBestMatchOrFallback(['xa','xb'], 'xa') == 'xa'); | |
// assert(LanguageDetect::findBestMatchOrFallback(['xa','xb'], 'xa,xb') == 'xa'); | |
// assert(LanguageDetect::findBestMatchOrFallback(['xa','xb'], 'xb,xa') == 'xb'); | |
// assert(LanguageDetect::findBestMatchOrFallback(['xa','xb'], 'xb-xx,xa') == 'xa'); | |
// assert(LanguageDetect::findBestMatchOrFallback(['xa','xb'], 'xb-xx,xa-xx') == 'xb'); | |
// assert(LanguageDetect::findBestMatchOrFallback(['xa','xb'], 'xb;q=0.7,xa;q=1.0') == 'xa'); | |
// assert(LanguageDetect::findBestMatchOrFallback(['xa','xb'], 'xd;q=0.7,xc;q=1.0') == 'xa'); | |
// assert(LanguageDetect::findBestMatchOrFallback(['xa'], 'fr,gr,en-us;q=0.8,en') == 'xa'); | |
// assert(LanguageDetect::findBestMatchOrFallback(['xa','xb'], '') == 'xa'); | |
// assert(LanguageDetect::findBestMatchOrFallback(['xa','xb'], '') == 'xa'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment