2010-09-22 2 views
118

Я использую следующий PHP-скрипт в качестве индекса для своего сайта.Определить язык браузера в PHP

Этот скрипт должен содержать определенную страницу в зависимости от языка браузера (автоматически определяется).

Этот сценарий не работает со всеми браузерами, поэтому он всегда включает в себя index_en.php для любого обнаруженного языка (причиной проблемы является, скорее всего, проблема с некоторым недопустимым заголовком Accept-Language).

Не могли бы вы предложить мне более надежное решение?

<?php 
// Open session var 
session_start(); 
// views: 1 = first visit; >1 = second visit 

// Detect language from user agent browser 
function lixlpixel_get_env_var($Var) 
{ 
    if(empty($GLOBALS[$Var])) 
    { 
     $GLOBALS[$Var]=(!empty($GLOBALS['_SERVER'][$Var]))? 
     $GLOBALS['_SERVER'][$Var] : (!empty($GLOBALS['HTTP_SERVER_VARS'][$Var])) ? $GLOBALS['HTTP_SERVER_VARS'][$Var]:''; 
    } 
} 

function lixlpixel_detect_lang() 
{ 
    // Detect HTTP_ACCEPT_LANGUAGE & HTTP_USER_AGENT. 
    lixlpixel_get_env_var('HTTP_ACCEPT_LANGUAGE'); 
    lixlpixel_get_env_var('HTTP_USER_AGENT'); 

    $_AL=strtolower($GLOBALS['HTTP_ACCEPT_LANGUAGE']); 
    $_UA=strtolower($GLOBALS['HTTP_USER_AGENT']); 

    // Try to detect Primary language if several languages are accepted. 
    foreach($GLOBALS['_LANG'] as $K) 
    { 
     if(strpos($_AL, $K)===0) 
     return $K; 
    } 

    // Try to detect any language if not yet detected. 
    foreach($GLOBALS['_LANG'] as $K) 
    { 
     if(strpos($_AL, $K)!==false) 
     return $K; 
    } 
    foreach($GLOBALS['_LANG'] as $K) 
    { 
     //if(preg_match("/[[(]{$K}[;,_-)]/",$_UA)) // matching other letters (create an error for seo spyder) 
     return $K; 
    } 

    // Return default language if language is not yet detected. 
    return $GLOBALS['_DLANG']; 
} 

// Define default language. 
$GLOBALS['_DLANG']='en'; 

// Define all available languages. 
// WARNING: uncomment all available languages 

$GLOBALS['_LANG'] = array(
'af', // afrikaans. 
'ar', // arabic. 
'bg', // bulgarian. 
'ca', // catalan. 
'cs', // czech. 
'da', // danish. 
'de', // german. 
'el', // greek. 
'en', // english. 
'es', // spanish. 
'et', // estonian. 
'fi', // finnish. 
'fr', // french. 
'gl', // galician. 
'he', // hebrew. 
'hi', // hindi. 
'hr', // croatian. 
'hu', // hungarian. 
'id', // indonesian. 
'it', // italian. 
'ja', // japanese. 
'ko', // korean. 
'ka', // georgian. 
'lt', // lithuanian. 
'lv', // latvian. 
'ms', // malay. 
'nl', // dutch. 
'no', // norwegian. 
'pl', // polish. 
'pt', // portuguese. 
'ro', // romanian. 
'ru', // russian. 
'sk', // slovak. 
'sl', // slovenian. 
'sq', // albanian. 
'sr', // serbian. 
'sv', // swedish. 
'th', // thai. 
'tr', // turkish. 
'uk', // ukrainian. 
'zh' // chinese. 
); 

// Redirect to the correct location. 
// Example Implementation aff var lang to name file 
/* 
echo 'The Language detected is: '.lixlpixel_detect_lang(); // For Demonstration 
echo "<br />";  
*/ 
$lang_var = lixlpixel_detect_lang(); //insert lang var system in a new var for conditional statement 
/* 
echo "<br />";  

echo $lang_var; // print var for trace 

echo "<br />";  
*/ 
// Insert the right page iacoording with the language in the browser 
switch ($lang_var){ 
    case "fr": 
     //echo "PAGE DE"; 
     include("index_fr.php");//include check session DE 
     break; 
    case "it": 
     //echo "PAGE IT"; 
     include("index_it.php"); 
     break; 
    case "en": 
     //echo "PAGE EN"; 
     include("index_en.php"); 
     break;   
    default: 
     //echo "PAGE EN - Setting Default"; 
     include("index_en.php");//include EN in all other cases of different lang detection 
     break; 
} 
?> 
+3

PHP 5.3.0+ поставляется с 'locale_accept_from_http()', который получает предпочтительный язык из заголовка 'Accept-Language'. Вы всегда должны отдавать предпочтение этому методу самостоятельно. Проверьте результат на список регулярных выражений, которые вы пытаетесь, и определите язык страницы таким образом. См. [PHP-I18N] (https://github.com/delight-im/PHP-I18N/blob/master/classes/I18N.php) для примера. – caw

+1

Проблема с 'locale_accept_from_http()' заключается в том, что вы не можете поддерживать лучший результат, который он возвращает, поэтому у вас все еще есть [проанализировать заголовок самостоятельно] (https://gist.github.com/Xeoncross/dc2ebf017676ae946082), чтобы найти * следующий лучший *. – Xeoncross

ответ

269

почему не вы держите его простым и чистым

<?php 
$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2); 
switch ($lang){ 
    case "fr": 
     //echo "PAGE FR"; 
     include("index_fr.php");//include check session FR 
     break; 
    case "it": 
     //echo "PAGE IT"; 
     include("index_it.php"); 
     break; 
    case "en": 
     //echo "PAGE EN"; 
     include("index_en.php"); 
     break;   
    default: 
     //echo "PAGE EN - Setting Default"; 
     include("index_en.php");//include EN in all other cases of different lang detection 
     break; 
} 
?> 
+0

Просто вопросы в случае, если СЕССИЯ на сервере отключена, скрипт может работать? спасибо – GibboK

+7

Датский, греческий и словенский языковые коды - это одна буква. Кажется, лучше взорваться вот так: http://www.php.net/manual/tr/reserved.variables.server.php#90293 – trante

+7

@trante: Почему вы говорите, что это одна буква? Голландский ('nl'), греческий (' el') и словенский ('sl') все выглядят как две буквы: http://msdn.microsoft.com/en-us/library/ms533052(v=vs.85) .aspx –

64

Accept-Language список взвешенных значений (см д параметра). Это означает, что просто смотреть на первый язык не означает, что он также является самым предпочтительным; на самом деле, значение 0 означает вообще неприемлемое значение q.

Таким образом, вместо того, чтобы просто смотреть на первом языке, анализировать список принятых языков и доступных языков и найти лучший матч:

// 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); 

Если findMatches возвращает пустой массив, ничего не было найдено, и вы можете вернуться к языку по умолчанию.

+0

Привет, сценарий работал отлично и теперь остановился. Возможно, если SESSION на сервере выключится, этот скрипт не будет работать? – GibboK

+0

@GIbboK: Нет, это не зависит от сеансов. – Gumbo

+0

Правильно, но я предпочитаю решение @diggersworld ... лучше писать меньше кода – lrkwz

-1

У меня есть этот, который устанавливает cookie. И, как вы можете видеть, он сначала проверяет, опубликован ли пользователь пользователем. Потому что язык браузера не всегда говорит о пользователе.

<?php 
    $lang = getenv("HTTP_ACCEPT_LANGUAGE"); 
    $set_lang = explode(',', $lang); 
    if (isset($_POST['lang'])) 
     { 
      $taal = $_POST['lang']; 
      setcookie("lang", $taal); 
      header('Location: /p/'); 
     } 
    else 
     { 
      setcookie("lang", $set_lang[0]); 
      echo $set_lang[0]; 
      echo '<br>'; 
      echo $set_lang[1]; 
      header('Location: /p/'); 
     } 
?> 
+9

Думаю, вы не можете отправлять заголовки, когда вы уже эхом отзывались? – 2012-07-19 20:31:59

+1

Я думаю, что отступы, стоящие за этим постом, имеют смысл, а именно предоставить пользователю возможность переключить язык и запомнить это решение. Обнаружение языка следует делать только один раз, чтобы лучше всего угадать первый выбор. – danijar

20

Официальный способ справиться с этим - использовать PECL HTTP library. В отличие от некоторых ответов здесь, это правильно обрабатывает приоритеты языка (q-значения), совпадения с частичным языком и возвращает самое близкое совпадение, или когда нет совпадений, оно возвращается к первому языку в вашем массиве.

PECL HTTP:
http://pecl.php.net/package/pecl_http

Как использовать:
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); 
+0

Я нашел рабочую ссылку, поэтому обновил ваш ответ, чтобы включить ее. –

33

Существующих ответы является слишком громоздким, так что я создал эту меньшую, авто-согласование версию.

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; 
} 

И использование образец:

//$_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

+5

Это * блестящий * и именно то, что мне нужно для конкретного проекта сегодня. Единственное, что я сделал, это позволить функции принимать язык по умолчанию и вернуться к этому, если между доступными языками и HTTP_ACCEPT_LANGUAGE нет соответствия. – Scottie

+7

О, суть с моими изменениями здесь: https://gist.github.com/humantorch/d255e39a8ab4ea2e7005 (я также объединил его в один файл для простоты). – Scottie

+2

Очень хороший метод! Возможно, вы должны проверить, содержит ли $ langs запись для языка. случилось со мной, что перферрированный язык был en-US, 2nd de и 3rd en, ваш метод всегда дал мне de, потому что первое значение en было перезаписано третьей записью –

2

Следующий сценарий представляет собой модифицированную версию кода Xeoncross в (спасибо за это Xeoncross), который падает-обратно в настройки языка по умолчанию если ни один из языков не соответствует поддерживаемым, или если найдено совпадение, оно заменяет исходный язык по умолчанию на новый в соответствии с приоритетом языка.

В этом случае браузер пользователя устанавливается в порядке приоритета на испанский, голландский, американский и английский языки, и приложение поддерживает английский и голландский только без региональных вариаций, а английский язык по умолчанию. Порядок значений в строке «HTTP_ACCEPT_LANGUAGE» не важен, если по какой-то причине браузер не упорядочивает значения правильно.

$supported_languages = array("en","nl"); 
$supported_languages = array_flip($supported_languages); 
var_dump($supported_languages); // array(2) { ["en"]=> int(0) ["nl"]=> int(1) } 

$http_accept_language = $_SERVER["HTTP_ACCEPT_LANGUAGE"]; // es,nl;q=0.8,en-us;q=0.5,en;q=0.3 

preg_match_all('~([\w-]+)(?:[^,\d]+([\d.]+))?~', strtolower($http_accept_language), $matches, PREG_SET_ORDER); 

$available_languages = array(); 

foreach ($matches as $match) 
{ 
    list($language_code,$language_region) = explode('-', $match[1]) + array('', ''); 

    $priority = isset($match[2]) ? (float) $match[2] : 1.0; 

    $available_languages[][$language_code] = $priority; 
} 

var_dump($available_languages); 

/* 
array(4) { 
    [0]=> 
    array(1) { 
     ["es"]=> 
     float(1) 
    } 
    [1]=> 
    array(1) { 
     ["nl"]=> 
     float(0.8) 
    } 
    [2]=> 
    array(1) { 
     ["en"]=> 
     float(0.5) 
    } 
    [3]=> 
    array(1) { 
     ["en"]=> 
     float(0.3) 
    } 
} 
*/ 

$default_priority = (float) 0; 
$default_language_code = 'en'; 

foreach ($available_languages as $key => $value) 
{ 
    $language_code = key($value); 
    $priority = $value[$language_code]; 

    if ($priority > $default_priority && array_key_exists($language_code,$supported_languages)) 
    { 
     $default_priority = $priority; 
     $default_language_code = $language_code; 

     var_dump($default_priority); // float(0.8) 
     var_dump($default_language_code); // string(2) "nl" 
    } 
} 

var_dump($default_language_code); // string(2) "nl" 
10

Проблема с выбранным ответом выше является то, что пользователь может иметь их первый выбор устанавливается в качестве языка, это не в случае структуры, но один из них другого выбора языка установлены. Вы должны зацикливаться до тех пор, пока не найдете совпадение.

Это супер простое решение, которое работает лучше. Браузеры возвращают языки в порядке предпочтения, что упрощает проблему. Хотя обозначение языка может содержать более двух символов (например, «EN-US»), обычно для двух первых достаточно. В следующем примере кода я ищу соответствие из списка известных языков, о которых знает моя программа.

$known_langs = array('en','fr','de','es'); 
$user_pref_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); 

foreach($user_pref_langs as $idx => $lang) { 
    $lang = substr($lang, 0, 2); 
    if (in_array($lang, $known_langs)) { 
     echo "Preferred language is $lang"; 
     break; 
    } 
} 

Надеюсь, вы найдете это быстрое и простое решение, которое вы можете легко использовать в своем коде. Я использую это в производстве довольно долгое время.

+1

«Браузеры возвращают языки в порядке предпочтения» - они могут это сделать, но вы не должны зависеть от этого. Используйте значения 'q' для определения предпочтений, вот что говорит вам спецификация. – Quentin

0

Все вышеперечисленное с запасной вариант для 'ан':

$lang = substr(explode(',',$_SERVER['HTTP_ACCEPT_LANGUAGE'])[0],0,2)?:'en'; 

... или запасной вариант языка по умолчанию и известный массив языка:

function lang($l = ['en'], $u){ 
    return $l[ 
     array_keys(
      $l, 
      substr(
       explode(
        ',', 
        $u ?: $_SERVER['HTTP_ACCEPT_LANGUAGE'] 
       )[0], 
       0, 
       2 
      ) 
     )[0] 
    ] ?: $l[0]; 
} 

Одна линия:

function lang($l=['en'],$u){return $l[array_keys($l,substr(explode(',',$u?:$_SERVER['HTTP_ACCEPT_LANGUAGE'])[0],0,2))[0]]?:$l[0];} 

Примеры:

// first known lang is always default 
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'en-us'; 
lang(['de']); // 'de' 
lang(['de','en']); // 'en' 

// manual set accept-language 
lang(['de'],'en-us'); // 'de' 
lang(['de'],'de-de, en-us'); // 'de' 
lang(['en','fr'],'de-de, en-us'); // 'en' 
lang(['en','fr'],'fr-fr, en-us'); // 'fr' 
lang(['de','en'],'fr-fr, en-us'); // 'de' 
+0

Очень сложно читать вашу функцию на одной линии. –

7

Попробуйте это:

######################################################### 
# Copyright © 2008 Darrin Yeager      # 
# https://www.dyeager.org/        # 
# Licensed under BSD license.       # 
# https://www.dyeager.org/downloads/license-bsd.txt # 
######################################################### 

function getDefaultLanguage() { 
    if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) 
     return parseDefaultLanguage($_SERVER["HTTP_ACCEPT_LANGUAGE"]); 
    else 
     return parseDefaultLanguage(NULL); 
    } 

function parseDefaultLanguage($http_accept, $deflang = "en") { 
    if(isset($http_accept) && strlen($http_accept) > 1) { 
     # Split possible languages into array 
     $x = explode(",",$http_accept); 
     foreach ($x as $val) { 
     #check for q-value and create associative array. No q-value means 1 by rule 
     if(preg_match("/(.*);q=([0-1]{0,1}.\d{0,4})/i",$val,$matches)) 
      $lang[$matches[1]] = (float)$matches[2]; 
     else 
      $lang[$val] = 1.0; 
     } 

     #return default language (highest q-value) 
     $qval = 0.0; 
     foreach ($lang as $key => $value) { 
     if ($value > $qval) { 
      $qval = (float)$value; 
      $deflang = $key; 
     } 
     } 
    } 
    return strtolower($deflang); 
} 
+0

Эй вы могли бы объяснить регулярное выражение, которое должно улавливать значение ** q ** с помощью '[0-1] {0,1}. \ D {0,4}'? Сначала я предполагаю, что вы имеете в виду '\ .' вместо' .' правильно? И не всегда ** q ** всегда формы '0.1324' или что-то еще? Разве не было бы достаточно написать '0 \.? \ D {0,4}'? Если у вас есть 'q = 1.0', вы можете перейти в другую часть. – Adam

+0

Было бы здорово увидеть пример использования здесь. –

2

Я думаю, что чистый способ это!

<?php 
    $lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2); 
    $supportedLanguages=['en','fr','gr']; 
    if(!in_array($lang,$supportedLanguages)){ 
    $lang='en'; 
    } 
    require("index_".$lang.".php"); 
+0

Это не учитывает языковые приоритеты в заголовке. –