Use Cases (更新: 2026/6/2)

用 Claude Code 实现 i18n:Next.js 多语言实战指南

用 Claude Code 为 Next.js 实现 i18n,覆盖 next-intl 路由、翻译检查、真实场景和常见坑。

用 Claude Code 实现 i18n:Next.js 多语言实战指南

先把 i18n 当成架构任务

i18n 是 internationalization 的缩写,中文通常叫国际化。它不是把页面文字翻译成几种语言那么简单。真正上线时,你还要处理 URL 结构、语言识别、SEO metadata、日期和货币格式、复数规则、内部链接,以及翻译缺失时的失败策略。Claude Code 很适合这类工作,因为它能同时阅读多个文件、编辑代码、运行检查命令并总结风险。但前提是,你要先告诉它目标架构,而不是只说“帮我翻译”。

Masa 在内容站和 SaaS 页面中踩过的坑是:先让 AI 生成一堆 JSON,页面看起来能跑,几周后才发现 URL、CTA、价格和 metadata 没有统一规则。更稳定的做法是,让 Claude Code 一次性建立“路由基础、翻译键、页面迁移、检查脚本、审核清单”。这里的基础可以理解为多语言功能的地基,地基稳定之后,新增英语、德语、印尼语时就不会每次重新设计。

本文以 Next.js App Router 和 next-intl 为例。审核代码时建议同时打开Claude Code 官方文档next-intl 路由设置Next.js 国际化指南和 MDN 的 Intl 参考。这些官方资料能帮助你确认 proxy.ts、locale 参数、日期和货币格式是否符合当前版本。

flowchart LR
  A[需求整理] --> B[路由设计]
  B --> C[翻译 JSON]
  C --> D[页面和 metadata]
  D --> E[翻译缺口检查]
  E --> F[SEO 与发布审核]

Next.js 与 next-intl 的基础文件

下面的例子以 ja 为默认语言,添加 ende。当前 Next.js 16 文档使用 proxy.ts 来处理这类路由拦截;旧项目可能仍使用 middleware.ts。让 Claude Code 先确认项目版本,再决定文件名,能避免多语言页面全量 404。

src/
  app/
    [locale]/
      layout.tsx
      page.tsx
  i18n/
    navigation.ts
    request.ts
    routing.ts
  messages/
    ja.json
    en.json
    de.json
  proxy.ts

routing.ts 负责集中管理语言和路径。不要把 /en/ja 这种字符串散落在组件里,否则后续改 URL 会非常痛苦。

// src/i18n/routing.ts
import { defineRouting } from 'next-intl/routing';

export const routing = defineRouting({
  locales: ['ja', 'en', 'de'],
  defaultLocale: 'ja',
  pathnames: {
    '/': '/',
    '/pricing': {
      ja: '/pricing',
      en: '/pricing',
      de: '/preise',
    },
    '/docs': {
      ja: '/docs',
      en: '/docs',
      de: '/dokumentation',
    },
  },
});

export type Locale = (typeof routing.locales)[number];
// src/i18n/navigation.ts
import { createNavigation } from 'next-intl/navigation';
import { routing } from './routing';

export const { Link, redirect, usePathname, useRouter, getPathname } =
  createNavigation(routing);

request.ts 在每次请求中读取 locale。如果用户访问了不支持的语言,就回到默认语言,而不是导入一个不存在的 JSON 文件。

// src/i18n/request.ts
import { hasLocale } from 'next-intl';
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';

export default getRequestConfig(async ({ requestLocale }) => {
  const requested = await requestLocale;
  const locale = hasLocale(routing.locales, requested)
    ? requested
    : routing.defaultLocale;

  return {
    locale,
    messages: (await import(`../messages/${locale}.json`)).default,
  };
});
// src/proxy.ts
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';

export default createMiddleware(routing);

export const config = {
  matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)',
};

翻译键要落到真实页面

翻译文件最好按页面和用途拆分。common 只放导航、语言选择、通用按钮等真正共享的文案;首页、价格页、文档页则用 HomePagePricingPageDocsPage 这类命名空间。命名空间就是一组相关翻译键的名字,它能让审核者快速知道某个文案属于哪个界面。

{
  "common": {
    "language": {
      "label": "显示语言",
      "ja": "日本語",
      "en": "English",
      "de": "Deutsch"
    },
    "nav": {
      "docs": "文档",
      "pricing": "价格"
    }
  },
  "HomePage": {
    "title": "用多语言传递团队知识",
    "lead": "根据读者语言展示 {count} 篇文章。",
    "cta": "预约咨询"
  }
}

页面中可以用 getTranslations。它适合 Server Component,也方便后续把 generateMetadata 一起翻译。

// src/app/[locale]/page.tsx
import { getTranslations, setRequestLocale } from 'next-intl/server';

type Props = {
  params: Promise<{ locale: string }>;
};

export default async function HomePage({ params }: Props) {
  const { locale } = await params;
  setRequestLocale(locale);
  const t = await getTranslations({ locale, namespace: 'HomePage' });

  return (
    <main>
      <h1>{t('title')}</h1>
      <p>{t('lead', { count: 42 })}</p>
      <a href={`/${locale}/pricing`}>{t('cta')}</a>
    </main>
  );
}

用脚本检查翻译缺口

Claude Code 生成翻译时,人工最容易漏掉的是 key drift,也就是某个语言有 HomePage.cta,另一个语言却没有。下面的脚本会比较基准语言和其他语言的键,适合放进 CI。

// scripts/check-translations.mjs
import { readdir, readFile } from 'node:fs/promises';

const messagesDir = new URL('../src/messages/', import.meta.url);
const baseLocale = 'ja';

function flattenKeys(value, prefix = '') {
  if (value === null || typeof value !== 'object' || Array.isArray(value)) {
    return [prefix];
  }

  return Object.entries(value).flatMap(([key, child]) => {
    const nextPrefix = prefix ? `${prefix}.${key}` : key;
    return flattenKeys(child, nextPrefix);
  });
}

async function readMessages(locale) {
  const file = new URL(`${locale}.json`, messagesDir);
  return JSON.parse(await readFile(file, 'utf8'));
}

const files = await readdir(messagesDir);
const locales = files.filter((file) => file.endsWith('.json')).map((file) => file.replace(/\.json$/, ''));
const baseKeys = new Set(flattenKeys(await readMessages(baseLocale)));
let hasError = false;

for (const locale of locales.filter((item) => item !== baseLocale)) {
  const targetKeys = new Set(flattenKeys(await readMessages(locale)));
  const missing = [...baseKeys].filter((key) => !targetKeys.has(key));
  const extra = [...targetKeys].filter((key) => !baseKeys.has(key));

  if (missing.length || extra.length) {
    hasError = true;
    console.error(`\n${locale}.json has translation key drift`);
    if (missing.length) console.error('Missing:', missing.join(', '));
    if (extra.length) console.error('Extra:', extra.join(', '));
  }
}

if (hasError) process.exit(1);
console.log(`Translation keys are aligned for ${locales.length} locales.`);

这个脚本不能判断翻译是否自然,但能阻止“按钮在英文页面消失”这类低级事故。让 Claude Code 添加脚本后,再让它把命令加入 package.json 和 CI,是更完整的工作流。

三个真实使用场景

第一个是 SaaS 价格页。价格页涉及货币、税、计费周期和套餐名称,不能用字符串拼接解决。日语的表达顺序和英语、德语不同,应该使用 Intl.NumberFormatnext-intl formatter。

第二个是技术文档站。middlewareproxylocalenamespace 这类词不一定要硬翻译。更好的做法是首次出现时用中文解释,比如“locale 是语言代码”,之后保留原词,方便读者搜索官方资料。

第三个是后台管理界面。这里最重要的不是 SEO,而是避免误操作。“删除”“停用”“撤销邀请”等按钮必须精确。确认弹窗、toast、审计日志也要纳入同一套翻译键,不能只翻页面标题。

内容站也很典型。文章正文之外,title、description、canonical、alternate links、OGP 图片和内部链接都要同步检查。可以把规则写进CLAUDE.md 最佳实践,让 Claude Code 每次更新多语言文章时都遵守同一流程。

常见坑与验证结果

后果对策
URL 设计后置发布后需要大量重定向先决定 prefix、子域名或独立域名
key 名太抽象title2text3 无法审核用页面名和含义命名
过度依赖 fallback缺失翻译在生产中被隐藏CI 中把缺失 key 当失败处理
手动拼日期和金额语序和分隔符错误使用 Intl 或 formatter
metadata 未翻译搜索结果仍是默认语言generateMetadata 也要国际化

我用一个小型消息文件验证过上面的检查脚本:从英文文件删除 HomePage.cta 后,脚本能正确报错。它不能判断“预约咨询”在所有市场是否自然,这部分仍然需要人工审核。更实用的分工是:Claude Code 和 CI 负责机械错误,人类负责 CTA、价格、法律文案和文化差异。想把流程固化到团队里,可以继续阅读Claude Code 审核工作流或从ClaudeCodeLab 咨询开始整理发布流程。

#Claude Code #i18n #国际化 #多语言 #Next.js
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。