Detect Browser Language in PHP
The existing answers are a little too verbose so I created this smaller, auto-matching version.
function prefered_language(array $available_languages, $http_accept_language) {
$available_languages = array_flip($available_languages);
$langs;
preg_match_all('~([\w-]+)(?:[^,\d]+([\d.]+))?~', strtolower($http_accept_language), $matches, PREG_SET_ORDER);
foreach($matches as $match) {
list($a, $b) = explode('-', $match[1]) + array('', '');
$value = isset($match[2]) ? (float) $match[2] : 1.0;
if(isset($available_languages[$match[1]])) {
$langs[$match[1]] = $value;
continue;
}
if(isset($available_languages[$a])) {
$langs[$a] = $value - 0.1;
}
}
arsort($langs);
return $langs;
}
And the sample usage:
//$_SERVER["HTTP_ACCEPT_LANGUAGE"] = 'en-us,en;q=0.8,es-cl;q=0.5,zh-cn;q=0.3';
// Languages we support
$available_languages = array("en", "zh-cn", "es");
$langs = prefered_language($available_languages, $_SERVER["HTTP_ACCEPT_LANGUAGE"]);
/* Result
Array
(
[en] => 0.8
[es] => 0.4
[zh-cn] => 0.3
)*/
Full gist source here
The official way to handle this is using the PECL HTTP library. Unlike some answers here, this correctly handles the language priorities (q-values), partial language matches and will return the closest match, or when there are no matches it falls back to the first language in your array.
PECL HTTP:
http://pecl.php.net/package/pecl_http
How to use:
http://php.net/manual/fa/function.http-negotiate-language.php
$supportedLanguages = [
'en-US', // first one is the default/fallback
'fr',
'fr-FR',
'de',
'de-DE',
'de-AT',
'de-CH',
];
// Returns the negotiated language
// or the default language (i.e. first array entry) if none match.
$language = http_negotiate_language($supportedLanguages, $result);
Accept-Language is a list of weighted values (see q parameter). That means just looking at the first language does not mean it’s also the most preferred; in fact, a q value of 0 means not acceptable at all.
So instead of just looking at the first language, parse the list of accepted languages and available languages and find the best match:
// parse list of comma separated language tags and sort it by the quality value
function parseLanguageList($languageList) {
if (is_null($languageList)) {
if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
return array();
}
$languageList = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
}
$languages = array();
$languageRanges = explode(',', trim($languageList));
foreach ($languageRanges as $languageRange) {
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($languageRange), $match)) {
if (!isset($match[2])) {
$match[2] = '1.0';
} else {
$match[2] = (string) floatval($match[2]);
}
if (!isset($languages[$match[2]])) {
$languages[$match[2]] = array();
}
$languages[$match[2]][] = strtolower($match[1]);
}
}
krsort($languages);
return $languages;
}
// compare two parsed arrays of language tags and find the matches
function findMatches($accepted, $available) {
$matches = array();
$any = false;
foreach ($accepted as $acceptedQuality => $acceptedValues) {
$acceptedQuality = floatval($acceptedQuality);
if ($acceptedQuality === 0.0) continue;
foreach ($available as $availableQuality => $availableValues) {
$availableQuality = floatval($availableQuality);
if ($availableQuality === 0.0) continue;
foreach ($acceptedValues as $acceptedValue) {
if ($acceptedValue === '*') {
$any = true;
}
foreach ($availableValues as $availableValue) {
$matchingGrade = matchLanguage($acceptedValue, $availableValue);
if ($matchingGrade > 0) {
$q = (string) ($acceptedQuality * $availableQuality * $matchingGrade);
if (!isset($matches[$q])) {
$matches[$q] = array();
}
if (!in_array($availableValue, $matches[$q])) {
$matches[$q][] = $availableValue;
}
}
}
}
}
}
if (count($matches) === 0 && $any) {
$matches = $available;
}
krsort($matches);
return $matches;
}
// compare two language tags and distinguish the degree of matching
function matchLanguage($a, $b) {
$a = explode('-', $a);
$b = explode('-', $b);
for ($i=0, $n=min(count($a), count($b)); $i<$n; $i++) {
if ($a[$i] !== $b[$i]) break;
}
return $i === 0 ? 0 : (float) $i / count($a);
}
$accepted = parseLanguageList($_SERVER['HTTP_ACCEPT_LANGUAGE']);
var_dump($accepted);
$available = parseLanguageList('en, fr, it');
var_dump($available);
$matches = findMatches($accepted, $available);
var_dump($matches);
If findMatches
returns an empty array, no match was found and you can fall back on the default language.
why dont you keep it simple and clean
<?php
$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
$acceptLang = ['fr', 'it', 'en'];
$lang = in_array($lang, $acceptLang) ? $lang : 'en';
require_once "index_{$lang}.php";
?>