Internationalisierung
Alle textuellen Ausgaben einer Anwendung müssen prinzipiell mehrsprachig erfolgen können. Hierfür wird Internationalisierung über Resource-Keys mit korrespondierenden Übersetzungen in Messages-Properties-Dateien eingesetzt.
Deklaration von Resource-Keys
Ein Resource-Key wird im Code in einer I18NConstants
-Klasse (pro Paket) definiert. Ein minimalistisches Template für eine solche I18NConstants
-Dateil sieht folgendermaßen aus:
package your.package;
/**
* Internationalization constants for this package.
*/
public class I18NConstants extends I18NConstantsBase {
// Define your keys here...
public static ResKey MY_KEY;
static {
initConstants(I18NConstants.class);
}
}
Die Keys werden als Pseudo-Konstanten vom Typ ResKey
(ohne den final
-Modifier) deklariert. Die Initialisierung erfolgt über die Magie initConstants(...)
zum Ladezeitpunkt der Klasse.
Deklaration von Übersetzungen
Übersetzungen für Keys werden in Resource-Bundles, die Übersetzungen für jede unterstützte Sprache enthalten, hinterlegt. Jedes Modul deklariert die von ihm hinzugefügten Bundles über eine Konfigurations-Sektion:
<config service-class="com.top_logic.basic.util.ResourcesModule">
<instance error-on-missing-key="true">
<static-bundles>
<bundles>
<bundle name="MyMessages" />
</bundles>
</static-bundles>
</instance>
</config>
Im Verzeichnis /webapp/WEB-INF/conf/resources/
des entsprechenden Module werden dann für jede Sprache (z.B. de
und en
) Übersetzungsdateien hinterlegt:
my.module/webapp/WEB-INF/conf/resources:
MyMessages_de.properties
MyMessages_en.properties
Für den oben deklarierten Key werden dann in
my.module/webapp/WEB-INF/conf/resources/MyMessages_de.properties
:
class.your.package.I18NConstants.MY_KEY = Eine deutsche Übersetzung.
eine deutsche und in
my.module/webapp/WEB-INF/conf/resources/MyMessages_en.properties
:
class.your.package.I18NConstants.MY_KEY = An Englisch translation.
eine englische Übersetzung hinterlegt.
Aus einer Key-Konstanten MY_KEY
mit vollqualifiziertem Namen your.package.I18NConstants.MY_KEY
wird durch Voranstellen von class.
der Schlüssel class.your.package.I18NConstants.MY_KEY
zu dem eine entsprechende Übersetzung hinterlegt werden kann. Der vollqualifizierte Name einer Konstanten kann in Eclipse durch Öffnen des Kontext-Menüs über der Deklaration und wählen des Menüpunkts "Copy qualified name" besonders einfach kopiert und in die entsprechende Internationalisierungsdatei übertragen werden.
Automatische Übersetzung
Die englische Übersetzung kann direkt im JavaDoc-Kommentar der Konstante in dem Tag @en
angegeben werden. In diesem Fall, wird der Text in diesem Tag durch den JavaDoc-Prozess extrahiert und in die System-Resource-Datei des Moduls übernommen.
package your.package;
public class I18NConstants extends I18NConstantsBase {
/**
* Dokument your key here.
*
* @en Your English message associated with your key.
* @tooltip The tooltip for your message.
*/
public static ResKey MY_KEY;
...
}
Im obigen Fall wird der Text "Your English message associated with your key
" als Wert in die englische Resource-Datei mit dem passenden Schlüssel für das Property MY_KEY
übernommen. Ist automatische Übersetzung aktiviert, wird beim Build-Prozess der englische Text in alle unterstützten Sprachen der Anwendung übersetzt und die entsprechenden System-Resourcen der anderen Sprachen gefüllt (siehe Übersetzungen aus Code-Dokumentation).
Zugriff auf Übersetzungen im Code
Die Klasse Resources
stellt den Zugriff auf Übersetzungen her. Im Rendering sind die passenden Resources
für den aktuellen Nutzer über DisplayContext.getResources()
erreichbar. An Stellen, an denen kein DisplayContext
verfügbar ist, kann über Resources.getInstance()
auf die passenden Ressourcen zugegriffen werden.
Das Nachschlagen einer Übersetzung geschieht über die API getString(ResKey)
:
void render(DisplayContext context, TagWriter out) {
ResKey key = I18NConstants.MY_KEY;
String text = context.getResources.getString(key);
out.writeText(text);
}
Obiges Beispiel druckt "Eine deutsche Übersetzung." für einen deutschen Nutzer und "An Englisch translation." für einen englischen.
Übersetzungen mit dynamischen Argumenten
Häufig kann es vorkommen, dass in den darzustellenden Text dynamische Inhalten (Zahlen, Datumsangaben, o.ä.) eingebettet werden müssen.
Die hinterlegte Übersetzund darf daher Platzhalter nach der Spezifikation von java.text.MessageFormat
enthalten. Die Dynamischen Inhalten können dann zusammen mit einem Basis-Resource-Key zu einem Message-Key kombiniert und an jede API weitergereicht werden, die mit ResKey
umgehen kann:
ResKey titleKey = ResKey.message(I18NConstants.MY_MESSAGE__DATE, new Date());
new MyDisplayControl(titleKey).write(...);
Wenn die Übersetzung direkt an der Stelle durchgeführt wird, an der die Argumente verfügbar sind, können diese auch direkt an die Resources
weitergereicht werden:
void render(DisplayContext context, TagWriter out) {
out.writeText(context.getResources.getMessage(I18NConstants.MY_MESSAGE__DATE, new Date()));
}
Ist gleichbedeutend mit:
void render(DisplayContext context, TagWriter out) {
out.writeText(context.getResources.getString(ResKey.message(I18NConstants.MY_MESSAGE__DATE, new Date())));
}
Format von Übersetzungen mit Argumenten
In der Übersetzung wird der dynamische Wert (hier das aktuelle Datum) an Stelle des Plazhalters {0}
in die Übersetzung eingebettet:
my.module/webapp/WEB-INF/conf/resources/MyMessages_de.properties
:
class.your.package.I18NConstants.MY_MESSAGE__DATE = Version vom {0}.
Details, wie Formattierungen in diese Platzhalter eingebettet werden können finden sich in der API-Dokumentation zu MessageFormat. Hierüber lassen sich auch komplexe Formattierungen erreichen, die bei Zahlenausgabe eine korrekte Singular/Plural-Form von sich auf die Zahl beziehenden Wörtern erreichen.
Konvention für Resource-Keys mit Argumenten
Konstantennamen für Übersetzungsschlüssel, welche dynamische Anteile enthalten, müssen die Argumente nach einem doppelten Unterstrich aufzählen (für jedes Argument ein einzelnes Wort, mehrere Argumente jeweils durch Unterstrich getrennt).
Keys mit drei Argumenten könnten beispielsweise folgendermaßen heißen:
public static ResKey MY_KEY__ARG1_ARG2_ARG3;
public static ResKey ERROR_MESSAGE__CODE_DATE_SEVERITY;
Übersetzungen mit eingebautem Fallback
Bei einer Übersetzung kann es sinnvoll sein, einen generischen Text zu hinterlegen, der in manchen Anwendungsfällen spezialisiert werden kann. Beispielsweise könnte für eine Anlage-Komponente generisch ein Titel "Neues Element anlegen" hinterlegt sein, so dass beim Einbinden der Komponente eine nette Default-Ansicht erreicht wird. Für konkrete Anwendungsfälle soll sich diese Übersetzung aber zu "Neues Projekt anlegen" oder "Meilenstein hinzufügen" spezialisieren lassen.
Hierfür lassen sich Resource-Keys zu Fallback-Chains zusammenfügen:
ResKey defaultKey = I18NConstants.CREATE_ELEMENT_TITLE;
ResKey specializedKey = component.getResPrefix().key("title");
ResKey titleKey = ResKey.fallback(specializedKey, defaultKey);
new MyDisplayControl(titleKey).write(...);
Theoretisch lassen sich so mehrstufige Fallback-Keys konstruieren, die dann an jede API weitergereicht werden können, die mit ResKey
umgehen kann.
Generierter Text anstatt von Übersetzungen
Ist eine API auf Internationalisierung vorbereitet z.B.
class MyDisplay {
void setLabel(ResKey labelKey) {...}
}
kann MyDisplay
einfach mit Resource-Keys verwendet werden:
display.setLabel(I18NConstants.WINDOW_TITLE);
Allerdings kann es unter Umständen auch notwendig sein, eine solche API mit einem fertigen Text (der beispielsweise aus einer Nutzereingabe stammt) zu bedienen. Auch dies ist möglich, indem der literale Text in einen ResKey
verpackt wird. Ein solcher spezieller Key wird bei der Auflösung wieder zu dem Text, mit dem er generiert wurde:
String userInput = ...;
display.setLabel(ResKey.text(userInput));
Resource Prefixe von Komponenten
Komponenten die eine Toolbar haben, müssen einen Resource Prefix in ihrer XML-Datei definieren. (Siehe: LayoutComponent.Config.getResPrefix()
) Dieser soll dem in #18227 definiertem Schema entsprechen:
- mit dem Prefix
layouts.
beginnen - nach dem konstanten Prefix aus einem Punkt-getrennten Pfad zu der Layout-XML-Datei bestehen, in der der Key definiert ist.
- als letzten Teil den lokalen Namen der Komponente haben, an der der Key definiert ist:
<component class="..." name="MyForm" page="/jsp/....jsp" resPrefix="layouts.demo.form.SomeFormDemo.MyForm." />