Detectar a linguagem do navegador em PHP

144

Eu uso o seguinte script PHP como índice para o meu site.

Este script deve incluir uma página específica, dependendo do idioma do navegador (detectado automaticamente).

Esse script não funciona bem com todos os navegadores; portanto, sempre inclui index_en.phpqualquer idioma detectado (a causa do problema provavelmente é um problema com o cabeçalho Accept-Language não sendo considerado).

Você poderia me sugerir uma solução mais robusta?

<?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
fonte
3
O PHP 5.3.0+ vem com o locale_accept_from_http()qual obtém o idioma preferido do Accept-Languagecabeçalho. Você sempre deve preferir esse método a um método auto-escrito. Verifique o resultado em uma lista de expressões regulares que você tenta e determine o idioma da página dessa maneira. Veja PHP-I18N para um exemplo.
caw
2
O problema locale_accept_from_http()é que você pode não suportar o melhor resultado retornado, portanto você ainda tem que analisar o cabeçalho para encontrar o próximo melhor .
Xeoncross 09/09
A resposta aceita para isso deve ser alterada para uma que leve em consideração vários idiomas.
Pekka
incluem e exigem do são acontecer no tempo de compilação de php então basicamente você incluir todo o índice * .php e mostrar apenas um - desperdício de ressources
Michael

Respostas:

361

por que você não o mantém simples e limpo

<?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
fonte
9
Os códigos de idioma holandês, grego e esloveno são uma letra. Parece melhor explodir assim: php.net/manual/tr/reserved.variables.server.php#90293
trante
10
@trante: Por que você diz que são uma letra? Holandês ( nl), grego ( el) e eslovena ( sl), todos parecem ser duas letras: msdn.microsoft.com/en-us/library/ms533052(v=vs.85).aspx
Peter K.
16
Este código não olha para a lista inteira. E se plfor a primeira prioridade e a frsegunda na minha lista de idiomas? Eu compraria inglês em vez de francês.
Kos
24
Esta falta de detecção de prioridades, e is'nt compatível com códigos diferentes de duas cartas
Axel Costas Pena
3
Não existem outros comprimentos além de duas letras! Entre no seu navegador favorito e altere a prioridade do idioma e você a verá.
precisa saber é o seguinte
76

Accept-Language é uma lista de valores ponderados (consulte q parâmetro). Isso significa que apenas olhar para o primeiro idioma não significa que também é o mais preferido; de fato, um q valor de 0 significa que não é aceitável.

Portanto, em vez de apenas olhar para o primeiro idioma, analise a lista de idiomas aceitos e os idiomas disponíveis e encontre a melhor correspondência:

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

Se findMatchesretornar uma matriz vazia, nenhuma correspondência foi encontrada e você poderá voltar ao idioma padrão.

quiabo
fonte
Olá, o script estava funcionando bem e agora pare. seria possível que, se a SESSION no servidor fosse desativada, esse script não funcionasse?
GibboK
@ GiBboK: Não, isso é independente das sessões.
Gumbo
Solução correta, mas eu prefiro @diggersworld ... melhor escrever menos código
lrkwz
Alguém pode me dizer quem, como é qdecidido o valor ? Graças
Phantom007
@ Phantom007 Depende da preferência: 0 = Não quero este idioma, 1 = Quero sempre este idioma.
Skyost 6/10/19
43

As respostas existentes são muito detalhadas, por isso criei esta versão menor e de correspondência automática.

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

E o uso da amostra:

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

Fonte de essência completa aqui

Xeoncross
fonte
6
Isso é brilhante e exatamente o que eu precisava para um projeto específico hoje. A única adição que fiz foi permitir que a função aceite um idioma padrão e volte a ele se não houver correspondência entre os idiomas disponíveis e o HTTP_ACCEPT_LANGUAGEs.
Scott Scott
7
Oh, uma essência com as minhas mudanças está aqui: gist.github.com/humantorch/d255e39a8ab4ea2e7005 (eu também combinou-o em um arquivo para simplificar)
Scott
2
Método muito bom! Talvez você deva verificar se $ langs já contém uma entrada para o idioma. Aconteceu-me que o idioma preferido era en-US, 2º e 3º en, seu método sempre me deu de, porque o primeiro valor de en foi sobrescrito pela 3ª entrada
Peter Pint
Também produz um aviso do PHP se nenhuma correspondência for encontrada. Seria bom lidar com isso graciosamente.
Simon East
26

A maneira oficial de lidar com isso é usando a biblioteca HTTP PECL . Diferentemente de algumas respostas aqui, isso lida corretamente com as prioridades de idioma (valores q), correspondências parciais de idioma e retornará a correspondência mais próxima ou, quando não houver correspondências, ele retornará ao primeiro idioma de sua matriz.

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

Como usar:
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
fonte
1
Encontrei um link funcional, por isso atualizei sua resposta para incluí-lo.
Simon East
Todos os três desses links parecem estar inoperantes e eles não parecem ter nenhuma instrução de instalação do Googleable facilmente (também essa função foi descontinuada de acordo com a página deles)
Brian Leishman
11

O problema com a resposta selecionada acima é que o usuário pode ter sua primeira opção definida como um idioma que não faz parte da estrutura do caso, mas uma das outras opções de idioma está definida. Você deve fazer um loop até encontrar uma correspondência.

Esta é uma solução super simples que funciona melhor. Os navegadores retornam os idiomas em ordem de preferência, simplificando o problema. Embora o designador de idioma possa ter mais de dois caracteres (por exemplo, "EN-US"), normalmente os dois primeiros são suficientes. No exemplo de código a seguir, estou procurando uma correspondência em uma lista de idiomas conhecidos que meu programa conhece.

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

Espero que você encontre uma solução rápida e simples que possa ser usada facilmente em seu código. Eu tenho usado isso na produção por um bom tempo.

Darryl
fonte
3
"Navegadores retornam os idiomas em ordem de preferência" - Eles podem funcionar, mas você não deve depender disso. Use qvalores para determinar a preferência, é o que a especificação diz que você deve fazer.
Quentin
7

Tente este:

#########################################################
# 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
fonte
Ei, você poderia explicar a regex que deve pegar o valor q com [0-1]{0,1}.\d{0,4}? Primeiro eu acho que você quer dizer, em \.vez de .certo? E não é q sempre da forma 0.1324ou algo assim? Não seria suficiente escrever 0\.?\d{0,4}? Se você tiver q=1.0, pode ir na outra parte.
Adam
Seria ótimo ver um exemplo de uso aqui.
Simon East
2
@SimonEast var_dump( getDefaultLanguage());
jirarium
4

O script a seguir é uma versão modificada do código do Xeoncross (obrigado por esse Xeoncross) que retorna a uma configuração de idioma padrão se nenhum idioma corresponder aos suportados, ou se uma correspondência for encontrada, ela substitui a configuração de idioma padrão por uma nova. de acordo com a prioridade do idioma.

Nesse cenário, o navegador do usuário é definido em ordem de prioridade para espanhol, holandês, inglês dos EUA e inglês e o aplicativo suporta inglês e holandês apenas sem variações regionais e o inglês é o idioma padrão. A ordem dos valores na cadeia "HTTP_ACCEPT_LANGUAGE" não é importante se, por algum motivo, o navegador não solicitar os valores corretamente.

$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
fonte
1

Eu acho que a maneira mais limpa é essa!

 <?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
fonte
Isso não leva em consideração as prioridades de idioma no cabeçalho.
Simon East
0

Todos os itens acima com fallback para 'en':

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

... ou com fallback de idioma padrão e matriz de idioma conhecido:

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

Uma linha:

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

Exemplos:

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

Experimentar,

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

Graças a

mrbengi
fonte
0

Rápido e simples:

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

NOTA: O código do primeiro idioma é o que está sendo usado pelo navegador, o restante são outros idiomas que o usuário configurou no navegador.

Alguns idiomas têm um código de região, por exemplo. en-GB, outros apenas têm o código do idioma, por exemplo. sk.

Se você deseja apenas o idioma e não a região (por exemplo, en, fr, es, etc.), pode usar:

$language =substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
Justin Levene
fonte
-1

Eu tenho esse, que define um cookie. E como você pode ver, ele primeiro verifica se o idioma foi publicado pelo usuário. Porque o idioma do navegador nem sempre fala sobre o usuário.

<?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
fonte
11
Eu acho que você não pode enviar cabeçalhos quando já ecoou coisas?
2
Eu acho que a indentação por trás deste post faz sentido, que é fornecer ao usuário uma maneira de mudar o idioma e lembrar dessa decisão. A detecção de idioma deve ser feita apenas uma vez para melhor adivinhar a primeira seleção.
danijar