Виявити мову браузера в PHP


144

Я використовую такий скрипт 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 .
каре

2
Проблема locale_accept_from_http()полягає в тому, що ви, можливо, не підтримуєте найкращий результат, який він повертає, тому у вас все ще є розбір заголовка самостійно, щоб знайти наступне-найкраще .
Xeoncross

Прийняту відповідь на це слід змінити на одну з тих, що враховують кілька мов.
Pekka

включити та вимагати це відбувається під час компіляції php, тому в основному ви включаєте весь індекс * .php та показуєте лише одне - марнотратство ресурсів
Майкл

Відповіді:


361

чому ти не тримаєш це просто і чисто

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

?>

9
Голландські, грецькі та словенські мовні коди - одна літера. Здається, краще вибухнути так: php.net/manual/tr/reserved.variables.server.php#90293
trante

10
@trante: Чому ти кажеш, що це одна літера? Голландська ( nl), грецька ( el) та словенська ( sl) видаються двома літерами: msdn.microsoft.com/en-us/library/ms533052(v=vs.85).aspx
Пітер К.

16
Цей код не стосується всього списку. Що робити, якщо plце перший пріоритет і frє другим у моєму списку мов? Я б отримав англійську, а не французьку.
Кос

24
Цьому не вистачає визначення пріоритетів, і він не сумісний з кодами, відмінними від двох літер
Аксель Костас Пена

3
Немає інших довжин, ніж дві літери! Зайдіть у свій улюблений браузер і змініть пріоритет мови, і ви його побачите.
Гігала

76

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(?:\.\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повертає порожній масив, збігу не знайдено, і ви можете повернутися до мови за замовчуванням.


Привіт, сценарій працював чудово і тепер зупинись. Можливо, якщо SESSION на сервері відключити цей сценарій, то він не буде працювати?
GibboK

@GIbboK: Ні, це не залежить від сесій.
Gumbo

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

Може хтось скажіть, будь ласка, хто як визначає цінність q? Спасибі
Phantom007

@ Phantom007 Залежить від переваг: 0 = Я не хочу цієї мови, 1 = Я завжди хочу цю мову.
Skyost

43

Існуючі відповіді трохи надто багатослівні, тому я створив цю меншу, автоматичну відповідну версію.

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

Повне джерело тут


6
Це геніально і саме те, що мені було потрібно для конкретного проекту сьогодні. Єдине доповнення, яке я зробив - це дозволити функції приймати мову за замовчуванням і повернутися до неї, якщо немає відповідності між доступними мовами та HTTP_ACCEPT_LANGUAGE.
Скотт

7
О, суть моїх змін тут: gist.github.com/humantorch/d255e39a8ab4ea2e7005 (я також об'єднав її в один файл для простоти)
Скотт,

2
Дуже приємний метод! Можливо, слід перевірити, чи $ langs вже містить запис для мови. трапилося зі мною , що perferred мова була ан-США, другий де і третьої єп, ваш метод завжди давав мені де, тому що перше значення ЕН перезаписані 3 входу
Пітер Пінта

Він також створює попередження PHP, якщо не знайдено збігів. Було б добре впоратися з цим витончено.
Саймон Схід

26

Офіційний спосіб вирішити це - використання бібліотеки HTTP PECL . На відміну від деяких відповідей тут, це правильно обробляє мовні пріоритети (значення 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);

1
Я знайшов робоче посилання, тому оновив вашу відповідь, щоб включити її.
Саймон Схід

Усі три цих посилання, здається, мертві, і, схоже, вони не мають жодних інструкцій щодо встановлення Google (також ця функція застаріла відповідно до їх сторінки)
Brian Leishman

11

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

Це надзвичайно просте рішення, яке працює краще. Браузери повертають мови в порядку уподобання, щоб спростити проблему. У той час як мовний позначник може містити більше двох символів (наприклад, "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;
    }
}

Сподіваюся, ви знайдете це швидке і просте рішення, яке ви зможете легко використовувати у своєму коді. Я використовую це у виробництві вже досить давно.


3
"Браузери повертають мови в порядку вподобання" - Можливо, це робити, але ви не повинні залежати від цього. Використовуйте qзначення для визначення переваг, саме це говорить специфікація.
Квентін

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

Гей, чи можете ви пояснити регулярний вираз, який повинен вловлювати значення q[0-1]{0,1}.\d{0,4} ? Спочатку я гадаю, ви маєте на увазі \.замість .права? І чи не завжди форма q0.1324 чи щось таке? Чи не було б тоді достатньо написати 0\.?\d{0,4}? Якщо у вас є, q=1.0то можете перейти в іншу частину.
Адам

Було б чудово побачити приклад використання тут.
Саймон Схід

2
@SimonEast var_dump( getDefaultLanguage());
jirarium

4

Наступний скрипт - це модифікована версія коду 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" 

1

Я думаю, що найчистіший спосіб - це!

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

Все вищезазначене з резервним на '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'

0

Спробуйте,

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

Завдяки


0

Швидкий і простий:

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

ПРИМІТКА. Перший код мови - це те, що використовується браузером, решта - це інші мови, які користувач налаштував у браузері.

Деякі мови мають код регіону, наприклад. en-GB, інші мають лише код мови, наприклад. ск.

Якщо ви хочете просто мову, а не регіон (наприклад, en, fr, es тощо), ви можете використовувати:

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

-1

У мене є цей, який встановлює печиво. І як ви бачите, він спочатку перевіряє, чи мова розміщена користувачем. Оскільки мова браузера не завжди розповідає про користувача.

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

11
Я думаю, ви не можете надсилати заголовки, коли ви вже відлунювали речі?

2
Я думаю, що відступ за цією публікацією має сенс, який полягає у наданні користувачеві способу перемикання мови та запам'ятовування цього рішення. Виявлення мови слід проводити лише один раз, щоб найкраще відгадати перший вибір.
danijar
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.