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

## 17.4 Internationalisierung und Lokalisierung

- 17.4 Internationalisierung und Lokalisierung
  - 17.4.1 Die Klasse Locale
  - 17.4.2 Zahlen formatieren
  - 17.4.3 Datum und Uhrzeit formatieren
  - 17.4.4 Laden von Ressourcen
    - Die Methode getBundle
    - Eigene Ressourcenklassen erstellen
    - Die Verkettung von ResourceBundles
    - Die Klasse PropertyResourceBundle

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:

- Mit Hilfe der Klasse `Locale` wird ein Land oder eine Region der Erde bezeichnet.
- Lokalisierte Ressourcen sorgen dafür, daß die statischen Textkonserven eines Programms dem jeweiligen Zielland ohne Änderung des eigentlichen Programmcodes angepaßt werden können.
- Die Klassen `Calendar` und `TimeZone` abstrahieren Kalender und Zeitzonen.
- Im Paket `java.text` befindet sich eine Vielzahl von Klassen zur Formatierung von Zahlen, Datums- und Uhrzeitwerten und Währungen.
- Ein- und Ausgabe-Streams unterstützen Konvertierungen von und zu länderspezifischen Zeichensätzen.
- Es gibt Unterstützung zur Eingabe von komplizierten Schriftzeichen, wie sie beispielsweise in der japanischen, chinesischen oder koreanischen Sprache verwendet werden.

### 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:

- "#0.0": Mindestens eine Vorkommastelle, genau eine Nachkommastelle
- "#0.000": Wie vorher, aber genau drei Nachkommastellen
- "000000.000": Wie vorher, aber mindestens sechs sichtbare Vorkommastellen
- "#.000000": Möglichst keine Vorkommastellen (was nur möglich ist, wenn die Zahl kleiner als 1 ist), aber genau sechs Nachkommastellen
- "#,###,##0.000": Mindestens eine Vorkommastelle, genau drei Nachkommastellen, größere Zahlen mit Tausenderseparatoren
- "0.000E00": Eine Vor- und drei Nachkommastellen, Exponentialdarstellung mit mindestens zwei Ziffern im Exponent

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:

- Zunächst wird versucht, eine genau zur gewünschten `Locale` passende Ressourceklasse zu finden.
- Ist eine solche nicht vorhanden, wird der Ressourcenname schrittweise verallgemeinert. Soweit vorhanden, wird zunächst die Variante, dann der Länder- und schließlich der Sprachteil entfernt bis nur noch der Basisname übrigbleibt.
- Falls immer noch keine passende Ressourcenklasse gefunden wurde, wird derselbe Ablauf für die Default-`Locale` wiederholt.

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
<< | < | > | >> | API | © 1998-2002 Guido Krüger, http://www.javabuch.de