среда, 2 ноября 2011 г.

PHP определение языка пользователя

Если вы делаете сайт на нескольких языках, то вы обязательно столкнётесь с задачей:
Как определить язык пользователя на стороне сервера (в PHP)?
Один из часто используемых вариантов - использовать Geo IP и дальше уже по стране определять язык.
Но на мой взгляд более правильный способ - это использовать заголовки, которые для этого предназначены, а именно Accept-Language, в котором перечислены с указанием приоритета предпочитаемые языки.
Ниже представлена функция на PHP, которая парсит заголовок Accept-Language и находит наиболее подходящий язык для пользователя из тех, что переданы массивом (параметр $languages).
При этом, если ни одного языка не нашлось в заголовке, то будет использоваться первый из массива $languages.
function getAcceptLanguage($languages)
{
    if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) || 
        !$_SERVER['HTTP_ACCEPT_LANGUAGE']) {
        return $languages[0];
    }  
   
    /*
     * Разбираем заголовок Accept-Language
     *
     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
     * 14.4 Accept-Language
     * Accept-Language = "Accept-Language" ":"
     *                   1#( language-range [ ";" "q" "=" qvalue ] )
     * language-range  = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
     *
     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.9
     * 3.9 Quality Values
     * qvalue         = ( "0" [ "." 0*3DIGIT ] )
     *                | ( "1" [ "." 0*3("0") ] )
     */
    preg_match_all("/([a-z]{1,8})(?:-([a-z]{1,8}))?(?:\s*;\s*q\s*=\s*(1|1\.0{0,3}|0|0\.[0-9]{0,3}))?\s*(?:,|$)/i",
                   $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches);

    // Результат по умолчанию - первый из доступных языков
    $result = $languages[0];
    $max_q = 0;
   
    for ($i = 0; $i < count($matches); $i++) {
        // Выделяем очередной язык
        $lang = $matches[1][$i];
        if (!empty($matches[2][$i])) {
            // Переводим ru-RU в ru_RU (т.к. локаль ru_RU)
            $lang .= '_'.$matches[2][$i]; 
        }
        // Определяем приоритет
        if (!empty($matches[3][$i])) {
            $q = (float)$matches[3][$i];   
        } else {
            $q = 1.0;
        }  
        // Проверяем есть ли проверяемый язык в массиве доступных
        if (in_array($lang, $languages) && ($q > $max_q)) {
            $result = $lang;
            $max_q = $q;
        }
        // Если язык только из первой части (например, просто ru, а не ru-RU) и более приоритетный язык еще не найден, 
        // то пробуем найти в массиве доступых языков тот, который начинается так же   
        elseif (empty($matches[2][$i]) && ($q * 0.8 > $max_q)) {
            $n = strlen($lang);
            foreach ($languages as $l) {
                if (!strncmp($l, $lang, $n)) {
                    $result = $l;
                    // Поскольку не точное совпадение, то уменьшаем q на 20%
                    $max_q = $q * 0.8;
                    break;
                }
            }
        }
    }
    return $result;
}
Пример использования:
$locale = getAcceptLanguage(array('en_US', 'ru_RU', 'de_DE'));