Songge has written about Spring Boot internationalization before, but that time did not talk about the source code, this time let’s focus on the source code to in-depth understanding of this problem.

Internationalization, also called i18n, why is it called that? Because English is internationalization, there are 18 letters between I and n, so it’s called i18n. If our application is internationalized, it can be easily switched between different language environments. The most common one is the switch between Chinese and English. The function of internationalization is also quite common.

1.SpringMVC internationalization configuration

Or say the usage first, then say the source code, so we are not easy to make confusion. Let’s start with how internationalization is handled in SSM.

First of all, we may have two requirements for internationalization:

  • Internationalize the page during rendering (this is done with the Spring tag)
  • Gets the message after the internationalization match in the interface

These are roughly the two scenarios above. Next Songge through a simple usage to demonstrate with you the specific gameplay.

Language_en_us. properties and language_zh-cn. properties are created in the Resources directory of the project, as shown in the figure below:

The contents are as follows:

Language_en_US. Properties:

login.username=Username
login.password=Password

Language_zh – CN. The properties:

Login. username= username login.password= user password

These two correspond to the English-Chinese environment respectively. Write configuration files, still need in for SpringMVC container provides a ResourceBundleMessageSource instance to load these two examples, are as follows:

<bean class="org.springframework.context.support.ResourceBundleMessageSource" id="messageSource">
    <property name="basename" value="language"/>
    <property name="defaultEncoding" value="UTF-8"/>
</bean>

Here the filename language and the default encoding format are configured.

Next we create a new login.jsp file like this:

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <%@ page contentType="text/html; charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <spring:message code="login.username"/> <input type="text"> <br> <spring:message code="login.password"/> <input type="text"> <br> </body> </html>

In this file, we refer to variables through the spring:message tag, which selects the appropriate language file based on the current situation.

Next we provide a controller for login.jsp:

@Controller public class LoginController { @Autowired MessageSource messageSource; @GetMapping("/login") public String login() { String username = messageSource.getMessage("login.username", null, LocaleContextHolder.getLocale()); String password = messageSource.getMessage("login.password", null, LocaleContextHolder.getLocale()); System.out.println("username = " + username); System.out.println("password = " + password); return "login"; }}

Simply return to the Login view in the controller.

I’ve also injected a MessageSource object, mainly to show you how to retrieve the internationalized language text in the processor.

After the configuration is complete, start the project for testing.

By default, the system determines the current locale according to the Accept-Language field in the request header, which is automatically sent by the browser. For convenience of testing, we can use PostMan to test, and then manually set the Accept_Language field.

First test the Chinese environment:

Then test the English environment:

All right, perfect! At the same time observe the IDEA console, can also print out the language text correctly.

This is based on the above AcceptHeaderLocaleResolver to parse out the current regional and language.

Sometimes we want the locale to be passed directly via the request parameter rather than via the request header, which we can do with SessionLocalereSolver or CookieLocalereSolver.

So let’s start with SessionLocaleSolver.

First, provide an instance of SessionLocaleSolver in the SpringMVC configuration file and configure an interceptor as follows:

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
            <property name="paramName" value="locale"/>
        </bean>
    </mvc:interceptor>
</mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.SessionLocaleResolver" id="localeResolver">
</bean>

SessionLocalereSolver is responsible for region resolution, so there’s nothing to say about that. The Interceptor LocalEChangeInterceptor is responsible for parameter resolution. When we configure the Interceptor, we set the parameter locale (the default is this), which means that we can pass the current environment information through the locale parameter in the future.

Once configured, let’s access the Login controller as follows:

You can control the locale directly by using the LocalEchangeInterceptor parameter, which is automatically resolved in the LocalEchangeInterceptor.

If you do not want to configure the LocalEchangeInterceptor interceptor, you can also manually resolve the Locale parameter and set the Locale as follows:

@Controller public class LoginController { @Autowired MessageSource messageSource; @GetMapping("/login") public String login(String locale,HttpSession session) { if ("zh-CN".equals(locale)) { session.setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, new Locale("zh", "CN")); } else if ("en-US".equals(locale)) { session.setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, new Locale("en", "US")); } String username = messageSource.getMessage("login.username", null, LocaleContextHolder.getLocale()); String password = messageSource.getMessage("login.password", null, LocaleContextHolder.getLocale()); System.out.println("username = " + username); System.out.println("password = " + password); return "login"; }}

The functions of SessionLocalereSolver can also be implemented by CookieLocalereSolver. The difference is that the former stores the parsed region information in the session, while the latter stores it in the Cookie. So if you store it in your session, as long as the session doesn’t change, then you don’t have to pass it again in the future, if the Cookie doesn’t change.

To use CookieLocalereResolver, simply provide an instance of CookieLocalereResolver directly in SpringMVC, as follows:

<bean class="org.springframework.web.servlet.i18n.CookieLocaleResolver" id="localeResolver"/>

Note that you also need to use the LocalEchangeInterceptor interceptor. If you do not use this interceptor, you will need to manually parse and configure the locale yourself, as follows:

@GetMapping("/login3")
public String login3(String locale, HttpServletRequest req, HttpServletResponse resp) {
    CookieLocaleResolver resolver = new CookieLocaleResolver();
    if ("zh-CN".equals(locale)) {
        resolver.setLocale(req, resp, new Locale("zh", "CN"));
    } else if ("en-US".equals(locale)) {
        resolver.setLocale(req, resp, new Locale("en", "US"));
    }
    String username = messageSource.getMessage("login.username", null, LocaleContextHolder.getLocale());
    String password = messageSource.getMessage("login.password", null, LocaleContextHolder.getLocale());
    System.out.println("username = " + username);
    System.out.println("password = " + password);
    return "login";
}

After the configuration is complete, start the project and test it. This time, test it in the same way as the SessionLocaleSolver test. I won’t say more about it.

In addition to all of these localereSolvers, there’s also a fixedlocalereSolver, which I won’t talk about because it’s a little bit rare.

2.Spring Boot internationalization configuration

2.1 Basic use

Spring, along the lines of the Boot and Spring support for the internationalization, the default is done through AcceptHeaderLocaleResolver parser, the parser, The default is to use the Accept-Language field of the request header to determine the context of the current request and to provide an appropriate response.

So in Spring Boot to do internationalization, we can do this part without configuration, directly started.

Start by creating a normal Spring Boot project and adding a Web dependency. After the project is successfully created, the default internationalization configuration file is placed in the resources directory, so we directly create four test files in this directory, as follows:

  • Our message file is directly created in the resources directory. When the IDEA is presented, there will be an extra Resource Bundle. You don’t have to care about this, do not manually create this directory.
  • Messages.properties is the default configuration, the others are for different language Settings, en_US for English (US), zh_CN for Simplified Chinese, and zh_TW for Traditional Chinese (there is a complete table of language abbreviations in the appendix at the end of this article).

After the four files are created, we can leave the first default blank and fill in the following contents for the other three files:

messages_zh_CN.properties

User. name= A Little Rain in Jiangnan

messages_zh_TW.properties

User. name= A little rain south of the Yangtze River

messages_en_US.properties

user.name=javaboy

After the configuration is complete, we can start using it directly. Where values are needed, inject the MessageSource instance directly.

The MessageSource that needs to be configured in Spring is not configured now, and Spring Boot will pass
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfigurationAutomatically configures a MessageSource instance for us.

Create a HelloController with the following contents:

@RestController public class HelloController { @Autowired MessageSource messageSource; @GetMapping("/hello") public String hello() { return messageSource.getMessage("user.name", null, LocaleContextHolder.getLocale()); }}

In HelloController we can directly inject an instance of MessageSource and then call the getMessage method in that instance to get the value of the variable, the first argument is to get the key of the variable, the second argument is if there is a placeholder in the value, You can pass in an argument from here, and the third argument passes in an instance of Locale, which corresponds to the current Locale.

Then we can call the interface directly.

By default, the current environment is configured with the Accept-Language of the request header when the interface is invoked. Here I test it with PostMan, and the results are as follows:

As you can see, I set the Accept-Language to ZH-CN in the request header, so I get simplified Chinese. If I set zh-tw, I will get traditional Chinese:

Is it Easy?

2.2 Custom Switch

Some friends think it is not convenient to switch the parameters in the request header, then you can also customize the parsing method. For example, parameters can be placed on the address bar as normal parameters. Our requirements can be realized through the following configuration.

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor(); interceptor.setParamName("lang"); registry.addInterceptor(interceptor); } @Bean LocaleResolver localeResolver() { SessionLocaleResolver localeResolver = new SessionLocaleResolver(); localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE); return localeResolver; }}

In this configuration, we first provide a SessionLocaleResolver as an example, the instance will replace the default AcceptHeaderLocaleResolver, Unlike AcceptHeaderLocaleResolver by request header to determine the current environmental information, SessionLocaleResolver will save the Locale for the client to the HttpSession object, It can also be modified (this means that the current environment information can be remembered once the front-end sends it to the browser, and the browser does not have to tell the server the current environment information again as long as the session is valid).

We also configured an interceptor that intercepts a request parameter with a key of lang (or locale if not configured) that specifies the current environment information.

OK, after the configuration is complete, start the project, access as follows:

We specify the current environment information by adding lang to the request. This specification is only required once, which means that the next request can be made without the lang parameter and the server is already aware of the current environment with the session unchanged.

Cookie Rock Aleresolver is also used in a similar way.

2.3 Other customizations

By default, our configuration files are in the resources directory, but you can customize them if you want. For example, they are in the resources/i18n directory:

However, with this definition, the system does not know where to load the configuration file, and additional configuration is required in application.properties (note that this is a relative path) :

spring.messages.basename=i18n/messages

In addition, there are some encoding format configuration, the content is as follows:

spring.messages.cache-duration=3600
spring.messages.encoding=UTF-8
spring.messages.fallback-to-system-locale=true

Spring.messages.cache-duration represents the cache expiration time of the messages file, and the cache will remain valid if not configured.

The spring.messages.fallback-to-system-locale attribute is a little bit magical, and there is no clear answer on the Internet, until I read the source code for a while to see the clue.

The function of the attribute in the org. Springframework. Context. Support. AbstractResourceBasedMessageSource# getDefaultLocale effective method:

protected Locale getDefaultLocale() { if (this.defaultLocale ! = null) { return this.defaultLocale; } if (this.fallbackToSystemLocale) { return Locale.getDefault(); } return null; }

If this property is true, the resource file for the current system will be found by default. Otherwise, null will be returned, and the default messages.properties file will be called.

3.LocaleResolver

The main component involved in internationalization is LocaleSolver, which is an open interface with four implementations officially provided by default. What environment is currently being used is resolved primarily through the LocalereSolver.

LocaleResolver

public interface LocaleResolver {
    Locale resolveLocale(HttpServletRequest request);
    void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);

}

Here are two methods:

  1. ResolveLocale: resolves the Locale object based on the current request.
  2. Set up the Locale object.

Let’s look at the inheritance of a LocaleSolver:

There are a few abstract classes in the middle, but ultimately only four are responsible for implementing them:

  • AcceptHeaderLocaleResolver: according to the Accept request header – Language field to determine the current regional Language, etc.
  • SessionLocalereSolver: Locale objects are stored in the Session as long as the Session does not change.
  • CookieLocaleResolver: The Locale object is stored in a Cookie as long as the Session remains the same.
  • FixedLocaleSolver: Provides a Locale object directly during configuration and cannot be modified later.

Let’s take a look at each of these classes.

3.1 AcceptHeaderLocaleResolver

AcceptHeaderLocaleResolver realized LocaleResolver interface directly, let’s take a look at its resolveLocale method:

@Override public Locale resolveLocale(HttpServletRequest request) { Locale defaultLocale = getDefaultLocale(); if (defaultLocale ! = null && request.getHeader("Accept-Language") == null) { return defaultLocale; } Locale requestLocale = request.getLocale(); List<Locale> supportedLocales = getSupportedLocales(); if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) { return requestLocale; } Locale supportedLocale = findSupportedLocale(request, supportedLocales); if (supportedLocale ! = null) { return supportedLocale; } return (defaultLocale ! = null ? defaultLocale : requestLocale); }
  1. The first step is to get the default Locale object.
  2. If a default Locale object exists and is not set in the request headerAccept-LanguageField directly returns the default Locale.
  3. From the request object to retrieve the current Locale, then query support supportedLocales, if supportedLocales or supportedLocales contains requestLocale, The RequestLocale is returned directly.
  4. If there is still no match before, the locales collection is extracted from the request, and then compared with the supported locales, and the locale that is successfully matched is selected to return.
  5. If none of the above returns, it determines whether defaultLocale is empty, if not, it returns defaultLocale, otherwise it returns defaultLocale.

Also look at its setLocale method, which throws an exception directly, meaning that the Locale is not allowed to be modified by handling the request header.

@Override
public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) {
    throw new UnsupportedOperationException(
            "Cannot change HTTP accept header - use a different locale resolution strategy");
}

3.2 SessionLocaleResolver

Much SessionLocaleResolver implementation of an abstract class AbstractLocaleContextResolver, increased support for TimeZone AbstractLocaleContextResolver, Let’s look at AbstractLocaleContextResolver:

public abstract class AbstractLocaleContextResolver extends AbstractLocaleResolver implements LocaleContextResolver { @Nullable private TimeZone defaultTimeZone; public void setDefaultTimeZone(@Nullable TimeZone defaultTimeZone) { this.defaultTimeZone = defaultTimeZone; } @Nullable public TimeZone getDefaultTimeZone() { return this.defaultTimeZone; } @Override public Locale resolveLocale(HttpServletRequest request) { Locale locale = resolveLocaleContext(request).getLocale(); return (locale ! = null ? locale : request.getLocale()); } @Override public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) { setLocaleContext(request, response, (locale ! = null ? new SimpleLocaleContext(locale) : null)); }}

As you can see, there is an added TimeZone property. Resolving the Locale out of the request calls the resolveLocalContext method, which is implemented in the subclass, and calls the setLocalContext method to set the Locale, which is also implemented in the subclass.

Let’s look at its subclass SessionLocaleSolver:

@Override
public Locale resolveLocale(HttpServletRequest request) {
    Locale locale = (Locale) WebUtils.getSessionAttribute(request, this.localeAttributeName);
    if (locale == null) {
        locale = determineDefaultLocale(request);
    }
    return locale;
}

Directly from the Session for the Locale, the default attribute name is SessionLocaleResolver class. The getName () + “Locale”, if there is no Locale information in the Session, The DetermineDefaultLocale method is called to load the Locale, and it will find the DefaultLocale first, and if the DefaultLocale is not null, it will return the DefaultLocale. Otherwise, get the Locale return from the request.

The setLocalContext method saves the parsed Locale.

@Override public void setLocaleContext(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable LocaleContext localeContext) { Locale locale = null; TimeZone timeZone = null; if (localeContext ! = null) { locale = localeContext.getLocale(); if (localeContext instanceof TimeZoneAwareLocaleContext) { timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone(); } } WebUtils.setSessionAttribute(request, this.localeAttributeName, locale); WebUtils.setSessionAttribute(request, this.timeZoneAttributeName, timeZone); }

Save it in Session. As you can see, this way of saving is basically the same as the self-saving code we demonstrated earlier, but it leads to the same direction.

3.3 FixedLocaleResolver

The FixedLocaleSolver has three constructors, and no matter which one is called, the default Locale is configured:

public FixedLocaleResolver() {
    setDefaultLocale(Locale.getDefault());
}
public FixedLocaleResolver(Locale locale) {
    setDefaultLocale(locale);
}
public FixedLocaleResolver(Locale locale, TimeZone timeZone) {
    setDefaultLocale(locale);
    setDefaultTimeZone(timeZone);
}

You can either pass the Locale in yourself or call the locale.getDefault () method to get the default Locale.

Consider the resolveLocale method:

@Override
public Locale resolveLocale(HttpServletRequest request) {
    Locale locale = getDefaultLocale();
    if (locale == null) {
        locale = Locale.getDefault();
    }
    return locale;
}

I don’t need to explain this one.

Note that its setLocalContext method throws the exception directly, which means that the Locale cannot be modified at a later stage.

@Override
public void setLocaleContext( HttpServletRequest request, @Nullable HttpServletResponse response,
        @Nullable LocaleContext localeContext) {
    throw new UnsupportedOperationException("Cannot change fixed locale - use a different locale resolution strategy");
}

3.4 CookieLocaleResolver

CookieLocalereSolver is similar to SessionLocalereSolver, except that the storage medium has become a Cookie and everything else is pretty much the same, so I won’t repeat it.

4. The appendix

I collected a list of language abbreviations to share with you:

language Referred to as”
Simplified Chinese (China) zh_CN
Traditional Chinese (Taiwan, China) zh_TW
Traditional Chinese (Hong Kong, China) zh_HK
English (Hong Kong, China) en_HK
English (USA) en_US
English (UK) en_GB
English (Global) en_WW
English (Canada) en_CA
English (Australia) en_AU
English (Ireland) en_IE
English (Finland) en_FI
Finnish (Finland) fi_FI
English (Denmark) en_DK
Danish (Denmark) da_DK
English (Israel) en_IL
Hebrew (Israel) he_IL
English (South Africa) en_ZA
English (India) en_IN
English (Norway) en_NO
English (Singapore) en_SG
English (New Zealand) en_NZ
English (Indonesia) en_ID
English (Philippines) en_PH
English (Thailand) en_TH
English (Malaysia) en_MY
English (Arabic) en_XA
Korean (Korean) ko_KR
Japanese (Japan) ja_JP
Dutch (Dutch) nl_NL
Dutch (Belgium) nl_BE
Portuguese (Portuguese) pt_PT
Portuguese (Brazil) pt_BR
French (France) fr_FR
French (Luxembourg) fr_LU
French (Switzerland) fr_CH
French (Belgium) fr_BE
French (Canada) fr_CA
Spanish (Latin America) es_LA
Spanish (Spanish) es_ES
Spanish (Argentina) es_AR
Spanish (United States) es_US
Spanish (Mexico) es_MX
Spanish (Colombia) es_CO
Spanish (Puerto Rico) es_PR
German (Germany) de_DE
German (Austria) de_AT
German (Switzerland) de_CH
Russian (Russia) ru_RU
Italian (Italian) it_IT
Greek (Greek) el_GR
Norse (Norway) no_NO
Hungarian (Hungarian) hu_HU
Turkish (Turkish) tr_TR
Czech (Czech Republic) cs_CZ
slovenian sl_SL
Polish (Polish) pl_PL
Swedish (Swedish) sv_SE
Spanish (Chile) es_CL

5. Summary

In this article, we will discuss the internationalization of SpringMVC and LocalereSolver. We will discuss the internationalization of SpringMVC and LocalereSolver.