用 Claude Code 实现 i18n:Next.js 多语言实战指南
用 Claude Code 为 Next.js 实现 i18n,覆盖 next-intl 路由、翻译检查、真实场景和常见坑。
先把 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 为默认语言,添加 en 和 de。当前 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 只放导航、语言选择、通用按钮等真正共享的文案;首页、价格页、文档页则用 HomePage、PricingPage、DocsPage 这类命名空间。命名空间就是一组相关翻译键的名字,它能让审核者快速知道某个文案属于哪个界面。
{
"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.NumberFormat 或 next-intl formatter。
第二个是技术文档站。middleware、proxy、locale、namespace 这类词不一定要硬翻译。更好的做法是首次出现时用中文解释,比如“locale 是语言代码”,之后保留原词,方便读者搜索官方资料。
第三个是后台管理界面。这里最重要的不是 SEO,而是避免误操作。“删除”“停用”“撤销邀请”等按钮必须精确。确认弹窗、toast、审计日志也要纳入同一套翻译键,不能只翻页面标题。
内容站也很典型。文章正文之外,title、description、canonical、alternate links、OGP 图片和内部链接都要同步检查。可以把规则写进CLAUDE.md 最佳实践,让 Claude Code 每次更新多语言文章时都遵守同一流程。
常见坑与验证结果
| 坑 | 后果 | 对策 |
|---|---|---|
| URL 设计后置 | 发布后需要大量重定向 | 先决定 prefix、子域名或独立域名 |
| key 名太抽象 | title2、text3 无法审核 | 用页面名和含义命名 |
| 过度依赖 fallback | 缺失翻译在生产中被隐藏 | CI 中把缺失 key 当失败处理 |
| 手动拼日期和金额 | 语序和分隔符错误 | 使用 Intl 或 formatter |
| metadata 未翻译 | 搜索结果仍是默认语言 | generateMetadata 也要国际化 |
我用一个小型消息文件验证过上面的检查脚本:从英文文件删除 HomePage.cta 后,脚本能正确报错。它不能判断“预约咨询”在所有市场是否自然,这部分仍然需要人工审核。更实用的分工是:Claude Code 和 CI 负责机械错误,人类负责 CTA、价格、法律文案和文化差异。想把流程固化到团队里,可以继续阅读Claude Code 审核工作流或从ClaudeCodeLab 咨询开始整理发布流程。
免费 PDF: Claude Code 速查表
输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。
我们会妥善保护你的信息,不发送垃圾邮件。
把 Claude Code 变成真正能带来结果的工作流
先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。
关于作者
Masa
专注 Claude Code 实务流程、团队导入和内容转化的工程师。
相关文章
从Obsidian到CLAUDE.md的Claude Code流程:不再反复解释上下文
把 Obsidian 工作笔记整理成 CLAUDE.md 运行说明,让 Claude Code 每次都带着正确上下文开始。
Claude Code 收入 CTA 路由:从文章分流到 PDF、Gumroad 与咨询
用 Claude Code 按读者意图把文章流量分到免费 PDF、Gumroad 教材或咨询入口。
Claude Code 团队交接规则: 把审查证据、权限、回滚和收入路径一起交付
面向团队的 Claude Code 交接格式: 证据、权限、回滚、免费 PDF、Gumroad 与咨询路径都要可审查。