PHP: Smarty und die Unterstützung mehrerer Sprachen / Multilingualität
Smarty ist die Templateengine für PHP schlechthin. Smarty ist sehr schnell und kann einfach erweitert werden. In diesem Artikel geht es darum, wie man Smarty mehrsprachig betreiben kann bzw. die Templates sprachunabhängig umsetzen kann. Am Ende des Artikels kann der Code heruntergeladen werden.
(Hinweis: In diesem Artikel wird PHP5 verwendet und vorrausgesetzt!)
Mit Smarty und ein paar Tricks ist es im Grunde sehr einfach, ein Projekt mehrsprachig zu gestalten. Dazu liest man alle nötigen Strings aus Sprachdateien aus und fügt sie an den jeweiligen Stellen ein. Freiwillige Übersetzer erstellen dann unterschiedliche Sprachdateien und man kann das Projekt auch in anderen Sprachen veröffentlichen.
Bei Smarty bedient man sich für diesen Zweck eines Prefilters. Ein solcher Prefilter verändert die Templates noch bevor sie von Smarty “kompiliert” werden.
So ist es z.B. möglich, ein Template wie folgt zu gestalten:
<table border="0"> <tr> <td>##user_form_username##</td> <td><input type="text" name="user" value="" /></td> </tr> <tr> <td>##user_form_password##</td> <td><input type="password" name="pass" value="" /></td> </tr> </table>
Die von Rauten (#) umschlossenen Zeichenketten spezifizieren einen bestimmten Sprach-String, dessen Ersetzung in den jeweiligen Sprachdateien angegeben wird. Eine deutsche Sprachdatei könnte etwa so aussehen:
user_form_username=Benutzername user_form_password=Password
Der Prefilter sucht also nach Sprachstrings und fügt an ihrer Stelle die entsprechende Ersetzung ein – je nachdem, welche Sprache eingestellt ist (z.B. in einer serverseitigen Konfigurationsdatei oder innerhalb der übermittelten Browserdaten des Clients usw.).
Für diese Funktionalität empfehle ich eine Klasse, die das Auslesen der Sprachdateien übernimmt. Der Einfachheit nenne ich sie “Lang“.
Ich muss aber darauf hinweisen, dass ich diese Klassen aus einem meiner eigenen Projekte entnommen habe. Daher kann es sein, dass ich teilweise vergessen habe, den Code an diesen Artikel anzupassen. Wenn also irgendetwas von “OBST” oder “XDB” dasteht, wisst ihr Bescheid.
Die Klasse Lang:
<?php /** * @author: Robert Nitsch */ class Lang { public $lng; protected $lng_strings; /** * Lädt alle Sprachstrings aus der entsprechenden Sprachdatei. * @param string $lng Die Sprache, die geladen werden soll, als Länderkürzel. */ function load_lng($lng) { $this->lng = $lng; /* DIR_ROOT muss außerhalb dieser Datei deklariert werden und muss den Pfad zum Hauptverzeichnis des Webprojekts beinhalten Außerdem setzt diese Klasse eine gewisse Verzeichnisstruktur vorraus... (/include/lng) */ $path = DIR_ROOT.'/include/lng/'.$lng.'.txt'; if(file_exists($path)) { $fh = fopen($path, 'r'); $matches = array(); while($zeile = fgets($fh)) { if(preg_match('/^([^;\s]+)\s*=([^;]*).*$/', $zeile, $matches)) { $this->lng_strings[$matches[1]] = $matches[2]; } } } else { throw new Exception("language file '$path' doesnt exist"); } } /** * Gibt einen Sprachstring zurück. * @param string $name Der Name des Sprachstrings. */ function get($name) { return $this->lng_strings[$name]; } }; /* -> singleton pattern (immer nur eine einzige Instanz dieser Klasse zulassen; Instanz(en) werden ausschließlich über diese Funktion erstellt) */ function getLangInstance($language) { static $lang_instance; if(!is_object($lang_instance)) { global $obst; $lang_instance = new Lang(); $lang_instance->load_lng($language); } return $lang_instance; } ?>
Die angepasste Smarty-Klasse nenne ich LangSmarty. Dem Konstruktor muss natürlich eine Instanz der Klasse Lang übergeben werden. Alles Weitere steht in den Kommentaren.
<?php /** * @author: Robert Nitsch */ class LangSmarty extends Smarty { /** * Speichert die Instanz der Klasse Lang ab. * @var Lang Instanz der Klasse Lang */ protected $lang; /** * Konstruktor * @param Lang $lang Eine Instanz der Klasse Lang, die die jeweiligen Sprachdaten beinhaltet. */ function LangSmarty(Lang &$lang) { $this->lang = $lang; // muss an das jeweilige Webprojekt angepasst werden $this->template_dir = './template_dir'; $this->compile_dir = './compile_dir'; $this->cache_dir = './cache_dir'; // den prefilter registrieren $this->register_prefilter(array(&$this, 'translate_prefilter')); } /** * Diese sogenannte Callback-Funktion wird für jedes Auftauchen von ##(...)## innerhalb von Templates * aufgerufen und gibt die jeweilige Ersetzung zurück. * @param string $string Die konkrete Übereinstimmung. Z.B. "##user_form_username##" */ function _translate_callback($string) { $string = substr($string[0], 2, strlen($string[0])-4); return $this->lang->get($string); } /** * Dies ist der Smarty-Prefilter. Er führt die Ersetzung der Sprachstrings ##(...)## * durch die Callback-Funktion _translate_callback() durch. */ function translate_prefilter($tpl_source, &$smarty) { return preg_replace_callback("/##[^#]*##/", array(&$this, '_translate_callback'), $tpl_source); } /** * Jede sprache soll einen eigenen Satz an Compiled- und Cached-Files haben. * Dafür wird das jeweilige Länderkürzel einfach in den Dateinamen dieser Dateien eingebaut. */ function fetch($_smarty_tpl_file, $_smarty_cache_id = null, $_smarty_compile_id = null, $_smarty_display = false) { $_smarty_compile_id = $this->lang->lng.$_smarty_compile_id; $_smarty_cache_id = $_smarty_compile_id; return parent::fetch( $_smarty_tpl_file, $_smarty_cache_id, $_smarty_compile_id, $_smarty_display); } } ?>
Anwendung:
<?php $lang = getLangInstance('de'); // instanz der Lang-Klasse erstellen $smarty = new LangSmarty($lang); // instanz der LangSmarty-Klasse erstellen, dem Konstruktor wird eine Lang-Instanz übergeben $smarty->display('startseite.tpl'); // das Template startseite.tpl kompilieren und das Ergebnis dessen Ausführung (ja, bei Smarty werden Templates ausgeführt) an den Client schicken // Sprachstrings werden automatisch eingefügt, sofern im Template vorhanden ?>
Das Ganze lässt sich aber noch optimieren. Die Lösung mit den oben gezeigten Sprachdateien ist zwar einfach, aber bei großen Projekten kann es in viel Arbeit ausarten alle Sprachdateien aktuell zu halten. Auch wenn man diese Arbeit an freiwillige Übersetzer delegieren kann, so empfiehlt es sich, das System generell zu verbessern.
Dies kann mit Hilfe von gettext erreicht werden.
Zitat Wikipedia:
GNU gettext ist die GNU-Internationalisierungsbibliothek. Normalerweise wird sie zur Entwicklung von mehrsprachigen Programmen genutzt. Die derzeit aktuelle Version ist 0.16.1.
Und ja, auch in PHP kann man auf gettext zugreifen: Gettext-Funktionen
Dank der objektorientieren Programmierung müssen wir nur die Klasse Lang an gettext anpassen. Das werde ich in diesem Artikel aber nicht mehr bis ins Detail erklären. Letztendlich läuft es darauf hinaus, dass man nur ein paar Zeilen verändert (nicht aber wesentliche Strukturen).
Der größte Aufwand steckt allerdings in der Gewöhnung an gettext. Bevor man auf gettext umsteigt, sollte man sich daher etwas einlesen. Mir hat der Text ONLamp.com — Gettext sehr gefallen…
Code herunterladen
Es macht keinen Sinn, den Code direkt aus dem Beitrag herauszukopieren, da verschiedene Zeichen von Wordpress ungünstig umgewandelt werden. Daher hier der Download des ZIP-Archivs mit den Klassen Lang und LangSmarty:
PHP-Smarty-Mehrsprachig.zip herunterladen
Hinweis: Die Dateien sind UTF-8-kodiert. Linux-Benutzer werden damit keine Probleme haben, Windows-Nutzer sollten auf den Notepad++ – Editor zurückgreifen.


