Numeronyme, wo man nur hinschaut

Ich nehme an, den meisten von euch geht es genauso: Wenn man mit den Abkürzungen wie i18n und L10n noch nicht so häufig konfrontiert wurde, fühlt man sich anfangs erst einmal etwas verloren. Liest man dann einen Text, in dem der Autor fast beiläufig weitere dieser Numeronyme wie g11n, a11y oder p13n einfließen lässt, kommt schnell die Vermutung auf, hier würde sich jemand auf unsere Kosten amüsieren wollen. Dabei handelt es sich rein schreibtechnisch um Begriffe, die nur mit ihrem Anfangs- und Endbuchstaben geschrieben werden und wo alle weiteren Buchstaben durch die Anzahl derselben ersetzt werden.

Für diesen Artikel sind die Numeronyme i18n und L10n wichtig. i18n steht für Internationalization (Internationalisierung). Mit L10n wird der Begriff Localization (Lokalisierung) abgekürzt. Es wird im Gegensatz zu den anderen Numeronymen mit einem großen L geschrieben, um Verwechslungen zu vermeiden. Beide werden seit einiger Zeit sehr gern zu g11n,  sprich Globalization (Globalisierung), zusammengefasst.

Das von den eingangs erwähnten Numeronymen noch Accessibility (Barrierefreiheit) und Personalization (Personalisierung) fehlt, bekommt man mit etwas Suche im Netz auch schnell heraus, alle Begriffe haben vor allem eines gemeinsam: Ohne weitere Erklärungen ist man noch nicht viel schlauer! Um zu erklären, was die Internationalisierung und die Lokalisierung unterscheidet, gibt es einiges an Material, aber wenn es kurz und einprägsam sein soll:

Wenn die i18n abgeschlossen ist, kann man mit der Übersetzung der Software beginnen.

Wenn die L10n abgeschlossen ist, dann ist die Übersetzung durchgeführt.

Im Normalfall beschäftigen sich also der Softwareentwickler mit der i18n und das Arbeitsfeld des Übersetzers ist die L10n. Aber auch wenn sich der Artikel hier mehr an Theme-Entwicklerinnen und -Entwickler richtet, ist es wichtig, genug über die L10n zu wissen, um dem Übersetzer im Zweifel helfen zu können. Oftmals geht es nicht um technische Unterstützung, sondern um das Verständnis des Kontexts. Mehr dazu aber später im Artikel.

Was ist denn eine Textdomain, bitte?

Wie musst man sein Theme darauf vorbereiten, dass es sich an andere Sprachen anpassen und schließlich die Übersetzungen anstelle der originalen Zeichenketten ausgeben kann? WordPress nutzt die gettext-Bibliothek und deren Werkzeuge. Es ist recht interessant und meiner Meinung nach auch empfehlenswert, sich auch auf dieser Ebene mit der Thematik auseinanderzusetzen, aber das würde den Rahmen dieses Beitrags sprengen.

In jedem Fall lässt sich mit diesem Wissen als Grundlage erklären, was eine Textdomain ist und wozu sie notwendig ist. Damit WordPress die geladenen Übersetzungen beim Ausführen der verschiedenen Anweisungen auch unterscheiden kann, braucht es eine eindeutige Zeichenkette. Man kann tatsächlich auch die Ausgabe anderer Textdomains (von Plugins beispielsweise) verwenden, aber die Nützlichkeit dieser Funktionalität hängt natürlich immer vom konkreten Einzelfall ab.

Falls ihr euer Theme im WordPress-Theme-Verzeichnis veröffentlichen wollt, sind außerdem die folgenden Regeln für die Namensgebung der Textdomain wichtig, um beim Theme Check des Review-Teams nicht durchzufallen. Aber auch wenn ihr „nur“ Themes speziell für eure Kunden erstellt, kann es nicht schaden, sich diese Vorgehensweise anzugewöhnen:

/**
* Theme Name: Mein Theme
* Author: Dennis Ploetner
* Text Domain: mein-theme
* Domain Path: /lang
*/Code-Sprache: PHP (php)

Der Name des Theme-Verzeichnisses und die Textdomain sollten übereinstimmen. Kleinschreibung wird erwartet. Minuszeichen sind erlaubt, Unterstriche nicht. Die Textdomain muss auch im Kopfbereich der style.css vermerkt sein, damit WordPress, auch wenn ein Theme noch nicht aktiv ist, die zugehörigen Metadaten lokalisiert ausgeben kann. Die Sprachdateien werden im Ordner /languages erwartet. Falls es Gründe gibt, einen anderen Namen zu wählen, kann das ebenfalls in der style.css vermerkt werden.

Seit der WordPress 4.6 werden Sprachdateien für Themes, die über WordPress.org verbreitet werden, bevorzugt von translate.wordpress.org geladen. Hierbei handelt es sich um eine riesige Plattform auf der die ehrenamtlichen Übersetzer, die ihr eventuell schon als Polyglots kennengelernt habt, die Lokalisierung von WordPress selbst und der frei verfügbaren Plugins und Themes durchführen.

In neueren Versionen von WordPress muss man die Textdomain also eigentlich gar nicht mehr explizit laden. Allerdings schadet es nicht, wenn man die Textdomain trotzdem manuell lädt, und für kostenpflichtige oder speziell für Kunden erstellte Themes, die diesen Mechanismus nicht nutzen können, ist dieser Schritt nach wie vor notwendig, damit man überhaupt eine lokalisierte Ausgabe erhalten kann.

function kp_load_theme_textdomain() {
	load_theme_textdomain( 'mein-theme', get_template_directory() . '/lang' );
}

add_action( 'after_setup_theme', 'kp_load_theme_textdomain' );Code-Sprache: PHP (php)

Die Basics – __() und _e()

Die erste Hürde ist also genommen. Zeit, über die Texte zu sprechen, die von der WordPress-Installation ausgegeben werden. Schaltet man in der Admin-Oberfläche die Sprache um, verändert sich die Ausgabe drastisch. Selbst die Darstellung der Seiten, die ein Besucher angezeigt bekommt, sollte das teilweise reflektieren. „Teilweise“ , weil ein Teil der Inhalte natürlich weiterhin aus der Datenbank kommt und die dort gespeicherten Inhalte – wie Seiten, Kategorien, Menüs usw. – nicht automatisch übersetzt werden.

Uns interessiert hier die i18n von Themes, also der Teil der Zeichenketten, der sich direkt in den Templates befindet. Das direkte Hineinschreiben von deutschen Sätzen und Beschriftungen von Buttons in die Dateien der Themes ist ja spätestens seit heute vorbei, nicht wahr?

__( 'Translate me', 'mein-theme' );Code-Sprache: PHP (php)

Eine Funktion, die man vermutlich am häufigsten in Themes antrifft, ist __(). Sie liefert die Übersetzung einer Zeichenkette für die momentan eingestellt Sprache. Wird keine passende Übersetzung gefunden, erhält man die originale Zeichenkette wieder. Da WordPress ohne konkrete Spracheinstellung mit amerikanischem Englisch startet, sollten die Zeichenketten das wiederspiegeln und eben keine deutschen Sätze und Ausdrücke enthalten. Englisch ist oft auch die Sprache, die die meisten Übersetzer gut versteht und aus der sie den Sinn der Botschaften gut in ihre jeweiligen Zielsprachen übertragen können.

_e( 'Click here', 'mein-theme' );Code-Sprache: PHP (php)

Soll die Zeichenkette ohne Umwege direkt ausgegeben werden, bietet sich der Einsatz von _e() an. Hierbei handelt es sich um eine komfortable Erweiterung der Funktion __().

Bisher habe ich immer nur ganz allgemein Zeichenketten benannt. Einige sind vermutlich Beschriftungen für Buttons, andere sind vielleicht fixe Sätze für den Dialog mit dem Besucher. Ein Teil der Kommunikation wird aber vermutlich dynamische Elemente enthalten und hier können sich tatsächlich einige unschöne Fehler einschleichen. Als Regel kann man sich hier merken, dass man Übersetzer mit HTML und mit Variablen verschont.

printf( __( 'Howdy, %s!', 'mein-theme' ), $username );Code-Sprache: PHP (php)

Lediglich Platzhalter wie %s und %d, wie sie beispielsweise von printf() und sprintf() verwendet werden, sind akzeptabel. Sollen in euren Sprachkonstrukten mehrere Platzhalter enthalten sein, ist die Kennzeichnung der Position Pflicht. Dies macht man, indem man den ersten String mit %1$s und den zweiten mit %2$s ausweist. Denkt immer daran, dass manche Sätze in einigen Sprachen komplett umgestellt sein könnten und eine fixe Reihenfolge der Parameter ein ernsthaftes Hindernis darstellen oder ein Grund für Frustration bei der Übersetzung sein könnten.

printf( __( 'Nice %1$s %2$s!', 'mein-theme' ), $brand, $model );Code-Sprache: PHP (php)

Das ist im Übrigen einer von vielen Gründen, warum die WordPress-Coding-Standards einfache Anführungszeichen für Zeichenketten vorziehen, weil doppelte Anführungszeichen zu einer Ersetzung durch den PHP-Interpreter führen würden, was in diesem Fall nicht beabsichtigt ist. Falls ihr euch nun fragt, wie man dann Zeilenumbrüche in den Strings unterbringen kann, muss ich sagen, dass die sowieso keine gute Idee darstellen, weil gettext mit bestimmten ASCII-Codes auf Kriegsfuß steht.

„Ein Suchergebnisse” _n() gibt die richtige Antwort

Neben __() und _e() ist _n() ebenfalls eine sehr häufig anzutreffende Funktion. Immer dann, wenn sich anhand eines numerischen Wertes feststellen lässt, ob Singular oder Plural verwendet werden soll, gibt nur _n() oder eine verwandte Funktion  die richtige Antwort.

In vielen Beispielen – auch auf den Seiten des Handbuchs für Theme Entwickler – sieht man wie _n() häufig mit printf() zusammen verwendet wird. Die Syntax wirkt dann am Ende oft irgendwie konfus. Man kann jedoch sagen, dass _n() selbst nur den unformatierten Wert braucht, um zu entscheiden welche Version verwendet werden soll, während printf() sich um eine formatierte Ausgabe bemüht und nicht selten eine der Hilfsfunktionen wie number_format_i18n() braucht, um ein annehmbares Ergebnis zu erzielen.

printf( _n( '%s star', '%s stars', $rating, 'mein-theme' ), $rating );Code-Sprache: PHP (php)

Manchmal ist es wichtig, dass man Singular und Plural strukturiert bereitstellt, ohne dass man bereits entscheiden muss, welche der beiden Formen zur Anwendung kommt. Die Zeichenketten können so in die Übersetzung aufgenommen werden und unser Theme kann zur Ausführung entscheiden, was schließlich ausgegeben wird. Ein konkreter Fall wäre die Tagwolke, wo zu jedem Begriff im Title-Attribut der Links „1 Thema“ oder „2 Themen“ usw. steht.

$message = _n_noop( '%s post', '%s posts', 'mein-theme' );
printf( translate_nooped_plural( $message, $count, 'mein-theme' ), number_format_i18n( $count ) );Code-Sprache: PHP (php)

Hierfür gibt es die Funktion _n_noop(), die sich um den ersten Teil des gerade eben beschriebenen Szenarien kümmert, während translate_nooped_plural() dann jeweils die richtige Auswahl vornimmt.

Sei nett, kommentiere und erkläre den Kontext!

Werden die Projekte größer, wird es oft wichtig, den Kontext anzugeben, in dem eine Zeichenkette ausgegeben wird. WordPress stellt daher für alle vorher genannten Funktionen jeweils eine Variante zur Verfügung, der man den Kontext mittels _x(), _ex(), _nx() und _nx_noop() übergibt.

In der deutschen Sprache kann man ein Substantiv an der Großschreibung erkennen, steht „Rasen“ aber am Anfang eines Satzes, ergibt sich der Kontext nur in der Bedeutung mit dem Rest des Satzes. Im Englischen wird die Mehrzahl der Wörter im Satz aber klein geschrieben und Begriffe wie zum Beispiel „post“ können sich auf das Substantiv oder Verb beziehen.

$string = _x( 'post', 'noun', 'mein-theme'); echo $string;
_ex( 'post', 'verb', 'mein-theme');Code-Sprache: PHP (php)

Ein zweiter Anwendungsfall ist immer dann gegeben, wenn sich mit der Ausgabe durch die Position im Template verändern muss. In einer Zusammenfassung kann ein Begriff dann komplett ausgeschrieben werden, während sich in der Kopfzeile einer Tabelle eine Abkürzung anbietet.

Ein weiteres Hilfsmittel, mit dem man sich die Dankbarkeit der Übersetzer sichern kann, ist das Kommentieren der Zeichenketten direkt vor dem Aufruf aller im Artikel genannten Funktionen. Leitet man den Kommentar mit „translators:“ ein, wird dieser automatisch in die Übersetzungsdateien übernommen und je nach Programm, mehr oder minder prominent angezeigt. Die Nachfragen nach der Bedeutung von „from %s“ oder den Werten für „in %s (%s)“ kann man nur so drastisch reduzieren.

Funktionen, die das Bild komplettieren

Es gibt einige Funktionen, die das ganze Thema abrunden und oft entweder gar nicht genannt oder eben (wie hier) nur am Rande erwähnt werden. Man sollte sich trotzdem mit ihnen auseinandersetzen, weil man eben keine hundertprozentiges Benutzererlebnis ohne sie garantieren kann.

Im Bereich JavaScript hat sich einiges getan und hier wird bald noch mehr Erklärungsbedarf bestehen. Aber wer am Ende bei seinen Besuchern nicht deshalb durchfallen möchte, weil die Ausgabe des JavaScripts nicht übersetzt war, der sollte sich mindestens die Funktionsweise von wp_localize_script() genau ansehen.

Weil wir gerade vorher noch beim Einsetzen von Werten waren: Oft sind es ja auch Zahlen, Preise oder Datumsangaben, die dort automatisch eingesetzt werden. Je nach Land und Sprache ist es oft eben nicht egal, ob die Ausgabe mit der angelsächsischen Formatierung gemacht wird. Von den möglichen Missverständnissen gar nicht zu sprechen, die bei der Ausgabe eines Datums im Format m/d/Y entstehen können. Der rigorose Einsatz der Funktionen number_format_i18n() und date_i18n() ist die Lösung für diese Art von Problemen.

Theoretisch kann ein Übersetzer HTML oder sogar JavaScript in die Übersetzungen einarbeiten. Auch wenn das eigentlich sowieso nicht erwünscht sein sollte, kann man mit den Funktionen esc_html__(), esc_html_e() und esc_html_x() entsprechende Vorsichtsmaßnahmen ergreifen. Mit den Funktionen esc_attr__(), esc_attr_e() und esc_attr_x() kann man auch die Ausgabe in den HTML Attributen sicherstellen.

Last but not least

Es gibt einige Möglichkeiten, wie man schließlich eine POT (Portable Object Template) Datei erstellen kann, mit der ein Übersetzer dann starten kann. Neben Projekten wie den Improved i18n WordPress Tools gibt es auch die Möglichkeit, sich das Leben mit einem Blank WordPress Pot zu erleichtern. Es handelt sich hierbei um eine ganz hervorragende Möglichkeit, etwas mehr über den Mechanismus selbst zu lernen.

Man muss lediglich einen Ordner /languages erstellen, die POT Datei des Repository als {$textdomain}.pot (also beispielsweise mein-theme.pot) dorthin kopieren und diese dann mit dem PoEditor öffnen. Nachdem man die Properties wie den eigenen Namen, E-Mail usw. angepasst hat, klickt man auf den Update-Button, der den Scan nach den zu übersetzenden Zeichenketten startet. Beim Speichern fragt der PoEditor vermutlich, ob er eine MO (Machine Object) Datei erstellen soll, was man verneinen kann.

An dieser Stelle hat man als Theme Entwickler/-in seine Arbeit beendet, was die Internationalisierung betrifft. Von nun an nimmt der Übersetzer seine Arbeit auf und ist hoffentlich mit wenigen bis keinen Problemen konfrontiert, um dann schließlich die Datei als {locale.po} (also beispielsweise de_DE.po) zu speichern und gleichzeitig eine kompilierte MO-Datei zu erhalten. Für WordPress ist nur die MO-Datei wichtig. Die PO-Datei gibt uns jedoch die Möglichkeit auch bei zukünftigen Änderungen im Theme auf Vorhandenem aufbauen zu können.