Рубрики
Без рубрики

Интернационализация и локализация в Java 8

Взгляните на поддержку Java 8 для интернационализации и локализации приложения.

Автор оригинала: baeldung.

1. Обзор

Интернационализация-это процесс подготовки заявки для поддержки различных лингвистических, региональных, культурных или политических данных. Это важный аспект любого современного многоязычного приложения.

Для дальнейшего чтения , мы должны знать, что существует очень популярная аббревиатура (вероятно, более популярная, чем фактическое название) для интернационализации – i18n из-за 18 букв между ” i ” и “n”.

Для современных корпоративных программ крайне важно обслуживать людей из разных уголков мира или из разных культурных областей. Различные культурные или языковые регионы определяют не только языковые описания, но и валюту, представление чисел и даже различающийся состав дат и времени.

Например, давайте сосредоточимся на цифрах по конкретным странам. У них есть различные десятичные и тысячные разделители:

  • 102 300,45 (США)
  • 102 300,45 (Польша)
  • 102.300,45 (Германия)

Существуют также различные форматы дат:

  • Понедельник, 1 января 2018 3:20:34 вечера по Центральноевропейскому времени (США)
  • понедельник, 1 января 2018 года в 15: 20 в ЭТОМ (Франция).
  • Понедельник, 1 января 2018, 03: 20: 34 CET (Китай)

Более того, в разных странах есть уникальные символы валют:

  • £1,200.60 (Великобритания)
  • € 1.200,60 (Италия)
  • 1 200,60 € (Франция)
  • $1200,60 (США)

Важно знать, что даже если страны имеют одну и ту же валюту и символ валюты – например, Франция и Италия, – положение их символа валюты может отличаться.

2. Локализация

В Java в нашем распоряжении есть фантастическая функция под названием Место действия класс.

Это позволяет нам быстро различать культурные локали и соответствующим образом форматировать наш контент. Это необходимо для процесса интернационализации. Так же, как и i18n, локализация также имеет свою аббревиатуру – l10n .

Основная причина использования Locale заключается в том, что все необходимое форматирование, зависящее от локали, может быть доступно без перекомпиляции. Приложение может обрабатывать несколько языков одновременно, поэтому поддержка нового языка проста.

Локали обычно представлены аббревиатурой языка, страны и варианта, разделенной подчеркиванием:

  • de (немецкий)
  • it_CH (итальянский, Швейцария)
  • en_US_UNIX (США, платформа UNIX)

2.1. Поля

Мы уже узнали, что Locale состоит из кода языка, кода страны и варианта. Есть еще два возможных поля для установки: скрипт и расширения .

Давайте посмотрим на список полей и посмотрим, каковы правила:

  • Язык может быть ISO 639 alpha-2 или alpha-3 кодом или подтегом зарегистрированного языка.
  • Регион (страна) – это ISO 3166 alpha-2 код страны или UN numeric-3 код региона.
  • Вариант -это значение с учетом регистра или набор значений, определяющих изменение Языкового стандарта .
  • Скрипт должен быть действительным ISO 15924 alpha-4 кодом.
  • Расширения – это карта, состоящая из односимвольных ключей и строковых значений.

Реестр подтегов языка IANA содержит возможные значения для языка , региона , варианта и сценария .

Список возможных значений extension отсутствует, но значения должны быть хорошо сформированы BCP-47 подтегами. Ключи и значения всегда преобразуются в нижний регистр.

2.2. Локаль.Строитель

Существует несколько способов создания объектов Locale . Один из возможных способов использует Locale.Builder . Locale.Builder имеет пять методов настройки, которые мы можем использовать для построения объекта и в то же время проверки этих значений:

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

String представление вышеупомянутого Locale является fr_CA_POSIX_#Latn .

Полезно знать, что установка “variant” может быть немного сложной, поскольку нет официального ограничения на значения вариантов, хотя метод setter требует, чтобы он был BCP-47 совместимым .

В противном случае он выбросит IllformedLocaleException .

В случае, когда нам нужно использовать значение, которое не проходит проверку, мы можем использовать конструкторы Locale , поскольку они не проверяют значения.

2.3. Конструкторы

Locale имеет три конструктора:

  • новая локаль(строковый язык)
  • новая локаль(язык строки, страна строки)
  • новая локаль(язык строки, страна строки, вариант строки)

Конструктор с 3 параметрами:

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

Допустимый вариант должен быть Строкой от 5 до 8 буквенно-цифровых символов или одной цифрой, за которой следуют 3 буквенно-цифровых символа. Мы можем применить “UNIX” к полю variant только через конструктор, поскольку он не соответствует этим требованиям.

Однако есть один недостаток использования конструкторов для создания объектов Locale – мы не можем устанавливать расширения и поля сценариев.

2.4. Константы

Это, вероятно, самый простой и самый ограниченный способ получения Locales . Класс Locale имеет несколько статических констант, которые представляют наиболее популярную страну или язык:

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

2.5. Языковые теги

Другим способом создания Locale является вызов статического заводского метода forLanguageTag(String languageTag) . Для этого метода требуется строка |, соответствующая стандарту IETF BCP 47 .

Вот как мы можем создать Великобританию Locale :

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

2.6. Доступные Локали

Даже если мы можем создать несколько комбинаций Место действия объекты, мы, возможно, не сможем их использовать.

Важно отметить, что Локали на платформе зависят от тех, которые были установлены в среде выполнения Java.

Поскольку мы используем Locales для форматирования, различные форматтеры могут иметь еще меньший набор доступных Locales , установленных во время выполнения.

Давайте проверим, как получить массивы доступных локалей:

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

После этого мы можем проверить, находится ли наш Locale среди доступных Locales.

Мы должны помнить, что набор доступных локалей различен для различных реализаций платформы Java | и различных областей функциональности .

Полный список поддерживаемых языков доступен на веб-странице Oracle Java SE Development Kit.

2.7. Локаль по умолчанию

При работе с локализацией нам, возможно, потребуется знать, что по умолчанию Locale в нашем экземпляре JVM . К счастью, есть простой способ сделать это:

Locale defaultLocale = Locale.getDefault();

Кроме того, мы можем указать Locale по умолчанию, вызвав аналогичный метод setter:

Locale.setDefault(Locale.CANADA_FRENCH);

Это особенно актуально, когда мы хотим создать JUnit тесты, которые не зависят от экземпляра JVM .

3. Цифры и валюты

Этот раздел относится к числам и валютам для вопросов, которые должны соответствовать различным соглашениям, касающимся конкретных языков.

Для форматирования примитивных типов чисел ( int , double ), а также их объектных эквивалентов ( Integer , Double ) мы должны использовать класс NumberFormat и его статические заводские методы.

Для нас интересны два метода:

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

Давайте рассмотрим пример кода:

Locale usLocale = Locale.US;
double number = 102300.456d;
NumberFormat usNumberFormat = NumberFormat.getInstance(usLocale);

assertEquals(usNumberFormat.format(number), "102,300.456");

Как мы видим, это так же просто, как создать Locale и использовать его для извлечения NumberFormat экземпляра и форматирования номера образца. Мы можем заметить, что вывод включает в себя специфичные для локали десятичные и тысячные разделители .

Вот еще один пример:

Locale usLocale = Locale.US;
BigDecimal number = new BigDecimal(102_300.456d);

NumberFormat usNumberFormat = NumberFormat.getCurrencyInstance(usLocale); 
assertEquals(usNumberFormat.format(number), "$102,300.46");

Форматирование валюты включает в себя те же шаги, что и форматирование числа. Единственное отличие заключается в том, что форматер добавляет символ валюты и круглую десятичную часть к двум цифрам.

4. Дата и время

Теперь мы узнаем о форматировании дат и времени, которое, вероятно, сложнее, чем форматирование чисел.

Прежде всего, мы должны знать, что форматирование даты и времени значительно изменилось в Java 8, поскольку оно содержит совершенно новый Date/Time API. Поэтому мы рассмотрим различные классы форматирования.

4.1. DateTimeFormatter

С момента появления Java 8 основным классом для локализации дат и времени является DateTimeFormatter класс . Он работает с классами, реализующими интерфейс TemporalAccessor , например, LocalDateTime , LocalDate, LocalTime или |/ZonedDateTime. Для создания DateTimeFormatter мы должны предоставить по крайней мере шаблон, а затем Locale. Давайте посмотрим пример кода:

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));

Мы видим, что после получения DateTimeFormatter все, что нам нужно сделать, это вызвать метод format () .

Для лучшего понимания мы должны ознакомиться с возможными шаблонными буквами.

Давайте рассмотрим, например, буквы:

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

Все возможные буквы шаблона с объяснением можно найти в документации Java DateTimeFormatter . Стоит знать, что конечное значение зависит от количества символов . В примере есть “ММММ”, который печатает полное название месяца, в то время как одна буква “М” даст номер месяца без ведущего 0.

Чтобы закончить с DateTimeFormatter , давайте посмотрим, как мы можем отформатировать 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);

Для форматирования LocalizedDateTime мы можем использовать метод ofLocalizedDateTime(стиль формата datetimestyles) и предоставить предварительно определенный FormatStyle.

Для более глубокого изучения Java 8 Дата/Время API, у нас есть существующая статья здесь .

4.2. DateFormat и Simpledateformat

Поскольку по-прежнему принято работать над проектами , использующими Даты и Календари , мы кратко представим возможности форматирования дат и времени с помощью классов DateFormat и SimpleDateFormat .

Давайте проанализируем способности первого:

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));

Формат даты работает с Датами и имеет три полезных метода:

  • getDateTimeInstance
  • getDateInstance
  • getTimeInstance

Все они принимают в качестве параметра предопределенные значения DateFormat . Каждый метод перегружен, поэтому передача Locale также возможна. Если мы хотим использовать пользовательский шаблон, как это сделано в DateTimeFormatter , мы можем использовать SimpleDateFormat . Давайте посмотрим короткий фрагмент кода:

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. Настройка

Благодаря некоторым хорошим дизайнерским решениям мы не привязаны к шаблону форматирования, зависящему от локали, и можем настроить почти каждую деталь, чтобы полностью удовлетворить вывод.

Чтобы настроить форматирование чисел, мы можем использовать Десятичный Формат и Десятичные форматосимволы .

Давайте рассмотрим короткий пример:

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");

Десятичный формат документация показывает все возможные символы шаблона. Все, что нам сейчас нужно знать, это то, что “000000000.000” определяет начальные или конечные нули,”, “- это разделитель тысяч, а”. ” – десятичная единица.

Также можно добавить символ валюты. Ниже мы видим, что тот же результат может быть достигнут с помощью Dateformatsymbols 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]");

Как мы видим, класс DecimalFormatSymbols позволяет нам указать любое форматирование чисел, которое мы можем себе представить.

Чтобы настроить Простой формат данных, мы можем использовать DateFormatSymbols .

Давайте посмотрим, насколько просто изменить названия дней:

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. Пакеты ресурсов

Наконец, решающей частью интернационализации в JVM является механизм Набора ресурсов .

Цель ResourceBundle состоит в том, чтобы предоставить приложению локализованные сообщения/описания, которые могут быть перенесены в отдельные файлы. Мы рассмотрим использование и настройку пакета ресурсов в одной из наших предыдущих статей – руководство по пакету ресурсов .

7. Заключение

Локали и форматы, которые их используют, – это инструменты, которые помогают нам создавать интернационализированное приложение. Эти инструменты позволяют нам создавать приложение, которое может динамически адаптироваться к языковым или культурным настройкам пользователя без нескольких сборок или даже необходимости беспокоиться о том, поддерживает ли Java Locale .

В мире, где пользователь может находиться где угодно и говорить на любом языке, возможность применения этих изменений означает, что наши приложения могут быть более интуитивно понятными и понятными большему числу пользователей по всему миру.

При работе с приложениями Spring Boot у нас также есть удобная статья для интернационализации Spring Boot .

Исходный код этого руководства с полными примерами можно найти на GitHub .