Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Перевод интерфейса: I18NProvider

Интернационализация (i18n — 18 букв между «i» и «n») — подготовка приложения к работе на нескольких языках. Тексты интерфейса не пишут прямо в коде, а выносят в файлы переводов: по одному на язык. В коде остаются только ключи, например nav.posts, а Vaadin по ключу и текущему языку находит нужную строку. За поиск отвечает I18NProvider — объект, который знает, какие языки есть и где лежат переводы.

Файлы переводов

Создайте папку src/main/resources/vaadin-i18n и в ней два файла. Английский:

# src/main/resources/vaadin-i18n/translations_en.properties
app.title=Blog admin
nav.posts=Posts
nav.new-post=New post
language=Language
welcome=Hello, {0}!

И русский:

# src/main/resources/vaadin-i18n/translations_ru.properties
app.title=Админка блога
nav.posts=Статьи
nav.new-post=Новая статья
language=Язык
welcome=Здравствуйте, {0}!

Это наборы ресурсов (resource bundles) — стандартный механизм Java. Окончание имени (_en, _ru) задаёт язык. Сохраняйте файлы в UTF-8. Писать свой I18NProvider не нужно: найдя папку vaadin-i18n, Vaadin сам подключит встроенный провайдер, и в нём будут языки en и ru.

getTranslation

У каждого компонента есть метод getTranslation(ключ). Переведём MainLayout:

// src/main/java/com/example/blog/views/MainLayout.java (фрагмент конструктора)
H1 title = new H1(getTranslation("app.title"));
title.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE, LumoUtility.Flex.GROW);
 
SideNav nav = new SideNav();
nav.addItem(new SideNavItem(getTranslation("nav.posts"), PostListView.class, VaadinIcon.LIST.create()));
nav.addItem(new SideNavItem(getTranslation("nav.new-post"), PostFormView.class, VaadinIcon.PLUS.create()));

Если язык браузера русский, на http://localhost:8080/ в шапке «Админка блога», в меню «Статьи» и «Новая статья». В браузере с английским языком будут «Blog admin», «Posts» и «New post». Язык браузера, которого нет в файлах (например, немецкий), получит первый язык из списка — английский.

Значения можно подставлять: getTranslation("welcome", "Анна") вернёт «Здравствуйте, Анна!». {0} — место для первого параметра, {1} — для второго.

Переключатель языка

Добавим в шапку выпадающий список:

// src/main/java/com/example/blog/views/MainLayout.java (фрагмент конструктора)
Select<Locale> language = new Select<>();
language.setAriaLabel(getTranslation("language"));
language.setItems(Locale.ENGLISH, Locale.of("ru"));
language.setItemLabelGenerator(locale -> locale.getDisplayLanguage(locale));
language.setValue(Locale.of(getLocale().getLanguage()));
language.addValueChangeListener(event -> {
    VaadinSession.getCurrent().setLocale(event.getValue());
    UI.getCurrent().getPage().reload();
});
 
addToNavbar(new DrawerToggle(), title, language);

Справа в шапке появится список «русский» / «English»: getDisplayLanguage(locale) пишет название языка на нём самом. Выберите «English» — страница перезагрузится, и весь каркас станет английским. Язык хранится в сессии (VaadinSession), поэтому при переходах между экранами он сохраняется. Импорты: java.util.Locale, com.vaadin.flow.component.UI, com.vaadin.flow.component.select.Select, com.vaadin.flow.server.VaadinSession.

Перезагрузка нужна, потому что тексты уже созданных компонентов сами не меняются. Без неё экран должен реализовать LocaleChangeObserver и в методе localeChange заново вызвать setText(getTranslation(...)) для каждого компонента.

Своё I18NProvider

Если переводы лежат в другом месте — в другой папке или в базе данных, — напишите провайдер сам. Достаточно бина Spring:

// src/main/java/com/example/blog/TranslationProvider.java
package com.example.blog;
 
import com.vaadin.flow.i18n.I18NProvider;
import java.text.MessageFormat;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import org.springframework.stereotype.Component;
 
@Component
public class TranslationProvider implements I18NProvider {
 
    @Override
    public List<Locale> getProvidedLocales() {
        return List.of(Locale.ENGLISH, Locale.of("ru"));
    }
 
    @Override
    public String getTranslation(String key, Locale locale, Object... params) {
        ResourceBundle bundle = ResourceBundle.getBundle("messages", locale);
        if (!bundle.containsKey(key)) {
            return "!" + key;
        }
        return MessageFormat.format(bundle.getString(key), params);
    }
}

Этот провайдер читает файлы src/main/resources/messages_en.properties и messages_ru.properties, а для неизвестного ключа возвращает !nav.postz, чтобы опечатку было видно на экране. Встроенный провайдер в таком случае показывает !ru: nav.postz.

Частая ошибка

Английский файл назвали без окончания — translations.properties, а русский — translations_ru.properties. Ошибки нет, но весь интерфейс у всех пользователей на русском, даже в английском браузере. Встроенный провайдер считает языками только файлы с окончанием, поэтому видит один язык — ru — и выбирает его для всех. Файл без окончания служит лишь запасным, когда ключа нет в файле языка. Дайте каждому языку свой файл: translations_en.properties и translations_ru.properties.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.