Wykryj język przeglądarki w PHP


144

Używam następującego skryptu PHP jako indeksu dla mojej witryny.

Ten skrypt powinien zawierać określoną stronę w zależności od języka przeglądarki (wykrywana automatycznie).

Ten skrypt nie działa dobrze we wszystkich przeglądarkach, więc zawsze obejmuje index_en.phpkażdy wykryty język (przyczyną problemu jest najprawdopodobniej problem z nieuwzględnianiem niektórych nagłówków Accept-Language).

Czy mógłbyś zaproponować mi bardziej solidne rozwiązanie?

<?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+ zawiera, locale_accept_from_http()który pobiera preferowany język z Accept-Languagenagłówka. Zawsze powinieneś preferować tę metodę od metody napisanej samodzielnie. Porównaj wynik z listą wyrażeń regularnych, które próbujesz, i określ w ten sposób język strony. Zobacz przykład PHP-I18N .
krakaj

2
Problem locale_accept_from_http()polega na tym, że możesz nie obsługiwać najlepszego wyniku, który zwraca, więc nadal możesz samodzielnie przeanalizować nagłówek, aby znaleźć następny najlepszy .
Xeoncross

Zaakceptowaną odpowiedź na to pytanie należy zmienić na jedną z tych, które uwzględniają wiele języków.
Pekka

include i require są wykonywane w czasie kompilacji php, więc w zasadzie dołączasz cały indeks * .php i pokazujesz tylko jeden - marnowanie zasobów
Michael

Odpowiedzi:


361

dlaczego nie utrzymujesz tego w prostocie i czystości

<?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
Kody języków w języku niderlandzkim, greckim i słoweńskim to jedna litera. Wydaje się, że lepiej wybuchnąć w ten sposób: php.net/manual/tr/reserved.variables.server.php#90293
trante

10
@trante: Dlaczego mówisz, że to jedna litera? Holenderski ( nl), grecki ( el) i słoweński ( sl) wydają się mieć dwie litery: msdn.microsoft.com/en-us/library/ms533052(v=vs.85).aspx
Peter K.

16
Ten kod nie obejmuje całej listy. A co, jeśli pljest priorytetem i frjest drugim na mojej liście języków? Zamiast francuskiego dostawałem angielski.
Kos

24
Brakuje priorytetów wykrywania i nie jest kompatybilny z kodami innymi niż dwie litery
Áxel Costas Pena

3
Nie ma innych długości niż dwie litery! Wejdź do swojej ulubionej przeglądarki i zmień priorytet języka, a zobaczysz.
Gigala,

76

Accept-Language to lista wartości ważonych (patrzparametr q ). Oznacza to, że samo spojrzenie na pierwszy język nie oznacza, że ​​jest on również najbardziej preferowany; w rzeczywistościwartość q równa 0 oznacza w ogóle nie do przyjęcia.

Zamiast więc patrzeć na pierwszy język, przeanalizuj listę akceptowanych języków i dostępnych języków i znajdź najlepsze dopasowanie:

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

Jeśli findMatcheszwraca pustą tablicę, nie znaleziono żadnego dopasowania i możesz wrócić do języka domyślnego.


Cześć, skrypt działał dobrze i teraz przestań. jest możliwe, że jeśli SESJA na serwerze jest wyłączona, ten skrypt nie zadziała?
GibboK,

@GIbboK: Nie, to jest niezależne od sesji.
Gumbo

Prawidłowo, ale wolę rozwiązanie @diggersworld ... lepiej pisz mniej kodu
lrkwz

Czy ktoś może mi powiedzieć, jaka jest wartość qdecyzji? Dzięki
Phantom007

@ Phantom007 Zależy od preferencji: 0 = nie chcę tego języka, 1 = zawsze chcę tego języka.
Skyost

43

Istniejące odpowiedzi są trochę zbyt szczegółowe, dlatego utworzyłem tę mniejszą, automatycznie dopasowującą się wersję.

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

I przykładowe użycie:

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

Pełne źródło treści tutaj


6
To jest genialne i dokładnie to, czego potrzebowałem dzisiaj do konkretnego projektu. Jedynym dodatkiem, jaki wprowadziłem, jest zezwolenie funkcji na akceptację domyślnego języka i powrót do tego, jeśli nie ma dopasowania między dostępnymi językami a HTTP_ACCEPT_LANGUAGE.
Scott

7
Och, podsumowanie moich zmian jest tutaj: gist.github.com/humantorch/d255e39a8ab4ea2e7005 (dla uproszczenia również połączyłem to w jeden plik)
Scott

2
Bardzo fajna metoda! Może powinieneś sprawdzić, czy $ langs zawiera już wpis dla języka. zdarzyło mi się, że preferowanym językiem był en-US, 2nd de i 3rd en, twoja metoda zawsze dawała mi de, ponieważ pierwsza wartość en została nadpisana przez trzeci wpis
Peter Pint.

Generuje również ostrzeżenie PHP, jeśli nie zostaną znalezione żadne dopasowania. Byłoby miło poradzić sobie z tym wdzięcznie.
Simon East,

26

Oficjalnym sposobem rozwiązania tego problemu jest użycie biblioteki PECL HTTP . W przeciwieństwie do niektórych odpowiedzi tutaj, to poprawnie obsługuje priorytety języka (wartości q), częściowe dopasowania języka i zwróci najbliższe dopasowanie, lub jeśli nie ma żadnych dopasowań, wraca do pierwszego języka w twojej tablicy.

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

Jak używać:
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
Znalazłem działający link, więc zaktualizuj swoją odpowiedź, aby go uwzględnić.
Simon East

Wszystkie trzy z tych linków wydają się być martwe i nie mają żadnych łatwych do Google instrukcji instalacji (również ta funkcja jest przestarzała zgodnie z ich stroną)
Brian Leishman

11

Problem z wybraną powyżej odpowiedzią polega na tym, że użytkownik może mieć swój pierwszy wybór jako język, którego nie ma w strukturze przypadku, ale jeden z pozostałych wyborów językowych jest ustawiony. Powinieneś zapętlić, aż znajdziesz dopasowanie.

To super proste rozwiązanie, które działa lepiej. Przeglądarki zwracają języki w kolejności preferencji, co upraszcza problem. Chociaż oznaczenie języka może składać się z więcej niż dwóch znaków (np. - „EN-US”), zwykle wystarczają pierwsze dwa. W poniższym przykładzie kodu szukam dopasowania z listy znanych języków, o których wie mój program.

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

Mam nadzieję, że uznasz to za szybkie i proste rozwiązanie, którego możesz łatwo użyć w swoim kodzie. Używam tego w produkcji od dłuższego czasu.


3
„Przeglądarki zwracają języki w kolejności preferencji” - mogą to zrobić, ale nie należy na tym polegać. Użyj qwartości, aby określić preferencje, tak mówi specyfikacja.
Quentin

7

Spróbuj tego:

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

Hej, czy mógłbyś wyjaśnić wyrażenie regularne, które powinno przechwytywać wartość q[0-1]{0,1}.\d{0,4} ? Po pierwsze, myślę, że masz na myśli \.zamiast .prawda? I czy nie q zawsze ma formę 0.1324czy coś? Czy nie wystarczyłoby wtedy pisać 0\.?\d{0,4}? Jeśli masz q=1.0, możesz przejść do drugiej części.
Adam

Byłoby wspaniale zobaczyć tutaj przykład użycia.
Simon East,

2
@SimonEast var_dump( getDefaultLanguage());
jirarium

4

Poniższy skrypt to zmodyfikowana wersja kodu Xeoncross (dziękujemy za Xeoncross), która powraca do domyślnego ustawienia języka, jeśli żaden język nie pasuje do obsługiwanych lub jeśli zostanie znaleziony pasujący, zastępuje domyślne ustawienie języka nowym zgodnie z priorytetem językowym.

W tym scenariuszu przeglądarka użytkownika jest ustawiona według priorytetu na hiszpański, holenderski, angielski (USA) i angielski, a aplikacja obsługuje tylko język angielski i holenderski bez różnic regionalnych, a język angielski jest językiem domyślnym. Kolejność wartości w ciągu „HTTP_ACCEPT_LANGUAGE” nie jest ważna, jeśli z jakiegoś powodu przeglądarka nie porządkuje poprawnie wartości.

$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

Myślę, że to najczystszy sposób!

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

To nie uwzględnia priorytetów językowych w nagłówku.
Simon East,

0

Wszystkie powyższe z powrotem do „en”:

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

... lub z domyślnym językiem zastępczym i znaną tablicą języków:

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

Jedna linia:

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

Przykłady:

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

Próbować,

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

Dzięki


0

Szybko i prosto:

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

UWAGA: Pierwszy kod języka jest używany przez przeglądarkę, reszta to inne języki, które użytkownik ustawił w przeglądarce.

Niektóre języki mają kod regionu, np. en-GB, inne mają po prostu kod języka, np. sk.

Jeśli chcesz tylko język, a nie region (np. En, fr, es itp.), Możesz użyć:

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

-1

Mam ten, który ustawia ciasteczko. Jak widać, najpierw sprawdza, czy język został opublikowany przez użytkownika. Ponieważ język przeglądarki nie zawsze mówi o użytkowniku.

<?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
Wydaje mi się, że nie możesz wysyłać nagłówków, jeśli już odtworzyłeś rzeczy?

2
Myślę, że wcięcie w tym poście ma sens, ponieważ ma dać użytkownikowi sposób na zmianę języka i zapamiętanie tej decyzji. Wykrywanie języka powinno być wykonywane tylko raz, aby jak najlepiej odgadnąć pierwszy wybór.
danijar
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.