Eine Sprachweiche wird ein wichtiger Teil meines Skripts. Für alle, die so etwas auch gebrauchen können, habe ich erstmal eine ganz große Variable:
$Babelfish=array(array('中文 (繁體/正字)','pu','zh-tw','zh-hk','zh-hant','zh-cht'),array('中文 (简体)','cn','zh-cn','zh-sg','zh-hans','zh-chs'),array('Ελληνικά','el'),array('Magyar','hu'),array('Íslenska','is'),array('Bahasia Indonesia','id','in'),array('Kalaallisut','kl'),array('ᐃᓄᒃᑎᑐᑦ','iu'),array('Català','ca'),array('العربية','ar'),array('עִבְרִית','he'),array('Gàidhlig','gd'),array('Italiano','it'),array('日本語','ja','jp'),array('韓國語/朝鮮語','ko'),array('Latviešu','lv'),array('Lietuvių','lt'),array('Македонски','mk'),array('Gaeilge','ga','gd-ie'),array('Malti','mt'),array('Norsk','no','nn','nb'),array('ພາສາລາວ','lo'),array('کوردی','ku'),array('ภาษาไทย','th'),array('ქართული','ka'),array('Sámegiella','sz'),array('Slovenščina','sl'),array('Slovenčina','sk'),array('Српски','sr'),array('Русский','ru'),array('Suomi','fi'),array('Français','fr'),array('Bosanski','bs'),array('Беларуская','be'),array('Română','ro'),array('Português','pt'),array('Polski','pl'),array('Svenska','sv'),array('Türkçe','tr'),array('Українська','uk'),array('Nederlands','nl'),array('Eesti','et'),array('Български','bg'),array('Euskara','eu'),array('Shqip','sq'),array('Hrvatski','hr'),array('Čeština','cs','cz'),array('Dansk','da'),array('اردو','ur'),array('Tiếng Việt','vi'),array('יידיש','yi','ji'),array('Español','es'),array('lingua latina','la'),array('فارسی','fa'),array('English','en'),array('Deutsch','de'));
Leider waren Khmer und indische Schriften auf meinem Computer nicht darstellbar, deshalb fehlen die in der Variablen. Ansonsten war ich sehr gründlich. Manche Angaben sind nicht eindeutig. Zum Beispiel steht ji für Jiddisch, aber die meisten Amis (einschließlich denen, die Software herstellen) können mit dem j nichts anfangen, darum steht auch yi für Jiddisch. Die Abkürzung kr hielt auf irgendeiner Netzseite jemand für die fehlerhafte Abkürzung für Koreanisch, während ich das eher für fehlerhaft für Kroatisch halte, aber es gibt solche Abkürzungen auch für kleine Sprachen ohne offizjelle Abkürzung und auf inoffizjelle habe ich verzichtet. Jedenfalls habe ich wegen der Mehrdeutigkeit die Variable als Array of Array konstruiert.
Besonders fies ist die Abkürzung zh. Die steht zwar für Chinesisch, aber sowohl für eine mit traditionellen Schriftzeichen geschriebenen Variante als auch für die Variante, die mit den durch die kommunistische Regierung reformierten Schriftzeichen geschrieben wird. Die Sprache ist zwar die selbe und darum wohl auch die Abkürzung, trotzdem halten viele chinesischsprachige Netzseiten (auch Wikipedia) beide Versionen bereit, so daß die Möglichkeit zum Umschalten hier sinnvoll wäre. Das wiederum wäre kein Problem, wenn dafür der Regionalcode (der an manchen Abkürzungen mit -xy dranhängt) eindeutig wäre. Aber das ist nicht der Fall, deshalb habe ich die eindeutigen Codes pu und cn erfunden. Mit denen ersetze ich in meiner Auswertung die übrigen Möglichkeiten die beiden Varianten zu bezeichnen. In meiner Variablen sind sie alle enthalten.
Möglicherweise gibt es beim Norwegischen ein ähnliches Problem wie beim Chinesischen, da bin ich mir nicht ganz sicher. Es gibt nämlich die Varianten Nynorsk und Bokmål, aber Letzteres bezeichnet möglicherweise nur das geschriebene Norwegisch. Jedenfalls bezeichnet Norsk offizjell jegliches Norwegisch.
Also es ist ganz schön kompliziert und es steckt viel Arbeit in der Variablen.
Welche Sprachversion eine mehrsprachige Netzseite anzeigen sollte, erfährt sie natürlich aus $_SERVER['HTTP_ACCEPT_LANGUAGE'], aber der Inhalt dieser Variablen ist nicht standardisiert. Da kann viel Blödsinn drinstehen, von fehlerhaften Bezeichnungen bis hin zu richtigen Angriffen. Deshalb halte ich es für die beste Methode mit einem RegEx in der Servervariablen nach den Abkürzungen aus meiner großen Variablen zu fischen. Also mit einem preg_match() als Bedingung einer IF-Anweisung innerhalb einer FOREACH-Schleife für den Array einer einzelnen Sprache innerhalb einer FOREACH-Schleife für den umgebenden Array.
Die Regionalcodes, die oft an die Sprachcodes angehängt werden, bestehen wie die Sprachcodes selbst aus jeweils zwei Buchstaben. Für die Sprachcodes gibt es noch zwei weitere Standards, die jeweils dreibuchstabige Codes verwenden, viel mehr und unbedeutendere (also zum Beispiel Klingonisch) bezeichnen können und eigentlich nicht für die Bezeichnungen der Sprachen von Netzseiten gedacht sind. Der Versuch die auch noch einzubeziehen würde unauflösbare Mehrdeutigkeiten erschaffen. So stünde dann yi nicht mehr nur für Jiddisch wie es üblich ist, sondern auch für eine Regionalsprache in China und das wäre korrekter, aber halt nicht das, was einem ein Brauser mittels yi in $_SERVER['HTTP_ACCEPT_LANGUAGE'] würde mitteilen wollen.
Wegen dem, was alles in $_SERVER['HTTP_ACCEPT_LANGUAGE'] erlaubt ist, sollte der verwendete RegEx nach einem Wortanfang und dann zwei Buchstaben fischen. In der Servervariablen sollten nur ASCII-Buchstaben vorkommen, daher sollte das nicht allzu schwierig sein.
Jetzt komme ich zum seltsamen Teil!
Also ich verwende
preg_match('@(\W|^)'.$Test.'@ismuU',$_SERVER['HTTP_ACCEPT_LANGUAGE'])
wobei $Test natürlich die Variable aus der inneren FOREACH-Schleife ist und darum jeweils die zweibuchstabigen Abkürzungen enthält.
Zu Testzwecken habe ich in meinem Brauser (Firefox auf OpenSUSE) in den bevorzugten Sprachen zehn Stück eingestellt. Seltsamerweise findet man Skript stattdessen vierundvierzig davon! Da fehlen noch einige, es wird also nicht meine gesamte große Variable gefunden.
Ich habe noch versucht, das Ergebnis zu sortieren und zwar mittels einem preg_match_all(), bei dem das Flag PREG_OFFSET_CAPTURE gesetzt ist. Das hat jedoch garkeinen Einfluß auf das beschriebene Verhalten. So oder so sind die erwarteten Ergebnisse nicht diejenigen, die am Anfang des zu umfangreichen Ergebnisses stehen.
Meine große Variable beginnt mit Chinesisch und dann Griechisch, in meinem erwartetem Ergebnis sollte natürlich erst Deutsch und dann Englisch und dann weitere Sprachen auftauchen. Das seltsame, zu umfangreiche Ergebnis beginnt stattdessen immer mit Japanisch und dafür sehe ich garkeinen möglichen Grund.
Was kann da passiert sein?
Ich hätte auf OpenSUSE noch den Konquerer als alternativen Brauser, aber ich konnte nicht herausfinden, wo ich dort bevorzugte Sprachen einstellen könnte. Darum sind dort nur Deutsch und Englisch eingestellt und die beiden werden mir auch als Ergebnis geliefert. Also de, en-US, en führt zu dem Ergebnis de, en beziehungsweise Deutsch, Englisch (ich habe je eine Variable für die ausgeschriebene Bezeichnung und für die jeweils erste ihrer Abkürzungen). Kein Überhang.
Ranma
Also mit einem preg_match() als Bedingung einer IF-Anweisung innerhalb einer FOREACH-Schleife für den Array einer einzelnen Sprache innerhalb einer FOREACH-Schleife für den umgebenden Array.
Für ein besseres Verständnis und zum Reproduzieren des Problems wäre es gut, wenn du diesen Code noch posten könntest.
Grundsätzlich würde ich das Sprach-Array eher so aufbauen:
$Babelfish = array(
'中文 (繁體/正字)' => array('pu','zh-tw','zh-hk','zh-hant','zh-cht'),
'中文 (简体)' => array('cn','zh-cn','zh-sg','zh-hans','zh-chs'),
'Ελληνικά' => array('el'),
/* ... */
'Deutsch' => array('de')
);
... also die Sprachbezeichnungen als Keys, so dass sich in den Arrays der zweiten Ebene nur die Sprachkürzel befinden. Auf die Keys kann man dann in einer Schleife gesondert zugreifen:
foreach ($Babelfish as $language => $language_codes) {
echo $language . "<br/>";
foreach ($language_codes as $language_code) {
echo $language_code . "<br/>";
}
}
$treffer=FALSE; if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE']))):
/* die äußere foreach-Schleife; ich verwende gerne die Langfassung,
damit ich weiß welches Ende zu welcher Verzweigung gehört */
foreach($Babelfish as $language):
/* den Eigennamen der Sprache brauche ich hier noch nicht, das ist
aber der erste Eintrag in jedem Array, darum */
$language=$lang0;
$dummy=array_shift($lang0);
/* die innere foreach-Schleife, hier wird nach den Treffern gesucht */
foreach($lang0 as $code):
if(preg_match('@(\W|^)'.$Test.'@ismuU',
$_SERVER['HTTP_ACCEPT_LANGUAGE'])):
/* normalerweise sollte eine Sprache nur durch einen Code
angezeigt werden, deshalb führt irgendein Treffer von FALSE
zu TRUE */
$treffer=TRUE;
endif;
endforeach;
/* außerhalb der inneren foreach-Schleife, Treffer zu den Ergebnisvariablen hinzufügen */
$long[]=$language[0];$short[]=$language[1];
endforeach;
/* Der Array $short[] sollte nun die üblichste Abkürzung für jede der bevorzugten
Sprachen anzeigen und der Array $long[] deren Eigennamen. */
else: $short[]='en'; $long[]='English';
endif;
So, jetzt habe ich es, glaube ich, auch gesehen. Das =FALSE sitzt außerhalb der ersten Foreach-Schleife und gehört wohl nach drinnen. Das erklärt nicht unbedingt die seltsame Reihenfolge des Ergebnisses, aber natürlich muß ich nun erst ausprobieren, was passiert, wenn ich die Anweisung nach innen setze.
Die Methode mit Schlüssel und Wert scheint mir so ziemlich das Gleiche zu sein, nur mit mehr Schreibarbeit?
Ranma
Ja, am versetzen des $treffer=FALSE; lag es. Der Überhang ist jetzt weg. Trotzdem bekomme ich das Ergebnis nicht sortiert. Zu den Aufgaben von $_SERVER['HTTP_ACCEPT_LANGUAGE'] gehört es, eine Gewichtung mitzuteilen und das geschieht normalerweise durch die Reihenfolge der Einträge in dieser Variablen. Deshalb wäre es sinnvoll, mein Ergebnis nach dieser Reihenfolge zu sortieren. Ich habe das jetzt mit strpos() versucht. Natürlich suche ich damit irgendwie nur immer nach dem ersten möglichen Kürzel, aber für meine bisherigen Versuche dürfte das keinen Unterschied machen. Mit strpos() in einer foreach-Schleife mit $short habe ich versucht ein Array zu füllen und danach mittels sort() neue Arrays aus meinen Arrays $short und $long zu machen. Das führt aber nicht zu einer Sortierung.
Natürlich wäre es noch besser die tatsächlich enthaltenen Kürzel für die Sortierung zu verwenden statt die Idealfälle. Vielleicht strpos() gleich bei jedem Treffer hinzufügen und danach sämtliche Zahlen irgendwie in Schlüssel für das Array umwandeln?
Oder sollte ich doch besser einen RegEx erstellen, der sowohl Kürzel wie de oder en, aber zugleich auch Kürzel wie gd-ie oder zh-hant erfaßt und trotzdem keinen Unfug zuläßt? (Am besten noch erweiterbar {ich habe $Babelfish leicht erweiterbar aufgebaut} auf Kürzel wie tlh, das steht für Klingonisch und hat keinen offizjellen zweibuchstabigen Code. Der Regionalcode ist manchmal wie bei gd-ie notwendig, meistens stört er nur.)
Wie mache ich das am besten?
Ranma
Die Idee mit dem neuen RegEx bedeutet natürlich, daß ich die gesamte Auswertung nochmal neu machen müßte. Natürlich liegt die Priorität darauf, daß es am Ende funktioniert. Aber ich bin auch so schon sehr langsam und wundere mich, ob es normal ist, dreiviertel der Zeit mit dem Finden und Entfernen von Fehlern zubringen zu müssen. Darum würde ich weniger Schreibarbeit und weniger nochmal neu schreiben vorziehen, als letzten Punkt auf der Prioritätenliste.
Ranma
Die Methode mit Schlüssel und Wert scheint mir so ziemlich das Gleiche zu sein, nur mit mehr Schreibarbeit?
Du meinst das zusätzliche Zeichen "=>"? dafür ist das Array dann beim Lesen des Codes deutlich besser zu erfassen und du benötigst keine solchen Workarounds wie mit dem array_shift()
foreach($lang0 as $code):
if(preg_match('@(\W|^)'.$Test.'@ismuU',
... und wo kommt das $Test nun her - oder sollte an der Stelle eigentlich $code stehen?
/* außerhalb der inneren foreach-Schleife, Treffer zu den Ergebnisvariablen hinzufügen */
$long[]=$language[0];$short[]=$language[1];
Außerhalb der Bedingung weiter oben? wenn ich $short und $long mit print_r ausgeben lasse, erhalte ich diese Ausgabe:
Array ( [0] => [1] => [2] => [3] => [4] => [5] => [6] => [7] => [8] => [9] => [10] => [11] => [12] => [13] => [14] => [15] => [16] => [17] => [18] => [19] => [20] => [21] => [22] => [23] => [24] => [25] => [26] => [27] => [28] => [29] => [30] => [31] => [32] => [33] => [34] => [35] => [36] => [37] => [38] => [39] => [40] => [41] => [42] => [43] => [44] => [45] => [46] => [47] => [48] => [49] => [50] => [51] => [52] => [53] => [54] => [55] => )
Array ( [0] => [1] => [2] => [3] => [4] => [5] => [6] => [7] => [8] => [9] => [10] => [11] => [12] => [13] => [14] => [15] => [16] => [17] => [18] => [19] => [20] => [21] => [22] => [23] => [24] => [25] => [26] => [27] => [28] => [29] => [30] => [31] => [32] => [33] => [34] => [35] => [36] => [37] => [38] => [39] => [40] => [41] => [42] => [43] => [44] => [45] => [46] => [47] => [48] => [49] => [50] => [51] => [52] => [53] => [54] => [55] => )
Sinnvoller wäre das Hinzufügen zu den Arrays $short und $long wohl eher innerhalb des if Blocks?
Edit:
da scheint noch einiges mehr im Argen:
$language=$lang0;
... sollte wohl eher sein:
$lang0 = $language;
... dann sind die oben ausgegebenen Array-Elemente zumindest nicht leer
Ja, in meinem angegebenem Code sind wenigstens zwei Variablen falsch bezeichnet. Bevor ich Code hier einstelle, benenne ich alle Variablen um. Darum bin ich auch so zögerlich damit. Ich habe gelesen, daß es nicht gut ist, wenn die Namen von Variablen erraten werden können. Ich weiß nur nicht mehr, ob das für eine bestimmte Situation war oder generell nicht gut ist. Jedenfalls benenne ich die Variablen für die Veröffentlichung um und auch wenn ich zwischen den Ebenen HTML, PHP und MySQL wechsle. Falls das übertrieben ist, dann ist das natürlich nur eine Quelle für Fehler. Falls nicht, dann muß ich noch $Test umbenennen.
array_shift() brauche ich eigentlich garnicht. Das dadurch beseitigte Element [0] im Array sollte schließlich sowieso nie im durchsuchten String enthalten sein. Aber falls doch, dann wäre es sogar sinnvoll das als gültige Angabe zu werten.
Innerhalb des IF-Blocks funktioniert das Hinzufügen praktisch perfekt.
Trotzdem ist eine Sache noch schräg. Ich habe noch eine Indizierung von $long und $short mittels strpos($_SERVER['HTTP_ACCEPT_LANGUAGE'], $code) vorgenommen. print_r() gibt die erwarteten Schlüssel und Werte aus. Aber nicht in der numerischen Reihenfolge. strpos() sollte doch integer-Werte liefern? Jedenfalls kann ich den Wert mit +1 um eins erhöhen. Weil $code immer mindestens zwei Zeichen lang ist, konnte ich das leicht ausprobieren ohne das Ergebnis zu verfälschen.
Eigentlich wollte ich noch die Lücken zwischen den Schlüsseln aus den Ergebnissen herausnehmen, vielleicht mit implode() und explode(). Aber dafür müßte die Reihenfolge erst stimmen, während print_r() die Integer(?)-Werte ungefähr als [33], [51], [42], [0], [15] ausgibt. Eigentlich sollte sich der Array doch automatisch danach ordnen? Jedenfalls finde ich keine Sortierfunktion, die das noch nachträglich übernehmen könnte.
Ranma
Es spricht nichts gegen die Umbenennung von Variabeln - der Code sollte dann allerdings darauf getestet werden, ob er funktionstüchtig ist, und ob man mit ihm das betreffende Problem reproduzieren kann
Trotzdem ist eine Sache noch schräg. Ich habe noch eine Indizierung von $long und $short mittels strpos($_SERVER['HTTP_ACCEPT_LANGUAGE'], $code) vorgenommen. print_r() gibt die erwarteten Schlüssel und Werte aus. Aber nicht in der numerischen Reihenfolge. strpos() sollte doch integer-Werte liefern?
Ja - dir geht es um die Positionen innerhalb von$_SERVER['HTTP_ACCEPT_LANGUAGE']? wie hast du die Return-Werte denn in dem Array gespeichert? auch hier wäre Code zum besseren Verständnis hilfreich :)
Jedenfalls kann ich den Wert mit +1 um eins erhöhen.
Du meinst das dritte Argument von strpos(), d.h. den Offset-Wert?