Gutenberg-Blöcke übersetzen

Seit WordPress 5.0 können auch in JavaScript Übersetzungsfunktionen genutzt werden. Beim Bau von Gutenberg-Blöcken sind die sehr nützlich, allerdings gibt es ein paar Hürden bei der Übersetzung, wenn das Plugin mit dem Block nicht im offiziellen WordPress.org-Verzeichnis liegt. Hier zeige ich, wie Übersetzungen für Blöcke angelegt werden können.

Voraussetzungen

Neben einem Plugin, in dem wir JS-Strings übersetzen möchten, brauchen wir WP-CLI 2.2.0 oder neuer. Falls ihr das Tool noch nicht installiert habt, findet ihr auf der Installing-Seite des WP-CLI-Handbuchs verschiedene Möglichkeiten dafür.

Zudem müssen für die Strings im JS die seit WordPress 5.0 vorhandenen JS-Übersetzungsfunktionen genutzt werden, die genauso funktionieren wie die PHP-Funktionen – __() wird vermutlich meist ausreichen.

Diese Funktionen sind Teil des wp-18n-Skripts, das ihr eurem Block-Skript bei der Registrierung als Abhängigkeit mitgeben müsst. Außerdem muss das Skript früh genug registriert werden, enqueue_block_editor_assets ist zu spät.

Wir nutzen den init-Hook, und damit kann das beispielsweise so aussehen:

add_action( 'init', function() { wp_register_script( 'krautpress-block-editor-script', plugins_url( 'assets/js/editor.blocks.js', __FILE__ ), [ 'wp-blocks', 'wp-element', 'wp-edit-post', 'wp-i18n' ] ); } );

Einbinden werden wir das Skript dann so:

add_action( 'enqueue_block_editor_assets', function() { wp_enqueue_script( 'krautpress-block-editor-script' ); } );

Wenn wir direkt im init-Part wp_enqueue_script() nutzen würden, würde das Skript überall geladen werden, auch im Frontend.

Übersetzungsdateien erstellen und laden

Wir haben jetzt alle Vorbereitungen getroffen und können an das Erstellen der Übersetzungsdateien gehen.

PO-Datei erstellen

Zunächst erstellen wir eine PO-Datei über das folgende WP-CLI-Kommando, das ihr in dem Verzeichnis eures Plugins ausführen müsst:

wp i18n make-pot ./ languages/<textdomain>-<lang>.po

<textdomain> ersetzt ihr dabei durch die Textdomain des Plugins, <lang> durch den Sprachcode, also beispielsweise de_DE. Danach könnt ihr die Strings in einem Programm wie Poedit oder mit Loco Translate übersetzen.

PO-Übersetzungen in JSON-Dateien konvertieren

Im Anschluss müssen die übersetzten Strings in das JED-1.x-Format (JSON) gebracht werden, wofür erneut WP-CLI zum Einsatz kommt:

wp i18n make-json languages/<textdomain>-<lang>.po --no-purge --pretty-print

Auch hier ersetzten wir die Platzhalter wieder entsprechend, sodass wir als Parameter an das Kommando den relativen Pfad zur übersetzten PO-Datei übergeben. Mit --no-purge geben wir an, dass die extrahierten Strings nicht aus der PO-Datei gelöscht werden sollen, und wir möchten das JSON schön formatiert (--pretty-print).

Der Befehl erstellt pro JavaScript-Datei eine JSON-Datei im languages-Verzeichnis, die nach dem Schema <textdomain>-<lang>-<hash>.json benannt ist. <hash> ist der md5()-Hash des Dateinamens der JS-Datei. Es kann aber durchaus sein (bei meinem Setup die Regel), dass dieser Wert nicht passt, weil es sich dabei um die Source-Datei handelt, nicht die kompilierte Datei, die letztlich von WordPress geladen wird.

Statt des Hashes kann aber noch das Handle des Skripts angegeben werden, das bei wp_register_script() als erster Parameter genutzt wird – in unserem Fall krautpress-block-editor-script. Damit müsste die Datei für die deutsche Übersetzung bei der Textdomain krautpress-block dann so heißen: krautpress-block-de_DE-krautpress-block-editor-script.json.

Gegebenenfalls mehrere JSON-Dateien zusammenführen

Wenn der Block aus mehreren Source-Dateien besteht, die in eine Datei kompiliert werden, müssen die Strings aus diesen Dateien in einer JSON-Datei zusammengeführt werden. Wenn wir zum Beispiel die folgenden beiden Dateien haben:

{ "translation-revision-date": "+0000", "generator": "GlotPress/2.3.0-alpha", "domain": "messages", "locale_data": { "messages": { "": { "domain": "messages", "plural-forms": "n != 1", "lang": "de_DE" }, "Block name": [ "Blockname" ], } } }
{ "translation-revision-date": "+0000", "generator": "GlotPress/2.3.0-alpha", "domain": "messages", "locale_data": { "messages": { "": { "domain": "messages", "plural-forms": "n != 1", "lang": "de_DE" }, "Field description": [ "Feldbeschreibung" ], } } }

Müssten wir sie in einer Datei zusammenführen, die so aussieht:

{ "translation-revision-date": "+0000", "generator": "GlotPress/2.3.0-alpha", "domain": "messages", "locale_data": { "messages": { "": { "domain": "messages", "plural-forms": "n != 1", "lang": "de_DE" }, "Block name": [ "Blockname" ], "Field description": [ "Feldbeschreibung" ], } } }

Jetzt sind beide Strings innerhalb des messages-Objekts enthalten.

Übersetzungen laden

Zum Laden der JS-Übersetzungen gibt es die Funktion wp_set_script_translations():

add_action( 'init', function() { wp_set_script_translations( '<handle>', '<textdomain>', plugin_dir_path( __FILE__ ) . 'languages' ); } );

Als Parameter erwartet sie

  1. das Handle des Skripts, für das die Übersetzungen sind, das wäre bei uns krautpress-block-editor-script.
  2. die Textdomain, bei uns krautpress-block.
  3. den absoluten Pfad zu den Übersetzungen.

Fazit

Das ist im Vergleich zu wp_localize_script(), womit übersetzbare Strings aus PHP in JavaScript integriert werden können, natürlich recht aufwendig. Einfach ist es, wenn das Plugin bei WordPress.org liegt, weil da die ganze Magie im Hintergrund abläuft und sich für die Übersetzenden nichts ändert.

Vorteil der neuen Vorgehensweise ist beispielsweise, dass sich auch Singular-/Plural-Übersetzungen in JS jetzt elegant über _n() lösen lassen.

Laut Website arbeiten zum Beispiel die Loco-Translate-Macher daran, auch die Erstellung von den JSON-Dateien zu unterstützen, aber mit GlotPress als WordPress-Plugin in Verbindung mit Traduttore von required gibt es vielleicht jetzt schon einen relativ bequemen Weg. Ich bin momentan noch bei der Einrichtung, aber wenn alles klappt, wird es hier darüber etwas zu lesen geben 🙃

Danke an Matze Kittsteiner, der die notwendigen Schritte bei seinem ersten Verwenden festgehalten und mir zur Verfügung gestellt hat 🙂

9 Kommentare

  1. *Statt des Hashes kann aber noch das Handle des Skripts angegeben werden*

    Wie dann, bitte? Bei `wp i18n make-json` hat man kein Parameter für den Dateinamen…

  2. Ein wenig nervig ist, dass man darauf achten muss, dass i18n.min.js vor der .json-Datei geladen wird. Also kein defer bzw. Lazy Loading möglich :/ Oder hast du dafür eine Lösung gefunden?

        1. Ah okay. Hm, ja, ist dann wahrscheinlich schwierig da was zu optimieren. Das einzige was mir da noch einfiele, wäre, die i18n-Datei inline einzubinden um sich wenigstens den Request zu sparen, aber dafür ist sie vielleicht etwas zu groß.

  3. Hey Florian

    Danke für den informativen Blogpost.
    Ich versuche aktuell meine Strings aus den JavaScripts übersetzbar zu machen. Mit dem Befehl `wp i18n make-pot ./ languages/-.po` werden JavaScript Dateien berücksichtig, leider aber nur die Dateien aus dem ’src‘ Verzeichnis und nicht die kompilierten im ‚dist‘ Verzeichnis. Im ‚dist‘ Verzeichnis hat beides, minifizierte und unminifizierte Scripte. Bist du diesem Problem auch schon begegnet, resp. kennst du eine Möglichkeit um hier Abhilfe zu schaffen?

    Danke schon im Voraus!

    1. Hi Joel,

      freut mich, dass der Post hilfreich ist 🙂

      Zu deiner Frage: tatsächlich hatte ich mich neulich auch gewundert, als ich den Befehl in einem Projekt ausgeführt hatte, wo sowohl die Source- als auch Produktiv-Dateien drin waren. Problem war da, dass in den kompilierten Dateien die Funktionen nicht mehr __(), et cetera heißen, sondern während des Kompilierens und Minifizierens umbenannt wurden. So konnte der WP-CLI-Befehl die Strings in den kompilierten Dateien nicht finden.

      In deinen unminifizierten Dateien sind aber wahrscheinlich die Funktionsnamen noch unverändert? Eventuell mal testen, das dist-Verzeichnis als source an den Befehl zu übergeben, oder über --include das Verzeichnis und die PHP-Datei(en): https://developer.wordpress.org/cli/commands/i18n/make-pot/

      Viele Grüße
      Florian

Schreibe einen Kommentar

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