CakePHP I18n & l10n
9 05 2008Internationalisierung von Internet-Anwendungen ist ein wichtiges Thema, auf das schon zu Beginn der Entwicklung ein Schwerpunkt gelegt werden sollte. Zwar ist nicht bei jeder Internetpräzens Internationalisierung notwendig, die Seiten sollte jedoch mindestens in Englischer Version vorliegen. Bei Internetanwendungen ist Internationalisierung absolut notwendig, möchte man nicht einen Großteil der potentiellen Nutzer ausschließen.
Zuerst einmal sollten die zwei wichtigen Begriffe Internationalisierung und Lokalisierung geklärt werden.
Unter Internationalisierung (am. engl. internationalization Abk. i18n, wobei 18 für die 18 Buchstaben zwischen ersten und letztem Buchstaben steht) versteht man die technische Fähigkeit einer Anwendung, sich leicht in andere Sprachen/Sprachräume/Kulturen überführen zu lassen. Diesen Vorgang der Überführung an die lokalen Gegebenheiten nennt man Lokalisierung.
Unter Lokalisierung (am. engl. localization Abk. l10n) verseht man die Überführung von Sprache, Zeit/Datumsstempel, Farben, Formatierungen usw. in einen anderen Sprachraum oder Kultur. Lokalisierung setzt (nicht zwingend) Internationalisierung vorraus.
Mit CakePHP 1.2 haben wir bereits ein weitesgehend internationalisiertes Werkzeug zur Hand. Alle Angaben beziehen sich auf CakePHP 1.2.0.6311.
Struktur
- LC_CTYPE
- LC_NUMERIC
- LC_TIME
- LC_COLLATE
- LC_MONETARY
- LC_MESSAGES
- LC_ALL
.
In der Regel kommt man LC_MESSAGES aus. In nächster Zeit werden hoffentlich der TimeHelper und andere kritische Komponenten von CakePHP I18n. Dann könnten auch LC_TIME und der Rest eine Rolle spielen. Unterhalb der Categories befinden sich die Sprachdateien, wir werden sie als Domänen bezeichnen. Die Standard- und Fallback-Domäne ist default.mo bzw. default.po. Domänen werden im Portable-Object-Format (.po) bzw. in binärer Form im Maschine-Object-Format (.mo) gespeichert. Ich persönlich finde .mo’s unpraktisch, da sie das Editieren erschweren.
Der Pfad sieht also so aus: app/locale/{3-Stelliger-Sprachcode oder Dialekt wie de_at, de usw.}/{Kategoriename, LC_MESSAGES etc.}/{default.po, core.mo usw.}
Sie sind eher für Desktop-Anwendungen gedacht. Der Aufbau einer .po-Datei ist sehr simpel. Zunächst benötigt die Datei einen Header.
msgid "" msgstr "" "Plural-Forms: nplurals=2; plural=n==0?0:(n==1?1:2);\n" "Project-Id-Version: 123 Projekt\n" "Last-Translator: Steffen Görtz <steffen.goertz@multicept.de>\n" "Language-Team: Multicept Gbr <info@multicept.de>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n"
Nach dem Header (der nicht zwingend vorhanden sein muss) folgen die Übersetzungen der einzelnen Strings
msgid "untranslated-string" msgstr "translated-string"
(mehr Infos zum .po-Format gibt es in der offiziellen GNU-gettext Dokumentation)
Besondere Bedeutung hat der Header “Plural-Forms“. Er gibt an, wieviele Pluralformen in der Zielsprache vorkommen können und wann welche Form zu wählen ist. Im Beispiel (für Deutsch) folgt zuerst mit “nplurals=%d;” die Anzahl der Pluralformen in der zu übersetzenden Sprache, hier 2. Danach folgt ein C-Ausdruck in Abhängigkeit von n, dessen Ergebnis bestimmt, welcher Plural in Frage kommt.
An dieser Stelle sei leider gesagt, dass CakePHP in der aktuellen Version keinen validen Automaten implementiert, der jeden möglichen Ausdruck auswerten kann. Es können daher nur bestimmte Ausdrucke verwertet werden; zumindest ein paar die sich auf der GNU-gettext Dokumentation befinden. Ich habe bereits ein Ticket eröffnet, mal sehen wie Gwoo und Co. reagieren werden.
Die Plurale werden in der .po-Datei so angelegt
msgid "Kommentar" msgid_plural "%d Kommentare" msgstr[0] "Keine Kommentare" msgstr[1] "Ein Kommentar" msgstr[2] "%d Kommentare"
Die .po-Dateien lassen sich per Hand oder komfortabler mit freien Werkzeugen, wie etwa poEdit, bearbeiten.
Es lassen sich beliebig viele Domänen anlegen und verwalten. In der Regel wird man mit einer auskommen, es sein denn man hat Komponenten mit sehr vielen Übersetzungen. In diesem Fall sollte man über eine Domäne nur für diese Komponente nachdenken.
CakePHP I18n Funktionen
Wie bereits gesagt kommt CakePHP mit allen benötigten Werkzeugen frei Haus. Folgende Funktionen sind überall verfügbar (werden in basics.php definiert):
/** * Returns a translated string if one is found, or the submitted message if not found. * * @param string $singular Text to translate * @param boolean $return Set to true to return translated string, or false to echo * @return mixed translated string if $return is false string will be echoed */ function __($singular, $return = false) ;
Die simpelste Funktion. Es werden lediglich Singulare unterstützt.
/** * Returns correct plural form of message identified by $singular and $plural for count $count. * Some languages have more than one form for plural messages dependent on the count. * * @param string $singular Singular text to translate * @param string $plural Plural text * @param integer $count Count * @param boolean $return true to return, false to echo * @return mixed plural form of translated string if $return is false string will be echoed */ function __n($singular, $plural, $count, $return = false) ;
Mit __n(…) können Ausdrücke wie Keine Kommentare, Ein Kommentar, x Kommentare übersetzt werden. Anhand von $n wird die richtige Form ausgewählt.
/** * Allows you to override the current domain for a single message lookup. * * @param string $domain Domain * @param string $msg String to translate * @param string $return true to return, false to echo * @return translated string if $return is false string will be echoed */ function __d($domain, $msg, $return = false) ;
Holt eine einfache Nachricht aus einer anderen Domäne als der derzeitg gewählten. Sowas ist für Komponenten nützlich.
/** * Allows you to override the current domain for a single plural message lookup * Returns correct plural form of message identified by $singular and $plural for count $count * from domain $domain * * @param string $domain Domain * @param string $singular Singular string to translate * @param string $plural Plural * @param integer $count Count * @param boolean $return true to return, false to echo * @return plural form of translated string if $return is false string will be echoed */ function __dn($domain, $singular, $plural, $count, $return = false);
Plurale Übersetzung für andere Domäne.
/** * Allows you to override the current domain for a single message lookup. * It also allows you to specify a category. * * The category argument allows a specific category of the locale settings to be used for fetching a message. * Valid categories are: LC_CTYPE, LC_NUMERIC, LC_TIME, LC_COLLATE, LC_MONETARY, LC_MESSAGES and LC_ALL. * * Note that the category must be specified with a numeric value, instead of the constant name. The values are: * LC_CTYPE 0 * LC_NUMERIC 1 * LC_TIME 2 * LC_COLLATE 3 * LC_MONETARY 4 * LC_MESSAGES 5 * LC_ALL 6 * * @param string $domain Domain * @param string $msg Message to translate * @param integer $category Category * @param boolean $return true to return, false to echo * @return translated string if $return is false string will be echoed */ function __dc($domain, $msg, $category, $return = false);
Singulare Übersetzung einer anderen Domäne und Kategorie
/** * Allows you to override the current domain for a single plural message lookup. * It also allows you to specify a category. * Returns correct plural form of message identified by $singular and $plural for count $count * from domain $domain * * The category argument allows a specific category of the locale settings to be used for fetching a message. * Valid categories are: LC_CTYPE, LC_NUMERIC, LC_TIME, LC_COLLATE, LC_MONETARY, LC_MESSAGES and LC_ALL. * * Note that the category must be specified with a numeric value, instead of the constant name. The values are: * LC_CTYPE 0 * LC_NUMERIC 1 * LC_TIME 2 * LC_COLLATE 3 * LC_MONETARY 4 * LC_MESSAGES 5 * LC_ALL 6 * * @param string $domain Domain * @param string $singular Singular string to translate * @param string $plural Plural * @param integer $count Count * @param integer $category Category * @param boolean $return true to return, false to echo * @return plural form of translated string if $return is false string will be echoed */ function __dcn($domain, $singular, $plural, $count, $category, $return = false);
Plurale Übersetzung einer anderen Domäne und Kategorie.
/** * The category argument allows a specific category of the locale settings to be used for fetching a message. * Valid categories are: LC_CTYPE, LC_NUMERIC, LC_TIME, LC_COLLATE, LC_MONETARY, LC_MESSAGES and LC_ALL. * * Note that the category must be specified with a numeric value, instead of the constant name. The values are: * LC_CTYPE 0 * LC_NUMERIC 1 * LC_TIME 2 * LC_COLLATE 3 * LC_MONETARY 4 * LC_MESSAGES 5 * LC_ALL 6 * * @param string $msg String to translate * @param integer $category Category * @param string $return true to return, false to echo * @return translated string if $return is false string will be echoed */ function __c($msg, $category, $return = false);
Singulare Übersetzung einer anderen Kategorie
Sprachen konfigurieren und I18n einsetzen
Zunächst sollte man die Standardsprache seiner Anwendung einstellen. Hierzu definieren die Konstante DEFAULT_LANGUAGE in unserer App-Config.
app/config/bootstrap.php
/* SVN FILE: $Id: bootstrap.php 6311 2008-01-02 06:33:52Z phpnut $ */ /** * Short description for file. * * Long description for file * * PHP versions 4 and 5 * * CakePHP(tm) : Rapid Development Framework * Copyright 2005-2008, Cake Software Foundation, Inc. * 1785 E. Sahara Avenue, Suite 490-204 * Las Vegas, Nevada 89104 * * Licensed under The MIT License * Redistributions of files must retain the above copyright notice. * * @filesource * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project * @package cake * @subpackage cake.app.config * @since CakePHP(tm) v 0.10.8.2117 * @version $Revision: 6311 $ * @modifiedby $LastChangedBy: phpnut $ * @lastmodified $Date: 2008-01-02 00:33:52 -0600 (Wed, 02 Jan 2008) $ * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ /** * * This file is loaded automatically by the app/webroot/index.php file after the core bootstrap.php is loaded * This is an application wide file to load any function that is not used within a class define. * You can also use this to include or require any files in your application. * */ /** * The settings below can be used to set additional paths to models, views and controllers. * This is related to Ticket #470 (https://trac.cakephp.org/ticket/470) * * $modelPaths = array('full path to models', 'second full path to models', 'etc...'); * $viewPaths = array('this path to views', 'second full path to views', 'etc...'); * $controllerPaths = array('this path to controllers', 'second full path to controllers', 'etc...'); * */ Configure::load('testapp'); //EOF ?>
app/config/testapp.php
// Spracheinstellungen # Standardsprache define('DEFAULT_LANGUAGE','deu');# Sprache erzwingen #Configure::write('Config.language','de'); $config['myapp'] = Configure::read('myapp'); ....
Zunächst definieren wir unsere Standardsprache in 3-Stellen-Notation. Diese wird ausgewählt, wenn die bevorzugte Sprache des Benutzers nicht über HTTP_ACCEPT_LANGUAGE festgestellt werden kann oder keine dieser Sprachen verfügbar ist. Danach können wir eine Sprache erzwingen. Diese Funktion kann man nutzen, wenn man ein Menu mit Sprachauswahl erstellen möchte. Soll eine ausgewählte Sprache dauerhaft erhalten bleiben kann man diese als Sessionvariable Config.language speichern.
MyController extends AppController { function setLanguage($lang) { $this->;Session->write('Config.language',$lang); } } ?>
Viel Spaß beim ausprobieren!







Danke für diesen Artikel, hat mir sehr geholfen!
[...] wie die i18n Features von CakePHP richtig eingebunden werden können, gibt es bei gouranga | CakePHP I18n & l10n ein gut geschriebenes und relativ umfassendes [...]
Wie macht man das, wenn man nur die Zeit umstellen will, von Englisch auf Deutsch ?