Inhalt
Übersetzungen mit i18n für php Scripts in WordPress konnte man schon bisher recht einfach mit den bekannten gettext-tools erledigen. Dafür musste lediglich eine Text-Domain definiert und die nötigen po-Files erzeugt, übersetzt und die mo-Files geladen werden.
Die Erzeugung der po- und mo-Files erledigt unter Linux sehr komfortabel Poedit, dass die zu übersetzenden String automatisch aus den Quellen lesen kann.
Danach kann man mit den üblichen Tools wie __(), _x(), _n(), _nx() und sprintf() Übersetzungen in die eigenen php-Skripte einbauen.
Viel Arbeit bei Javascript
Bei Javascript sah das andes aus. Zwar gab es auch dort schon immer eine Möglichkeit, die Scripts zu übersetzen, aber die war im Vergleich sehr unkomfortabel.
Tool der Wahl war bisher die Funktion wp_localize_script(), mit der man ein Array mit Übersetzungen an das Script übergeben konnte. Um das Zusammensammeln der zu übersetzenden Texte musste man sich aber selbst kümmern, ebenso wie um die eigentliche Übersetzung mit den gettext-Tools im php.
Praktisch sah das dann wie folgt aus. Im php-Script:
load_plugin_textdomain('myTextDomain', false, <i18n-dir>); $i18n = [ 'apples' => __('apples', 'myTextDomain'), 'pears' => __('pears', 'myTextDomain'), 'bananas' => __('bananas', 'myTextDomain'), 'plums' => __('plums', 'myTextDomain'), 'you compare' => __('you compare', 'myTextDomain); 'and' => __('and', 'myTextDomain'); }; wp_register_script('myScript', plugins_url('myScript.js', __FILE__)); wp_localize_script('myScript', 'i18n', $i18n); wp_enqueue_script('myScript');
Die eigentlichen Übersetzungen müssen im entsprechenden po/mo-Files im Verzeichnis <i18n-dir> natürlich eingetragen sein. Die po-Dateien sind nach der Text-Domain und dem Locale benannt:
<Text-Domain>-<locale>.mo
Also z.B. bei der deutschen Übersetzung:
myTextDomain-de_DE.mo
Poedit sammelt die zu übersetzenden Strings automatisch ein, wenn die Source-Files im Katalog entsprechend definiert sind. Ein Eintrag in der po-Datei sieht in etwa so aus:
# @ myTextDomain #: myScript.php:3 msgid "apples" msgstr ""
Hat man im Poedit die Übersetzung eingetragen, steht in der Datei myTextDomain-de_DE.po dann:
# @ myTextDomain #: myScript.php:3 msgid "apples" msgstr "Äpfel"
Im Javascript (myScript.js) sieht es dann so aus:
var translated = i18n['you compare'] + i18n['apples'] + i18n['and'] + i18n['pears'];
Hauptproblem dieser Vorgehensweise ist die statische Erzeugung des localization-Arrays. Bei jeder Änderung der zu übersetzenden Texte im Javascript muss auch im php dieses Array angepasst werden. Fehler sind dabei fast unausweichlich.
Hilfe durch Gutenberg
Über den neuen Gutenberg-Editor mag man denken, was man will; in Sachen i18n-Nutzung in Javascript hat er eindeutig Fortschritte gebracht. Der Editor selbst läuft großenteils in Javascript. Dadurch hat das Entwicklungsteam erkannt, dass in Sachen Javascript-Internationalisierung dringender Handlungsbedarf bestand und entwickelte das neue wp-i18n Paket.
Es bildet die Arbeitsweise mit den gettext-Tools im php fast identisch in Javascript ab, und zwar inklusive Mehrzahl-Formen und Textersetzungen mit sprintf(). Dafür müssen nur wenige Vorbereitung getroffen werden.
Vorarbeiten für wp-i18n
Zunächst muss das wp-i18n-Paket beim Registrieren das Javascript-Files als Voraussetzung angegeben werden:
wp_register_script('myScript', plugins_url('myScript.js', __FILE__), ['wp-i18n']);
Zusätzlich muss definiert werden, welche Textdomain geladen werden soll:
wp_set_script_translations('myScript', 'myTextDomain');
Optional kann in wp_set_script_translations als dritter Parameter auch das Verzeichnis der Übersetzungsdateien angegeben werden.
Poedit kann die Übersetzungen auch aus Javascript-Dateien automatisch lesen. Damit bleibt die Arbeit damit unverändert. Allerdings verlangt wp-i18n die Übersetzungen nicht im mo-Format, sondern als JSON-Datei.
Das wp cli Tool i18n
Um das nicht mühsam selbst übersetzen zu müssen, wurde das Tool i18n zu den Kommandozeilenwerkzeugen wp cli hinzugefügt. Damit kann man sowohl das Gerüst der po-Dateien erzeugen (make-pot) als auch aus ihnen die nötigen Dateien im json-Format übersetzen (make-json).
wp i18n wertet dabei die Quellen der zu übersetzenden Strings im po-File aus und erzeugt pro Javascript-Datei und Text-Domain eine spezielle json-Datei, die nur die nötigen Übersetzungen beinhaltet. Das spart Bandbreite beim Laden der Dateien.
In Normalfall entfernt wp i18n nach der Übersetzung die Strings, die nur in Javascript benutzt werden, aus der po-Datei. So wird die daraus übersetzte mo-Datei kleiner, denn sie enthält dann nur noch die Übersetzungen, die im php gebraucht werden. Das wird durch den --purge Schalter gesteuert.
Die Purge-Falle umgehen
Hier steckt allerdings auch eine Falle: liest man mit Poedit die Quellen automatisch ein, verschwinden nach der Benuttzung von wp i18n die bereits übersetzten Texte, wenn sie nur in Javascript benutzt werden. Der Grund dafür ist, dass der --purge Schalter von wp i18n als Standard gesetzt ist.
Setzt man den schalter auf --no-purge um, funktioniert das Einlesen der Übersetzungen zwar wieder, aber man erhält unnötig große mo-Dateien für die php-Übersetzungen.
Ich umgehe das, indem ich die po-Dateien in einem gesonderten Verzeichnis speichere und vor jedem Erzeugen der mo- und json-Dateien in das Arbeitsverzeichnis kopiere. Mit Poedit bearbeite ich immer nur die Ursprungsdateien. So funktioniert sowohl das Einlesen der Übersetzungen als auch die Optimierung der po- und json-Dateien. Das Script sieht in etwa so aus:
cp <save-directory>/*.po <working-directory> wp i18n make-json <working-directory> for po in `ls <working-directory>/*.po`; do msgfmt $po -o <working-directory>/`basename $po .po`.mo done
Das Script kopiert zunächst die po-Dateien aus dem Sicherungsverzeichnis in das Arbeitsverzeichnis. Dann erzeugt wp i18n die json-Dateien für Javascript und bereinigt dabei die po-Dateien (--purge ist als Standard gesetzt). Aus den bereinigten Dateien werden die mo-Dateien für php übesetzt. Die fertigen json- und mo-Dateien liegen am Ende im Arbeitsverzeichnis; im Sicherungsverzeichnis befinden sich weiterhin die ürsprünglichen po-Dateien mit den Übersetzungen für Javascript und php.
wp i18n vergibt automatisch Dateinamen für die json-Dateien. Sie beginnen mit der Text-Domain, dann folgt der Locale-Code und ein automatisch generierter Identifikator. In unserem Falle könnte die json-Datei für die deutsche Übersetzung etwa so heißen:
myTextDomain-de_DE-f1ec35c30589d74e43b2b2fe390ac66b.json
Für jedes Script wird jeweils in jeder Sprache eine entsprechende Datei erzeugt.
Dynamische Übersetzungen
Ein Problem, das auch gettext im php hat, bleibt allerdings ungelöst: es gibt (meines Wissens) kein spezielles Verfahren, mit dem man Übersetzungen für dynamisch erzeugte Texte hinterlegen kann, also Texte, die zur Kompilierungszeit nicht im Quelltext stehen. Das können zum Beispiel aus Datenbanken gelesene oder sonstige zur Laufzeit erzeugte Texte sein.
In php kann man sich damit behelfen, pro Projekt ein Dummy-Script anzulegen, dass gettext-Aufrufe für die dynamischen Texte enthält; bei uns etwa so:
<?php __('apples', 'myTextDomain'), __('pears', 'myTextDomain'), __('bananas', 'myTextDomain'), __('plums', 'myTextDomain'), __('you compare', 'myTextDomain); __('and', 'myTextDomain');
Das Gleiche kann man auch für Javascript tun. Hier muss man die i18n-Aufrufe allerdings in genau der Datei unterbringen, in der sie später auch benutzt werden sollen. Das liegt an der Optimierung der json-Dateien, die ja nur die Texte enthalten, die auch in der jeweiligen Javascript-Datei benutzt werden. Das geht zum Beispiel mit einer Dummy-Funktion:
function DummyTranslations() { __('apples', 'myTextDomain'), __('pears', 'myTextDomain'), __('bananas', 'myTextDomain'), __('plums', 'myTextDomain'), __('you compare', 'myTextDomain); __('and', 'myTextDomain'); }
Das ist leider noch umständlicher als in php, denn so muss man die Übersetzungsaufrufe möglicherweise sogar in mehreren Javascript-Dateien einfügen. Da bleibt für die WordPress-Entwickler also noch Raum für Optimierungen.