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;
}
?>
GibboK
źródło
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"; 

?>
Pramendra Gupta
źródło
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.

Gumbo
źródło
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

Xeoncross
źródło
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);
diggersworld
źródło
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.

Darryl
źródło
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);
}
user956584
źródło
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" 
Noel Whitemore
źródło
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");
Mike Antoniadis
źródło
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'
Toby
źródło
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

mrbengi
źródło
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);
Justin Levene
źródło
-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/');
        } 
?>
Matthijs
źródło
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