Formatierung und Internationalisierung

Inhalt

 

 

Applets in Archiven

Programmeinstellungen

Programmeinstellungen lassen sich am leichtesten als Zeichenketten in Dateien speichern. Solche Einstellungen betreffen etwa Flags, die den Programmablauf steuern. Es wird hier zunächst angenommen, dass die Einstellungen vom Benutzer geändert werden können. Diese Dateien werden daher nicht als Ressourcen behandelt.

Will man Einstellungen einlesen, die nicht änderbare Ressourcen sind, so kann man natürlich auf diese Dateien wie schon beschrieben zugreifen. Bei Menütexten oder auszugebenden Meldungen sollte man allerdings erwägen, die Internationalisierungstools zu verwenden, die weiter unten beschrieben werden.

Zunächst werden Einstellungen in Instanzen von Properties gespeichert. Dieser Datentyp ist ein Kind von Hashtable. Es existieren allerdings Funktionen zum Lesen und Schreiben auf Streams. Property-Enthalten die Properties zeilenweise wie folgt

Test=true

Dann stehen links vom Gleichheitszeichen die Schlüssel und rechts die Werte der Ressourcen. Das folgende Programm verwendet eine Datei test.properties vom Home-Verzeichnes des Benutzers.

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Properties;

public class Test
{

    public static void main (String args[])
    {
        new Test();
    }

    public Test ()
    {
        Properties P = new Properties();
        String filename = System.getProperty("user.home")
                + "/test.properties";

        try
        {
            // Lies Properties ein:
            FileInputStream in = new FileInputStream(filename);
            P.load(in);
            in.close();
        }
        catch (Exception e)
        {
            System.out.println(e);
        }

        try
        {
            // Drucke einige Properties:

            System.out.println(P.getProperty("Tes"));
            System.out.println(P.getProperty("Test"));
            System.out.println(P.getProperty("Test", "no value"));
            System.out.println(toBoolean(P.getProperty("Test", "false")));

            // Fuege eine Property hinzu
            P.put("Test", "true");

            // Speichere die Properties wieder ab:
            FileOutputStream out = new FileOutputStream(filename);
            P.store(out, "Test Properties");
            out.close();
        }
        catch (Exception e)
        {
            System.out.println(e);
        }
    }

    public static boolean toBoolean (String s)
    // Intepretiere true oder false
    {
        return s.equals("true");
    }
}

Dieses Programm sollte man zweimal laufen lassen. Beim ersten Mal existiert die Datei test.properties im Homeverzeichnis des Benutzers noch nicht. Daher wird folgendes ausgegeben.

java.io.FileNotFoundException: C:\Users\reneg\test.properties 
   (Das System kann die angegebene Datei nicht finden)
null
null
no value
false

Nicht existierende Properties haben den Wert null. Man kann aber einen Default-Wert angeben. Beim zweiten Durchlauf existiert die Datei.

#Test Properties 
#Sat Aug 19 09:30:15 CEST 2023 
Test=true

Es wird nun folgendes ausgegeben.

null
true
true
true

Einige Properties werden vom System vorgegeben und sind mit Hilfe der System-Klasse erreichbar. Das folgende Programm listet alle diese System-Properties auf. Es zeigt außerdem, wie man eine Enumeration aller Keys in einer HashTable bekommt.

import java.util.Enumeration;
import java.util.Properties;

public class Test
{
    public static void main (String args[])
    {
        Properties P = System.getProperties();
        Enumeration<Object> e = P.keys();
        while (e.hasMoreElements())
        {
            String key = (String) e.nextElement();
            String value = (String) P.get(key);
            System.out.println(key + "=" + value);
        }
    }
}

Die Anfang der Ausgabe dieses Programm auf meinem System ist der folgende.

java.specification.version=20
sun.cpu.isalist=amd64
sun.jnu.encoding=Cp1252
java.vm.vendor=Oracle Corporation
sun.arch.data.model=64
user.variant=
java.vendor.url=https://java.oracle.com/
java.vm.specification.version=20
os.name=Windows 11
...

Einige dieser Properties sind sehr gut für Applikationen verwendbar. Z.B. lässt sich die verwendete Java-Version abfragen. An anderer Stelle verwenden wir file.separator und user.home verwendet.

Internationalisierung

Java-Programme sollen leicht auf jedem Rechner der Welt einsetzbar sein. Bei der Ein- und Ausgabe von Texten haben wir schon die Verwendung von Datei-Encoding kennengelernt. Nun gibt es aber weit mehr nationale Einstellungen, wie Zahlenformate, Datum usw. Außerdem soll ein Programm Menütexte und Fehlermeldungen internationalisieren.

Zu diesem Zweck verwendet Java die Klasse Locale.  Es gibt aber dort Konstanten für diverse Lände, die man direkt zur initialisierung des von NumberFormat verwenden kann.

Der eingestellte Sprachraum ist nun dafür verantwortlich, wie etwa Zahlen formatiert werden. Die Klasse, die Zahlen formatiert, ist NumberFormat. Man kann nicht direkt eine Instanz dieser Klasse anlegen, sondern benötigt dazu deren statische Methode getInstance() (und Vewandte). Mit Hilfe einer Instanz (die auch mit einem anderen Sprachraum initialisiert werden kann), kann man Zahlen lokal formatieren. Als Beispiel vergleichen wir englische und deutsche Ausgaben.

import java.text.NumberFormat;
import java.util.Locale;

public class Test
{
    public static void main (String args[])
    {
        // lokale Ausgabe (hier deutsch)
        NumberFormat numberformat = NumberFormat.getInstance();

        System.out.println(numberformat.format(Math.PI));
        numberformat.setMaximumFractionDigits(16);
        System.out.println(numberformat.format(Math.PI));
        numberformat.setGroupingUsed(true); // Gruppiert Dreiergruppen von Zahlen
        System.out.println(numberformat.format(1000000000.01));

        // englische Ausgabe (US Format):
        numberformat = NumberFormat.getInstance(Locale.ENGLISH);

        System.out.println(numberformat.format(Math.PI));
        numberformat.setMaximumFractionDigits(16);
        System.out.println(numberformat.format(Math.PI));
        numberformat.setGroupingUsed(true);
        System.out.println(numberformat.format(1000000000.01));
    }
}

Die Ausgaben unterscheiden sich durch die Verwendung von Komma und Punkt. Allerdings kann auch ein lokaler Computer anders eingestellt worden sein.

3,142
3,141592653589793
1.000.000.000,01
3.142
3.141592653589793
1,000,000,000.01

Offenbar ist der Sprachraum auch wichtig für Zahleingaben. Dazu fügen wir folgende Zeilen hinzu

        try
        {
            double x = numberformat.parse(numberformat.format(1000000000.01))
                    .doubleValue();
            System.out.println(x);
        }
        catch (ParseException e)
        {
        }

Die Ausgabe zeigt, dass ein Formatierer seine eigenen Ausgabe lesen kann (auch wenn sie z.B. Gruppierungen enthält). Zurückgegeben wird ein Objekt Number, dessen Methoden man zur Umformatierung in Zahlen verwenden kann.

Die Alternative

System.out.println(Double.valueOf("100.23").doubleValue());

funktioniert nur mit Zahlen, die im Englischen Format mit Dezimalpunkt formatiert sind. Die Wrapper-Klasse Double für den eingebauten Datentyp double macht aus dem eingebauten Typ ein Objekt. Sie ist dazu gedacht, double-Werte in Datenstrukturen zu speichern, die nur Objekte akzeptieren, wie zum Beispiel Hashtables. Double hat die gezeigte statistische Methode zur Umwandlung von String nach Double.

Es existieren auch Formatierer für Währungseinheiten, Prozentwerte und Daten. Wir demonstrieren alle drei in einem Programm.

import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.Date;

public class Test
{
    public static void main (String args[])
    {
        NumberFormat price = NumberFormat.getCurrencyInstance();
        NumberFormat percent = NumberFormat.getPercentInstance();
        System.out.println(price.format(12.31));
        percent.setMinimumFractionDigits(2);
        System.out.println(percent.format(0.1231));

        DateFormat date = DateFormat.getDateInstance(DateFormat.SHORT);
        System.out.println(date.format(new Date()));
        date = DateFormat.getDateTimeInstance(
                DateFormat.LONG, DateFormat.LONG);
        System.out.println(date.format(new Date()));
    }
}

Die Ausgabe ist

12,31 DM
12,31%
29.06.98
29. Juni 1998 11:46:10 GMT+02:00

Die Klasse Date behandelt ein Datum. Sie ist insbesondere von der aktuellen Zeitzone betroffen.

Ein weiteres Gebiet für Internationalisierung ist die Ausgabe von Programmen in internationaler Form. Als Beispiel zeigen wir hier ein japanisches Java-Programm.

Wie man sieht, sind alle Menütexte japanisch. Das gleiche sollte für Fehlermeldungen und Hilfetexte gelten.

Um ein solches Programm zu erzeugen, sollte man die verwendeten Strings aus einer Ressource auslesen. Dazu verwendet man Property-Dateien, deren Namen aus einem String mit angehängtem Sprachraum und der Dateierweiterung .properties besteht. Ein Beispiel ist Test_de.properties. Java wählt diese Ressourcen-Datei automatisch aus, wenn man sich im deutschen Sprachraum befindet (oder Local.setDefault entsprechend eingestellt hat) und Ressourcen-Bündel verwendet. Dies geschieht am zweckmäßigsten in einer statischen Variable der Hauptklasse.

Als Beispiel erstellt man in dem Verzeichnis, wo sich die Klasse verwendet zwei Property-Dateien Test.properties und Test_de.properties mit einer Property Test, die einmal den Wert English und einmal den Wert Deutsch hat. Auf einem deutschen System wird die zweite verwendet. In jedem anderen Land die erste.

import java.util.ResourceBundle;

public class Test
{
    static ResourceBundle B; // verwendetes Bundle

    public static void initBundle ()
    // Initialisiere B
    {
        try
        {
            B = ResourceBundle.getBundle("Test");
        }
        catch (Exception e)
        {
            B = null;
        }
    }

    public static void main (String args[])
    // Zirkel wird als Applikation aufgerufen
    {
        initBundle();

        if (B != null) System.out.println(B.getString("Test"));
    }

}

Übungsaufgaben

  1. Erweitern Sie die Klasse Help um die Fähigkeit, automatisch die lokale Hilfedatei aufzurufen. Dazu nehmen Sie an, dass die Hilfedatei z.B. topic_de_DE.txt heißt. Verwenden Sie Local.getDefault().
  2. Wie oft fiel der 13. eines Monats in diesem Jahrhundert (oder allgemeiner zwischen zwei Jahreszahlen, die in der Kommandozeile eingegeben werden können) auf die einzelnen Wochentage? Benutzen Sie dazu die Klasse GregorianCalendar und ihre Methode get(GregorianCalender.DAY_OF_WEEK). Geben Sie zunächst den aktuellen Wochentag aus, um das Ergebnis interpretieren zu können.

Lösungen.

Aufgaben ohne Lösung

  1. Testen Sie die Eingabe eines Datums mit der parse-Methode von DateFormat.
  2. Packen Sie ein Applet in ein Archiv und starten Sie es mit dem Browser.
  3. Packen Sie eine Hilfedatei dazu und zeigen Sie die Datei mit unserer Help-Klasse an.

Zurück zum Java-Kurs