Java-Lokalisierung - Formatieren von Nachrichten

1. Einleitung

In diesem Tutorial werden wir uns überlegen, wie wir Nachrichten basierend auf dem Gebietsschema lokalisieren und formatieren können .

Wir werden sowohl Javas MessageFormat als auch die Drittanbieter-Bibliothek ICU verwenden.

2. Anwendungsfall für die Lokalisierung

Wenn unsere Anwendung ein breites Publikum von Benutzern aus der ganzen Welt erreicht, möchten wir natürlich je nach den Vorlieben des Benutzers unterschiedliche Nachrichten anzeigen .

Der erste und wichtigste Aspekt ist die Sprache, die der Benutzer spricht. Andere können Währungs-, Zahlen- und Datumsformate enthalten. Last but not least sind kulturelle Vorlieben: Was für Benutzer aus einem Land akzeptabel ist, kann für andere unerträglich sein.

Angenommen, wir haben einen E-Mail-Client und möchten Benachrichtigungen anzeigen, wenn eine neue Nachricht eintrifft.

Ein einfaches Beispiel für eine solche Nachricht könnte sein:

Alice has sent you a message.

Es ist in Ordnung für englischsprachige Benutzer, aber nicht englischsprachige Benutzer sind möglicherweise nicht so glücklich. Beispielsweise möchten französischsprachige Benutzer diese Nachricht lieber sehen:

Alice vous a envoyé un message. 

Während sich die Polen über diesen freuen würden:

Alice wysłała ci wiadomość. 

Was ist, wenn wir eine ordnungsgemäß formatierte Benachrichtigung wünschen, selbst wenn Alice nicht nur eine Nachricht, sondern nur wenige Nachrichten sendet?

Wir könnten versucht sein, das Problem zu lösen, indem wir verschiedene Teile in einer einzigen Zeichenfolge wie folgt verketten:

String message = "Alice has sent " + quantity + " messages"; 

Die Situation kann leicht außer Kontrolle geraten, wenn wir Benachrichtigungen benötigen, wenn nicht nur Alice, sondern auch Bob die Nachrichten senden könnten:

Bob has sent two messages. Bob a envoyé deux messages. Bob wysłał dwie wiadomości.

Beachten Sie, wie sich das Verb im Fall der polnischen Sprache ( wysłała vs wysłał ) ändert . Es zeigt die Tatsache, dass die Verkettung banaler Zeichenfolgen für die Lokalisierung von Nachrichten selten akzeptabel ist .

Wie wir sehen, gibt es zwei Arten von Problemen: eines bezieht sich auf Übersetzungen und das andere auf Formate . Lassen Sie uns sie in den folgenden Abschnitten ansprechen.

3. Nachrichtenlokalisierung

Wir können die Lokalisierung oder l10n einer Anwendung als den Prozess der Anpassung der Anwendung an den Komfort des Benutzers definieren . Manchmal wird auch der Begriff Internalisierung oder i18n verwendet.

Um die Anwendung zu lokalisieren, entfernen wir zunächst alle fest codierten Nachrichten, indem wir sie in unseren Ressourcenordner verschieben :

Jede Datei sollte Schlüssel-Wert-Paare mit den Nachrichten in der entsprechenden Sprache enthalten. Beispielsweise sollte die Datei messages_en.properties das folgende Paar enthalten:

label=Alice has sent you a message.

messages_pl.properties sollte das folgende Paar enthalten:

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

In ähnlicher Weise zuweisen andere Dateien die entsprechenden Werte auf den Schlüssel - Label . Um die englische Version der Benachrichtigung abzurufen, können wir jetzt ResourceBundle verwenden :

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

Der Wert der variablen Nachricht lautet "Alice hat Ihnen eine Nachricht gesendet."

Die Locale- Klasse von Java enthält Verknüpfungen zu häufig verwendeten Sprachen und Ländern.

Im Fall der polnischen Sprache könnten wir Folgendes schreiben:

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

Lassen Sie uns nur erwähnen, dass das System ein Standardgebietsschema verwendet, wenn wir kein Gebietsschema angeben. Weitere Informationen zu diesem Thema finden Sie in unserem Artikel „Internationalisierung und Lokalisierung in Java 8“. Anschließend wählt das System unter den verfügbaren Übersetzungen diejenige aus, die dem derzeit aktiven Gebietsschema am ähnlichsten ist.

Das Platzieren der Nachrichten in den Ressourcendateien ist ein guter Schritt, um die Anwendung benutzerfreundlicher zu gestalten. Es erleichtert die Übersetzung der gesamten Anwendung aus folgenden Gründen:

  1. Ein Übersetzer muss nicht in der Anwendung nach Nachrichten suchen
  2. Ein Übersetzer kann die gesamte Phrase sehen, was hilft, den Kontext zu erfassen und somit eine bessere Übersetzung zu ermöglichen
  3. Wir müssen nicht die gesamte Anwendung neu kompilieren, wenn eine Übersetzung für eine neue Sprache fertig ist

4. Nachrichtenformat

Obwohl wir die Nachrichten aus dem Code an einen separaten Speicherort verschoben haben, enthalten sie dennoch einige fest codierte Informationen. Es wäre schön, die Namen und Nummern in den Nachrichten so anpassen zu können, dass sie grammatikalisch korrekt bleiben.

Wir können die Formatierung als einen Prozess zum Rendern der Zeichenfolgenvorlage definieren, indem wir die Platzhalter durch ihre Werte ersetzen.

In den folgenden Abschnitten werden zwei Lösungen betrachtet, mit denen wir die Nachrichten formatieren können.

4.1. Javas MessageFormat

Um Zeichenfolgen zu formatieren, definiert Java in java.lang.String zahlreiche Formatierungsmethoden . Über java.text.format.MessageFormat können wir jedoch noch mehr Unterstützung erhalten .

Zur Veranschaulichung erstellen wir ein Muster und geben es an eine MessageFormat- Instanz weiter:

String pattern = "On {0, date}, {1} sent you " + "{2, choice, 0#no messages|1#a message|2#two messages|2<{2, number, integer} messages}."; MessageFormat formatter = new MessageFormat(pattern, Locale.UK); 

Die Musterzeichenfolge verfügt über Steckplätze für drei Platzhalter.

Wenn wir jeden Wert liefern:

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

Dann füllt MessageFormat die Vorlage aus und rendert unsere Nachricht:

On 27-Apr-2019, Alice sent you two messages.

4.2. MessageFormat- Syntax

Aus dem obigen Beispiel sehen wir, dass das Nachrichtenmuster:

pattern = "On {...}, {..} sent you {...}.";

enthält Platzhalter , die die geschweiften Klammern {...} mit einem erforderlichen Argumente Index und zwei optionalen Argumenten, Art und Stil :

{index} {index, type} {index, type, style}

The placeholder's index corresponds to the position of an element from the array of objects that we want to insert.

When present, the type and style may take the following values:

type style
number integer, currency, percent, custom format
date short, medium, long, full, custom format
time short, medium, long, full, custom format
choice custom format

The names of the types and styles largely speak for themselves, but we can consult the official documentation for more details.

Let's take a closer look, though, at custom format.

In the example above, we used the following format expression:

{2, choice, 0#no messages|1#a message|2#two messages|2<{2, number, integer} messages}

In general, the choice style has the form of options separated by the vertical bar (or pipe):

Inside the options, the match value ki and the string vi are separated by # except for the last option. Notice that we may nest other patterns into the string vi as we did it for the last option:

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

The choice type is a numeric-based one, so there is a natural ordering for the match values ki that split a numeric line into intervals:

If we give a value k that belongs to the interval [ki, ki+1) (the left end is included, the right one is excluded), then value vi is selected.

Let's consider in more details the ranges of the chosen style. To this end, we take this pattern:

pattern = "You''ve got " + "{0, choice, 0#no messages|1#a message|2#two messages|2<{0, number, integer} messages}.";

and pass various values for its unique placeholder:

n message
-1, 0, 0.5 You've got no messages.
1, 1.5 You've got a message.
2 You've got two messages.
2.5 You've got 2 messages.
5 You've got 5 messages.

4.3. Making Things Better

So, we're now formatting our messages. But, the message itself remains hardcoded.

From the previous section, we know that we should extract the strings patterns to the resources. To separate our concerns, let's create another bunch of resource files called formats:

In those, we'll create a key called label with language-specific content.

For example, in the English version, we'll put the following string:

label=On {0, date, full} {1} has sent you + {2, choice, 0#nothing|1#a message|2#two messages|2<{2,number,integer} messages}.

We should slightly modify the French version because of the zero message case:

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}.

And we'd need to do similar modifications as well in the Polish and Italian versions.

In fact, the Polish version exhibits yet another problem. According to the grammar of the Polish language (and many others), the verb has to agree in gender with the subject. We could resolve this problem by using the choice type, but let's consider another solution.

4.4. ICU's MessageFormat

Let's use the International Components for Unicode (ICU) library. We have already mentioned it in our Convert a String to Title Case tutorial. It's a mature and widely-used solution that allows us to customize the application for various languages.

Here, we're not going to explore it in full details. We'll just limit ourselves to what our toy application needs. For the most comprehensive and updated information, we should check the ICU's official site.

At the time of writing, the latest version of ICU for Java (ICU4J) is 64.2. As usual, in order to start using it, we should add it as a dependency to our project:

 com.ibm.icu icu4j 64.2 

Suppose that we want to have a properly formed notification in various languages and for different numbers of messages:

N English Polish
0 Alice has sent you no messages.

Bob has sent you no messages.

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

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

1 Alice has sent you a message.

Bob has sent you a message.

Alice wysłała ci wiadomość.

Bob wysłał ci wiadomość.

> 1 Alice has sent you N messages.

Bob has sent you N messages.

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

Bob wysłał ci N wiadomości.

Zunächst sollten wir ein Muster in den länderspezifischen Ressourcendateien erstellen.

Lassen Sie uns wieder verwenden , um die Datei formats.properties und fügen Sie ein Schlüssel Label-icu mit folgendem Inhalt:

label-icu={0} has sent you + {2, plural, =0 {no messages} =1 {a message} + other {{2, number, integer} messages}}.

Es enthält drei Platzhalter, die wir füttern, indem wir dort ein Array mit drei Elementen übergeben:

Object[] data = new Object[] { "Alice", "female", 0 }

Wir sehen, dass in der englischen Version der geschlechtsspezifische Platzhalter keinen Nutzen hat, während in der polnischen:

label-icu={0} {2, plural, =0 {nie} other {}} + {1, select, male {wysłał} female {wysłała} other {wysłało}} + ci {2, plural, =0 {żadnych wiadomości} =1 {wiadomość} + other {{2, number, integer} wiadomości}}.

Wir verwenden es, um zwischen wysłał / wysłała / wysłało zu unterscheiden .

5. Schlussfolgerung

In diesem Tutorial haben wir uns überlegt, wie Sie die Nachrichten lokalisieren und formatieren können, die wir den Benutzern unserer Anwendungen zeigen.

Wie immer befinden sich die Codefragmente für dieses Tutorial in unserem GitHub-Repository.