Penyetempatan Java - Memformat Mesej

1. Pengenalan

Dalam tutorial ini, kita akan mempertimbangkan bagaimana kita dapat melokalkan dan memformat mesej berdasarkan Lokal .

Kami akan menggunakan JavaFormat Pesan dan perpustakaan pihak ketiga, ICU.

2. Kes Penggunaan Penyetempatan

Apabila aplikasi kami memperoleh khalayak pengguna yang luas dari seluruh dunia, kami semestinya ingin menunjukkan mesej yang berbeza berdasarkan pilihan pengguna .

Aspek pertama dan paling penting adalah bahasa yang dituturkan oleh pengguna. Yang lain mungkin merangkumi format mata wang, nombor dan tarikh. Yang terakhir adalah pilihan budaya: apa yang boleh diterima oleh pengguna dari satu negara mungkin tidak dapat ditoleransi oleh negara lain.

Anggaplah kita mempunyai pelanggan e-mel dan kita ingin menunjukkan pemberitahuan apabila mesej baru tiba.

Contoh ringkas mesej seperti ini:

Alice has sent you a message.

Tidak apa-apa untuk pengguna berbahasa Inggeris, tetapi yang bukan berbahasa Inggeris mungkin tidak begitu gembira. Sebagai contoh, pengguna berbahasa Perancis lebih suka melihat mesej ini:

Alice vous a envoyé un message. 

Walaupun orang Poland senang melihatnya:

Alice wysłała ci wiadomość. 

Bagaimana jika kita ingin mendapat pemberitahuan yang diformat dengan baik bahkan ketika Alice mengirim bukan hanya satu pesan, tetapi beberapa pesan?

Kami mungkin tergoda untuk mengatasi masalah ini dengan menggabungkan pelbagai kepingan dalam satu rentetan, seperti ini:

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

Keadaan dapat dengan mudah terkawal apabila kita memerlukan pemberitahuan sekiranya bukan hanya Alice tetapi juga Bob yang dapat menghantar mesej:

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

Notis, bagaimana perubahan kata kerja dalam hal Poland ( wysłała vs wysłał ) bahasa. Ini menggambarkan kenyataan bahawa penggabungan rentetan dangkal jarang diterima untuk melokalkan pesanan .

Seperti yang kita lihat, kita mendapat dua jenis masalah: satu berkaitan dengan terjemahan dan yang lain berkaitan dengan format . Mari kita hadapi di bahagian berikut

3. Penyetempatan Mesej

Kami mungkin menentukan penyetempatan, atau l10n , aplikasi sebagai proses penyesuaian aplikasi untuk keselesaan pengguna . Kadang kala, istilah internalisasi, atau i18n , juga digunakan.

Untuk melokalisasikan aplikasi, pertama sekali, mari kita hapuskan semua mesej dengan kod keras dengan memindahkannya ke folder sumber kami :

Setiap fail harus mengandungi pasangan nilai-kunci dengan pesan dalam bahasa yang sesuai. Sebagai contoh, fail message_en.properties harus mengandungi pasangan berikut:

label=Alice has sent you a message.

messages_pl.properties harus mengandungi pasangan berikut:

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

Begitu juga, fail lain memberikan nilai yang sesuai pada label kunci . Sekarang, untuk mengambil pemberitahuan versi bahasa Inggeris, kami dapat menggunakan ResourceBundle :

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

Nilai mesej berubah adalah "Alice telah menghantar mesej kepada anda."

Kelas Locale Java mengandungi jalan pintas ke bahasa dan negara yang sering digunakan.

Untuk bahasa Poland, kami mungkin menulis perkara berikut:

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

Mari kita sebutkan bahawa jika kita tidak memberikan lokal, sistem akan menggunakan yang lalai. Kami dapat memperincikan lebih banyak masalah ini dalam artikel kami "Pengantarabangsaan dan Pelokalan di Java 8". Kemudian, di antara terjemahan yang tersedia, sistem akan memilih yang paling serupa dengan tempat yang aktif sekarang.

Menempatkan mesej dalam fail sumber adalah langkah yang baik untuk menjadikan aplikasi lebih mesra pengguna. Ini menjadikannya lebih mudah untuk menerjemahkan keseluruhan aplikasi dengan alasan berikut:

  1. penterjemah tidak perlu melihat aplikasi untuk mencari mesej
  2. seorang penterjemah dapat melihat keseluruhan frasa yang membantu memahami konteks dan dengan itu memudahkan terjemahan yang lebih baik
  3. kita tidak perlu menyusun semula keseluruhan aplikasi apabila terjemahan untuk bahasa baru sudah siap

4. Format Mesej

Walaupun kami telah memindahkan mesej dari kod ke lokasi yang terpisah, pesan tersebut masih mengandungi beberapa maklumat yang dikodekan. Alangkah senangnya dapat menyesuaikan nama dan nombor dalam mesej sedemikian rupa sehingga mereka tetap betul dari segi tatabahasa.

Kami mungkin mendefinisikan pemformatan sebagai proses rendering templat rentetan dengan mengganti placeholder dengan nilai mereka.

Pada bahagian berikut, kami akan mempertimbangkan dua penyelesaian yang membolehkan kami memformat mesej.

4.1. FormatFormat Java

Untuk memformat rentetan, Java menentukan banyak kaedah format dalam java.lang.String . Tetapi, kita dapat lebih banyak sokongan melalui java.text.format.MessageFormat .

Untuk memberi gambaran, mari buat corak dan berikannya kepada contoh 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); 

Rentetan corak mempunyai slot untuk tiga tempat letak.

Sekiranya kami memberikan setiap nilai:

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

Kemudian MessageFormat akan mengisi templat dan memberikan mesej kami:

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

4.2. Sintaks MesejFormat

Dari contoh di atas, kita melihat bahawa corak mesej:

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

mengandungi tempat letak yang merupakan tanda kurung keriting {…} dengan indeks argumen yang diperlukan dan dua argumen pilihan, jenis dan gaya :

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

Pertama sekali, kita harus membuat corak dalam fail sumber khusus tempat.

Mari kita gunakan semula format file.properties dan tambahkan label-icu kunci dengan kandungan berikut:

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

Ini mengandungi tiga tempat letak yang kami makan dengan menyebarkan array tiga elemen:

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

Kami melihat bahawa dalam versi bahasa Inggeris, placeholder bernilai jantina tidak ada gunanya, sementara dalam bahasa Poland:

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

kami menggunakannya untuk membezakan antara wysłał / wysłała / wysłało .

5. Kesimpulan

Dalam tutorial ini, kami mempertimbangkan cara melokalkan dan memformat pesan yang kami tunjukkan kepada pengguna aplikasi kami.

Seperti biasa, coretan kod untuk tutorial ini terdapat di repositori GitHub kami.