hand implementation of internationalization support example development
Internationalization support should be common for app development partners; as a java back-end partner, generally speaking, there are not many opportunities to contact internationalization. After all, there are not many companies that have business overseas.
SpringBoot provides internationalization support, and there are related tutorials on the Internet. However, when I actually experienced it, I found that it was not as smooth as expected; this article will introduce how SpringBoot supports nationalization, and some precautions in the support process
<!-- more -->
I. Project environment
1. Project dependencies
This project is developed with the help of SpringBoot 2.2.1.RELEASE
+ maven 3.5.3
+ IDEA
Open a web service for testing
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
2. Configuration file
In the configuration file, specify the internationalization parameters and thmeleaf configuration information
application.yml
spring:
messages:
basename: i18n/messages/messages
encoding: UTF-8
fallbackToSystemLocale: false
thymeleaf:
mode: HTML
encoding: UTF-8
servlet:
content-type: text/html
cache: false
3. Internationalized information files
The above configuration spring.messages.basename
specifies the directory and prefix of the international configuration file, the value is i18n/messages/messages
So in the resource directory, create a new file i18n/messages
, the internationalized file name is messages-xxx.properties
, the project result is as follows
Corresponding information such as simplified Chinese messages_zh_CN.properties
200=成功
500=內部异常
name=用户名
pwd=密码
English messages_en_US.properties
200=success
500=unexpected exception
name=user name
pwd=password
Traditional messages_zh_TW.properties
200=成功
500=內部異常
name=用戶名
pwd=密碼
Description
Note spring.messages.basename
this configuration value of international documents directory + file name prefix, such as the above, if the last layer of less
messages
, you will be prompted to get any configuration
Secondly, in IDEA, after selecting the nationalized file, click on Resource Bundle
below, you can enter a more friendly edit box as shown in the figure above, and support to modify information in multiple languages at once
II. Internationalization Support
The previous is the basic configuration of internationalization, so how to get the value of different languages according to the key in the previous configuration?
1. MessageSource
In SpringBoot, MessageSource
is mainly used to obtain value information in different languages.
Like a most basic package
public class MsgUtil {
private static MessageSource messageSource;
public static void inti(MessageSource messageSource) {
MsgUtil.messageSource = messageSource;
}
/**
* 获取单个国际化翻译值
*/
public static String get(String msgKey) {
try {
return messageSource.getMessage(msgKey, null, LocaleContextHolder.getLocale());
} catch (Exception e) {
return msgKey;
}
}
}
2. Test the demo
Next, write a basic test demo, modify LocalContextHolder
according to the passed parameters, so as to realize the switch of different languages
@Controller
@SpringBootApplication
public class Application {
public Application(MessageSource messageSource) {
MsgUtil.inti(messageSource);
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
@Data
@Accessors(chain = true)
public static class RspWrapper<T> {
private int code;
private String msg;
private T data;
}
@GetMapping(path = "change")
@ResponseBody
public String changeLocal(String language) {
String[] s = language.split("_");
LocaleContextHolder.setLocale(new Locale(s[0], s[1]));
RspWrapper res = new RspWrapper<>().setCode(200).setMsg(MsgUtil.get("200")).setData(true);
return JSON.toJSONString(res);
}
}
The demo is as follows
3. Sub-thread support
Although the language can be switched according to the request parameters above, there is a problem. If internationalization support is performed in the child thread, it will not take effect.
@GetMapping(path = "change2")
@ResponseBody
public String changeLocal(String language) {
String[] s = language.split("_");
LocaleContextHolder.setLocale(new Locale(s[0], s[1]));
RspWrapper res = new RspWrapper<>().setCode(200).setMsg(MsgUtil.get("200")).setData(true);
return JSON.toJSONString(res);
}
As shown in the figure below, even if the language is modified, the return is the default Chinese
The solution to this is to specify the second inheritable parameter as true when setting Locale
@GetMapping(path = "change3")
@ResponseBody
public String changeLocal(String language) {
String[] s = language.split("_");
LocaleContextHolder.setLocale(new Locale(s[0], s[1]));
RspWrapper res = new RspWrapper<>().setCode(200).setMsg(MsgUtil.get("200")).setData(true);
return JSON.toJSONString(res);
}
4. Cookies to cache internationalized information
Although the above supports the setting of internationalization based on the parameters language=zh_CN
, it is necessary to bring this parameter 060cd72d6027fe every time the parameters are passed, and we also need to parse the request parameters ourselves. We can consider using interceptors to achieve unified Local settings
This interceptor can be written in accordance with the above method, of course, it is more recommended to directly use the packaged one
@Configuration
public class AutoConfig implements WebMvcConfigurer {
/**
* 这个如果不存在,则会抛异常: nested exception is java.lang.UnsupportedOperationException: Cannot change HTTP accept header - use a different locale resolution strategy
*
* @return
*/
@Bean
public LocaleResolver localeResolver() {
// 也可以换成 SessionLocalResolver, 区别在于国际化的应用范围
CookieLocaleResolver localeResolver = new CookieLocaleResolver();
localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
return localeResolver;
}
/**
* 根据请求参数,来设置本地化
*
* @return
*/
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
// Defaults to "locale" if not set
localeChangeInterceptor.setParamName("language");
return localeChangeInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry interceptorRegistry) {
interceptorRegistry.addInterceptor(localeChangeInterceptor());
}
}
Please pay attention to the above localResolver
, when we do not register this bean, the operation will throw an exception nested exception is java.lang.UnsupportedOperationException: Cannot change HTTP accept header - use a different locale resolution
In the above example, CookieLocaleResolver
is used, so the language information will be cached in the cookie. Once the modification is made, the subsequent changes will take effect.
The test is as follows
@GetMapping(path = "say")
@ResponseBody
public String say(String name) {
RspWrapper res = new RspWrapper<>().setCode(200).setMsg(MsgUtil.get("200")).setData(MsgUtil.get("name") + ":" + name);
return JSON.toJSONString(res);
}
@GetMapping(path = "say2")
@ResponseBody
public String say2(String name) {
RspWrapper res = new RspWrapper<>().setCode(200).setMsg(MsgUtil.get("200")).setData(MsgUtil.get("name") + ":" + name);
return JSON.toJSONString(res);
}
The language is mainly set in one place, and subsequent visits without language parameters will reuse the previously set language, which makes it more concise to use
5. Internationalization of page elements
The above is that the returned json string supports internationalization. Another scenario is the page we return. We hope that the rendered data can also achieve internationalization support.
There is no difficulty in achieving this on the basis of the above
In the resource directory, create a new directory templates
, create a new template file index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="YiHui"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>一灰灰blog 国际化测试页面</title>
</head>
<body>
<div>
<div class="title">hello world!</div>
<br/>
<div class="content" th:text="'name: ' + ${name}">默认用户名</div>
<br/>
<div class="sign" th:text="'pwd: ' + ${pwd}">默认密码</div>
<br/>
</div>
</body>
</html>
Corresponding controller
@GetMapping(path = {"", "/", "/index"})
public String index(Model model) {
model.addAttribute("name", MsgUtil.get("name"));
model.addAttribute("pwd", MsgUtil.get("pwd"));
return "index";
}
Although the above implementation of nationalization support, it does not look elegant. Is it necessary to escape the back-end interface? Isn't there an easier way?
Themeleaf provides a simpler way to support, just change the above $ to #
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="YiHui"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>一灰灰blog 国际化测试页面</title>
</head>
<body>
<div>
<div class="title">hello world!</div>
<br/>
<div class="content" th:text="'name: ' + #{name}">默认用户名</div>
<br/>
<div class="sign" th:text="'pwd: ' + #{pwd}">默认密码</div>
<br/>
<div class="content" th:text="'200: ' + #{200}">200</div>
<br/>
<div class="content" th:text="'500: ' + #{500}">500</div>
</div>
</body>
</html>
Corresponding rest
@GetMapping(path = "show")
public String show() {
return "show";
}
6. Matters needing attention
In the process of realizing internationalization, I encountered the following problems, hereby record
6.1 Unable to obtain configuration information
Using messageSource.getMessage(msgKey, null, LocaleContextHolder.getLocale())
query the configuration information, the result shows org.springframework.context.NoSuchMessageException: No message found under code '200' for locale 'en_US'.
When the above problem occurs, of course, first determine whether this parameter is really configured, and secondly, confirm spring.messages.basename
is accurate, and the corresponding value is the directory + language prefix
- If my configuration file is
i18n/messages/messages_en_US.properties
, then the value should bei18n/messages/messages
6.2 Chinese garbled problem
- Set code
spring.messages.encoding=utf-8
If you find that the above setting still does not take effect, then consider whether the configuration file is utf-8 encoding
6.3 Support internationalization upon request
LocaleChangeInterceptor
needs to be added to realize the parsing of the locale according to the request parameters
Secondly, you need to register LocaleResolver
. For example, use CookieLocaleResolver
demo to save the internationalization information (if you don’t set it, it will throw an exception)
II. Other
0. Project
- Engineering: https://github.com/liuyueyi/spring-boot-demo
- Project source code: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/150-i18n
1. A Grey Blog
It is not as good as the letter. The above content is purely a family statement. Due to limited personal ability, it is inevitable that there will be omissions and errors. If you find a bug or have better suggestions, criticisms and corrections are welcome, and I am grateful.
The following is a gray personal blog, which records all the blog posts in study and work. Welcome everyone to visit
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。