多语言
NocoBase 3 支持多语言,本章节主要介绍如何编写可翻译的文案、组织翻译文件、覆盖插件文案等。如果你只想切换语言或者加一种新语言,看内置能力 / 多语言就够了。

多语言翻译的实现
在界面上,我们看到的文字、语句,如果要让它能够被翻译,就需要在翻译文件里写上它,并在组件里用 key 引用它。
可以直接告诉 Coding Agent:
它会把文案加进 client/locales/en-US.ts 和 zh-CN.ts,再改组件。
比如文案如下:
orders.title 这样的名字就是 key,两个文件里用同一个 key 对应同一句话的不同语言。
改完的组件是这样:
需要拼接变量时,可以传递变量。文案里用 {{变量名}} 占位:
翻译文件在哪
应用自己的翻译放在 client/locales/:
en-US.ts 是基准。它导出一个类型,其他语言用这个类型标注,所以漏翻一个 key 是编译错误,pnpm typecheck 会直接报出来:
key 可以嵌套,用点号访问:t('orders.title')。
服务端也有一份 server/locales/,结构一样。接口报错、邮件正文这类由服务端产生的文字写在那里;纯界面文案不需要。
改插件里的文案
插件的措辞不一定符合你的业务习惯,此时可以在应用的翻译文件里覆盖它。
交给 Coding Agent 时,直接指界面上的那句话就行:
说清楚两件事:界面上的哪个位置、想改成什么。位置要说,因为同一个词在插件里可能对应好几个 key——菜单里的和页面标题里的是两处,不说明白容易改错或者漏改。
Agent 会在你的翻译文件里加一个 overrides 块,按插件包名分组:
覆盖在所有插件加载完之后才应用,所以不管加载顺序如何,你的措辞总是赢。
覆盖适合的是「叫法不一样」——同一个东西,插件叫「工作流」,你们公司叫「审批流程」。如果是插件压根没翻译某种语言,那应该去给插件提 issue,而不是在这里补齐。
服务端和后台任务的语言
界面的语言跟着浏览器走,服务端的不是。
接口报错跟着请求走。 在路由里拿翻译器就行,语言已经由中间件解析好了:
后台任务没有请求可依据,语言必须自己定。 定时任务、一次性后台任务、Webhook 都属于这种情况。这里有一条容易写错的规则:
发出去的内容,语言跟着收件人走,不是跟着触发这件事的人走。
A 提交申请触发了给 B 的通知,这封通知该用 B 的语言。按 A 的语言发,或者按服务端默认语言发,都是错的。
漏掉 ensureLocaleLoaded 不会报错,只会静默回落到默认语言——测试的时候不一定发现得了,所以写的时候就要记得。
一次给多个人发消息时,每个人的语言要分别处理,不能一批人共用一种语言。
加一种新语言
在 client/locales/ 加一个语言文件,并在 index.ts 里登记;服务端有自己的文案时,server/locales/ 也要加。应用支持哪些语言就是由这些文件决定的,没有别的清单需要同步。
完整步骤和给 Coding Agent 的描述见内置能力 / 多语言。
怎么确认改对了
常见问题
组件里的文案改了,页面上没变化
先确认改的是不是当前语言的文件。另外检查这段文字是不是属于某个插件——插件的文案要用 overrides 覆盖,改自己的 key 不会生效。
某个组件在测试里渲染时报错或显示 key
这个组件被单独渲染时没有翻译环境。给 t() 加一个 defaultValue 就能保证它始终可读:
插件导出的组件,翻译错了
比如插件 @acme/app-plugin-crm 导出的组件,有个翻译的 key 是 orders.title。而应用本身也有一个同名的 key。翻译的归属跟着渲染位置走,不跟着代码归属走。如果不做显式处理的话,插件的翻译可能会被应用的翻译覆盖。
这种组件需要显式指明自己的命名空间——也就是插件的包名:
判断方法:这个组件会不会在插件自己的页面之外被渲染?会的话就要指明。
相关链接
- 内置能力 / 多语言——切换语言、新增语言、设置默认语言
- 接口——服务端报错怎么写
- 定时任务——定时触发的业务动作如何定义和观测

