Internationalization (i18n) Guide
How OpenBuddy localizes — single-file dict, locale switcher, SSR.
I18N.md7 分钟阅读更新于 2018-10-20本页目录
This guide explains how OpenBuddy's UI strings are organized, how to add a new translation, and how to become a language maintainer.
Overview
OpenBuddy uses a custom in-house i18n system built on @openbuddy/ui-locale. It supports:
- ICU MessageFormat (plurals, gender, selects)
- Lazy locale loading (initial bundle stays small)
- Plural-rule awareness (CLDR)
- Right-to-left (RTL) layout (experimental)
- Date / number / currency formatting via
Intl.*
The current UI ships in:
| Locale | Code | Status | Coverage |
|---|---|---|---|
| English | en |
✅ Default | 100% |
| 简体中文 | zh-CN |
✅ Shipped | 100% |
| 日本語 | ja |
🚧 In progress | 35% |
| 한국어 | ko |
🚧 In progress | 28% |
| Español | es |
🚧 In progress | 22% |
| Deutsch | de |
🚧 In progress | 18% |
Coverage is measured nightly by pnpm i18n:coverage.
File layout
src/locales/
├── en-US.json # English (default)
├── zh-CN.json # 简体中文
└── …Each locale is a single JSON file. Strings are organized into top-level keys by feature:
common— shared strings (buttons, errors)composer— chat composersettings— settings panelsconversation— chat history / messagespermission— permission promptsauth— authentication flowsbilling— billing & creditstenant— tenant / org selectormarketplace— plugin marketplaceskill— skill libraryscene— scene tabsabout— about / version info
Adding a new locale
- Open an issue with the
i18nandlocale:labels, naming your language and intended maintainer. - Fork the repo.
- Create
src/locales/<your-locale>.json(copysrc/locales/en-US.json). - Translate every string. Keep keys identical; only change values.
- Test locally:
pnpm i18n:check <your-locale>. - Open a PR with title
i18n(<locale>): initial translation. - Become maintainer — you'll be added to the
@louloulin/i18n-<locale>team and granted merge rights forsrc/locales/<your-locale>.jsononly.
Translation guidelines
Do
- ✅ Match the register of the original (formal/informal).
- ✅ Use standard locale conventions (date formats, decimal separators, etc.).
- ✅ Preserve placeholders verbatim:
{name},{count, plural, one {# item} other {# items}}. - ✅ Test plurals for
one,few,many,other— singular/plural isn't 1:1 in many languages. - ✅ Keep brand names untranslated (OpenBuddy, Pi, Cordis, WorkBuddy, …).
- ✅ Keep code identifiers untranslated (provider names, IPC channel names).
Don't
- ❌ Translate brand names or technical proper nouns.
- ❌ Add new keys that don't exist in
en/. New keys must first be added toen/. - ❌ Use machine translation as the final word — always human-review.
- ❌ Embed HTML or Markdown in translation values. Use the
richformatter instead.
Naming conventions
| Language | Native name | Code |
|---|---|---|
| English | English | en |
| 简体中文 | 简体中文 | zh-CN |
| 繁體中文 | 繁體中文 | zh-TW |
| 日本語 | 日本語 | ja |
| 한국어 | 한국어 | ko |
| Español | Español | es |
| Français | Français | fr |
| Deutsch | Deutsch | de |
| Português | Português | pt |
| Русский | Русский | ru |
| العربية | العربية | ar |
| हिन्दी | हिन्दी | hi |
Plural rules
ICU MessageFormat is used for plurals:
{
"files.count": "{count, plural, one {# file} other {# files}}",
"files.count_compact": "{count, plural, one {# file} other {# files}}",
"minutes.ago": "{minutes, plural, one {# minute ago} other {# minutes ago}}"
}Verify your plural forms against the CLDR plural rules table.
Right-to-left (RTL)
RTL is supported via dir="rtl" on the root element. The CSS uses logical properties (margin-inline-start, padding-block-end) which automatically flip.
For an RTL locale:
- Set
direction: "rtl"in your locale config. - Verify all icons (especially directional arrows) have RTL counterparts.
- Test on at least one RTL layout (Arabic, Hebrew).
Becoming a language maintainer
- Submit 3 substantial PRs of translation work.
- File a Discussion titled
i18n: request maintainership for <locale>. - Get 👍 from 2 current maintainers.
- A maintainer adds you to
@louloulin/i18n-<locale>. - You can now merge
src/locales/<your-locale>.jsondirectly.
Tools
| Tool | Command | Purpose |
|---|---|---|
| Coverage report | pnpm i18n:coverage |
Show % translated per locale |
| Missing keys | pnpm i18n:missing <locale> |
List keys present in en/ but not in <locale>/ |
| Stale keys | pnpm i18n:stale |
Find keys in <locale>/ that aren't in en/ anymore |
| Format check | pnpm i18n:format |
Validate JSON files |
| Lint | pnpm i18n:lint |
Check for hardcoded strings in src/ |
| Sync | pnpm i18n:sync |
Pull new keys from en/ into all locales (mark as TODO) |
概述
OpenBuddy 用自研的 @openbuddy/ui-locale 做国际化。支持:
- ICU MessageFormat(复数、性别、select)
- 按需加载 locale(初始 bundle 体积小)
- CLDR 复数规则
- RTL(右到左)布局(实验性)
Intl.*日期/数字/货币格式化
当前 UI 已发布的语言:
| Locale | 代码 | 状态 | 覆盖率 |
|---|---|---|---|
| English | en |
✅ 默认 | 100% |
| 简体中文 | zh-CN |
✅ 出货 | 100% |
| 日本語 | ja |
🚧 进行中 | 35% |
| 한국어 | ko |
🚧 进行中 | 28% |
| Español | es |
🚧 进行中 | 22% |
| Deutsch | de |
🚧 进行中 | 18% |
覆盖率每晚由 pnpm i18n:coverage 测算。
文件结构
src/locales/
├── en-US.json # English(默认)
├── zh-CN.json # 简体中文
└── …每个 locale 一个 JSON 文件。字符串按 feature 划分顶层 key:
(同英文列表)
新增 locale
- 开 Issue 带
i18n与locale:标签,注明语言与计划维护者。 - Fork 仓库。
- 新建
src/locales/<你的-locale>.json(拷src/locales/en-US.json)。 - 翻译 所有字符串。键保持不变,只改值。
- 本地测试:
pnpm i18n:check <你的-locale>。 - 提 PR,标题
i18n(<locale>): initial translation。 - 成为维护者 —— 你会被加进
@louloulin/i18n-<locale>团队,获得src/locales/<你的-locale>/的合并权。
翻译规范
应该
- ✅ 匹配原文语域(正式/非正式)
- ✅ 使用本地习惯(日期格式、小数分隔符等)
- ✅ 保留占位符原样:
{name}、{count, plural, one {# item} other {# items}} - ✅ 测
one、few、many、other—— 单复数在很多语言中不是 1:1 - ✅ 品牌名保持原文(OpenBuddy、Pi、Cordis、WorkBuddy……)
- ✅ 代码标识保持原文(Provider 名、IPC 通道名)
不要
- ❌ 翻译品牌名或技术专有名词
- ❌ 新增
en/中不存在的 key —— 必须先在en/加 - ❌ 把机器翻译当最终结果 —— 始终人审
- ❌ 在翻译值里塞 HTML 或 Markdown。用
rich格式化器。
语言命名规范
| 语言 | 本地名称 | 代码 |
|---|---|---|
| English | English | en |
| 简体中文 | 简体中文 | zh-CN |
| 繁體中文 | 繁體中文 | zh-TW |
| 日本語 | 日本語 | ja |
| 한국어 | 한국어 | ko |
| Español | Español | es |
| Français | Français | fr |
| Deutsch | Deutsch | de |
| Português | Português | pt |
| Русский | Русский | ru |
| العربية | العربية | ar |
| हिन्दी | हिन्दी | hi |
复数规则
复数用 ICU MessageFormat:
{
"files.count": "{count, plural, one {# file} other {# files}}",
"minutes.ago": "{minutes, plural, one {# minute ago} other {# minutes ago}}"
}对照 CLDR 复数规则表 校验。
RTL(右到左)
RTL 通过根元素 dir="rtl" 支持。CSS 用逻辑属性(margin-inline-start、padding-block-end),自动翻转。
RTL locale:
- 在 locale config 中设
direction: "rtl"。 - 验证所有图标(尤其是方向箭头)有 RTL 对应版本。
- 至少在一个 RTL 布局(阿拉伯语、希伯来语)上测。
成为语言维护者
- 提交 3 个实质性翻译 PR。
- 在 Discussion 开贴,标题
i18n: request maintainership for <locale>。 - 获得 2 位当前维护者点赞。
- 维护者把你加进
@louloulin/i18n-<locale>。 - 你可以直接合并
src/locales/<你的-locale>/**。
工具
| 工具 | 命令 | 用途 |
|---|---|---|
| 覆盖率报告 | pnpm i18n:coverage |
显示每个 locale 的翻译率 |
| 缺失 key | pnpm i18n:missing <locale> |
列出在 en/ 但 <locale>/ 没有的 key |
| 过期 key | pnpm i18n:stale |
找 <locale>/ 中已经不在 en/ 的 key |
| 格式校验 | pnpm i18n:format |
验证 JSON 文件 |
| Lint | pnpm i18n:lint |
检查 src/ 中的硬编码字符串 |
| 同步 | pnpm i18n:sync |
从 en/ 拉新 key 到所有 locale(标记 TODO) |
Help us reach every developer. / 帮我们触达每一位开发者。
认领一种语言?开 Discussion 标签 i18n。