Автор оригинала: Andrew Shcherbakov.
Локализация Java – Форматирование Сообщений
Локализация Java – Форматирование Сообщений
Локализация Java – Форматирование Сообщений
Локализация Java – Форматирование Сообщений
Когда наше приложение приобретает широкую аудиторию пользователей со всего мира, мы, естественно, можем захотеть показывать различные сообщения в зависимости от предпочтений пользователя .
Первый и самый важный аспект-это язык, на котором говорит пользователь. Другие могут включать форматы валюты, номера и даты. И последнее, но не менее важное-культурные предпочтения: то, что приемлемо для пользователей из одной страны, может быть неприемлемым для других.
Предположим, что у нас есть почтовый клиент, и мы хотим показывать уведомления при поступлении нового сообщения.
Простым примером такого сообщения может быть следующее:
Alice has sent you a message.
Это хорошо для англоговорящих пользователей, но не говорящие по-английски могут быть не так счастливы. Например, франкоговорящие пользователи предпочли бы видеть это сообщение:
Alice vous a envoyé un message.
В то время как польские люди были бы рады увидеть это:
Alice wysłała ci wiadomość.
Что делать, если мы хотим иметь правильно отформатированное уведомление даже в том случае, когда Алиса отправляет не одно сообщение, а несколько сообщений?
У нас может возникнуть искушение решить эту проблему, объединив различные фрагменты в одну строку, как это:
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ł ) языка. Это иллюстрирует тот факт, что банальная конкатенация строк редко приемлема для локализации сообщений .
Как мы видим, мы получаем два типа проблем: одна связана с переводами, а другая-с форматами . Давайте рассмотрим их в следующих разделах.
3. Локализация сообщений
Мы можем определить локализацию или l10n приложения как процесс адаптации приложения к удобству пользователя . Иногда также используется термин интернализация, или i18n .
Чтобы локализовать приложение, прежде всего, давайте удалим все жестко закодированные сообщения, переместив их в папку resources :
Каждый файл должен содержать пары ключ-значение с сообщениями на соответствующем языке. Например, файл 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 будет “Алиса отправила вам сообщение.”
Класс Locale Java содержит ярлыки для часто используемых языков и стран.
В случае польского языка мы могли бы написать следующее:
ResourceBundle bundle = ResourceBundle.getBundle("messages", Locale.forLanguageTag("pl-PL")); String message = bundle.getString("label");
Давайте просто упомянем, что если мы не предоставим языковой стандарт, то система будет использовать его по умолчанию. Мы можем более подробно рассмотреть этот вопрос в нашей статье “Интернационализация и локализация в Java 8”. Затем среди доступных переводов система выберет тот, который наиболее похож на текущую активную локаль.
Размещение сообщений в файлах ресурсов – хороший шаг к тому,чтобы сделать приложение более удобным для пользователя. Это облегчает перевод всего приложения по следующим причинам:
- переводчику не нужно просматривать приложение в поисках сообщений
- переводчик может видеть всю фразу целиком, что помогает понять контекст и, следовательно, облегчает лучший перевод
- нам не нужно перекомпилировать все приложение, когда будет готов перевод на новый язык
4. Формат сообщения
Несмотря на то, что мы переместили сообщения из кода в отдельное место, они все еще содержат некоторую жестко закодированную информацию. Было бы неплохо иметь возможность настраивать имена и номера в сообщениях таким образом, чтобы они оставались грамматически правильными.
Мы можем определить форматирование как процесс визуализации шаблона строки путем замены заполнителей их значениями.
В следующих разделах мы рассмотрим два решения, которые позволяют нам форматировать сообщения.
4.1. Формат сообщений Java
Для форматирования строк Java определяет многочисленные методы форматирования в java.lang.Строка . Но мы можем получить еще большую поддержку через java.text.format.Message Format .
Чтобы проиллюстрировать это, давайте создадим шаблон и передадим его в Формат сообщения экземпляр:
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);
Строка шаблона имеет слоты для трех заполнителей.
Если мы предоставим каждое значение:
String message = formatter.format(new Object[] {date, "Alice", 2});
Формат сообщения заполнит шаблон и отобразит ваше сообщение:
On 27-Apr-2019, Alice sent you two messages.
4.2. Синтаксис Формата сообщения
Из приведенного выше примера мы видим, что шаблон сообщения:
pattern = "On {...}, {..} sent you {...}.";
содержит заполнители, которые представляют собой фигурные скобки {…} с обязательным аргументом index и двумя необязательными аргументами/| type и style :
{index} {index, type} {index, type, style}
Индекс заполнителей соответствует положению элемента из массива объектов, которые мы хотим вставить.
При наличии тип и стиль могут принимать следующие значения:
номер | целое число, валюта, процент, пользовательский формат |
дата | короткий, средний, длинный, полный, пользовательский формат |
время | короткий, средний, длинный, полный, пользовательский формат |
выбор | пользовательский формат |
Названия типов и стилей в значительной степени говорят сами за себя, но мы можем обратиться к официальной документации для получения более подробной информации.
Однако давайте подробнее рассмотрим пользовательский формат .
В приведенном выше примере мы использовали следующее выражение формата:
{2, choice, 0#no messages|1#a message|2#two messages|2<{2, number, integer} messages}
В общем случае стиль выбора имеет вид опций, разделенных вертикальной полосой (или трубой):
Внутри параметров значение соответствия k i и строка v i разделяются символом#, за исключением последнего параметра. Обратите внимание, что мы можем вложить другие шаблоны в строку v i , как мы сделали это для последнего варианта:
{2, choice, ...|2<{2, number, integer} messages}
Тип выбора-числовой , поэтому существует естественный порядок значений совпадения k i , который разбивает числовую строку на интервалы:
Если мы зададим значение k , принадлежащее интервалу [k i , k i+1 ) (левый конец включен, правый исключен), то будет выбрано значение v i .
Рассмотрим более подробно диапазоны выбранного стиля. С этой целью мы берем этот шаблон:
pattern = "You''ve got " + "{0, choice, 0#no messages|1#a message|2#two messages|2<{0, number, integer} messages}.";
и передать различные значения для его уникального заполнителя:
-1, 0, 0.5 | У тебя нет никаких сообщений. |
1, 1.5 | У тебя есть сообщение. |
2 | У вас два сообщения. |
2.5 | У вас есть 2 сообщения. |
5 | У вас есть 5 сообщений. |
4.3. Улучшение Ситуации
Итак, теперь мы форматируем наши сообщения. Но само сообщение остается жестко закодированным.
Из предыдущего раздела мы знаем, что мы должны извлечь шаблоны строк в ресурсы. Чтобы разделить наши проблемы, давайте создадим еще одну группу файлов ресурсов под названием форматы :
В них мы создадим ключ под названием 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
Давайте использовать Международные компоненты для библиотеки Unicode (ICU). Мы уже упоминали об этом в нашем учебнике Преобразование строки в регистр заголовка . Это зрелое и широко используемое решение, которое позволяет нам настраивать приложение для различных языков.
Здесь мы не собираемся исследовать его во всех подробностях. Мы просто ограничимся тем, что нужно нашему игрушечному приложению. Для получения наиболее полной и обновленной информации мы должны проверить официальный сайт ICU .
На момент написания статьи последняя версия ICU для Java ( ICU4J ) составляет 64.2. Как обычно, чтобы начать использовать его, мы должны добавить его в качестве зависимости в наш проект:
com.ibm.icu icu4j 64.2
Предположим, что мы хотим иметь правильно сформированное уведомление на разных языках и для разного количества сообщений:
0 | Элис не посылала тебе никаких сообщений. Боб не прислал тебе никаких сообщений. | Элис не посылала тебе никаких сообщений. Боб не посылал тебе никаких сообщений. |
1 | Элис прислала тебе сообщение. Боб прислал тебе сообщение. | Элис прислала тебе сообщение. Боб прислал тебе сообщение. |
> 1 | Элис отправила тебе N сообщений. Боб отправил тебе N сообщений. | Алиса прислала вам N сообщений. Боб прислал вам N сообщений. |
Прежде всего, мы должны создать шаблон в файлах ресурсов, зависящих от локали.
Давайте повторно используем файл formats.properties и добавим туда ключ label-icon со следующим содержимым:
label-icu={0} has sent you + {2, plural, =0 {no messages} =1 {a message} + other {{2, number, integer} messages}}.
Он содержит три заполнителя, которые мы подаем, передавая туда трехэлементный массив:
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}}.
we use it in order to distinguish between отправил/отправила/отправил .
5. Conclusion
В этом уроке мы рассмотрели, как локализовать и отформатировать сообщения, которые мы демонстрируем пользователям наших приложений.
Как всегда, фрагменты кода для этого урока находятся в нашем репозитории GitHub .