Internationalisering og lokalisering i Java 8

1. Oversigt

Internationalisering er en proces til forberedelse af en ansøgning til understøttelse af forskellige sproglige, regionale, kulturelle eller politisk specifikke data. Det er et væsentligt aspekt af enhver moderne flersproget applikation.

For yderligere læsning, vi skal vide, at der er en meget populær forkortelse (sandsynligvis mere populær end det egentlige navn) til internationalisering - i18n på grund af de 18 bogstaver mellem 'i' og 'n'.

Det er afgørende for nutidens virksomhedsprogrammer at tjene mennesker fra forskellige dele af verden eller flere kulturelle områder. Forskellige kultur- eller sprogregioner bestemmer ikke kun sprogspecifikke beskrivelser, men også valuta, antal repræsentation og endda divergerende dato og tidssammensætning.

Lad os for eksempel fokusere på landespecifikke tal. De har forskellige decimaler og tusind separatorer:

  • 102.300,45 (USA)
  • 102 300,45 (Polen)
  • 102.300,45 (Tyskland)

Der er også forskellige datoformater:

  • Mandag 1. januar 2018 15:20:34 CET (USA)
  • lundi 1. januar 2018 15 h 20 CET (Frankrig).
  • 2018 年 1 月 1 日 星期一 下午 03 时 20 分 34 秒 CET (Kina)

Hvad mere er, forskellige lande har unikke valutasymboler:

  • £ 1.200,60 (Storbritannien)
  • 1.200,60 € (Italien)
  • 1.200,60 € (Frankrig)
  • $ 1.200,60 (USA)

En vigtig kendsgerning at vide er, at selvom lande har samme valuta og valutasymbol - som Frankrig og Italien - kunne deres valutasymbols position være anderledes.

2. Lokalisering

Inden for Java har vi en fantastisk funktion til vores rådighed kaldet Lokal klasse.

Det giver os mulighed for hurtigt at skelne mellem kulturelle lokaliteter og formatere vores indhold korrekt. Det er vigtigt inden for internationaliseringsprocessen. Det samme som i18n har Localization også sin forkortelse - l10n.

Hovedårsagen til brug Lokal er, at al nødvendig lokalspecifik formatering kan tilgås uden rekompilering. En applikation kan håndtere flere lokaliteter på samme tid, så understøttelse af nyt sprog er ligetil.

Lokaliteter er normalt repræsenteret af sprog, land og variant forkortelse adskilt af en understregning:

  • de (tysk)
  • it_CH (italiensk, Schweiz)
  • da_US_UNIX (United States, UNIX-platform)

2.1. Felter

Det har vi allerede lært Lokal består af sprogkode, landekode og variant. Der er yderligere to mulige felter at indstille: script og udvidelser.

Lad os se gennem en liste over felter og se, hvad reglerne er:

  • Sprog kan være en ISO 639 alpha-2 eller alpha-3 kode eller registreret sprog.
  • Område (Land) er ISO 3166 alpha-2 landekode eller FN-tal-3 områdenummer.
  • Variant er en sagsfølsom værdi eller et sæt værdier, der angiver en variation af Lokal.
  • Manuskript skal være gyldig ISO 15924 alpha-4 kode.
  • Udvidelser er et kort, der består af enkelttegntaster og Snor værdier.

IANA Language Subtag Registry indeholder mulige værdier for Sprog, område, variant og manuskript.

Der er ingen liste over mulige udvidelse værdier, men værdierne skal være velformede BCP-47 undertags. Tasterne og værdierne konverteres altid til små bogstaver.

2.2. Lokalbygger

Der er flere måder at skabe på Lokal genstande. En mulig måde anvendes Lokalbygger. Lokalbygger har fem setter-metoder, som vi kan bruge til at opbygge objektet og samtidig validere disse værdier:

Lokal locale = ny Locale.Builder () .setLanguage ("fr") .setRegion ("CA") .setVariant ("POSIX") .setScript ("Latn") .build ();

Det Snor repræsentation af ovenstående Lokal er fr_CA_POSIX_ # Latn.

Det er godt at vide det indstilling af 'variant' kan være en smule vanskelig, da der ikke er nogen officiel begrænsning af variantværdier, selvom setter-metoden kræver, at den er BCP-47 kompatibel.

Ellers vil det smide Uformeret Lokaleksposition.

I det tilfælde, hvor vi har brug for en værdi, der ikke godkender validering, kan vi bruge Lokal konstruktører, da de ikke validerer værdier.

2.3. Konstruktører

Lokal har tre konstruktører:

  • nyt sprog (strengsprog)
  • ny lokalitet (strengssprog, strengland)
  • ny lokalitet (strengssprog, strengland, strengvariant)

En 3-parameter konstruktør:

Lokal lokalitet = ny lokal ("pl", "PL", "UNIX");

En gyldig variant skal være en Snor med 5 til 8 alfanumeriske eller enkelt numeriske efterfulgt af 3 alfanumeriske. Vi kan kun anvende “UNIX” på variant felt kun via konstruktør, da det ikke opfylder disse krav.

Der er dog en ulempe ved at bruge konstruktører til at skabe Lokal objekter - vi kan ikke indstille udvidelser og scriptfelter.

2.4. Konstanter

Dette er sandsynligvis den enkleste og mest begrænsede måde at få Lokale. Det Lokal klasse har flere statiske konstanter, som repræsenterer det mest populære land eller sprog:

Lokal japan = Locale.JAPAN; Lokal japansk = Lokal.JAPANSK;

2.5. Sprogmærker

En anden måde at skabe på Lokal kalder den statiske fabriksmetode forLanguageTag (String languageTag). Denne metode kræver en Snor der opfylder IETF BCP 47 standard.

Sådan kan vi skabe Det Forenede Kongerige Lokal:

Locale uk = Locale.forLanguageTag ("en-UK");

2.6. Tilgængelige lokaliteter

Selvom vi kan oprette flere kombinationer af Lokal genstande, kan vi muligvis ikke bruge dem.

En vigtig note at være opmærksom på er, at Lokale på en platform er afhængige af dem, der er installeret i Java Runtime.

Som vi bruger Lokale til formatering kan de forskellige formater have et endnu mindre sæt Lokale tilgængelige, der er installeret i Runtime.

Lad os kontrollere, hvordan man henter arrays af tilgængelige lokaliteter:

Lokal [] numberFormatLocales = NumberFormat.getAvailableLocales (); Lokal [] dateFormatLocales = DateFormat.getAvailableLocales (); Locale [] locales = Locale.getAvailableLocales ();

Derefter kan vi kontrollere, om vores Lokal bor blandt tilgængelige Lokale.

Det skal vi huske sættet af tilgængelige lokaliteter er forskelligt for forskellige implementeringer af Java-platformenog forskellige funktionsområder.

Den komplette liste over understøttede lokaliteter er tilgængelig på Oracle's Java SE Development Kit-webside.

2.7. Standard landestandard

Mens vi arbejder med lokalisering, skal vi muligvis vide, hvad standard Lokal på vores JVM eksempel er. Heldigvis er der en enkel måde at gøre det på:

Lokal standardLocale = Locale.getDefault ();

Vi kan også specificere en standard Lokal ved at kalde en lignende setter metode:

Locale.setDefault (Locale.CANADA_FRENCH);

Det er især relevant, når vi gerne vil oprette JUnit test, der ikke afhænger af en JVM eksempel.

3. Tal og valutaer

Dette afsnit henviser til tal og valutaformater, der skal være i overensstemmelse med forskellige landespecifikke konventioner.

For at formatere primitive taltyper (int, dobbelt) såvel som deres objektækvivalenter (Heltal, Dobbelt), skal vi bruge NumberFormat klasse og dens statiske fabriksmetoder.

To metoder er interessante for os:

  • NumberFormat.getInstance (landestandard)
  • NumberFormat.getCurrencyInstance (landestandard lokalitet)

Lad os undersøge en prøvekode:

Lokal usLocale = Locale.US; dobbelt tal = 102300.456d; NumberFormat usNumberFormat = NumberFormat.getInstance (usLocale); assertEquals (usNumberFormat.format (nummer), "102.300.456");

Som vi kan se er det så simpelt som at skabe Lokal og bruge den til at hente NumberFormat forekomst og formatering af et eksempelnummer. Det kan vi bemærke output inkluderer landespecifik decimal og tusind separatorer.

Her er et andet eksempel:

Lokal usLocale = Locale.US; BigDecimal tal = nyt BigDecimal (102_300.456d); NumberFormat usNumberFormat = NumberFormat.getCurrencyInstance (usLocale); assertEquals (usNumberFormat.format (nummer), "$ 102,300.46");

Formatering af en valuta involverer de samme trin som formatering af et tal. Den eneste forskel er, at formateringen tilføjer valutasymbol og rund decimaldel til to cifre.

4. Dato og klokkeslæt

Nu skal vi lære om formatering af datoer og tidspunkter, som sandsynligvis er mere komplekse end formateringsnumre.

Først og fremmest skal vi vide, at dato- og tidsformatering ændret sig væsentligt i Java 8, da den indeholder helt nyt Dato tid API. Derfor vil vi se gennem forskellige formateringsklasser.

4.1. DateTimeFormatter

Siden Java 8 blev introduceret, er hovedklassen til lokalisering af datoer og tidspunkter den DateTimeFormatter klasse. Det fungerer på klasser, der implementeres TemporalAccessor interface, for eksempel LocalDateTime, LocalDate, LocalTime eller ZonedDateTime. At oprette en DateTimeFormatter vi skal mindst give et mønster og derefter Lokal. Lad os se en eksempelkode:

Locale.setDefault (Locale.US); LocalDateTime localDateTime = LocalDateTime.of (2018, 1, 1, 10, 15, 50, 500); Strengmønster = "dd-MMMM-åååå HH: mm: ss.SSS"; DateTimeFormatter defaultTimeFormatter = DateTimeFormatter.ofPattern (mønster); DateTimeFormatter deTimeFormatter = DateTimeFormatter.ofPattern (mønster, lokalitet.GERMANY); assertEquals ("01-januar-2018 10: 15: 50.000", defaultTimeFormatter.format (localDateTime)); assertEquals ("01-Januar-2018 10: 15: 50.000", deTimeFormatter.format (localDateTime));

Vi kan se det efter hentning DateTimeFormatter alt hvad vi skal gøre er at ringe til format() metode.

For en bedre forståelse bør vi gøre os bekendt med mulige mønsterbogstaver.

Lad os se på bogstaver for eksempel:

Symbol Betydning Præsentationseksempler ------ ------- ------------ ------- y år-til-år 2004; 04 M / L måned / årstal / tekst 7; 07; Jul; Juli; J d måned-dag nummer 10 H time-dag (0-23) nummer 0 m minut-til-time nummer 30 s sekund-for-minut nummer 55 S brøkdel af sekund fraktion 978

Alle mulige mønsterbogstaver med forklaring kan findes i Java-dokumentationen til DateTimeFormatter.Det er værd at vide, at den endelige værdi afhænger af antallet af symboler. Der er 'MMMM' i eksemplet, der udskriver hele månedsnavnet, mens et enkelt 'M' bogstav giver månedstallet uden et førende 0.

For at afslutte DateTimeFormatter, lad os se, hvordan vi kan formatere LocalizedDateTime:

LocalDateTime localDateTime = LocalDateTime.of (2018, 1, 1, 10, 15, 50, 500); ZoneId losAngelesTimeZone = TimeZone.getTimeZone ("America / Los_Angeles"). ToZoneId (); DateTimeFormatter localizedTimeFormatter = DateTimeFormatter .ofLocalizedDateTime (FormatStyle.FULL); Streng formattedLocalizedTime = localizedTimeFormatter.format (ZonedDateTime.of (localDateTime, losAngelesTimeZone)); assertEquals ("Mandag 1. januar 2018 10:15:50 PST", formattedLocalizedTime);

For at formatere LocalizedDateTime, kan vi bruge ofLocalizedDateTime (FormatStyle dateTimeStyle) metode og give en foruddefineret FormatStyle.

For et mere dybtgående kig på Java 8 Dato tid API, vi har en eksisterende artikel her.

4.2. Datoformat og SimpleDateFormatter

Da det stadig er almindeligt at arbejde på projekter, der gør brug af Datoer og Kalendere, vi introducerer kort mulighederne for formatering af datoer og klokkeslæt med Datoformat og SimpleDateFormat klasser.

Lad os analysere evner fra den første:

GregorianCalendar gregorianCalendar = ny GregorianCalendar (2018, 1, 1, 10, 15, 20); Dato dato = gregorianCalendar.getTime (); DateFormat ffInstance = DateFormat.getDateTimeInstance (DateFormat.FULL, DateFormat.FULL, Locale.ITALY); DateFormat smInstance = DateFormat.getDateTimeInstance (DateFormat.SHORT, DateFormat.MEDIUM, Locale.ITALY); assertEquals ("giovedì 1 febbraio 2018 10.15.20 CET", ffInstance.format (dato)); assertEquals ("01/02/18 10.15.20", smInstance.format (dato));

Datoformat arbejder med Datoer og har tre nyttige metoder:

  • getDateTimeInstance
  • getDateInstance
  • getTimeInstance

Alle tager foruddefinerede værdier af Datoformat som en parameter. Hver metode er overbelastet, så forbipasserende Lokal er også mulig. Hvis vi vil bruge et brugerdefineret mønster, som det gøres i DateTimeFormatter, vi kan bruge SimpleDateFormat. Lad os se et kort kodestykke:

GregorianCalendar gregorianCalendar = ny GregorianCalendar (2018, 1, 1, 10, 15, 20); Dato dato = gregorianCalendar.getTime (); Locale.setDefault (ny lokalitet ("pl", "PL")); SimpleDateFormat fullMonthDateFormat = ny SimpleDateFormat ("dd-MMMM-åååå HH: mm: ss: SSS"); SimpleDateFormat shortMonthsimpleDateFormat = ny SimpleDateFormat ("dd-MM-åååå HH: mm: ss: SSS"); assertEquals ("01-lutego-2018 10: 15: 20: 000", fullMonthDateFormat.format (dato)); assertEquals ("01-02-2018 10: 15: 20: 000", shortMonthsimpleDateFormat.format (dato));

5. Tilpasning

På grund af nogle gode designbeslutninger er vi ikke bundet til et landespecifikt formateringsmønster, og vi kan konfigurere næsten alle detaljer til at være fuldt tilfredse med et output.

For at tilpasse nummerformatering kan vi bruge Decimalformat og DecimalFormatSymboler.

Lad os overveje et kort eksempel:

Locale.setDefault (Locale.FRANCE); BigDecimal tal = nyt BigDecimal (102_300.456d); DecimalFormat zeroDecimalFormat = ny DecimalFormat ("000000000.0000"); DecimalFormat dollarDecimalFormat = ny DecimalFormat ("$ ###, ###. ##"); assertEquals (zeroDecimalFormat.format (nummer), "000102300,4560"); assertEquals (dollarDecimalFormat.format (antal), "$ 102 300,46"); 

Det Decimalformat dokumentation viser alle mulige mønstertegn. Alt, hvad vi har brug for at vide nu er, at "000000000.000" bestemmer ledende eller efterfølgende nuller, ',' er tusind separator og '.' er decimal.

Det er også muligt at tilføje et valutasymbol. Vi kan se nedenfor, at det samme resultat kan opnås ved hjælp af DateFormatSymbol klasse:

Locale.setDefault (Locale.FRANCE); BigDecimal tal = nyt BigDecimal (102_300.456d); DecimalFormatSymboler decimalFormatSymbols = DecimalFormatSymbols.getInstance (); decimalFormatSymbols.setGroupingSeparator ('^'); decimalFormatSymbols.setDecimalSeparator ('@'); DecimalFormat separatorerDecimalFormat = ny DecimalFormat ("$ ###, ###. ##"); separatorsDecimalFormat.setGroupingSize (4); separatorsDecimalFormat.setCurrency (Currency.getInstance (Locale.JAPAN)); separatorsDecimalFormat.setDecimalFormatSymbols (decimalFormatSymbols); assertEquals (separatorsDecimalFormat.format (nummer), "$ 10 ^ [e-mailbeskyttet]");

Som vi kan se, DecimalFormatSymboler klasse giver os mulighed for at specificere enhver nummerformatering, vi kan forestille os.

At tilpasse SimpleDataFormat, vi kan bruge DateFormatSymbols.

Lad os se, hvor enkelt det er at ændre dagsnavne:

Dato dato = ny gregoriansk kalender (2018, 1, 1, 10, 15, 20) .getTime (); Locale.setDefault (ny lokalitet ("pl", "PL")); DateFormatSymbols dateFormatSymbols = nye DateFormatSymbols (); dateFormatSymbols.setWeekdays (ny streng [] {"A", "B", "C", "D", "E", "F", "G", "H"}); SimpleDateFormat newDaysDateFormat = ny SimpleDateFormat ("EEEE-MMMM-åååå HH: mm: ss: SSS", dateFormatSymbols); assertEquals ("F-lutego-2018 10: 15: 20: 000", newDaysDateFormat.format (dato));

6. Ressourcepakker

Endelig er den afgørende del af internationaliseringen i JVM er Ressourcepakke mekanisme.

Formålet med en ResourceBundle er at give en applikation lokaliserede meddelelser / beskrivelser, som kan eksternaliseres til de separate filer. Vi dækker brug og konfiguration af ressourcepakken i en af ​​vores tidligere artikler - guide til ressourcepakken.

7. Konklusion

Lokale og formaterne, der bruger dem, er værktøjer, der hjælper os med at oprette en internationaliseret applikation. Disse værktøjer giver os mulighed for at oprette en applikation, der dynamisk kan tilpasse sig brugerens sproglige eller kulturelle indstillinger uden flere builds eller endda behøver at bekymre sig om Java understøtter Lokal.

I en verden, hvor en bruger kan være hvor som helst og tale ethvert sprog, betyder evnen til at anvende disse ændringer, at vores applikationer kan være mere intuitive og forståelige for flere brugere globalt.

Når vi arbejder med Spring Boot-applikationer, har vi også en praktisk artikel til Spring Boot Internationalization.

Kildekoden til denne vejledning med fulde eksempler kan findes på GitHub.