功能指南

国际化接入

为应用添加语言支持、管理翻译文件和配置多语言内容的实操指南

概览

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

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

翻译文件采用两层模型:共享翻译(所有应用共用)+ 应用级覆盖(某个应用需要特殊文案时使用)。更详细的分层策略说明请参阅 国际化策略

翻译文件结构

en.json
zh.json
messages.ts
en.json
zh.json
  • packages/i18n/translations/shared/ — 跨应用共用的 UI 文案,如按钮、导航、通用错误提示等。
  • apps/01mvp-web/src/modules/i18n/translations/ — 仅在当前应用中使用的文案,如品牌词、业务术语。

应用层翻译文件只维护与共享层的差异部分,不要把共享层的完整内容复制过去,否则后续更新翻译会非常麻烦。

新增语言

在 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 使用嵌套结构按业务域分组,不使用扁平化 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>
  );
}

useTranslations("auth.login") 指定命名空间后,后续调用 t("title") 实际读取的是 auth.login.title

运行检查命令验证覆盖

cd apps/01mvp-web && pnpm i18n:analyze

该命令检查代码中使用的翻译 key 是否在所有语言文件中存在,缺失的 key 会输出为警告。

如果某个翻译 key 在目标语言中缺失,页面不会报错,而是回退显示 key 字符串本身(如 auth.login.title)。开发阶段很方便,但上线前务必用 pnpm i18n:analyze 检查。

翻译覆盖规则

当用户请求语言为 locale 时,最终翻译消息按以下顺序加载,后者覆盖前者

  1. 共享默认语言shared/{defaultLocale}.json(即 shared/zh.json
  2. 共享目标语言shared/{locale}.json(同名 key 覆盖上一步)
  3. 应用默认语言app/{defaultLocale}.json(覆盖共享层的同名 key)
  4. 应用目标语言app/{locale}.json(最高优先级)

对于非默认语言,如果目标语言缺少某个 key,系统会自动回退到默认语言的值。这意味着你只需要翻译有差异的 key。

在邮件模板中使用翻译

邮件模板的翻译 key 统一放在 mail 命名空间下。邮件发送时会自动加载对应 locale 的翻译:

// apps/01mvp-web/src/lib/mail/send.ts
import { getMessagesForLocale } from "@01mvp/i18n";

在邮件模板中通过 messages.mail.* 访问翻译内容。邮件翻译的 key 格式示例:

{
  "mail": {
    "common": {
      "useLink": "请使用以下链接:",
      "openLinkInBrowser": "或复制以下链接到浏览器中打开:"
    },
    "magicLink": {
      "body": "您请求了一次魔法链接登录。",
      "login": "立即登录"
    }
  }
}

邮件模板的翻译独立于 UI 翻译,使用 Proxy 实现优雅的 key 缺失回退——即使某个邮件翻译 key 不存在,也不会导致发送失败,而是显示 locale.mail.key 格式的提示文本。

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>
  );
}

常见问题

相关资源