Go Home Page
Die Programmiersprache Go
© 2015 Hans-Werner Heinzen @ Bitloeffel.de
Die Nutzung dieses Werks ist unter den Bedingungen der "Creative Commons Namensnennung - Keine Bearbeitungen 4.0 International"-Lizenz erlaubt.

Internationalisierung

(englische Version)

Es soll hier um Internationalisierung von Serversoftware mit Go gehen. Oder, um's noch weiter einzuschränken: um die Lokalisierung von Fehlermeldungen. Die ist wichtig, denn: Ein Server, der Daten in alle Welt liefert, sollte auch alle Sprachen der Welt sprechen.

Es gibt bereits eine Menge von i18n-Lösungen für Go, einige davon beeindruckend umfangreich.

Und schon stellen sich die nächsten Fragen: Welches Paket soll's denn sein? Wie benutze ich es? Welche Probleme löst es? Welche nicht? Das herauszufinden, war mir ... zu mühsam, ehrlich, ich hab's versucht.

Problem und Lösungsweg

Gehen wir die Sache mal anders an. Konstruieren wir ein Beispiel, und gehen Schritt für Schritt vor.

Irgendwo im Quellkode des Severs wird vielleicht folgende Fehlermeldung generiert:

    Cannot write to file /tmp/tempfile:
    987654321 bytes needed, 123456789 bytes available

Ein deutscher Benutzer möchte aber stattdessen vielleicht lieber lesen:

    Verfügbar sind noch 118MB.
    Datei /tmp/tempfile benötigt aber 942MB.
    Schreiben nicht möglich.

Drei Probleme lassen sich identifizieren:

  1. Feste Textbestandteile wie "Cannot write to file", "bytes needed", "bytes available" sind zu übersetzen.
  2. Variable Bestandteile "/tmp/tempfile", "987654321" und "123456789" sind einzusetzen, eventuell in anderer Reihenfolge.
  3. Zahlenangaben werden (auch abhängig von Land und Sprache) eventuell verschieden formatiert. In unserem Beispiel "118MB" statt "123456789 bytes"

Hier eine naheliegende Lösung:

  1. Die festen Textbausteine bilden zusammen den Suchstring für eine Tabelle mit Übersetzungen. (Wir zeigen hier ein geeignetes JSON-Format.) Eine Suchfunktion kann dann aufgrund des Strings und des Sprachkodes (z.B. "de") die entsprechende Übersetzung zurückgeben:
        [
            "Cannot write to file :  bytes needed,  bytes available": [
                {"de": "Verfügbar sind noch . Datei  benötigt aber . Schreiben nicht möglich."}
            ]
        ]
    
  2. Das Einsetzen der Variablen in den übersetzten Text wird der Mechanik des Pakets text/template überlassen. Die Textbausteine/Übersetzungen werden dafür mit Template-Ausdrücken (incl. Variablennamen) erweitert:
        [
            "Cannot write to file {{.File}}: {{.Need}} needed, {{.Avail}} available": [
                {"de": "Verfügbar sind noch {{.Avail}}. Datei {{.File}} benötigt aber {{.Need}}. Schreiben nicht möglich."}
            ]
        ]
    
  3. Das Formatieren der Zahlenangaben geschieht ebenfalls über text/template; hier mithilfe einer Funktion aus dem Paket humanize ("bytes" soll im Template der Name für "humanize.Bytes" sein):
        [
            "Cannot write to file {{.File}}: {{.Need}} needed, {{.Avail}} available": [
                {"de": "Verfügbar sind noch {{bytes .Avail}}. Datei {{.File}} benötigt aber {{bytes .Need}}. Schreiben nicht möglich."}
            ]
        ]
    
    Aber auch das Standardpaket strings enthält hilfreiche Funktionen, mit denen Strings manipuliert werden können. Mit Replace kann man leicht den Dezimalpunkt zu einem Komma machen. Und TrimRight hilft, Nullen rechts abzuschneiden.

stringl10n und stringl10nextract

Das Kommando stringl10n folgt dem beschriebenen Lösungsweg. Es benutzt ebenfalls text/template, bekommt alle nötigen Informationen aus einer Textdatei (JSON-Format) und generiert Go-Kode, der alle Daten enthält, also ohne zusätzliche Datei auskommt, und zwei Funktionen bereitstellt: l10nTranslate und l10nSubstitute.

Damit kann der Programmierer an geeigneter Stelle die nötigen Umformungen vornehmen.

Es bleibt natürlich Aufgabe des Programmierers, die zu übersetzenden Textstrings zu identifizieren, und sie zusammen mit den gewünschten Übersetzungen und Infos zu benutzten Variablen und Funktionen in einer JSON-Datei zur Verfügung zu stellen.

Maschinelle Hilfe für diese Aufgabe ist denkbar. Ein erster Schritt in diese Richtung ist das Kommando stringl10nextract , welches Stringliterale aus dem Go-Kode eines Quellverzeichnisses extrahiert und im geforderten JSON-Format ausgibt. Es bleibt aber noch genug Handarbeit übrig ... und Raum für weitere Werkzeuge.

Wie geht's weiter

Im letzten Abschnitt habe ich von einer "geeigneten Stelle" gesprochen, an der die stringl10n- Funktionen einzusetzen sind. Es sind dies jedenfalls nicht die Stellen, an denen die Fehlermeldungen ausgelöst werden! Sonst müsste man ja jeder einzelnen Funktion den gewünschten Sprachkode mitgeben. Eine globale Variable tut's auch nicht, weil ein Server ja üblicherweise mehrere Anfragen gleichzeitig bedient.

Über diese Schwierigkeit hat mir ein "extended error type" (Paket mist) hinweggeholfen. Dieser transportiert den (zu übersetzenden) Meldungstext getrennt von eventuell benutzten Variablen. Doch das ist eine andere Geschichte ...