Internationalization (i18n) with Spring

When developing applications, it can be interesting to put labels in a separate properties file, so that they can be re-used. For example, we often noticed that within our applications there were slight variations on specific words, which broke consistency. The easiest way to solve that is to centralize these labels. Another benefit you get by having these labels centralized is that it makes translating easier. Just switch the properties file and you can have your application translated in no time. If you’re working with the Spring framework already, this is very easy to do, and is part of Spring MVC (internationalization or i18n).

Project setup

I’m going to start with a simple Spring boot project, so open start.spring.io. Enter the project metadata, and as dependencies you choose Web and Thymeleaf. Once done, press the big Generate project button, unzip the archive and import it in your IDE. Well done, your project is set up!

spring-initializr

Creating a simple webpage

Now, let’s create a simple webpage using Spring MVC. First we have to create a controller:

@Controller
@RequestMapping
public class MainController {
    @Autowired
    private AwesomeWebsiteServiceImpl service;
    @RequestMapping
    public ModelAndView getAwesomeWebsite() {
        return new ModelAndView("awesomeWebsite", "website", "The most awesome website is " + service.getAwesomeWebsite());
    }
}

This controller is going to use a service called AwesomeWebsiteServiceImpl to retrieve the most awesome website. The view used withing the getAwesomeWebsite() method is "awesomeWebsite" and the model name is "website".

So, let’s create the AwesomeWebsiteServiceImpl first:

@Service
public class AwesomeWebsiteServiceImpl {
    private String[] awesomeWebsites = new String[] { "http://g00glen00b.be", "http://start.spring.io" };
    @Autowired
    private Random random;

    public String getAwesomeWebsite() {
        return awesomeWebsites[random.nextInt(awesomeWebsites.length)];
    }
}

So, what happens here isn’t that hard. We have an array of strings containing the most awesome websites (a bit opinionated though 😀). Then we use the getAwesomeWebsite() method to obtain a random website from that list.

We do have to inject the Random instance though, to do that you open the Application class (the class with the @SpringBootApplication annotation), and you add the following bean:

@Bean
public Random randomGenerator() {
    return new Random();
}

Finally, we have to add the HTML template itself inside the src/main/resoruces/templates folder. We called the view “awesomeWebsite”, so that means we have to create an HTML file called awesomeWebsite.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
  </head>
  <body>
    <div class="container">
      <h1 class="text-center">Most awesome website<br /><small th:text="${website}"></small></h1>
    </div>
  </body>
</html>

As you can see here, we’re using the Thymeleaf th:text attribute, containing an expression that will be rendered when we view that page. In this case it contains ${website} which is a placeholder for the website model we defined earlier in our controller (as part of the ModelAndView).

Now, if we run the application, we should see something like this:

first-demo

Internationalization

Now, is this application available in multiple languages? No. So let’s do that now. Even if you won’t have an application available in multiple languages, you can still profit from using Spring i18n, because now you have a centralized spot to change any label.

To do that, you first have to open application.yml or application.properties and then you have to add the following properties:

spring:
  messages:
    basename: messages/messages
    cache-seconds: -1
    encoding: UTF-8 

Or in properties:

spring.messages.basename=messages/messages
spring.messages.cache-seconds=-1
spring.messages.encoding=UTF-8

Back in the early days you had to define your message resolver bean yourself, but now all you need is some properties, if you use Spring boot. So, what happens now is that we told Spring that inside messages/ folder we have several properties files starting with messages_{code}.properties. The code in this case is the locale code (for example nl, nl_be, en, en_us, …) but make sure that you always use underscores.

First of all, let’s create a default file called messages.properties inside the src/main/resources/messages folder:

label.mostAwesomeWebsite=Most awesome website

So, I just created a property called label.mostAwesomeWebsite, and it contains the text “Most awesome website”, great!

Now, open the awesomeWebsite.html again, and remove the “Most awesome website” text and replace it by the following:

<span th:text="#{label.mostAwesomeWebsite}"></span>

We do have to replace it by a separate HTML element, because otherwise the placeholder would replace the <small> tag as well. Anyways, if you run the application again, you’ll see that it still looks the same, obviously.

So, let’s create another file in your own language, for me it is Dutch, so I’m going to create a file called messages_nl.properties.

If I provide the same labels now, but using a different value, I now have a translated version:

label.mostAwesomeWebsite=De beste website

If you now run the application again, and you make sure that you’re using the correct language settings in your browser to see the different language, you’ll see that it is now translated.

language-settings

Using internationalization in your code

There is one issue though, the programmatically defined message is still not translated.

demo-nl

Obviously, the most easy way to fix that is to move the message (except the website) to the HTML template as well. However, sometimes you need to provide translated messages programmatically as well, for example when showing error messages. So, let’s see how you could fix that as well.

First of all, we’re going to add another property to messages.properties and the translated messages_nl.properties:

message.mostAwesomeWebsite=The most awesome website is {0}
message.mostAwesomeWebsite=De beste website is {0}

Now, open the MainController. First of all you have to autowire the MessageSource itself:

@Autowired
private MessageSource messageSource;

Then, inside the getAwesomeWebsite() method we have to change a few things:

@RequestMapping
public ModelAndView getAwesomeWebsite() {
    final String[] params = {service.getAwesomeWebsite()};
    final String msg = messageSource.getMessage("message.mostAwesomeWebsite", params, LocaleContextHolder.getLocale());
    return new ModelAndView("awesomeWebsite", "website", msg);
}

First of all, we have to create a new String array, containing the parameters we want to use. You may have noticed that we added the {0} thing to our message properties. This will be replaced by the first element in the given array, in this case, the website coming from the service.

Now, the next part is that we use the messageSource to obtain the message with the message.mostAwesomeWebsite key, provide the given parameters, and to provide the locale, you can use the LocaleContextHolder which contains the locale of the request.
Now all you have to do is to replace original model with the new msg.

If you run the example again, you’ll see that it is now properly translated.

demo-full-nl

Using parameters in Thymeleaf

We kinda did two things here, we programmatically used the MessageSource and we provided parameters. Using parameters is also possible if you use Thymeleaf. So, if we undo all our changes and replace getAwesomeWebsite() in our controller by this:

return new ModelAndView("awesomeWebsite", "website", service.getAwesomeWebsite());

And then we go to awesomeWebsite.html and we replace the following:

<small th:text="${website}"></small>

With this:

<small th:text="#{message.mostAwesomeWebsite(${website})}"></small>

Then you’ll see that it yields exactly the same result.

Achievement: Brought your application to the next level with Spring i18n

If you’re seeing this, then it means you successfully managed to make it through this article. If you’re interested in the full code example, you can find it on GitHub.

Tagged , , , .

g00glen00b

Consultant at Cronos and Tech lead at Aquafin. Usually you can find me trying out new libraries and technologies. Loves both Java and JavaScript.

  • Christian Kersten

    Hey,

    I Did exactly as you did.
    I have added the following 3 lines to application.properties;
    spring.messages.basename=messages/messages
    spring.messages.cache-seconds=-1
    spring.messages.encoding=UTF-8

    I have an index.html file with : “p th:text=”#{text}”></p" (I did write it without the otherwise I won’t show)
    Then I have an resources/messages/messages_nl.properties file with: text = Dit is nederlands
    I have also made a messages.properties file with the same value.

    And as WHK Yan did, I have the same error: ??text_nl_NL??
    In the index.html it does recognise the “text” value.

    Dus ik hoop dat je me kan helpen 🙂

    Groeten,

    Christian

    • Christian Kersten

      In the mean time I use this method to get it working:

      @Bean
      public ResourceBundleMessageSource messageSource() {
      ResourceBundleMessageSource source = new ResourceBundleMessageSource();
      source.setBasenames(“i18n/language”);
      source.setUseCodeAsDefaultMessage(true);
      return source;
      }

      My language file: resources/i18n/language_nl_NL.properties

      I still have the problem that I don’t know how to change the language for testing and later on for implementation.

  • Wentao Zhou

    Thanks, but I do not know why I always get the error: No message found under code ‘welcome’ for locale ‘en_US’. Do you have any idea about what is the root cause ? I configured the basename in application.yml the same as what you mentioned in your article, and for the properties file, I am pretty sure there are the same as yours.

  • BerthierLemieux

    Awesome, exactly the information I was looking for! Thanks for posting.

  • WHK Yan

    Does not works using spring boot, the labels says ??xxx??. And in tutorial says messages-{code}.properties but is
    messages_{code}.properties

    • You’re right about the messages_{code}.properties, I changed it, thanks! Other than that it should work though?

      • WHK Yan

        I put the messages folder into root folder as “resource folder” but works only when put the mesages folder into resources folder. This is not specific in tutorial. Now works fine. Thanks.

      • WHK Yan

        en-us does not works, works only with “_” chars, your says: “The code in this case is the locale code (for example nl, nl-be, en, en-us, …)” but is nl, nl_be, en, en_us, es, es_cl, etc…

  • Hinotori

    thanks! i was looking for the Messagesource thing