独家合作CubenceAI 中转平台立减 20%访问

开发文档

国际化指南

国际化指南

本文档介绍 Claude Code Hub 的国际化(i18n)架构设计与实现,帮助开发者理解和扩展多语言支持。


概述

Claude Code Hub 使用 next-intl 实现国际化,支持以下特性:

  • 5 种语言:简体中文(默认)、繁体中文、英语、俄语、日语
  • URL 路由前缀:所有页面路径包含语言前缀(如 /zh-CN/dashboard
  • Cookie 持久化:用户语言偏好通过 NEXT_LOCALE Cookie 保存
  • 自动检测:根据浏览器 Accept-Language 头自动选择语言

技术栈

核心依赖

版本用途
next-intl^4.x核心 i18n 库
next-intl/plugin-Next.js 插件集成
next-intl/server-服务端翻译加载
next-intl/navigation-类型安全的路由导航

App Router 集成

// next.config.ts
import createNextIntlPlugin from "next-intl/plugin";

const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts");

export default withNextIntl(nextConfig);

目录结构

i18n 配置目录

src/i18n/
├── config.ts          # 语言配置和类型定义
├── routing.ts         # 路由配置和导航工具
├── request.ts         # 服务端请求配置
├── index.ts           # 统一导出
└── README.md          # 详细技术文档

翻译文件目录

messages/
├── zh-CN/             # 简体中文(默认语言)
│   ├── index.ts       # 命名空间聚合导出
│   ├── common.json    # 通用文本
│   ├── auth.json      # 认证相关
│   ├── dashboard.json # 仪表盘
│   ├── settings.json  # 设置页面
│   ├── providers.json # 供应商管理
│   ├── users.json     # 用户管理
│   ├── errors.json    # 错误消息
│   ├── ui.json        # UI 组件
│   └── ...            # 其他命名空间
├── zh-TW/             # 繁体中文
├── en/                # 英语
├── ru/                # 俄语
└── ja/                # 日语

支持的语言代码

语言代码语言名称原生名称
zh-CNChinese (Simplified)简体中文
zh-TWChinese (Traditional)繁體中文
enEnglishEnglish
ruRussianРусский
jaJapanese日本語

翻译文件格式

JSON 结构规范

翻译文件使用 JSON 格式,支持嵌套键和插值变量:

// messages/zh-CN/auth.json
{
  "login": {
    "title": "登录",
    "description": "输入管理员令牌以访问系统",
    "tokenLabel": "管理员令牌",
    "tokenPlaceholder": "请输入管理员令牌",
    "submitButton": "登录",
    "loggingIn": "登录中...",
    "success": "登录成功",
    "error": "登录失败,请检查令牌是否正确"
  },
  "logout": {
    "confirm": "确定要退出登录吗?",
    "success": "已退出登录"
  },
  "errors": {
    "loginFailed": "登录失败",
    "networkError": "网络错误,请稍后重试",
    "invalidToken": "无效的认证令牌"
  }
}

命名空间聚合

每个语言目录的 index.ts 文件聚合所有命名空间:

// messages/zh-CN/index.ts
import auth from "./auth.json";
import common from "./common.json";
import dashboard from "./dashboard.json";
import errors from "./errors.json";
import settings from "./settings.json";
import ui from "./ui.json";
// ... 其他命名空间

export default {
  auth,
  common,
  dashboard,
  errors,
  settings,
  ui,
  // ... 其他命名空间
};

插值变量

支持在翻译文本中使用占位符:

{
  "greeting": "你好,{name}!",
  "itemCount": "共 {count} 项",
  "pageInfo": "第 {current} 页,共 {total} 页",
  "minLength": "{field}长度不能少于{min}个字符"
}

使用翻译

服务端组件

在服务端组件中使用 useTranslationsgetTranslations

// 方式一:useTranslations(推荐用于 React 组件)
import { useTranslations } from "next-intl";

export default function ServerComponent() {
  const t = useTranslations("auth");

  return (
    <div>
      <h1>{t("login.title")}</h1>
      <p>{t("login.description")}</p>
    </div>
  );
}

// 方式二:getTranslations(用于非组件函数)
import { getTranslations } from "next-intl/server";

export async function generateMetadata() {
  const t = await getTranslations("common");
  return {
    title: t("siteTitle"),
  };
}

客户端组件

客户端组件使用方式相同,但需要 "use client" 指令:

"use client";

import { useTranslations } from "next-intl";
import { Link } from "@/i18n/routing";

export default function ClientComponent() {
  const t = useTranslations("dashboard");

  return (
    <div>
      <h1>{t("title")}</h1>
      <Link href="/settings">{t("goToSettings")}</Link>
    </div>
  );
}

Server Actions

在 Server Actions 中使用 getTranslations

"use server";

import { getTranslations } from "next-intl/server";

export async function createUser(formData: FormData) {
  const t = await getTranslations("errors");

  try {
    // 业务逻辑...
  } catch (error) {
    return { error: t("CREATE_USER_FAILED") };
  }
}

带变量的翻译

const t = useTranslations("ui");

// 使用插值变量
<span>{t("pagination.pageInfo", { current: 1, total: 10 })}</span>
// 输出: "第 1 页,共 10 页"

<span>{t("pagination.total", { total: 100 })}</span>
// 输出: "共 100 项"

添加新语言

步骤一:创建翻译文件目录

# 以添加韩语 (ko) 为例
mkdir -p messages/ko

步骤二:复制并翻译文件

# 复制英语作为模板
cp messages/en/*.json messages/ko/
cp messages/en/index.ts messages/ko/

# 然后翻译每个 JSON 文件

步骤三:更新配置

编辑 src/i18n/config.ts

// 添加新语言到 locales 数组
export const locales = ["zh-CN", "zh-TW", "en", "ru", "ja", "ko"] as const;

// 更新类型定义(自动推导)
export type Locale = (typeof locales)[number];

// 添加语言标签
export const localeLabels: Record<Locale, string> = {
  "zh-CN": "简体中文",
  "zh-TW": "繁体中文",
  en: "English",
  ru: "Русский",
  ja: "日本語",
  ko: "한국어",  // 新增
};

// 添加英文名称
export const localeNamesInEnglish: Record<Locale, string> = {
  "zh-CN": "Chinese (Simplified)",
  "zh-TW": "Chinese (Traditional)",
  en: "English",
  ru: "Russian",
  ja: "Japanese",
  ko: "Korean",  // 新增
};

步骤四:测试验证

# 启动开发服务器
bun run dev

# 访问新语言路由
open http://localhost:23000/ko/dashboard

翻译完整性检查

添加新语言后,务必确保所有命名空间的翻译文件都已创建,且键值与其他语言保持一致。缺失的翻译键会显示为 namespace.key 格式。


翻译键管理

命名约定

规则示例说明
使用 camelCaseloginButton键名使用驼峰命名
按功能分组login.title, login.error使用点号分隔层级
动作使用动词save, delete, confirm按钮等操作用动词
状态使用形容词loading, empty, error状态描述用形容词

命名空间划分

命名空间用途示例键
common通用按钮、操作save, cancel, confirm
auth认证相关login.title, logout.confirm
dashboard仪表盘页面overview, statistics
settings设置页面general, appearance
providers供应商管理create, edit, test
users用户管理list, create, delete
errors错误消息UNAUTHORIZED, NOT_FOUND
uiUI 组件table.pagination, empty.title

复用策略

  1. 通用文本放 common:如 savecancelloading
  2. 页面特定文本放对应命名空间:如 dashboard.overview
  3. 错误消息统一放 errors:便于集中管理
  4. UI 组件文本放 ui:如分页、表格、空状态

日期和数字格式化

日期格式化

import { useFormatter } from "next-intl";

function DateDisplay() {
  const format = useFormatter();
  const date = new Date();

  return (
    <span>
      {format.dateTime(date, {
        year: "numeric",
        month: "short",
        day: "numeric",
      })}
    </span>
  );
}

数字格式化

import { useFormatter } from "next-intl";

function NumberDisplay() {
  const format = useFormatter();

  return (
    <div>
      {/* 货币格式 */}
      <span>{format.number(1234.56, { style: "currency", currency: "USD" })}</span>

      {/* 百分比格式 */}
      <span>{format.number(0.856, { style: "percent" })}</span>

      {/* 紧凑格式 */}
      <span>{format.number(1000000, { notation: "compact" })}</span>
    </div>
  );
}

格式化配置

可以在 src/i18n/request.ts 中预定义常用的日期和数字格式,然后在组件中引用。


语言切换

UI 实现

项目提供了 LanguageSwitcher 组件:

// src/components/ui/language-switcher.tsx
import { LanguageSwitcher } from "@/components/ui/language-switcher";

// 在导航栏或设置页面使用
<LanguageSwitcher size="sm" />

组件特性

  • 下拉选择器显示所有支持的语言
  • 使用原生语言名称显示(如"简体中文"而非"Chinese")
  • 切换时自动更新 URL 和 Cookie
  • 保持当前页面路径不变
  • 支持键盘导航(Tab、Enter、方向键)

路由处理

语言切换时,next-intluseRouter 自动处理:

"use client";

import { useRouter, usePathname } from "@/i18n/routing";
import { useLocale } from "next-intl";

function LanguageSwitcher() {
  const router = useRouter();
  const pathname = usePathname();
  const currentLocale = useLocale();

  const handleLocaleChange = (newLocale: string) => {
    // 自动更新 URL 前缀和设置 Cookie
    router.push(pathname, { locale: newLocale });
  };

  // ...
}

语言偏好通过 Cookie 持久化:

// src/i18n/routing.ts
localeCookie: {
  name: "NEXT_LOCALE",
  maxAge: 365 * 24 * 60 * 60,  // 1 年
  path: "/",
  sameSite: "lax",
}

最佳实践

翻译完整性检查

开发时启用错误警告:

// src/i18n/request.ts
onError:
  process.env.NODE_ENV === "development"
    ? (error) => {
        console.error("i18n error:", error);
      }
    : undefined,

// 缺失翻译时显示键名
getMessageFallback: ({ namespace, key }) => {
  return `${namespace}.${key}`;
},

占位符使用规范

// 好的做法:使用有意义的变量名
{
  "greeting": "你好,{userName}!",
  "fileSize": "文件大小:{size} MB",
  "duration": "持续时间:{hours} 小时 {minutes} 分钟"
}

// 避免:使用无意义的变量名
{
  "greeting": "你好,{0}!",
  "fileSize": "文件大小:{x} MB"
}

避免硬编码文本

// 错误:硬编码文本
<button>保存</button>

// 正确:使用翻译
const t = useTranslations("common");
<button>{t("save")}</button>

路由导航使用 i18n 工具

// 错误:使用 next/link
import Link from "next/link";
<Link href="/dashboard">Dashboard</Link>

// 正确:使用 i18n/routing
import { Link } from "@/i18n/routing";
<Link href="/dashboard">Dashboard</Link>  // 自动添加语言前缀

相关资源

Previous
扩展开发