Интернационализация приложения Spring Boot

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

· 4 минуты на чтение
Интернационализация приложения Spring Boot

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

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

В этой статье мы рассмотрим, как Spring Boot поддерживает интернационализацию и как реализовать ее в вашем приложении.

Спонсор поста

Пакеты ресурсов

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

Чтобы создать пакет ресурсов в Spring Boot, необходимо создать файл свойств для каждого языка, который вы хотите поддерживать. Файл свойств должен иметь одинаковое имя, но другое расширение, которое представляет код языка. Например, если вы хотите поддерживать английский и русский языки, вы создадите два файла свойств: messages_en.properties и messages_ru.properties.

Так выглядит пакет ресурсов в Idea

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

label.one=test
label.two=test two

Аналогично для русского языка:

label.one=тест
label.two=тест два

Проблемы с кириллицей

По стандарту файлы ресурсов должны быть закодированы в ISO-8859-1. Поэтому если просто написать label.one=тест, на выходе получим ????.

Необходимо преобразовать кириллицу в Unicode.

label.one=\u0442\u0435\u0441\u0442
label.one=тест
Я для этого использую плагин в Idea: String Manipulation.

MessageSource

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

private final MessageSource messageSource;

public String getLocalizedMessage(String key, Object[] args) {
    Locale locale = LocaleContextHolder.getLocale();
    return messageSource.getMessage(key, args, locale);
}

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

Рандомный блок

Параметры сообщения

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

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

welcome=Welcome, {0}!

В приведенном выше примере {0} - это заполнитель для имени пользователя.

Чтобы заменить заполнитель значением, вы можете использовать метод getMessage() , который принимает массив аргументов. Например:

String welcomeMessage = messageSource.getMessage("welcome.message", new Object[] {"John"}, Locale.US);

Именованные параметры

Можно также использовать именованные заполнители вместо пронумерованных. Например:

welcome.message=Welcome, {name}!

Чтобы заменить именованный заполнитель значением, вместо массива можно использовать Map. Например:

Map<String, Object> args = new HashMap<>();
args.put("name", "John");
String welcomeMessage = messageSource.getMessage("welcome.message", args, Locale.US);

Демо проект

😺
Репозиторий с примерами: Struchkov Git | GitHub

Напишем небольшое REST приложение с одним эндпойнтом.

Для начала создадим пакет ресурсов с названием messages. После чего настроим бин MessageSource:

@Configuration
public class AppConfig {

    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("classpath:messages");
        messageSource.setCacheSeconds(3600);
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }
    
}

Обратите внимание на метод setBasename, который указывает название пакета ресурсов.

Теперь создадим эндпойнт:

@GetMapping("/hello")
public String hello(@RequestParam("name") String name, HttpServletRequest request) {
    return messageSource.getMessage(
            "welcome", 
            new Object[]{name}, 
            localeResolver.resolveLocale(request)
    );
}

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

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

public class UserLocaleResolver implements LocaleResolver {

    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        final String lang = request.getHeader("Language");
        return Locale.forLanguageTag(lang);
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
        // Not needed for this example
    }

}

И добавим бин в AppConfig:

@Bean
public LocaleResolver localeResolver() {
    return new UserLocaleResolver();
}
⚠️
@Component или @Service не будет работать над UserLocaleResolver. Нужно именно создать через Java-конфигурацию.

В нашем примере локализация определяется по передаваемому http-header. В зависимости от ваших требований, механизм определения будет отличаться. Например, можно получать установленный язык пользователя из БД.

Вернемся к нашему эндпойнту. Используя метод MessageSource.getMessage() мы передаем ключ шаблона, а также параметры, которые необходимо доставить в заполнители ({0}). Если в шаблоне есть параметры, а вы их не передадите, то будет выброшено исключение. Также вы получите исключение, если в файле ресурсов не окажется шаблона с переданным названием.

Можно сделать сервис-обертку над MessageSource, чтобы упростить его использование.

Сообщения ошибок валидации

Если вы используете Spring Boot Validation, о котором я рассказывал в статье: Валидация данных в Spring Boot. То вам необходимо также сообщать об ошибках пользователя на его языке.

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

Например, укажем ограничение на длину имени: @Size(max = 10) @RequestParam("name") String name. И выполним запрос:

GET http://localhost:8080/hello?name=йцукенгшщзхъ
Language: ru

{
  "timestamp": "2023-03-12T10:54:14.226+00:00",
  "status": 500,
  "error": "Internal Server Error",
  "message": "hello.name: размер должен находиться в диапазоне от 0 до 10",
  "path": "/hello"
}
Для русской локали свой вывод
GET http://localhost:8080/hello?name=йцукенгшщзхъ
Language: en

{
  "timestamp": "2023-03-12T10:56:19.431+00:00",
  "status": 500,
  "error": "Internal Server Error",
  "message": "hello.name: size must be between 0 and 10",
  "path": "/hello"
}
А для английской свой

Вы можете добавить собственный текст ошибки. Для этого создайте файл ValidationMessages_ru.properties, и напишите там текст сообщения.

jakarta.validation.custom.name=\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430\u044f \u0434\u043b\u0438\u043d\u0430 \u0438\u043c\u0435\u043d\u0438. \u0418\u043c\u044f \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u043e\u0442 {min} \u0434\u043e {max}

И используйте кастомное значение в поле message у аннотаций валидации:

@Size(max = 10, message = "{jakarta.validation.custom.name}")

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

Заключение

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

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

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

Struchkov Mark
Struchkov Mark
Задавайте вопросы, если что-то осталось не понятным👇