Das ResourceBundle API ist für den Zugriff auf Textressourcen das Mittel der Wahl. Der Aufruf von
ResourceBundle.getBundle([name])sucht nach Ressourcen unter Anwendung folgender Strategie:
Die ResourceBundle API bietet insgesamt drei Ansatzpunkte zur Nutzung:
public class MyResourceBundle extends ListResourceBundle { // private static final Object[][] CONTENTS = new Object[][] { {"city", "London"}, {"town", "Dover"} }; @Override protected Object[][] getContents() { return CONTENTS; } } public class MyResourceBundle_de extends ListResourceBundle { // private static final Object[][] CONTENTS = new Object[][] { {"city", "Berlin"}, {"town", "Hannover"} }; @Override protected Object[][] getContents() { return CONTENTS; } } :
/** * Implementierung eines ResourceBundles unter Nutzung einer externen Resource, hier: Datenbanktabelle * (Pseudocode) * * Tabellenstruktur: * key – der Schlüssel (not null) * root – Standardwert (not null) * de – Spezialisierung deutsch * it - Spezialisierung italiano * : */ public abstract class DBResourceBundle extends ResourceBundle { // // Injektion der Resource Database etc. // private Properties properties; // public DBResourceBundle() { buildProperties(); } // protected abstract String getColumnName(); // public Enumeration getKeys() { return properties.propertyNames(); } // protected Object handleGetObject(String key) { return properties.getProperty(key); } // /** * Laden der Properties aus einer Datenbanktabelle. */ private void buildProperties() { // 1. lese alle Einträge der Ressourcentabelle wenn die Spalte getColumnName() nicht leer ist // 2. Für alle Einträge: properties.setProperty(schlüssel, wert); } } // Ausimplementierungen public class MyResourceBundle extends DBResourceBundle { protected String getColumnName() { return "root"; } } public class MyResourceBundle_de extends DBResourceBundle { protected String getColumnName() { return "de"; } } public class MyResourceBundle_it extends DBResourceBundle { protected String getColumnName() { return "it"; } } :
Diese Varianten dürfen dabei für die Definition von ResourceBundle-Hierarchien beliebig gemischt werden! Eigene Ausprägungen von Locale können auch verwendet werden, das Default Locale wird gegebenenfalls über die JVM Startparameter -Duser.language und -Duser.region vorgegeben (statt –Duser.region in der Form country_variant kann auch –Duser.country und –Duser.variant explizit festgelegt werden).
Sollen Textressourcen zur Laufzeit geändert werden, muss ResourceBundle.clearCache() gerufen werden. Alle ResourceBundle Instanzen werden verworfen und beim nächsten Zugriff durch ResourceBundle.getBundle neu geladen. Diese Dynamik setzt voraus, dass alle Zugriffe auf Ressourcen über ResourceBundle.getBundle initiiert werden, damit Änderungen im eigenen Code ankommen. Man sollte deshalb das Ablegen von ResourceBundles in Klassenvariablen unterlassen und immer ResourceBundle.getBundle nutzen. Die ResourceBundle-Implementierung ist für die Bereitstellung hoch dynamischer Textressourcen allerdings nicht geeignet, mit anderen Worten clearCache() wird nur selten gerufen.
Parametrisierung von Textressourcen - die MessageFormat API
Textressourcen mit Parametern sind mittels der MessageFormat API zu realisieren. Auch hier erfolgt die Internationalisierung der Formatierung auf der Basis von Locale.getDefault(). Beim Rendering der Parameter delegiert MessageFormat das Locale an die eingesetzten spezialisierten Formatter.
// eigentlich aus ResourceBundle holen, hier nur skizziert String text = "Um {1,time} am {1,date} haben Sie einen Betrag von {0,number,integer} Euro überwiesen."; // MessageFormat Instanzen können wiederverwendet werden MessageFormat messageFormat = new MessageFormat(text); : // Formatierung: String msg = messageFormat.format(new Object[]{ new Double(66.66), new Date()});
ResourceBundle in Java EE Umgebungen
ResourceBundle cached die Bundlehierarchien per Classloader, auf diese Weise sind die ResourceBundle der verschiedenen Java EE Anwendungsmodule voneinander isoliert. Die Initialisierung der ResourceBundle ist synchronisiert, da ResourceBundle nach der Initialisierung unveränderlich sind muss der Zugriff auf die eigentlichen Ressourcen nicht synchronisiert sein. ResourceBundle können also unter normalen Umständen (ResourceBundle.clearCache wird selten gerufen, kein exotisches Classloading im Container) problemlos in Java EE Umgebungen eingesetzt werden. Zu beachten ist auch: ResourceBundle.clearCache ist im gleichen Classloader-Context zu rufen, für welchen die Initialisierung erfolgen soll. Das erreicht man, indem man den Classloader (wenn er bekannt ist) explizit vorgibt, oder indem man ResourceBundle.clearCache im Context der Anwendung betreffenden Anwendung aufruft.
Der Aufruf von Locale.getDefault() bei der Verwendung von ResourceBundle und MessageFormat muss verhindert werden. Das Default-Locale der Serverumgebung sagt nichts über die Lokalisierung des Clients der Anfrage. Man muss sich also zunächst die gewünschte Lokalisierungsinformationen des Clients beschaffen und gegebenenfalls merken:
// Lesen der bevorzugten Nutzer-Locale aus den Angaben des Browser-Requests.. Locale usersPrefLocale = request.getLocale(); // .. oder alternativ, wenn der Browser nicht die korrekte Lokalisierung besitzen kann // aus anderen Quellen, hier mit einer hypothetischen getUsersPrefLocale-Funktion // Locale usersPrefLocale = someService.getUsersPrefLocale(userdaten); // Setzen des Nutzer-Locale an der Session session.setValue("preferredLocale", usersPrefLocale);Anschließend werden bei der Verwendung von ResourceBundle und MessageFormat diese Lokalisierungsangaben explizit verwendet:
// Lesen der Nutzer-Locale aus der Session Locale usersPrefLocale = (Locale) session.getValue("preferredLocale"); // Nutzung lokalisierter Bundles und Formater ResourceBundle messages = ResourceBundle.getBundle("MyResourceBundle", usersPrefLocale); MessageFormat messageFormat = new MessageFormat(messages.getString("MyResourceBundle"), usersPrefLocale); :Hinweise:
Blueprint: Designing Enterprise Applications
Java Tutorial on Internationalization