Isolierung Locale-spezifischer
Daten

Isolierung Locale-spezifischer Daten

Locale-spezifische Daten müssen an die Sprach- und Landeskonventionen des jeweiligen Endbenutzers angepasst werden. Der in einer Benutzerschnittstelle anzuzeigende Text ist das wohl offensichtlichste Beispiel derart Locale-spezifischer Daten. Thema dieses Unterkapitels ist die Erzeugung und das Laden von ResourceBundle-Objekten bzw. der Zugriff auf derartige Objekte, mit deren Hilfe Locale-spezifische Daten verwaltet werden können.

Die Klasse ResourceBundle

Aus konzeptioneller Sicht ist jedes ResourceBundle-Objekt eine Menge von Sub-klassen, die zueinander in Beziehung stehen und die denselben Basisnamen aufweisen. Die folgende Aufzählung illustriert die Verwendung dieser Klassen. Basisname der Klasse, die Beschriftungen von Buttons verwaltet, ist ButtonLabel. Die auf den Basisnamen folgenden Zeichen repräsentieren den Sprach- und den Landes-Code bzw. eine Variante einer Localen. ButtonLabel_de_DE entspricht also der Localen, die durch den Sprach-Code für Deutsch (de) und durch den Landes-Code für Deutschland (DE) spezifiziert wird.

Um ein geeignetes ResourceBundle-Objekt auszuwählen, wird die Methode ResourceBundle.getBundle aufgerufen. Im folgenden Beispiel wird das ResourceBundle-Objekt von ButtonLabel für die Locale ausgewählt, die der Sprache Deutsch, dem Land Deutschland und der Plattform UNIX entspricht.

code 

Locale currentLocale = new Locale("de", "DE", "UNIX");
ResourceBundle introLabels = ResourceBundle.getBundle("ButtonLabel", currentLocale);

Wenn eine ResourceBundle-Klasse für eine ausgewählte Locale nicht existiert, versucht die Methode getBundle, die bestmögliche Entsprechung zu laden. Wenn also bspw. keine Klasse für die Kombination ButtonLabel_de_DE_UNIX existiert, sucht die Methode getBundle in folgender Reihenfolge nach Alternativen:

  • ButtonLabel_de_DE_UNIX
  • ButtonLabel_de_DE
  • ButtonLabel_de
  • ButtonLabel

Findet die getBundle-Methode in dieser Reihenfolge keine Entsprechung, so wird eine weitere Suche in derselben Art und Weise in der Standard-Localen durchgeführt. Schlägt auch dies fehl, so wird eine Fehlermeldung ausgelöst (MissingResourceException).

Es ist empfehlenswert, stets eine Basisklasse ohne weitere Suffixe anzugeben, wie im Beispiel ButtonLabel. In diesem Fall ist eine Fehlersituation leicht zu vermeiden.

Die abstrakte Klasse ResourceBundle beinhaltet zwei Subklassen: ListResourceBundle und PropertyResourceBundle. Die auszuwählende Subklasse hängt zum einen vom Datentyp und zum anderen von der Art, in der sie lokalisiert werden soll, ab.

Ein PropertyResourceBundle-Objekt greift auf ein oder mehrere Properties Files zu. Übersetzbare Texte sollten daher immer in Properties Files gespeichert werden. Da diese ausschließlich aus Text bestehen und nicht Teil des Java-Quellcodes sind, können sie in beliebigen Texteditoren von Übersetzern erzeugt werden. Hierzu ist keinerlei Programmiererfahrung notwendig.

Properties Files können ausschließlich aus Werten für String-Objekte bestehen. Müssen hingegen andere Datentypen gespeichert werden, so sind ListResourceBundle-Objekte zu verwenden. Der Zugriff von PropertyResourceBundle-Objekten auf Properties Files erfolgt analog zum Zugriff durch ResourceBundle-Objekte.

Die Klasse ListResourceBundle verwaltet Ressourcen in Form einer Liste. Jedes ListResourceBundle-Objekt greift auf eine .class-Datei zu. In Erweiterung zu ResourceBundle-Objekten kann in einem ListResourceBundle-Objekt jedes Locale-spezifische Objekt gespeichert werden. Um eine zusätzliche Locale zu unterstützen, muss eine neue Quelldatei erzeugt und in eine .class-Datei kompiliert werden. Da aber Übersetzer üblicherweise keine Programmierer sind, sollten Textobjekte, die eine Übersetzung erfordern, nicht in einem ListResourceBundle-Objekt verwaltet werden.

Die Klasse ResourceBundle ist extrem flexibel. Wenn bspw. zunächst ein Locale-spezifischer Text in ein ListResourceBundle-Objekt geladen wurde, anschließend aber entschieden wurde, ein PropertyResourceBundle-Objekt zu verwenden, so ist die Auswirkung auf den Code minimal. Der folgende Aufruf von getBundle lädt bspw. ein ResourceBundle-Objekt für eine geeignete Locale, in der das ButtonLabel-Objekt von einer .class-Datei oder von einem Properties File verwaltet wird:

code 

ResourceBundle introLabels = ResourceBundle.getBundle("ButtonLabel", currentLocale);

ResourceBundle-Objekte enthalten eine Liste von (Schlüssel-Wert)-Paaren. Ein Schlüssel wird immer als Zeichenkette angegeben, dessen Wert anschließend aus dem ResourceBundle-Objekt geladen wird. Der Wert ist das Locale-spezifische Objekt. Im folgenden Beispiel werden die Schlüssel OkSchluessel und AbbrechenSchluessel verwendet:

code 

class ButtonLabel_de extends ListResourceBundle {

    // Deutsche Version
    public Object[][] ladeInhalte() {

      return inhalte;

    }
    static final Object[][] inhalte= {

      {"OkSchluessel", "OK"},
      {"AbbrechenSchluessel", "Cancel"},

    }

}

Um die Zeichenkette OK aus dem ResourceBundle-Objekt zu laden, muss der geeignete Schlüssel angegeben werden, wenn getString aufgerufen wird:

code 

String okLabel = ButtonLabel.getString("OkSchluessel");

Es sollte darauf hingewiesen werden, dass das vorangehende Beispiel stark vereinfacht ist, da die String-Werte hart im Quellcode verdrahtet sind. Dies ist allerdings keine gute Programmierpraxis, da bspw. Übersetzer mit Properties Files arbeiten, die getrennt vom Quellcode sind. Ein Properties File beinhaltet stets Paare aus Schlüssel und Wert. Ein Beispiel hierfür ist im Folgenden beschrieben:

code 

OkSchluessel = OK
AbbrechenSchluessel= Abbrechen

Vorbereitung von ResourceBundle-Objekten

Wird eine Anwendung entwickelt, die über eine Benutzeroberfläche verfügt, so sind meist eine Vielzahl von Locale-spezifischer Objekte enthalten. Zu Beginn sollte daher der Programm-Code auf Objekte überprüft werden, die mit der Locale variieren. Diese Objekte können bspw. Instanzen folgender Klassen sein: String, Component, Graphics, Image, Color oder AudioClip. Diese Liste beinhaltet keine Objekte, die Zahlen, Datumsangaben oder Zeiten repräsentieren. Der Grund hierfür liegt darin, dass das Format dieser Daten zwar mit der Locale variiert, nicht aber die Objekte selbst. Anstelle einer Isolierung dieser Objekte in einem ResourceBundle-Objekt findet eine Formatierung durch spezielle Locale-sensitive Klassen statt. Dies wird im Folgenden detailliert erklärt.

Im Allgemeinen werden die Objekte, die in einem ResourceBundle-Objekt gespeichert sind, vorab definiert; sie sind in einer fertig gestellten Anwendung (auch zur Laufzeit) nicht veränderbar. Es versteht sich aber von selbst, dass ein String-Objekt, das von einem Endbenutzer in einem Textfeld eingegeben werden soll, nicht in einem ResourceBundle-Objekt isoliert werden sollte, da eine derartige Zeichenkette von Tag zu Tag variieren kann. Die Zeichenkette ist daher eher für die jeweilige Programmausführung als für die Locale, in der das Programm läuft, spezifisch.

Üblicherweise sind die meisten Objekte, die in einem ResourceBundle-Objekt isoliert werden, String-Objekte. Nicht alle String-Objekte sind allerdings Locale-spezifisch. Wenn ein String bspw. niemals einem Endbenutzer präsentiert wird, muss er auch nicht internationalisiert werden. Es wird erkennbar, dass die Entscheidung, welche String-Objekte zu internationalisieren sind, nicht immer eindeutig ist. Ein Beispiel hierfür sind Log-Dateien, die ein Endbenutzer normalerweise nicht einsieht. Das Problem liegt hierbei in den vielfältigen Auslegungsmöglichkeiten des Wortes normalerweise.

ResourceBundle-Objekte können organisiert werden, indem jedes mit einer anderen Kategorie von Objekten geladen wird. So können bspw. alle Beschriftungen von Buttons in ein ResourceBundle-Objekt ButtonLabelsBundle geladen werden. Aus der Kategorisierung ergeben sich die folgenden Vorteile:

  • Der Code ist einfacher lesbar und damit auch einfach zu warten.
  • Das Einladen eines Objekts aus einem ResourceBundle-Objekt ist schneller, wenn das ResourceBundle-Objekt lediglich eine kleine Anzahl von Objekten enthält.
  • Übersetzer können einfacher mit kleineren Properties Files arbeiten.

Die meisten Locale-spezifischen Daten bestehen aus String-Objekten. Wenn diese Objekte übersetzt werden müssen, so können sie in einem ResourceBundle-Objekt gespeichert werden, das auf Properties Files zurückgreift. Übersetzer können dann zusätzliche Sprachen anbieten, indem neue Properties Files generiert werden.

ResourceBundle-Objekte und Properties Files

Anhand dieses Abschnitts wird ein Java-Programm entwickelt, das die erklärten Konzepte verdeutlicht. Hierzu sind die folgenden Schritte auszuführen:

  1. Erzeugung eines Standard-Properties-Files
    Es sollte stets ein Standard-Properties-File generiert werden. Der Name der Datei muss hierzu mit dem Basisnamen des
    ResourceBundle-Objekts beginnen und mit dem Suffix .properties enden. In diesem Beispiel ist der Basisname ButtonsBundle. Das Properties File muss daher ButtonsBundle.properties heißen. Inhalt der Datei sind die folgenden Zeilen:

code 

# Dies ist das Standard-ButtonsBundle.properties File
b1 = OK
b2 = Abbrechen
b3 = Ende

 

    Es ist zu beachten, dass Kommentarzeilen immer mit dem Doppelkreuz (#) beginnen. Die anderen Zeilen beinhalten die Paare aus Schlüssel und dazugehörigem Wert. Es ist allerdings darauf zu achten, den Schlüssel nach der Definition nicht weiter zu verändern, da dieser meist vom Quellcode referenziert wird. Die Werte hingegen werden von Übersetzern verändert, wenn neue Properties Files für weitere Sprachen geschaffen werden.

  • Erzeugung weiterer Properties Files
    Um eine zusätzliche Locale zu unterstützen, müssen neue Properties Files angelegt werden, die die übersetzten Werte beinhalten. Änderungen am Source-Code sind dazu nicht notwendig, da das Programm auf die Schlüssel, nicht aber auf die Werte zugreift. Soll bspw. die englische Sprache unterstützt werden, so würden die Werte aus der Datei
    ButtonsBundle.properties übersetzt und in der Datei ButtonsBundle_en_US.properties gespeichert werden. Hierzu ist zu bemerken, dass der Name der Datei aus dem Basisnamen und dem Suffix .properties besteht, zusätzlich aber aus dem Sprach- und dem Landes-Code (hierzu siehe Tab. 6-1 und Tab. 6-2). Der Inhalt der Datei ButtonsBundle_en_US.properties sieht dann folgendermaßen aus:
  • code 

    # Dies ist die Datei ButtonsBundle_en_US.properties
    b1 = OK
    b2 = Cancel
    b3 = End

    1. Spezifikation der Localen
      Das Beispielprogramm erzeugt folgendes Locale-Objekt:

    code 

    Locale[] unterstuetzteLocale = {

      new Locale("de","DE"),
      new Locale("en","US")

    }
    Locale derzeitigeLocale = new Locale("de","DE");

      Die Aufrufe der Locale-Konstruktoren spezifizieren jeweils Sprach- und Landes-Code. Diese Codes stimmen mit den Properties Files überein, die in den ersten beiden Schritten erzeugt wurden.

    • Erzeugung des ResourceBundle-Objekts
      In diesem Schritt wird erkennbar, wie die Locale, die Properties Files und das
      ResourceBundle-Objekt zueinander in Beziehung stehen. Um ein ResourceBundle-Objekt zu erzeugen, muss die getBundle-Methode aufgerufen werden, die den Basisnamen und die Locale angibt:
    • code 

      ResourceBundle labels = ResourceBundle.getBundle("ButtonsBundle",derzeitigeLocal e);

        Die Methode getBundle überprüft zuerst, ob eine .class-Datei existiert, die dem Basisnamen entspricht. Ist dies nicht der Fall, so wird nach Properties Files gesucht. In diesem Beispiel findet getBundle daher die entsprechenden Properties Files. Nach erfolgreicher Lokalisierung gibt getBundle ein PropertyResourceBundle-Objekt zurück, das mit den Paaren aus Schlüssel und Wert aus dem Properties File geladen wurde.
        Wenn kein Properties File für eine Locale existiert, so sucht die Methode
        getBundle nach einem Properties File, das dem gewünschten am nächsten kommt. Dieses Vorgehen wurde bereits beschrieben.

      • Einladen des lokalisierten Textes
        Um die übersetzten Werte aus dem
        ResourceBundle-Objekt abzufragen, muss die getString-Methode verwendet werden:
      • code 

        String wert = buttons.getString(schluessel);


          Der String, der als Ergebnis von
          getString zurückgeliefert wird, entspricht dem als Argument angegebenen Schlüssel. Der String ist dann auch in der geeigneten Sprache, gesetzt den Fall, dass ein Properties File für die angegebene Locale existiert. Da sich die Schlüssel nicht verändern, muss auch der Aufruf von getString niemals verändert werden.

        • Iteration über alle Schlüssel
          Wenn die Werte aller Schlüssel eines
          ResourceBundle-Objekts geladen werden sollen, muss die Methode getKeys verwendet werden. Diese Methode liefert eine Aufzählung (Enumeration-Objekt) aller Schlüssel des ResourceBundle-Objekts als Ergebnis zurück. Anschließend kann über das Enumeration-Objekt iteriert werden und jeder Wert mit der getString-Methode geladen werden. Das folgende Programmsegment illustriert dieses Vorgehen:
        • code 

          ResourceBundle buttons = ResourceBundle.getBundle("ButtonsBundle",derzeitigeLocal e);
          Enumeration buendleSchluessel = buttons.getKeys();
          while (buendleSchluessel.hasMoreElements()) {

            String schluessel = (String)buendleSchluessel.nextElement();
            String wert = buttons.getString(schluessel);
            System.out.println("Schluessel = " + schluessel + ", " + "Wert = " + wert);

          }

          1. Ausführung des Programms
            Setzt man das Programm zusammen und führt es aus, so ergibt sich die folgende Ausgabe. Die erste Gruppe der Zeilen zeigt die Werte, die
            getString für verschiedene Locale-Objekte zurückliefert. Die zweite Zeilengruppe zeigt die letzten drei Zeilen an, wenn mit der getKeys-Methode über die Schlüssel iteriert wird.

          code 

          Locale = de_DE, Schluessel = b2, Wert = Abbrechen
          Locale = en_US, Schluessel = b2, Wert = Cancel

           

          Schluessel = b3, Wert = Ende
          Schluessel = b2, Wert = Abbrechen
          Schluessel = b1, Wert = OK

          Verwendung von ListResourceBundle-Objekten

          In diesem Abschnitt wird anhand eines Beispiels schrittweise die Verwendung des ListResourceBundle-Objekts dargestellt.

          1. Erzeugung der ListResourceBundle-Subklassen
            Ein
            ListResourceBundle-Objekt greift grundsätzlich auf eine .class-Datei zu. Im ersten Schritt muss daher eine derartige Datei für jede unterstützte Locale generiert werden. Im Beispiel ist der Basisname des ListResourceBundle-Objekts AdressenBundle. Wie im vorangegangenen Beispiel werden zwei Locale-Objekte unterstützt, für die die Dateien AdressenBundle_de_DE.class und AdressenBundle_en_US.class erzeugt werden müssen. Die Klasse AdressenBundle ist im folgenden Code definiert. Wie bei Properties Files besteht auch der Klassenname aus dem Basisnamen und den Sprach- und Landes-Codes. Innerhalb der Klasse wird eine zweidimensionale Liste mit den Paaren aus Schlüssel und Wert initialisiert. Als Schlüssel werden in diesem Beispiel Name, Adresse und Alter verwendet. Die Schlüssel müssen als String-Objekte erzeugt werden und in jeder Klasse der Menge AdressenBundle identisch sein. Die Werte können einen beliebigen Objekttyp haben.

          code 

          import java.util.*;
          public class AdressenBundle_de_DE extends ListResourceBundle {

            public Object[][] ladeInhalte() {

              return inhalte;

            }
            private Object[][] inhalte= {

              {"Name", new String ("Fischer")},{"Adresse", new String("KOM - TU Darmstadt")},{"Alter", new Integer(29)},

            }

          }

          1. Spezifikation der Localen
            Die Spezifikation der Localen erfolgt analog zum vorangegangenen Beispiel:

          code 

          Locale[] unterstuetzteLocales = {

            new Locale("en","US"),
            new Locale("de","DE")

          };

            Jede Locale entspricht einer der AdressenBundle-Klassen. Die deutsche Locale, die mit de und DE angegeben wird, entspricht daher der Datei AdressenBundle_de_DE.class.

          • Erzeugung des ResourceBundle-Objekts
            Auch die Generierung des
            ListResourceBundle-Objekts erfolgt analog zum vorangegangenen Beispiel:
          • code 

            ResourceBundle stats = ResourceBundle.getBundle("AdressenBundle",derzeitigeLoca le);

            1. Laden der internationalisierten Objekte
              Nachdem ein
              ListResourceBundle-Objekt für die Locale erzeugt wurde, können Objekte mittels der Schlüssel eingeladen werden. Mittels der folgenden Programmzeile kann das Alter geladen werden, indem getObject mit dem Schlüsselparameter Alter aufgerufen wird. Es ist darauf zu achten, dass getObject ein Objekt zurückgibt, weshalb eine Typumwandlung (Casting) auf Integer erfolgen muss:

            code 

            Integer alter = (Integer)adressen.getObject("Alter");


SPNavRight SPNavRight SPNavRight
BuiltByNOF