Rocket.Chat 国际化(i18n)工程实践指南:翻译键的存储、命名、插值与自动校验

发布时间:2026/9/9 13:44:13

Rocket.Chat 国际化(i18n)工程实践指南:翻译键的存储、命名、插值与自动校验
Rocket.Chat 国际化i18n工程实践指南翻译键的存储、命名、插值与自动校验【免费下载链接】Rocket.ChatThe Secure CommsOS™ for mission-critical operations项目地址: https://gitcode.com/GitHub_Trending/ro/Rocket.ChatRocket.Chat 的翻译体系以rocket.chat/i18n包为核心为 Web 客户端、Meteor 服务器以及omnichannel-transcript等微服务提供共享的语言资源。本文是仓库内 docs/i18n.md 的深度展开先梳理 68 个语言文件如何组织、为何en是唯一事实来源再逐一讲解翻译键的命名规范、五大命名空间、i18next 插值与复数规则随后结合源码剖析三个运行时如何初始化、服务端为何必须显式传lng最后介绍内置 i18n 代码检查器所强制的全部规则。读者读完既能写出一条符合规范的翻译键也能解释构建时类型生成、lint:fix自动排序等底层机制。客户端特有的Trans组件与转义约定不在本文范围详见 docs/frontend/i18n.md。翻译资源在哪里、如何组织所有翻译都是扁平 JSON 文件放在packages/i18n/src/locales/目录下一个语言一个文件按language.i18n.json命名。当前仓库内共有 68 个语言文件从af.i18n.json南非荷兰语到zh.i18n.json简体中文其间覆盖ar、de、fr、ja、ko、pt-BR、ru、tr等主流语言以及zh-HK、zh-TW等地区变体。这些文件描述的是键key与字符串value的映射而非文档中的占位符介绍因此任何消费rocket.chat/i18n的运行时——无论是浏览器还是 Node 服务——看到的都是同一份资源集合。en.i18n.json是唯一的基准语言en.i18n.json是base language也是新增功能时唯一允许手工编辑的文件。围绕它形成三条硬性约定缺键回退任何 locale 缺失的键都会回退到en每个运行时的初始化参数都带fallbackLng: en。多余键即陈旧某个 locale 中存在、但en中不存在的键会被判定为过时残留由检查器的wipe-extra-keys任务删除。顺序继承其他 67 个语言文件的键顺序完全由en推导而来所以重排en里的键就等于重排所有语言文件。这也意味着为新功能添加翻译时只需要在en.i18n.json里追加键。其他语言的翻译由外部流程单独提供不要为自己的功能手写其他语言的翻译。键的联合类型是从en构建时生成的仓库中packages/i18n/src/resources.ts是一个刻意保留的假文件dummy内容只是core.key1、onboarding.key1之类的占位联合类型。真正的键联合类型RocketchatI18nKeys是在构建阶段由脚本根据en.i18n.json实时生成到dist/resources.d.ts中的。在src/scripts/build.mts中可以看到这段逻辑遍历 base language 的每个键输出一个RocketchatI18n接口再取keyof得到RocketchatI18nKeys。需要特别注意的是拼错的键不是编译错误。虽然packages/i18n/src/index.ts通过模块增强给 i18next 的TFunction追加了以RocketchatI18nKeys为参数类型的重载但这是添加过载而非收窄签名——文档明确指出这是为类型检查性能而刻意避免的收窄。因此验证键名要靠 grep 基准语言文件而不是编译器。刚添加的键在重新构建包之前也不会进入生成的类型yarn workspace rocket.chat/i18n build该构建还会把每个 locale 逐一写入dist/resources/并生成dist/languages.js语言清单文件。翻译键的命名规范代码库主导的命名约定是大写下划线式Capitalized snake case即Sentence_case_with_underscores这也是Sentence_case_with_underscores被i18next识别为普通字符串的示例形式{ Cam_on: Camera on, Delete_room: Delete room, You_are_offline_please_reconnect: You are offline, please reconnect }用含义命名而不是用字面值或渲染位置命名命名的第一原则是让键描述语义而不是描述当前文案更不是描述它渲染在哪个按钮上。Delete_room在文案从 Delete room 改成 Remove channel 时依然成立而Delete_room_red_button这样的键会随着 UI 细节变化立刻失效。这类键的前三个示例在en.i18n.json中都能直接检索到如Cam_on: Camera on。第二原则是含义完全相同时复用已有键。但仅仅因为英文恰好相同就复用是危险的——按上下文变形的语言如需要性、数、格配合的语种会需要分开的键。更关键的是事后拆开一个被共享的键对所有语言文件都是一次破坏性变更代价极高。命名空间恰好五个键可以被最多五种命名空间之一作为前缀以点号分隔i18next 的nsSeparator: .core默认 ·onboarding·registration·cloud·subscription{ onboarding.component.form.action.next: Next, subscription.callout.title.limitsReached: Limits reached }无前缀的键自动落在core命名空间。这套集合定义在packages/i18n/src/index.tsnamespacesMap记录了这五个命名空间defaultTranslationNamespace为core。命名空间的目的是让客户端按需加载资源子集例如只用core和onboarding而不是充当一般性的分组工具——从源码extractTranslationNamespaces的实现看它只是按前缀把扁平键拆回五个对象。还要注意命名空间内部键的风格差异core里用大写下划线而命名空间内部如onboarding、subscription的键沿用现有条目使用小写点号路径onboarding.component.form.action.next。插值Interpolation运行时文案需要动态值时使用 i18next 的命名占位符{{likeThis}}占位符名称采用 camelCase{ Room_removed: Room {{roomName}} removed from ABAC management }三种占位符形态与三种废弃形态基础语言里至今还残存两类废弃写法新增键时严禁模仿形态状态{{name}}✅ 正确应使用__name__❌ 已废弃由检查器自动改写replace-2-underscores%s❌ 传统 sprintf基于位置传参属历史遗留sprintf形式目前在运行时仍然有效无论是客户端还是 Meteor 服务器都安装并启用了i18next-sprintf-postprocessor通过packages/i18n/src/index.ts导出的addSprinfToI18n把t包裹起来——当参数是一个数组时它会把t(key, replaces)转成t(key, { postProcess: sprintf, sprintf: replaces })。但它是位置式的翻译者一旦调整句子语序参数就会悄悄错位。因此不要新增任何%s键。当前 base locale 中仍可直接 grep 到 6 处%s由find-sprintf-params任务持续标记为 backlog。另外部分键名也内嵌了旧标记例如Added__username__to_team、__count__result_found两者在en.i18n.json中都能检索到。这仅是命名上的历史遗留其值使用的是{{...}}占位符语义正确。新键不要模仿这种命名。严禁用碎片拼接句子词序并不是普适的而翻译者只能看到你拼出来的碎片。下面这种写法是错误的${t(Deleted)} ${count} ${t(messages)};正确做法是让一个键承载整个句子t(Messages_deleted, { count });携带计数的键还需要配套复数形式因此Messages_deleted在语言文件里应定义为一个复数对象见下文复数化。需要区分的是用「标签键 运行时值」组合出Label: value这样的键值对是允许的把一段散文拆到多个键里才是不允许的。格式化器Formatters占位符后加逗号即可挂载 i18next 格式化器。所有运行时都内置基于Intl的内建格式化器{ Exceeded_limits: Your workspace exceeded the {{val, list}} license limits., Seats_used: {{count, number}} seats used }项目里还有一个自定义格式化器capitalize但只在客户端注册见apps/meteor/client/providers/TranslationProvider.tsx。它存在的意义是某些语言需要不同的词序翻译者可以在翻译文件内部把落在句首的那个词首字母大写而无需改代码。当前en中没有键使用它。注意不要在一个服务器也会渲染的键里用它——服务器没有注册该格式化器值会原样透传、不生效。复数化Pluralization需要随数量变化文案时把键定义成一个复数形式对象并在调用时传入count由 i18next 依据该语言在 CLDR 中的复数规则挑选形态{ message_counter: { one: {{count}} message, other: {{count}} messages } }对英语而言只有one和other两种其他语言则不同——例如阿拉伯语有六种复数形态。这正是不能手写判断的原因count 1 ? t(message_counter_one) : t(message_counter_other);上面是错误示范。正确写法是把决策交给 i18nextt(message_counter, { count });特殊形态zeroi18next 还支持一个特殊的zero形态用于空状态文案读起来比 0 items 更自然的场景{ Calls_in_queue: { zero: Queue is empty, one: {{count}} call in queue, other: {{count}} calls in queue } }但只有当措辞确实不同时才加zero——对英语而言 0 已经能被other覆盖没必要重复定义。复数形态是按语言逐一校验的不属于该语言 CLDR 形态集的形态会被wipe-invalid-plurals剥离合法集合是zero、one、two、few、many、other其中zero为 i18next 特例而某个 locale 缺少en已定义的形态则会被find-missing-plurals报告。相关实现可以分别在src/scripts/check.mts与src/scripts/common.mts后者通过 i18next 的pluralResolver取各语言复数后缀中看到。服务端使用三个运行时与必须传 lng客户端、Meteor 服务器与独立服务共享同一份资源但初始化方式不同运行时初始化客户端apps/meteor/client/providers/TranslationProvider.tsx——en随包静态内置非英语活动语言通过 HTTP 按需加载Meteor 服务器apps/meteor/server/lib/i18n.ts—— 启动即加载全部 68 个语言常驻内存omnichannel-transcript服务ee/apps/omnichannel-transcript/src/i18n.ts—— 与服务器相同的全量预载形态在服务器代码里应当导入共享实例而不是自己 new 一个import { i18n } from ../../app/utils/lib/i18n;服务端每次调用都要显式传lng文档直言这其实暴露了服务端 i18n 设计上的一个缺口。服务端实例以lng: en初始化且没有任何按请求取语言的上下文。漏传lng不会报错——它只是静默地返回英语。在约 200 个服务端调用点中只有大约三分之一传了lng所以周边代码不能作为可靠参照。错误示范——无论接收者是谁都返回英语i18n.t(Username_and_message_must_not_be_empty);正确示范i18n.t(Username_and_message_must_not_be_empty, { lng: user.language || settings.get(Language) || en });这条回退链——接收者的语言 → 工作区Language设置 →en——是既定的通行写法目前还没有共享的辅助函数所以每个调用点都是这么显式写出来的。选语言时遵循一条准则取阅读这段字符串的人的语言而不总是当前操作用户的语言。通知、邮件、导出文件都是渲染给接收者看的。不要在 API 边界翻译更优的做法是接口只返回键由客户端负责翻译——这也是绝大多数接口已经在做的。原因是客户端天然知道读者的语言而服务端必须被告知。因此新接口应优先返回翻译键而不是翻译后的字符串。独立的子系统packages/livechat要注意packages/livechat拥有自己的一套翻译在src/i18n/下与rocket.chat/i18n完全无关。这套体系有自己的特点语言文件是普通的language.json统一嵌套在单个translation根键下键采用lower_snake_case复数用_one/_other键后缀而非嵌套对象表达。本文描述的所有规则——包括代码检查器——对 livechat 都不适用。反过来也一样不要在这两套体系之间互相照搬约定。代码检查器linter强制了什么在packages/i18n目录下执行yarn workspace rocket.chat/i18n lint会运行 ESLint 加上src/scripts/check.mts中实现的自定义检查任务。绝大多数问题都可以用lint:fix自动修复yarn workspace rocket.chat/i18n lint:fix检查任务一览任务规则sort-base-keysen的键按字母序排序大小写不敏感sort-keys每个 locale 遵循en的键顺序wipe-extra-keys语言文件不得包含en中没有的键wipe-invalid-plurals复数形态对该语言必须合法外加zerofind-missing-plurals语言必须定义en定义的全部复数形态replace-2-underscores__name__→{{name}}missing-placeholders/extra-placeholders占位符必须与en完全一致find-duplicate-keysJSON 中不得出现重复键trim-eof文件末尾不得有尾随空白排序的两处细节与执行顺序sort-base-keys必须先于sort-keys运行因为其他所有语言文件的顺序都由en推导而来。新增的键放在en的任何位置都可以——lint:fix会自动把它挪到正确位置并同步重排其他 67 个文件。但有两处排序细节不是字母序而是 JavaScript 本身强制的对应实现见src/scripts/check.mts的isIntegerLikeKey与compareBaseKeys整数样式的键排最前如500因为JSON.parse无论文件里怎么写都会把这类键提升到对象最前面排序必须与实际 parse 结果一致才能让 lint 通过仅大小写不同的键如Private/private当前有 69 对在大小写不敏感比较下会打平需要再用纯码点比较打破平局保证顺序唯一且规范。find-sprintf-params定义了但不进默认运行有一个任务已定义却被排除在默认运行之外因此不会让构建失败——find-sprintf-params它负责标记en中残留的%s当前可实测为 6 处。它被排除是因为存在历史 backlog不应借功能 PR 顺手顺手清理它。想在不改动任何文件的前提下检查可以单跑cd packages/i18n node --experimental-transform-types ./src/scripts/check.mts -t find-sprintf-params-t参数支持传递任务名会清空默认任务集合、只执行指定的检查。提交规范仅含翻译改动的提交translation-only changes使用i18n:作为 commit 类型前缀遵循仓库 pull request 模板的约定。把 key 改动与功能逻辑改动分开提交能让翻译相关的审阅与后续语言同步都更清晰。小结一份可直接照做的检查清单最后把整篇指南浓缩成写新翻译键时的自检清单只编辑packages/i18n/src/locales/en.i18n.json追加的键用Sentence_case_with_underscores或对应命名空间内既有的小写点号路径风格语义相同就复用旧键语义不同绝不共用不要用渲染位置、颜色等 UI 特征命名动态值一律用{{camelCase}}绝不用%s、__name__或碎片拼接句子带计数的键定义成复数对象并传count把复数决策交给 i18next 的 CLDR 规则服务端渲染的文案务必按接收者语言 →Language设置 →en的链条显式传lng新接口优先返回键、在客户端翻译最后跑一次yarn workspace rocket.chat/i18n lint:fix让排序、占位符一致性、陈旧键清理等规则自动落地。【免费下载链接】Rocket.ChatThe Secure CommsOS™ for mission-critical operations项目地址: https://gitcode.com/GitHub_Trending/ro/Rocket.Chat创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

牛客Java刷题21-26:继承、多态、接口与抽象类深度解析

牛客Java刷题21-26:继承、多态、接口与抽象类深度解析

2026/9/9 13:44:13

从“照着敲都会,一运行就报错”到真正理解面向对象,是每个Java零基础学习者必须跨过的一道坎。这个系列的牛客刷题指南走到第21~26题,正好切入Java里最核心也最容易让人绕晕的一组概念:继承、多态、接口和抽象类。这篇文章就把这6…

Java Object类11个方法详解:从源码原理到实战应用

Java Object类11个方法详解:从源码原理到实战应用

2026/9/9 13:34:13

做Java开发这些年,我面试过不少候选人,也被人问过很多次“Object类有哪些方法”。这个问题看似基础,但它就像一面镜子,能照出一个人对Java语言底层设计到底理解到什么程度。毕竟Object是所有类的父类,Java里一切对象行…

Simulink异步电机定子匝间短路仿真建模全解析

Simulink异步电机定子匝间短路仿真建模全解析

2026/9/9 13:34:13

最近帮一个做电机故障诊断方向的朋友把定子匝间短路仿真的模型理了一遍,顺手把整套思路整理出来。这次要聊的是在Matlab Simulink环境下给感应电机(也就是异步电机)做定子匝间短路仿真的完整过程。电机故障诊断方向的同学和工程师应该都有印象…

机盖重拓扑P2阶段:硬表面建模布线细节与工程实践指南

机盖重拓扑P2阶段:硬表面建模布线细节与工程实践指南

2026/9/9 14:24:15

很多做硬表面建模的同学,应该都有过这种体验:高模雕刻阶段很爽,细节怎么加都行,一到重拓扑就头疼,尤其是机盖这种“看似平整、实则到处都是曲面转折”的部件。前面 P1 阶段可能已经解决了大型和整体布线框架&#xff0…

Claude Code本地代理实战:npx启动cc-switch全指南

Claude Code本地代理实战:npx启动cc-switch全指南

2026/9/9 14:24:15

1. “ruflo”到底是什么?一个被误传的AI工具名背后的真实图景最近在多个开发者社区、技术群和AI工具分享帖里,频繁出现“ruflo”这个词——它常和Claude Code、Codex、npx、Agent开发等热词捆绑出现,比如“ruflo安装失败”“ruflo Codex配置…

opencode不是工具名,而是开发协作失焦的信号

opencode不是工具名,而是开发协作失焦的信号

2026/9/9 14:24:15

1. “opencode”不是标准工具名,而是开发者在混乱生态中喊出的求救信号“opencode”这个词本身没有官方定义——它既不是 npm 官方注册包、不是 GitHub 上有明确 star 数与文档的开源项目、也不是任何主流 IDE 内置功能模块。但过去三个月里,我在技术社区…

XS2A动态沙箱搭建实战:基于XS2ABank实现PSD2合规测试

XS2A动态沙箱搭建实战:基于XS2ABank实现PSD2合规测试

2026/9/9 14:24:15

简介:这是一套面向开放银行接口开发、测试与合规验证人员的XS2A动态沙箱,以柏林集团NextGenPSD2模型为基础,模拟ASPSP的OpenAPI PSD2服务,让第三方服务商(TPP)在隔离环境中验证账户信息服务与支付发起流程。…

从Cursor套壳Kimi事件,拆解AI编程工具的模型API接入与验证

从Cursor套壳Kimi事件,拆解AI编程工具的模型API接入与验证

2026/9/9 14:24:15

早上看到群里一堆人转发同一条消息的时候,我第一反应是“又来瓜了”。紧接着点进去一看,好家伙,某国外AI编程工具在对话里承认自己是Kimi,创始人出来回应说是“忘记署名了”。Cursor套壳Kimi被全网锤这个事,刷了一整天…

新手也能上手 AI论文软件:2026年最新测评与推荐

新手也能上手 AI论文软件:2026年最新测评与推荐

2026/9/9 14:14:15

2026年真正好用的AI论文软件,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

中国人民大学杨琳团队《Nature Communications》 | 全球潮汐湿地土壤有机碳时空格局与环境驱动:一项2009-2020年的全球评估

中国人民大学杨琳团队《Nature Communications》 | 全球潮汐湿地土壤有机碳时空格局与环境驱动:一项2009-2020年的全球评估

2026/9/9 1:14:29

本文首发于“生态学者”!从“湿地面积”到“土壤碳密度”:为什么需要重新认识潮汐湿地蓝碳变化?潮汐湿地位于陆地与海洋的交汇地带,包括红树林、盐沼和潮滩,是全球重要的蓝碳生态系统。其土壤能够长期储存大量有机碳&a…

adb抓包

adb抓包

2026/9/8 4:55:53

前言 本文介绍如何通过 tcpdump 在 Android 手机上抓取网络数据包,并在电脑端使用 Wireshark 进行分析。适用于需要排查 App 网络请求、分析接口调用或调试网络问题的开发与测试场景。1. 手机要有 root 权限2. 下载 tcpdump3. adb push C:\Users\zhangkuixun\Downlo…

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战

2026/9/8 22:37:26

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战 在云原生基础设施中,容器镜像体积直接决定了服务的部署速度与弹性扩容敏捷度。对于传统的 Go / Java 微服务,镜像体积通常被严格控制在 50MB 到 200MB 以内,拉取镜像只…

扩散模型图像恢复实战:从DDPM原理到PyQt5可视化系统

扩散模型图像恢复实战:从DDPM原理到PyQt5可视化系统

2026/9/9 0:03:36

简介:面向毕业设计场景的PyQt5扩散模型图像恢复项目,提供完整Python源码与项目说明,适合图像处理、深度学习方向的高年级本科生与研究生参考。项目在模块设计上覆盖图像处理、扩散模型、参数配置、用户界面与结果评估五部分,具体涉…

开关电源环路裕量测试实战:相位裕量与增益裕量详解

开关电源环路裕量测试实战:相位裕量与增益裕量详解

2026/9/9 0:03:36

1. 项目概述:为什么环路裕量测试是电子工程师绕不开的“体检项目”“从零开始的电子工程师生活(6)——环路裕量测试”,这个标题一出来,老电源工程师可能已经下意识摸了摸示波器探头,新同事则大概率在想&…

定时插座芯片怎么选?专用定时IC与单片机MCU选型对比

定时插座芯片怎么选?专用定时IC与单片机MCU选型对比

2026/9/9 0:03:36

拆开市面上不同价位的定时插座,你会发现一个有意思的现象:有的里面躺着一颗黑色的软封装芯片,丝印都看不清;有的则是一块小小的蓝色或绿色PCB,上面赫然印着STM8或者STC的字样。同样叫"定时插座",…

远程协作的工作台整理

远程协作的工作台整理

2026/9/8 4:23:39

远程协作的工作台整理远程协作的核心不是再加一个工具,而是让交接信息足够完整。异步任务要写明目标、输入位置、完成标准和需要决策的人。 工作台的最小配置 将日程、待办、代码和沟通入口收拢到少数固定位置;通知按紧急程度分层。工作台不需要模仿办公…

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

2026/9/8 3:19:39

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

2026/9/8 4:00:23

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…