Detect Browser Language in PHP

后端 未结 13 1224
孤城傲影
孤城傲影 2020-11-22 09:16

I use the following PHP script as index for my website.

This script should include a specific page depending on the browser\'s language (automatically detected).

相关标签:
13条回答
  • 2020-11-22 09:28

    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"; 
    
    ?>
    
    0 讨论(0)
  • 2020-11-22 09:29

    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 (e.g. - "EN-US"), typically the first two are sufficient. 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.

    0 讨论(0)
  • 2020-11-22 09:32

    Accept-Language is a list of weighted values (see q parameter). 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.

    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.

    0 讨论(0)
  • 2020-11-22 09:32

    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

    0 讨论(0)
  • 2020-11-22 09:37

    All of the above with fallback to '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'
    
    0 讨论(0)
  • 2020-11-22 09:38

    Unfortunately, none of the answers to this question takes into account some valid HTTP_ACCEPT_LANGUAGE such as:

    • q=0.8,en-US;q=0.5,en;q=0.3: having the q priority value at first place.
    • ZH-CN: old browsers that capitalise (wrongly) the whole langcode.
    • *: that basically say "serve whatever language you have".

    After a comprehensive test with thousands of different Accept-Languages in my server, I ended up having this language detection method:

    define('SUPPORTED_LANGUAGES', ['en', 'es']);
    
    function detect_language() {
        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 'en';
    }
    
    0 讨论(0)
提交回复
热议问题