Определить язык браузера в 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;
}
?>
GibboK
источник
3
PHP 5.3.0+ поставляется с тем, locale_accept_from_http()который получает предпочтительный язык из Accept-Languageзаголовка. Вы должны всегда предпочитать этот метод самописному методу. Сравните результат со списком регулярных выражений, которые вы пытаетесь определить, таким образом язык страницы. Смотрите PHP-I18N для примера.
caw
2
Проблема в locale_accept_from_http()том, что вы, возможно, не поддерживаете лучший результат, который он возвращает, поэтому у вас все равно есть возможность самостоятельно проанализировать заголовок, чтобы найти следующий лучший .
Xeoncross
Принятый ответ на этот вопрос должен быть заменен одним из тех, которые учитывают несколько языков.
Пекка
include и require происходят во время компиляции php, поэтому в основном вы включаете все индексы * .php и показываете только один - пустая трата ресурсов
Michael

Ответы:

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
транзит
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: Нет, это не зависит от сессий.
Гамбо
Правильно, но я предпочитаю решение @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
)*/

Полный источник здесь

Xeoncross
источник
6
Это блестяще и именно то, что мне нужно для конкретного проекта сегодня. Единственное добавление, которое я сделал, - разрешить функции принимать язык по умолчанию и вернуться к этому, если нет совпадения между доступными языками и HTTP_ACCEPT_LANGUAGE.
Скотт,
7
О, суть моих изменений здесь: gist.github.com/humantorch/d255e39a8ab4ea2e7005 (я также объединил это в один файл для простоты)
Скотт,
2
Очень хороший метод! Возможно, вам следует проверить, содержит ли $ langs запись для языка. со мной случилось, что предпочитаемым языком был en-US, 2nd de и 3rd en, ваш метод всегда давал мне de, потому что первое значение en было перезаписано третьей записью
Peter Pint
Он также выдает предупреждение PHP, если совпадений не найдено. Было бы неплохо справиться с этим изящно.
Саймон Ист
26

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

HTTP PECL:
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);
diggersworld
источник
1
Я нашел рабочую ссылку, поэтому обновил свой ответ, чтобы включить ее.
Саймон Ист
Все три из этих ссылок, кажется, устарели, и, похоже, у них нет простых инструкций по установке Googleable (также эта функция устарела в соответствии с их страницей)
Брайан Лейшман,
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);
}
user956584
источник
Эй, не могли бы вы объяснить регулярное выражение, которое должно поймать значение q с [0-1]{0,1}.\d{0,4}? Сначала я предполагаю, что вы имеете в виду, \.а не .правильно? И не всегда ли q имеет форму 0.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');
}

Благодаря

mrbengi
источник
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/');
        } 
?>
Matthijs
источник
11
Я полагаю, вы не можете отправлять заголовки, когда вы уже что-то повторили?
2
Я думаю, что отступление от этого поста имеет смысл: предоставить пользователю возможность переключать язык и запомнить это решение. Определение языка должно быть сделано только один раз, чтобы лучше угадать первый выбор.
Данияр