Pengantarabangsaan dan Penyetempatan di Jawa 8

1. Gambaran keseluruhan

Pengantarabangsaan adalah proses menyiapkan aplikasi untuk menyokong pelbagai data khusus bahasa, wilayah, budaya atau politik. Ini adalah aspek penting bagi aplikasi pelbagai bahasa moden.

Untuk pembacaan lebih lanjut , kita harus tahu bahawa ada singkatan yang sangat popular (mungkin lebih popular daripada nama sebenarnya) untuk pengantarabangsaan - i18n kerana 18 huruf antara 'i' dan 'n'.

Sangat penting bagi program perusahaan masa kini untuk melayani orang dari pelbagai tempat di dunia atau pelbagai bidang budaya. Kawasan budaya atau bahasa yang berbeza tidak hanya menentukan perihalan khusus bahasa tetapi juga mata wang, perwakilan nombor dan komposisi tarikh dan waktu yang berbeza.

Sebagai contoh, mari kita fokus pada nombor khusus negara. Mereka mempunyai pelbagai pemisah perpuluhan dan ribu:

  • 102,300.45 (Amerika Syarikat)
  • 102 300,45 (Poland)
  • 102.300,45 (Jerman)

Terdapat juga format tarikh yang berbeza:

  • Isnin, 1 Januari 2018 3:20:34 PM CET (Amerika Syarikat)
  • lundi 1 janvier 2018 15 h 20 CET (Perancis).
  • 2018 年 1 月 1 日 星期一 下午 03 时 20 分 34 秒 CET (China)

Terlebih lagi, pelbagai negara mempunyai simbol mata wang yang unik:

  • £ 1,200.60 (United Kingdom)
  • € 1.200,60 (Itali)
  • 1 200,60 € (Perancis)
  • $ 1,200.60 (Amerika Syarikat)

Fakta penting yang perlu diketahui ialah walaupun negara mempunyai simbol mata wang dan mata wang yang sama - seperti Perancis dan Itali - kedudukan simbol mata wang mereka mungkin berbeza.

2. Penyetempatan

Di Jawa, kami mempunyai ciri hebat yang dapat dinamakan kelas Locale .

Ini membolehkan kita membezakan dengan cepat antara tempat budaya dan memformat kandungan kita dengan tepat. Ini penting bagi proses pengantarabangsaan. Sama seperti i18n, Penyetempatan juga mempunyai singkatannya - l10n .

Sebab utama untuk menggunakan Locale adalah bahawa semua format khusus lokasi yang diperlukan dapat diakses tanpa penyusunan semula. Aplikasi dapat menangani pelbagai lokasi pada masa yang sama sehingga menyokong bahasa baru adalah mudah.

Lokasi biasanya diwakili oleh bahasa, negara, dan singkatan varian yang dipisahkan oleh garis bawah:

  • de (Jerman)
  • it_CH (Itali, Switzerland)
  • en_US_UNIX (Amerika Syarikat, platform UNIX)

2.1. Padang

Kami telah mengetahui bahawa Locale terdiri daripada kod bahasa, kod negara, dan varian. Terdapat dua lagi bidang yang mungkin ditetapkan: skrip dan peluasan .

Mari lihat senarai bidang dan lihat apa peraturannya:

  • Bahasa boleh menjadi kod ISO 639 alpha-2 atau alpha-3 atau subtag bahasa berdaftar.
  • Wilayah (Negara) adalah kod negara ISO 3166 alpha-2 atau kod kawasan angka-3 PBB .
  • Variant adalah nilai sensitif huruf atau set nilai yang menentukan variasi yang Locale .
  • Skrip mestilah kod ISO 15924 alpha-4 yang sah .
  • Sambungan adalah peta yang terdiri daripada kunci aksara tunggal dan nilai String .

Registry Subtag Bahasa IANA mengandungi kemungkinan nilai untuk bahasa , wilayah , varian dan skrip .

Tidak ada senarai kemungkinan nilai peluasan , tetapi nilainya mesti subteg BCP-47 yang terbentuk dengan baik. Kunci dan nilai selalu ditukar menjadi huruf kecil.

2.2. Locale.Builder

Terdapat beberapa cara untuk membuat objek Locale . Salah satu cara yang mungkin menggunakan Locale.Builder . Locale.Builder mempunyai lima kaedah setter yang dapat kita gunakan untuk membina objek dan pada masa yang sama mengesahkan nilai-nilai tersebut:

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

The String perwakilan di atas Locale adalah fr_CA_POSIX_ # Latn .

Adalah baik untuk mengetahui bahawa menetapkan 'varian' mungkin sedikit rumit kerana tidak ada batasan rasmi pada nilai varian, walaupun kaedah setter mengharuskannya mematuhi BCP-47 .

Jika tidak, ia akan membuang IllformedLocaleException .

Sekiranya kita perlu menggunakan nilai yang tidak lulus pengesahan, kita dapat menggunakan konstruktor Lokal kerana mereka tidak mengesahkan nilai.

2.3. Pembina

Locale mempunyai tiga pembina:

  • Lokasi baru (Bahasa rentetan)
  • Lokasi baru (Bahasa rentetan, Negara tali)
  • Lokasi baru (Bahasa rentetan, String country, String varian)

Pembina 3 parameter:

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

Varian yang sah mestilah Rentetan dari 5 hingga 8 alfanumerik atau angka tunggal diikuti oleh 3 alfanumerik. Kami hanya dapat menerapkan "UNIX" pada bidang varian hanya melalui konstruktor kerana ia tidak memenuhi syarat tersebut.

Namun, ada satu kelemahan menggunakan konstruktor untuk membuat objek Lokal - kami tidak dapat menetapkan bidang peluasan dan skrip.

2.4. Constants

This is probably the simplest and the most limited way of getting Locales. The Locale class has several static constants which represent the most popular country or language:

Locale japan = Locale.JAPAN; Locale japanese = Locale.JAPANESE;

2.5. Language Tags

Another way of creating Locale is calling the static factory method forLanguageTag(String languageTag). This method requires a String that meets the IETF BCP 47 standard.

This is how we can create the UK Locale:

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

2.6. Available Locales

Even though we can create multiple combinations of Locale objects, we may not be able to use them.

An important note to be aware of is that the Locales on a platform are dependent on those that have been installed within the Java Runtime.

As we use Locales for formatting, the different formatters may have an even smaller set of Locales available that are installed in the Runtime.

Let's check how to retrieve arrays of available locales:

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

After that, we can check whether our Locale resides among available Locales.

We should remember that the set of available locales is different for various implementations of the Java Platformand various areas of functionality.

The complete list of supported locales is available on the Oracle's Java SE Development Kit webpage.

2.7. Default Locale

While working with localization, we might need to know what the default Locale on our JVM instance is. Fortunately, there's a simple way to do that:

Locale defaultLocale = Locale.getDefault();

Also, we can specify a default Locale by calling a similar setter method:

Locale.setDefault(Locale.CANADA_FRENCH);

It's especially relevant when we'd like to create JUnit tests that don't depend on a JVM instance.

3. Numbers and Currencies

This section refers to numbers and currencies formatters that should conform to different locale-specific conventions.

To format primitive number types (int, double) as well as their object equivalents (Integer, Double), we should use NumberFormat class and its static factory methods.

Two methods are interesting for us:

  • NumberFormat.getInstance(Locale locale)
  • NumberFormat.getCurrencyInstance(Locale locale)

Let's examine a sample code:

Locale usLocale = Locale.US; double number = 102300.456d; NumberFormat usNumberFormat = NumberFormat.getInstance(usLocale); assertEquals(usNumberFormat.format(number), "102,300.456");

As we can see it's as simple as creating Locale and using it to retrieve NumberFormat instance and formatting a sample number. We can notice that the output includes locale-specific decimal and thousand separators.

Here's another example:

Locale usLocale = Locale.US; BigDecimal number = new BigDecimal(102_300.456d); NumberFormat usNumberFormat = NumberFormat.getCurrencyInstance(usLocale); assertEquals(usNumberFormat.format(number), "$102,300.46");

Formatting a currency involves the same steps as formatting a number. The only difference is that the formatter appends currency symbol and round decimal part to two digits.

4. Date and Time

Now, we're going to learn about dates and times formatting which's probably more complex than formatting numbers.

First of all, we should know that date and time formatting significantly changed in Java 8 as it contains completely new Date/Time API. Therefore, we're going to look through different formatter classes.

4.1. DateTimeFormatter

Since Java 8 was introduced, the main class for localizing of dates and times is the DateTimeFormatter class. It operates on classes that implement TemporalAccessor interface, for example, LocalDateTime, LocalDate, LocalTime or ZonedDateTime. To create a DateTimeFormatter we must provide at least a pattern, and then Locale. Let's see an example code:

Locale.setDefault(Locale.US); LocalDateTime localDateTime = LocalDateTime.of(2018, 1, 1, 10, 15, 50, 500); String pattern = "dd-MMMM-yyyy HH:mm:ss.SSS"; DateTimeFormatter defaultTimeFormatter = DateTimeFormatter.ofPattern(pattern); DateTimeFormatter deTimeFormatter = DateTimeFormatter.ofPattern(pattern, Locale.GERMANY); assertEquals( "01-January-2018 10:15:50.000", defaultTimeFormatter.format(localDateTime)); assertEquals( "01-Januar-2018 10:15:50.000", deTimeFormatter.format(localDateTime));

We can see that after retrieving DateTimeFormatter all we have to do is to call the format() method.

For a better understanding, we should familiarize with possible pattern letters.

Let's look at letters for example:

Symbol Meaning Presentation Examples ------ ------- ------------ ------- y year-of-era year 2004; 04 M/L month-of-year number/text 7; 07; Jul; July; J d day-of-month number 10 H hour-of-day (0-23) number 0 m minute-of-hour number 30 s second-of-minute number 55 S fraction-of-second fraction 978

All possible pattern letters with explanation can be found in the Java documentation of DateTimeFormatter.It's worth to know that final value depends on the number of symbols. There is ‘MMMM' in the example which prints the full month name whereas a single ‘M' letter would give the month number without a leading 0.

To finish on DateTimeFormatter, let's see how we can format 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("Monday, January 1, 2018 10:15:50 AM PST", formattedLocalizedTime);

In order to format LocalizedDateTime, we can use the ofLocalizedDateTime(FormatStyle dateTimeStyle) method and provide a predefined FormatStyle.

For a more in-depth look at Java 8 Date/Time API, we have an existing article here.

4.2. DateFormat and SimpleDateFormatter

As it's still common to work on projects that make use of Dates and Calendars, we'll briefly introduce capabilities of formatting dates and times with DateFormat and SimpleDateFormat classes.

Let's analyze abilities of the first one:

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

DateFormat works with Dates and has three useful methods:

  • getDateTimeInstance
  • getDateInstance
  • getTimeInstance

All of them take predefined values of DateFormat as a parameter. Each method is overloaded, so passing Locale is possible as well. If we want to use a custom pattern, as it's done in DateTimeFormatter, we can use SimpleDateFormat. Let's see a short code snippet:

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

5. Customization

Due to some good design decisions, we're not tied to a locale-specific formatting pattern, and we can configure almost every detail to be fully satisfied with an output.

To customize number formatting, we can use DecimalFormat and DecimalFormatSymbols.

Let's consider a short example:

Locale.setDefault(Locale.FRANCE); BigDecimal number = new BigDecimal(102_300.456d); DecimalFormat zeroDecimalFormat = new DecimalFormat("000000000.0000"); DecimalFormat dollarDecimalFormat = new DecimalFormat("$###,###.##"); assertEquals(zeroDecimalFormat.format(number), "000102300,4560"); assertEquals(dollarDecimalFormat.format(number), "$102 300,46"); 

The DecimalFormat documentation shows all possible pattern characters. All we need to know now is that “000000000.000” determines leading or trailing zeros, ‘,' is a thousand separator, and ‘.' is decimal one.

It's also possible to add a currency symbol. We can see below that the same result can be achieved by using DateFormatSymbol class:

Locale.setDefault(Locale.FRANCE); BigDecimal number = new BigDecimal(102_300.456d); DecimalFormatSymbols decimalFormatSymbols = DecimalFormatSymbols.getInstance(); decimalFormatSymbols.setGroupingSeparator('^'); decimalFormatSymbols.setDecimalSeparator('@'); DecimalFormat separatorsDecimalFormat = new DecimalFormat("$###,###.##"); separatorsDecimalFormat.setGroupingSize(4); separatorsDecimalFormat.setCurrency(Currency.getInstance(Locale.JAPAN)); separatorsDecimalFormat.setDecimalFormatSymbols(decimalFormatSymbols); assertEquals(separatorsDecimalFormat.format(number), "$10^[email protected]");

As we can see, DecimalFormatSymbols class enables us to specify any number formatting we can imagine.

To customize SimpleDataFormat, we can use DateFormatSymbols.

Let's see how simple is a change of day names:

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

6. Resource Bundles

Finally, the crucial part of internationalization in the JVM is the Resource Bundle mechanism.

The purpose of a ResourceBundle is to provide an application with localized messages/descriptions which can be externalized to the separate files. We cover usage and configuration of the Resource Bundle in one of our previous articles – guide to the Resource Bundle.

7. Conclusion

Locales and the formatters that utilize them are tools that help us create an internationalized application. These tools allow us to create an application which can dynamically adapt to the user's linguistic or cultural settings without multiple builds or even needing to worry about whether Java supports the Locale.

Di dunia di mana pengguna boleh berada di mana saja dan dapat menuturkan bahasa apa pun, kemampuan untuk menerapkan perubahan ini bermaksud aplikasi kita dapat lebih intuitif dan difahami oleh lebih banyak pengguna di seluruh dunia.

Semasa bekerja dengan aplikasi Spring Boot, kami juga mempunyai artikel yang mudah untuk Spring Boot Internationalization.

Kod sumber tutorial ini, dengan contoh lengkap, boleh didapati di GitHub.