PHP language detection

Viewed 35135

I'm trying to build multilangual site.

I use this piece of code to detect users language. If you havent chosen a language, it will include your language file based on HTTP_ACCEPT_LANGUAGE.

I don't know where it gets it from though:

session_start();

if (!isset($_SESSION['lang'])) {
   $_SESSION['lang'] = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
}

elseif (isset($_GET['setLang']) && $_GET['setLang'] == 'en') $_SESSION['lang'] = "en";
elseif (isset($_GET['setLang']) && $_GET['setLang'] == 'sv') $_SESSION['lang'] = "sv";
elseif (isset($_GET['setLang']) && $_GET['setLang'] == 'pl') $_SESSION['lang'] = "pl";
elseif (isset($_GET['setLang']) && $_GET['setLang'] == 'fr') $_SESSION['lang'] = "fr";

include('languages/'.$_SESSION['lang'].'.php');

It works for me and includes the polish lang file. But is this code accurate? Or is there another way?

10 Answers

Here's a function for selecting the best out of a group of supported languages. It extracts languages from Accept-Language, then sorts the given array of languages according to their priority.

function select_best_language($languages) {
    if (!$_SERVER['HTTP_ACCEPT_LANGUAGE']) return $languages[0];
    $default_q=100;
    foreach (explode(",",$_SERVER['HTTP_ACCEPT_LANGUAGE']) as $lqpair) {
        $lq=explode(";q=",$lqpair);
        if ($lq[1]) $lq[1]=floatval($lq[1]); else $lq[1]=$default_q--;
        $larr[$lq[0]]=$lq[1];
    }
    usort($languages,function($a,$b) use ($larr) { return $larr[$b]<=>$larr[$a]; });
    return $languages[0];
}

$lang = select_best_language(['en','fr','it']);

I solved this issue for PHP7.4+ with the code below. It strips out the first two letters of the locale code, takes into account cases like 'en-US' and produces a map like:

$map = [
    'en' => 1,
    'de' => 0.8,
    'uk' => 0.3
];

The code is as follows:


$header  = 'en-US,de-DE;q=0.8,uk;q=0.3';
$pattern = '((?P<code>[a-z-_A-Z]{2,5})([;q=]+?(?P<prio>0.\d+))?)';

preg_match_all($pattern, $header, $matches);
['code' => $codes, 'prio' => $values] = $matches;

$map = \array_combine(
    \array_map(fn(string $language) => strtolower(substr($language, 0, 2)), \array_values($codes)),
    \array_map(fn(string $value)    => empty($value) ? 1 : (float)$value, \array_values($values))
);

Please note that the regex is probably suboptimal, improvement suggestions are welcome. Named capture groups are always in the order of matching, so we can use array_combine safely.

Related