Spring WebFlux Rest API internationalization i18n

Eric Anicet
4 min readApr 17

--

The purpose of this story is to explain how to configure and handles internationalization in a Spring WebFlux Rest API.

Photo by Lucas George Wendt on Unsplash

· Prerequisites
· Overview
· What is Internationalization?
· Getting Started
MessageSource Bean
Custom class for LocaleContextResolver
MessageTranslator class
Translation files
· Testing
· Conclusion
· References

Prerequisites

This is the list of all the prerequisites for following this story:

  • Java 17
  • Spring Boot / Starter WebFlux 3.0.5
  • Maven 3.6.3
  • Postman

Overview

When you produce public APIs, you don’t know which clients will consume your services, so it’s important to standardize your API’s return messages so you can communicate effectively with your consumers.

The Java Platform provides a rich set of APIs for developing global applications. These internationalization APIs are based on the Unicode standard and include the ability to adapt text, numbers, dates, currency, and user-defined objects to any country’s conventions.

What is Internationalization?

If you internationalize, you design or develop your content, application, specification, and so on, in a way that ensures it will work well for, or can be easily adapted for, users from any culture, region, or language.

The word ‘Internationalization’ is often abbreviated to ‘i18n’. This is widely used abbreviation, derived from the fact that there are 18 letters between the ‘i’ and the ’n’. — https://www.w3.org/standards/webdesign/i18n

Getting Started

We will start by creating a simple Spring Boot project from start.spring.io, with the following dependencies: Spring Reactive Web, Lombok, and Validation.

MessageSource Bean

MessageSource is a strategy interface for resolving messages, with support for the parameterization and internationalization of such messages.

Spring provides two out-of-the-box implementations for production:

@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:messages");
messageSource.setDefaultEncoding("ISO-8859-1");
messageSource.setUseCodeAsDefaultMessage(true);
return messageSource;
}

The important thing is the basename because it specifies an array of locale filenames that will resolve to the name provided. Regular and XML properties files are supported: e.g. “messages” will find a “messages.properties”, “messages_en.properties” etc arrangement as well as “messages.xml”, “messages_en.xml” etc.

Custom class for LocaleContextResolver

We need to create a custom class that implements the LocaleContextResolver interface where we’ll override resolveLocaleContext which resolves the current locale context via the given request.

@Component
public class LocaleResolver implements LocaleContextResolver {

@Override
public LocaleContext resolveLocaleContext(ServerWebExchange exchange) {
String language = exchange.getRequest().getHeaders().getFirst("Accept-Language");
return new SimpleLocaleContext(StringUtils.isNotEmpty(language) ? Locale.forLanguageTag(language) : Locale.getDefault());
}

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

}

MessageTranslator class

Let’s create a new MessageTranslator class responsible for choosing the correct message based on the specified locale.

@Component
public class MessageTranslator {

private static MessageSource messageSource;

private static LocaleResolver localeResolver;

@Autowired
MessageTranslator(MessageSource messageSource, LocaleResolver localeResolver) {
MessageTranslator.messageSource = messageSource;
MessageTranslator.localeResolver = localeResolver;
}

/**
* @param msg message code that should be translated.
* @param args message parameters
* @return translated message
*/
public static String getMessage(String msg, Object[] args, ServerWebExchange exchange) {
LocaleContext localeContext = localeResolver.resolveLocaleContext(exchange);
return messageSource.getMessage(msg, args, msg, Objects.requireNonNull(localeContext.getLocale()));
}
/**
* @param msg message code that should be translated.
* @param args message parameters
* @return translated message
*/
public static String getMessage(String msg, Object[] args) {
LocaleContext localeContext = new SimpleLocaleContext(Locale.getDefault());
return messageSource.getMessage(msg, args, msg, Objects.requireNonNull(localeContext.getLocale()));
}

/**
* @param msg message code that should be translated.
* @return translated message
*/
public static String getMessage(String msg, ServerWebExchange exchange) {
LocaleContext localeContext = localeResolver.resolveLocaleContext(exchange);
return messageSource.getMessage(msg, null, msg, Objects.requireNonNull(localeContext.getLocale()));
}

/**
* @param msg message code that should be translated.
* @return translated message
*/
public static String getMessage(String msg) {
LocaleContext localeContext = new SimpleLocaleContext(Locale.getDefault());
return messageSource.getMessage(msg, null, msg, Objects.requireNonNull(localeContext.getLocale()));
}
}

Translation files

We can add translation files in the project resources directory.

messages.properties is the default translation file when no match message file was found.

Now, we can set custom messages in our API.

  • MessageTranslator.getMessage("internal.server.exception", echange)without parameters.
  • MessageTranslator.getMessage("error.id.not.found", arg,echange)with argument parameters in the translator.

Testing

We are all done with our code. We can run our application and test it.

GET http://localhost:8080/api/book/101
GET http://localhost:8080/api/book/101
GET http://localhost:8080/api/book/101
GET http://localhost:8080/api/book/101

Conclusion

In this story, We have seen how to configure and handle internationalization in a Spring WebFlux Rest API.

The complete source code is available on GitHub.

References

--

--

Eric Anicet