Kapitel 8 Internationale programmer

En af Javas store fordele er, at de kompilerede filer er platformsuafhængige. Det ville være rart, hvis de også var sproguafhængige – og her tænkes ikke på programmeringssprog, men på de sprog som brugerne taler.

Du læser en gammel bog
Den bog, du læser her, er fra 1998, og mange ting kan have ændret sig siden da.
Vi håber, at du stadig kan finde relevant information i den.
Hvis du vil læse aktuelle oplysninger om de avancerede dele af Java, anbefaler vi
bogen Core Java – Advanced Features

Der er mange grunde til at ønske sproguafhængighed. For eksempel er det globale marked et særdeles stort marked med mange potentielle kunder. For at opnå succes på det globale marked er det nødvendigt, at ens produkt kan tilpasses de lokale forhold. Eksempelvis foretrækker danskerne at se datoer som dd/mm-åå og se forkortelsen kr. foran beløb, mens en amerikaner vil foretrække, at datoer præsenteres som mm/dd/åå, og at beløb har et dollar-tegn foran.

 

Internationale produkter kan naturligvis opnås ved at have forskellige kildetekster til de forskellige versioner af programmet. Så kan man lade hver version af programmet understøtte et specielt sprog. Dette kræver en rekompilering af applikationen, hver gang man vil understøtte et nyt sprog, men det er det mindste problem. Hvis man foretager en ændring eller en opdatering af programmet, skal den samme ændring foretages i samtlige versioner, og samtlige versioner skal rekompileres.

 

Der er to processer, der skal gennemføres, for at et program succesfuldt kan overføres fra et område til et andet:

 

  • Internationalization
  • Localization

 

Internationalization er den proces, hvor man tager et program og gør det nemt at flytte programmet fra et sprogmæssigt og geografisk område til et andet.

 

Efter et program har gennemgået internationalization-processen kan det blive localized. Det vil sige, at man foretager de ændringer, der tilpasser produktet til en specifik målgruppe.

 

Dette kapitel vil beskrive såvel internationalization- som localizationprocessen.

 

Internationalization

 

Et program, der er blevet internationalized, har følgende kendetegn:

 

  • Ved kun at tilføje localization-data, kan det samme program køres hvor som helst.
  • Den tekst, der vises på brugergrænsefladen, vises i brugerens sprog.
  • Tekstelementer – for eksempel labels på brugergrænsefladen – er ikke kodet direkte ind i programmet. I stedet er de gemt udenfor kildeteksten.
  • Understøttelse af nye sprog medfører ikke et behov for rekompilering af hele applikationen.
  • Datoer, beløb og lignede præsenteres i et format, der passer til brugerens ønske.

 

 

For at skrive et program, der opfylder disse krav, kan man med fordel bruge Javas Internationalization API. Dette API giver mulighed for at anvende klasser til at gemme og indlæse sprogafhængige objekter og stiller metoder til rådighed til formatering af beskeder, datoer, tidspunkter og tal. Det stiller også services til rådighed, så man kan sammenligne tekst og foretage søgninger. Endeligt gør API’et det muligt at vise, indlæse og arbejde med Unicode-tegn.

 

Lokale indstillinger

 

En central del af Javas Internationalization API er java.util.Locale-klassen. Denne klasse indeholder ikke bare en række konstanter, der gør det muligt at angive sprog og lande, men også metoder til angivelse af det nuværende land og sprogområde.

 

I Java-terminologi kaldes operationer, der skal tilpasses fra område til område, for lokalafhængige. At vise en dato er således en lokalafhængig operation, idet det gøres forskelligt alt efter, hvilket område programmet afvikles i.

 

For at programmet kan tilpasse de lokalafhængige operationer korrekt, er det nødt til at vide, hvilket område det afvikles i. Det nytter jo ikke meget, at man er nødt til at inkludere denne information i kildeteksten – så vil det alligevel kræve en rekompilering af flytte produktet fra et område til et andet. En måde at finde ud af, hvordan brugeren foretrækker sin informationer vist, er ved at spørge. Under installationen eller første gang programmet startes, kan brugeren blive mødt med en række spørgsmål, der skal bruges til at definere formateringen af programmets beskeder. For at brugeren ikke skal være nødt til at komme med de samme svar på de samme spørgsmål, hver gang et nyt program tages i brug, opbevarer de fleste operativsystemer denne information. I Windows 95 kan man ændre denne information i Kontrolpanelet.

 

For at opretholde Javas platformsuafhængighed kan man ikke bare bruge de metoder, Windows 95 stiller til rådighed til at hente Kontrolpanelets information. I stedet skal man bruge nogle af de metoder, Locale stiller til rådighed. Det er herefter op til den virtuelle Java-maskine at foretage de platformsafhængige kald, der henter informationerne. Locale-klassen er defineret således:

 

public class Locale extends Object

{

public static java.util.Locale[]getAvailableLocales ();

public static java.util.Locale getDefault ();

public static java.lang.String[] getISOCountries ();

public static java.lang.String[] getISOLanguages ();

public static synchronized void setDefault (java.util.Locale);

 

public java.lang.String getCountry ();

public final java.lang.String getDisplayCountry ();

public java.lang.String getDisplayCountry (java.util.Locale);

public final java.lang.String getDisplayLanguage ();

public java.lang.String getDisplayLanguage (java.util.Locale);

public final java.lang.String getDisplayName ();

public java.lang.String getDisplayName (java.util.Locale);

public final java.lang.String getDisplayVariant ();

public java.lang.String getDisplayVariant (java.util.Locale);

public java.lang.String getISO3Country () throws   java.util.MissingResourceException;

public java.lang.String getISO3Language () throws java.util.MissingResourceException;

public java.lang.String getLanguage ();

public java.lang.String getVariant ();

}

 

Hvis man vil hente de lokale indstillinger, kan man gøre det ved hjælp af nedenstående kode:

 

Locale indstillinger;

indstillinger = Locale.getDefault();

 

Det er også muligt at oprette et Locale-objekt, der er tilpasset et bestemt område. Dette kan gøres ved at angive en sprogkode og landekode i Locale’s constructor. I appendiks A findes en oversigt over de forskellige landekoder i henhold til ISO-standarden. Som det fremgår af denne tabel har Danmark landekoden dk. Det betyder, at man kan oprette et Locale-objekt, der repræsenterer dansk og Danmark, ved at skrive:

 

Locale indstillinger;

indstillinger = new Locale (“da”, “DK”);

 

”da” angiver sproget, mens det andet parameter angiver landet. Grunden, til at der både skal angives sprog og land, er, at det ikke er alle lande, der har et fælles sprog. For eksempel har Canada både en engelsksproget og fransksproget del. Hvis man vil oprette et Locale-objekt, der repræsenterer den fransksprogede del af Canada, kan det gøres med nedenstående kode:

 

Locale indstillinger;

indstillinger = new Locale (“fr”, “CA”);

 

Brugergrænsefladen

 

Det første skridt mod at internationalisere sine programmer er at isolere de dele af Java-programmet, der er lokalafhængige. Dette vil typisk især være brugergrænsefladen: tekster, menupunkter, genvejstaster, beskeder og så

 

videre. Det kan også dreje sig om billeder, der skal ændres, enten fordi de indeholder tekst, der skal oversættes, eller fordi de kan være upassende eller uforståelige i andre områder og kulturer. For eksempel vil en svensker nok have svært ved at se det smarte i, at fødselsdage i et kalenderprogram markeres med Dannebrog.

 

Javas internationalization API indeholder en klasse, der gør det nemt at organisere og indlæse lokalafhængig informationer som tekststrenge eller andre ressourcer. Denne klasse hedder ResourceBundle. Klassen er abstrakt, og der findes to klasser i API’et, som nedarver fra den. Det er klasserne PropertyResourceBundle og ListResourceBundle – mere om dem senere.

 

Man kan bruge så mange ResourceBundle-objekter man vil. Man kan for eksempel opdele teksterne i labels, knapper og beskeder. Hvert sprog skal herudover have sin egen ResourceBundle. Der er en tæt sammenhæng mellem Locale-objekter og ResourceBundle-objekter. Således skal man bruge et Locale-objekt for at hente en ResourceBundle. Derudover får hver ResourceBundle et navn, der indeholder en sprog- og landekode. Hvis man for eksempel har alle sine tekster til at ligge i en ResourceBundle, der hedder Tekster, kan de forskellige udgaver få følgende navne:

 

Tekster

Tekster_dk_DK

Tekster_fr_CA

 

Den første ResourceBundle indeholder de tekster, der skal bruges, hvis der ikke kan findes tekster, der er tilpasset det pågældende sprogområde. Dernæst findes en ResourceBundle indeholdende danske tekster til brug i Danmark, og derefter franske tekster til brug i Canada.

 

Man henter den rette ResourceBundle ved at anvende den statiske metode getBundle, der findes i ResourceBundle-klassen:

 

Locale indstillinger = new Locale (“da”, “DK”);

ResourceBundle tekster = ResourceBundle.getBundle (“Tekster”, indstillinger);

 

Hvis der ikke findes en ResourceBundle, der passer præcis til sprogindstillingerne, vil der blive søgt efter den ResourceBundle, der bedst opfylder indstillingerne. I dette tilfælde vil søgerækkefølgen se således ud:

 

Tekster_da_DK

Tekster_da

Tekster

 

Hvis getBundle ikke kan finde en ResourceBundle, der svarer til de ønskede indstillinger, opstår en MissingResourceException. Dette kan kun ske, hvis der ikke defineres en ResourceBundle uden indstillinger – der vil således ikke opstå nogen exception i dette tilfælde, da søgningen altid vil resultere i, at klassen Tekster bliver fundet.

 

Som nævnt findes der to subklasser af ResourceBundle. PropertyResourceBundle er understøttet af en eller flere property-filer. Disse er almindelige tekstfiler og indeholder ikke Java-kode. Det vil sige, at de kan opdateres af en oversætter eller indtaster uden teknisk erfaring. Der er dog

 

den begrænsning, at property-filer kun kan indeholde informationer om String-objekter. Har man brug for at opbevare mere komplekse ressourcer og objekter, skal man i stedet anvende ListResourceBundle.

 

PropertyResourceBundle

 

For at kunne bruge PropertyResourceBundle skal man som nævnt oprette en property-fil. Denne fil skal have et brugervalgt navn efterfulgt af den sprog- og landekode, der svarer til indholdet af filen. Filen skal have typen .properties. I property-filer bruges gitter-tegnet (#) til at angive kommentarer. Bortset fra det svarer opbygningen af filerne til opbygningen af de ini-filer, man kender fra Windows. Dog er property-filer ikke inddelt i sektioner. Den generelle opbygning af en linie i en property-fil ser således ud:

 

navn = værdi

Nedenstående er et eksempel på en property-fil:

 

#Denne fil indeholder de tekster, der bruges i brugergrænsefladen

okKnap = OK

annullerKnap = Annullér

LukKnap = Luk

 

Denne fil kan for eksempel kaldes Tekster_da_DK.properties. Hvis der skulle findes en tilsvarende fil til det engelske sprog, kunne den kaldes Tekster_en.properties for at angive, at den kan bruges i alle engelsktalende lande. Denne fil kunne se således ud:

 

#This file contains the text used in the user interface

okKnap = OK

annullerKnap = Cancel

LukKnap = Close

 

Som det ses, er det kun værdierne, der ændres – ikke navnene. Hvilke navne man vælger er op til programmøren, men når man én gang har lagt sig fast på et navn, bør man ikke ændre det, da det skal bruges i programmets kildetekst.

 

Når property-filerne er genereret er teksterne klar til brug i programmet. Teksterne hentes på samme måde, hvadenten der er tale om en PropertyResourceBundle eller en ListResourceBundle.

 

ListResourceBundle

 

Hvor PropertyResourceBundle blev repræsenteret af en property-fil, bliver ListResourceBundle repræsenteret af en rigtig Java-klasse. Den første opgave, når der bruges ListResourceBundle, er således at oprette en klasse for hvert område og sprog. Klasserne skal navngives efter samme system som property-filerne – dog har de filtypen .class i stedet for .properties. Derudover er det et krav, at klasserne skal nedarve fra klassen ListResourceBundle, så de kan behandles abstrakt. Nedenstående er et eksempel på en

 

dansk udgave af en ListResourceBundle.

 

import java.util.ListResourceBundle;

 

public class ListTekster_dk_DK extends ListResourceBundle

{

private Object[][] contents =

{

{“Areal”, new Integer (44413)},

{“Indbyggere”, new Integer (5100000)}

};

 

public Object[][] getContents()

{

return contents;

}

}

 

Den tilsvarende engelske klasse kunne se således ud:

 

import java.util.ListResourceBundle;

 

public class ListTekster_en_UK extends ListResourceBundle

{

private Object[][] contents =

{

{“Areal”, new Integer (245000)},

{“Indbyggere”, new Integer (52500000)}

};

 

public Object[][] getContents()

{

return contents;

}

}

 

Ligesom ved PropertyResourceBundle gælder det, at de navne, der bruges til at genkende teksten (Areal og Indbyggere), må vælges af programmøren og ikke skal ændres under oversættelsen.

 

Fordelen ved en ListResourceBundle er, at den kan repræsentere alle former for data. Ulempen er, at klassen skal kompileres, før applikationen understøtter det pågældende sprog. Derudover er det lettere for en oversætter eller indtaster uden teknisk erfaring at lave en fejl, der gør, at klassen ikke kan kompileres.

 

Brug af ResourceBundle

 

Når man har defineret sin ResourceBundle – enten ved hjælp af en PropertyResourceBundle eller en ListResourceBundle – kan man bruge den i sit program. Nedenstående eksempel viser, hvordan det kan gøres.

 

import java.util.*;

 

 

public class InternationalizedClass

{

public static final void main (String args[])

{

Locale indstillinger = Locale.getDefault();

 

ResourceBundle tekster = ResourceBundle.getBundle (“Tekster”, indstillinger);

 

String okKnap = tekster.getString(“okKnap”);

}

}

 

 

Det første, der sker i main-metoden, er, at de lokale indstillinger hentes. Det vil sige, at man spørger operativsystemet, hvilket sprog brugeren taler, og hvilket land computeren befinder sig i. På baggrund af dette, finder man en ResourceBundle, der svarer til disse indstillinger. I dette tilfælde er det en PropertyResourceBundle ved navn Tekster, men det kunne lige så godt have været en ListResourceBundle – fremgangsmåden er den samme. Hvis der både findes en class-fil og en property-fil, der passer til beskrivelsen, vil class-filen blive brugt.

 

Som bekendt kan en PropertyResourceBundle kun indeholde strenge. Derfor er det naturligvis også getString, der bruges til at angive, hvilken værdi der skal hentes. Har man behov for at hente oplysninger i andre formater, kan man gøre det ved hjælp af getObject-metoden.



 

Formatering af tal og beløb

 

Man skal imidlertid ikke kun være opmærksom på oversættelse af tekster, når man skriver et internationalt produkt. Man skal også sørge for, at tal bliver præsenteret rigtigt. For eksempel bruger danskere komma som decimaladskiller og punktum som tusindadskiller, mens englændere og amerikanere gør det præcis modsatte.

 

Formatering af tal kan udføres ved hjælp af klassen NumberFormat. Desværre understøtter denne klasse ikke alle verdens lokationer, og der er således ingen garanti for, at den understøtter det område, man har brug for. Man kan få en oversigt over de understøttede områder, ved at kalde metoden NumberFormat.getAvailableLocales(), der returnerer et array af Locale-objekter.

 

Hvis man skal have formateret et tal, sker det gennem NumberFormat’s format-metode. Nedenstående eksempel viser fremgangsmåden.

 

import java.util.*;

import java.text.*;

 

public class FormatDouble

{

public static final void main (String args[])

{

double tal = 3234.1238;

Locale indstillinger = Locale.getDefault();

 

NumberFormat formatter = NumberFormat.getNumberInstance(indstillinger);

 

System.out.println (formatter.format(tal));

}

}

 

Udskriften fra eksemplet vil blive

 

3.234,124

 

Ovenstående eksempel viser, hvordan en double formateres, men fremgangsmåden er den samme for alle Javas primitive typer. NumberFormat kan også formatere tal, der er repræsenteret i en wrapper-klasse.

 

NumberFormat kan imidlertid andet og mere end bare at formatere tal med tusind- og decimaladskiller. Klassen kan også bruges til at formatere beløb. I så fald skal man blot bruge en CurrencyInstance i stedet for en NumberInstance. Nedenstående eksempel viser, hvordan et beløb kan bliver formateret.

 

import java.text.*;

import java.util.*;

 

public class FormatCurrency

{

public static final void main (String args[])

{

double tal = 1753.75;

Locale indstillinger = Locale.getDefault();

NumberFormat formatter = NumberFormat.getCurrencyInstance (indstillinger);

 

System.out.println (formatter.format (tal));

}

}

 

Udskriften af dette eksempel er

 

kr 1.753,53

 

Den sidste mulighed, NumberFormat stiller til rådighed, er formatering af procentværdier. Det vil sige, at man tager et decimaltal og konverterer det til en procentsats. Nedenstående eksempel viser, hvordan det kan gøres.

 

import java.text.*;

import java.util.*;

 

public class FormatPercent

{

public static final void main (String args[])

{

double tal = 0.753;

Locale indstillinger = Locale.getDefault();

NumberFormat formatter = NumberFormat.getPercentInstance();

 

System.out.println (formatter.format(tal));

 

}

}

 

Dette eksempel giver udskriften

 

75%

 

Som nævnt understøtter NumberFormat-klassen desværre ikke alle områder og sprog, og der kan være situationer, hvor man af den ene eller den anden grund har behov for at formatere et beløb på en bestemt måde. Dette kan man gøre ved hjælp af det, der i Java kaldes mønstre. Et mønster angiver, hvor der skal sættes gruppeadskiller (tusindadskiller), hvor mange decimaler beløbet skal have, og om der skal sættes tegn foran eller bagved. For at kunne bruge mønstre til at formatere tal skal man bruge klasserne DecimalFormat og DecimalFormatSymbols. I første omgang er kun DecimalFormat nødvendig. Kaldet til dennes constructor inkluderer en tekststreng, der indeholder det mønster, der skal formateres efter. Nedenstående eksempel viser, hvordan man nemt kan formatere med DecimalFormat. Hvordan mønstrene designes vil blive gennemgået senere.

 

import java.util.*;

import java.text.*;

 

public class FormatUserDefined

{

public static final void main (String arsg[])

{

double tal = 12375.714;

String pattern = “###,###.##”;

DecimalFormat formatter = new DecimalFormat (pattern);

 

System.out.println (formatter.format(tal));

}

}

 

Eksemplet giver følgende udskrift

 

12.375,71

 

Umiddelbart kan det se sært ud, at et komma i mønstret erstattes med et punktum i udskriften, ligesom punktummet i mønstret erstattes med et komma. Det skyldes, at et punktum i mønstret simpelthen bare betyder, at der her skal indsættes den decimaladskiller, der bruges i det aktuelle område. De generelle regler for design af mønstre er som følger:

 

  • Gitterlåge (#) bruges til at repræsentere et hvilket som helst ciffer. Hvis der er flere repræsentationer af #, end der er cifre i tallet, ses der bort fra de overskydende gitterlåger. Hvis der er flere cifre i tallet, end der er repræsentationer af # i mønstret på venstre side af decimaltegnet, bliver alle cifre vist alligevel. Hvis der er for få gitterlåger på højre side af decimaltegnet, vil der blive foretaget afrundning.
  • Nul (0) bruges til at repræsentere et hvilket som helst ciffer. De samme regler gælder som ved #, dog med den undtagelse, at hvis der er flere nuller, end der er cifre i tallet, vil der blive brugt enten foranstillede eller bagvedstillede nuller for at give tallet den samme længde som mønstret.
  • Komma (,) angiver, at der på denne plads skal indsættes en gruppeadskiller (tusindadskiller).
  • Punktum (.) angiver, at der på denne plads skal indsættes decimaladskiller.
  • E deler mantisse og eksponent.
  • Semikolon (;) bruges til at adskille forskellige formater i det samme mønster.
  • Minus (-) angiver, at der normalt skal bruges negativt fortegn.
  • Procent (%) angiver, at tallet skal ganges med 100 og vises som procent.
  • Spørgsmålstegn (?) angiver, at tallet skal ganges med 1000 og vises som promille.
  • Valutategn (¤) bruges til at angive, at møntfoden skal indsættes på det sted.
  • Dobbelt-valutategn (¤¤) angiver, at der i stedet for den gængse møntforkortelse eller mønttegn skal indsættes den internationale møntkode for valutaen.
  • Alle andre tegn bliver indsat som de er.
  • Apostrof (’) kan bruges til at omkranse tegn, der skal indsættes som de er, men ellers ville have haft betydning for mønstret (eksempelvis #).

 

Nedenstående eksempel viser nogle forskellige mønste og deres udskrifter:

import java.text.*;

import java.util.*;

 

public class MultipleFormat

{

private static void print (String pattern, double tal)

{

DecimalFormat formatter = new DecimalFormat (pattern);

 

System.out.println (“(” + pattern + ” , ” + tal + “) : ” + formatter.format(tal));

}

 

public static final void main (String args[])

{

String[] patterns = {

“¤ ###,###.##”,

“¤¤ ###,###.##”,

“000,000.00”,

“0.0000”,

“-#”

};

 

for (int i = 0; i < patterns.length; i++)

print (patterns[i], 123456.123);

}

}

 

Eksemplet genererer følgende udskrift:

 

 

(¤ ###,###.## , 123456.123) : kr 123.456,12

(¤¤ ###,###.## , 123456.123) : DKK 123.456,12

(000,000.00 , 123456.123) : 00.123.456,12

(0.0000 , 123456.123) : 123456,1230

(-# , 123456.123) : -123456

 

Ovenstående gennemgang har imidlertid kun vist, hvordan man ændrer formatet af udskriften, ikke hvordan man eksempelvis ændrer decimaladskiller. Til denne opgave bruges DecimalFormatSymbols. Man kan oprette et DecimalFormatSymbols-objekt på to måder. Enten kan man oprette et helt fra bunden, eller man kan tage udgangspunkt i et eksisterende objekt og foretage ændringer på det. Det gøres ved at give et Locale-objekt med i DecimalFormatSymbol’s constructor. Det kan for eksempel se således ud:

 

Locale indstillinger = Locale.getDefault();

DecimalFormatSymbol symboler = new DecimalFormatSymbol(indstillinger);

 

Når DecimalFormatSymbol-objektet er oprettet, kan man kalde en række metoder på det. Disse fremgår af nedenstående signatur af DecimalFormatSymbol-klassen:

 

public class DecimalFormatSymbols extends Object implements Cloneable, Serializable

{

public java.text.DecimalFormatSymbols();

public java.text.DecimalFormatSymbols(java.util.Locale);

 

public char getDecimalSeparator ();

public char getDigit ();

public char getGroupingSeparator ();

public java.lang.String getInfinity ();

public char getMinusSign ();

public java.lang.String getNaN ();

public char getPatternSeparator ();

public char getPerMill ();

public char getPercent ();

public char getZeroDigit ();

public void setDecimalSeparator (char);

public void setDigit (char);

public void setGroupingSeparator (char);

public void setInfinity (java.lang.String);

public void setMinusSign (char);

public void setNaN (java.lang.String);

public void setPatternSeparator (char);

public void setPerMill (char);

public void setPercent (char);

public void setZeroDigit (char);

}

 

Som det fremgår af signaturen, indeholder klassen metoder til både at sætte og hente de forskellige oplysninger. Hvilke metoder, der foretager hvilke operationer, burde være indlysende.

 


Formatering af datoer og klokkeslæt

 

Ligesom tal og beløb har behov for at blive formateret i henhold til områdets standarder, har datoer og klokkeslæt også behov for det. Nogle lande skriver måned før dagen, og det er forskelligt, hvilke tegn, der anvendes til at adskille dag og måned og måned/dag og år.

 

I Javas Internationalization API håndteres disse formateringsfunktioner af DateFormat-klassen. Denne klasse har mange ligheder med NumberFormat-klassen, men der er også nogle forskellige. For eksempel skal NumberFormat kun tage sig af at formatere tallet – DateFormat skal også tage højde for, at der findes forskellige måder at præsentere datoen på. Der findes i Java fem forskellige måder at præsentere en dato på:

 

  • DEFAULT. Præsenterer datoen i det format, brugeren har valgt i operativsystemet.
  • SHORT. Kort præsentation af datoen. For eksempel 24/12/98.
  • MEDIUM. Længere præsentation af datoen. For eksempel 24. dec. 98.
  • LONG. Lang præsentation af datoen. For eksempel 24. december 1998.
  • FULL. Komplet beskrivelse af datoen. For eksempel Torsdag den 24. december 1998.

 

Det afhænger af operativsystemet, hvordan de forskellige præsentationer er implementeret. Enkelte præsentationer kan være ens.

 

Hvilken præsentation, man ønsker at anvende, skal angives i kaldet til getDateInstance. Hver af disse præsentationer er repræsenteret af en statisk variabel i DateFormat-klassen. I kaldet til getDateInstance skal også det Locale-objekt, der indeholder de indstillinger, man vil bruge til at formatere datoen, angives.

 

Nedenstående klasse viser, hvordan man kan formatere en dato i forskellige formater:

 

import java.text.*;

import java.util.*;

 

public class FormatDate

{

public static final void main (String args[])

{

Locale indstillinger = Locale.getDefault();

 

DateFormat defaultFormat = DateFormat.getDateInstance (DateFormat.DEFAULT, indstillinger);

DateFormat shortFormat = DateFormat.getDateInstance (DateFormat.SHORT, indstillinger);

DateFormat mediumFormat = DateFormat.getDateInstance (DateFormat.MEDIUM, indstillinger);

DateFormat longFormat = DateFormat.getDateInstance (DateFormat.LONG, indstillinger);

DateFormat fullFormat = DateFormat.getDateInstance (DateFormat.FULL, indstillinger);

 

Date today = new Date();

 

 

System.out.println (defaultFormat.format(today));

System.out.println (shortFormat.format(today));

System.out.println (mediumFormat.format(today));

System.out.println (longFormat.format(today));

System.out.println (fullFormat.format(today));

}

}

 

Hvis man vil formatere klokkeslæt i stedet for datoer, kan man stadigvæk bruge DateFormat-klassen. I stedet for at bruge en DateInstance skal man blot bruge en TimeInstance.

 

Ligesom der findes flere forskellige præsentationer af datoer, findes der også flere forskellige præsentationer af klokkeslæt. Det er de samme, statiske variabler i DateFormat-klassen, der bruges til at vælge præsentationen af datoer som klokkeslæt.

 

Der findes følgende forskellige præsentationer af klokkeslæt:

 

  • DEFAULT. Den præsentation brugeren har valgt i operativsystemet.
  • SHORT. Kort præsentation. For eksempel 17:30.
  • MEDIUM. En længere præsentation. For eksempel 17:30:15.
  • LONG. En lang præsentation. For eksempel 17:30:15 GMT+02:00.
  • FULL. Komplet præsentation. For eksempel Klokken 17:30:15 GMT+02:00.

 

Igen gælder det, at det ikke er sikkert, at alle operativsystemer implementerer præsentationerne på samme måde.

 

Dette eksempel viser, hvordan man kan præsentere et klokkeslæt på forskellige måder:

 

import java.text.*;

import java.util.*;

 

public class FormatTime

{

public static final void main (String args[])

{

Locale indstillinger = Locale.getDefault();

 

DateFormat defaultFormat = DateFormat.getTimeInstance (DateFormat.DEFAULT, indstillinger);

DateFormat shortFormat = DateFormat.getTimeInstance (DateFormat.SHORT, indstillinger);

DateFormat mediumFormat = DateFormat.getTimeInstance (DateFormat.MEDIUM, indstillinger);

DateFormat longFormat = DateFormat.getTimeInstance (DateFormat.LONG, indstillinger);

DateFormat fullFormat = DateFormat.getTimeInstance (DateFormat.FULL, indstillinger);

 

Date now = new Date();

 

System.out.println (defaultFormat.format(now));

System.out.println (shortFormat.format(now));

 

System.out.println (mediumFormat.format(now));

System.out.println (longFormat.format(now));

System.out.println (fullFormat.format(now));

}

}

 

Ligesom man kan vælge at formatere tal efter et bestemt mønster, kan man gøre det samme ved datoer og klokkeslæt. Til dette bruger man klassen java.text.SimpleDateFormat. Dennes constructor tager et mønster som parameter. Mønsteret er en tekststreng, der angiver, hvordan datoen skal formateres. Følgende regler gælder for definition af mønstret:

 

  • G angiver, om datoen er før eller efter Kristi fødsel.
  • y angiver året.
  • M angiver måneden.
  • d angiver datoen.
  • h angiver timetallet efter 12-timerssystemet (1 – 12).
  • H angiver timetallet efter 24-timerssystemet (0 – 23).
  • m angiver antallet af minutter.
  • s angiver antallet af sekunder.
  • S angiver antallet af millisekunder.
  • E angiver navnet på ugedagen.
  • D angiver dagens nummer i året.
  • F angiver dagens nummer i måneden.
  • w angiver ugens nummer i året.
  • W angiver ugens nummer i måneden.
  • a angiver, om klokkeslættet er AM eller PM.
  • k angiver timetallet efter 24-timerssystemet (1-24).
  • K angiver timetallet efter 12-timerssystemet (0-11).
  • z angiver tidszonen.

 

Antallet af gange, et tegn forekommer i mønstret, afgør hvordan informationen skal præsenteres. Der gælder de følgende regler:

 

  • Ved information, der altid vises som tekst, vil 4 eller flere ens tegn medføre, at det lange format benyttes. Ellers vil et kortere format blive benyttet, hvis et sådant findes.
  • Ved information, der altid vises med tal, angiver antallet af tegn, minimumslængden for tallet. Hvis der er færre cifre i tallet, vil det blive indledt med nuller.
  • Ved årstal vil to gentagne tegn dog betyde, at årstallet skal forkortes til to cifre (de to sidste).
  • Ved informationer, der både kan vises som tal eller tekst, vil færre end tre tegn angive, at talformatet skal bruges.

 

Nedenstående klasse er et eksempel på brugen af et brugerdefineret mønster.

 

import java.text.*;

import java.util.*;

 

public class TimePattern

{

 

public static final void main (String args[])

{

String datePattern = “‘Den’ D’. dag i året’ yyyy G ‘er’ EEEE’, den’ d’.’ MMMM’. Den falder i årets’ w’. uge, og månedens’ W’. uge'”;

String timePattern = “‘Klokken er’ HH:mm:ss.SSSS ‘i tidszonen’ z”;

 

Date today = new Date();

 

SimpleDateFormat dateFormatter = new SimpleDateFormat (datePattern);

SimpleDateFormat timeFormatter = new SimpleDateFormat (timePattern);

 

System.out.println (dateFormatter.format (today));

System.out.println (timeFormatter.format (today));

}

}

 

 

Kørslen af programmet giver denne udskrift:

 

Den 174. dag i året 1998 AD er tirsdag, den 23. juni. Den falder i årets 26. uge, og månedens 4. uge

Klokken er 19:38:33.450 i tidszonen GMT+00:00

 

Hvor man ved formatering af tal brugte klassen NumberFormatSymbols, kan man ved formatering af datoer og klokkeslæt bruge klassen DateFormatSymbols til at ændre de symboler, der bruges i formateringen. Her skal man blot være opmærksom på, at symbolerne, man ændrer, blandt andet er navnene på ugedagene og månederne.

 

DateFormatSymbols-klassen indeholder en række interessante funktioner, der alle fremgår af dens signatur.

 

public class DateFormatSymbols extends Object implements Serializable, Cloneable

{

public java.text.DateFormatSymbols();

public java.text.DateFormatSymbols(java.util.Locale);

 

public java.lang.String[] getAmPmStrings ();

public java.lang.String[] getEras ();

public java.lang.String getLocalPatternChars ();

public java.lang.String[] getMonths ();

public java.lang.String[] getShortMonths ();

public java.lang.String[] getShortWeekdays ();

public java.lang.String[] getWeekdays ();

public java.lang.String[][] getZoneStrings ();

public void setAmPmStrings (java.lang.String[]);

public void setEras (java.lang.String[]);

public void setLocalPatternChars (java.lang.String);

public void setMonths (java.lang.String[]);

public void setShortMonths (java.lang.String[]);

 

public void setShortWeekdays (java.lang.String[]);

public void setWeekdays (java.lang.String[]);

public void setZoneStrings (java.lang.String[][]);

}

 

Igen gælder det, at klassen indeholder metoder til både at hente og ændre indstillingerne. Det vil sige, at man enten kan oprette et DateFormatSymbols-objekt fra bunden eller hente et områdes indstillinger og arbejde videre på disse og eventuelt modificere dem.

 

Håndtering af beskeder

 

Tidligere i kapitlet blev det gennemgået, hvordan man kunne gemme tekster i en ResourceBundle, så det var let at håndtere oversættelser uden at skulle rekompilere hele applikationen. Det løser imidlertid kun en del af problematikken med beskeder. Hvis en besked både består af tekst og data, løber man i problematikken med forskellig ordstilling på forskellige sprog. Hvis man vil udskrive en besked om, at en fil ikke kan læses, kan det på engelsk gøres således

 

System.out.println (“Unable to read the file ” + filename);

 

mens den tilsvarende danske tekst vil se således ud

 

System.out.println (“Filen ” + filename + ” kunne ikke læses”);

 

For at løse dette problem kan man bruge MessageFormat-klassen.

 

At håndtere sammensatte strenge som eksemplet ovenfor er en proces, der kan deles op i fem trin:

 

  1. Identificér variablerne i teksten og definér deres typer.
  2. Isolér beskedens mønster i en ResourceBundle.
  3. Angiv beskedens parametre.
  4. Opret et MessageFormat-objekt.
  5. Formatér beskeden ved brug af mønstret og parametrene.

 

At identificere variablerne og definere deres typer gøres ved at kigge på teksten, og se hvilke dele af den, der kan skiftes ud. Hvis teksten for eksempel er

 

Klokken 8:05 stod hr. Madsen op.

 

er det tydeligt, at klokkeslættet (8:05) er en variabel. Ligeledes er ”hr. Madsen” en variabel, for det kunne lige så godt have været fru Petersen, der var stået op. Klokkeslættet er en variabel af typen Date, mens navnet er en variabel af typen String.

 

Næste punkt er at isolere mønstret i en ResourceBundle. Da teksten er en streng, er det nemmest at gøre det i en PropertyResourceBundle. Hvis den overordnede ResourceBundle kaldes Besked, vil den danske udgave af filen hedde Besked_da_DK.properties. Filen kan komme til at se således ud:

 

besked = Klokken {0,time,short) stod {1} op.

 

 

Som det ses, er hver variabel nu angivet i krøllede parenteser. Først i hver krøllet parentes angives nummeret på det parameter, der skal indsættes her. Her kommer parametrene i rækkefølge, men det er ikke en nødvendighed.

 

For den første variabel er der angivet yderligere to oplysninger. Den første angiver, at der skal vises et tidspunkt, mens den anden angiver, at tidspunktet skal vises i det korte format. Den anden parameter (med nummeret 1) er blot en tekststreng, der skal vises, og det er ikke nødvendigt med yderligere informationer for at kunne vise den.

 

Generelt findes der følgende regler for angivelse af variabler i et mønster:

  • Første parameter er altid nummeret på det parameter, der skal indsættes på denne plads.
  • Andet parameter kan undlades eller være time¸ date, number eller choice.
    • Hvis det andet parameter er time eller date, kan det tredje parameter være short, medium, long eller full eller et brugerdefineret dato- eller tidsmønster.
    • Hvis det andet parameter er number, kan det tredje parameter være currency, percent, integer, eller et brugerdefineret mønster til formatering af tal.
    • Hvis det andet parameter er choice, skal det tredje parameter være et brugerdefineret mønster.

 

Hvis et parameter mangler eller ikke er af den type, der er angive, opstår en parseException.

 

Når property-filen er defineret, er den nem at indlæse fra selve programmet:

 

Locale indstillinger = Locale.getDefault();

ResourceBundle besked = ResourceBundle.getBundle(“Besked”, indstillinger);

 

Med det på plads kan man definere det array, der skal indeholde parametrene til teksten. Det kan se således ud:

 

Object[] parametre = {

new Date();

“Hr. Madsen”

};

 

Når parametrene er defineret, kan man gå videre med at instantiere et MessageFormat-objekt.

 

MessageFormat formatter = new MessageFormat(“”);

Locale indstillinger = Locale.getDefault();

formatter.setLocale (indstillinger);

 

I modsætning til de andre formatter-klasser (NumberFormat og DateFormat) opretter man en instans af MessageFormat ved at bruge dens constructor. Som parameter til constructoren gives en tekststreng, der indeholder det mønster, der skal formateres efter. I dette eksempel bliver mønstret først angivet senere, og parametret er derfor en tom streng.

 

For at sikre, at de variabler, der skal vises i strengen, bliver formateret

 

rigtigt, informeres MessageFormat om, hvilket Locale-objekt, der skal bruges til formateringen.

 

Når MessageFormat-objektet er oprettet, kan man formatere teksten. Det gøres ved at indlæse mønstret fra ResourceBundle’n og angive dette som det mønster, MessageFormat-objektet skal bruge. Herefter skal man blot kalde MessageFormat’s format-metode:

 

formatter.applyPattern (besked.getString(“besked”));

System.out.println (formatter.format(parametre));

 

Ental og flertal

 

Problemet med beskeder er imidlertid ikke kun at få dem sat sammen i den rigtige rækkefølge. Man skal også sikre sig, at de er grammatisk korrekte. Hvis man for eksempel vil udskrive en besked om, hvor mange filer, der er blevet slettet, skal man tage højde for, at beskeden ikke er den samme, hvis der bliver slettet 1 eller 2 beskeder. Så vil beskederne hedde:

 

Der blev slettet 1 fil.

Der blev slettet 2 filer.

 

Løsningen på problemet kunne være, at angive flertalsformen i parentes

 

Der blev slettet 2 fil(er).

 

men det er ikke særlig pænt. Det giver i øvrigt også et problem i bestemt form – hvis man eksempelvis vil bekræfte, at filerne virkeligt er blevet slettet, kan man enten skrive

 

Filen er blevet slettet.

 

eller

 

Filerne er blevet slettet.

 

Her er det ikke muligt at sætte flertalsformen i parentes.

 

Javas løsning på problemet er klassen ChoiceFormat. Fremgangsmåden, når ChoiceFormat-klassen bruges, er næsten den samme, som ved de andre format-klasser.

  1. Definér mønstret
  2. Læg mønstret i en ResourceBundle
  3. Opret et MessageFormat-objekt
  4. Opret et ChoiceFormat-objekt
  5. Anvend mønstret
  6. Anvend ChoiceFormat-objektet
  7. Angiv parametrene
  8. Formatér beskeden på baggrund af mønstret og parametrene.

I de tidligere eksempler var parametrene i teksten en af Javas typer. Det er ikke længere tilfældet. Nu er parametrene en blanding af tekst og de egentlige variabler. Eksempelvis

 

Der blev ikke slettet nogen filer.

Der blev slettet 1 fil.

 

Der blev slettet 2 filer.

 

Som det ses, kan alt efter ”Der blev” blive ændret – det må derfor opfattes som variabler. Det vil sige, at mønstret kommer til at se således ud:

 

Der blev {0}

 

Det parameter, der findes i mønstret, er imidlertid væsentligt sværere at håndtere end de, der tidligere er blevet arbejdet med i kapitlet. Dette skyldes, at antallet af filer, der er blevet slettet, påvirker teksten, hvilket medfører, at man skal have en måde til at finde ud af, hvilken tekst der hører til hvilken talværdi. Derudover indgår der et tal i teksten – det kan jo godt tænkes, at der bliver slettet mere end 2 filer. Det betyder, at det skal være muligt at ændre tallet i flertalsformen af beskeden.

 

Disse problemer vil blive løst i det følgende.

 

Første skridt på vejen er at oprette en ResourceBundle. Igen vil der blive brugt en PropertyResourceBundle, da den kun skal håndtere tekststrenge. Filen kommer i dette eksempel til at hedde SletFil_da_DK.properties, og den ser således ud:

 

Pattern = Der blev {0}

IngenFiler = ikke slettet nogen filer.

EnFil = slettet en fil.

FlereFiler = slettet {1} filer.

 

Pattern angiver det mønster, der skal anvendes – det blev defineret tidligere. De efterfølgende tekster er de, der kan indsættes på parameter 0’s plads. Teksten kaldt FlereFiler er selv et mønster, der gør det muligt at indsætte antallet af filer. Når property-filen er oprettet, er det ikke noget problem at indlæse den som en ResourceBundle:

 

Locale indstillinger = Locale.getDefault ();

ResourceBundle tekster = ResourceBundle.getBundle (“SletFil”, indstillinger);

 

Ligesom i det foregående tilfælde, skal klassen MessageFormat bruges til at formatere teksten:

 

MessageFormat formatter = new MessageFormat (“”);

formatter.setLocale (indstillinger);

 

Når MessageFormat-objektet er oprettet, skal der oprettes et ChoiceFormat-objekt. For at man kan lave dette, skal man først lave et array, der angiver grænseværdierne for, hvornår en given tekst skal bruges:

 

double[] limits = {0, 1, 2};

 

Det vil sige, at hvis antallet af slettede filer er større end eller lig med 0, bliver den første tekst vist. Hvis antallet er større end eller lig med 1, bliver den næste vist, mens tal større end eller lig med 2 vil resultere i, at den sidste tekst bliver vist.

 

ChoiceFormat skal imidlertid også vide, hvilke tekster det er, der skal vises. Dette gør man objektet opmærksom på ved at oprette et array af tekststrenge:

 

 

 

String beskeder[] = {

tekster.getString (“IngenFiler”),

tekster.getString (“EnFil”),

tekster.getString (“FlereFiler”)

};

 

Som det ses, bliver teksterne indlæst fra den ResourceBundle, der blev defineret tidligere. Når grænseværdierne og teksterne er defineret, kan ChoiceFormat-objektet oprettes:

 

ChoiceFormat choice = new ChoiceFormat (limits, beskeder);

 

Efter at have oprettet ChoiceFormat-objektet kan man tilknytte mønstret til MessageFormat-objektet:

 

String pattern = tekster.getString (“pattern”);

formatter.applyPattern (pattern);

 

Det næste, der skal gøres, er at oprette et array af Format-objekter. Dette array skal indeholde referencer til de objekter, der skal bruges til at formatere de forskellige parametre i mønstret:

 

Format[] formatListe = {choice, NumberFormat.getNumberInstance()};

formatter.setFormats (formatListe);

 

Ovenstående angiver, at det første parameter – det, der angives med {0} – skal formateres med choice. Dernæst angives det, at det andet parameter – {1} – skal formateres med et NumberFormat-objekt. Det andet parameter er som bekendt antallet af filer, der blev slettet, hvis dette er 2 eller derover.

 

Tilbage er der nu kun at bruge mønstret. Hertil skal der bruges nogle parametre. I dette tilfælde skal det angives, hvor mange filer der er blevet slettet. Dette skal angives to gange, da det i første omgang angiver, hvilken tekststreng der skal vises, og dernæst bruges til at vise antallet af slettede filer.

 

For at vise, om eksemplet virker, kan man skrive følgende kode:

 

Object[] parametre = new Object[2];

 

for (int i = 0; i < 4; i++)

{

parametre[0] = new Integer (i);

parametre[1] = new Integer (i);

System.out.println (formatter.format(parametre));

}

 

Så genererer programmet følgende udskrift:

 

Der blev ikke slettet nogen filer.

Der blev slettet en fil.

Der blev slettet 2 filer.

Der blev slettet 3 filer.

 

 

Sammenligning af tekster

 

Forskellige sprog og – især – forskellige tegnsæt giver også problemer, når man skal sammenligne tekster. Sammenligning af tekster er ikke bare at se, om to tekster er ens, men også – hvis de ikke er det – at se, hvilken tekst, der kommer først i alfabetet. Her er der en del ting, man skal være opmærksom på. For eksempel vil et ord, der starter med A kommer før et ord, der starter med B, men hvis ordet eksempelvis er Aalborg, skal det til sidst, fordi dobbelt-A fortolkes som Å.

 

Normalt vil sammenligning af strenge i Java foregå ved hjælp af compareTo-metoden. Denne foretager en binær sammenligning af teksterne. Det betyder, at den sorterer efter tegnenes Unicode-værdi. Rækkefølgen her er ikke nødvendigvis den samme som rækkefølgen i alfabetet. Derudover tager compareTo ikke højde for, at to bogstaver ved siden af hinanden måske skal fortolkes som ét bogstav.

 

For at sikre en sproguafhængig sammenligning skal man bruge klassen java.text.Collator.

 

Man opretter en instans af denne ved at kalde metoden getInstance:

 

Locale indstillinger = Locale.getdefault();

Collator sammenligner = Collater.getInstance(indstillinger);

 

Herefter kan man bruges Collator-objektets compare-metode til at sammenligne tekststrenge:

 

sammenligner.compare(“abe”, “delfin”);

 

Sammenfatning

 

I dette kapitel er de problemer, der kan opstå, når man flytter et program fra et land til et andet, blevet gennemgået. Der er blevet set på, hvordan man kan bruge klasser som NumberFormat og DateFormat til at formatere henholdsvis tal og datoer til den lokale standard. Det er også blevet gennemgået, hvordan man håndterer tekster ved hjælp af MessageFormat og den lidt mere komplicerede ChoiceFormat-klasse. Endeligt er det blevet forklaret, hvordan man kan bruge Collator-klassen til at sammenligne tekster på en mere sproguafhængig måde end String’s compareTo-funktion.

 

FAQ

Hvordan indlæser jeg tal og datoer, som er formateret efter de lokale standarder?

Normalt når man indlæser tal og datoer, sker dette efter engelsk standard – det vil blandt andet sige, at punktum bruges som decimaladskiller. Hvis man vil bruge de lokale indstillinger, når man indlæser, skal man indlæse tallet eller datoen i en tekststreng. Herefter skal man oprette en instans af DateFormat eller NumberFormat. På dette objekt skal man kalde parse-funktionen. Nedenstående main-metode viser, hvordan det kan gøres:

 

 

 

public static final void main (String args[])

{

String tal = “12.194,52”;

 

Locale indstillinger = Locale.getDefault();

NumberFormat formatter = NumberFormat.getInstance (indstillinger);

 

Double d = null;

 

try

{

d = (Double) formatter.parse (tal);

System.out.println (d.doubleValue());

}

catch (ParseException pe)

{

System.out.println (pe.toString());

}

}

 

Hvordan tager man højde for, at tekster i brugergrænsefladen ikke fylder lige meget på alle sprog?

 

Hvis man giver labels i brugergrænsefladen absolutte størrelser, kan man komme ud for, at nogle tekster ikke bliver vist i deres fulde længde, hvis de fylder mere på et andet sprog end det originale. Den bedste måde at afhjælpe dette problem er at lade være med at angive absolutte størrelser og i stedet bruge en layout-manager.

 

Hvilke fordele og ulemper er der ved at anvende Unicode-tegn?

 

Fordelen ved at bruge Unicode-tegn er, at Unicode-tegntabellen indeholder alle tegn. Ulempen er, at tegnene fylder mere end de almindelige tegn, og at de er langsommere at arbejde med. Derudover skal man være opmærksom på, at det ikke er alle platforme, der understøtter Unicode, fordi der ikke findes ret mange skrifttyper, der understøtter alle Unicode-standardens tegn.

 

Du læser en gammel bog
Den bog, du læser her, er fra 1998, og mange ting kan have ændret sig siden da.
Vi håber, at du stadig kan finde relevant information i den.
Hvis du vil læse aktuelle oplysninger om de avancerede dele af Java, anbefaler vi
bogen Core Java – Advanced Features

Comments are closed.