国际化
Monorepo 下的多语言支持策略、翻译管理与接入实操指南
策略概览
项目使用 next-intl(Next.js 官方推荐的国际化方案)实现多语言支持,当前内置两种语言:
| 语言 | Locale 代码 | 货币 |
|---|---|---|
| 简体中文(默认) | zh | CNY |
| English | en | USD |
翻译文件采用两层模型:共享翻译(所有应用共用)+ 应用级覆盖(某个应用需要特殊文案时使用)。这种分层设计适配 monorepo 多项目场景,避免翻译文件膨胀和维护混乱。
目录结构
packages/i18n/translations/shared/— 跨应用共用的 UI 文案,如按钮、导航、通用错误提示等。apps/01mvp-web/src/modules/i18n/translations/— 仅在当前应用中使用的文案,如品牌词、业务术语。
应用层翻译文件只维护与共享层的差异部分,不要把共享层的完整内容复制过去,否则后续更新翻译会非常麻烦。
合并与优先级
请求语言为 locale 时,最终消息按以下顺序合并(后者覆盖前者):
加载共享默认语言 -- shared/defaultLocale
加载共享目标语言 -- shared/locale(覆盖上一步的同名 key)
加载应用默认语言 -- app/defaultLocale(覆盖共享层的同名 key)
加载应用目标语言 -- app/locale(最高优先级)
缺失的翻译 key 会回退到 key 字符串本身(比如 documentation.title),不会中断页面渲染。这在开发阶段很方便,但上线前记得用 pnpm i18n:analyze 检查有没有漏翻。
Key 规范
翻译 key 必须使用嵌套结构按业务域分组,不要用扁平化 key(如 "loginTitle")。嵌套结构方便按模块管理和查找。
使用嵌套对象按业务域分组:
{
"auth": { "login": { "title": "登录" } },
"documentation": { "title": "文档" }
}组件调用:const t = useTranslations("auth.login"); return <p>{t("title")}</p>;
useTranslations("auth.login") 指定命名空间后,后续调用 t("title") 实际读取的是 auth.login.title。
新增语言
在 config 中添加 locale
编辑 apps/01mvp-web/src/lib/config/index.ts,在 i18n.locales 对象中新增语言条目:
i18n: {
enabled: true,
locales: {
en: { currency: "USD", label: "English" },
zh: { currency: "CNY", label: "简体中文" },
ja: { currency: "JPY", label: "日本語" }, // 新增
},
defaultLocale: "zh",
defaultCurrency: "CNY",
localeCookieName: "NEXT_LOCALE",
},创建翻译文件
在共享层和应用层分别新建对应的 JSON 文件:
# 共享翻译
touch packages/i18n/translations/shared/ja.json
# 应用覆盖(按需)
touch apps/01mvp-web/src/modules/i18n/translations/ja.json可以先从 en.json 复制一份再逐条翻译。
验证 next-intl 路由识别
项目的路由配置从 config.i18n.locales 读取 locale 列表(见 apps/01mvp-web/src/modules/i18n/routing.ts),无需额外修改路由配置。新增的 locale 会自动生效。
使用 LocaleSwitch 测试
页面右上角的语言切换器(LocaleSwitch 组件)会自动读取 config 中的 locale 列表。新增的语言会自动出现在下拉菜单中。切换后页面刷新即加载新语言。
添加翻译文案
在共享翻译文件中添加 key
翻译 key 使用嵌套结构按业务域分组:
{
"auth": {
"login": {
"title": "Sign In",
"submit": "Continue"
}
}
}在 packages/i18n/translations/shared/en.json 和 shared/zh.json 中同时添加对应的 key。
在组件中使用 useTranslations() hook
import { useTranslations } from "next-intl";
export function LoginForm() {
const t = useTranslations("auth.login");
return (
<div>
<h1>{t("title")}</h1>
<button>{t("submit")}</button>
</div>
);
}运行检查命令验证覆盖
cd apps/01mvp-web && pnpm i18n:analyze该命令检查代码中使用的翻译 key 是否在所有语言文件中存在,缺失的 key 会输出为警告。
MDX 文档国际化
Fumadocs 根据当前语言自动选择 page.zh.mdx 或 page.en.mdx。
LocaleSwitch 组件
LocaleSwitch 是一个下拉菜单组件,位于 packages/ui-shared/src/LocaleSwitch.tsx,提供语言切换功能。
工作原理:
- 自动读取
config.i18n.locales中定义的语言列表 - 使用
useLocale()hook 获取当前语言 - 切换时调用
onLocaleChange回调(用于更新 locale cookie),然后刷新页面
放置位置:通常放在页面头部导航栏中。如果应用使用了 ConfigContext 提供配置,LocaleSwitch 会自动读取可用语言列表,无需额外传参。
import { LocaleSwitch } from "@01mvp/ui-shared";
export function Header() {
return (
<nav>
{/* 其他导航内容 */}
<LocaleSwitch />
</nav>
);
}