Java-lokalisatie - Berichten opmaken

1. Inleiding

In deze tutorial zullen we bekijken hoe we dat kunnen lokaliseer en formatteer berichten gebaseerd op Locale.

We gebruiken beide Java's MessageFormat en de bibliotheek van derden, ICU.

2. Gebruiksscenario voor lokalisatie

Wanneer onze applicatie een breed publiek van gebruikers van over de hele wereld trekt, willen we dat natuurlijk graag verschillende berichten weergeven op basis van de voorkeuren van de gebruiker.

Het eerste en belangrijkste aspect is de taal die de gebruiker spreekt. Andere kunnen valuta-, getal- en datumnotaties bevatten. Last but not least zijn er culturele voorkeuren: wat acceptabel is voor gebruikers uit het ene land, kan ondraaglijk zijn voor andere.

Stel dat we een e-mailclient hebben en we willen notificaties laten zien wanneer er een nieuw bericht binnenkomt.

Een eenvoudig voorbeeld van zo'n bericht zou dit kunnen zijn:

Alice heeft je een bericht gestuurd.

Het is prima voor Engelssprekende gebruikers, maar niet-Engels sprekende gebruikers zijn misschien niet zo blij. Franstalige gebruikers zien dit bericht bijvoorbeeld liever:

Alice vous een gezant een bericht. 

Terwijl Poolse mensen blij zouden zijn door deze te zien:

Alice wysłała ci wiadomość. 

Wat als we een correct opgemaakte melding willen hebben, zelfs in het geval dat Alice niet slechts één bericht stuurt, maar weinig berichten?

We zouden in de verleiding kunnen komen om het probleem aan te pakken door verschillende stukken in een enkele string samen te voegen, als volgt:

String message = "Alice heeft verzonden" + aantal + "berichten"; 

De situatie kan gemakkelijk uit de hand lopen als we meldingen nodig hebben in het geval dat niet alleen Alice maar ook Bob de berichten zou kunnen verzenden:

Bob heeft twee berichten gestuurd. Bob een gezant deux-berichten. Bob wysłał dwie wiadomości.

Merk op hoe het werkwoord verandert in het geval van Pools (wysłała vs wysłał) taal. Het illustreert het feit dat banale aaneenschakeling van tekenreeksen is zelden acceptabel voor het lokaliseren van berichten.

Zoals we zien, krijgen we twee soorten problemen: de ene is gerelateerd aan vertalingen en de andere is gerelateerd aan formaten. Laten we ze in de volgende secties bespreken.

3. Lokalisatie van berichten

We kunnen de lokalisatie, of l10n, van een applicatie als het proces van het aanpassen van de applicatie aan het comfort van de gebruiker. Soms de term internalisatie, of i18n, wordt ook gebruikt.

Om de applicatie te lokaliseren, laten we allereerst alle hardgecodeerde berichten elimineren door ze naar ons middelen map:

Elk bestand moet sleutel / waarde-paren bevatten met de berichten in de corresponderende taal. Bijvoorbeeld file messages_nl.properties moet het volgende paar bevatten:

label = Alice heeft je een bericht gestuurd.

messages_pl.properties moet het volgende paar bevatten:

label = Alice wysłała ci wiadomość.

Evenzo wijzen andere bestanden de juiste waarden toe aan de sleutel etiket. Nu kunnen we gebruiken om de Engelse versie van de melding op te halen ResourceBundle:

ResourceBundle bundle = ResourceBundle.getBundle ("berichten", Locale.UK); String message = bundle.getString ("label");

De waarde van de variabele bericht zal zijn "Alice heeft je een bericht gestuurd."

Java's Locale class bevat snelkoppelingen naar veelgebruikte talen en landen.

In het geval van de Poolse taal kunnen we het volgende schrijven:

ResourceBundle bundle = ResourceBundle.getBundle ("berichten", Locale.forLanguageTag ("pl-PL")); String message = bundle.getString ("label");

Laten we even vermelden dat als we geen locale bieden, het systeem een ​​standaard zal gebruiken. We kunnen meer details over dit probleem vinden in ons artikel "Internationalisering en lokalisatie in Java 8". Vervolgens kiest het systeem uit de beschikbare vertalingen de vertaling die het meest lijkt op de momenteel actieve landinstelling.

Het plaatsen van de berichten in de bronbestanden is een goede stap om de applicatie gebruiksvriendelijker te maken. Het maakt het gemakkelijker om de hele applicatie te vertalen om de volgende redenen:

  1. een vertaler hoeft niet door de applicatie te kijken om de berichten te zoeken
  2. een vertaler kan de hele zin zien, wat helpt om de context te begrijpen en dus een betere vertaling mogelijk maakt
  3. we hoeven niet de hele applicatie opnieuw te compileren als er een vertaling voor een nieuwe taal klaar is

4. Berichtformaat

Hoewel we de berichten van de code naar een aparte locatie hebben verplaatst, bevatten ze nog steeds wat hardgecodeerde informatie. Het zou fijn zijn om de namen en nummers in de berichten aan te passen zodanig dat ze grammaticaal correct blijven.

We kunnen de opmaak definiëren als een proces van het renderen van de string-sjabloon door de plaatshouders te vervangen door hun waarden.

In de volgende secties zullen we twee oplossingen bekijken waarmee we de berichten kunnen opmaken.

4.1. Java's MessageFormat

Om strings te formatteren, definieert Java talrijke opmaakmethoden in java.lang.String. Maar we kunnen nog meer ondersteuning krijgen via java.text.format.MessageFormat.

Laten we ter illustratie een patroon maken en dit naar een MessageFormat voorbeeld:

String pattern = "Op {0, date}, {1} stuurde je" + "{2, choice, 0 # geen berichten | 1 # een bericht | 2 # twee berichten | 2 <{2, number, integer} berichten} . "; MessageFormat-formatter = nieuw MessageFormat (patroon, Locale.UK); 

De patroontekenreeks heeft slots voor drie tijdelijke aanduidingen.

Als we elke waarde leveren:

String message = formatter.format (new Object [] {date, "Alice", 2});

Dan MessageFormat vult het sjabloon in en geeft ons bericht weer:

Op 27 april 2019 heeft Alice je twee berichten gestuurd.

4.2. MessageFormat Syntaxis

Uit het bovenstaande voorbeeld zien we dat het berichtpatroon:

pattern = "Op {...} heeft {..} je {...} gestuurd.";

bevat tijdelijke aanduidingen die de accolades zijn {…} met een vereist argument inhoudsopgave en twee optionele argumenten, type en stijl:

{index} {index, type} {index, type, stijl}

De index van de tijdelijke aanduiding komt overeen met de positie van een element uit de reeks objecten die we willen invoegen.

Indien aanwezig is het type en stijl kan de volgende waarden aannemen:

typestijl
aantalgeheel getal, valuta, percentage, aangepast formaat
datumkort, medium, lang, volledig, aangepast formaat
tijdkort, medium, lang, volledig, aangepast formaat
keuzeaangepast formaat

De namen van de typen en stijlen spreken grotendeels voor zich, maar we kunnen de officiële documentatie raadplegen voor meer details.

Laten we echter eens nader kijken naar aangepast formaat.

In het bovenstaande voorbeeld hebben we de volgende formaatuitdrukking gebruikt:

{2, choice, 0 # geen berichten | 1 # een bericht | 2 # twee berichten | 2 <{2, number, integer} berichten}

Over het algemeen heeft de keuzestijl de vorm van opties gescheiden door de verticale balk (of buis):

Binnen de opties, de matchwaarde kik en de string vik worden gescheiden door # behalve de laatste optie. Merk op dat we andere patronen in de string kunnen nestelen vik zoals we het deden voor de laatste optie:

{2, choice, ... | 2 <{2, number, integer} berichten}

Het keuze type is een numeriek type, dus er is een natuurlijke volgorde voor de overeenkomstwaarden kik die een numerieke regel in intervallen splitsen:

Als we een waarde geven k dat hoort bij het interval [kik, kik + 1) (het linker uiteinde is inbegrepen, het rechter uiteinde is uitgesloten), dan waarde vik is geselecteerd.

Laten we de reeksen van de gekozen stijl eens nader bekijken. Hiervoor gebruiken we dit patroon:

pattern = "Je hebt" + "{0, keuze, 0 # geen berichten | 1 # een bericht | 2 # twee berichten | 2 <{0, getal, geheel getal} berichten}.";

en verschillende waarden doorgeven voor zijn unieke tijdelijke aanduiding:

nbericht
-1, 0, 0.5Je hebt geen berichten.
1, 1.5Je hebt een bericht.
2Je hebt twee berichten.
2.5Je hebt 2 berichten.
5Je hebt 5 berichten.

4.3. Dingen beter maken

Dus we formatteren nu onze berichten. Maar het bericht zelf blijft hard gecodeerd.

Uit de vorige sectie weten we dat we de tekenreekspatronen naar de bronnen moeten extraheren. Om onze zorgen te scheiden, gaan we nog een stel bronbestanden maken met de naam formaten:

Daarin maken we een sleutel genaamd etiket met taalspecifieke inhoud.

In de Engelse versie plaatsen we bijvoorbeeld de volgende tekenreeks:

label = Op {0, date, full} heeft {1} je + {2, choice, 0 # niets | 1 # een bericht | 2 # twee berichten | 2 <{2, getal, geheel getal} berichten} gestuurd.

We moeten de Franse versie enigszins aanpassen vanwege het nul-berichtgeval:

label = {0, date, short}, {1} 0 <vous a envoyé + {2, choice, 0 # aucun message | 1 # un message | 2 # deux messages | 2 <{2, number, integer} messages} .

En we zouden ook soortgelijke wijzigingen moeten aanbrengen in de Poolse en Italiaanse versies.

In feite vertoont de Poolse versie nog een ander probleem. Volgens de grammatica van de Poolse taal (en vele andere) moet het werkwoord qua geslacht overeenkomen met het onderwerp. We zouden dit probleem kunnen oplossen door het keuze-type te gebruiken, maar laten we een andere oplossing overwegen.

4.4. ICU's MessageFormat

Laten we de Internationale componenten voor Unicode (ICU) bibliotheek. We hebben het al genoemd in onze tutorial Convert a String to Title Case. Het is een volwassen en veelgebruikte oplossing waarmee we de applicatie voor verschillende talen kunnen aanpassen.

Hier gaan we het niet in detail onderzoeken. We beperken ons alleen tot wat onze speelgoedtoepassing nodig heeft. Voor de meest uitgebreide en bijgewerkte informatie, moeten we de officiële site van de ICU raadplegen.

Op het moment van schrijven was de nieuwste versie van ICU voor Java (ICU4J) is 64,2. Om het te kunnen gebruiken, moeten we het, zoals gewoonlijk, als afhankelijkheid aan ons project toevoegen:

 com.ibm.icu icu4j 64.2 

Stel dat we een correct opgemaakte melding willen hebben in verschillende talen en voor verschillende aantallen berichten:

NEngelsPools
0Alice heeft je geen berichten gestuurd.

Bob heeft je geen berichten gestuurd.

Alice weet niet wat ze zegt.

Bob nie wysłał ci żadnej wiadomości.

1Alice heeft je een bericht gestuurd.

Bob heeft je een bericht gestuurd.

Alice wysłała ci wiadomość.

Bob wysłał ci wiadomość.

> 1Alice heeft je N-berichten gestuurd.

Bob heeft je N berichten gestuurd.

Alice wysłała ci N wiadomości.

Bob wysłał ci N wiadomości.

Allereerst moeten we een patroon maken in de landspecifieke bronbestanden.

Laten we het bestand opnieuw gebruiken formats.properties en voeg daar een sleutel toe label-icu met de volgende inhoud:

label-icu = {0} heeft je + {2, plural, = 0 {geen berichten} = 1 {een bericht} + andere {{2, getal, geheel getal} berichten}} gestuurd.

Het bevat drie tijdelijke aanduidingen die we voeden door daar een matrix met drie elementen door te geven:

Object [] data = nieuw object [] {"Alice", "vrouwelijk", 0}

We zien dat in de Engelse versie de plaatsaanduiding met geslachtswaarde geen zin heeft, terwijl in de Poolse:

label-icu = {0} {2, plural, = 0 {nie} other {}} + {1, select, man {wysłał} vrouw {wysłała} anders {wysłało}} + ci {2, plural, = 0 { żadnych wiadomości} = 1 {wiadomość} + andere {{2, getal, geheel getal} wiadomości}}.

we gebruiken het om onderscheid te maken tussen wysłał / wysłała / wysłało.

5. Conclusie

In deze tutorial hebben we overwogen hoe we de berichten die we demonstreren aan de gebruikers van onze applicaties, kunnen lokaliseren en formatteren.

Zoals altijd staan ​​de codefragmenten voor deze tutorial in onze GitHub-repository.