Détecter la langue du navigateur en PHP

144

J'utilise le script PHP suivant comme index pour mon site Web.

Ce script doit inclure une page spécifique en fonction de la langue du navigateur (détectée automatiquement).

Ce script ne fonctionne pas bien avec tous les navigateurs, il inclut donc toujours index_en.phppour toute langue détectée (la cause du problème est très probablement un problème avec un en-tête Accept-Language non pris en compte).

Pouvez-vous me suggérer une solution plus robuste?

<?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
la source
3
PHP 5.3.0+ est livré avec locale_accept_from_http()qui obtient la langue préférée de l'en- Accept-Languagetête. Vous devriez toujours préférer cette méthode à une méthode auto-écrite. Comparez le résultat à une liste d'expressions régulières que vous essayez et déterminez la langue de la page de cette façon. Voir PHP-I18N pour un exemple.
caw
2
Le problème avec locale_accept_from_http()est que vous pouvez ne pas prendre en charge le meilleur résultat qu'il renvoie, vous avez donc toujours l' analyse de l'en-tête vous-même pour trouver le meilleur résultat suivant .
Xeoncross
La réponse acceptée à cette question doit être remplacée par l'une de celles qui prennent en compte plusieurs langues.
Pekka
include et require se produisent au moment de la compilation de php, donc fondamentalement, vous incluez tous les index * .php et n'en montrez qu'un seul - gaspillage de ressources
Michael

Réponses:

361

pourquoi ne pas garder les choses simples et propres

<?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
la source
9
Les codes de langue du néerlandais, du grec et du slovène sont composés d'une seule lettre. Il semble préférable d'exploser comme ceci: php.net/manual/tr/reserved.variables.server.php#90293
trante
10
@trante: Pourquoi dites-vous qu'ils ne sont qu'une lettre? Le néerlandais ( nl), le grec ( el) et le slovène ( sl) semblent tous être composés de
Peter K.
16
Ce code ne regarde pas la liste entière. Et si plc'est la première priorité et la frdeuxième dans ma liste de langues? J'obtiendrais l'anglais au lieu du français.
Kos
24
Cela manque de détection des priorités et n'est pas compatible avec des codes différents de deux lettres
Áxel Costas Pena
3
Il n'y a pas d'autre longueur que deux lettres! Allez dans votre navigateur préféré et changez la priorité de la langue et vous le verrez.
Gigala
76

Accept-Language est une liste de valeurs pondérées (voirparamètre q ). Cela signifie que le simple fait de regarder la première langue ne signifie pas que c'est aussi la plus préférée; en fait, unevaleur q de 0 signifie pas du tout acceptable.

Donc, au lieu de simplement regarder la première langue, analysez la liste des langues acceptées et des langues disponibles et trouvez la meilleure correspondance:

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

Si findMatchesretourne un tableau vide, aucune correspondance n'a été trouvée et vous pouvez revenir sur la langue par défaut.

Gombo
la source
Salut, le script fonctionnait bien et maintenant arrêtez. pourrait être possible que si SESSION sur le serveur est désactivé ce script ne fonctionnera pas?
GibboK le
@GIbboK: Non, c'est indépendant des sessions.
Gumbo le
Correct mais je préfère la solution @diggersworld ... mieux vaut écrire moins de code
lrkwz
Quelqu'un peut-il s'il vous plaît me dire qui comment est la valeur de qdécidé? Merci
Phantom007
@ Phantom007 Dépend de la préférence: 0 = je ne veux pas de cette langue, 1 = je veux toujours cette langue.
Skyost
43

Les réponses existantes sont un peu trop détaillées, j'ai donc créé cette version plus petite et à correspondance automatique.

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

Et l'utilisation de l'échantillon:

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

Source complète ici

Xeoncross
la source
6
C'est génial et exactement ce dont j'avais besoin pour un projet particulier aujourd'hui. Le seul ajout que j'ai fait est d'autoriser la fonction à accepter une langue par défaut et à y revenir s'il n'y a pas de correspondance entre les langues disponibles et HTTP_ACCEPT_LANGUAGE.
Scott
7
Oh, l'essentiel de mes modifications est ici: gist.github.com/humantorch/d255e39a8ab4ea2e7005 (je l'ai également combiné en un seul fichier pour plus de simplicité)
Scott
2
Très belle méthode! Vous devriez peut-être vérifier si $ langs contient déjà une entrée pour la langue. m'est arrivé que le langage préféré était en-US, 2nd de et 3rd en, votre méthode m'a toujours donné de, car la première valeur de en a été écrasée par la 3ème entrée
Peter Pint
Il produit également un avertissement PHP si aucune correspondance n'est trouvée. Ce serait bien de gérer cela avec grâce.
Simon East
26

La façon officielle de gérer cela consiste à utiliser la bibliothèque HTTP PECL . Contrairement à certaines réponses ici, cela gère correctement les priorités de langue (valeurs q), les correspondances partielles de langue et renverra la correspondance la plus proche, ou lorsqu'il n'y a pas de correspondance, il revient à la première langue de votre tableau.

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

Comment utiliser:
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
la source
1
J'ai trouvé un lien fonctionnel, donc mis à jour votre réponse pour l'inclure.
Simon East
Ces trois liens semblent être morts, et ils ne semblent pas avoir d'instructions d'installation facilement googleable (cette fonction est également obsolète selon leur page)
Brian Leishman
11

Le problème avec la réponse sélectionnée ci-dessus est que l'utilisateur peut avoir son premier choix défini comme une langue qui n'est pas dans la structure de cas, mais l'un de ses autres choix de langue est défini. Vous devriez boucler jusqu'à ce que vous trouviez une correspondance.

C'est une solution super simple qui fonctionne mieux. Les navigateurs renvoient les langues par ordre de préférence, ce qui simplifie le problème. Bien que l'indicateur de langue puisse comporter plus de deux caractères (par exemple, «EN-US»), les deux premiers sont généralement suffisants. Dans l'exemple de code suivant, je recherche une correspondance dans une liste de langues connues dont mon programme est conscient.

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

J'espère que vous trouverez une solution simple et rapide que vous pourrez facilement utiliser dans votre code. J'utilise ceci en production depuis un bon moment.

Darryl
la source
3
"Les navigateurs renvoient les langues par ordre de préférence" - Ils pourraient le faire, mais vous ne devriez pas en dépendre. Utilisez des qvaleurs pour déterminer la préférence, c'est ce que la spécification dit que vous devriez faire.
Quentin
7

Essaye celui-là:

#########################################################
# 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
la source
Hey, pourriez-vous expliquer le regex qui devrait attraper la valeur q avec [0-1]{0,1}.\d{0,4}? D'abord, je suppose que vous voulez dire \.au lieu de .droite? Et q n'est-il pas toujours de la forme 0.1324ou quelque chose? Ne serait-il pas alors suffisant d'écrire 0\.?\d{0,4}? Si c'est le cas, vous q=1.0pouvez aller dans la partie autre.
Adam le
Ce serait formidable de voir un exemple d'utilisation ici.
Simon East
2
@SimonEast var_dump( getDefaultLanguage());
jirarium
4

Le script suivant est une version modifiée du code de Xeoncross (merci pour ce Xeoncross) qui revient à un paramètre de langue par défaut si aucune langue ne correspond à celles prises en charge, ou si une correspondance est trouvée, il remplace le paramètre de langue par défaut par un nouveau selon la priorité de la langue.

Dans ce scénario, le navigateur de l'utilisateur est défini par ordre de priorité sur l'espagnol, le néerlandais, l'anglais américain et l'anglais et l'application prend en charge l'anglais et le néerlandais uniquement sans variations régionales et l'anglais est la langue par défaut. L'ordre des valeurs dans la chaîne "HTTP_ACCEPT_LANGUAGE" n'est pas important si, pour une raison quelconque, le navigateur ne classe pas correctement les valeurs.

$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
la source
1

Je pense que le moyen le plus propre est celui-ci!

 <?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
la source
Cela ne tient pas compte des priorités linguistiques dans l'en-tête.
Simon East
0

Tout ce qui précède avec un retour à «en»:

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

... ou avec une langue de secours par défaut et un tableau de langues connu:

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

Une ligne:

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

Exemples:

// 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
la source
0

Essayer,

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

Grâce à

mrbengi
la source
0

Rapide et simple:

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

REMARQUE: Le premier code de langue est ce qui est utilisé par le navigateur, les autres sont d'autres langues que l'utilisateur a configurées dans le navigateur.

Certaines langues ont un code de région, par exemple. en-GB, d'autres ont juste le code de langue, par exemple. sk.

Si vous voulez juste la langue et non la région (par exemple. En, fr, es, etc.), vous pouvez utiliser:

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

J'ai celui-ci, qui crée un cookie. Et comme vous pouvez le voir, il vérifie d'abord si la langue est publiée par l'utilisateur. Parce que la langue du navigateur ne parle pas toujours de l'utilisateur.

<?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
la source
11
Je suppose que vous ne pouvez pas envoyer d'en-têtes lorsque vous avez déjà fait écho à des choses?
2
Je pense que l'indention derrière ce post a du sens, qui est de fournir à l'utilisateur un moyen de changer de langue et de se souvenir de cette décision. La détection de la langue ne doit être effectuée qu'une seule fois pour mieux deviner la première sélection.
danijar