Internationalisering en lokalisatie in Java 8

1. Overzicht

Internationalisering is een proces waarbij een aanvraag wordt voorbereid ter ondersteuning van verschillende taalkundige, regionale, culturele of politiek-specifieke gegevens. Het is een essentieel aspect van elke moderne meertalige applicatie.

Om verder te lezen, we moeten weten dat er een zeer populaire afkorting is (waarschijnlijk populairder dan de echte naam) voor internationalisering - i18n vanwege de 18 letters tussen 'i' en 'n'.

Het is cruciaal voor huidige bedrijfsprogramma's om mensen uit verschillende delen van de wereld of uit meerdere culturele gebieden te dienen. Afzonderlijke culturele of taalgebieden bepalen niet alleen taalspecifieke beschrijvingen, maar ook valuta, cijferweergave en zelfs afwijkende datum- en tijdsamenstelling.

Laten we ons bijvoorbeeld concentreren op landspecifieke nummers. Ze hebben verschillende decimalen en scheidingstekens voor duizendtallen:

  • 102.300,45 (Verenigde Staten)
  • 102300,45 (Polen)
  • 102.300,45 (Duitsland)

Er zijn ook verschillende datumnotaties:

  • Maandag 1 januari 2018 15:20:34 uur CET (Verenigde Staten)
  • lundi 1 janvier 2018 15 h 20 CET (Frankrijk).
  • 2018 年 1 月 1 日 星期一 下午 03 时 20 分 34 秒 CET (China)

Bovendien hebben verschillende landen unieke valutasymbolen:

  • £ 1.200,60 (Verenigd Koninkrijk)
  • € 1.200,60 (Italië)
  • 1200,60 € (Frankrijk)
  • $ 1.200,60 (Verenigde Staten)

Een belangrijk feit om te weten is dat zelfs als landen dezelfde valuta en hetzelfde valutasymbool hebben - zoals Frankrijk en Italië - de positie van hun valutasymbool anders zou kunnen zijn.

2. Lokalisatie

Binnen Java hebben we een fantastische functie tot onze beschikking, de Locale klasse.

Het stelt ons in staat om snel onderscheid te maken tussen culturele locaties en onze inhoud op de juiste manier op te maken. Het is essentieel voor binnen het internationaliseringsproces. Hetzelfde als i18n, heeft lokalisatie ook de afkorting - l10n.

De belangrijkste reden voor gebruik Locale is dat alle vereiste locale-specifieke opmaak toegankelijk is zonder hercompilatie. Een applicatie kan meerdere landinstellingen tegelijk aan, dus het ondersteunen van een nieuwe taal is eenvoudig.

Landinstellingen worden meestal weergegeven door taal, land en variantafkorting, gescheiden door een onderstrepingsteken:

  • de (Duits)
  • it_CH (Italiaans, Zwitserland)
  • en_US_UNIX (Verenigde Staten, UNIX-platform)

2.1. Velden

Dat hebben we al geleerd Locale bestaat uit taalcode, landcode en variant. Er zijn nog twee mogelijke velden om in te stellen: script en extensies.

Laten we een lijst met velden bekijken en kijken wat de regels zijn:

  • Taal kan een ISO 639 alpha-2 of alpha-3 code of geregistreerde taal subtag.
  • Regio (Land) is ISO 3166 alfa-2 landcode of UN numeriek-3 netnummer.
  • Variant is een hoofdlettergevoelige waarde of een reeks waarden die een variatie van een Locale.
  • Script moet een geldig zijn ISO 15924 alpha-4 code.
  • Extensies is een kaart die bestaat uit toetsen van één teken en Draad waarden.

Het IANA Language Subtag Registry bevat mogelijke waarden voor taal, regio, variant en script.

Er is geen lijst met mogelijke uitbreiding waarden, maar de waarden moeten goed gevormd zijn BCP-47 subtags. De sleutels en waarden worden altijd naar kleine letters geconverteerd.

2.2. Locale.Builder

Er zijn verschillende manieren om te creëren Locale voorwerpen. Een mogelijke manier maakt gebruik van Locale.Builder. Locale.Builder heeft vijf setter-methoden die we kunnen gebruiken om het object te bouwen en tegelijkertijd die waarden te valideren:

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

De Draad vertegenwoordiging van het bovenstaande Locale is fr_CA_POSIX_ # Latn.

Dat is goed om te weten het instellen van ‘variant 'kan een beetje lastig zijn, aangezien er geen officiële beperking is voor variantwaarden, hoewel de setter-methode dit vereist BCP-47 compatibel.

Anders zal het gooien IllformedLocaleException.

In het geval dat we een waarde moeten gebruiken die niet door de validatie komt, kunnen we gebruiken Locale constructeurs omdat ze geen waarden valideren.

2.3. Constructeurs

Locale heeft drie constructeurs:

  • nieuwe locale (String-taal)
  • nieuwe locale (String-taal, String-land)
  • nieuwe locale (String-taal, String-land, String-variant)

Een constructor met 3 parameters:

Locale locale = nieuwe Locale ("pl", "PL", "UNIX");

Een geldige variant moet een Draad van 5 tot 8 alfanumerieke tekens of enkelvoudig numeriek gevolgd door 3 alfanumerieke tekens. We kunnen alleen "UNIX" toepassen op het variant veld alleen via constructor omdat het niet aan die vereisten voldoet.

Er is echter een nadeel van het gebruik van constructors om Locale objecten - we kunnen geen extensies en scriptvelden instellen.

2.4. Constanten

Dit is waarschijnlijk de eenvoudigste en meest beperkte manier om te krijgen Landinstellingen. De Locale klasse heeft verschillende statische constanten die het meest populaire land of de meest populaire taal vertegenwoordigen:

Locale japan = Locale.JAPAN; Locale japans = Locale.JAPANESE;

2.5. Taal tags

Een andere manier van creëren Locale roept de statische fabrieksmethode aan forLanguageTag (String languageTag). Deze methode vereist een Draad dat voldoet aan de IETF BCP 47 standaard.

Dit is hoe we het VK kunnen creëren Locale:

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

2.6. Beschikbare landinstellingen

Ook al kunnen we meerdere combinaties maken van Locale objecten, kunnen we ze mogelijk niet gebruiken.

Een belangrijke opmerking om op te letten is dat de Landinstellingen op een platform zijn afhankelijk van degene die zijn geïnstalleerd binnen de Java Runtime.

Zoals we gebruiken Landinstellingen voor het formatteren kunnen de verschillende formatters een nog kleinere set hebben van Landinstellingen beschikbaar die zijn geïnstalleerd in de Runtime.

Laten we eens kijken hoe we arrays met beschikbare landinstellingen kunnen ophalen:

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

Daarna kunnen we controleren of onze Locale bevindt zich onder de beschikbare Landinstellingen.

Dat moeten we onthouden de set beschikbare landinstellingen is verschillend voor verschillende implementaties van het Java-platformen verschillende functionaliteiten.

De volledige lijst met ondersteunde landinstellingen is beschikbaar op de webpagina van Oracle's Java SE Development Kit.

2.7. Standaardlandinstelling

Tijdens het werken met lokalisatie moeten we wellicht weten wat de standaardinstelling is Locale op onze JVM instantie is. Gelukkig is er een eenvoudige manier om dat te doen:

Locale defaultLocale = Locale.getDefault ();

We kunnen ook een standaard specificeren Locale door een vergelijkbare setter-methode aan te roepen:

Locale.setDefault (Locale.CANADA_FRENCH);

Het is vooral relevant wanneer we willen creëren JUnit tests die niet afhankelijk zijn van een JVM voorbeeld.

3. Cijfers en valuta

Dit gedeelte verwijst naar de opmaak van getallen en valuta die moeten voldoen aan verschillende landspecifieke conventies.

Om primitieve nummertypen (int, dubbele) evenals hun objectequivalenten (Geheel getal, Dubbele), moeten we gebruiken Nummer formaat klasse en zijn statische fabrieksmethoden.

Twee methoden zijn voor ons interessant:

  • NumberFormat.getInstance (landinstelling)
  • NumberFormat.getCurrencyInstance (landinstelling)

Laten we een voorbeeldcode bekijken:

Locale usLocale = Locale.US; dubbel getal = 102300.456d; NumberFormat usNumberFormat = NumberFormat.getInstance (usLocale); assertEquals (usNumberFormat.format (nummer), "102,300.456");

Zoals we kunnen zien, is het net zo eenvoudig als creëren Locale en het gebruiken om op te halen Nummer formaat instantie en het formatteren van een voorbeeldnummer. Dat merken we de uitvoer bevat locale-specifieke decimalen en scheidingstekens voor duizendtallen.

Hier is nog een voorbeeld:

Locale usLocale = Locale.US; BigDecimal-nummer = nieuw BigDecimal (102_300.456d); NumberFormat usNumberFormat = NumberFormat.getCurrencyInstance (usLocale); assertEquals (usNumberFormat.format (nummer), "$ 102.300,46");

Het opmaken van een valuta omvat dezelfde stappen als het opmaken van een getal. Het enige verschil is dat de formatter het valutasymbool en het ronde decimale deel aan twee cijfers toevoegt.

4. Datum en tijd

Nu gaan we leren over het opmaken van datums en tijden, wat waarschijnlijk complexer is dan het opmaken van getallen.

Allereerst moeten we weten dat de datum- en tijdnotatie aanzienlijk is veranderd in Java 8, omdat het volledig nieuw is Datum Tijd API. Daarom gaan we door verschillende formatter-klassen kijken.

4.1. DateTimeFormatter

Sinds de introductie van Java 8 is de hoofdklasse voor het lokaliseren van datums en tijden de DateTimeFormatter klasse. Het werkt op klassen die implementeren TemporalAccessor interface, bijvoorbeeld LocalDateTime, LocalDate, LocalTime of ZonedDateTime. Om een DateTimeFormatter we moeten in ieder geval een patroon geven, en dan Landinstelling. Laten we een voorbeeldcode bekijken:

Locale.setDefault (Locale.US); LocalDateTime localDateTime = LocalDateTime.of (2018, 1, 1, 10, 15, 50, 500); Tekenreekspatroon = "dd-MMMM-jjjj UU: mm: ss.SSS"; DateTimeFormatter defaultTimeFormatter = DateTimeFormatter.ofPattern (patroon); DateTimeFormatter deTimeFormatter = DateTimeFormatter.ofPattern (patroon, Locale.GERMANY); assertEquals ("01-januari-2018 10: 15: 50.000", defaultTimeFormatter.format (localDateTime)); assertEquals ("01-Januar-2018 10: 15: 50.000", deTimeFormatter.format (localDateTime));

Dat kunnen we zien na het ophalen DateTimeFormatter het enige wat we hoeven te doen is de formaat() methode.

Voor een beter begrip moeten we vertrouwd raken met mogelijke patroonbrieven.

Laten we bijvoorbeeld naar letters kijken:

Symbool Betekenis Presentatie Voorbeelden ------ ------- ------------ ------- y jaartaljaar 2004; 04 M / L maandnummer / tekst 7; 07; Jul; Juli; J d dag-van-maand nummer 10 H uur-van-dag (0-23) nummer 0 m minuut-van-uur nummer 30 s tweede-van-minuut nummer 55 S fractie-van-seconde 978

Alle mogelijke patroonbrieven met uitleg zijn te vinden in de Java-documentatie van DateTimeFormatter.Het is de moeite waard om te weten dat de uiteindelijke waarde afhangt van het aantal symbolen. Er is ‘MMMM 'in het voorbeeld dat de volledige maandnaam afdrukt, terwijl een enkele‘ M'-letter het maandnummer zou geven zonder een voorafgaande 0.

Om af te sluiten DateTimeFormatter, laten we eens kijken hoe we kunnen formatteren 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); String formattedLocalizedTime = localizedTimeFormatter.format (ZonedDateTime.of (localDateTime, losAngelesTimeZone)); assertEquals ("Maandag 1 januari 2018 10:15:50 AM PST", formattedLocalizedTime);

Om te formatteren LocalizedDateTime, kunnen we de ofLocalizedDateTime (FormatStyle dateTimeStyle) methode en geef een voorgedefinieerde Formaatstijl.

Voor een meer diepgaande blik op Java 8 Datum Tijd API, we hebben hier een bestaand artikel.

4.2. Datumnotatie en SimpleDateFormatter

Omdat het nog steeds gebruikelijk is om aan projecten te werken die gebruik maken van Datums en Kalendersintroduceren we kort mogelijkheden voor het opmaken van datums en tijden met Datumnotatie en SimpleDateFormat klassen.

Laten we de mogelijkheden van de eerste analyseren:

GregorianCalendar gregorianCalendar = nieuwe GregorianCalendar (2018, 1, 1, 10, 15, 20); Datum datum = 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 (datum)); assertEquals ("01/02/18 10.15.20", smInstance.format (datum));

Datumnotatie werkt met Datums en heeft drie handige methoden:

  • getDateTimeInstance
  • getDateInstance
  • getTimeInstance

Ze nemen allemaal vooraf gedefinieerde waarden van Datumnotatie als parameter. Elke methode is overbelast, dus voorbijgaand Locale is ook mogelijk. Als we een aangepast patroon willen gebruiken, zoals het is gedaan in DateTimeFormatter, we kunnen gebruiken SimpleDateFormat. Laten we een kort codefragment bekijken:

GregorianCalendar gregorianCalendar = nieuwe GregorianCalendar (2018, 1, 1, 10, 15, 20); Datum datum = gregorianCalendar.getTime (); Locale.setDefault (nieuwe locale ("pl", "PL")); SimpleDateFormat fullMonthDateFormat = nieuwe SimpleDateFormat ("dd-MMMM-jjjj HH: mm: ss: SSS"); SimpleDateFormat shortMonthsimpleDateFormat = nieuwe SimpleDateFormat ("dd-MM-jjjj HH: mm: ss: SSS"); assertEquals ("01-lutego-2018 10: 15: 20: 000", fullMonthDateFormat.format (datum)); assertEquals ("01-02-2018 10: 15: 20: 000", shortMonthsimpleDateFormat.format (datum));

5. Maatwerk

Vanwege een aantal goede ontwerpbeslissingen zijn we niet gebonden aan een landspecifiek opmaakpatroon en kunnen we bijna elk detail configureren om volledig tevreden te zijn met een uitvoer.

Om de nummeropmaak aan te passen, kunnen we gebruiken DecimalFormat en DecimalFormatSymbols.

Laten we een kort voorbeeld bekijken:

Locale.setDefault (Locale.FRANCE); BigDecimal-nummer = nieuw BigDecimal (102_300.456d); DecimalFormat zeroDecimalFormat = nieuw DecimalFormat ("000000000.0000"); DecimalFormat dollarDecimalFormat = nieuw DecimalFormat ("$ ###, ###. ##"); assertEquals (zeroDecimalFormat.format (getal), "000102300,4560"); assertEquals (dollarDecimalFormat.format (getal), "$ 102300,46"); 

De DecimalFormat documentatie toont alle mogelijke patroonkarakters. Het enige dat we nu moeten weten is dat "000000000.000" de voorloop- of volgnullen bepaalt, ‘, 'een duizendtal scheidingsteken is en‘.' is een decimaal.

Het is ook mogelijk om een ​​valutasymbool toe te voegen. We kunnen hieronder zien dat hetzelfde resultaat kan worden bereikt door te gebruiken Datum Formaat Symbool klasse:

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

Zoals we kunnen zien, DecimalFormatSymbols class stelt ons in staat om elke getalopmaak te specificeren die we maar kunnen bedenken.

Aan te passen SimpleDataFormat, we kunnen gebruiken Datumopmaaksymbolen.

Laten we eens kijken hoe eenvoudig een verandering van dagnamen is:

Datum datum = nieuwe Gregoriaanse kalender (2018, 1, 1, 10, 15, 20) .getTime (); Locale.setDefault (nieuwe locale ("pl", "PL")); DateFormatSymbols dateFormatSymbols = nieuwe DateFormatSymbols (); dateFormatSymbols.setWeekdays (nieuwe String [] {"A", "B", "C", "D", "E", "F", "G", "H"}); SimpleDateFormat newDaysDateFormat = nieuwe SimpleDateFormat ("EEEE-MMMM-jjjj HH: mm: ss: SSS", dateFormatSymbols); assertEquals ("F-lutego-2018 10: 15: 20: 000", newDaysDateFormat.format (date));

6. Bronnenbundels

Ten slotte is het cruciale onderdeel van internationalisering in de JVM is de Bronnenbundel mechanisme.

Het doel van een ResourceBundle is om een ​​applicatie te voorzien van gelokaliseerde berichten / beschrijvingen die naar de afzonderlijke bestanden kunnen worden geëxternaliseerd. We behandelen het gebruik en de configuratie van de bronnenbundel in een van onze vorige artikelen - gids voor de bronnenbundel.

7. Conclusie

Landinstellingen en de formatters die ze gebruiken, zijn tools die ons helpen een geïnternationaliseerde applicatie te maken. Met deze tools kunnen we een applicatie maken die zich dynamisch kan aanpassen aan de taalkundige of culturele instellingen van de gebruiker zonder meerdere builds of zelfs zonder ons zorgen te hoeven maken of Java de Locale.

In een wereld waarin een gebruiker overal kan zijn en elke taal kan spreken, betekent de mogelijkheid om deze veranderingen toe te passen dat onze applicaties intuïtiever en begrijpelijker kunnen zijn voor meer gebruikers wereldwijd.

Als u met Spring Boot-applicaties werkt, hebben we ook een handig artikel voor Spring Boot Internationalization.

De broncode van deze tutorial, met volledige voorbeelden, is te vinden op GitHub.