Das i18n Package ist eine kleine Sammlung von Klassen zur Internationalisierung von PHP Web-Projekten. Mit deren Hilfe ist es möglich mehrsprachige Seiten einfacher zu verwalten als mit den Standard PHP-Funktionen. Die Übersetzungstexte können entweder in normalen Textdateien, speziellen vorkompilierten Gettext Dateien oder in einer MySQL Tabelle gespeichert werden. Alles funktioniert unabhängig von den setlocale Einstellungen in PHP.
Um Probleme mit erzeugten Cookies und Sessions zu vermeiden sollte man zuerst die Ausgabepufferung bei jeden Script aktivieren indem man folgende Zeilen am Beginn einfügt:
ob_start();
session_start();
und am Ende:
ob_end_flush();
Fangen wir mit einer der Basisklassen, der Language Klasse, an. Aufgabe dieser ist es die vom Benutzer bevorzugten Einstellungen bezüglich Locale, Sprache und Land aus dem HTTP_ACCEPT_LANGUAGE Header zu ermitteln. Sollten hier keine brauchbaren Informationen gefunden werden, so wird versucht die IP Adresse des Benutzers aufzulösen und so zumindest ein Land und eventuell eine Sprache zu ermitteln.
Zuerst erzeugen wir ein neues Objekt:
include('class.Language.inc.php');
$lg = new Language();
Nehmen wir an der Benutzer ist aus Österreich und hat Deutsch als seine bevorzugte Sprache im Browser eingestellt (de-at). Um an diese Informationen zu kommen können folgende Methoden verwendet werden:
$lg->getLocale()
gibt de_at aus (Bindestriche
werden durch Unterstriche ersetzt)
$lg->getLang()
gibt de aus
$lg->getCountry()
gibt at aus
Da der Benutzer ja auch mehrere bevorzugte Locales haben kann, können diese Informationen natürlich auch abgerufen werden:
$lg->getUserRawArray()
gibt einen Array mit allen bevorzugten
Locales aus
$lg->getUserLangArray()
gibt einen Array mit allen bevorzugten
Sprachen aus
$lg->getUserCountryArray()
gibt einen Array mit allen bevorzugten
Ländern aus
Was passiert aber falls der Benutzer hier keine Informationen eingestellt hat oder preisgeben will? In diesem Fall wird auf die Standardeinstellungen der Klasse zurückgegriffen. Diese befinden sich alle in der Datei i18n_settings.ini:
[Language]
default_locale = "en"
default_language = "en"
default_country = "us"
Um diese Standardeinstellungen unabhängig von den Benutzereinstellungen auslesen zu können kann man folgende Methoden verwenden:
$lg->getDefaultLocale()
$lg->getDefaultLanguage()
$lg->getDefaultCountry()
In der i18n_settings.ini Datei kann aber noch mehr eingestellt werden:
Bei der Erzeugung von Language Objekten können aber auch alle ermittelten Daten ganz einfach übergangen werden und ein bestimmter Locale erzwungen werden indem man ihn einfach als Argument übergibt:
$lg_gb = new Language('en_gb');
Das war die Language Klasse. Jetzt verwenden wir die daraus gewonnen Informationen zum Übersetzen von Texten. Dafür nehmen wir die Translator Klasse her die die Language Klasse erweitert.
include('class.Translator.inc.php');
$translation = new Translator('','frontpage, everypage');
Diese Klasse nimmt nun bereits zwei Argumente an. Das erste kann wieder zur
Erzwingung eines bestimmten Locales verwendet werden. Das zweite wird
speziell dann benötigt wenn man mit Text- oder Gettext-Dateien arbeitet.
Hier wird eine kommaunterteilte Liste mit Dateinamen ohne die
Dateinamenerweiterung eingetragen (Die stellt man in der i18n_settings.ini Datei
ein). Diese Dateien müssen sich im jeweiligen Sprach-Unterverzeichnis
des „locale“ Verzeichnisses befinden falls man reine Textdateien
verwendet oder dann noch einmal ein Unterverzeichnis „LC_MESSAGES“ tiefer
falls man Gettext Dateien verwendet.
Die Sprach-Unterverzeichnisse müssen der ISO Norm für Locales
entsprechen mit der Ausnahme dass halt ein Unter- statt einem
Bindestrich verwendet wird. (Zum Beispiel: „it“, „de_at“, „en_gb“,…).
Falls man den MySQL Modus verwendet ist das zweite Argument eine Liste
von Namespaces. Alle Übersetzungstexte die diesem
Namespace angehören werden dann automatisch geladen
um nicht für jeden Text einzeln einen Datenbankzugriff machen
zu müssen.
Außer im MySQL Modus ist es möglich auch Alias Sprachen anzulegen.
Falls man bereit ein Verzeichnis „en“ mit allen Übersetzungen
hat und man nicht alles noch mal für „en_gb“ zum Beispiel übersetzen
und Warten will, so legt man in dieses weitere Verzeichnis einfach
eine Datei namens „redirect“ (ohne Dateinamen-Erweiterung)
die als Text die eigentliche Sprache (in diesem Fall „en“) enthält.
Alias Sprachen funktionieren im MySQL Modus nicht!
Im Package sind bereits ein paar Beispielsprachen enthalten um so den Einstieg ein bisschen zu erleichtern und um zu zeigen wie die Übersetzungsdateien auszusehen haben. Auf die Erzeugung von Gettext Dateien wird hier aber nicht eingegangen. Dazu gibt es genug Tutorials im Web…
Wenn ein neues Translator Objekt erzeugt wird, dann wird die Liste der bevorzugten
Locales des Benutzers aus der Language Klasse mit den vorhandenen Sprach-Locales
aus der Translator Klasse verglichen und der bestmögliche Locale
gewählt. Auch geben die Methoden getLocale()
, getLang()
und getCountry()
nicht
mehr die bevorzugten Benutzereinstellungen wieder sondern die
Informationen aus dem automatisch ausgewählten Übersetzungs-Locale.
Wenn man dann also mal die MySQL Tabelle oder die Übersetzungsdateien
angelegt hat kann man mit dem Übersetzen von Texten beginnen
indem man die translate()
Methode verwendet.
echo $translation->translate('no_records_found');
„no_records_found“ ist der String nach dem in den Dateien oder der Tabelle gesucht und ausgegeben wird. Falls man Umlaute und sonstige Sonderzeichen auch gleich codiert ausgeben will kann man folgende Methode verwenden:
echo $translation->translateEncode('no_records_found');
Um darüber hinaus noch weitere Zeichen, wie zum Beispiel „ד, „’“, „‡“… zu codieren kann man die translateEncodePlus Methode verwenden:
echo $translation->translateEncodePlus('no_records_found');
Um sich Schreibarbeit zu ersparen kann man auch folgende Kurzversionen verwenden:
echo $translation->_('no_records_found');
= translate
echo $translation->__('no_records_found');
= translateEncode
echo $translation->___('no_records_found');
= translateEncodePlus
Falls für den String keine Übersetzung gefunden wurde wird folgende Fehlermeldung bei der Ausgabe auftauchen:
ERROR TRANSLATING: »» no_records_foun ««
Diese Meldung kann in der i18n_settings.ini Datei deaktiviert werden. Dann wird nur der nicht übersetze String ausgegeben.
Wenn man mit Gettext Dateien arbeitet ist die Situation ein bisschen komplizierter
falls man mehr als nur eine Übersetzungsdatei bei der Objekterzeugung
angegeben hat. In diesem Fall muss der entsprechende Dateiname
für jeden zu übersetzenden String (außer der Erste
in der Liste) als zweites Argument an die Translate()
Methode übergeben
werden:
echo $translation->Translate('another_string', 'translationfile2');
echo $translation->TranslateEncode('another_string', 'translationfile2');
echo $translation->_('another_string', 'translationfile2');
echo $translation->__('another_string', 'translationfile2');
Falls benötigt, kann die Sprache für ein Objekt auch dynamisch geändert werden:
$translation2 = new Translator('de','frontpage, everypage');
echo $translation2->_('good_morning');
$translation2->changeLocale('en');
echo $translation2->_('good_morning');
Zuerst würde hier Guten Morgen ausgegeben werden, bei der zweiten Ausgabe Good Morning.
Andere Methoden in der Klasse:
$translation->getLanguageFilesArray()
gibt einen Array mit
allen gefundenen Übersetzungs-Locales aus.
$translation->getModus()
gibt den Modus aus („gettext“, „inc“ or „mysql“)
$translation->getRealLocale()
Gibt das tatsächlich
verwendete Locale zurück falls ein Alias verwendet wird.
Jetzt wissen wir wie wir multilinguale Scripts machen können. Aber
wie kann der Benutzer eine andere Sprache auswählen?
Dafür brauchen wir die ChooseLanguage Klasse die die Translator Klasse
erweitert:
include('class.ChooseLanguage.inc.php');
$translation3 = new ChooseLanguage('','frontpage, everypage');
echo $translation3->returnDropdownLang();
Damit wird ein <select>
Formularelement mit allen
möglichen Sprachen in der jeweiligen Sprache ausgegeben. Die anderen
Formularelemente wie Senden Knöpfe usw. müssen dann im Script
manuell hinzugefügt werden. Auf jeden Fall muss das Ziel des Formulars
eine Seite sein die die ChooseLanguage Klasse wieder aufruft. Falls einem
ein Dropdown-Menü nicht liegt kann man die Formularelemente auch
manuell erstellen. Der Name dieser muss aber “locale” lauten
und muss als Wert dann ein, der ISO Norm entsprechendes, Locale besitzen.
Beispiel:
<input type="hidden" name="locale" value="en">
Andere Methoden in der ChooseLanguage Klasse:
$translation3->countStrings();
gibt die Anzahl gefundener Übersetzungstexte
in den angegebenen Dateien, bzw. Namespaces der Übersetzungstabelle
zurück
$translation3->countLanguages();
gibt die Anzahl vorhandener Übersetzung-Locales
zurück
Basierend auf diesen Klassen gibt es noch ein paar andere Klassen um Datumsangaben, Zahlen, Währungen usw. zu im Kontext der Sprache zu formatieren:
include('class.FormatDate.inc.php');
$fd = new FormatDate();
$timestamp = $fd->ISOdatetimeToUnixtimestamp('2003-12-24 13:45:00');
echo $fd->longDateTime($timestamp);
Dies würde das ISO Datum '2003-12-24 13:45:00' formatieren. Falls das eingestellte Locale „de“ ist, würde die Ausgabe folgendermaßen aussehen:
Mittwoch, 24. Dezember 2003 – 13.45 Uhr
Beim Locale „it“ würde es folgendermaßen aussehen.
Mercoledì, 24 dicembre 2003 – 13.45
Es stehen folgende Methoden für die Formatierung zur Verfügung: shortDate()
, shortTime()
,
shortDateTime()
, middleDate()
, middleTime()
, middleDateTime()
, longDate()
, longTime()
, longDateTime()
.
Diese brauchen als Argument alle einen UNIX Timestamp…
Um dem Benutzer mehr Auswahl bieten zu können ist es möglich mit
Hilfe der ChooseFormatDate Klasse ein <select>
Dropdownmenü, ähnlich
dem der ChooseLanguage Klasse, auszugeben. Zur Auswahl stehen dann
das klassische Datumsformatierung in der jeweiligen Sprache, das ISO
Format und das swatch Format.
Angaben zur Formatierung können in der l10n.ini Datei gemacht werden.
Zahlen werden auch sprachabhängig unterschiedlich formatiert. Die englisch geschriebene Zahl 123456.789 würde in der deutschen Schreibweise 123 456,789 lauten.
include('class.FormatNumber.inc.php');
$fn = new FormatNumber();
echo $fn->number(123456.789, 2);
Mit dieser Methode werden Zahlen formatiert. Das zweite Argument gibt die
Anzahl an Nachkommastellen an die angezeigt werden sollen.
Die percent()
Methode arbeitet auf die gleiche Weise.
Bei der Formatierung von Währungseinheiten gibt es mehr
Argumente:
$fn->currency(99.90, 'full', 'gb', FALSE);
Dies würde in der deutschen Schreibweise 99,90
Pfund ausgeben und in der Englischen 99.90 Pound.
Das erste Argument ist die zu formatierende Zahl, das Zweite definiert
die Schreibweise der Währungseinheit: full bedeutet
das der ausgeschriebene Name der Währung verwendet wird
(zum Beispiel „Pound“), short würde
das Kürzel ausgeben (hier „GBP“) und symbol würde
das Währungssymbol ausgeben (hier „£“).
Das dritte Argument gibt das Land der Währung an (hier „England“)
und das letzte Argument gibt an ob nur die „große“ Währungsangabe
verwendet werden soll oder nicht. Beispiel:
$fn->currency(0.99, 'full', 'de', FALSE);
gibt 99
Cent aus
$fn->currency(0.99, 'full', 'de', TRUE);
gibt 0.99
Euro aus
Angaben zur Formatierung können in der l10n.ini Datei gemacht werden.
Diese Klasse beinhaltet nur die US Maßeinheit und das metrische System.
include('class.FormatMeasure.inc.php');
$fn = new FormatMeasure ('si','uscs');
Hier wurde ein neues Objekt erzeugt welches als Eingabe Werte in metrischen Angaben hernimmt und Diese umgerechnet dann in US Einheiten ausgibt. Falls das zweite Argument nicht übergeben wurde, wird versucht wiederum anhand der Sprache das richtige System zu wählen.
Die Methoden die zur Verfügung stehen sind linear()
, surface()
, capacity()
, cooking()
, liquid()
und temperature()
.
Die meisten davon benötigen drei Argumente. Das Erste ist die Zahl,
das Zweite und Dritte sind die Ein- und Ausgabeeinheit. Zum Beispiel
würde dies bei der Methode linear()
so aussehen:
0: mm|in,
1: cm|ft,
2: dm|yd,
3: m|fur,
4: km|mi,
Alle Maßeinheiten sind in der Klassendokumentation zu finden.
Im Kontext der Sprache können zwei Filter-Methoden auf Texte angewendet
werden. Die Methode wordFilter()
ersetzt „böse“ Wörter,
wie zum Beispiel Schimpfwörter, mit „*“ Sternchen.
Eine kommagetrennte Liste kann für jede Sprache separat in der l10n.ini Datei
angegeben werden.
Die Methode filterSpecialWords()
sucht nach Schlüsselwörtern
und formatiert diese mit abbr, acronym oder dfn HTML
Tags. Die Wörter und dazugehörigen Texte können in der words.ini Datei
geändert werden, die in jedem Sprachverzeichnis liegt.
Mit der Shared Memory Funktionalität können alle ini Dateien
bei erstmaligen Lesen auch in den gemeinsamen Speicher des Servers
geschrieben werden. Auf diese Weise erspart man sich ein einlesen
der Dateien mit jedem Seitenzugriff. Standardmäßig ist dies jedoch
deaktivert. Zur Aktivierung muss die Klassenvariable $use_shared_mem
in
der I18N Klasse auf TRUE gesetzt werden.
Allerdings werden Änderungen an einer
der Dateien nicht automatisch übernommen. Dazu kann der Speicher
aber manuell gelöscht werden indem man die $flush_sm
Variable
(ebenfalls in der I18N Klasse) auf TRUE setzt.
Wer eine andere Datenbank als MySQL verwenden will muss wohl oder übel
die betreffenden Methoden manuell umschreiben. Es ist nicht geplant
irgendeine Art von DB Abstract Layer zu verwenden um die Klassen unabhängig
von irgendeinem Framework halten zu können.
Da ich noch nie PEAR verwendet habe, kann
ich auch nicht sagen wie schwer es wäre die Klassen dort mit der DB
Klasse zu implementieren, aber wer will kann es ja mal probieren und es mir
dann sagen :-)
Datenbankcode kommt in folgenden Methoden der Klassen Translator
und ChooseLanguage vor: setConnection()
, checkLocale()
, languagesFilesList()
, readLanguageFile()
, Translate()
, getLastUpdateDate()
und countStrings()
.
Falls man irgendeine Art von Cache-Mechanismus verwendet so ist seit
Version 1.054 in der Klasse Translator eine Methode namens getLastUpdateDate()
zu
finden. Diese gibt das aktuellste Änderungsdatum (als UNIX Timestamp)
der ausgewählten Übersetzungsdateien bzw. Namespaces
zurück. So kann man überprüfen ob irgendwelche Übersetzungen
neuer sind als die gecachte Seite und gegebenenfalls Diese neu erzeugen
lassen.
Die Klassen wurden noch nicht mit smarty getestet, sollten aber ohne Probleme funktionieren wenn man die Designvorlage folgendermaßen aufruft:
php file:
$smarty->assign('LANG_fist_name', $translator->__('first_name'));
$smarty->assign('LANG_last_name', $translator->__('last_name'));
…
$smarty->display('template_filename.tpl', $translator->getLocale());
template_filename.tpl:
<html><body><p>{$LANG_first_name} {$LANG_last_name}</p></body></html>
Auf diese Weise sollte bei eingeschaltetem Caching für jede Sprache eine eigene kompilierte Seite gespeichert werden.
Autor: Flaimo
Datum: 2003-06-13
URLs:
Projektseite
Beispielscript