Javaローカリゼーション–メッセージのフォーマット

1. 前書き

このチュートリアルでは、_Locale_に基づいてメッセージを*ローカライズおよびフォーマット*する方法を検討します。
Javaの_MessageFormat_とサードパーティのライブラリ_ICU._の両方を使用します

2. ローカライズの使用例

私たちのアプリケーションが世界中から幅広いユーザーを獲得するとき、当然、ユーザーの好みに基づいて異なるメッセージを表示したい場合があります*。
最初の最も重要な側面は、ユーザーが話す言語です。 その他には、通貨、数値、日付の形式が含まれる場合があります。 最後に、文化的な好みがあります。ある国のユーザーにとって受け入れられるものは、他の国では受け入れられないかもしれません。
電子メールクライアントがあり、新しいメッセージが到着したときに通知を表示するとします。
このようなメッセージの簡単な例は次のとおりです。
Alice has sent you a message.
英語を話すユーザーにとっては問題ありませんが、英語を話さないユーザーはそれほど幸せではないかもしれません。 たとえば、フランス語を話すユーザーは次のメッセージを表示することを好みます。
Alice vous a envoyé un message.
ポーランド人はこれを見て喜んでいるでしょう:
Alice wysłała ci wiadomość.
アリスが1つのメッセージだけでなく、少数のメッセージを送信する場合でも、適切にフォーマットされた通知が必要な場合はどうでしょうか。
次のように、単一の文字列にさまざまな部分を連結することにより、問題に対処したくなるかもしれません。
String message = "Alice has sent " + quantity + " messages";
アリスだけでなくボブもメッセージを送信する可能性がある場合に通知が必要になると、状況は簡単に制御できなくなります。
Bob has sent two messages.
Bob a envoyé deux messages.
Bob wysłał dwie wiadomości.
ポーランド語(_wysłała_vs_wysłał_)言語の場合、動詞がどのように変化するかに注意してください。 これは、* banal string concatenationがメッセージのローカライズにほとんど受け入れられない*ことを示しています。
ご覧のように、2種類の問題があります。1つは翻訳に関連し、もう1つは形式に関連しています*。 次のセクションでそれらに対処しましょう。

3. メッセージのローカリゼーション

アプリケーションの*ローカリゼーション、または_l10n_は、アプリケーションをユーザーの快適さに適応させるプロセスとして定義できます*。 時々、_internalization、または_i18n_という用語も使用されます。
アプリケーションをローカライズするために、まず、すべてのハードコードされたメッセージを_resources_フォルダーに移動して削除します。
link:/uploads/messages-localization-100x51.png%20100w []
各ファイルには、対応する言語のメッセージとキーと値のペアが含まれている必要があります。 たとえば、ファイル_messages_en.properties_には次のペアが含まれている必要があります。
label=Alice has sent you a message.
_messages_pl.properties_には次のペアが含まれている必要があります。
label=Alice wysłała ci wiadomość.
同様に、他のファイルはキー_label_に適切な値を割り当てます。 ここで、英語版の通知を取得するために、_ResourceBundle_を使用できます。
ResourceBundle bundle = ResourceBundle.getBundle("messages", Locale.UK);
String message = bundle.getString("label");
変数_message_の値は_「Aliceからメッセージが送信されました。」_
Javaの_Locale_クラスには、頻繁に使用される言語と国へのショートカットが含まれています。
ポーランド語の場合、次のように記述できます。
ResourceBundle bundle
  = ResourceBundle.getBundle("messages", Locale.forLanguageTag("pl-PL"));
String message = bundle.getString("label");
ロケールを提供しない場合、システムはデフォルトのロケールを使用します。 この問題の詳細については、記事「link:/java-8-localization[Java 8の国際化とローカリゼーション]」を参照してください。 次に、使用可能な翻訳の中から、システムは現在アクティブなロケールに最も近いものを選択します。
リソースファイルにメッセージを配置することは、アプリケーションをより使いやすくするための良いステップです。 次の理由により、アプリケーション全体の翻訳が容易になります。
  1. 翻訳者は検索でアプリケーションを調べる必要はありません
    メッセージの

  2. 翻訳者はフレーズ全体を見ることができ、コンテキストを把握するのに役立ちます
    したがって、より良い翻訳を促進します

  3. 翻訳時にアプリケーション全体を再コンパイルする必要はありません
    新しい言語の準備ができている

4. メッセージフォーマット

メッセージをコードから別の場所に移動しましたが、ハードコードされた情報がまだ含まれています。 メッセージ内の名前と番号を、文法的に正しいままにするようにカスタマイズできると便利です。*
*フォーマットを、プレースホルダーをその値で置き換えることにより、文字列テンプレートをレンダリングするプロセスとして定義できます。*
次のセクションでは、メッセージのフォーマットを可能にする2つのソリューションを検討します。

4.1. Java MessageFormat

文字列をフォーマットするために、Javaはlink:/string/format[_java.lang.String_の多数のフォーマットメソッド]を定義します。 ただし、_https://docs.oracle.com/javase/8/docs/api/java/text/MessageFormat.html [java.text.format.MessageFormat] _を介してさらにサポートを得ることができます。
説明のために、パターンを作成し、_MessageFormat_インスタンスにフィードしてみましょう。
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);
パターン文字列には、3つのプレースホルダー用のスロットがあります。
各値を指定する場合:
String message = formatter.format(new Object[] {date, "Alice", 2});
次に、__MessageFormat __はテンプレートに入力し、メッセージをレンダリングします。
On 27-Apr-2019, Alice sent you two messages.

4.2. _MessageFormat_構文

上記の例から、メッセージパターンは次のようになります。
pattern = "On {...}, {..} sent you {...}.";
必須引数_index_と2つのオプション引数_type_および_style_を伴う中括弧_ \ {…} _であるプレースホルダーが含まれています。
{index}
{index, type}
{index, type, style}
プレースホルダーのインデックスは、挿入するオブジェクトの配列の要素の位置に対応しています。
存在する場合、_type_および_style_は次の値を取ることができます。
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

タイプとスタイルの名前は大部分がそれ自体を物語っていますが、詳細についてはhttps://docs.oracle.com/javase/8/docs/api/java/text/MessageFormat.html [公式ドキュメント]を参照してください。
ただし、_custom形式を詳しく見てみましょう。 _
上記の例では、次のフォーマット式を使用しました。
{2, choice, 0#no messages|1#a message|2#two messages|2<{2, number, integer} messages}
一般に、選択スタイルは、垂直バー(またはパイプ)で区切られたオプションの形式を持ちます。
link:/uploads/message-format-syntax-100x15.png%20100w []
オプション内では、最後のオプションを除き、一致値_k〜i〜_と文字列_v〜i〜_は#で区切られます。 最後のオプションで行ったように、文字列_v〜i〜_に他のパターンをネストできることに注意してください。
{2, choice, ...|2<{2, number, integer} messages}
*選択タイプは数値ベースのものです*。したがって、数値行を間隔に分割する一致値_k〜i〜_には自然な順序があります。
link:/uploads/choice-style-ordering-100x22.png%20100w []
間隔_ [k〜i〜、k〜i + 1〜)_(左端が含まれ、右端が含まれない)に属する値_k_を指定すると、値_v〜i〜_が選択されます。
選択したスタイルの範囲をより詳細に検討してみましょう。 このため、次のパターンを使用します。
pattern = "You''ve got "
  + "{0, choice, 0#no messages|1#a message|2#two messages|2<{0, number, integer} messages}.";
一意のプレースホルダーにさまざまな値を渡します。
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. 物事をより良くする

そのため、メッセージをフォーマットしています。 ただし、メッセージ自体はハードコーディングされたままです。
前のセクションから、文字列パターンをリソースに抽出する必要があることがわかりました。 懸念を分離するために、_formats_と呼ばれる別のリソースファイルを作成しましょう。
link:/uploads/messages-format-100x50.png%20100w []
これらでは、言語固有のコンテンツを持つ_label_というキーを作成します。
たとえば、英語版では、次の文字列を入力します。
label=On {0, date, full} {1} has sent you
  + {2, choice, 0#nothing|1#a message|2#two messages|2<{2,number,integer} messages}.
メッセージがゼロであるため、フランス語版を少し変更する必要があります。
label={0, date, short}, {1}{2, choice, 0# ne|0<} vous a envoyé
  + {2, choice, 0#aucun message|1#un message|2#deux messages|2<{2,number,integer} messages}.
また、ポーランド語版とイタリア語版でも同様の変更を行う必要があります。
実際、ポーランド語版にはさらに別の問題があります。 ポーランド語(および他の多くの言語)の文法によると、動詞は性別で主題に同意する必要があります。 *選択タイプを使用してこの問題を解決できましたが、別の解決策を検討しましょう。*

4.4. ICUの_MessageFormat_

_Unicodeの国際コンポーネント_(ICU)ライブラリを使用してみましょう。 link:/java-string-title-case#icu4j [文字列をタイトルケースに変換する]チュートリアルで既に言及しています。 これは、さまざまな言語に合わせてアプリケーションをカスタマイズできる成熟した広く使用されているソリューションです。
ここでは、詳細を詳しく説明しません。 おもちゃのアプリケーションに必要なものだけに制限します。 最も包括的な最新情報については、http://site.icu-project.org/ [ICUの公式サイト]を確認する必要があります。
執筆時点では、https://search.maven.org/search?q = g:com.ibm.icu%20AND%20a:icu4j [ICU for Java(_ICU4J_)]の最新バージョンは64.2です。 いつものように、使用を開始するには、プロジェクトへの依存関係として追加する必要があります。
<dependency>
    <groupId>com.ibm.icu</groupId>
    <artifactId>icu4j</artifactId>
    <version>64.2</version>
</dependency>
さまざまな言語で、さまざまな数のメッセージに対して適切に形成された通知を取得するとします。
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. + _

まず、ロケール固有のリソースファイルにパターンを作成する必要があります。
ファイル_formats.properties_を再利用して、次の内容のキー_label-icu_を追加してみましょう。
label-icu={0} has sent you
  + {2, plural, =0 {no messages} =1 {a message}
  + other {{2, number, integer} messages}}.
3要素の配列を渡すことでフィードする3つのプレースホルダーが含まれています。
Object[] data = new Object[] { "Alice", "female", 0 }
英語版では、性別を重視したプレースホルダーは役に立たないが、ポーランド語ではプレースホルダーは役に立たないことがわかります。
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}}.
_wysłał/wysłała/wysłało_を区別するために使用します。

5. 結論

このチュートリアルでは、アプリケーションのユーザーに示すメッセージをローカライズおよびフォーマットする方法を検討しました。
いつものように、このチュートリアルのコードスニペットはhttps://github.com/eugenp/tutorials/tree/master/java-strings-2[Githubリポジトリ]にあります。