Как настроить i18n в Spring boot 2 + Webflux + Thymeleaf?

Я просто начинаю новый проект на основе Spring boot 2 + Webflux. Об обновлении версии весеннего ботинка и замене spring-boot-starter-web классы spring-boot-starter-webflux например

  • WebMvcConfigurerAdapter
  • LocaleResolver
  • LocaleChangeInterceptor

не хватает. Как теперь я могу настроить defaultLocale и перехватчик для изменения языка?

Ответ 1

Просто добавьте WebFilter который устанавливает заголовок Accept-Language из значения параметра запроса. Следующий пример получает язык из параметра запроса языка в URI, например http://localhost:8080/examples?language=es:

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.adapter.HttpWebHandlerAdapter;
import reactor.core.publisher.Mono;

import static org.springframework.util.StringUtils.isEmpty;

@Component
public class LanguageQueryParameterWebFilter implements WebFilter {

    private final ApplicationContext applicationContext;

    private HttpWebHandlerAdapter httpWebHandlerAdapter;

    public LanguageQueryParameterWebFilter(final ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @EventListener(ApplicationReadyEvent.class)
    public void loadHttpHandler() {
        this.httpWebHandlerAdapter = applicationContext.getBean(HttpWebHandlerAdapter.class);
    }

    @Override
    public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
        final ServerHttpRequest request = exchange.getRequest();
        final MultiValueMap<String, String> queryParams = request.getQueryParams();
        final String languageValue = queryParams.getFirst("language");

        final ServerWebExchange localizedExchange = getServerWebExchange(languageValue, exchange);
        return chain.filter(localizedExchange);
    }

    private ServerWebExchange getServerWebExchange(final String languageValue, final ServerWebExchange exchange) {
        return isEmpty(languageValue)
                ? exchange
                : getLocalizedServerWebExchange(languageValue, exchange);
    }

    private ServerWebExchange getLocalizedServerWebExchange(final String languageValue, final ServerWebExchange exchange) {
        final ServerHttpRequest httpRequest = exchange.getRequest()
                .mutate()
                .headers(httpHeaders -> httpHeaders.set("Accept-Language", languageValue))
                .build();

        return new DefaultServerWebExchange(httpRequest, exchange.getResponse(),
                httpWebHandlerAdapter.getSessionManager(), httpWebHandlerAdapter.getCodecConfigurer(),
                httpWebHandlerAdapter.getLocaleContextResolver());
    }
}

Он использует @EventListener(ApplicationReadyEvent.class), чтобы избежать циклических зависимостей.

Не стесняйтесь протестировать его и предоставить отзывы об этом POC.

Ответ 2

С spring-boot-starter-webflux есть

  • DelegatingWebFluxConfiguration
  • LocaleContextResolver

Например, чтобы использовать параметр запроса "lang" для явного управления локалью:

  1. LocaleContextResolver, чтобы resolveLocaleContext() возвращал SimpleLocaleContext определенный параметром GET "lang". Я QueryParamLocaleContextResolver эту реализацию QueryParamLocaleContextResolver. Обратите внимание, что по умолчанию LocaleContextResolver является org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver.

  2. Создайте класс @Configuration который расширяет DelegatingWebFluxConfiguration. Переопределите DelegatingWebFluxConfiguration.localeContextResolver() для возврата QueryParamLocaleContextResolver который мы только что создали на шаге 1. Назовите этот класс конфигурации WebConfig.

  3. В WebConfig переопределите DelegatingWebFluxConfiguration.configureViewResolvers() и добавьте компонент ThymeleafReactiveViewResolver в качестве средства ThymeleafReactiveViewResolver представления. Мы делаем это потому, что по какой-то причине DelegatingWebFluxConfiguration пропустит ThymeleafReactiveViewResolver после шага 2.

Также я должен упомянуть, что для использования i18n с реактивным стеком необходим этот компонент:

    @Bean
    public MessageSource messageSource() {
        final ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasenames("classpath:/messages");
        messageSource.setUseCodeAsDefaultMessage(true);
        messageSource.setDefaultEncoding("UTF-8");
        messageSource.setCacheSeconds(5);
        return messageSource;
}

После создания естественного шаблона, некоторых файлов свойств и контроллера вы увидите, что:

  • localhost:8080/test?lang=zh дает вам китайскую версию

  • localhost:8080/test?lang=en дает вам английскую версию

Только не забудьте <meta charset="UTF-8"> в <head>, иначе вы можете увидеть некоторые неприятные отображения китайских иероглифов.

Ответ 3

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

Документация (см. 1.2.2. WebHandler API): https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-config-customize

MyLocaleContextResolver.java

public class MyLocaleContextResolver implements LocaleContextResolver {


    @Override
    public LocaleContext resolveLocaleContext(ServerWebExchange exchange) {      
        return new SimpleLocaleContext(Locale.FRENCH);        
    }

    @Override
    public void setLocaleContext(ServerWebExchange exchange, LocaleContext localeContext) {
        throw new UnsupportedOperationException();
    }
}

Затем в файле конфигурации (с пометкой @Configuration) или в файле приложения для весенней загрузки определите свой собственный компонент HttpHandler.

Application.java

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public HttpHandler httpHandler(ApplicationContext context) {

        MyLocaleContextResolver localeContextResolver = new MyLocaleContextResolver();

        return WebHttpHandlerBuilder.applicationContext(context)
                .localeContextResolver(localeContextResolver) // set your own locale resolver
                .build();

    }

}

Вот оно!