Spring Boot 国际化(i18n)完全指南

Spring Boot 国际化(i18n)完全指南

一、什么是 i18n?

i18n 是 "internationalization" 的缩写(i 和 n 之间有 18 个字母),核心思想是:把用户可见的文本从代码中抽离到外部资源文件,运行时根据语言环境动态加载对应文件,实现多语言切换而无需改代码。


二、核心机制

工作原理

复制代码
客户端请求(携带 Accept-Language: en_US)
        ↓
LocaleResolver 解析语言环境 → Locale.US
        ↓
MessageSource 按优先级查找资源文件:
  1. messages_en_US.properties  ← 精确匹配
  2. messages_en.properties     ← 语言匹配
  3. messages.properties        ← 默认兜底
        ↓
找到 key 对应的 value → 返回文案

三个核心组件

组件 职责
MessageSource 负责根据 key + Locale 加载对应语言的文本
LocaleResolver 负责从请求中解析出用户的语言偏好
资源文件 (*.properties) 存储各语言的 key-value 文案

LocaleResolver 的常见实现

类型 判断依据 适用场景
AcceptHeaderLocaleResolver HTTP 请求头 Accept-Language API 服务、微服务
CookieLocaleResolver Cookie 中存储的语言偏好 Web 应用
SessionLocaleResolver Session 中存储的语言偏好 传统 Web 应用
FixedLocaleResolver 固定语言,不可更改 单语言应用

三、资源文件规则

命名规范

复制代码
{basename}.properties              ← 默认(兜底)
{basename}_{language}.properties   ← 按语言
{basename}_{language}_{country}.properties  ← 按语言+地区

查找优先级(以 Locale("zh", "CN") 为例)

复制代码
1. messages_zh_CN.properties   ← 最精确
2. messages_zh.properties      ← 语言级别
3. messages.properties         ← 默认兜底

如果高优先级文件中没有某个 key,会自动 fallback 到低优先级文件。

文件编码

  • .properties 文件默认使用 ISO-8859-1 编码
  • 中文需要写成 Unicode 转义形式:\u4F1A\u5458 = "会员"
  • 配置 encoding: UTF-8 后可直接写中文(Spring Boot 推荐方式)

四、配置方式

方式一:application.yml 配置(推荐)

yaml 复制代码
spring:
  messages:
    basename: i18n/messages          # 资源文件路径前缀(相对于 classpath)
    cache-duration: 3s               # 缓存时长,开发时设短方便热更新
    encoding: UTF-8                  # 文件编码
    fallback-to-system-locale: true  # 是否回退到系统默认语言
配置项 说明
basename 文件路径前缀,多个用逗号分隔:i18n/messages,i18n/errors
cache-duration 缓存刷新间隔,生产环境可设大(如 1h),开发设小(如 3s
encoding 设为 UTF-8 后 properties 文件可直接写中文
fallback-to-system-locale 找不到对应语言文件时是否用 JVM 默认 Locale

方式二:Java Config 配置

java 复制代码
@Configuration
public class I18nConfig {

    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource source = new ResourceBundleMessageSource();
        source.setBasename("i18n/messages");
        source.setDefaultEncoding("UTF-8");
        source.setCacheSeconds(3);
        return source;
    }

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

两种方式效果相同。当 Java Bean 和 YAML 同时配置时,Java Bean 优先。


注:

博客:

https://blog.csdn.net/badao_liumang_qizhi

五、完整通用示例

以一个用户注册模块为例,演示完整的 i18n 实现。

1. 目录结构

复制代码
src/main/resources/
├── application.yml
└── i18n/
    ├── messages.properties           # 默认(中文)
    ├── messages_zh_CN.properties     # 中文(可为空,默认已是中文)
    └── messages_en_US.properties     # 英文

2. 资源文件内容

messages.properties(默认,中文兜底):

properties 复制代码
# 用户模块
user.register.username.empty=用户名不能为空
user.register.username.duplicate=用户名"{0}"已被注册
user.register.password.too.short=密码长度不能少于{0}位
user.register.email.invalid=邮箱格式不正确
user.register.success=注册成功,欢迎{0}!

# 通用
common.param.invalid=参数校验失败
common.system.error=系统繁忙,请稍后重试

messages_en_US.properties(英文):

properties 复制代码
# User module
user.register.username.empty=Username cannot be empty
user.register.username.duplicate=Username "{0}" is already taken
user.register.password.too.short=Password must be at least {0} characters
user.register.email.invalid=Invalid email format
user.register.success=Registration successful, welcome {0}!

# Common
common.param.invalid=Parameter validation failed
common.system.error=System is busy, please try again later

messages_zh_CN.properties(留空即可,fallback 到默认文件):

properties 复制代码
# 留空,默认文件已是中文

3. application.yml

yaml 复制代码
spring:
  messages:
    basename: i18n/messages
    cache-duration: 3s
    encoding: UTF-8

4. 配置类

java 复制代码
@Configuration
public class I18nConfig {

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

5. 封装工具类(方便全局调用)

java 复制代码
@Component
public class I18nUtil {

    private static MessageSource messageSource;

    @Resource
    public void setMessageSource(MessageSource messageSource) {
        I18nUtil.messageSource = messageSource;
    }

    /**
     * 获取国际化消息.
     *
     * @param key  消息 key
     * @param args 占位符参数
     * @return 对应语言的消息文本
     */
    public static String getMessage(String key, Object... args) {
        Locale locale = LocaleContextHolder.getLocale();
        return messageSource.getMessage(key, args, locale);
    }

    /**
     * 获取国际化消息(带默认值).
     */
    public static String getMessage(String key, String defaultMsg, Object... args) {
        Locale locale = LocaleContextHolder.getLocale();
        return messageSource.getMessage(key, args, defaultMsg, locale);
    }
}

6. Service 层使用

java 复制代码
@Slf4j
@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public void register(UserRegisterRequest request) {
        // 校验用户名
        if (StringUtils.isBlank(request.getUsername())) {
            throw new BusinessException(I18nUtil.getMessage("user.register.username.empty"));
        }

        // 校验密码长度(带占位符参数)
        if (request.getPassword().length() < 8) {
            throw new BusinessException(
                I18nUtil.getMessage("user.register.password.too.short", 8));
        }

        // 校验用户名重复
        if (userMapper.existsByUsername(request.getUsername())) {
            throw new BusinessException(
                I18nUtil.getMessage("user.register.username.duplicate", request.getUsername()));
        }

        // 保存用户
        userMapper.insert(buildUser(request));
        log.info("用户注册成功, username={}", request.getUsername());
    }
}

7. Controller 层

java 复制代码
@Slf4j
@RestController
@RequestMapping("/api/user")
public class UserController {

    @Resource
    private UserService userService;

    @PostMapping("/register")
    public Result<String> register(@RequestBody UserRegisterRequest request) {
        try {
            userService.register(request);
            String successMsg = I18nUtil.getMessage("user.register.success", request.getUsername());
            return Result.success(successMsg);
        } catch (BusinessException e) {
            return Result.fail(e.getMessage());
        } catch (Exception e) {
            log.error("用户注册异常, error={}", e.getMessage(), e);
            return Result.fail(I18nUtil.getMessage("common.system.error"));
        }
    }
}

8. 运行效果

中文请求(或不带 Accept-Language):

复制代码
POST /api/user/register
Body: {"username": "", "password": "123"}

响应:{"code": 500, "message": "用户名不能为空"}

英文请求

复制代码
POST /api/user/register
Headers: Accept-Language: en_US
Body: {"username": "", "password": "123"}

响应:{"code": 500, "message": "Username cannot be empty"}

带占位符参数

复制代码
POST /api/user/register
Headers: Accept-Language: en_US
Body: {"username": "tom", "password": "123"}

响应:{"code": 500, "message": "Password must be at least 8 characters"}

六、占位符语法

资源文件支持 {0}, {1}, {2} 等位置占位符:

properties 复制代码
# {0} = 用户名, {1} = 日期
user.welcome=欢迎{0},您的账号创建于{1}

代码中传参:

java 复制代码
messageSource.getMessage("user.welcome", new Object[]{"张三", "2026-06-16"}, locale);
// 输出:欢迎张三,您的账号创建于2026-06-16

七、参数校验 + i18n 整合

Spring Validation 的注解也支持 i18n,在 message 属性中引用资源 key:

java 复制代码
@Data
public class UserRegisterRequest {

    @NotBlank(message = "{user.register.username.empty}")
    private String username;

    @Size(min = 8, message = "{user.register.password.too.short}")
    private String password;

    @Email(message = "{user.register.email.invalid}")
    private String email;
}

注意:用 {} 包裹 key 名。Spring 会自动从 MessageSource 查找对应文案。


八、常见问题

Q1: 为什么 zh_CN 文件是空的?

默认文件(messages.properties)里已经写了中文。查找链是 zh_CN → 默认,所以不需要重复写一遍。只有当默认文件用英文、要额外支持中文时才需要填 zh_CN。

Q2: cache-duration: 3s 生产环境要改吗?

要。开发时设 3 秒方便调试,生产环境建议设大一些(如 1h-1 表示永不刷新),避免频繁读文件影响性能。

Q3: 一个项目能有多个 basename 吗?

可以,逗号分隔:

yaml 复制代码
spring:
  messages:
    basename: i18n/messages,i18n/errors,i18n/validation

对应的文件结构:

复制代码
i18n/
├── messages.properties
├── messages_en_US.properties
├── errors.properties
├── errors_en_US.properties
├── validation.properties
└── validation_en_US.properties

Q4: 找不到 key 时会怎样?

默认抛出 NoSuchMessageException。可以用带默认值的方法避免:

java 复制代码
messageSource.getMessage("some.key", null, "默认文案", locale);

Q5: 如何动态切换语言(不靠请求头)?

使用 LocaleChangeInterceptor,通过 URL 参数切换:

java 复制代码
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
    LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
    interceptor.setParamName("lang");  // ?lang=en_US
    return interceptor;
}

访问 http://localhost:8080/api/user?lang=en_US 即可切换到英文。


九、总结

要素 作用
messages.properties 默认兜底文案
messages_{locale}.properties 特定语言文案
MessageSource 根据 key + locale 查找文案的核心接口
LocaleResolver 从请求中解析用户语言偏好
{0} 占位符 支持动态参数替换
cache-duration 控制文件缓存刷新频率
@NotBlank(message = "{key}") 校验注解直接对接 i18n
相关推荐
один but you2 小时前
const和constexpr常量表达式
java·前端·javascript
码云数智-大飞2 小时前
RAII 与智能指针深度拆解
java·前端·算法
云烟成雨TD2 小时前
Agent Scope Java 2.x 系列【19】Harness:从零搭建 MySQL 文件系统
java·人工智能·agent
qq3621967052 小时前
阿里裁员新消息(2026最新动态汇总)
java·开发语言·前端
a1117762 小时前
“黑夜流星“个人引导页 网页html
java·前端·html
进阶的小名2 小时前
Spring Boot SSE + Nginx 配置:解决 EventSource 不实时返回、连接超时、流式响应被缓冲问题
spring boot·后端·nginx
砚底藏山河2 小时前
沪深A股:如何获取基金持股数据
java·python·数据分析·maven
代码改善世界2 小时前
【C++进阶】C++11:列表初始化、右值引用与移动语义、完美转发全解析
java·开发语言·c++
AIGS0012 小时前
JBoltAI V4.5企业智能体平台:技术架构拆解
java·人工智能·ai大模型应用
一勺菠萝丶2 小时前
Maven SNAPSHOT 父 POM 无法解析问题排查
java·maven