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 .