In vielen unserer Projekte ist für den Kunden Mehrsprachigkeit ein sehr wichtiges Thema. Sei es bei einer App, die in verschiedensprachigen AppStores veröffentlicht werden soll, bei einer Intranet Anwendung, die für Zweigstellen in verschiedenen Ländern bereitgestellt wird oder bei der neuen Firmenhomepage, die in mehreren Sprachen abrufbar sein soll.
Während manche unserer eingesetzten Frameworks & CMS-Systeme von Haus aus eine sehr gute Unterstützung von Mehrsprachigkeit mitbringen (z.B. die App-Entwicklungsumgebungen von Android und iOS), gilt es bei nativen PHP Projekten selbst für eine sinnvolle Integration verschiedener Sprachen zu Sorgen.
Diese Integration von Mehrsprachigkeit in einem Software-Projekt erfordert eine gute Planung und konsequente Umsetzung des Features von Beginn an. Nur so lässt sich vermeiden, dass man am Ende der Entwicklung eine Codebasis steht, bei der die Mehrsprachigkeit unwartbar und tief im Code versteckt ist.
Am Beispiel unseres oft eingesetzten hauseigenen PHP Frameworks möchte ich vorstellen, wie wir die Mehrsprachigkeit sinnvoll in das bestehende System integriert haben.
Mehrsprachigkeit mit PHP
Der naive Implementierungsansatz ist es, die verschiedenen Sprachen per switch oder if-else Konstrukte direkt bei der jeweiligen Codestelle abzufragen und entsprechend auszugeben:
if($config::getCurrentLanguage() == Config::ENGLISH) { echo "Hello"; } if($config::getCurrentLanguage() == Config::SPANISH) echo "Ola"; } else { echo "Hallo"; }
Dieser Ansatz hat aber einige gravierende Nachteile:
- Keine zentrale Stelle zum Editieren des Texts
- Hinzufügen einer neuen Sprache erfordert, dass alle Stellen im Code gefunden und angefasst werden
- Die Programmlogik wird durch eine große Menge von „unnötigen“ if-else Konstrukten aufgebläht und der Code wird immer schwerer wartbar
Das grundlegende Ziel der Implementierung der Mehrsprachigkeit sollte deshalb eine gekonnte Trennung der Codebasis und der tatsächlichen Übersetzung sein. PHP bringt mit der Unterstützung von GNU gettext schon eine relativ elegante Möglichkeit mit, diese Trennung vorzunehmen.
Um gettext zu benutzen, werden alle Textausgaben innerhalb des Quellcodes in einen gettext Aufruf gekapselt:
echo "Das ist ein toller Tag!";
wird zu
echo gettext("Das ist ein toller Tag!"); /*Alternativ kann auch _ statt gettext verwendet werden*/ echo _("Das ist ein toller Tag!");
Beim Aufruf schaut gettext nun in den bereitgestellten Übersetzungsdateien nach, ob eine entsprechende Übersetzung vorhanden ist. Falls ja, wird der übersetzte Text ausgegeben, ansonsten der Original-String.
Die Übersetzungsdateien sind sogenannte .po (Portable Object) Dateien, welche folgendermaßen aufgebaut sind:
msgid "Das ist ein toller Tag!" msgstr "It's a wonderful day!"
msgid
ist dabei der eindeutige Identifikator des zu übersetzenden Strings (Es werden statt der sprechenden Standardübersetzung auch gern abstrakte Identifier wie WELCOME_TO_PAGE_GOOD_DAY
verwendet. Der Programmierer muss selbst entscheiden, ob es sinnvoller ist, dass diese abstrakten Identifier oder der Text in der Ursprungssprache zurück gegeben werden, sollte keine Übersetzung gefunden werden.).
Hinter dem Schlüsselwort msgstr
folgt nun die jeweilige Übersetzung für die unterstützten Sprachen. Es muss für jede unterstützte Sprache ein eigene solche .po angelegt werden. Diese Dateien werden danach mit dem Konsolenkommando msgfmt
in binäre .mo (Machine Objekt) Dateien umgewandelt.
PHP entscheidet dann auf Basis der gesetzten Locale
welche Übersetzung gebraucht wird und versucht das passende .mo zu finden und den entsprechenden String auszulesen.
Unterstützung der Template Engine Smarty
Unser selbstentwickeltes PHP-Framework setzt auf eine konsequente Trennung von Darstellung und Logik. Wir setzen deshalb die Template Engine Smarty ein, um diese Trennung zu erreichen. Smarty benutzt dabei zur Darstellung Template Dateien (HTML angereichert um Steuerungsanweisungen), welche durch eine korrespondiere PHP Datei befüllt werden.
/*Beispiel eines Smarty Templates, Steuerungsanweisungen werden durch { } geskapselt*/ <table> {foreach $persons as $person} <tr> <td>{$person->getName()}</td> <tr> </table>
Da der Text nun in diesem Template File und nicht mehr in PHP selbst stehen, müssen wir auf ein Smarty Plugin zurückgreifen, welches die gettext-Abfragen übernimmt. Nach intensiver Suchen und ausführlichen Tests hat sich das Plugin smarty3-i18n als beste Alternative heraus kristallisiert.
Eine einfache gettext-Anweisung sieht mit diesem Plugin in Smarty wie folgt aus:
{t}Bitte geben Sie Ihren Namen ein:{/t}
Übergabe von Variablen
Oft ist der Text bei Anwendungen nicht komplett statisch, sondern abhängig vom aktuellen Zustand der Anwendung. Ein einfaches Beispiel dafür ist eine Willkommensnachricht für den User nach dem Login, bei dem der Benutzer mit seinem Namen begrüßt wird. Es ist dabei nötig, den Text in den Übersetzungsdateien mit dem Namen des Users aus der Datenbank anzureichern.
Dazu ist es möglich, Ersetzungsvariablen innerhalb der Übersetzungen zu definieren. Im unseren einfachen Beispiel würde das so aussehen:
/*Die po-Datei*/ msgid "Willkommen auf der Webseite, %1 %2" msgstr "Welcome to the website, %1 %2"
/*Smarty Template, die Variable $user wird aus PHP gefüllt*/ {t firstname=$user->getFirstname() lastname=$user->getLastname()}Willkommen auf der Webseite, %1 %2{/t}
Bei der Ausgabe des Smarty Templates wird dann automatisch der Text um den Namen des Benutzers ergänzt und ausgegeben. Die Bezeichnung der Variablen ist dabei egal, es zählt bei der späteren Ersetzung nur die Reihenfolge mit der die Variablen übergeben werden.