检测 PHP 中的浏览器语言
Posted
技术标签:
【中文标题】检测 PHP 中的浏览器语言【英文标题】:Detect Browser Language in PHP 【发布时间】:2011-04-15 19:00:24 【问题描述】:我使用以下 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;
?>
【问题讨论】:
PHP 5.3.0+ 带有locale_accept_from_http()
,它从Accept-Language
标头中获取首选语言。你应该总是更喜欢这种方法而不是自写的方法。根据您尝试的正则表达式列表检查结果并以这种方式确定页面语言。有关示例,请参见 PHP-I18N。
locale_accept_from_http()
的问题是你可能不支持它返回的最佳结果,所以你仍然有parse the header yourself 来找到 next-best。
对此接受的答案应更改为考虑多种语言的答案之一。
include 和 require 是在 php 的编译时发生的,所以基本上你包括所有 index*.php 并只显示一个 - 浪费资源
【参考方案1】:
你为什么不保持简单和干净
<?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";
?>
【讨论】:
荷兰语、希腊语和斯洛文尼亚语的语言代码是一个字母。像这样爆炸似乎更好:php.net/manual/tr/reserved.variables.server.php#90293 @trante:你为什么说它们是一个字母?荷兰语 (nl
)、希腊语 (el
) 和斯洛文尼亚语 (sl
) 似乎都是两个字母:msdn.microsoft.com/en-us/library/ms533052(v=vs.85).aspx
这段代码没有查看整个列表。如果pl
是第一优先级而fr
在我的语言列表中是第二个呢?我会得到英语而不是法语。
这缺乏检测优先级,并且与代码different from two letters不兼容
只有两个字母没有其他长度!进入您喜欢的浏览器并更改语言优先级,您就会看到它。【参考方案2】:
Accept-Language 是加权值列表(参见 q 参数)。这意味着只看第一语言并不意味着它也是最受欢迎的;事实上,q 值为 0 表示根本不可接受。
因此,与其只查看第一种语言,不如解析接受语言和可用语言的列表并找到最佳匹配:
// 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(?:\.\d0,3)|1(?:\.00,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
返回一个空数组,则表示未找到匹配项,您可以使用默认语言。
【讨论】:
嗨,脚本工作正常,现在停止。如果服务器上的 SESSION 关闭,这个脚本可能不起作用? @GIbboK:不,这与会话无关。 正确,但我更喜欢@diggersworld 解决方案...最好少写代码 谁能告诉我q
的值是怎么决定的?谢谢
@Phantom007 取决于偏好:0 = 我不想要这种语言,1 = 我一直想要这种语言。【参考方案3】:
现有的答案有点过于冗长,所以我创建了这个较小的自动匹配版本。
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
【讨论】:
这是精彩,正是我今天特定项目所需要的。我做的唯一补充是允许函数接受默认语言,如果可用语言和 HTTP_ACCEPT_LANGUAGEs 之间不匹配,则回退到该语言。 哦,我的更改要点在这里:gist.github.com/humantorch/d255e39a8ab4ea2e7005(为简单起见,我也将其合并到一个文件中) 非常好的方法!也许您应该检查 $langs 是否已经包含该语言的条目。发生在我身上,首选语言是 en-US,2nd de 和 3rd en,你的方法总是给我 de,因为 en 的第一个值被第三个条目覆盖 如果没有找到匹配项,它也会产生一个 PHP 警告。能优雅地处理这件事会很好。 没有按预期工作,我在浏览器中的首选语言是("en","ar","en-us")
发生的事情是显示 ar
是首选语言:\【参考方案4】:
处理这个问题的官方方法是使用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);
【讨论】:
我找到了一个有效的链接,所以更新了你的答案以包含它。 所有这三个链接似乎都已失效,而且它们似乎没有任何易于通过 Google 搜索的安装说明(根据其页面,此功能也已被弃用)【参考方案5】:上面选择的答案的问题在于,用户可能将他们的首选语言设置为不在案例结构中的语言,但设置了他们的其他语言选择之一。您应该循环直到找到匹配项。
这是一个超级简单的解决方案,效果更好。浏览器按优先顺序返回语言,从而简化了问题。虽然语言指示符可以超过两个字符(例如 - “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;
我希望您发现这是一个快速简单的解决方案,您可以轻松地在您的代码中使用它。我已经在生产中使用它很长一段时间了。
【讨论】:
“浏览器按优先顺序返回语言”——他们可能会这样做,但你不应该依赖它。使用q
值来确定偏好,这就是规范所说的你应该做的。【参考方案6】:
试试这个:
#########################################################
# 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.\d0,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);
https://www.dyeager.org/blog/2008/10/getting-browser-default-language-php.html
【讨论】:
嘿,你能解释一下应该用[0-1]0,1.\d0,4
捕获 q 值的正则表达式吗?首先我猜你的意思是\.
而不是.
对吧? q 不总是0.1324
或其他形式吗?写0\.?\d0,4
还不够吗?如果你有q=1.0
,那么你可以进入else部分。
很高兴在这里看到一个使用示例。
@SimonEast var_dump( getDefaultLanguage());
【参考方案7】:
不幸的是,这个问题的答案都没有考虑到一些有效的HTTP_ACCEPT_LANGUAGE
,例如:
q=0.8,en-US;q=0.5,en;q=0.3
:具有q
优先级值。
ZH-CN
:旧版浏览器(错误地)大写整个语言代码。
*
:基本上就是说“为你提供任何语言”。
在对到达我的服务器的数千种不同的接受语言进行全面测试后,这是我的语言检测方法:
define('SUPPORTED_LANGUAGES', ['en', 'es']);
function detect_language($fallback='en')
foreach (preg_split('/[;,]/', $_SERVER['HTTP_ACCEPT_LANGUAGE']) as $sub)
if (substr($sub, 0, 2) == 'q=') continue;
if (strpos($sub, '-') !== false) $sub = explode('-', $sub)[0];
if (in_array(strtolower($sub), SUPPORTED_LANGUAGES)) return $sub;
return $fallback;
【讨论】:
【参考方案8】:以下脚本是 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"
【讨论】:
【参考方案9】:快速简单:
$language = trim(substr( strtok(strtok($_SERVER['HTTP_ACCEPT_LANGUAGE'], ','), ';'), 0, 5));
注意: 第一种语言代码是浏览器使用的语言,其余的是用户在浏览器中设置的其他语言。
某些语言有区域代码,例如。 en-GB,其他只有语言代码,例如。 sk。
如果您只想要语言而不是地区(例如 en、fr、es 等),您可以使用:
$language =substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
【讨论】:
【参考方案10】:我认为最干净的方法是这样!
<?php
$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
$supportedLanguages=['en','fr','gr'];
if(!in_array($lang,$supportedLanguages))
$lang='en';
require("index_".$lang.".php");
【讨论】:
这不考虑标题中的语言优先级。【参考方案11】:php-intl 扩展中有一个方法:
locale_accept_from_http($_SERVER['HTTP_ACCEPT_LANGUAGE'])
【讨论】:
【参考方案12】:以上所有都回退到“en”:
$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'
【讨论】:
【参考方案13】:试试,
$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0,2);
if ($lang == 'tr')
include_once('include/language/tr.php');
elseif ($lang == 'en')
include_once('include/language/en.php');
elseif ($lang == 'de')
include_once('include/language/de.php');
elseif ($lang == 'fr')
include_once('include/language/fr.php');
else
include_once('include/language/tr.php');
Thanks to
【讨论】:
【参考方案14】:对于 LARAVEL 用户,这是一行代码,它返回一个非常干净的首选语言集合(或数组):
$langs = Str::of($_SERVER['HTTP_ACCEPT_LANGUAGE'])
->explode(',')
->transform(fn($lang) => Str::substr($lang, 0, 2))
->unique();
【讨论】:
【参考方案15】:我有这个,它设置了一个 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/');
?>
【讨论】:
我猜你已经回显的东西不能发送标题? 我认为这篇文章背后的缩进是有道理的,它为用户提供了一种切换语言的方式,并记住了这个决定。语言检测应该只进行一次以最好地猜测第一个选择。以上是关于检测 PHP 中的浏览器语言的主要内容,如果未能解决你的问题,请参考以下文章