ToolJet 本地化(Localization)贡献指南:从零为 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
ToolJet 是一款用于构建内部工具、仪表盘、业务应用、工作流与 AI Agent 的开源企业级应用生成平台,其前端界面基于 i18next 构建了一套完整的国际化(i18n)机制。本文基于 ToolJet 官方文档中的本地化贡献指南,结合仓库内真实的前端源码与翻译文件,完整讲解如何为 ToolJet 添加一门新语言——从创建语言 JSON 文件、翻译关键词,到在语言注册表中登记并最终出现在产品界面,帮助你以最低成本参与开源贡献,让 ToolJet 更贴近全球不同语言与文化的用户。读完本文,你将掌握一整套可复现、可提交的本地化贡献流程,并理解语言文件在运行时是如何被加载与切换的。
本地化机制概览:ToolJet 是如何支持多语言的
ToolJet 的本地化采用标准的「翻译键值 + 语言文件 + 运行时加载」模型:
- 界面中的所有可翻译文本都以**翻译键(key)**的形式写在组件代码中,而不是直接写死文字;
- 每种语言对应一个独立的 JSON 文件,存放「键 → 目标语言文本」的映射;
- 前端启动时通过 i18next 加载对应语言的 JSON 文件,并支持在界面中随时切换语言。
以仓库现状为例,frontend/assets/translations 目录下目前已经包含 10 个语言文件:en.json、fr.json、es.json、it.json、id.json、uk.json、ru.json、de.json、zh.json以及注册表languages.json。en.json是全仓库最完整的基础语言文件(约 1077 行),其他语言文件均以它为蓝本翻译而来,因此en.json也是你新增语言时唯一的翻译基准。
添加翻译的完整步骤
第一步:定位 translations 目录
进入仓库根目录下的frontend目录,再进入assets,在 assets 内部即可找到translations目录。它的结构如下:
frontend |-- assets |-- -- translations |-- -- -- languages.json |-- -- -- en.json其中languages.json是语言注册表(决定哪些语言会出现在产品界面上),各语言的 JSON 文件(如en.json)存放实际的翻译内容。
第二步:按 ISO 639-1 语言代码创建语言文件
在translations目录内新建一个以语言代码命名的 JSON 文件。语言代码必须遵循 ISO 639-1 标准(两字母代码),例如:
en—— English(英语)fr—— French(法语)es—— Spanish(西班牙语)zh—— Chinese(中文)de—— German(德语)
假设我们要把 ToolJet 本地化为法语:在translations目录下新建文件并命名为fr.json,fr即法语的 ISO 639-1 语言代码。
第三步:复制 en.json 作为翻译模板
打开仓库中的 en.json,将其中全部内容复制到新建的fr.json中。这一步非常关键——它保证了新语言文件与基础语言文件的键结构完全一致,不会因为遗漏某个键而导致界面出现空白或回退。
en.json采用嵌套的键组织方式,顶层大致包含如下结构:
{ "globals": { "readDocumentation": "Read documentation", "cancel": "Cancel", "save": "Save", "search": "Search", "delete": "Delete", "add": "Add" }, "errorBoundary": "Something went wrong.", "viewer": "Sorry!. This app is under maintenance", "app": { "updateAvailable": "Update available", "newVersionReleased": "A new version of ToolJet has been released." } }可以看到,globals中存放的是全站通用的高频词(保存、取消、编辑、删除等),而app等命名空间则存放某个功能模块的专属文案。翻译时只需替换每个键右侧的值(value),键名(key)必须原样保留。
第四步:逐条翻译关键词
复制完成后,即可开始把fr.json中的英文值逐条替换为法语。以仓库中真实存在的 fr.json 为例:
{ "globals": { "readDocumentation": "Lire la documentation", "cancel": "Annuler", "save": "Sauvegarder", "back": "Retour", "edit": "Éditer", "search": "Recherche", "delete": "Effacer", "add": "Ajouter", "enabled": "Activé", "disabled": "Désactivé", "yes": "Oui", "submit": "Soumettre", "select": "Sélectionner", "saving": "Enregistrement...", "saveDatasource": "Enregistrer la source de données", "authorize": "Autoriser", "connect": "Relier", "send": "Envoyer" }, "errorBoundary": "Quelque chose s'est mal passé." }翻译时请特别注意:
- 保持键名不变:只修改 value,绝不改动 key;
- 保持 JSON 合法:法语中大量使用撇号(如
s'est、l'interface),必须转义为\'或使用双引号包裹正确的转义形式,否则 JSON 解析会失败,导致整个语言文件无法加载; - 注意特殊字符编码:é、è、ç、ü 等 Unicode 字符应保持 UTF-8 编码,仓库内语言文件均以 UTF-8 存储;
- 上下文一致性:同一个键在界面多处复用,翻译措辞要尽量中性、通用,与组件使用场景匹配。
第五步:在 languages.json 中注册语言
翻译完成后,只差最后一步——将新语言登记到 languages.json 中,它才会出现在 ToolJet 界面的语言选择器里。
你需要向languageList数组中追加一个包含三个键值对的对象:
| 字段 | 含义 | 示例 |
|---|---|---|
lang | 语言的英文名称 | French |
code | ISO 639-1 语言代码 | fr |
nativeLang | 该语言母语者视角的名称 | Français |
以法语为例,注册后的languages.json应如下所示:
{ "languageList": [ { "lang": "English", "code": "en", "nativeLang": "English" }, { "lang": "French", "code": "fr", "nativeLang": "Français" } ] }对照仓库中当前的 languages.json 实际内容,完整注册表包含 9 种语言:
{ "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" } ] }注意:code必须与你新建的 JSON 文件名完全一致(例如注册fr,文件就必须是fr.json),语言选择器正是通过该 code 来拼接翻译文件路径并加载的。
源码纵深:语言文件在运行时如何被加载与切换
理解完贡献流程后,我们再从源码层面看这套机制是如何运作的,这能帮助你在翻译时理解「为什么这样注册」「界面如何找到你的语言」。
i18next 初始化与翻译文件加载
前端应用入口 frontend/src/index.jsx 中完成了 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':只按语言代码(如fr)加载,不细分地区变体(如fr-FR);fallbackLng: 'en':当目标语言的某个键缺失时,自动回退到英文,避免界面出现空白;lng: language:初始语言取自服务端配置的LANGUAGE,缺省为en;loadPath: ${path}assets/translations/{{lng}}.json:翻译文件按${语言代码}.json的命名约定从assets/translations目录加载——这就是为什么你的文件名必须与注册的code严格一致的底层原因。
语言选择器组件:切换语言的完整调用链
界面右上角的语言切换入口由 frontend/src/_components/LanguageSelection.jsx 组件实现,它揭示了你新增语言后用户侧看到的效果:
- 组件挂载时通过
fetch('/assets/translations/languages.json')拉取注册表,并读取data.languageList; - 用当前
i18n.language在列表中查找对应语言;如果当前语言未被注册,则回退到en:const filteredLanguage = languageRef.current.find((ln) => ln.code === lang); if (filteredLanguage === undefined) { setLanguage(languageRef.current.find((ln) => ln.code === 'en')); } else { setLanguage(filteredLanguage); } - 用户点击某个语言后调用
i18n.changeLanguage(lang.code)完成切换:const onLanguageSelection = (lang) => { setLanguage(lang); i18n.changeLanguage(lang.code); handleClose(); }; - 弹窗内还提供了语言搜索能力,分别按
lang(英文名)、nativeLang(母语名)和code三种字段做前缀匹配过滤。
这段源码同时也印证了注册表的重要性:如果新语言没有写进languages.json的languageList,即使fr.json文件已创建,用户也无法在选择器中找到并切换到该语言。
翻译键在界面中的使用方式
翻译键通过 react-i18next 的useTranslationHook 消费。例如LanguageSelection.jsx中弹窗标题与搜索框占位符:
const { t } = useTranslation(); // ... <Modal.Title>{t('header.languageSelection.changeLanguage', 'Change language')}</Modal.Title> // ... <SearchBox placeholder={t('header.languageSelection.searchLanguage', 'Search language')} />t()的第一个参数是翻译键路径,第二个参数是回退文案(当键不存在时显示)。这意味着:即使某条翻译暂时缺失,界面也不会崩溃,而是显示英文回退——这也是建议以en.json为完整蓝本逐条翻译的原因,翻译覆盖越全,用户界面体验越一致。
提交前的质量检查清单
在提交本地化贡献前,建议按以下清单自检:
- 文件命名:语言文件位于 frontend/assets/translations 下,文件名为两字母 ISO 639-1 代码(如
fr.json); - JSON 合法性:可用
node -e "JSON.parse(require('fs').readFileSync('frontend/assets/translations/fr.json'))"验证文件可被正确解析; - 键完整性:与
en.json逐键比对,确保没有遗漏或多余的键; - 注册登记:
languages.json的languageList中已添加包含lang、code、nativeLang三个字段的对象,且code与文件名一致; - 编码:文件以 UTF-8 保存,非 ASCII 字符(如法语重音字符)显示正常;
- 特殊字符转义:撇号、引号等已按 JSON 规则转义。
相关参考文件
- 本地化指南文档:docs/versioned_docs/version-3.0.0-LTS/contributing-guide/l10n.md
- 翻译文件目录:frontend/assets/translations
- 语言注册表:languages.json
- 英文基准文件:en.json
- 法语翻译示例:fr.json
- i18n 初始化源码:frontend/src/index.jsx
- 语言选择器组件:frontend/src/_components/LanguageSelection.jsx
按照上述流程,你就可以为 ToolJet 贡献一门新的语言,帮助更多地区的用户以母语使用这一开源平台。如果你在本地化过程中遇到任何问题,也可以随时联系 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),仅供参考