mcpField
I
i18n

i18n

github.com/openclaudia开发725352 次安装5 天前更新
代码仓库

关于 i18n

i18n 是面向 Next.js App Router 项目的国际化技能,使用 next-intl v4 配置路由、翻译文件和 SEO 语言标记。适合需要将页面与用户界面扩展到多个语言版本的开发者。

主要功能

  1. 01配置 next-intl v4 国际化
  2. 02设置语言路由与中间件
  3. 03提取界面字符串并生成翻译文件
  4. 04为站点地图添加 hreflang
  5. 05验证翻译并检查构建

适用场景

  1. 01为 Next.js App Router 添加多语言路由
  2. 02将页面文案整理到翻译文件
  3. 03为各语言页面配置 SEO 标记
  4. 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:评估项目

  1. 检查 Next.js 版本(package.json)——必须为 13+,并使用 App Router
  2. 检查是否已进行部分 i18n 配置(查找 next-intl、next-i18next、[locale] 路由)
  3. 确定所有需要翻译的页面/路由
  4. 确定所有面向用户的字符串(组件中的硬编码文本)
  5. 询问用户需要支持哪些语言区域(默认建议: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]/ 下:

  1. 创建 src/app/[locale]/layout.tsx,其中包含:

    • 返回所有语言区域的 generateStaticParams()
    • setRequestLocale(locale) 调用
    • 用 <NextIntlClientProvider> 包裹子组件
    • <html lang={locale} dir={rtlLocales.includes(locale) ? 'rtl' : 'ltr'}>
    • 在 <head> 中为所有语言区域和 x-default 添加 Hreflang <link> 标签
  2. 将现有页面移至 src/app/[locale]/

  3. 每个页面都应调用 setRequestLocale(locale) 以进行静态生成

步骤 7:将字符串提取到翻译文件中

  1. 创建 src/messages/en.json,按部分整理所有面向用户的字符串:

    {
      "common": { "signIn": "Sign In", ... },
      "tools": { "tool-slug": { "title": "...", "description": "..." } },
      "faq": { "tool-slug": [{ "question": "...", "answer": "..." }] }
    }
    
  2. 使用 useTranslations() 替换组件中的所有硬编码字符串:

    const t = useTranslations('common')
    return <button>{t('signIn')}</button>
    
  3. 对于服务端组件,使用 getTranslations():

    const t = await getTranslations('common')
    

步骤 8:翻译为所有语言区域

对于每个非英语语言区域,创建 src/messages/{locale}.json,结构与 en.json 相同。

翻译策略

通过 codex-tasks 技能并行使用 Codex Agent,以节省 Claude 使用额度:

  1. 使用 /codex-tasks,为每个语言区域启动一个 Codex 任务(最多并行 7 个)
  2. 每个任务读取 en.json,翻译所有字符串,并写入 {locale}.json
  3. Codex 提示词应包含:
    • 完整的 en.json 内容(或其读取路径)
    • 目标语言名称和语言区域代码
    • 以下说明:
      • 翻译应自然,不要逐字直译
      • 技术术语保留英文(PowerPoint、PDF、API 等)
      • 完全保留 JSON 结构(键和嵌套层级相同)
      • 原样保留 {count}、{name} 等插值变量
      • 将结果写入 src/messages/{locale}.json
  4. 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 步:验证

  1. 构建项目:npm run build — 检查所有静态页面是否正确生成
  2. 测试英文 URL 没有前缀:https://example.com/tools
  3. 测试本地化 URL 带有前缀:https://example.com/es/tools
  4. 验证站点地图包含 hreflang 替代链接
  5. 检查阿拉伯语的 RTL 渲染
  6. 运行第 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 字符和表达冗长的语言)
  • 构建时间随语言数量线性增长

评论

支持 Markdown