Как сделать локализацию в java

Обновлено: 02.07.2024

Класс Locale предназначен для отображения определенного региона. Под регионом принято понимать не только географическое положение, но также языковую и культурную среду. Например, помимо того, что указывается страна Швейцария, можно указать также и язык - французский или немецкий.

Определено два варианта конструкторов в классе Locale :

Первые два параметра в обоих конструкторах определяют язык и страну, для которой определяется локаль, согласно кодировке ISO. Список поддерживаемых стран и языков можно получить и с помощью вызова статических методов Locale.getISOLanguages() Locale.getISOCountries() , соответственно. Во втором варианте конструктора указан также строковый параметр variant , в котором кодируется информация о платформе. Если здесь необходимо указать дополнительные параметры, то их требуется разделить символом подчеркивания, причем, более важный параметр должен следовать первым.

Статический метод getDefault() возвращает текущую локаль, сконструированную на основе настроек операционной системы, под управлением которой функционирует JVM.

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

Класс ResourceBundle

Абстрактный класс ResourceBundle предназначен для хранения объектов, специфичных для локали. Например, когда необходимо получить набор строк, зависящих от локали, используют ResourceBundle .

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

Набор ресурсов - это фактически набор классов, имеющих одно базовое имя. Далее наименование класса дополняется наименованием локали, с которой связывается этот класс. Например, если имя базового класса будет MyResources , то для английской локали имя класса будет MyResources_en , для русской - MyResources_ru . Помимо этого, может добавляться идентификатор языка, если для данного региона определено несколько языков. Например, MyResources_de_CH - так будет выглядеть швейцарский вариант немецкого языка. Кроме того, можно указать дополнительный признак variant (см. описание Locale ). Так, описанный раннее пример для платформы UNIX будет выглядеть следующим образом: MyResources_de_CH_UNIX .

Загрузка объекта для нужной локали производится с помощью статического метода getBundle .:

На основе указанного базового имени (первый параметр), указанной локали (второй параметр) и локали по умолчанию (задается настройками ОС или JVM) генерируется список возможных имен ресурса. Причем, указанная локаль имеет более высокий приоритет, чем локаль по умолчанию. Если обозначить составляющие указанной локали (язык, страна, вариант) как 1, а локали по умолчанию - 2, то список примет следующий вид:

Например, если необходимо найти ResourceBundle для локали fr_CH (Швейцарский французский), а локаль по умолчанию en_US , при этом название базового класса ResourceBundle MyResources , то порядок поиска подходящего ResourceBundle будет таков.

Результатом работы getBundle будет загрузка необходимого класса ресурсов в память, однако данные этого класса могут быть сохранены на диске. Таким образом, если нужный класс не будет найден, то к требуемому имени класса будет добавлено расширение ".properties" и будет предпринята попытка найти файл с данными на диске.

Следует помнить, что необходимо указывать полностью квалифицированное имя класса ресурсов, т.е. имя пакета, имя класса. Кроме того, класс ресурсов должен быть доступен в контексте его вызова (там, где вызывается getResourceBundle ), то есть не быть private и т.д.

Всегда должен создаваться базовый класс без суффиксов, т.е. если вы создаете ресурсы с именем MyResource , должен быть в наличии класс MyResource.class .

ResourceBundle хранит объекты в виде пар ключ/значение. Как уже отмечалось ранее, класс ResourceBundle абстрактный, поэтому при его наследовании необходимо переопределить методы:

Первый метод должен возвращать список всех ключей, которые определены в ResourceBundle , второй должен возвращать объект, связанный с конкретным ключом.

Рассмотрим пример использования ResourceBundle :

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

Классы ListResourceBundle и PropertiesResourceBundle

У класса ResourceBundle определено два прямых потомка ListResourceBundle и PropertiesResourceBundle . PropertiesResourceBundle хранит набор ресурсов в файле, который представляет собой набор строк.

Алгоритм конструирования объекта, содержащего набор ресурсов, был описан в предыдущем параграфе. Во всех случаях, когда в качестве последнего элемента используется .properties , например, baseclass + "_" + language1 + "_" + country1 + ".properties" , речь идет о создании ResourceBundle из файла с наименованием baseclass + "_" + language1 + "_" + country1 и расширением properties . Обычно класс ResourceBundle помещают в пакет resources , а файл свойств - в каталог resources . Тогда для того, чтобы инстанциировать нужный класс, необходимо указать полный путь к этому классу (файлу):

ListResourceBundle хранит набор ресурсов в виде коллекции и является абстрактным классом. Классы, которые наследуют ListResourceBundle , должны обеспечить:

Подскажите, пожалуйста, как сделать на примере интернационализацию текста (локализация на трех языках: en, ru, kz). Если можно небольшой код примера для программы, хотя бы для одного языка. Результат должен иметь выбор на трех языках.

Далее необходимо создать файлы для каждой локали: например data_ru_RU.properties и data_en_US.properties . Файл без указания локали, т.е. data.properties будет файлом по умолчанию, если система не найдет нужную локаль - будет использоваться именно он. По-моему можно даже создавать в таком виде data_ru.properties и data_en.properties . В эти файлы соответственно нужно записать все ресурсы, в данном случае:

Кроме того все файлы я рекомендую "пропустить" через утилиту native2ascii . Например вот так:

Главная страница

По умолчанию язык определяется по заголовку Accept-Language запроса. Но пользователь может изменить язык с интерфейса, и тогда он получает страницы на выбранном языке, невзирая на Accept-Language.

Для смены локали предусмотрены ссылки (они есть на картинке выше):

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

Потому что локаль (Locale) сохраняется в куки или в сессии (ниже покажу разницу).

Прежде всего, давайте поместим в файл сами локализованные значения.

messages_.properties — тут хранятся локализованные значения

Для каждой поддерживаемой локали создадим свой файл messages_код-локали.properties в папке ресурсов. Мы создали три файла:

messages_fr.properties содержит значения на французском:

Через знак равенства перечислены код (который будет в Thymeleaf-шаблоне) и значение (которое будет на итоговой html-странице).

messages_ru.properties — на русском.

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

Для начала мы не будем создавать никакой конфигурации, посмотрим, как Spring Boot определяет локаль по умолчанию.

Подход по умолчанию — анализ Accept-Language

AcceptHeaderLocaleResolver

А делает он это, как уже было сказано, анализируя заголовок Accept-Language запроса — с помощью класса AcceptHeaderLocaleResolver.

Этот класс — одна из реализаций интерфейса LocaleResolver — распознавателя локали. Используется по умолчанию, если мы не задали в конфигурации другого LocaleResolver.

Заголовок Accept-Language браузер добавляет самостоятельно, в зависимости от вашей реальной локали. Но можно имитировать его в Postman.

Попробуем в Postman имитировать французскую локаль:

Запрос в PostMan с имитацией Accept-Language

Запрос в PostMan с имитацией Accept-Language

Как видите, выдается страница на французском.

Возможность переключить язык из интерфейса

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

Для этого надо настроить конфигурацию с бинами LocaleChangeInterceptor и LocaleResolver: а именно SessionLocaleResolver либо CookieLocaleResolver.

SessionLocaleResolver

Если сессии поддерживаются, то этот бин подойдет. В таком случае вся конфигурация выглядит так:

LocaleChangeInterceptor позволяет перехватить тот самый параметр ссылки:

А SessionLocaleResolver позволяет хранить локаль в сессии. То есть в нашем приложении при переключении языка выдается куки JSESSEIONID, и при любых дальнейших запросах:

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

CookieLocaleResolver

Этот бин подойдет, если в приложении сессии не поддерживаются. Чтобы его использовать, в конфигурации надо заменить LocaleResolver:

Тут мы просто выдаем другой куки (не JSESSIONID, а см. на картинке длинное название org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=fr):


Время жизни этого куки мы тоже задаем в бине выше.

Вывод локализованных значений из ресурсов в шаблон Thymeleaf

Наконец, обратите внимание на синтаксис шаблона Thymeleaf для вывода значений из ресурсов:

Итоги

Мы рассмотрели подход и реализацию интернационализации сайта. Есть еще антипаттерн передачи языка в Path Variable:

Мы будем использовать как Java MessageFormat, так и стороннюю библиотеку ICU.

2. Вариант использования локализации

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

А полякам было бы приятно увидеть это:

У нас может возникнуть соблазн решить эту проблему, объединив различные части в одну строку, например:

Как видим, возникают проблемы двух типов: одни связаны с переводами, а другие - с форматами . Давайте рассмотрим их в следующих разделах.

Мы можем определить локализацию или l10n приложения как процесс адаптации приложения к удобству пользователя . Иногда также используется термин интернализация, или i18n .


messages_pl.properties должен содержать следующую пару:

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

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

В случае с польским языком мы могли бы написать следующее:

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

In order to format strings, Java defines numerous format methods in java.lang.String. But, we can get even more support via java.text.format.MessageFormat.

To illustrate, let's create a pattern and feed it to a MessageFormat instance:

The pattern string has slots for three placeholders.

If we supply each value:

Then MessageFormat will fill in the template and render our message:

4.2. MessageFormat Syntax

From the example above, we see that the message pattern:

contains placeholders which are the curly brackets with a required argument index and two optional arguments, type and 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:

In general, the choice style has the form of options separated by the vertical bar (or pipe):


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:

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:

We should slightly modify the French version because of the zero message case:

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:

Suppose that we want to have a properly formed notification in various languages and for different numbers of messages:

Читайте также: