Kategorien
Allgemein

i18n für Javascript in WordPress

Über­set­zun­gen mit i18n für php Scripts in Word­Press konn­te man schon bis­her recht ein­fach mit den bekann­ten get­text-tools erle­di­gen. Dafür muss­te ledig­lich eine Text-Domain defi­niert und die nöti­gen po-Files erzeugt, über­setzt und die mo-Files gela­den wer­den.

Die Erzeu­gung der po- und mo-Files erle­digt unter Linux sehr kom­for­ta­bel Poe­dit, dass die zu über­set­zen­den String auto­ma­tisch aus den Quel­len lesen kann.

Danach kann man mit den übli­chen Tools wie __(), _x(), _n(), _nx() und sprintf() Über­set­zun­gen in die eige­nen php-Skrip­te ein­bau­en.

Viel Arbeit bei Javascript

Bei Java­script sah das andes aus. Zwar gab es auch dort schon immer eine Mög­lich­keit, die Scripts zu über­set­zen, aber die war im Ver­gleich sehr unkom­for­ta­bel.

Tool der Wahl war bis­her die Funk­ti­on wp_localize_script(), mit der man ein Array mit Über­set­zun­gen an das Script über­ge­ben konn­te. Um das Zusam­men­sam­meln der zu über­set­zen­den Tex­te muss­te man sich aber selbst küm­mern, eben­so wie um die eigent­li­che Über­set­zung mit den get­text-Tools im php.

Prak­tisch 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 eigent­li­chen Über­set­zun­gen müs­sen im ent­spre­chen­den po/­mo-Files im Ver­zeich­nis <i18n-dir> natür­lich ein­ge­tra­gen sein. Die po-Datei­en sind nach der Text-Domain und dem Loca­le benannt:

<Text-Domain>-<locale>.mo

Also z.B. bei der deut­schen Über­set­zung:

myTextDomain-de_DE.mo

Poe­dit sam­melt die zu über­set­zen­den Strings auto­ma­tisch ein, wenn die Source-Files im Kata­log ent­spre­chend defi­niert sind. Ein Ein­trag in der po-Datei sieht in etwa so aus:

# @ myTextDomain
#: myScript.php:3
msgid "apples"
msgstr ""

Hat man im Poe­dit die Über­set­zung ein­ge­tra­gen, steht in der Datei myTextDomain-de_DE.po dann:

# @ myTextDomain
#: myScript.php:3
msgid "apples"
msgstr "Äpfel"

Im Java­script (myScript.js) sieht es dann so aus:

var translated = i18n['you compare'] + i18n['apples'] + i18n['and'] + i18n['pears'];

Haupt­pro­blem die­ser Vor­ge­hens­wei­se ist die sta­ti­sche Erzeu­gung des loca­li­za­ti­on-Arrays. Bei jeder Ände­rung der zu über­set­zen­den Tex­te im Java­script muss auch im php die­ses Array ange­passt wer­den. Feh­ler sind dabei fast unaus­weich­lich.

Hilfe durch Gutenberg

Über den neu­en Guten­berg-Edi­tor mag man den­ken, was man will; in Sachen i18n-Nut­zung in Java­script hat er ein­deu­tig Fort­schrit­te gebracht. Der Edi­tor selbst läuft gro­ßen­teils in Java­script. Dadurch hat das Ent­wick­lungs­team erkannt, dass in Sachen Java­script-Inter­na­tio­na­li­sie­rung drin­gen­der Hand­lungs­be­darf bestand und ent­wi­ckel­te das neue wp-i18n Paket.

Es bil­det die Arbeits­wei­se mit den get­text-Tools im php fast iden­tisch in Java­script ab, und zwar inklu­si­ve Mehr­zahl-For­men und Tex­ter­set­zun­gen mit sprintf(). Dafür müs­sen nur weni­ge Vor­be­rei­tung getrof­fen wer­den.

Vorarbeiten für wp-i18n

Zunächst muss das wp-i18n-Paket beim Regis­trie­ren das Java­script-Files als Vor­aus­set­zung ange­ge­ben wer­den:

wp_register_script('myScript',   plugins_url('myScript.js', __FILE__), ['wp-i18n']);

Zusätz­lich muss defi­niert wer­den, wel­che Text­do­main gela­den wer­den soll:

wp_set_script_translations('myScript', 'myTextDomain');

Optio­nal kann in wp_set_script_translations als drit­ter Para­me­ter auch das Ver­zeich­nis der Über­set­zungs­da­tei­en ange­ge­ben wer­den.

Poe­dit kann die Über­set­zun­gen auch aus Java­script-Datei­en auto­ma­tisch lesen. Damit bleibt die Arbeit damit unver­än­dert. Aller­dings ver­langt wp-i18n die Über­set­zun­gen nicht im mo-For­mat, son­dern als JSON-Datei.

Das wp cli Tool i18n

Um das nicht müh­sam selbst über­set­zen zu müs­sen, wur­de das Tool i18n zu den Kom­man­do­zei­len­werk­zeu­gen wp cli hin­zu­ge­fügt. Damit kann man sowohl das Gerüst der po-Datei­en erzeu­gen (make-pot) als auch aus ihnen die nöti­gen Datei­en im json-For­mat über­set­zen (make-json).

wp i18n wer­tet dabei die Quel­len der zu über­set­zen­den Strings im po-File aus und erzeugt pro Java­script-Datei und Text-Domain eine spe­zi­el­le json-Datei, die nur die nöti­gen Über­set­zun­gen beinhal­tet. Das spart Band­brei­te beim Laden der Datei­en.

In Nor­mal­fall ent­fernt wp i18n nach der Über­set­zung die Strings, die nur in Java­script benutzt wer­den, aus der po-Datei. So wird die dar­aus über­setz­te mo-Datei klei­ner, denn sie ent­hält dann nur noch die Über­set­zun­gen, die im php gebraucht wer­den. Das wird durch den --pur­ge Schal­ter gesteu­ert.

Die Purge-Falle umgehen

Hier steckt aller­dings auch eine Fal­le: liest man mit Poe­dit die Quel­len auto­ma­tisch ein, ver­schwin­den nach der Benutt­zung von wp i18n die bereits über­setz­ten Tex­te, wenn sie nur in Java­script benutzt wer­den. Der Grund dafür ist, dass der --pur­ge Schal­ter von wp i18n als Stan­dard gesetzt ist.

Setzt man den schal­ter auf --no-pur­ge um, funk­tio­niert das Ein­le­sen der Über­set­zun­gen zwar wie­der, aber man erhält unnö­tig gro­ße mo-Datei­en für die php-Über­set­zun­gen.

Ich umge­he das, indem ich die po-Datei­en in einem geson­der­ten Ver­zeich­nis spei­che­re und vor jedem Erzeu­gen der mo- und json-Datei­en in das Arbeits­ver­zeich­nis kopie­re. Mit Poe­dit bear­bei­te ich immer nur die Ursprungs­da­tei­en. So funk­tio­niert sowohl das Ein­le­sen der Über­set­zun­gen als auch die Opti­mie­rung der po- und json-Datei­en. 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-Datei­en aus dem Siche­rungs­ver­zeich­nis in das Arbeits­ver­zeich­nis. Dann erzeugt wp i18n die json-Datei­en für Java­script und berei­nigt dabei die po-Datei­en (--pur­ge ist als Stan­dard gesetzt). Aus den berei­nig­ten Datei­en wer­den die mo-Datei­en für php übe­setzt. Die fer­ti­gen json- und mo-Datei­en lie­gen am Ende im Arbeits­ver­zeich­nis; im Siche­rungs­ver­zeich­nis befin­den sich wei­ter­hin die ürsprüng­li­chen po-Datei­en mit den Über­set­zun­gen für Java­script und php.

wp i18n ver­gibt auto­ma­tisch Datei­na­men für die json-Datei­en. Sie begin­nen mit der Text-Domain, dann folgt der Loca­le-Code und ein auto­ma­tisch gene­rier­ter Iden­ti­fi­ka­tor. In unse­rem Fal­le könn­te die json-Datei für die deut­sche Über­set­zung etwa so hei­ßen:

myTextDomain-de_DE-f1ec35c30589d74e43b2b2fe390ac66b.json

Für jedes Script wird jeweils in jeder Spra­che eine ent­spre­chen­de Datei erzeugt.

Dynamische Übersetzungen

Ein Pro­blem, das auch get­text im php hat, bleibt aller­dings unge­löst: es gibt (mei­nes Wis­sens) kein spe­zi­el­les Ver­fah­ren, mit dem man Über­set­zun­gen für dyna­misch erzeug­te Tex­te hin­ter­le­gen kann, also Tex­te, die zur Kom­pi­lie­rungs­zeit nicht im Quell­text ste­hen. Das kön­nen zum Bei­spiel aus Daten­ban­ken gele­se­ne oder sons­ti­ge zur Lauf­zeit erzeug­te Tex­te sein.

In php kann man sich damit behel­fen, pro Pro­jekt ein Dum­my-Script anzu­le­gen, dass get­text-Auf­ru­fe für die dyna­mi­schen Tex­te ent­hält; bei uns etwa so:

<?php
__('apples', 'myTextDomain'),
__('pears', 'myTextDomain'),
__('bananas', 'myTextDomain'),
__('plums', 'myTextDomain'),
__('you compare', 'myTextDomain);
__('and', 'myTextDomain');

Das Glei­che kann man auch für Java­script tun. Hier muss man die i18n-Auf­ru­fe aller­dings in genau der Datei unter­brin­gen, in der sie spä­ter auch benutzt wer­den sol­len. Das liegt an der Opti­mie­rung der json-Datei­en, die ja nur die Tex­te ent­hal­ten, die auch in der jewei­li­gen Java­script-Datei benutzt wer­den. Das geht zum Bei­spiel mit einer Dum­my-Funk­ti­on:

function DummyTranslations() {
  __('apples', 'myTextDomain'),
  __('pears', 'myTextDomain'),
  __('bananas', 'myTextDomain'),
  __('plums', 'myTextDomain'),
  __('you compare', 'myTextDomain);
  __('and', 'myTextDomain');
}

Das ist lei­der noch umständ­li­cher als in php, denn so muss man die Über­set­zungs­auf­ru­fe mög­li­cher­wei­se sogar in meh­re­ren Java­script-Datei­en ein­fü­gen. Da bleibt für die Word­Press-Ent­wick­ler also noch Raum für Opti­mie­run­gen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.