Spring 中的国际化(i18n)属于进阶但非常实用的特性,尤其在多语言后台管理系统、前后端分离项目、企业级应用中几乎是标配。下面系统性地讲一下 Spring Boot 中最常用、最地道的国际化实现方式(2025 年最新最佳实践)。
1. 核心组件总览
| 组件 | 作用 | 常用实现类 |
|---|---|---|
MessageSource | 读取国际化资源文件的核心接口 | ResourceBundleMessageSource(最常用)ReloadableResourceBundleMessageSource(支持热加载) |
LocaleResolver | 决定当前用户使用哪个 Locale | SessionLocaleResolver(Session 级别)CookieLocaleResolver(Cookie 级别)AcceptHeaderLocaleResolver(仅根据请求头,常见于前后端分离)FixedLocaleResolver(固定) |
LocaleChangeInterceptor | 通过请求参数切换语言(如 ?lang=zh_CN) | 必须注册到 Spring MVC 中 |
Validator + MessageCodesResolver | 校验错误消息国际化 | DefaultMessageCodesResolver |
| Thymeleaf / Vue / React 等视图层 | 前端取消息 | #{} 或 [[#{msg.key}]] 或 i18n.t() |
2. 推荐的配置文件方式(Spring Boot 3.x + Spring 6)
1. 目录结构(最标准)
src/main/resources/
├── i18n/
│ ├── messages.properties # 默认(通常是 en_US)
│ ├── messages_zh_CN.properties # 简体中文
│ ├── messages_en_US.properties # 英文
│ ├── messages_ja_JP.properties # 日语
│ └── validation/ # 校验专用(可选)
│ ├── ValidationMessages.properties
│ └── ValidationMessages_zh_CN.properties
2. application.yml 配置(推荐方式)
spring:
messages:
basename: i18n/messages,i18n/validation/ValidationMessages # 多个用逗号分隔
encoding: UTF-8
fallback-to-system-locale: false # 不回退系统语言
use-code-as-default-message: false # 找不到 key 时不直接返回 key
3. Java 配置(更灵活,支持热加载、生产推荐)
@Configuration
public class I18nConfig {
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource =
new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:i18n/messages");
messageSource.setDefaultEncoding("UTF-8");
// 生产环境建议 1-1 永不刷新,开发环境可以设 1 秒热加载
messageSource.setCacheSeconds(3600);
messageSource.setFallbackToSystemLocale(false);
messageSource.setUseCodeAsDefaultMessage(false);
return messageSource;
}
@Bean
public LocaleResolver localeResolver() {
// 推荐:Cookie 方式,用户切换后长期有效
CookieLocaleResolver resolver = new CookieLocaleResolver("lang");
resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE); // 或者 Locale.US
resolver.setCookieMaxAge(Duration.ofDays(365));
resolver.setCookiePath("/");
return resolver;
// 前后端分离纯 API 项目常用 AcceptHeaderLocaleResolver
// return new AcceptHeaderLocaleResolver();
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
interceptor.setParamName("lang"); // ?lang=zh_CN 或 ?lang=en_US
return interceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
}
3. 常用使用方式
1. 在 Controller/Service 中注入使用
@Service
@RequiredArgsConstructor
public class UserService {
private final MessageSource messageSource;
public String getWelcomeMessage(Locale locale) {
return messageSource.getMessage("welcome.title", null, locale);
// 带参数
// return messageSource.getMessage("welcome.user", new Object[]{"张三"}, locale);
}
}
// Controller 中可以直接用当前请求的 Locale
@GetMapping("/hello")
public String hello(@RequestHeader(name = "Accept-Language", required = false) Locale locale) {
return messageSource.getMessage("hello", null, locale);
}
2. 更优雅的方式:封装一个工具类
@Component
@RequiredArgsConstructor
public class I18nUtils {
private final MessageSource messageSource;
public String get(String code, Object... args) {
return messageSource.getMessage(code, args, RequestContextUtils.getLocale(
((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest()));
}
}
3. 在 Thymeleaf 中使用
<h1 th:text="#{welcome.title}">默认标题</h1>
<p th:text="#{welcome.user(${user.name})}"></p>
<!-- 手动指定语言 -->
<span th:text="${#messages.msg('hello', null, T(java.util.Locale).US)}"></span>
4. 校验消息国际化(JSR-303)
public class UserDTO {
@NotBlank(message = "{user.name.notblank}")
private String username;
@Email(message = "{email.invalid}")
private String email;
}
在 ValidationMessages_zh_CN.properties 中:
user.name.notblank=用户名不能为空
email.invalid=邮箱格式不正确
4. 前后端分离项目(Vue/React)的最佳实践
推荐把所有消息统一放在后端,通过接口返回 code,前端用 i18n 插件翻译。
// 统一响应体
public class R<T> {
private String code; // 例如 "user.login.success"
private String message; // 实际多语言消息
private T data;
}
// 全局异常处理
@ExceptionHandler(BindException.class)
public R<Void> handle(BindException e, HttpServletRequest request) {
Locale locale = RequestContextUtils.getLocale(request);
String msg = e.getBindingResult().getAllErrors().stream()
.map(err -> messageSource.getMessage(err, locale))
.collect(Collectors.joining(";"));
return R.error("validation.failed", msg);
}
前端(Vue i18n 示例):
this.$i18n.t(error.response.data.code)
5. 进阶技巧
- 多模块项目:使用
classpath*:/i18n/**/messages加载所有子模块的 messages。 - 数据库动态国际化:实现自己的
MessageSource,从数据库读取(适用于 CMS)。 - 区域化(Regional)支持:如 zh_CN(大陆)、zh_TW(台湾),文件分别放
messages_zh_CN.properties和messages_zh_TW.properties。 - IDEA 插件:Resource Bundle Editor 极大提升效率。
总结
| 场景 | 推荐方案 |
|---|---|
| 传统 SSR 项目(Thymeleaf) | ReloadableResourceBundleMessageSource + CookieLocaleResolver + 拦截器 |
| 前后端分离纯 API 项目 | AcceptHeaderLocaleResolver + 统一返回 code + 前端 i18n |
| 企业级多模块后台系统 | 配置文件方式 + Cookie + 数据库动态消息(可选) |
这样配置后,你的 Spring Boot 项目就拥有了生产级别的、热加载支持、可长期记住用户偏好的完整国际化能力。需要的代码我可以直接给你一个完整的 GitHub 示例项目模板。需要的话告诉我。