ToolJet 前端本地化(L10n)贡献指南:从零为产品新增一套界面语言翻译
【免费下载链接】ToolJetOpen-source foundation of ToolJet AI - the enterprise app generation platform for internal tools, dashboards, business applications, workflows and AI agents. Build visually, from a prompt, or from Claude Code, Codex and Cursor over MCP 🚀项目地址: https://gitcode.com/GitHub_Trending/to/ToolJet
本文对应官方贡献指南 docs/docs/contributing-guide/l10n.md,带你完整走一遍 ToolJet 的本地化流程。作为面向全球用户的开源低代码平台,ToolJet 的前端界面文本全部由 JSON 翻译文件驱动,社区贡献者只需要“建文件、复制模板、逐条翻译、注册语言”四步,就能让整个产品界面呈现一门新语言。读完本文,你将掌握翻译文件的目录结构与 JSON 命名空间约定、languages.json 注册机制、i18next 的加载与回退原理,以及如何通过环境变量把某门语言设为实例默认语言。
ToolJet 的本地化机制概览
ToolJet 前端的国际化(i18n)并不是一个黑盒,而是基于成熟的i18next + react-i18next + i18next-http-backend组合实现的按需加载方案。三条核心链路在源码中可以逐一验证:
- 翻译资源:所有语言文案存放在 frontend/assets/translations 目录下,每门语言一个
<语言码>.json文件,另有一个languages.json维护“支持语言清单”。 - 运行时加载:前端启动入口 frontend/src/index.jsx 中完成
i18n初始化,通过loadPath拼接assets/translations/{{lng}}.json,让 i18next 在运行期按需 HTTP 拉取对应语言的 JSON。 - 界面切换:语言选择组件 frontend/src/_components/LanguageSelection.jsx 从
languages.json读取清单、渲染语言弹窗,并在用户选中后调用i18n.changeLanguage(lang.code)实现全局即时切换。
对贡献者来说,你不需要改动任何 React 组件或构建逻辑——本地化的全部工作都集中在 JSON 资源文件上。这也是官方将其定位为“最容易上手的贡献方式”的原因。
frontend |-- assets | |-- translations | | |-- languages.json # 语言注册清单 | | |-- en.json # 英文(模板基准) | | |-- fr.json # 法语 | | |-- ...docs/static/img/l10n/files.png直观展示了该目录的真实组织形态:
翻译文件命名与目录约定
本地化工作的第一步是“按规范命名新语言文件”,文件位置与命名规则如下:
- 目录:
frontend/assets/translations(仓库根目录下frontend包内的静态资源目录); - 文件名:必须是
语言代码.json,语言代码遵循ISO 639-1标准(两位小写字母); - 清单:同时维护
languages.json,只有登记在清单中的语言才会出现在产品界面的语言选择器里。
当前仓库中已内置了 9 门语言的翻译文件(可实际查看 frontend/assets/translations):
| 语言 | 文件 | 语言 | 文件 |
|---|---|---|---|
| 英语 | en.json | 乌克兰语 | uk.json |
| 法语 | fr.json | 俄语 | ru.json |
| 西班牙语 | es.json | 德语 | de.json |
| 意大利语 | it.json | 中文 | zh.json |
| 印度尼西亚语 | id.json |
提示:官方文档示例以法语(
fr,ISO 639-1 中 French 的代码)展开,本文后续步骤保持一致。
新增一门语言的四个步骤
第 1 步:创建<languagecode>.json翻译文件
进入frontend/assets/translations目录,新建一个以目标语言代码命名的 JSON 文件。以法语为例,创建fr.json(fr即法语的语言代码):
frontend/assets/translations/fr.json # 新建第 2 步:复制en.json作为翻译模板
打开现有的en.json,将全部内容原样复制到新文件中。en.json是 ToolJet 的“母本”语言文件,其中包含了全部界面文案的键(key),键层级多、条目多(当前仓库中该文件约 1000+ 行),因此永远不要把英文翻译文件当成可丢弃的临时文件——它是新增语言唯一的键来源。官方文档同样强调:新语言文件必须以en.json为起点逐条翻译,避免漏键。
docs/static/img/l10n/en.png展示了en.json的真实内容形态(顶部是globals命名空间中的通用按钮文案):
第 3 步:逐条翻译键值
复制完成后,在fr.json中把每个 key 右侧的英文值替换为对应的法语文本。文件采用嵌套命名空间结构组织,例如抽取自仓库真实en.json的开头部分:
{ "globals": { "readDocumentation": "Read documentation", "cancel": "Cancel", "save": "Save", "savechanges": "Save changes", "execute": "Execute", "edit": "Edit", "search": "Search", "add": "Add", "delete": "Delete" }, "errorBoundary": "Something went wrong.", "viewer": "Sorry!. This app is under maintenance", "app": { "updateAvailable": "Update available", "newVersionReleased": "A new version of ToolJet has been released.", "readReleaseNotes": "Read release notes & update", "skipVersion": "Skip this version" } }真实文件共有 25 个顶级命名空间,覆盖全局按钮、错误页、登录注册、编辑器、头部导航、首页、工作流仪表盘、组件管理、日期选择器等全部界面区域:
globals · errorBoundary · viewer · app · stripe · openApi · slack · googleSheets zendesk · profile · verificationSuccessPage · loginSignupPage · editor · header homePage · workflowsDashboard · confirmationPage · onBoarding · redirectSso · oAuth2 widgetManager · widget · leftSidebar · datepicker · notifications翻译时的三条纪律:
- 只改值、不动键:key 是前端代码里
t(...)查表的“身份证”,改名会导致文案丢失并回退到英文; - 保留插值占位符:部分值包含
{{xxx}}模板变量(例如 Slack 授权文案中的{{whiteLabelText}},见 en.json 的slack命名空间),翻译时必须原样保留,否则运行期无法注入动态内容; - 尽量同步最新键:仓库里
en.json是键最全的母本(25 个顶级命名空间),而部分旧语言文件缺少后来新增的命名空间(如法语/德语文件目前为 21 个)。得益于 i18next 的fallbackLng: 'en'配置(见 frontend/src/index.jsx),缺失的键会自动显示英文而非空白——但若要保证完整体验,翻译时应以最新en.json为基准补齐。
第 4 步:在languages.json中注册语言
翻译完成后,还需要把语言登记到 frontend/assets/translations/languages.json。官方要求为每种语言添加一个包含三个键值对的对象:
| 字段 | 含义 | 示例(法语) |
|---|---|---|
lang | 语言名称(英文表述,用于与语言列表展示) | "French" |
code | 语言代码(须与 JSON 文件名一致) | "fr" |
nativeLang | 该语言的母语名称(用于语言选择器中以母语展示) | "Français" |
languages.json整体结构是一个languageList数组。以“英语 + 法语”为例:
{ "languageList": [ { "lang": "English", "code": "en", "nativeLang": "English" }, { "lang": "French", "code": "fr", "nativeLang": "Français" } ] }仓库当前真实的注册清单即为该结构的完整范例,读者可打开 languages.json 对照:
{ "languageList": [ { "lang": "English", "code": "en", "nativeLang": "English" }, { "lang": "French", "code": "fr", "nativeLang": "Français" }, { "lang": "Spanish", "code": "es", "nativeLang": "Español" }, { "lang": "Italian", "code": "it", "nativeLang": "Italiano" }, { "lang": "Indonesian", "code": "id", "nativeLang": "Bahasa Indonesia" }, { "lang": "Ukrainian", "code": "uk", "nativeLang": "Українська" }, { "lang": "Russian", "code": "ru", "nativeLang": "Русский" }, { "lang": "German", "code": "de", "nativeLang": "Deutsch" }, { "lang": "Chinese", "code": "zh", "nativeLang": "Chinese" } ] }完成以上四步后,新语言即已可被 ToolJet 前端识别与加载。docs/static/img/l10n/list.png展示了语言选择器的实际交互形态——注意每条语言同时展示了lang(英文名)与nativeLang(母语名)两行文本,这与 LanguageSelection.jsx 的渲染逻辑一一对应:
源码视角:新语言如何被前端“看见”
为了让新加的翻译真正生效,需要理解前端三条调用链。以下均可直接在仓库中查阅源码验证。
① i18next 的初始化与加载路径
在 frontend/src/index.jsx 中,应用从服务端拉取的public_config读取LANGUAGE配置作为初始语言(缺省en),然后初始化 i18next:
const language = config.LANGUAGE || 'en'; const path = config?.SUB_PATH || '/'; i18n .use(Backend) .use(initReactI18next) .init({ load: 'languageOnly', fallbackLng: 'en', lng: language, backend: { loadPath: `${path}assets/translations/{{lng}}.json`, }, });几个关键配置项的含义:
load: 'languageOnly':只按两位语言代码匹配,例如浏览器上报en-US也只会去加载en.json,不会尝试加载en-US.json;fallbackLng: 'en':当前语言缺失某条文案时自动回退英文,这正是“漏键不报错、只显示英文”的兜底机制;loadPath:基于 HTTP 的后端按需加载模板,{{lng}}在运行期替换为语言代码(如fr),因此只要 JSON 文件放在该目录、命名正确,前端无需重新注册资源映射;path(SUB_PATH):当 ToolJet 部署在子路径(sub-path)下时,翻译文件同样走子路径前缀,保证静态资源可访问。
② 语言选择器的清单读取与切换
frontend/src/_components/LanguageSelection.jsx 是整个本地化 UI 的核心实现,包含三段逻辑:
- 拉取清单:组件挂载时
fetch('/assets/translations/languages.json'),将languageList存入引用; - 当前语言回显:以
i18n.language(缺省en)在清单中查找当前语言,找不到则回退到英文条目; - 实时切换:
onLanguageSelection中调用i18n.changeLanguage(lang.code)完成全局语言切换,界面上所有经useTranslation()与t(...)取值的文案会立即重渲染。
组件还提供了语言搜索能力:用户可按lang、nativeLang或code的前缀过滤语言列表(LanguageSelection.jsx),例如输入fr即可快速定位法语。
③ 页面中“取文案”的方式
业务组件通过react-i18next的useTranslation()获取t函数来读取翻译,并支持“命名空间 key + 英文兜底默认值”的写法。例如语言选择器自身的标题:
const { t } = useTranslation(); // 读取 header.languageSelection.changeLanguage,缺省英文 "Change language" t('header.languageSelection.changeLanguage', 'Change language');对应的 key 定义位于en.json的header.languageSelection命名空间:
"header": { "languageSelection": { "changeLanguage": "Change language", "searchLanguage": "Search language" } }这解释了为什么新增语言的 JSON 文件必须保留与en.json完全一致的键结构——t()的路径就是 JSON 里的嵌套路径,任何一个层级写错都会导致取不到值而回退英文。
设置部署实例的默认语言(LANGUAGE 环境变量)
除了用户在界面手动切换外,ToolJet 还允许自托管实例的管理员通过环境变量设定全站默认语言。在官方部署环境变量文档 docs/docs/setup/env-vars.md 中给出了如下说明:
| 变量 | 说明 |
|---|---|
LANGUAGE | 期望的默认语言代码(LANGUAGE_CODE) |
例如将实例默认界面语言设为法语,设置LANGUAGE=fr即可;运行时前端启动逻辑会读取该值作为i18n.init的lng(对应 frontend/src/index.jsx 的config.LANGUAGE)。该变量与languages.json中的code必须一一对应,未登记的语言代码不会命中任何翻译文件。
注意:官方文档注明,云版本(cloud)上不开放自定义默认语言的选项,此项仅适用于可自行控制环境变量的自托管部署。
贡献前自检清单与常见陷阱
向官方仓库提交本地化改动前,建议逐项核对:
- 文件名:是否使用 ISO 639-1 两位语言代码,如
fr.json、zh.json; - 键完整性:新文件是否以当前最新
en.json为基准整体复制,再逐条翻译; - JSON 合法性:翻译文本中的引号、换行需要正确转义,文件必须是合法 JSON(可先用
JSON.parse校验,避免拖拽符号破坏结构); - 键未被改写:
t('header.languageSelection.changeLanguage', ...)这类调用路径对应的嵌套结构未被改动; - 插值占位符保留:
{{whiteLabelText}}一类模板变量保持原样; - 注册清单:
languages.json的languageList中已添加含lang/code/nativeLang的对象,且code与文件名严格一致; - 本地验证:本地启动
frontend后打开语言选择器,确认新语言出现在列表中、切换后无大面积英文回退(个别回退属于“键未翻译”,应在语言文件中补齐)。
关于“浏览器语言自动检测”的现状说明
官方文档正文中有一段被注释掉(HTML 注释区块内)的描述:ToolJet 会自动检测浏览器默认语言并切换,若浏览器语言不可用则回退英文。在本文所依据的仓库版本中,该行为尚未作为正式文档承诺对外发布(相关章节仍处于注释状态),且前端初始语言实际由服务端下发的LANGUAGE配置决定。因此社区贡献者在本地开发时可先通过语言选择器手动切换验证,不必依赖浏览器自动检测。
小结
ToolJet 的本地化贡献流程可以被精确概括为一条命令链级别的操作闭环:
- 在 frontend/assets/translations 创建
语言代码.json; - 复制 en.json 全部内容作为翻译母本;
- 按“只改值、不动键、保留插值占位符”的原则逐条翻译;
- 在 languages.json 中追加
{ lang, code, nativeLang }注册项。
后端兜底由 frontend/src/index.jsx 的 i18next 初始化负责(缺省英文、HTTP 按需加载),界面展示由 LanguageSelection.jsx 负责(清单拉取、搜索、changeLanguage即时切换)。整个过程中无需触碰任何组件源码,风险低、易评审,是进入 ToolJet 社区贡献的最佳切入点之一。
【免费下载链接】ToolJetOpen-source foundation of ToolJet AI - the enterprise app generation platform for internal tools, dashboards, business applications, workflows and AI agents. Build visually, from a prompt, or from Claude Code, Codex and Cursor over MCP 🚀项目地址: https://gitcode.com/GitHub_Trending/to/ToolJet
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考