2019-09-Mehrsprachigkeit_und_Java

September 2019, Michael Höreth

Eines Tages ist es bei jedem Softwareprojekt soweit: man möchte es mehrsprachig machen. Und noch ein paar Monate später verliert man den Überblick der Übersetzungen und findet sich nicht mehr zurecht. Dieser Artikel beschreibt einen neuen Ansatz zum Umgang mit Übersetzungsdateien in JAVA. Ich praktiziere das bereits seit mehreren Jahren und mein Fazit: Übersetzungen können sogar Spaß machen! 😉

Zuerst einmal muss festgestellt werden, dass Java üblicherweise seine Übersetzungen aus messages_LOCALE.properties Dateien bezieht. Dieses Dateiformat ist standardisiert und hinreichend performant für diesen Einsatzzweck. Es handelt sich dabei leider um unstrukturierte Key-Value Dokumente pro Sprache. Um wenigstens ein bisschen Ordnung in die Keys zu bekommen, strukturiert man sie häufig mit folgender Punkt-Schreibweise:

form.validation.error.summary=Please check the Validation Error before submitting the form

Mit zunehmender Zahl der Übersetzungen steigt das Risiko der Tippfehler der Keys, was letztlich zu einer Inkonsistenz der verschiedenen Properties-Dateien führt. Es scheint auch unmöglich, Änderungen an der Struktur der Keys vorzunehmen. Möchte man z.B. validation in validator umbenennen, müssen alle relevanten Keys in allen Dateien angepasst werden.

Oder was ist mit fehlenden Übersetzungen? Schön wäre zum Beispiel ein Fallback auf eine andere Sprache, solange ein Eintrag noch nicht übersetzt wurde. Die RessourceBundle Klasse bietet das nicht und auch von einer eigenen Implementierung ist abzuraten, wenn man die optimale Laufzeitperformance behalten möchte.

Zu diesen "redaktionellen" Problemen gesellen sich dann noch weitere "technische". JAVA Properties-Dateien müssen Latin-1 kodiert sein. UTF-8 Zeichen müssen escaped werden, z.B.: ä = \u00E4. Logische Zeilenumbrüche müssen mit einem Backslash beendet werden. Selbst Zeichen wie z.B. das Ausrufezeichen oder der Doppelpunkt müssen gesondert behandelt werden.

Allen Problemen ist gemein, dass der Compiler sie nicht bemängelt. Selbst die Frontend-Tests werden kaum jede Sprache gesondert abtesten. Am Ende wird das System einfach zur Laufzeit immer wieder falsche Übersetzungen liefern.

In einem meiner früheren Projekte wurde sogar auch mal ein zusätzlicher Test-Step eingeführt, der die Properties-Dateien miteinander verglich und auf fehlende Keys hinwies. Das verbesserte die Qualität der Software, aber der "Fun-Faktor" hielt sich in Grenzen und es stellte sich die Frage:

Sollte man die Properties-Dateien wirklich manuell bearbeiten? - Nein. 😉

Muss ich dann gleich eine große Übersetzungssuite kaufen wie es bei großen Unternehmen üblich ist? - Eigentlich auch nein.

Man könnte die Übersetzungen auch strukturiert in einer einzigen und übersichtlichen XML-Datei pflegen und diese mit einem zusätzlichen Build-Step in die erforderlichen Properties-Dateien transformieren. Das ist letztlich notwendig, weil man nicht wirklich zur Laufzeit XML-Dateien "nur" für Übersetzungen parsen möchte. Es reicht völlig, wenn diese rechenintensive Aufgabe im Kontext eines Builds ausgeführt wird. Hier ein Ausschnitt einer solchen i18n.xml Datei, die als neue Datenquelle der Übersetzungstexte dienen würde:

<entry key="form">
	<description>Form entry and validation.</description>
	<entry key="validation">
		<entry key="error">
			<text locale="en">Validation Error</text>
			<text locale="de">Validierungsfehler</text>
			<text locale="fr">Erreur de Validation</text>
		</entry>
	</entry>
</entry>

Daraus ist ein Maven-Plugin entstanden, das seit letztem Jahr als Open Source auf GitHub und Maven Central verfügbar ist:

Es hat mich immer wieder überrascht, dass die Usability sehr viel mit dem passenden Wording zu tun hat. Bei der eigentlichen Arbeit des Übersetzen wurde häufig klar, dass vielleicht auch in der Ausgangssprache nicht zu 100% die gewünschte Bedeutung getroffen wurde. Soll heißen - das Übersetzungsthema macht eigentlich sehr viel Spaß, solange man sich voll darauf konzentrieren kann.

Ich möchte die gewonnenen Vorteile eines zusätzlichen Build-Steps in Form eines Maven-Plugins nochmal kurz zusammenfassen:

  • Pflege der Übersetzungen in einer einzigen XML-Datei
  • die Struktur der Keys muss nicht mehr simuliert werden
  • es gibt keine Redundanzen der Keys mehr
  • keine vergessenen Übersetzungen mehr bzw. Fallback auf andere Sprache möglich
  • Platzhalter-Mechanismus innerhalb der Übersetzungstexte
  • und speziell für Java-Properties-Dateien: es kann optional eine Java "Accessor Klasse" generiert werden, die für jeden Übersetzungstext eine eigene Methode anbietet. Damit wären die Übersetzungen dann zu 100% vom Compiler abgesichert.

Ich hoffe, ich konnte hiermit einen neuen Ansatz für die Erstellung von Übersetzungsdateien aufzeigen. Wen's interessiert - das Maven Plugin kann außerdem auch Dateien für Javascript und iOS erzeugen (Dokumentation siehe github.com/hoereth/i18n-maven-plugin).