关于 i18n
i18n 是面向 Next.js App Router 项目的国际化技能,使用 next-intl v4 配置路由、翻译文件和 SEO 语言标记。适合需要将页面与用户界面扩展到多个语言版本的开发者。
主要功能
- 01配置 next-intl v4 国际化
- 02设置语言路由与中间件
- 03提取界面字符串并生成翻译文件
- 04为站点地图添加 hreflang
- 05验证翻译并检查构建
适用场景
- 01为 Next.js App Router 添加多语言路由
- 02将页面文案整理到翻译文件
- 03为各语言页面配置 SEO 标记
- 04检查翻译文件与静态页面构建
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ openclaudia/ openclaudia-skills --skill i18n安装后在 Claude Code 中输入/i18n确认已加载。
详细介绍
为 Next.js 项目添加国际化
使用 next-intl v4 为 Next.js(App Router)项目添加完整的国际化支持。本技能涵盖路由、翻译文件、站点地图 hreflang,以及所有语言区域的批量翻译。
步骤 1:评估项目
- 检查 Next.js 版本(
package.json)——必须为 13+,并使用 App Router - 检查是否已进行部分 i18n 配置(查找
next-intl、next-i18next、[locale]路由) - 确定所有需要翻译的页面/路由
- 确定所有面向用户的字符串(组件中的硬编码文本)
- 询问用户需要支持哪些语言区域(默认建议:en、es、fr、de、pt、ja、ar、zh、zh-tw、id、vi、ms、ru、hi)
步骤 2:安装依赖
npm install next-intl
步骤 3:创建 i18n 配置文件
在 src/i18n/ 下创建 4 个文件:
src/i18n/config.ts
export const locales = ['en', 'es', 'fr', 'de', 'pt', 'ja', 'ar', 'zh', 'zh-tw', 'id', 'vi', 'ms', 'ru', 'hi'] as const
export type Locale = (typeof locales)[number]
export const defaultLocale: Locale = 'en'
export const localeNames: Record<Locale, string> = {
en: 'English',
es: 'Espanol',
fr: 'Francais',
de: 'Deutsch',
pt: 'Portugues',
ja: '日本語',
ar: 'العربية',
zh: '简体中文',
'zh-tw': '繁體中文',
id: 'Bahasa Indonesia',
vi: 'Tieng Viet',
ms: 'Bahasa Melayu',
ru: 'Русский',
hi: 'हिन्दी',
}
export const rtlLocales: Locale[] = ['ar']
src/i18n/routing.ts
import { defineRouting } from 'next-intl/routing'
import { defaultLocale, locales } from './config'
export const routing = defineRouting({
locales,
defaultLocale,
localePrefix: 'as-needed', // English URLs stay clean, other locales get /es/, /fr/, etc.
})
src/i18n/navigation.ts
import { createNavigation } from 'next-intl/navigation'
import { routing } from './routing'
export const { Link, redirect, usePathname, useRouter } = createNavigation(routing)
src/i18n/request.ts
import { getRequestConfig } from 'next-intl/server'
import { routing } from './routing'
export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale
if (!locale || !routing.locales.includes(locale as any)) {
locale = routing.defaultLocale
}
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default,
}
})
步骤 4:创建中间件
创建 src/middleware.ts:
import createMiddleware from 'next-intl/middleware'
import { routing } from '@/i18n/routing'
export default createMiddleware({
...routing,
localeDetection: false, // Don't auto-redirect based on Accept-Language
})
export const config = {
matcher: ['/((?!_next|api|images|fonts|favicon|sitemap|robots).*)'],
}
关键决策:localeDetection: false 可阻止根据浏览器语言自动重定向用户,从而保持英文 URL 对 SEO 的稳定性。用户可以通过语言选择器手动切换语言。
步骤 5:更新 next.config
使用 createNextIntlPlugin 包装现有配置:
import createNextIntlPlugin from 'next-intl/plugin'
const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts')
// ... existing config ...
export default withNextIntl(nextConfig)
步骤 6:添加 [locale] 动态路由
将所有页面内容移至 src/app/[locale]/ 下:
-
创建
src/app/[locale]/layout.tsx,其中包含:- 返回所有语言区域的
generateStaticParams() setRequestLocale(locale)调用- 用
<NextIntlClientProvider>包裹子组件 <html lang={locale} dir={rtlLocales.includes(locale) ? 'rtl' : 'ltr'}>- 在
<head>中为所有语言区域和x-default添加 Hreflang<link>标签
- 返回所有语言区域的
-
将现有页面移至
src/app/[locale]/ -
每个页面都应调用
setRequestLocale(locale)以进行静态生成
步骤 7:将字符串提取到翻译文件中
-
创建
src/messages/en.json,按部分整理所有面向用户的字符串:{ "common": { "signIn": "Sign In", ... }, "tools": { "tool-slug": { "title": "...", "description": "..." } }, "faq": { "tool-slug": [{ "question": "...", "answer": "..." }] } } -
使用
useTranslations()替换组件中的所有硬编码字符串:const t = useTranslations('common') return <button>{t('signIn')}</button> -
对于服务端组件,使用
getTranslations():const t = await getTranslations('common')
步骤 8:翻译为所有语言区域
对于每个非英语语言区域,创建 src/messages/{locale}.json,结构与 en.json 相同。
翻译策略
通过 codex-tasks 技能并行使用 Codex Agent,以节省 Claude 使用额度:
- 使用
/codex-tasks,为每个语言区域启动一个 Codex 任务(最多并行 7 个) - 每个任务读取
en.json,翻译所有字符串,并写入{locale}.json - Codex 提示词应包含:
- 完整的
en.json内容(或其读取路径) - 目标语言名称和语言区域代码
- 以下说明:
- 翻译应自然,不要逐字直译
- 技术术语保留英文(PowerPoint、PDF、API 等)
- 完全保留 JSON 结构(键和嵌套层级相同)
- 原样保留
{count}、{name}等插值变量 - 将结果写入
src/messages/{locale}.json
- 完整的
- Codex 任务完成后,使用下方的验证脚本检查结果——Codex 的输出质量不一,必须进行检查
验证
翻译后,运行验证脚本以发现问题:
import json
locales = ['es', 'fr', 'de', 'pt', 'ja', 'ar', 'zh', 'zh-tw', 'id', 'vi', 'ms', 'ru', 'hi']
english_words = ['the ', 'and ', 'you ', 'your ', 'our ', 'this ', 'that ', 'with ', 'from ', 'will ']
with open('src/messages/en.json') as f:
en = json.load(f)
for loc in locales:
with open(f'src/messages/{loc}.json') as f:
data = json.load(f)
# Check: missing sections
missing = [s for s in en if s not in data]
# Check: residual English content
eng_count = 0
def check(d):
nonlocal eng_count # won't work in inline script; use list trick
if isinstance(d, dict):
for v in d.values(): check(v)
elif isinstance(d, str):
if sum(1 for w in english_words if w in d.lower()) >= 3:
eng_count += 1
check(data)
status = 'OK' if not missing and eng_count == 0 else 'ISSUES'
print(f'{loc}: {status} (missing={len(missing)}, english={eng_count})')
步骤 9:使用 Hreflang 更新站点地图
更新 src/app/sitemap.ts,添加 hreflang 备用链接:
import { MetadataRoute } from 'next'
import { locales } from '@/i18n/config'
const baseUrl = 'https://www.example.com'
function buildAlternates(path: string): Record<string, string> {
const alternates: Record<string, string> = {}
for (const locale of locales) {
const prefix = locale === 'en' ? '' : `/${locale}`
alternates[locale] = `${baseUrl}${prefix}${path}`
}
return alternates
}
export default function sitemap(): MetadataRoute.Sitemap {
return pages.map((path) => ({
url: `${baseUrl}${path}`,
lastModified: new Date(),
alternates: { languages: buildAlternates(path) },
}))
}
重要:每个页面只生成一个包含 hreflang 备用链接的规范 URL,而不是为每个语言区域生成一个 URL。这样可以避免搜索结果中出现重复内容。
步骤 10:添加语言选择器(可选)
添加语言切换组件,使用来自 @/i18n/navigation 的 useRouter 和 usePathname,在保留当前路径的同时切换语言区域。
第 11 步:验证
- 构建项目:
npm run build— 检查所有静态页面是否正确生成 - 测试英文 URL 没有前缀:
https://example.com/tools - 测试本地化 URL 带有前缀:
https://example.com/es/tools - 验证站点地图包含 hreflang 替代链接
- 检查阿拉伯语的 RTL 渲染
- 运行第 8 步中的翻译验证脚本
常见问题
public/sitemap.xml与开发模式下的动态src/app/sitemap.ts冲突 — 删除静态文件或重命名- 中间件匹配器 必须排除
_next、api、sitemap、robots和静态资源路径 localePrefix: 'as-needed'至关重要 — 它能保持默认语言的 URL 简洁,确保 SEO 连贯性localeDetection: false可防止不必要的重定向,避免影响 SEO 并使用户困惑- 大型翻译文件(每种语言 5000 行以上)可能导致 git 推送失败 — 使用
git config http.postBuffer 524288000 - 彻底验证翻译 — 自动翻译经常会产生混杂多种语言的输出;Codex 任务完成后,务必使用英文单词检测脚本进行验证
语言数量参考
- 14 种语言 x N 个页面 = 构建时生成 14N 个静态页面
- 每个语言 JSON 文件的大小通常是 en.json 的 2 到 5 倍(CJK 字符和表达冗长的语言)
- 构建时间随语言数量线性增长