Skip to content

Instantly share code, notes, and snippets.

@SimonEast
Created January 15, 2018 05:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SimonEast/4817d8c48cf69164a850fe655d1737b0 to your computer and use it in GitHub Desktop.
Save SimonEast/4817d8c48cf69164a850fe655d1737b0 to your computer and use it in GitHub Desktop.
PHP: Choosing the closest matching language from Accept-Language header
<?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