Spring参数国际化

spring实现

先上代码结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
➜  i18n-demo tree     
.
├── pom.xml
└── src
├── main
│   ├── java
│   │   └── cn
│   │   └── idea360
│   │   └── i18n
│   │   ├── I18nDemoApplication.java
│   │   ├── config
│   │   │   └── MvcConfig.java
│   │   └── web
│   │   └── HelloController.java
│   └── resources
│   ├── application.properties
│   ├── i18n
│   │   ├── messages.properties
│   │   ├── messages_en_US.properties
│   │   └── messages_zh_CN.properties
│   ├── static
│   └── templates
└── test
└── java
└── cn
└── idea360
└── i18n
└── I18nDemoApplicationTests.java

messages.properties可以为空, 但不能没有

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.idea360</groupId>
<artifactId>i18n-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>i18n-demo</name>
<description>i18n-demo</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</project>

application.properties

1
2
3
spring.messages.basename=i18n/messages
spring.messages.cache-duration=1
spring.messages.encoding=UTF-8

properties文件注意需要 UTF-8 编码, 避免中文乱码

i18n配置文件

messages_en_US.properties

1
user.name=idea360.cn

messages_zh_CN.properties

1
user.name=当我遇上你

默认国际化

默认情况下会根据请求头中的 Accept-Language 来切换locale, 具体实现参见 AcceptHeaderLocaleResolver.

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class LocaleConfiguration {

@Bean
public LocaleResolver localeResolver() {
// spring默认国际化解决方案
AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver();
resolver.setDefaultLocale(Locale.US); // 设置默认 Locale
return resolver;
}
}

自定义国际化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package cn.idea360.i18n.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
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.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

import java.util.Locale;

/**
* @author cuishiying
*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {

@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver();
slr.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
return slr;
}

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

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

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package cn.idea360.i18n.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
* @author cuishiying
*/
@RestController
public class HelloController {

@Resource
private MessageSource messageSource;

/**
* 默认中文, 接口可以不传参数lang, session按上次设置的lang执行
* http://localhost:8080/hello
* http://localhost:8080/hello?lang=en_US
* http://localhost:8080/hello?lang=zh_CN
* @return
*/
@GetMapping("/hello")
public String hello() {
return messageSource.getMessage("user.name", null, LocaleContextHolder.getLocale());
}
}

其他国际化测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package cn.idea360.i18n;

import org.junit.jupiter.api.Test;

import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Date;
import java.util.Locale;
import java.util.ResourceBundle;

/**
* 国际化 {@link <a href="https://github.com/labulakalia/ibm_bak/blob/main/ibm_articles/SpringBoot%E6%9E%B6%E6%9E%84%E4%B8%AD%E7%9A%84%E5%9B%BD%E9%99%85%E5%8C%96%E6%94%AF%E6%8C%81%E5%AE%9E%E8%B7%B5.md">...</a>}
* @author cuishiying
* @date 2024-02-27
*/
public class I18nTest {

/**
* 显示名字
*/
@Test
void displayName_test() {
Locale locale=new Locale("en","US");
System.out.println(locale.getDisplayName());
System.out.println(locale.getDisplayName(Locale.US));
}

/**
* 数字格式化
*/
@Test
void numberFormat_test() {
Locale localeEn = new Locale("en", "US");
Locale localeZh = new Locale("zh", "CN");
NumberFormat formatEn = NumberFormat.getNumberInstance(localeEn);
NumberFormat formatZh = NumberFormat.getNumberInstance(localeZh);
double num = 123456.78;
System.out.println(formatEn.format(num));
System.out.println(formatZh.format(num));
}

/**
* 货币格式化
*/
@Test
void currencyFormat_test() {
Locale localeEn = new Locale("en", "US");
Locale localeZh = new Locale("zh", "CN");
NumberFormat formatEn = NumberFormat.getCurrencyInstance(localeEn);
NumberFormat formatZh = NumberFormat.getCurrencyInstance(localeZh);
double num = 123456.78;
System.out.println(formatEn.format(num));
System.out.println(formatZh.format(num));
}

/**
* 百分比格式化
*/
@Test
void percentFormat_test() {
Locale localeEn = new Locale("en", "US");
Locale localeZh = new Locale("zh", "CN");
NumberFormat formatEn = NumberFormat.getPercentInstance(localeEn);
NumberFormat formatZh = NumberFormat.getPercentInstance(localeZh);
double num = 0.2;
System.out.println(formatEn.format(num));
System.out.println(formatZh.format(num));
}

/**
* 多语言
*/
@Test
void messageFormat_test() {
Locale localeEn = new Locale("en", "US");
Locale localeZh = new Locale("zh", "CN");
ResourceBundle enResource = ResourceBundle.getBundle("i18n/messages", localeEn);
MessageFormat enFormat = new MessageFormat(enResource.getString("user.name"));
ResourceBundle zhResource = ResourceBundle.getBundle("i18n/messages", localeZh);
MessageFormat zhFormat = new MessageFormat(zhResource.getString("user.name"));
System.out.println(enFormat.format(null));
System.out.println(zhFormat.format(null));
}

/**
* Date格式化
*/
@Test
void dateFormat_test() {
Date date = new Date();
DateFormat df1 = DateFormat.getDateInstance(DateFormat.LONG, new Locale("en", "US"));
DateFormat df2 = DateFormat.getDateInstance(DateFormat.LONG, new Locale("zh", "CN"));
System.out.println(df1.format(date));
System.out.println(df2.format(date));
}

/**
* LocalDateTime格式化
*/
@Test
void localDateTimeFormat_test() {
LocalDateTime localDateTime = LocalDateTime.now();
DateTimeFormatter df1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.US);
DateTimeFormatter df2 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.CHINA);
System.out.println(df1.format(localDateTime));
System.out.println(df2.format(localDateTime));
}

/**
* ZonedDateTime格式化
*/
@Test
void zonedDateTimeFormat_test() {
ZonedDateTime zonedDateTime = ZonedDateTime.now();
DateTimeFormatter df1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG).withLocale(Locale.US);
DateTimeFormatter df2 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG).withLocale(Locale.CHINA);
System.out.println(df1.format(zonedDateTime));
System.out.println(df2.format(zonedDateTime));
}
}

自定义简单轮子

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.idea360</groupId>
<artifactId>i18n-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>i18n-demo</name>
<description>i18n-demo</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>

</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>cn.idea360.i18ndemo.I18nDemoApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>

resources

resources/i18n 下分别创建配置文件

  • message_en_US.properties
1
user.title=china
  • message_zh_CN.properties
1
user.title=中国

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package cn.idea360.i18ndemo.web;

import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Properties;

/**
* @author cuishiying
*/
@RestController
@RequestMapping
public class I18nController {

/**
* http://127.0.0.1:8080/locale?lang=en_US
* http://127.0.0.1:8080/locale?lang=zh_CN
*/
@GetMapping("/locale")
public Object getLocale(@RequestParam("lang") String lang) throws Exception {
// 默认配置文件
InputStream inputStream = I18nController.class.getResourceAsStream("/application.properties");
Properties properties = new Properties();
properties.load(inputStream);
properties.forEach((k, v) -> System.out.println(k + "=" + v));

// 国际化配置
String locale = String.format("/i18n/message_%s.properties", lang);
List<String> lines = IOUtils.readLines(I18nController.class.getResourceAsStream(locale), StandardCharsets.
UTF_8);
lines.forEach(System.out::println);
return lines;
}
}