在 Spring Boot 中接入国际化,开发者通常要配置 MessageSource、地区解析器和拦截器,经过多步装配后才能读取第一条翻译。Solon I18n 选择把这套流程压缩到三个解析器、一个注解和一个工具类中,重点不是少写几行配置,而是让地区识别、资源查找和消息读取回到框架约定之内。
三个解析器解决的是输入差异
国际化请求通常包含三个问题:当前请求使用什么地区、到哪里查找资源、如何把消息键解析成最终文本。传统配置会把这些问题分散在 Bean、拦截器和资源加载器中;Solon I18n 则通过解析器集中处理差异,业务代码只消费最终结果。
这种设计对应用代码有两个直接影响:
- 控制器不需要知道地区来自请求头、参数、Cookie 还是其他上下文。
- 业务服务不需要直接操作资源文件,只通过稳定的消息键取值。
- 切换地区识别策略时,改动停留在接入层,不必逐个修改控制器。
来源摘要没有给出三个解析器的具体类名及各版本配置项,因此接入现有项目时应以所使用 Solon 版本的模块文档为准,不要根据 Spring 的同名接口直接套用配置。
一个注解划定国际化边界
注解适合表达“这个入口需要国际化上下文”,工具类则适合在真正需要文本的位置读取消息。二者配合后,控制器不再承担解析地区、选择资源包和处理回退规则等工作。
可以把业务边界写成下面这种形式。这里假设当前版本提供摘要中提到的 @I18n 和 I18nUtil;包名、注解作用范围以及工具类方法签名需要按实际版本调整:
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Mapping;
// 以下两个 import 是示意,请替换为当前 Solon I18n 版本中的实际包名。
import org.noear.solon.i18n.annotation.I18n;
import org.noear.solon.i18n.I18nUtil;
@I18n
@Controller
public class GreetingController {
@Mapping("/greeting")
public String greeting() {
return I18nUtil.get("greeting.message");
}
}
这段代码最值得保留的不是具体类名,而是调用边界:请求入口声明国际化能力,业务逻辑只传递 greeting.message 这样的稳定键。不要把中文或英文句子写进控制器,也不要让领域服务依赖 HTTP 请求对象。
可以这样实践:建立一条可验证的翻译链路
下面是一套可以直接改造到项目中的最小资源结构。资源目录和命名约定应根据当前 Solon I18n 版本校正。
src/main/resources/
└── i18n/
├── messages.properties
├── messages_en_US.properties
└── messages_zh_CN.properties
默认资源:
# src/main/resources/i18n/messages.properties
greeting.message=Hello
order.created=Order {0} was created
中文资源:
# src/main/resources/i18n/messages_zh_CN.properties
greeting.message=你好
order.created=订单 {0} 已创建
英文资源:
# src/main/resources/i18n/messages_en_US.properties
greeting.message=Hello
order.created=Order {0} was created
启动应用后,可以用 HTTP 请求验证地区解析是否符合项目约定。下面假设解析器读取标准 Accept-Language 请求头;如果项目选择参数或 Cookie 解析器,应相应修改请求:
curl -i -H 'Accept-Language: zh-CN' http://localhost:8080/greeting
curl -i -H 'Accept-Language: en-US' http://localhost:8080/greeting
curl -i -H 'Accept-Language: fr-FR' http://localhost:8080/greeting
第三个请求尤其重要:它用来确认不存在 fr-FR 资源时,系统究竟回退到默认语言、返回消息键,还是抛出异常。回退行为不能只靠开发者猜测,应当写进自动化测试。
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.Test;
class I18nKeyContractTest {
@Test
void messageKeyMustResolveToVisibleText() {
// 将 resolveForTest 替换成项目对 I18nUtil 的测试封装。
String text = TestI18n.resolveForTest("greeting.message", "zh-CN");
assertNotNull(text);
assertFalse(text.isBlank());
assertFalse(text.equals("greeting.message"));
}
}
这个测试示例中的 TestI18n 是项目适配层,不是摘要声明的 Solon API。可以让它在测试启动阶段初始化 Solon 上下文,再调用实际的 I18nUtil。这样升级框架时,只需调整一个适配点。
零样板代码不等于零治理
框架省掉配置后,团队仍需要管理消息键和资源质量。实际落地时建议检查以下事项:
- 消息键按领域组织,例如
order.created、payment.failed,不要使用text1一类无语义名称。 - 明确默认地区以及缺失翻译的回退策略,并为未知地区编写测试。
- 保证各语言资源中的占位符一致,避免
{0}、{1}数量不匹配。 - 不要把异常堆栈、数据库字段名等内部信息直接送入翻译模板。
- 在日志和指标中保留消息键;面向用户输出翻译文本,面向排障保留稳定标识。
Solon I18n 的价值在于把国际化从一串基础设施装配动作变成应用约定。迁移时可以先选择一个只读接口,接通地区解析、资源回退和消息读取,再逐步覆盖其他控制器。配置确实少了,但资源契约、回退测试和版本 API 核对仍然不可省略。