I18n Starter
受众:开发者 摘要:统一多语言文案解析。覆盖 Validation、异常、业务消息、数据库覆盖、缺失 key 收集。
LangFacade + SpringMessageSource兜底 + 数据库优先。
何时使用
| 场景 | 建议 |
|---|---|
| Validation 校验消息 | @NotBlank(message = "{messages.user.username.not-blank}") |
| 业务异常消息 | throw new BusinessException("{messages.user.username.exists}", username) |
| 框架内置消息 key | MessageKeys 常量类 |
| 业务代码查文案 | Lang.get("messages.result.success") / Lang.message("{...}", args) |
| 后台维护多语言 / 运行时修改文案 | 启用 database.enabled + i18n_message 表 |
| 收集运行时缺失的 key | 启用 missing.enabled |
| 调用第三方翻译服务 | 用 translate.md(I18n 拿文案,Translate 做翻译) |
快速开始
spring:
open:
i18n:
default-locale: zh-CN
database:
enabled: true # 启用数据库覆盖Validation:
@NotBlank(message = "{messages.user.username.not-blank}")
private String username;业务代码:
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
// 直接按 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:
throw new BusinessException("{messages.user.username.exists}", username);
throw new ResourceNotFoundException(I18nMessageKeys.I18N_MESSAGE_NOT_FOUND, locale, key);配置项
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 # 缺失文案默认来源| 配置 | 默认值 | 说明 |
|---|---|---|
enabled | true | 启用开关 |
basename | i18n/messages | 公共资源文件 basename(可列表) |
discover-basenames | true | 自动发现 classpath 下的 i18n/**/messages*.properties,用于聚合模块内默认文案 |
default-locale | zh-CN | 未提供 Locale 或请求语言没有对应资源包时的默认回退语言 |
database.enabled | false | 数据库文案覆盖 |
missing.enabled | true | 缺失文案收集 |
missing.source | missing | 缺失文案来源标记 |
环境变量示例:
SPRING_OPEN_I18N_DEFAULT_LOCALE=zh-CN
SPRING_OPEN_I18N_DISCOVER_BASENAMES=true
SPRING_OPEN_I18N_DATABASE_ENABLED=true
SPRING_OPEN_I18N_MISSING_ENABLED=trueProvider
不提供 Provider 切换(底层固定 Spring MessageSource)。扩展通过:
- 自定义
I18nMessageRepositoryBean 接入其他存储 - 自定义
MissingMessageHandler改变缺失收集行为 - 自定义
MessageResolver改变解析逻辑
解析顺序
数据库启用文案 → 资源文件 → 默认消息 → key 本身数据库参与解析需要同时满足:
spring.open.i18n.database.enabled=true- 存在
I18nMessageRepositoryBean
默认应用通过 spring-open-core-i18n 加载数据库仓储实现。裸 starter 使用时如果没有数据库仓储,自动退回资源文件模式。
Key 命名规范
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-common的MessageKeys只放跨模块共享的框架词汇(exception / validation / data / result 等),不收业务模块或单个 starter 的私有 key。- 模块私有 key 一律放当前模块内的
<Module>MessageKeys,例如InstallMessageKeys、SmsMessageKeys、MallMessageKeys;单一模块消费的 key 沉到该模块,不上浮 common。 - 新增 Validation、异常和业务提示时,不再直接散落裸字符串;注解需要编译期常量时,用模块常量拼接
{}表达式。
| 写法 | 用途 |
|---|---|
{messages.user.username.not-blank} | Spring i18n 消息 key |
${DB_HOST:127.0.0.1} | Spring 配置占位符 |
不要在 Validation 或异常消息中使用 ${}。文案带变量用 {0} / {1} MessageFormat 占位符。
资源文件
默认公共目录:
src/main/resources/i18n/常见文件:
i18n/messages.properties
i18n/messages_zh_CN.properties
i18n/messages_en_US.properties模块内目录:
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当前内置模块已按该目录拆分存量默认文案:config、iam、i18n、resources、runtime、money、conversation、cashier、cms、blog、forum、mall、short-link、live-code、open-platform、install、search。中央 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-found、messages.forum.topic.not-found。 - 业务模块不要引用其他业务模块的私有 key;确实跨模块复用的文案上提到公共
i18n/messages。 - 请求语言没有对应资源包时,按
spring.open.i18n.default-locale回退;未配置时默认回退zh-CN。
语言编码
项目内统一使用 BCP 47 风格语言编码:
| 常量 | 标准值 | 资源文件后缀 |
|---|---|---|
LocaleCodes.ZH_CN | zh-CN | zh_CN |
LocaleCodes.ZH_TW | zh-TW | zh_TW |
LocaleCodes.EN_US | en-US | en_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 会写入:
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。
流程:
- 在
zh-CN写入或收集源文案 - 配置 Translate provider(
google-translate/tencent-cloud) - 调用
/api/v1/admin/i18n/messages/translate-batch预览 save=true保存目标语言文案到i18n_message
保存后 source 记录为 translation:<provider>(例 translation:google-translate)。
模块边界
| 模块 | 职责 |
|---|---|
spring-open-starter-common | Lang / MessageResolver / MessageKeys 契约 |
spring-open-starter-i18n | MessageSource / 资源文件 / 数据库读取抽象 / 缺失收集 |
spring-open-starter-translate | 翻译 Provider |
spring-open-core-i18n | i18n_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;运营文案走原文动态翻译
验证命令
./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