简体   繁体   English

检测 PHP 中的浏览器语言

[英]Detect Browser Language in PHP

I use the following PHP script as index for my website.我使用以下 PHP 脚本作为我网站的索引。

This script should include a specific page depending on the browser's language (automatically detected).此脚本应包含特定页面,具体取决于浏览器的语言(自动检测)。

This script does not work well with all browsers, so it always includes index_en.php for any detected language (the cause of the problem is most probably an issue with some Accept-Language header not being considered).此脚本不适用于所有浏览器,因此它始终包含针对任何检测到的语言的index_en.php (问题的原因很可能是未考虑某些 Accept-Language 标头的问题)。

Could you please suggest me a more robust solution?你能建议我一个更强大的解决方案吗?

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

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

?>

Accept-Language is a list of weighted values (see q parameter). Accept-Language是一个加权值列表(参见q参数)。 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.事实上, q值为 0 意味着根本不可接受。

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.如果findMatches返回一个空数组,则未找到匹配项,您可以使用默认语言。

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 .处理此问题的官方方法是使用PECL HTTP 库 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.与此处的某些答案不同,这会正确处理语言优先级(q 值)、部分语言匹配并将返回最接近的匹配,或者当没有匹配时,它会回退到数组中的第一种语言。

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

How to use:如何使用:
http://php.net/manual/fa/function.http-negotiate-language.php 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);

The problem with the selected answer above is that the user may have their first choice set as a language that's not in the case structure, but one of their other language choices are set.上面所选答案的问题在于,用户可能将他们的第一选择设置为不在案例结构中的语言,但设置了他们的其他语言选择之一。 You should loop until you find a match.您应该循环直到找到匹配项。

This is a super simple solution that works better.这是一个超级简单的解决方案,效果更好。 Browsers return the languages in order of preference, so that simplifies the problem.浏览器按偏好顺序返回语言,从而简化了问题。 While the language designator can be more than two characters (eg - "EN-US"), typically the first two are sufficient.虽然语言指示符可以超过两个字符(例如 - “EN-US”),但通常前两个就足够了。 In the following code example I'm looking for a match from a list of known languages my program is aware of.在下面的代码示例中,我正在从我的程序知道的已知语言列表中寻找匹配项。

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

I hope you find this a quick and simple solution that you can easily use in your code.我希望您发现这是一个快速简单的解决方案,您可以轻松地在代码中使用它。 I've been using this in production for quite a while.我在生产中使用它已经有一段时间了。

Try this one:试试这个:

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

The following script is a modified version of Xeoncross's code (thank you for that Xeoncross) that falls-back to a default language setting if no languages match the supported ones, or if a match is found it replaces the default language setting with a new one according to the language priority.以下脚本是 Xeoncross 代码的修改版本(感谢您提供 Xeoncross),如果没有语言与受支持的语言匹配,则回退到默认语言设置,或者如果找到匹配项,它将用新语言替换默认语言设置根据语言优先级。

In this scenario the user's browser is set in order of priority to Spanish, Dutch, US English and English and the application supports English and Dutch only with no regional variations and English is the default language.在这种情况下,用户的浏览器按优先级顺序设置为西班牙语、荷兰语、美国英语和英语,应用程序仅支持英语和荷兰语,没有地区差异,英语是默认语言。 The order of the values in the "HTTP_ACCEPT_LANGUAGE" string is not important if for some reason the browser does not order the values correctly.如果由于某种原因浏览器没有正确排序值,则“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" 

Unfortunately, none of the answers to this question takes into account some valid HTTP_ACCEPT_LANGUAGE such as:不幸的是,这个问题的答案都没有考虑到一些有效的HTTP_ACCEPT_LANGUAGE例如:

  • q=0.8,en-US;q=0.5,en;q=0.3 : having the q priority value at first place. q=0.8,en-US;q=0.5,en;q=0.3 :将q优先级值放在首位。
  • ZH-CN : old browsers that capitalise (wrongly) the whole langcode. ZH-CN :旧浏览器(错误地)大写整个语言代码。
  • * : that basically say "serve whatever language you have". * :基本上是说“服务于您拥有的任何语言”。

After a comprehensive test with thousands of different Accept-Languages that reached my server, this is my language detection method:在对到达我的服务器的数千种不同的 Accept-Languages 进行全面测试后,这是我的语言检测方法:

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

I think the cleanest way is this!我认为最干净的方法是这个!

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

Quick and simple:快速简单:

$language = trim(substr( strtok(strtok($_SERVER['HTTP_ACCEPT_LANGUAGE'], ','), ';'), 0, 5));

NOTE: The first language code is what is being used by the browser, the rest are other languages the user has setup in the browser.注意:第一种语言代码是浏览器正在使用的语言,其余是用户在浏览器中设置的其他语言。

Some languages have a region code, eg.某些语言有区域代码,例如。 en-GB, others just have the language code, eg. en-GB,其他人只有语言代码,例如。 sk.斯克。

If you just want the language and not the region (eg. en, fr, es, etc.), you can use:如果您只想要语言而不是地区(例如 en、fr、es 等),您可以使用:

$language =substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);

php-intl 扩展中有一个方法:

 locale_accept_from_http($_SERVER['HTTP_ACCEPT_LANGUAGE'])

All of the above with fallback to 'en':以上所有内容都回退到“en”:

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

...or with default language fallback and known language array: ...或使用默认语言回退和已知语言数组:

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

One Line:一行:

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

Examples:例子:

// 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'

Try,试试,

$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感谢

FOR LARAVEL USERS, here's a single line of code that returns a very clean collection (or array) of preferred languages:对于 LARAVEL 用户,这里有一行代码返回一个非常干净的首选语言集合(或数组):

$langs = Str::of($_SERVER['HTTP_ACCEPT_LANGUAGE'])
        ->explode(',')
        ->transform(fn($lang) => Str::substr($lang, 0, 2))
        ->unique();

I've got this one, which sets a cookie.我有这个,它设置了一个 cookie。 And as you can see, it first checks if the language is posted by the user.正如您所看到的,它首先检查语言是否由用户发布。 Because browser language not always tells about the user.因为浏览器语言并不总是告诉用户。

<?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/');
        } 
?>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM