Software localization

Spring WebFlux Tutorial on Internationalization

In this Spring WebFlux tutorial, we will show you how to configure your own web application with internationalization and localization support.
Software localization blog category featured image | Phrase

In our previous blog posts, we have already talked about how to implement internationalization with Spring Boot and Spring MVC. In this Spring WebFlux tutorial on internationalization, we will create a web application with internationalization and localization support in Spring WebFlux. First, we will go through all basic steps to bring up a simple web application with Spring Boot 2.0 and Spring WebFlux. Furthermore, we will add the necessary configuration to support internationalization and localization. Besides, we will use Thymeleaf as template engine. Hence, we expect the reader has basic knowledge of Maven, Spring Boot, Spring WebFlux, and Thymeleaf.

Project Setup

Let us go over the basics of setting up our project to get things rolling...

Dependency

First of all, we declare our project parent in pom.xml to spring-boot-starter-parent. Then, we add dependencies for Spring WebFlux and Thymeleaf as below:

https://gist.github.com/linhvovn/2424c831ebea5a2e589c306431825c5f#file-spring-boot-parent

<parent>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-parent</artifactId>

    <version>2.1.1.RELEASE</version>

    <relativePath/>

</parent>

<dependencies>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-webflux</artifactId>

    </dependency>

    <dependency>

        <groupId>org.thymeleaf</groupId>

        <artifactId>thymeleaf-spring5</artifactId>

    </dependency>

</dependencies>

In fact, the latest Spring Boot version can be found over on Maven Central.

Meanwhile, please note that we use Java 9 in this article. Hence, the declaration is as below:

<plugin>

    <groupId>org.apache.maven.plugins</groupId>

    <artifactId>maven-compiler-plugin</artifactId>

    <configuration>

        <source>9</source>

        <target>9</target>

    </configuration>

</plugin>

Configuration

We create a WebConfig class and decorate it with @Configuration annotation as below:

@Configuration

@EnableWebFlux

public class WebConfig implements ApplicationContextAware, WebFluxConfigurer {

    ApplicationContext context;

    @Override

    public void setApplicationContext(ApplicationContext context) {

        this.context = context;

    }

    //...

}

In the above code, the WebConfig implements the ApplicationContextAware to allow us getting access to the ApplicationContext. Moreover, we also need to implement the WebFluxConfigurer as this will make it possible for us to register our ViewResolver later.

Additionally, we decorate the class with @EnableWebFlux annotation. This will enable the use of annotated controllers and functional endpoints. However, only one @Configuration class should have the @EnableWebFlux annotation.

Using Thymeleaf As Template Engine

Thymeleaf, as a modern server-side Java template engine that emphasizes natural HTML templates, offers a reactive-friendly way to integrate with Spring 5 WebFlux:

//...

     @Bean

    public ITemplateResolver thymeleafTemplateResolver() {

        final SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();

        resolver.setApplicationContext(this.context);

        resolver.setPrefix("classpath:views/");

        resolver.setSuffix(".html");

        resolver.setTemplateMode(TemplateMode.HTML);

        resolver.setCacheable(false);

        resolver.setCheckExistence(false);

        return resolver;

    }

    @Bean

    public ISpringWebFluxTemplateEngine thymeleafTemplateEngine() {

        SpringWebFluxTemplateEngine templateEngine = new SpringWebFluxTemplateEngine();

        templateEngine.setTemplateResolver(thymeleafTemplateResolver());

        return templateEngine;

    }

    @Bean

    public ThymeleafReactiveViewResolver thymeleafReactiveViewResolver() {

        ThymeleafReactiveViewResolver viewResolver = new ThymeleafReactiveViewResolver();

        viewResolver.setTemplateEngine(thymeleafTemplateEngine());

        return viewResolver;

    }

    @Override

    public void configureViewResolvers(ViewResolverRegistry registry) {

        registry.viewResolver(thymeleafReactiveViewResolver());

    }

    //...

As we can see here, we need a Template Resolver, a Template Engine, and a View Resolver as the necessary settings.

For instance, we use SpringResourceTemplateResolver as the implementation of Template Resolver. This is where we specify the location of our template files and the file extension. In this article, our templates will be in HTML format.

After that, we assign that Template Resolver to a Template Engine which is the SpringWebFluxTemplateEngine. Similarly, the Template Engine is referred to by a View Resolver which is the ThymeleafReactiveViewResolver here.

Eventually, we register our View Resolver to Spring by overriding the configureViewResolvers method of the interface WebFluxConfigurer.

Next, we can put a sample index.html file under resources/views as our template. The file content is:

<!DOCTYPE html>

<html>

  <head>

    <meta charset="utf-8">

    <title>I18N Spring Webflux</title>

  </head>

  <body>

  	<p>Hi there, you have successfully setup application with Spring Boot, Spring Webflux and Thymleaf</p>

  </body>

</html>

Handler and Router

Until here, we can create a WelcomeHandler which will return the index.html template:

@Component

public class WelcomeHandler {

    public Mono<ServerResponse> hello(ServerRequest request) {

        return ServerResponse

                .ok()

                .contentType(MediaType.TEXT_HTML)

                .render("index");

    }

    //...

 }

In Spring WebFlux, we need a Router to route a request to the corresponding handler. Let's create a WelcomeRouter as below:

@Configuration

public class WelcomeRouter {

	@Bean

	public RouterFunction<ServerResponse> route(WelcomeHandler welcomeHandler) {

		return RouterFunctions

			.route(RequestPredicates.GET("/index")

                                .and(RequestPredicates.accept(MediaType.TEXT_HTML)), welcomeHandler::hello);

  }

}

As a result, the request comes to /index, will be handled by WelcomeHandler.hello method.

Running the Application

At this point, we add below class to be able to run our Spring Boot application:

@SpringBootApplication

public class I18NWebfluxApplication {

    public static void main(String[] args) {

        SpringApplication.run(I18NWebfluxApplication.class, args);

    }

}

Now run our application and access the server at http://localhost:8080/index. We will see the following result:

Demo app | Phrase

I18n in Spring WebFlux

We've now reached the point at which we can start implementing i18n and l10n...

MessageSource Config

To support multiple languages in our project, we need to declare a message properties file for each of the supported languages.

Let's add a messages.properties under resources/languages to serve the default language:

label.welcome = Welcome

label.content = This is sample project for Internalization and Localization in Webflux

label.changeLang = Supported languages

label.lang.en = English

label.lang.fr = French

label.lang.cn = Chinese

label.lang.de = German

Additionally, we can add messages_de.propertiesmessages_fr.properties and messages_zh.properties. This will add the ability to support German, French and Chinese languages.

Meanwhile, we can see that a message properties file contains a set of key-pair values. We will use the key to refer to the desired message in our view later.

Afterward, we need to tell Spring where we put our message properties files. Therefore, we declare a MessageSource bean in our WebConfig as below:

@Bean

public MessageSource messageSource() {

    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();

    messageSource.setBasenames("languages/messages");

    messageSource.setDefaultEncoding("UTF-8");

    return messageSource;

}

Custom LocaleContextResolver

There are two implementations of LocaleContextResolver available:

  • AcceptHeaderLocaleContextResolver: uses the primary locale specified in the "Accept-Language" header of the HTTP request. This is the default one.
  • FixedLocaleContextResolver: always returns a fixed locale and optionally time zone

In this article, we will try to implement a custom LocaleContextResolver. The purpose of it is to resolve the locale from a request parameter:

public class RequestParamLocaleContextResolver implements LocaleContextResolver{

    @Override

    public LocaleContext resolveLocaleContext(ServerWebExchange exchange) {

        Locale targetLocale = Locale.getDefault();

        List<String> referLang = exchange.getRequest().getQueryParams().get("lang");

        if (referLang != null && !referLang.isEmpty()) {

            String lang = referLang.get(0);

            targetLocale = Locale.forLanguageTag(lang);

        }

        return new SimpleLocaleContext(targetLocale);

    }

    //...

}

Furthermore, we need to tell Spring to use our implementation as the LocaleContextResolver. Hence, we add a configuration class which extends the DelegatingWebFluxConfiguration class. Then, we will override the method createLocaleContextResolver to return our custom implementation:

@Configuration

public class LocaleSupportConfig extends DelegatingWebFluxConfiguration {

    @Override

    protected LocaleContextResolver createLocaleContextResolver() {

        return new RequestParamLocaleContextResolver();

    }

}

Welcome Page

Up until here, we have everything ready for the configuration. Let's create a welcome.html template as below:

<!DOCTYPE html>

<html>

  <head>

    <meta charset="utf-8">

    <title>I18N Spring Webflux</title>

  </head>

  <body>

    <h2 data-th-text="#{label.welcome}"></h2>

    <p data-th-text="#{label.content}"></p>

    <p data-th-text="#{label.changeLang}"></p>

    <ul>

    	<li>

    		<a href="/welcome?lang=en" data-th-text="#{label.lang.en}"></a>

    	</li>

    	<li>

    		<a href="/welcome?lang=de" data-th-text="#{label.lang.de}"></a>

    	</li>

    	<li>

    		<a href="/welcome?lang=fr" data-th-text="#{label.lang.fr}"></a>

    	</li>

    	<li>

    		<a href="/welcome?lang=zh" data-th-text="#{label.lang.cn}"></a>

    	</li>

    </ul>

  </body>

</html>

The above template uses Thymeleaf syntax to render localized messages. Indeed, the real message will be chosen at runtime based on the selected locale.

In addition to this, we also need to update our WelcomeHandler and WelcomeRouter to use this template:

@Component

public class WelcomeHandler {

    public Mono<ServerResponse> hello(ServerRequest request) {

        return ServerResponse

                .ok()

                .contentType(MediaType.TEXT_HTML)

                .render("index");

    }

    public Mono<ServerResponse> welcome(ServerRequest request) {

        return ServerResponse

                .ok()

                .contentType(MediaType.TEXT_HTML)

                .render("welcome");

    }

}
@Configuration

public class WelcomeRouter {

	@Bean

	public RouterFunction<ServerResponse> route(WelcomeHandler welcomeHandler) {

		return RouterFunctions

			.route(RequestPredicates.GET("/index")

                                .and(RequestPredicates.accept(MediaType.TEXT_HTML)), welcomeHandler::hello)

			.andRoute(RequestPredicates.GET("/welcome")

                                .and(RequestPredicates.accept(MediaType.TEXT_HTML)), welcomeHandler::welcome);

	}

}

Testing

Altogether, our project is ready for final testing. Let's start the application and access http://localhost:8080/welcome:

Testing site in demo app in English | Phrase

Then, click on French anchor. What you will see:

Testing site in demo app in French | Phrase

That being the case, we have successfully set up a Spring WebFlux project with I18N support.

Summary

Let's summarize what we have done in this article.

At first, we have brought up a Spring WebFlux project which uses Thymeleaf as Template Engine. Subsequently, we added internationalization and localization support to the project by using message.properties, MessageResource and LocaleContextResolver.

Regardless of the two available implementations of LocaleContextResolver, we have tried to implement a custom RequestParamLocaleContextResolver. Therefore, we can change the application locale by changing the request parameter.

Finally, we can find the whole implementation of this article on our Github.