国际化
Oak 的国际化并不是一个孤立的前端插件,而是进入了项目编译流程的一部分。
这意味着:
- 页面可以定义自己的 locales;
- 组件可以定义自己的 locales;
- 模块可以定义自己的 locales;
- 实体生成出来的内容也可以带着 locales;
- 多个依赖模块的国际化资源会被统一编译进项目;
- 当前 locale 会进入前后端 runtime context。
make:locale 做了什么
npm run make:locale 最终会调用 oak-domain/src/compiler/localeBuilder.ts。
它会扫描项目和依赖模块中的 locale 目录,然后统一生成:
src/data/i18n.json
src/data/i18n.ts
其中 src/data/i18n.json 承载真实 i18n 行数据,src/data/i18n.ts 是兼容旧代码的 wrapper,会从 ./i18n.json 读取后导出。这样可以避免巨大的 i18n 数组继续塞进 TypeScript 源码里,也让 tscBuilder 在构建时把相对 JSON 运行时资产复制到 lib / es 等输出目录。
也就是说,Oak 的 i18n 不是“运行时到处动态拼 JSON”,而是先把项目里的本地化资源编译成标准数据;只是当前标准数据的主体已经从 TypeScript 文件迁移到了 JSON 文件。
默认会扫描哪些目录
LocaleBuilder 默认会收集这些位置:
src/pages/**/localessrc/components/**/localessrc/locales/*src/oak-app-domain/**/localesweb/src下的 locale 目录wechatMp/src下的 locale 目录
如果项目依赖了其它 Oak 模块,这些模块中的 locales 也会一并进入编译结果。
依赖模块是否参与共享 locale 编译由 package.json 的 Oak 元数据控制。当前 LocaleBuilder 会识别安装在顶层 node_modules、具有对象形式 oak 配置且 oak.i18n === true 的包,并从其 es 产物收集页面、组件和 locales。当前 Oak 包通常同时声明 oak.package: true,但 locale 收集本身判断的是 oak.i18n。实体相关的多语言元数据来自 es/entities 的声明和值合并解析,不要求发布 src。
应用侧排查“依赖包 locale 没进 src/data/i18n.json”时,先检查依赖包是否发布了对应 es/.../locales 文件和 oak.i18n: true,不要默认认为是 LocaleBuilder 漏扫了整个 node_modules。
locale 文件长什么样
Oak 中的 locale 文件通常就是普通 JSON,例如:
{
"title": "系统管理",
"create": "新建",
"remove": "删除"
}
文件名一般使用语言标识,例如:
zh_CN.jsonen_US.json
LocaleBuilder 在编译时会把 zh_CN 这种文件名转换成 zh-CN 语言标识。
namespace 是怎么来的
Oak 会根据 locale 所在的位置自动生成 namespace。
例如:
- 页面 locale 会带
p-前缀; - 组件 locale 会带
c-前缀; src/locales/common这类全局目录会带l-common这样的前缀;- 如果资源来自依赖模块,还会自动带上模块名。
所以在真实项目里,你经常会看到类似下面这样的 namespace:
projectName-l-common
projectName-l-error
projectName-l-menu
项目可以在 src/initializeFeatures.ts 中通过:
features.locales.loadServerData([
'projectName-l-common',
'projectName-l-error',
'projectName-l-menu'
]);
去主动加载的服务端 i18n 数据。
在组件里如何使用
Oak 组件方法里自带 t(key, params?),也就是说,组件层并不需要自己再额外接一个 i18n 工具对象。
你更需要关心的是:
- 文案是否放到了正确的 locale 目录;
- 是否执行过
make:locale; - 需要服务端参与的 namespace 是否已正确加载。
如果逻辑运行在 aspect、trigger、checker、watcher、timer 或 routine 中,则优先从 context 获取 locale。这样后端翻译、通知、LocalizedContent 生成和前端展示使用的是同一套语言上下文。
和系统翻译能力的关系
国际化不再只是页面文案。项目如果增加了翻译相关实体或依赖,还需要先完成 project:init -> make:domain -> make:dep 的依赖/domain 同步,再生成 locale;不要把实体变化只当作 locale 文件变化处理。
为什么 Oak 要把 i18n 做进编译流程
这样做有三个直接好处:
- 项目和依赖模块的多语言资源可以统一收敛;
- i18n 数据本身可以进入 Oak 的数据体系;
- 前端多端共享时,不需要每个端再各自维护一套散乱的 locale 逻辑。
从 Oak 的整体设计目标来看,这其实和它对 Entity、dependency、feature 的处理方式完全一致:尽量把“看似分散的共性能力”统一纳入框架体系中。
一个实践建议
在 Oak 项目里,最推荐的做法不是“哪里缺文案就临时补一段字符串”,而是:
- 页面文案放页面目录;
- 组件文案放组件目录;
- 全局文案放
src/locales/*; - 修改后执行
make:locale、build和upgrade:locale; - 提交和发布时同时保留
src/data/i18n.json与src/data/i18n.ts。
完整同步顺序是:
npm run make:locale
npm run build
npm run upgrade:locale
前两步更新并编译 locale 数据,最后一步才把 i18n 行同步到数据库。
这样一来,国际化资源的组织方式会和 Oak 其它部分一样清晰,不容易在项目变大后失控。