i18n in Java 11, Spring Boot, and JavaScript

What are i18n and l10n? Internationalization (i18n) is the process of making your application capable of rendering its text in multiple languages. Localization (l10n) means your application has been coded in such a way that it meets language, cultural, or other requirements of a particular locale. These requirements can include formats for date, time, and currency, as well as symbols, icons, and colors, among many other things. i18n enables l10n.

Why is i18n and l10n important? Because you want to make your app accessible to as many users as possible! If you’re a native English speaker, you’re spoiled because English is currently the language of business, and many apps offer an English translation. Internationalizing your Java app is relatively straightforward, thanks to built-in mechanisms. Same goes for Spring Boot - it’s there by default!

This tutorial will show you how to internationalize a simple Java app, a Spring Boot app with Thymeleaf, and a JavaScript Widget.

Java i18n with Resource Bundles

A resource bundle is a .properties file that contains keys and values for specific languages. Using resource bundles allows you to make your code locale-independent. To see how this works, create a new directory on your hard drive for this tutorial’s exercises. For example, java-i18n-example. Navigate to this directory from the command line and create a Hello.java file.

public class Hello {

 public static void main(String[] args) {
 System.out.println("Hello, World!");
 }
}

Run java Hello.java and you should see "Hello, World!" printed to your console.

If you see any error similar to the one below, it’s because you’re using a Java version < 11. JEP 330 is an enhancement in Java 11 that allows you to run a single file of Java source code, without compiling it.

$ java Hello.java
Error: Could not find or load main class Hello.java

You can install Java 11 from AdoptOpenJDK 11 or use SDKMAN!

curl -s "https://get.sdkman.io" | bash

Once you have SDKMAN installed, you can list the available java versions with sdk list java:

$ sdk list java
================================================================================
Available Java Versions
================================================================================
 13.ea.07-open 8.0.202-zulu
 12.ea.31-open 8.0.202-amzn
 + 11.ea.26-open 8.0.202.j9-adpt
 11.0.2-sapmchn 8.0.202.hs-adpt
 11.0.2-zulu 8.0.202-zulufx
 * 11.0.2-open 8.0.201-oracle
 11.0.2.j9-adpt > + 8.0.181-zulu
 11.0.2.hs-adpt 7.0.181-zulu
 11.0.2-zulufx 1.0.0-rc-12-grl
 + 11.0.1-open 1.0.0-rc-11-grl
 + 11.0.0-open 1.0.0-rc-10-grl
 10.0.2-zulu 1.0.0-rc-9-grl
 10.0.2-open 1.0.0-rc-8-grl
 9.0.7-zulu
 9.0.4-open

================================================================================
+ - local version
* - installed
> - currently in use
================================================================================

Set up your environment to use the latest version of OpenJDK with the command below:

sdk default java 11.0.2-open

Now you should be able to run your Hello.java as a Java program.

$ java Hello.java
Hello, World!

Look Ma! No compiling needed!! 😃

Create a messages_en_US.properties file in the same directory and add keys + translations for the terms hello and world.

hello=Hello
world=World

Create messages_es.properties and populate it with Spanish translations.

hello=Hola
world=Mundo

Modify Hello.java to use Locale and ResourceBundle to retrieve the translations from these files.

import java.util.Locale;
import java.util.ResourceBundle;

public class Hello {

 public static void main(String[] args) {
 String language = "en";
 String country = "US";

 if (args.length == 2) {
 language = args[0];
 country = args[1];
 }

 var locale = new Locale(language, country);
 var messages = ResourceBundle.getBundle("messages", locale);

 System.out.print(messages.getString("hello") + " ");
 System.out.println(messages.getString("world"));
 }
}

Run your Java program again, and you should see "Hello World".

$ java Hello.java
Hello World

Improve the parsing of arguments to allow only specifying the language.

if (args.length == 1) {
 language = args[0];
} else if (args.length == 2) {
 language = args[0];
 country = args[1];
}

Run the same command with an es argument and you’ll see a Spanish translation:

$ java Hello.java es
Hola Mundo

Yeehaw! It’s pretty cool that Java has i18n built-in, eh?

Internationalization with Spring Boot and Thymeleaf

Spring Boot has i18n built-in thanks to the Spring Framework and its MessageSource implementations. There’s a ResourceBundleMessageSource that builds on ResourceBundle, as well as a ReloadableResourceBundleMessageSource that should be self-explanatory.

Inject MessageSource into a Spring bean and call getMessage(key, args, locale) to your heart’s content! Using MessageSource will help you on the server, but what about in your UI? Let’s create a quick app to show you how you can add internationalization with Thymeleaf.

Go to start.spring.io and select Web and Thymeleaf as dependencies. Click Generate Project and download the resulting demo.zip file. If you’d rather do it from the command line, you can use HTTPie to do the same thing.

mkdir bootiful-i18n
cd bootiful-i18n
http https://start.spring.io/starter.zip dependencies==web,thymeleaf -d | tar xvz

Open the project in your favorite IDE and create HomeController.java in src/main/java/com/example/demo.

package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

 @GetMapping("/")
 String home() {
 return "home";
 }
}

Create a Thymeleaf template at src/main/resources/templates/home.html that will render the "home" view.

<html xmlns:th="http://www.thymeleaf.org">
<body>
 <h1 th:text="#{title}"></h1>
 <p th:text="#{message}"></p>
</body>
</html>

Add a messages.properties file in src/main/resources that defines your default language (English in this case).

title=Welcome
message=Hello! I hope you're having a great day.

Add a Spanish translation in the same directory, in a messages_es.properties file.

title=Bienvenida
message=¡Hola! Espero que estés teniendo un gran día. 😃

Spring Boot uses Spring’s LocaleResolver and (by default) its AcceptHeaderLocalResolver implementation. If your browser sends an accept-language header, Spring Boot will try to find messages that match.

To test it out, open Chrome and enter chrome://settings/languages in the address bar. Expand the top "Language" box, click Add languages and search for "Spanish". Add the option without a country and move it to the top language in your preferences. It should look like the screenshot below when you’re finished.

Chrome Languages

For Firefox, navigate to about:preferences, scroll down to "Language and Appearance" and click the Choose button next to "Choose your preferred language for displaying pages". Select Spanish and move it to the top.

Firefox Languages

Once you have your browser set to return Spanish, start your Spring Boot app with ./mvnw spring-boot:run (or mvnw spring-boot:run if you’re using Windows).

Add <defaultGoal>spring-boot:run</defaultGoal> in the <build> section of your pom.xml if you want to only type ./mvnw to start your app.

Navigate to http://localhost:8080 and you should see a page with Spanish words.

Home in Spanish

Add the Ability to Change Locales with a URL Parameter

This is a nice setup, but you might want to allow users to set their own language. You might’ve seen this on websites in the wild, where they have a flag that you can click to change to that country’s language. To make this possible in Spring Boot, create a MvcConfigurer class alongside your HomeController.

package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;

@Configuration
public class MvcConfigurer implements WebMvcConfigurer {

 @Bean
 public LocaleResolver localeResolver() {
 return new CookieLocaleResolver();
 }

 @Bean
 public LocaleChangeInterceptor localeInterceptor() {
 LocaleChangeInterceptor localeInterceptor = new LocaleChangeInterceptor();
 localeInterceptor.setParamName("lang");
 return localeInterceptor;
 }

 @Override
 public void addInterceptors(InterceptorRegistry registry) {
 registry.addInterceptor(localeInterceptor());
 }
}

This class uses a CookieLocaleResolver that’s useful for saving the locale preference in a cookie, and defaulting to the accept-language header if none exists.

Restart your server and you should be able to override your browser’s language preference by navigating to http://localhost:8080/?lang=en.

Overriding the browser’s language preference

Your language preference will be saved in a cookie, so if you navigate back to http://localhost:8080, the page will render in English. If you quit your browser and restart, you’ll be back to using your browser’s language preference.

Hot Reloading Thymeleaf Templates and Resource Bundles in Spring Boot 2.1

If you’d like to modify your Thymeleaf templates and see those changes immediately when you refresh your browser, you can add Spring Boot’s Developer Tools to your pom.xml.

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-devtools</artifactId>
</dependency>

This is all you need to do if you have your IDE setup to copy resources when you save a file. If you’re not using an IDE, you’ll need to define a property in your application.properties:

spring.thymeleaf.prefix=file:src/main/resources/templates/

To hot-reload changes to your i18n bundles, you’ll need to rebuild your project (for example, by running ./mvnw compile). If you’re using Eclipse, a rebuild and restart should happen automatically for you. If you’re using IntelliJ IDEA, you’ll need to go to your run configuration and change "On frame deactivation" to be Update resources.

Update resources in IntelliJ IDEA

See this Stack Overflow answer for more information.

Customize the Language used by Okta’s Sign-In Widget

The last example I’d like to show you is a Spring Boot app with Okta’s embedded Sign-In Widget. The Sign-In Widget is smart enough to render the language based on your browser’s accept-language header.

However, if you want to sync it up with your Spring app’s LocalResolver, you need to do a bit more configuration. Furthermore, you can customize things so it sets the locale from the user’s locale setting in Okta.

To begin, export the custom login example for Spring Boot:

svn export https://github.com/okta/samples-java-spring/trunk/custom-login
If you don’t have svn installed, go here and click the Download button.

Create an OIDC App on Okta

If you already have an Okta Developer account, log in to it. If you don’t, create one at developer.okta.com/signup. After you’re logged in to your Okta dashboard, complete the following steps:

  1. From the Applications page, choose Add Application.

  2. On the Create New Application page, select Web.

  3. Give your app a memorable name, then click Done.

Your settings should look similar to the ones below.

OIDC Web App on Okta

You can specify your issuer (found under API > Authorization Servers), client ID, and client secret in custom-login/src/main/resources/application.yml as follows:

okta:
 oauth2:
 issuer: https://{yourOktaDomain}/oauth2/default</span></span>
<span id="L4" class="line"> <span class="na">client-id</span><span class="pi">:</span> <span class="pi">{</span><span class="nv">yourClientID</span><span class="pi">}</span></span>
<span id="L5" class="line"> <span class="na">client-secret</span><span class="pi">:</span> <span class="pi">{</span><span class="nv">yourClientSecret</span><span class="pi">}</span></span></code></pre>
</div>
</div>
<div class="paragraph">
<p>However, it&#8217;s more secure if you store these values in environment variables and keep them out of source control (especially if your code is public).</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell"><span id="L1" class="line"><span class="nb">export </span><span class="nv">OKTA_OAUTH2_ISSUER</span><span class="o">=</span>https://<span class="o">{</span>yourOktaDomain<span class="o">}</span>/oauth2/default</span>
<span id="L2" class="line"><span class="nb">export </span><span class="nv">OKTA_OAUTH2_CLIENT_ID</span><span class="o">={</span>yourClientID<span class="o">}</span></span>
<span id="L3" class="line"><span class="nb">export </span><span class="nv">OKTA_OAUTH2_CLIENT_SECRET</span><span class="o">={</span>yourClientSecret<span class="o">}</span></span></code></pre>
</div>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
I recommend adding the above exports to a <code>.okta.env</code> file in the root of your project and adding <code>*.env</code> to <code>.gitignore</code>. Then run <code>source .okta.env</code> before you start your app.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>After making these changes, you can start the app using <code>./mvnw</code>. Open your browser to <code><a href="http://localhost:8080" class="bare">http://localhost:8080</a></code>, click <strong>Login</strong> and you should be able to authenticate. If you still have your browser set to use Spanish first, you&#8217;ll see that the Sign-In Widget automatically renders in Spanish.</p>
</div>
<div class="imageblock" style="text-align: center">
<div class="content">
<img src="/assets/blog/java-i18n/sign-in-widget-es-10b5a5f59bf67feed8b8c07ec256d92f6d23210d16daa93c7a83da5e07f17df4.png" alt="Sign-In Widget in Spanish" width="800">
</div>
</div>
<div class="paragraph">
<p>This works because Spring auto-enables <code>AcceptHeaderLocaleResolver</code>.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="add-i18n-messages-and-sync-locales">Add i18n Messages and Sync Locales</h2>
<div class="sectionbody">
<div class="paragraph">
<p>It <em>seems</em> like things are working smoothly at this point. However, if you add a <code>LocaleChangeInterceptor</code>, you&#8217;ll see that changing the language doesn&#8217;t change the widget&#8217;s language. To see this in action, create an <code>MvcConfigurer</code> class in <code>custom-login/src/main/java/com/okta/spring/example</code>.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java"><span id="L1" class="line"><span class="kn">package</span> <span class="n">com</span><span class="o">.</span><span class="na">okta</span><span class="o">.</span><span class="na">spring</span><span class="o">.</span><span class="na">example</span><span class="o">;</span></span>
<span id="L2" class="line"></span>
<span id="L3" class="line"><span class="kn">import</span> <span class="nn">org.springframework.context.annotation.Bean</span><span class="o">;</span></span>
<span id="L4" class="line"><span class="kn">import</span> <span class="nn">org.springframework.context.annotation.Configuration</span><span class="o">;</span></span>
<span id="L5" class="line"><span class="kn">import</span> <span class="nn">org.springframework.web.servlet.LocaleResolver</span><span class="o">;</span></span>
<span id="L6" class="line"><span class="kn">import</span> <span class="nn">org.springframework.web.servlet.config.annotation.InterceptorRegistry</span><span class="o">;</span></span>
<span id="L7" class="line"><span class="kn">import</span> <span class="nn">org.springframework.web.servlet.config.annotation.WebMvcConfigurer</span><span class="o">;</span></span>
<span id="L8" class="line"><span class="kn">import</span> <span class="nn">org.springframework.web.servlet.i18n.CookieLocaleResolver</span><span class="o">;</span></span>
<span id="L9" class="line"><span class="kn">import</span> <span class="nn">org.springframework.web.servlet.i18n.LocaleChangeInterceptor</span><span class="o">;</span></span>
<span id="L10" class="line"></span>
<span id="L11" class="line"><span class="nd">@Configuration</span></span>
<span id="L12" class="line"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MvcConfigurer</span> <span class="kd">implements</span> <span class="n">WebMvcConfigurer</span> <span class="o">{</span></span>
<span id="L13" class="line"></span>
<span id="L14" class="line"> <span class="nd">@Bean</span></span>
<span id="L15" class="line"> <span class="kd">public</span> <span class="n">LocaleResolver</span> <span class="nf">localeResolver</span><span class="o">()</span> <span class="o">{</span></span>
<span id="L16" class="line"> <span class="k">return</span> <span class="k">new</span> <span class="nf">CookieLocaleResolver</span><span class="o">();</span></span>
<span id="L17" class="line"> <span class="o">}</span></span>
<span id="L18" class="line"></span>
<span id="L19" class="line"> <span class="nd">@Bean</span></span>
<span id="L20" class="line"> <span class="kd">public</span> <span class="n">LocaleChangeInterceptor</span> <span class="nf">localeInterceptor</span><span class="o">()</span> <span class="o">{</span></span>
<span id="L21" class="line"> <span class="n">LocaleChangeInterceptor</span> <span class="n">localeInterceptor</span> <span class="o">=</span> <span class="k">new</span> <span class="n">LocaleChangeInterceptor</span><span class="o">();</span></span>
<span id="L22" class="line"> <span class="n">localeInterceptor</span><span class="o">.</span><span class="na">setParamName</span><span class="o">(</span><span class="s">"lang"</span><span class="o">);</span></span>
<span id="L23" class="line"> <span class="k">return</span> <span class="n">localeInterceptor</span><span class="o">;</span></span>
<span id="L24" class="line"> <span class="o">}</span></span>
<span id="L25" class="line"></span>
<span id="L26" class="line"> <span class="nd">@Override</span></span>
<span id="L27" class="line"> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">addInterceptors</span><span class="o">(</span><span class="n">InterceptorRegistry</span> <span class="n">registry</span><span class="o">)</span> <span class="o">{</span></span>
<span id="L28" class="line"> <span class="n">registry</span><span class="o">.</span><span class="na">addInterceptor</span><span class="o">(</span><span class="n">localeInterceptor</span><span class="o">());</span></span>
<span id="L29" class="line"> <span class="o">}</span></span>
<span id="L30" class="line"><span class="o">}</span></span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Restart the custom-login app and navigate to <code><a href="http://localhost:8080/?lang=en" class="bare">http://localhost:8080/?lang=en</a></code>. If you click the login button, you&#8217;ll see that the widget is still rendered in Spanish. To fix this, crack open <code>LoginController</code>, add <code>language</code> as a model attribute, and add a <code>Locale</code> parameter to the <code>login()</code> method. Spring MVC will resolve the <code>Locale</code> automatically with <a href="https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolver.html"><code>ServletRequestMethodArgumentResolver</code></a>.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java"><span id="L1" class="line"><span class="kn">package</span> <span class="n">com</span><span class="o">.</span><span class="na">okta</span><span class="o">.</span><span class="na">spring</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">controllers</span><span class="o">;</span></span>
<span id="L2" class="line"></span>
<span id="L3" class="line"><span class="o">...</span></span>
<span id="L4" class="line"><span class="kn">import</span> <span class="nn">java.util.Locale</span><span class="o">;</span></span>
<span id="L5" class="line"></span>
<span id="L6" class="line"><span class="nd">@Controller</span></span>
<span id="L7" class="line"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">LoginController</span> <span class="o">{</span></span>
<span id="L8" class="line"></span>
<span id="L9" class="line"> <span class="o">...</span></span>
<span id="L10" class="line"> <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">LANGUAGE</span> <span class="o">=</span> <span class="s">"language"</span><span class="o">;</span></span>
<span id="L11" class="line"></span>
<span id="L12" class="line"> <span class="nd">@GetMapping</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/custom-login"</span><span class="o">)</span></span>
<span id="L13" class="line"> <span class="kd">public</span> <span class="n">ModelAndView</span> <span class="nf">login</span><span class="o">(</span><span class="n">HttpServletRequest</span> <span class="n">request</span><span class="o">,</span></span>
<span id="L14" class="line"> <span class="nd">@RequestParam</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"state"</span><span class="o">,</span> <span class="n">required</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span> <span class="n">String</span> <span class="n">state</span><span class="o">,</span></span>
<span id="L15" class="line"> <span class="n">Locale</span> <span class="n">locale</span><span class="o">)</span></span>
<span id="L16" class="line"> <span class="kd">throws</span> <span class="n">MalformedURLException</span> <span class="o">{</span></span>
<span id="L17" class="line"></span>
<span id="L18" class="line"> <span class="o">...</span></span>
<span id="L19" class="line"> <span class="n">mav</span><span class="o">.</span><span class="na">addObject</span><span class="o">(</span><span class="n">LANGUAGE</span><span class="o">,</span> <span class="n">locale</span><span class="o">);</span></span>
<span id="L20" class="line"></span>
<span id="L21" class="line"> <span class="k">return</span> <span class="n">mav</span><span class="o">;</span></span>
<span id="L22" class="line"> <span class="o">}</span></span>
<span id="L23" class="line"></span>
<span id="L24" class="line"> <span class="o">...</span></span>
<span id="L25" class="line"><span class="o">}</span></span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Then modify <code>custom-login/src/main/resources/templates/login.html</code> and add a <code>config.language</code> setting that reads this value.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-javascript" data-lang="javascript"><span id="L1" class="line"><span class="nx">config</span><span class="p">.</span><span class="nx">redirectUri</span> <span class="o">=</span> <span class="cm">/*[[${redirectUri}]]*/</span> <span class="s1">'{redirectUri}'</span><span class="p">;</span></span>
<span id="L2" class="line"><span class="nx">config</span><span class="p">.</span><span class="nx">language</span> <span class="o">=</span> <span class="cm">/*[[${language}]]*/</span> <span class="s1">'{language}'</span><span class="p">;</span></span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Restart everything, go to <code><a href="http://localhost:8080/?lang=en" class="bare">http://localhost:8080/?lang=en</a></code>, click the login button and it should now render in English.</p>
</div>
<div class="imageblock" style="text-align: center">
<div class="content">
<img src="/assets/blog/java-i18n/sign-in-widget-en-8025821fc4f38b6bfec77f50d1151f38d91c0776d242f4c4bfba518f6b48d3c6.png" alt="Sign-In Widget in English" width="800">
</div>
</div>
<div class="sect2">
<h3 id="add-internationalization-bundles-for-thymeleaf">Add Internationalization Bundles for Thymeleaf</h3>
<div class="paragraph">
<p>To make it a bit more obvious that changing locales is working, create <code>messages.properties</code> in <code>custom-login/src/main/resources</code>, and specify English translations for keys.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-properties" data-lang="properties"><span id="L1" class="line"><span class="py">hello</span><span class="p">=</span><span class="s">Hello</span></span>
<span id="L2" class="line"><span class="py">welcome</span><span class="p">=</span><span class="s">Welcome home, {0}!</span></span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Create <code>messages_es.properties</code> in the same directory, and provide translations.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-properties" data-lang="properties"><span id="L1" class="line"><span class="py">hello</span><span class="p">=</span><span class="s">Hola</span></span>
<span id="L2" class="line"><span class="py">welcome</span><span class="p">=</span><span class="s">¡Bienvenido a casa {0}!</span></span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Open <code>custom-login/src/main/resources/templates/home.html</code> and change <code>&lt;p&gt;Hello!&lt;/p&gt;</code> to the following:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-html" data-lang="html"><span id="L1" class="line"><span class="nt">&lt;p</span> <span class="na">th:text=</span><span class="s">"#{hello}"</span><span class="nt">&gt;</span>Hello!<span class="nt">&lt;/p&gt;</span></span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Change the welcome message when the user is authenticated too. The <code>{0}</code> value will be replaced by the arguments passed into the key name.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-html" data-lang="html"><span id="L1" class="line"><span class="nt">&lt;p</span> <span class="na">th:text=</span><span class="s">"#{welcome(${#authentication.name})}"</span><span class="nt">&gt;</span>Welcome home,</span>
<span id="L2" class="line"> <span class="nt">&lt;span</span> <span class="na">th:text=</span><span class="s">"${#authentication.name}"</span><span class="nt">&gt;</span>Joe Coder<span class="nt">&lt;/span&gt;</span>!<span class="nt">&lt;/p&gt;</span></span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Restart Spring Boot, log in, and you should see a welcome message in your chosen locale.</p>
</div>
<div class="imageblock" style="text-align: center">
<div class="content">
<img src="/assets/blog/java-i18n/home-es-d477b92dfd1ff7b25b86da13ebb9fc3fb0a3bafc93e95c1e85c68b8489a170bd.png" alt="Home page in Spanish" width="800">
</div>
</div>
<div class="paragraph">
<p>You gotta admit, this is sah-weet! There&#8217;s something that tells me it&#8217;d be even better if the locale is set from your user attributes in Okta. Let&#8217;s make that happen!</p>
</div>
</div>
<div class="sect2">
<h3 id="use-the-users-locale-from-okta">Use the User&#8217;s Locale from Okta</h3>
<div class="paragraph">
<p>To set the locale from the user&#8217;s information in Okta, create an <code>OidcLocaleResolver</code> class in the same directory as <code>MvcConfigurer</code>.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java"><span id="L1" class="line"><span class="kn">package</span> <span class="n">com</span><span class="o">.</span><span class="na">okta</span><span class="o">.</span><span class="na">spring</span><span class="o">.</span><span class="na">example</span><span class="o">;</span></span>
<span id="L2" class="line"></span>
<span id="L3" class="line"><span class="kn">import</span> <span class="nn">org.slf4j.Logger</span><span class="o">;</span></span>
<span id="L4" class="line"><span class="kn">import</span> <span class="nn">org.slf4j.LoggerFactory</span><span class="o">;</span></span>
<span id="L5" class="line"><span class="kn">import</span> <span class="nn">org.springframework.context.annotation.Configuration</span><span class="o">;</span></span>
<span id="L6" class="line"><span class="kn">import</span> <span class="nn">org.springframework.security.core.context.SecurityContext</span><span class="o">;</span></span>
<span id="L7" class="line"><span class="kn">import</span> <span class="nn">org.springframework.security.core.context.SecurityContextHolder</span><span class="o">;</span></span>
<span id="L8" class="line"><span class="kn">import</span> <span class="nn">org.springframework.security.oauth2.core.oidc.user.OidcUser</span><span class="o">;</span></span>
<span id="L9" class="line"><span class="kn">import</span> <span class="nn">org.springframework.web.servlet.i18n.CookieLocaleResolver</span><span class="o">;</span></span>
<span id="L10" class="line"></span>
<span id="L11" class="line"><span class="kn">import</span> <span class="nn">javax.servlet.http.HttpServletRequest</span><span class="o">;</span></span>
<span id="L12" class="line"><span class="kn">import</span> <span class="nn">java.util.Locale</span><span class="o">;</span></span>
<span id="L13" class="line"></span>
<span id="L14" class="line"><span class="nd">@Configuration</span></span>
<span id="L15" class="line"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">OidcLocaleResolver</span> <span class="kd">extends</span> <span class="n">CookieLocaleResolver</span> <span class="o">{</span></span>
<span id="L16" class="line"> <span class="kd">private</span> <span class="kd">final</span> <span class="n">Logger</span> <span class="n">logger</span> <span class="o">=</span> <span class="n">LoggerFactory</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="n">OidcLocaleResolver</span><span class="o">.</span><span class="na">class</span><span class="o">);</span></span>
<span id="L17" class="line"></span>
<span id="L18" class="line"> <span class="nd">@Override</span></span>
<span id="L19" class="line"> <span class="kd">public</span> <span class="n">Locale</span> <span class="nf">resolveLocale</span><span class="o">(</span><span class="n">HttpServletRequest</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span></span>
<span id="L20" class="line"> <span class="n">SecurityContext</span> <span class="n">securityContext</span> <span class="o">=</span> <span class="n">SecurityContextHolder</span><span class="o">.</span><span class="na">getContext</span><span class="o">();</span></span>
<span id="L21" class="line"> <span class="k">if</span> <span class="o">(</span><span class="n">securityContext</span><span class="o">.</span><span class="na">getAuthentication</span><span class="o">().</span><span class="na">getPrincipal</span><span class="o">()</span> <span class="k">instanceof</span> <span class="n">OidcUser</span><span class="o">)</span> <span class="o">{</span></span>
<span id="L22" class="line"> <span class="n">OidcUser</span> <span class="n">user</span> <span class="o">=</span> <span class="o">(</span><span class="n">OidcUser</span><span class="o">)</span> <span class="n">securityContext</span><span class="o">.</span><span class="na">getAuthentication</span><span class="o">().</span><span class="na">getPrincipal</span><span class="o">();</span></span>
<span id="L23" class="line"> <span class="n">logger</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Setting locale from OidcUser: {}"</span><span class="o">,</span> <span class="n">user</span><span class="o">.</span><span class="na">getLocale</span><span class="o">());</span></span>
<span id="L24" class="line"> <span class="k">return</span> <span class="n">Locale</span><span class="o">.</span><span class="na">forLanguageTag</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">getLocale</span><span class="o">());</span></span>
<span id="L25" class="line"> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span></span>
<span id="L26" class="line"> <span class="k">return</span> <span class="n">request</span><span class="o">.</span><span class="na">getLocale</span><span class="o">();</span></span>
<span id="L27" class="line"> <span class="o">}</span></span>
<span id="L28" class="line"> <span class="o">}</span></span>
<span id="L29" class="line"><span class="o">}</span></span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Then update <code>MvcConfigurer</code> to use this class:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java"><span id="L1" class="line"><span class="nd">@Bean</span></span>
<span id="L2" class="line"><span class="kd">public</span> <span class="n">LocaleResolver</span> <span class="nf">localeResolver</span><span class="o">()</span> <span class="o">{</span></span>
<span id="L3" class="line"> <span class="k">return</span> <span class="k">new</span> <span class="nf">OidcLocaleResolver</span><span class="o">();</span></span>
<span id="L4" class="line"><span class="o">}</span></span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Try it out by restarting, navigating to <code><a href="http://localhost:8080/?lang=es" class="bare">http://localhost:8080/?lang=es</a></code>, and authenticating. You should land back on your app&#8217;s homepage with English (or whatever your user&#8217;s locale is) as the language.</p>
</div>
<div class="imageblock" style="text-align: center">
<div class="content">
<img src="/assets/blog/java-i18n/home-en-6679e2e33c1ac0f98164d1cc4cd2dd048be209af31b7d62a5a91b2c85ea6ac72.png" alt="Home page in English" width="800">
</div>
</div>
<div class="paragraph">
<p>Yeehaw! Feels like Friday, doesn&#8217;t it?! 😃</p>
</div>
</div>
<div class="sect2">
<h3 id="i18n-in-javascript-with-angular-react-and-vue">i18n in JavaScript with Angular, React, and Vue</h3>
<div class="paragraph">
<p>In this post, you saw how to internationalize a basic Java program and a Spring Boot app. We barely scratched the service on how to do i18n in JavaScript. The good news is I have an excellent example of i18n for JavaScript apps.</p>
</div>
<div class="paragraph">
<p><a href="https://www.jhipster.tech">JHipster</a> is powered by Spring Boot and includes localization for many languages on the server and the client. It supports three awesome front-end frameworks: Angular, React, and Vue. It uses the following libraries to lazy-load JSON files with translations on the client. I invite you to check them out if you&#8217;re interested in doing i18n in JavaScript (or TypeScript).</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Angular: <a href="http://www.ngx-translate.com/">ngx-translate</a></p>
</li>
<li>
<p>React: a <a href="https://github.com/jhipster/react-jhipster/blob/master/src/language/translate.tsx">Translate</a> component based off <a href="https://github.com/bloodyowl/react-translate">react-translate</a></p>
</li>
<li>
<p>Vue: <a href="https://kazupon.github.io/vue-i18n/">Vue I18n</a></p>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="internationalize-your-java-apps-today">Internationalize Your Java Apps Today!</h2>
<div class="sectionbody">
<div class="paragraph">
<p>I hope you&#8217;ve enjoyed this whirlwind tour of how to internationalize and localize your Java and Spring Boot applications. If you&#8217;d like to see the completed source code, you can <a href="https://github.com/oktadeveloper/okta-java-i18n-example">find it on GitHub</a>.</p>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
Baeldung&#8217;s <a href="https://www.baeldung.com/spring-boot-internationalization">Guide to Internationalization in Spring Boot</a> was a useful resource when writing this post.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>We like to write about Java and Spring Boot on this here blog. Here are a few of my favorites:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><a href="/blog/2018/09/12/secure-java-ee-rest-api">Build a Java REST API with Java EE and OIDC</a></p>
</li>
<li>
<p><a href="/blog/2018/11/26/spring-boot-2-dot-1-oidc-oauth2-reactive-apis">Spring Boot 2.1: Outstanding OIDC, OAuth 2.0, and Reactive API Support</a></p>
</li>
<li>
<p><a href="/blog/2019/02/19/add-social-login-to-spring-boot">Add Social Login to Your JHipster App</a></p>
</li>
<li>
<p><a href="/blog/2018/05/17/microservices-spring-boot-2-oauth">Build and Secure Microservices with Spring Boot 2.0 and OAuth 2.0</a></p>
</li>
<li>
<p><a href="/blog/2018/03/01/develop-microservices-jhipster-oauth">Develop a Microservices Architecture with OAuth 2.0 and JHipster</a></p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Follow us on your favorite social network { <a href="https://twitter.com/oktadev">Twitter</a>, <a href="https://www.linkedin.com/company/oktadev">LinkedIn</a>, <a href="https://www.facebook.com/oktadevelopers/">Facebook</a>, <a href="https://www.youtube.com/channel/UC5AMiWqFVFxF1q9Ya1FuZ_Q">YouTube</a> } to be notified when we publish awesome content in the future.</p>
</div>
<div class="paragraph">
<p><strong>Changelog:</strong></p>
</div>
<div class="ulist">
<ul>
<li>
<p>Feb 26, 2019: Fixed Spanish translation and simplified <code>Locale</code> resolution in <code>LoginController</code>. Thanks to <a href="https://twitter.com/danfenz/status/1100322287386062849">Daniel Fernández</a> and <a href="https://twitter.com/sam_brannen/status/1100339463329333250">Sam Brannen</a> for their help. You can see the example app changes in <a href="https://github.com/oktadeveloper/okta-java-i18n-example/pull/1">okta-java-i18n-example#1</a>; changes to this post can be viewed in <a href="https://github.com/okta/okta.github.io/pull/2740">okta.github.io#2740</a>.</p>
</li>
</ul>
</div>
</div>
</div>

i18n in Java 11, Spring Boot, and JavaScript