跳到内容

I18n Starter

受众:开发者 摘要:统一多语言文案解析。覆盖 Validation、异常、业务消息、数据库覆盖、缺失 key 收集。Lang Facade + Spring MessageSource 兜底 + 数据库优先。

何时使用

场景建议
Validation 校验消息@NotBlank(message = "{messages.user.username.not-blank}")
业务异常消息throw new BusinessException("{messages.user.username.exists}", username)
框架内置消息 keyMessageKeys 常量类
业务代码查文案Lang.get("messages.result.success") / Lang.message("{...}", args)
后台维护多语言 / 运行时修改文案启用 database.enabled + i18n_message
收集运行时缺失的 key启用 missing.enabled
调用第三方翻译服务translate.md(I18n 拿文案,Translate 做翻译)

快速开始

yaml
spring:
  open:
    i18n:
      default-locale: zh-CN
      database:
        enabled: true        # 启用数据库覆盖

Validation:

java
@NotBlank(message = "{messages.user.username.not-blank}")
private String username;

业务代码:

java
String message = Lang.get("messages.result.success");
String welcome = Lang.message("{messages.welcome.text}", username, appName);

HTTP 请求语言通过 Accept-Language: zh-CN / zh-TW / en-US 头解析。

Facade API

java
// 直接按 key 取文案
Lang.get("messages.user.username.not-blank");
Lang.get("messages.user.username.exists", username);  // 带 MessageFormat 参数

// 解析 {messages.xxx} 形式表达式
Lang.message("{messages.welcome.text}", username, appName);
Lang.message("plain text");      // 非 key 直接返回

// 指定 Locale
Lang.get("messages.x", Locale.ENGLISH);

// 当前请求的 Locale
Locale current = LocaleContextHolder.getLocale();

异常使用消息 key:

java
throw new BusinessException("{messages.user.username.exists}", username);
throw new ResourceNotFoundException(I18nMessageKeys.I18N_MESSAGE_NOT_FOUND, locale, key);

配置项

yaml
spring:
  open:
    i18n:
      enabled: true
      basename: i18n/messages          # 公共资源;也可配置为列表
      discover-basenames: true         # 自动发现 i18n/**/messages*.properties
      default-locale: zh-CN
      database:
        enabled: false                 # 启用数据库覆盖和缺失文案持久化
      missing:
        enabled: true                  # 收集缺失文案
        source: missing                # 缺失文案默认来源
配置默认值说明
enabledtrue启用开关
basenamei18n/messages公共资源文件 basename(可列表)
discover-basenamestrue自动发现 classpath 下的 i18n/**/messages*.properties,用于聚合模块内默认文案
default-localezh-CN未提供 Locale 或请求语言没有对应资源包时的默认回退语言
database.enabledfalse数据库文案覆盖
missing.enabledtrue缺失文案收集
missing.sourcemissing缺失文案来源标记

环境变量示例:

dotenv
SPRING_OPEN_I18N_DEFAULT_LOCALE=zh-CN
SPRING_OPEN_I18N_DISCOVER_BASENAMES=true
SPRING_OPEN_I18N_DATABASE_ENABLED=true
SPRING_OPEN_I18N_MISSING_ENABLED=true

Provider

不提供 Provider 切换(底层固定 Spring MessageSource)。扩展通过:

  • 自定义 I18nMessageRepository Bean 接入其他存储
  • 自定义 MissingMessageHandler 改变缺失收集行为
  • 自定义 MessageResolver 改变解析逻辑

解析顺序

text
数据库启用文案 → 资源文件 → 默认消息 → key 本身

数据库参与解析需要同时满足:

  • spring.open.i18n.database.enabled=true
  • 存在 I18nMessageRepository Bean

默认应用通过 spring-open-core-i18n 加载数据库仓储实现。裸 starter 使用时如果没有数据库仓储,自动退回资源文件模式。

Key 命名规范

properties
messages.user.username.not-blank=用户名不能为空
messages.user.username.exists=用户名已存在:{0}
messages.notification.template.variable-missing=通知模板变量缺失:{0}
前缀用途
messages.result.*通用返回消息
messages.exception.*通用异常消息
messages.validation.*通用校验消息
messages.<module>.*业务模块消息

多语言 key 必须常量化管理:

  • spring-open-starter-commonMessageKeys 只放跨模块共享的框架词汇(exception / validation / data / result 等),不收业务模块或单个 starter 的私有 key。
  • 模块私有 key 一律放当前模块内的 <Module>MessageKeys,例如 InstallMessageKeysSmsMessageKeysMallMessageKeys;单一模块消费的 key 沉到该模块,不上浮 common。
  • 新增 Validation、异常和业务提示时,不再直接散落裸字符串;注解需要编译期常量时,用模块常量拼接 {} 表达式。
写法用途
{messages.user.username.not-blank}Spring i18n 消息 key
${DB_HOST:127.0.0.1}Spring 配置占位符

不要在 Validation 或异常消息中使用 ${}。文案带变量用 {0} / {1} MessageFormat 占位符。

资源文件

默认公共目录:

text
src/main/resources/i18n/

常见文件:

text
i18n/messages.properties
i18n/messages_zh_CN.properties
i18n/messages_en_US.properties

模块内目录:

text
spring-open-modules/spring-open-module-mall/src/main/resources/i18n/mall/
├── messages.properties
├── messages_zh_CN.properties
├── messages_zh_TW.properties
└── messages_en_US.properties

当前内置模块已按该目录拆分存量默认文案:configiami18nresourcesruntimemoneyconversationcashiercmsblogforummallshort-linklive-codeopen-platforminstallsearch。中央 starter 资源只保留框架通用、starter 默认和跨模块共享文案。

约定:

  • i18n/messages*.properties 放框架通用文案、通用异常、通用校验和跨模块共享 key。
  • i18n/<module>/messages*.properties 放模块默认文案,例如 i18n/mall/messages_zh_CN.properties
  • messages.properties 只作为 basename 占位,默认保持空文件,不承载具体语言文案。
  • 中文写入 messages_zh_CN.properties
  • 繁体中文写入 messages_zh_TW.properties
  • 英文写入 messages_en_US.properties
  • 模块 key 仍必须带模块前缀,例如 messages.mall.order.not-foundmessages.forum.topic.not-found
  • 业务模块不要引用其他业务模块的私有 key;确实跨模块复用的文案上提到公共 i18n/messages
  • 请求语言没有对应资源包时,按 spring.open.i18n.default-locale 回退;未配置时默认回退 zh-CN

语言编码

项目内统一使用 BCP 47 风格语言编码:

常量标准值资源文件后缀
LocaleCodes.ZH_CNzh-CNzh_CN
LocaleCodes.ZH_TWzh-TWzh_TW
LocaleCodes.EN_USen-USen_US

Java 代码中的字符串语言编码统一使用 com.springopen.starter.common.i18n.LocaleCodes,语言解析和归一化统一使用 com.springopen.starter.common.i18n.LocaleUtils。配置、接口、前端请求头统一传短横线形式,下划线形式只用于 Java 资源文件名或外部系统输入归一化。

资源文件适合放框架默认文案、模块默认文案和开发期稳定文案。运行时修正、后台维护、收集缺失走数据库。

数据库文案

表:i18n_message

字段说明
locale语言(zh-CN / zh-TW / en-US
message_key文案 key
message_value文案内容
source来源(database / missing / translation:google-translate 等)
missing是否为缺失 key 自动收集
enabled是否启用
optimistic_lock_version乐观锁
deleted_at逻辑删除

同一 locale + message_key 只能有一条有效文案。

启用后台后,数据库文案覆盖资源文件同名 key。

缺失文案收集

启用 database + missing 后,运行时访问不存在的 key 会写入:

text
locale=zh-CN
message_key=messages.example.missing
message_value=messages.example.missing
source=missing
missing=true
enabled=true

如果资源文件存在某 key 但数据库没有,解析返回资源文件文案,同时可收集一条数据库记录便于后台统一管理。

缺失文案收集失败不影响业务请求主链路。

后台 API

Core I18n 模块提供:

方法路径说明权限
GET/api/v1/admin/i18n/messages分页查询i18n:message:view
GET/api/v1/admin/i18n/messages/{id}详情i18n:message:view
POST/api/v1/admin/i18n/messages创建i18n:message:create
PUT/api/v1/admin/i18n/messages/{id}修改i18n:message:update
PUT/api/v1/admin/i18n/messages/{id}/enabled启停i18n:message:update
DELETE/api/v1/admin/i18n/messages/{id}删除i18n:message:delete
DELETE/api/v1/admin/i18n/messages批量删除(body 传 ID)i18n:message:delete
POST/api/v1/admin/i18n/messages/translate翻译单条预览 view / 保存 update
POST/api/v1/admin/i18n/messages/translate-batch批量翻译预览 view / 保存 update

分页支持 locale / keyword(匹配 key / value / source)/ missing / enabled 过滤。

翻译协作

I18n 后台翻译入口复用 Translate starter。

流程:

  1. zh-CN 写入或收集源文案
  2. 配置 Translate provider(google-translate / tencent-cloud
  3. 调用 /api/v1/admin/i18n/messages/translate-batch 预览
  4. save=true 保存目标语言文案到 i18n_message

保存后 source 记录为 translation:<provider>(例 translation:google-translate)。

模块边界

模块职责
spring-open-starter-commonLang / MessageResolver / MessageKeys 契约
spring-open-starter-i18nMessageSource / 资源文件 / 数据库读取抽象 / 缺失收集
spring-open-starter-translate翻译 Provider
spring-open-core-i18ni18n_message 后台 CRUD / 权限 / MyBatis-Plus 仓储

业务模块只依赖 Lang / MessageKeys / Spring Validation,直接依赖 i18n 后台实现。

开发约定

  • Controller 不直接拼接用户可见文案
  • DTO Validation 必须用 {messages.xxx}
  • 异常优先用消息 key 或 MessageKeys 常量
  • 新增模块文案时同步补充默认资源文件
  • 新增后台实体时 Validation、异常、业务提示都补 i18n key
  • 不把密钥、手机号、邮箱等敏感值写进文案内容
  • 文案带变量用 {0} / {1} MessageFormat 占位符

注意事项

  • 动态业务元数据(菜单、字典、支付方式、CMS 栏目等)走 .ai/decisions/adr-029-dynamic-metadata-i18n.md 业务元数据本地化方案,强行用 i18n key 维护
  • 框架内置消息固定走 key;运营文案走原文动态翻译

验证命令

bash
./mvnw -pl spring-open-starters/spring-open-starter-i18n -am test -DskipITs
./mvnw -pl spring-open-core/spring-open-core-i18n -am test -DskipITs
./mvnw -pl spring-open-application -am test -DskipITs -DskipFrontend

相关

  • Translate(翻译 Provider):translate.md
  • 动态业务元数据本地化(菜单 / 字典等):.ai/decisions/adr-029-dynamic-metadata-i18n.md
  • I18n 后台管理:spring-open-core-i18n
  • Web MVC starter(Locale 解析):web.md
  • Provider 扩展模型:provider.md

Released under the Apache License 2.0.