![]() |
PHP:
|
|
| |
| Name: | Christian Seiler |
|---|---|
| E-Mail: |
Bei Fragen zu diesem Beitrag bitte den Autor des Beitrags kontaktieren!
Der Browser kann laut HTTP-Spezifikation mehrere Sprachen mitsenden, die der Benutzer bevorzugt. Der Benutzer kann dabei festlegen, in welcher Reihenfolge er diese bevorzugt. Der Webserver kann dann dem Browser die Seite in der passendsten Sprache liefern. Dieses Feature nennt man "Content negotiation".
Der Apache-Webserver bietet Mechanismen an, dies durchzuführen, jedoch erfordert dies, für jede unterschiedene Sprache eine eigene Datei anzulegen. Dies mag bei statischen Seiten sehr praktisch sein, bei dynamischen Seiten kann es den Wartungsaufwand beträchtlich erhöhen. Auch möchte man unter Umständen besser in den Mechanismus eingreifen können. Und schließlich kooperiert PHP auch mit anderen Webservern als dem Apache.
01 <?php 02 // Browsersprache ermitteln 03 function lang_getfrombrowser ($allowed_languages, $default_language, $lang_variable = null, $strict_mode = true) { 04 // $_SERVER['HTTP_ACCEPT_LANGUAGE'] verwenden, wenn keine Sprachvariable mitgegeben wurde 05 if ($lang_variable === null) { 06 $lang_variable = $_SERVER['HTTP_ACCEPT_LANGUAGE']; 07 } 08 09 // wurde irgendwelche Information mitgeschickt? 10 if (empty($lang_variable)) { 11 // Nein? => Standardsprache zurückgeben 12 return $default_language; 13 } 14 15 // Den Header auftrennen 16 $accepted_languages = preg_split('/,\s*/', $lang_variable); 17 18 // Die Standardwerte einstellen 19 $current_lang = $default_language; 20 $current_q = 0; 21 22 // Nun alle mitgegebenen Sprachen abarbeiten 23 foreach ($accepted_languages as $accepted_language) { 24 // Alle Infos über diese Sprache rausholen 25 $res = preg_match ('/^([a-z]{1,8}(?:-[a-z]{1,8})*)'. 26 '(?:;\s*q=(0(?:\.[0-9]{1,3})?|1(?:\.0{1,3})?))?$/i', $accepted_language, $matches); 27 28 // war die Syntax gültig? 29 if (!$res) { 30 // Nein? Dann ignorieren 31 continue; 32 } 33 34 // Sprachcode holen und dann sofort in die Einzelteile trennen 35 $lang_code = explode ('-', $matches[1]); 36 37 // Wurde eine Qualität mitgegeben? 38 if (isset($matches[2])) { 39 // die Qualität benutzen 40 $lang_quality = (float)$matches[2]; 41 } else { 42 // Kompabilitätsmodus: Qualität 1 annehmen 43 $lang_quality = 1.0; 44 } 45 46 // Bis der Sprachcode leer ist... 47 while (count ($lang_code)) { 48 // mal sehen, ob der Sprachcode angeboten wird 49 if (in_array (strtolower (join ('-', $lang_code)), $allowed_languages)) { 50 // Qualität anschauen 51 if ($lang_quality > $current_q) { 52 // diese Sprache verwenden 53 $current_lang = strtolower (join ('-', $lang_code)); 54 $current_q = $lang_quality; 55 // Hier die innere while-Schleife verlassen 56 break; 57 } 58 } 59 // Wenn wir im strengen Modus sind, die Sprache nicht versuchen zu minimalisieren 60 if ($strict_mode) { 61 // innere While-Schleife aufbrechen 62 break; 63 } 64 // den rechtesten Teil des Sprachcodes abschneiden 65 array_pop ($lang_code); 66 } 67 } 68 69 // die gefundene Sprache zurückgeben 70 return $current_lang; 71 } 72 ?>
<?php
include_once ('./http_sprache.php');
$allowed_langs = array ('es', 'en');
$lang = lang_getfrombrowser ($allowed_langs, 'es', null, false);
var_dump ($lang);
?>
Die Funktion lang_getfrombrowser erwartet zwei obligatorische Parameter $allowed_languages und $default_language. Der erste ist ein Array der erlaubten Sprachcodes, die klein geschrieben werden müssen. Der zweite ist die Standardsprache, wenn keine andere Sprache gefunden werden kann. Es gibt noch zwei optionale Parameter, $lang_variable und $strict_mode. Wenn $lang_variable nicht null ist, dann wird dieser Parameter anstelle von $_SERVER['HTTP_ACCEPT_LANGUAGE'] verwendet. Der letzte Parameter gibt an, ob sich die Funktion exakt an die HTTP-Sepzifiaktion halten soll, oder nicht. Unten dazu mehr.
Als erstes wird geprüft, ob nun überhaupt etwas in $lang_variable enthalten ist. Wenn das nicht der Fall ist, wird direkt auf die Standardsprache zurückgegriffen werden (
Zeilen 9-13). Die HTTP-Spezifikation besagt nämlich, dass angenommen werden kann, dass alle Sprachen gleich gut akzeptiert werden, wenn keine Sprache mitgeliefert wird.
Daraufhin wird das Feld der mitgelieferten Sprachen nach dem Komma aufgetrennt (
Zeilen 15-16). In dem Array $accepted_languages stehen nun alle vom Browser akzeptierten Sprachen.
Jede dieser Sprachen kann mit einer Qualität übergeben werden, die sich zwischen 0 und 1 befindet. Je höher die Qualität ist, desto größer ist die Akzeptanz der Sprache auf der Seite des Benutzers. Als erstes wird die Standardsprache der aktuellen Sprache zugewiesen und ihr wird auch die Qualität 0 gegeben (
Zeilen 18-20). Falls keine unterstützte Sprache gefunden wird, wird sie zurückgeliefert. Jede gefundene Sprache wird aber eine höhere Qualität haben und somit gegenüber der Standardsprache bevorzugt werden.
Nun werden in der foreach-Schleife die einzelnen Sprachen, die der Browser mitgeliefert hat, abgearbeitet (
Zeilen 23-67). Zuerst wird ein regulärer Ausdruck verwendet, um zum einen die Syntax zu prüfen und zum zweiten die Informationen zu extrahieren (
Zeilen 24-26).
Wenn die Syntax nicht gültig war, dann wird diese eine vom Browser gemachte Angabe einfach ignoriert und die Schleife wird fortgesetzt (
Zeilen 28-32). Wenn die Syntax dagegen gültig war, dann wird die Sprache nach dem Bindestrich getrennt und im Array $lang_code gespeichert (
Zeilen 34-35).
Wenn eine Qualität mitgegeben wurde, dann wird diese verwendet, wenn dem nicht der Fall war, wird die Qualität 1 angenommen (
Zeilen 37-44).
Die HTTP-Spezifikation schreibt vor, dass der übergebene Sprachcode direkt zu interpretieren ist. Das Problem dabei ist, dass jemand, der z.B. die Sprache de-at akzeptiert, wahrscheinlich nichts dagegen hat, wenn er eine Seite in de ausgeliefert bekommt. Daher wurde der Sprachcode auch noch nach dem Bindestrich aufgetrennt. Nun wird solange der jeweils letzte Teil von der Sprache abgeschnitten, bis entweder das Array, das die Sprachteile beinhaltet, leer ist, oder eine Sprache, die verfügbar ist, gefunden wurde. (
Zeilen 46-66).
In der Schleife wird zuerst geprüft, ob die Sprache überhaupt verfügbar ist (
Zeilen 48-49). Ist diese Bedingung erfüllt, folgt die nächste Abfrage, ob die angegebene Qualität größer als die aktuelle Qualität ist (
Zeilen 50-51). Wenn dies alles der Fall ist, dann wird die aktuelle Sprache auf diese Sprache gesetzt und die aktuelle Qualität auf diese Qualität gesetzt (
Zeilen 52-54). Daraufhin wird die while-Schleife abgebrochen, weil damit die Sprache erfolgreich gefunden wurde (
Zeilen 55-56).
Wenn die Sprache nicht verfügbar ist, entscheidet der Parameter $strict_mode, wie weiter zu verfahren ist. Wenn sich die Funktion wirklich konform verhalten soll, muss sie sich jetzt die nächste Sprache anschauen und darf diese Sprache nicht weiter analysieren. Also wird die while-Schleife abgebrochen (
Zeilen 61-62). Wenn die Funktion etwas toleranter sein darf, wird dagegen vom $lang_code-Array das letzte Element mit array_pop entfernt und die Schleife wird fortgesetzt (
Zeilen 64-65).
Am Ende gibt die Funktion die gefundene Sprache zurück (
Zeilen 69-70).
Die komplette PHP-Datei steht auch zum
Download bereit.
Die Funktion verhält sich standardmäßig konform zur Spezifikation. Der Programmierer muss also im Klaren sein, dass er die Spezifikation missachtet, wenn er den vierten Parameter auf false setzt. Es kann jedoch, wie oben beschrieben, von Vorteil sein, die Spezifikation in diesem einen Punkt zu missachten.
Wenn man anstelle dieser Funktion das Feature des Apache-Webservers verwendet, dann kann man diesen Punkt jedoch nicht beeinflussen, denn der Apache-Webserver hält sich hier an die HTTP-Spezifikation.
Wenn keine passende Sprache gefunden wurde, gibt es eine andere mögliche Alternative, als eine Standardsprache zurückzuliefern. Die HTTP-Spezifikation beschreibt den Fehlercode 406, der immer dann vom Server generiert werden darf, wenn kein zu den Angaben des Browsers passendes Dokument gefunden werden kann. Dieser Fehlercode sollte laut Spezifikation mit einer Liste von verfügbaren Sprachen einhergehen. Um dies mit dieser Funktion zu bewerkstelligen, sollte man null als Standardsprache übergeben. Wenn die Funktion daraufhin null zurückgibt, weiß man, dass der Browser keine Sprache akzeptiert, die vom Programmierer angeboten wird und das Script kann nun die HTTP-Fehlermledung ausgeben. Dazu eignet sich die PHP-Funktion header.
Es gibt eine PEAR-Klasse HTTP, die eine (statische) Methode negotiateLanguage besitzt, die das Gleiche bewerkstelligen soll. Im Moment verhält sich diese Methode aber nicht standardkonform, denn sie ist z.T. nicht streng genug in der Syntaxprüfung. Außerdem versucht die Funktion anhand der Domäne (z.B. irgendein-provider.de wird als Deutschland »erkannt«) des Besuchers festzustellen, welche Sprache er/sie will, falls die andere Methode fehlschlägt. Die Domäne sagt aber nur u.U. etwas über die Herkunft des Benutzers aus und dieses Verhalten ist bei der PEAR-Methode nicht abstellbar, von daher ist von dem Gebrauch dieser Methode im Moment abzuraten.
Die folgenden Stellen werden empfohlen, um das obige Beispiel besser zu verstehen, oder um weitere Möglichkeiten und Details zu erfahren.
Das PHP-Handbuch: die Funktion preg_match
Das PHP-Handbuch: die Funktion preg_split
Das PHP-Handbuch: die Funktion array_pop
Das PHP-Handbuch: die Funktion in_array
Das PEAR-Handbuch: die Methode HTTP::negotiateLanguage
Die HTTP-Spezifikation: Der Aufbau des Accept-Language-Feldes
Die HTTP-Spezifikation: Der Fehlercode 406
Die Apache-Dokumentation: Content-Negotiation bei statischen Seiten
Michael Schröpl: Alternative Dokumentinhalte via HTTP