Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 3. Auflage
 <<    <     >    >>   API  Kapitel 17 - Utility-Klassen II

17.4 Internationalisierung und Lokalisierung



Seit dem JDK 1.1 bietet Java Unterstützung für das Internationalisieren von Anwendungen. Auf diese Weise können Programme geschrieben werden, die nicht nur im eigenen, sondern auch in anderen Ländern der Erde verwendbar sind. Verschiedene Länder und Regionen unterscheiden sich nicht nur durch die verwendete Sprache und die zugrunde liegenden Schrift- und Zahlzeichen, sondern auch durch unterschiedliche Kalender und Zeitzonen, abweichende Zahlen-, Zeit- und Datumsformatierungen, eigene Währungen, unterschiedliche Telefonnummern- und Anschriftenkonventionen und so weiter.

Die Begriffe Internationalisierung und Lokalisierung werden häufig synonym gebraucht oder miteinander verwechselt. Unter Internationalisierung wollen wir das Bündel technischer Maßnahmen verstehen, mit denen ein Programm für die Verwendung in verschiedenen Ländern und Sprachräumen vorbereitet wird. Unter Lokalisierung verstehen wir dagegen die konkrete Anpassung eines internationalisierten Programms an eine ganz bestimmte Sprache. Ein Teil der Internationalisierung eines Programmes ist es also beispielsweise, wenn Textkonstanten nicht mehr fest im Programm enthalten sind, sondern zur Laufzeit aus Ressourcendateien geladen werden. Die Lokalisierung hingegen besteht darin, eben diese Textressourcen in die jeweilige Sprache zu übersetzen.

Im angelsächsischen Sprachraum werden die beiden unhandlichen Begriffe meist abgekürzt. Statt »Internationalisierung« wird I18N gesagt, statt »Lokalisierung« L10N. Die Spielregel ist ganz einfach: Zwischen den ersten und letzten Buchstaben des jeweiligen Wortes steht die Gesamtzahl der Zeichen.

 Hinweis 

Internationalisierung ist ein nicht-triviales Thema, das viele Facetten hat. Einige Beispiele dafür, in welcher Weise es von Java unterstützt wird, sind:

17.4.1 Die Klasse Locale

Ausgangspunkt der Lokalisierung eines Java-Programms ist die Klasse Locale aus dem Paket java.util. Jede Instanz dieser Klasse identifiziert eine bestimmte geografische, kulturelle oder politische Region auf der Erde inklusive der Sprache, die dort üblicherweise verwendet wird. Ein Locale-Objekt kann wie folgt erzeugt werden:

public Locale(String language, String country)
java.util.Locale

Das erste Argument ist ein String mit dem Code für die Sprache, wie er im Standard ISO-639 definiert ist. Der Sprachcode wird kleingeschrieben und lautet z.B. "en" für englisch, "fr" für französich oder "de" für deutsch. Das zweite Argument gibt das Land gemäß dem Standard ISO-3166 an, hier werden stets große Buchstaben verwendet. So stehen beispielsweise "US" für die USA, "GB" für England, "FR" für Frankreich und "DE" für Deutschland.

Der Länderteil ist optional. Wird an seiner Stelle ein Leerstring übergeben, repräsentiert die Locale lediglich eine Sprache ohne spezifisches Land. "en" alleine steht dann beispielsweise für die englische Sprache, wie sie nicht nur in England, sondern auch in den USA oder Kanada verständlich wäre. Soll dagegen auf kanadische Besonderheiten abgestellt werden, ist als Land "CA" zu ergänzen. Für den französischsprachigen Teil von Kanada würde dagegen eine Locale aus "fr" und "CA" gebildet werden.

Neben Sprach- und Länderkennung kann eine Locale einen dritten Parameter haben. Dieser wird als Variante bezeichnet und kann dazu verwendet werden, Betriebssysteme oder Konfigurationen voneinander zu unterscheiden. So werden lokalisierte Hilfetexte unter UNIX möglicherweise mit anderen Zeichensätzen oder Hilfsprogrammen arbeiten als unter Windows oder auf dem Macintosh. Wir wollen darauf allerdings nicht weiter eingehen.

 Hinweis 

Die Klasse Locale bietet mit der statischen Methode getDefault die Möglichkeit, eine Locale zu beschaffen, die dem Land entspricht, in dem das laufende Programm ausgeführt wird. Mit getCountry kann der Länder-, mit getLanguage der Sprachcode und mit getVariant die Variante ermittelt werden:

public static Locale getDefault()

public String getLanguage()
public String getCountry()
public String getVariant()
java.util.Locale

Mit getDefault kann ein Programm zur Laufzeit ermitteln, in welchem Land es läuft und welche Sprache dort gesprochen wird. Weiterhin stellt die Klasse noch einige Konstanten mit vordefinierten Locale-Objekten für wichtige Länder und Sprachen zur Verfügung.

Das folgende Beispielprogramm ermittelt die Standard-Locale des aktuellen Rechners und die vordefinierten Locales für Deutschland, England und die USA und gibt sie auf der Console aus:

001 /* Listing1704.java */
002 
003 import java.util.*;
004 
005 public class Listing1704
006 {
007   public static void main(String[] args)
008   {
009     System.out.println("Default: " + Locale.getDefault());
010     System.out.println("GERMANY: " + Locale.GERMANY);
011     System.out.println("UK     : " + Locale.UK);
012     System.out.println("US     : " + Locale.US);
013   }
014 }
Listing1704.java
Listing 17.4: Darstellung einiger Locales

Die Ausgabe des Programm sieht hierzulande etwa so aus:

Default: de_DE
GERMANY: de_DE
UK     : en_GB
US     : en_US

17.4.2 Zahlen formatieren

In früheren Abschnitten wurde schon gezeigt, wie Fließkommazahlen erzeugt und verarbeitet werden können. Nach Umwandlung in einen String lassen sie sich mit den Methoden print und println auf der Console, in Dateien oder auf einer graphischen Oberfläche ausgeben - allerdings ohne die Möglichkeit, auf die Formatierung der Ausgabe Einfluß zu nehmen. Wir wollen uns in diesem Abschnitt die Klasse DecimalFormat aus dem Paket java.text ansehen und zeigen, wie Ganz- und Fließkommazahlen mit ihrer Hilfe formatiert werden können.

DecimalFormat ist eine Spezialisierung der Klasse NumberFormat und dient zur Formatierung von Ganz- oder Fließkommazahlen in Dezimaldarstellung unter Berücksichtigung länderspezifischer Besonderheiten. Der Aufrufer legt dabei die Anzahl der Vor- oder Nachkommastellen fest, und DecimalFormat entscheidet, ob ein Punkt oder Komma als Dezimaltrennzeichen verwendet werden soll.

Eine Instanz der Klasse DecimalFormat kann entweder mit new oder durch Aufruf einer der Factory-Methoden der Klasse NumberFormat erzeut werden:

public DecimalFormat(String pattern)
java.text.DecimalFormat

public static NumberFormat getNumberInstance()
public static NumberFormat getNumberInstance(Locale locale)
java.text.NumberFormat

Soll ein DecimalFormat-Objekt für die aktuelle Locale erzeugt werden, kann es durch Übergabe eines Formatstrings direkt instanziert werden. Wird es dagegen für eine andere Locale benötigt, muß es mit der statischen Methode getNumberInstance der Klasse NumberFormat erzeugt werden. In diesem Fall muß noch die Methode applyPattern aufgerufen werden, um den Formatstring zuzuweisen:

public void applyPattern(String pattern)
java.text.NumberFormat

getNumberInstance gibt einen Wert vom Typ NumberFormat zurück (der Basisklasse von DecimalFormat). Das ist zwar in westlichen Ländern in der Regel eine Instanz von DecimalFormat. Ganz sicher kann sich ein Programm aber nur sein, wenn es den Typ des Rückgabewerts vor der Konvertierung nach DecimalFormat mit instanceof überprüft.

 Warnung 

Der Formatstring definiert eine Maske zur Formatierung der Ausgabe. Er besteht aus zwei Teilen, die durch ein Semikolon voneinander getrennt sind. Der erste gibt die Formatierungsregeln für positive, der zweite für negative Zahlen an. Der zweite Teil kann auch ausgelassen werden. Dann werden negative Zahlen wie positive Zahlen mit vorangestelltem Minuszeichen formatiert. Jedes Zeichen im Formatstring regelt die Darstellung der korrespondierenden Ziffer. Der Formatstring kann folgende Zeichen enthalten:

Symbol Bedeutung
0 Eine einzelne Ziffer
# Eine einzelne Ziffer. Wird ausgelassen, falls führende Null.
. Dezimaltrennzeichen
, Tausendertrennzeichen
E Aktiviert Exponentialdarstellung
% Ausgabe als Prozentwert

Tabelle 17.1: Formatzeichen für DecimalFormat

Zusätzlich können beliebige Zeichen vorangestellt oder hinten angefügt werden; sie werden dann unverändert ausgegeben. Ungültige Formatstrings werden mit einer IllegalArgumentException quittiert. Falls nach einem Semikolon ein eigener Formatstring für negative Zahlen angegeben wird, muß er mit Ausnahme eines veränderten Prefixes und Suffixes genau dem Formatstring für positive Zahlen entsprechen.

Die Formatierung einer Zahl wird durch Aufruf von format ausgeführt:

public final String format(long number)
public final String format(double number)
java.text.DecimalFormat

Die Methode gibt es sowohl mit einem long-Parameter zur Ausgabe von Ganzzahlen als auch mit einem double zur Ausgabe von Fließkommazahlen. Da der Formatstring bereits bei der Konstruktion des Objekts übergeben wurde, kann format mehrfach aufgerufen werden, um nacheinander weitere Zahlen zu formatieren.

Das folgende Programm zeigt die beispielhafte Anwendung von DecimalFormat. Es gibt die Zahl 1768.3518 für die aktuelle Locale in unterschiedlichen Längen und Formatierungen auf der Console aus:

001 /* Listing1705.java */
002 
003 import java.text.*;
004 
005 public class Listing1705
006 {
007   public static void print(double value, String format)
008   {
009     DecimalFormat df = new DecimalFormat(format);
010     System.out.println(df.format(value));
011   }
012   public static void main(String[] args)
013   {
014     double value = 1768.3518;
015     print(value, "#0.0");
016     print(value, "#0.000");
017     print(value, "000000.000");
018     print(value, "#.000000");
019     print(value, "#,###,##0.000");
020     print(value, "0.000E00");
021   }
022 }
Listing1705.java
Listing 17.5: Anwendung von DecimalFormat

Die Ausgabe des Programms lautet:

1768,4
1768,352
001768,352
1768,351800
1.768,352
1,768E03

Sie kommt wie folgt zustande:

Wenn das Programm mit englischer Locale aufgerufen worden wäre, wären in der Ausgabe Punkte und Kommata vertauscht (denn im englischen Sprachraum werden Kommata als Tausenderseparatoren und Punkte als Dezimaltrennzeichen verwendet). Die Ausgabe wäre dann wie folgt gewesen:

1768.4
1768.352
001768.352
1768.351800
1,768.352
1.768E03
12345678.909

Neben der Ausgabesteuerung mit Hilfe des Formatstrings stellt DecimalFormat noch weitere Methoden zur Konfiguration der Ausgabe zur Verfügung. Beispiele sind etwa setGroupingSize zur Einstellung der Größe der Tausendergruppen oder setDecimalSeparatorAlwaysShown zur Festlegung, ob auch Ganzzahlen mit Dezimalzeichen ausgegeben werden sollen. Zudem bietet DecimalFormat eine Methode parse, mit der lokalisierte Zahleneingaben eingelesen werden können. Weitere Details zu diesen Methoden können in der API-Dokumentation nachgelesen werden.

 Hinweis 

17.4.3 Datum und Uhrzeit formatieren

Neben Zahlen lassen sich mit dem Paket java.text auch Datums- und Uhrzeitwerte unter Beachtung nationaler Besonderheiten formatieren und in einen String konvertieren. Die dafür vorgesehene Klasse DateFormat wird zunächst (analog zu NumberFormat) mit einer Factory-Methode instanziert und konfiguriert. Anschließend wird format aufgerufen, um den Ausgabestring zu erzeugen.

Bei der Instanzierung gibt es einige Varianten zu unterscheiden:

public static final DateFormat getDateInstance(
  int style,
  Locale locale
)

public static final DateFormat getTimeInstance(
  int style,
  Locale locale
)

public static final DateFormat getDateTimeInstance(
  int datestyle,
  int timestyle,
  Locale locale
)

public final String format(Date date)
java.text.DateFormat

Mit getDateInstance wird eine Instanz zur Ausgabe eines Datums erzeugt, getTimeInstance dient zur Ausgabe der Uhrzeit und getDateTimeInstance zur kombinierten Ausgabe von Datum und Uhrzeit. Alle Methoden erwarten ein Argument style, das mit einer der Konstanten SHORT, MEDIUM, LONG, FULL oder DEFAULT belegt werden muß. Es gibt an, wie lang die jeweilige Ausgabe später sein soll und welche Daten sie enthalten soll. So werden beispielsweise bei der kurzen Uhrzeit lediglich Stunden und Minuten ausgegeben, während in den übrigen Stufen auch die Sekunden und andere Informationen enthalten sind. Als zweites Argument wird die gewünschte Locale angegeben; für die Standard-Locale kann es auch ausgelassen werden.

Das folgende Programm formatiert das aktuelle Datum und die aktuelle Uhrzeit mit allen möglichen style-Parametern und gibt die Ergebnisse auf der Console aus:

001 /* Listing1706.java */
002 
003 import java.util.*;
004 import java.text.*;
005 
006 public class Listing1706
007 {
008   public static void print(Calendar cal, int style)
009   {
010     DateFormat df;
011     df = DateFormat.getDateInstance(style);
012     System.out.print(df.format(cal.getTime()) + " / ");
013     df = DateFormat.getTimeInstance(style);
014     System.out.println(df.format(cal.getTime()));
015   }
016 
017   public static void main(String[] args)
018   {
019     GregorianCalendar cal = new GregorianCalendar();
020     print(cal, DateFormat.SHORT);
021     print(cal, DateFormat.MEDIUM);
022     print(cal, DateFormat.LONG);
023     print(cal, DateFormat.FULL);
024     print(cal, DateFormat.DEFAULT);
025   }
026 }
Listing1706.java
Listing 17.6: Anwendung von DateFormat

Die Ausgabe des Programms lautet:

24.05.00 / 21:58
24.05.2000 / 21:58:55
24. Mai 2000 / 21:58:55 GMT+02:00
Mittwoch, 24. Mai 2000 / 21.58 Uhr GMT+02:00
24.05.2000 / 21:58:55

Leider ist nicht sehr genau dokumentiert, welche Felder des Datums bzw. der Uhrzeit in Abhängigkeit von dem style-Parameter tatsächlich ausgegeben werden, zudem ist die Formatierung länderspezifisch. Wenn es also auf eine präzise definierte Formatierung ankommt, muß ausprobiert werden, welche Ergebnisse jeweils zustande kommen, und die am besten passende Variante gewählt werden. Alternativ kann mit Hilfe der Klasse FieldPosition auf jedes einzelne Feld zugegriffen und so eine genauere Steuerung der Ausgabe erreicht werden.

 Warnung 

17.4.4 Laden von Ressourcen

Ein ausgeliefertes Java-Programm besteht in aller Regel nicht nur aus Bytecode in Form von .class-Dateien, sondern enthält weitere Dateien mit zusätzlichen Informationen. Dazu zählen beispielsweise Textkonserven und Grafikdateien für grafische Oberflächen, Sounddateien zur akustischen Untermalung von Programmereignissen oder Hilfsdateien mit Klartextmeldungen zu möglichen Programmfehlern. Diese zusätzlichen Informationen werden als Ressourcen bezeichnet und müssen zusammen mit dem Programm ausgeliefert werden. Die meisten von ihnen müssen lokalisiert werden.

Java stellt seit der Version 1.1 mit der Klasse ResourceBundle aus dem Paket java.util ein universelles Hilfsmittel zur Verwendung derartiger Ressourcen zur Verfügung. Die Grundidee ist dabei einfach: Ein ResourceBundle ist ein Java-Objekt, das eine Sammlung von Ressourcen zusammenfasst und auf Anfrage einzeln zur Verfügung stellt. Jede Ressource hat einen eindeutigen und für alle unterstützten Sprachvarianten identischen Namen, mit dem auf sie zugegriffen werden kann. Ein konkretes ResourceBundle ist stets sprachabhängig und enthält nur die Ressourcen für eine bestimmte Locale.

Damit ist ein ResourceBundle zunächst nicht viel mehr als eine Schlüssel-Wert-Collection wie beispielsweise Hashtable oder HashMap. Der Clou liegt in der Tatsache, daß der Name des ResourceBundle die Locale angibt, deren Daten er enthält. Während der vordere Teil fest und für alle lokalisierten Varianten gleich ist, ist der hintere Teil Locale-spezifisch und gibt Sprache, Land und gegebenenfalls Variante an. Lautet der feste Teil beispielsweise "ImageResources", wären "ImageResources_fr" und "ImageResources_en_US" die Namen für die französiche und US-englische Variante.

Die Methode getBundle

ResourceBundle stellt eine statische Methode getBundle zur Verfügung, mit der zu einem Basisnamen und einer vorgegebenen Locale ein ResourceBundle beschafft werden kann. Diese gibt es in unterschiedlichen Ausprägungen:

public static final ResourceBundle getBundle(String baseName)
  throws MissingResourceException

public static final ResourceBundle getBundle(
  String baseName,
  Locale locale
)
java.util.ResourceBundle

Die erste Variante besorgt ein ResourceBundle für die aktuelle Default-Locale, die zweite für die als Argument angegebene. In beiden Fällen wird wie folgt vorgegangen:

Ist beispielsweise Deutschland die Default-Locale und soll die Resource "MyTextResource" für Frankreich beschafft werden, sucht getBundle nacheinander nach folgenden Klassen:

  1. MyTextResource_fr_FR
  2. MyTextResource_fr
  3. MyTextResource_de_DE
  4. MyTextResource_de
  5. MyTextResource

Die Suche bricht ab, wenn die erste passende Klasse gefunden wurde. Ist überhaupt keine passende Ressource vorhanden, löst getBundle eine MissingResourceException aus. Dies ist auch der Fall, wenn die gefundene Klasse nicht aus ResourceBundle abgeleitet ist.

Eigene Ressourcenklassen erstellen

Eigene Resourcenklassen müssen aus ResourceBundle abgeleitet werden und die beiden (in der Basisklasse abstrakten) Methoden getKeys und handleGetObject überlagern:

public Enumeration getKeys()

protected Object handleGetObject(String key)
  throws MissingResourceException
java.util.ResourceBundle

handleGetObject liefert eine Ressource zu einem vorgegebenen Schlüssel, getKeys eine Aufzählung aller vorhandenen Schlüssel. Gibt es zu einem vorgegebenen Schlüssel keine Ressource, muß handleGetObject null zurückgeben.

Das folgende Listing zeigt eine Klasse SimpleTextResource, die zur Internationalisierung von einfachen Texten verwendet werden kann. Die lokalisierten Varianten können dabei sehr einfach realisiert werden, indem sie aus SimpleTextResource abgeleitet werden. Sie müssen dann lediglich im Konstruktor die Hashtable mit den gewünschten Schlüssel-/Textpaaren füllen.

001 /* SimpleTextResource.java */
002 
003 import java.util.*;
004 
005 public class SimpleTextResource
006 extends ResourceBundle
007 {
008   protected Hashtable data = new Hashtable();
009 
010   public Enumeration getKeys()
011   {
012     return data.keys();
013   }
014 
015   public Object handleGetObject(String key)
016   {
017     return data.get(key);
018   }
019 
020   public ResourceBundle getParent()
021   {
022     return parent;
023   }
024 }
SimpleTextResource.java
Listing 17.7: Die Klasse SimpleTextResource

Nun soll ein ResourceBundle mit dem Namen "MyTextResource" erstellt werden, das Übersetzungen zu den beiden Schlüsseln "Hi" und "To" liefert. Dazu definieren wir zunächst eine Klasse MyTextResource, die immer dann verwendet wird, wenn keine passende lokale Variante gefunden wird. In unserem Fall soll sie die Texte in englischer Sprache zur Verfügung stellen:

001 /* MyTextResource.java */
002 
003 public class MyTextResource
004 extends SimpleTextResource
005 {
006   public MyTextResource()
007   {
008     data.put("Hi", "Hello");
009     data.put("To", "World");
010   }
011 }
MyTextResource.java
Listing 17.8: Die Basisvariante MyTextResource

Des weiteren wollen wir eine allgemeine deutschsprachige und eine spezielle schweizerische Variante zur Verfügung stellen:

001 /* MyTextResource_de.java */
002 
003 public class MyTextResource_de
004 extends SimpleTextResource
005 {
006   public MyTextResource_de()
007   {
008     data.put("Hi", "Hallo");
009     data.put("To", "Welt");
010   }
011 }
MyTextResource_de.java
Listing 17.9: Die deutschsprachige Variante MyTextResource_de

001 /* MyTextResource_de_CH.java */
002 
003 public class MyTextResource_de_CH
004 extends SimpleTextResource
005 {
006   public MyTextResource_de_CH()
007   {
008     data.put("Hi", "Grüezi");
009     data.put("To", "Schweiz");
010   }
011 }
MyTextResource_de_CH.java
Listing 17.10: Die schweizerische Variante MyTextResource_de_CH

Will ein Client auf eine Ressource in einem ResourceBundle zugreifen, tut er das nicht durch direkten Aufruf von handleGetObject, sondern verwendet dazu eine der folgenden Methoden:

public Object getObject(String key)
  throws MissingResourceException

public String getString(String key)
  throws MissingResourceException

public final String[] getStringArray(String key)
  throws MissingResourceException
java.util.ResourceBundle

getObject liefert die Ressource als Object, getString als String und getStringArray als String-Array. Die letzten beiden Methoden dienen vornehmlich der Bequemlichkeit: sie rufen selber getObject auf und nehmen dem Aufrufer die anschließend erforderliche Typkonvertierung ab.

Das folgende Listing zeigt ein einfaches Testprogramm, das die Textressourcen für verschiedene Lokalisierungen ermittelt und auf der Console ausgibt. Das ResourceBundle wird beschafft, indem getBundle mit "MyTextResource" und der jeweils gewünschten Locale als Argument aufgerufen wird. Anschließend wird auf die übersetzten Texte mit Aufrufen von getString zugegriffen:

001 /* Listing1711.java */
002 
003 import java.util.*;
004 
005 public class Listing1711
006 {
007   public static void sayHello(Locale locale)
008   {
009     System.out.print(locale + ": ");
010     ResourceBundle textbundle = ResourceBundle.getBundle(
011       "MyTextResource",
012       locale
013     );
014     if (textbundle != null) {
015       System.out.print(textbundle.getString("Hi") + ", ");
016       System.out.println(textbundle.getString("To"));
017     }
018   }
019 
020   public static void main(String[] args)
021   {
022     sayHello(Locale.getDefault());
023     sayHello(new Locale("de", "CH"));
024     sayHello(Locale.US);
025     sayHello(Locale.FRANCE);
026   }
027 }
Listing1711.java
Listing 17.11: Test von MyTextResource

Die Ausgabe des Programms ist:

de_DE: Hallo, Welt
de_CH: Grüezi, Schweiz
en_US: Hallo, Welt
fr_FR: Hallo, Welt

Die Default-Locale war beim Testen "de_DE". Zwar wurde keine passende Klasse MyTextResource_de_DE definiert, aber durch den oben beschriebenen Fallback-Mechanismus liefert getBundle eine Instanz von MyTextResource_de. Bei der nächsten Lokalisierung wird die erforderliche Klasse MyTextResource_de_CH direkt gefunden, und die Ausgabe erfolgt entsprechend. Zu den letzten beiden Lokalisierungswünschen werden keine passenden Ressourcen gefunden. getBundle verwendet in beiden Fällen die Datei MyTextResource_de zur Default-Locale, und die Ausgaben erfolgen in deutscher Sprache.

Damit die Suche nach Ressourcen und die (im nächsten Abschnitt zu besprechende) Vaterverkettung richtig funktionieren, müssen zu einer Ressource auch stets alle allgemeineren Ressourcen vorhanden sein. Gibt es etwa - wie in unserem Beispiel - eine Ressource mit der Endung "_de_CH", so müssen auch die Ressourcen mit der Endung "_de" und die Basisressource ohne lokale Endung vorhanden sein.

 Warnung 

Um die Übersichtlichkeit zu verbessern, sollten Ressourcendateien in eigenen Verzeichnissen gehalten werden. Da sie mit Hilfe des Classloaders geladen werden, müssen sie in einem Verzeichnis abgelegt werden, in dem auch eine Klasse gefunden werden würde. Man könnte beispielsweise im Hauptpaket der Anwendung ein Unterverzeichnis resources anlegen und alle Ressourcendateien dort plazieren. Der im Programm zu verwendende Ressourcenname wird dann einfach vorne um "resources." ergänzt. Derartige Ressourcen, die über den Klassenpfad erreichbar sind, werden auch dann gefunden, wenn das Programm in Form einer .jar-Datei ausgeliefert wird.

 Tip 

Die Verkettung von ResourceBundles

Ein Aufruf von getObject wird zunächst an die Methode handleGetObject weitergeleitet. Ist deren Rückgabewert nicht null, wird er an den Aufrufer übergeben. Andernfalls wird die Anfrage an den Vater des ResourceBundles weitergereicht. Die Vaterverkettung erfolgt bei der Instanzierung von ResourceBundle-Objekten automatisch, indem das jeweils nächstmögliche allgemeinere ResourceBundle als Vater verwendet wird. So ist beispielsweise der Vater von "MyTextResource_de_DE" die Ressource "MyTextResource_de", und deren Vater ist "MyTextResource".

Durch diese Vaterverkettung muß ein spezielleres ResourceBundle nur die Ressourcen zur Verfügung stellen, die sich gegenüber dem Vater unterscheiden oder neu hinzugekommen sind. Alle unveränderten Ressourcen brauchen dagegen nicht erneut definiert zu werden. Soll beispielsweise eine "MyTextResource" für englischsprachige Lokalisierungen definiert werden, die sich nur durch die Übersetzung des Schlüssels "To" von der Basisressource unterscheidet, muß "Hi" nicht definiert werden:

001 /* MyTextResource_en.java */
002 
003 public class MyTextResource_en
004 extends SimpleTextResource
005 {
006   public MyTextResource_en()
007   {
008     data.put("To", "World of English");
009   }
010 }
MyTextResource_en.java
Listing 17.12: Die englische Variante MyTextResource_en

Nach dem Anlegen und Übersetzen dieser Klasse würde ein Aufruf von Listing 17.11 zu folgender Ausgabe führen:

de_DE: Hallo, Welt
de_CH: Grüezi, Schweiz
en_US: Hello, World of English
fr_FR: Hallo, Welt

Die Übersetzung von "Hi" kommt nun aus der Vater-Ressource MyTextResource.

Die Klasse PropertyResourceBundle

Die vorangegangenen Beispiele haben an Textkonserven exemplarisch gezeigt, wie man Ressourcen lokalisieren kann. Sollen einfache Texte übersetzt werden, gibt es aber noch einen einfacheren Weg. Das oben beschriebene Suchschema von getBundle war nämlich insofern unvollständig, als nach jedem einzelnen Schritt zusätzlich eine Datei desselben Names wie die aktuelle Klasse, aber mit der Erweiterung .properties, gesucht wird. Wird also beispielsweise in einem beliebigen Suchschritt die Klasse MyResource_de_DE nicht gefunden, so sucht getBundle vor dem Übergang zum nächsten Schritt nach einer Datei mit dem Namen MyResource_de_DE.properties.

Ist eine solche vorhanden, wird eine Instanz der Klasse PropertyResourceBundle erzeugt und an den Aufrufer zurückgegeben. PropertyResourceBundle liest die Eingabedatei (sie muß dasselbe Format wie die in Abschnitt 14.4.4 beschriebenen Property-Dateien haben) und speichert alle darin gefundenen Schlüssel-Wert-Paare ab. Anschließend können diese als gewöhnliche Text-Ressourcen vom Programm verwendet werden.

Um beispielsweise die vorigen Beispiele um eine französische Übersetzung zu erweitern, könnten wir einfach eine Datei MyTextResource_fr.properties anlegen und ihr folgenden Inhalt geben:

Hi=Salut
To=monde francais

Listing 17.11 würde nun folgende Ausgabe erzeugen:

de_DE: Hallo, Welt
de_CH: Grüezi, Schweiz
en_US: Hello, World of English
fr_FR: Salut, monde francais

 Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0.1
 <<    <     >    >>   API  © 1998-2003 Guido Krüger, http://www.javabuch.de