为网站新增一种语言
以日语为例,补齐语言配置、界面资源、文档内容和发布验证。
这一篇给网站增加日语。目标不只是让语言菜单多一个选项,还要让界面、链接、业务文案和需要发布的内容一起工作。
当前模板启用英语、简体中文和繁体中文,具体以你项目的 config/i18n.ts 为准。下面以 ja 为新语言代码,文件名、配置和内容目录都使用同一个代码。
一、先区分界面与内容
| 内容 | 所在位置 | 例子 |
|---|---|---|
| 可用语言与默认语言 | config/i18n.ts | 语言代码、名称、书写方向 |
| 界面文案 | locales/<语言>.json | 导航、表单、提示、邮件模板文案 |
| 文档 | content/docs/<语言>/ | 教程正文和侧边栏标题 |
| 博客 | content/blog/<语言>/ | 文章标题、摘要、正文 |
增加语言配置不会自动翻译 JSON,也不会生成对应的文档和博客。先决定这次发布哪些内容,再准备资源。
二、补齐日语资源
检查 locales/ja.json 是否存在。有文件不代表翻译已完成,先确认它是否只是空对象或部分占位内容。
以 locales/en.json 的键结构为基准,补齐日语文件。保留对象层级、键名、数组结构和插值变量,只修改要显示的文本。
如果跟做了前面几篇教程,在日语文件中还需要合并以下工作台文案:
"pages": {
"workspace": {
"title": "マイワークスペース",
"description": "保存したリンクをここで管理できます。"
}
}这只是需要合并的片段,完整日语文件还应包含其他已有页面的键。付费教程新增的 recipes.savedLinks 也要翻译,不要只检查模板原本包含的文案。
翻译时特别注意:
- 插值名称保持一致,例如
{count}、{name},不要翻译括号中的名称。 - HTML 或富文本模板中的标签、链接和变量保持完整。
- 多个复数分支之间的
|是消息语法,普通竖线需要按既有写法转义为{'|'}。 - JSON 必须有效,不能加入注释或尾随逗号。
现有服务端和客户端加载器会按 locales/*.json 发现资源,不需要在加载器中手工添加日语分支。
三、启用语言
在 config/i18n.ts 的 websiteI18nDefinitions.languages 中启用或添加:
{
code: 'ja',
name: '日本語',
direction: 'ltr',
},如果文件里已有这段注释,取消注释即可,不要再添加重复项。本次只增加可选语言,保留原有 defaultLanguage。更改默认语言会影响默认入口和回退行为,应作为另一项明确改动处理。
语言类型会从配置推导。组件通过项目现有翻译接口读取文案,不需要写 locale === 'ja' 的判断。
完成配置后重启开发服务器,打开 /ja/,再访问上一教程的 /ja/workspace。确认页面路由与文案都正确。
四、检查组件是否使用翻译资源
服务端页面继续使用:
serverLocalized({ c, id: 'pages.workspace.title' })已有客户端组件继续使用 Localized、localized 或项目的翻译 Hook,不创建一套新的语言映射对象。
如果某段文案仍然是英文或中文,先定位它是否直接写在 JSX 或配置的 value 中。将需要翻译的文案放进语言资源,再使用资源键引用。日期和数字显示也应沿用现有格式化工具或 Intl,使用当前语言作为参数。
页面链接使用现有国际化路由工具,例如 getFullPath('/workspace', locale)。业务 API 通常仍是 /api/...,不要为了日语另建 /ja/api/...。现有请求封装负责携带语言信息,自己写请求时则需要按 API 的语言约定传递 Accept-Language。
五、准备日语文档和博客
如果网站发布文档,创建 content/docs/ja/。从现有语言目录复制你实际要发布的文章及其目录配置,保留对应文件的编号和路径,再翻译 frontmatter、正文与 meta.json 中的显示标题。
建议先做一个小而完整的内容集合:
content/docs/ja/
├── meta.json
└── 000_index.mdx从同项目已有语言目录复制这两个文件最稳妥,再将 meta.json 的 pages 调整为日语目录中实际存在的页面。不要照搬一份指向尚未翻译章节的完整侧边栏。
添加语言时,逐个检查文档内链。日语目录中的链接只能指向 /ja/docs/... 下实际存在的日语文章,或当前文章内部的锚点。目标尚未翻译时,先补齐译文,或暂时将链接改为纯文字说明。不要用其他语言的文章作为替代,也不要生成不存在的页面地址。各语言对应文章的锚点 ID 统一使用相同的英文名称。
博客也采用相同思路,在 content/blog/ja/ 中添加准备发布的文章。只有项目启用了博客且本次确实要发布日语博客时,才需要增加这些内容。每种语言有哪些文章,以自己的内容目录为准。
六、检查缺失的资源键
可以在项目根目录新建临时文件 check-ja-keys.mjs,用于比较英文和日语资源的结构:
import { readFileSync } from 'node:fs';
const readLocale = (name) => JSON.parse(
readFileSync(new URL(`./locales/${name}.json`, import.meta.url), 'utf8'),
);
function collect(value, prefix = '', result = new Map()) {
if (value !== null && typeof value === 'object') {
result.set(prefix, Array.isArray(value) ? 'array' : 'object');
for (const [key, child] of Object.entries(value)) {
collect(child, prefix ? `${prefix}.${key}` : key, result);
}
} else {
result.set(prefix, typeof value);
}
return result;
}
const source = collect(readLocale('en'));
const target = collect(readLocale('ja'));
const missing = [...source.keys()].filter((key) => !target.has(key));
const mismatched = [...source.keys()].filter(
(key) => target.has(key) && target.get(key) !== source.get(key),
);
const extra = [...target.keys()].filter((key) => !source.has(key));
console.log({ missing, mismatched, extra });
if (missing.length || mismatched.length) process.exitCode = 1;执行:
node check-ja-keys.mjsmissing 是缺失键,mismatched 是结构或值类型不一致,extra 则需要判断是否为过时的键。这个检查只能发现结构问题,无法确认翻译质量,也不能代替插值变量和实际界面的人工检查。使用完可以删除临时脚本。
七、生成内容并验证发布
执行项目检查:
npm run lint
npm run typecheck
npm run build构建会生成内容集合和搜索索引。单独排查文档编译或搜索问题时,可以运行:
npm run gen:search-index如果部署使用 KV 文档内容,本地同步使用 npm run kv:sync:local,远程同步使用 npm run kv:sync:remote。已有 deploy:update 流程会处理发布所需的同步,单独运行构建并不等于远程内容已更新。
最后按真实用户流程验收:
- 从语言菜单切换到日语,能打开首页和工作台。
- 登录、注册、表单校验、空状态和失败提示都有对应文案。
- 购买区域显示日语名称和说明,金额与实际套餐仍一致。
- 文档侧边栏只列出有效页面,正文链接和图片正常。
- 日语搜索能找到已发布的日语文档。
- 邮件等不在当前页面中展示的文案,也按对应流程检查。
- 英语、简体中文和繁体中文入口仍能正常使用。
遇到部分页面回退到其他语言时,先检查资源键、内容是否存在和发布同步情况。不要在组件里为日语加临时分支,否则下一次增加语言还会重复修改业务代码。更多机制说明见国际化和文档。