Spring进阶特性国际化(i18n)

Spring 中的国际化(i18n)属于进阶但非常实用的特性,尤其在多语言后台管理系统、前后端分离项目、企业级应用中几乎是标配。下面系统性地讲一下 Spring Boot 中最常用、最地道的国际化实现方式(2025 年最新最佳实践)。

1. 核心组件总览

组件作用常用实现类
MessageSource读取国际化资源文件的核心接口ResourceBundleMessageSource(最常用)
ReloadableResourceBundleMessageSource(支持热加载)
LocaleResolver决定当前用户使用哪个 LocaleSessionLocaleResolver(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. 进阶技巧

  1. 多模块项目:使用 classpath*:/i18n/**/messages 加载所有子模块的 messages。
  2. 数据库动态国际化:实现自己的 MessageSource,从数据库读取(适用于 CMS)。
  3. 区域化(Regional)支持:如 zh_CN(大陆)、zh_TW(台湾),文件分别放 messages_zh_CN.propertiesmessages_zh_TW.properties
  4. IDEA 插件:Resource Bundle Editor 极大提升效率。

总结

场景推荐方案
传统 SSR 项目(Thymeleaf)ReloadableResourceBundleMessageSource + CookieLocaleResolver + 拦截器
前后端分离纯 API 项目AcceptHeaderLocaleResolver + 统一返回 code + 前端 i18n
企业级多模块后台系统配置文件方式 + Cookie + 数据库动态消息(可选)

这样配置后,你的 Spring Boot 项目就拥有了生产级别的、热加载支持、可长期记住用户偏好的完整国际化能力。需要的代码我可以直接给你一个完整的 GitHub 示例项目模板。需要的话告诉我。

文章已创建 3070

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部