SpringBoot实现国际化(i18n)的完全指南

 更新时间:2026年06月17日 09:02:32   作者:霸道流氓气质  
这篇文章主要介绍了i18n的概念、核心机制、资源文件规则、配置方式及示例,涵盖从基本概念到具体实现的全方位内容,融合了i18n、LocaleResolver、资源文件规则等关键词,全面解析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 的常见实现

类型判断依据适用场景
AcceptHeaderLocaleResolverHTTP 请求头 Accept-LanguageAPI 服务、微服务
CookieLocaleResolverCookie 中存储的语言偏好Web 应用
SessionLocaleResolverSession 中存储的语言偏好传统 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 配置(推荐)

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 配置

@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 优先。

五、完整通用示例

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

1. 目录结构

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

2. 资源文件内容

messages.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(英文):

# 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 到默认文件):

# 留空,默认文件已是中文

3. application.yml

spring:
  messages:
    basename: i18n/messages
    cache-duration: 3s
    encoding: UTF-8

4. 配置类

@Configuration
public class I18nConfig {

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

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

@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 层使用

@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 层

@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} 等位置占位符:

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

代码中传参:

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

七、参数校验 + i18n 整合

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

@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 吗?

可以,逗号分隔:

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。可以用带默认值的方法避免:

messageSource.getMessage("some.key", null, "默认文案", locale);

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

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

@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

以上就是SpringBoot实现国际化(i18n)的完全指南的详细内容,更多关于SpringBoot国际化(i18n)实现的资料请关注脚本之家其它相关文章!

相关文章

  • java 虚拟机中对象访问详解

    java 虚拟机中对象访问详解

    这篇文章主要介绍了java 虚拟机中对象访问详解的相关资料,需要的朋友可以参考下
    2017-03-03
  • 浅谈线性表的原理及简单实现方法

    浅谈线性表的原理及简单实现方法

    下面小编就为大家带来一篇浅谈线性表的原理及简单实现方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • java父子节点parentid树形结构数据的规整

    java父子节点parentid树形结构数据的规整

    这篇文章主要介绍了java父子节点parentid树形结构数据的规整,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • Java使用位运算实现权限管理的示例详解

    Java使用位运算实现权限管理的示例详解

    在开发中,权限管理是一个非常常见的需求,本文将详细讲解如何使用 Java 中的 位运算 实现一个轻量级、高效的权限管理系统,并提供完整的代码示例和解释,感兴趣的小伙伴可以了解下
    2025-06-06
  • springboot各种下载文件的方式汇总

    springboot各种下载文件的方式汇总

    下载功能其实就是用户输入指定文件路径信息,然后把文件返回给用户,下面这篇文章主要给大家介绍了关于springboot各种下载文件的方式,需要的朋友可以参考下
    2022-10-10
  • MyBatis动态<if>标签使用避坑指南

    MyBatis动态<if>标签使用避坑指南

    这篇文章主要为大家介绍了MyBatis动态<if>标签使用避坑指南,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • Java面向对象设计原则之迪米特法则介绍

    Java面向对象设计原则之迪米特法则介绍

    迪米特法则解决类与类之间耦合度问题,如果类A调用了B类的某一个方法,则这两个类就形成了一种紧耦合的方式,当B类这个方法发生变化时,一定会影响A类的执行结果。迪米特法则要求每一个类尽可能少的与其他类发生关系
    2023-02-02
  • Idea创建Jsp项目完整版教程

    Idea创建Jsp项目完整版教程

    一直在使用eclipse,对idea嗤之以鼻,前些日子换成了idea以后觉得太香了,这篇文章主要给大家介绍了关于Idea创建Jsp项目的相关资料,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2024-04-04
  • Java中ZIP文件中文乱码问题的解决指南

    Java中ZIP文件中文乱码问题的解决指南

    文章介绍Java文件压缩解压中处理中文乱码的解决方案,包括指定UTF-8编码、使用缓冲流及Ant工具配置,确保文件名和内容正确显示,避免因字符集不一致导致的乱码问题,本文结合示例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2025-09-09
  • Spring依赖注入的三种方式详解

    Spring依赖注入的三种方式详解

    这篇文章主要给大家介绍了三种Spring依赖注入的方式, settter方法注入,构造器注入以及变量(filed) 注入这三种方式,文章通过代码示例给大家介绍的非常详细,需要的朋友可以参考下
    2023-11-11

最新评论