国际化

Monorepo 下的多语言支持策略、翻译管理与接入实操指南

策略概览

项目使用 next-intl(Next.js 官方推荐的国际化方案)实现多语言支持,当前内置两种语言:

语言Locale 代码货币
简体中文(默认)zhCNY
EnglishenUSD

翻译文件采用两层模型:共享翻译(所有应用共用)+ 应用级覆盖(某个应用需要特殊文案时使用)。这种分层设计适配 monorepo 多项目场景,避免翻译文件膨胀和维护混乱。

目录结构

en.json
zh.json
messages.ts
en.json
zh.json
  • 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.jsonshared/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.mdxpage.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>
  );
}

常见问题

相关资源