mcpField
I
i18n

i18n

github.com/openclaudia開発725352 回インストール5 日前に更新
リポジトリ

i18n について

i18nは、next-intl v4を使ってNext.js App Routerプロジェクトを多言語化するスキルです。ロケールルーティング、翻訳ファイル、SEO向けの言語メタデータを設定する開発者向けです。

主な機能

  1. 01next-intl v4の設定
  2. 02ロケールルーティングとミドルウェアの設定
  3. 03UI文字列の抽出と翻訳ファイル化
  4. 04サイトマップへのhreflang追加
  5. 05翻訳とビルドの検証

活用シーン

  1. 01Next.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)を確認します。App Router を使用した 13 以降のバージョンが必要です。
  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 にすると、ブラウザーの言語に基づく自動リダイレクトを防げます。これにより、SEO のために英語の URL を安定して維持できます。ユーザーは言語セレクターから手動で言語を切り替えられます。

ステップ 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'}>
    • すべてのロケールと x-default を含む <head> 内の 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:すべてのロケールに翻訳する

英語以外の各ロケールについて、en.json と同じ構造の src/messages/{locale}.json を作成します。

翻訳戦略

Claude のクレジットを節約するため、codex-tasks スキルを使って 並列 Codex エージェント を使用します。

  1. /codex-tasks を使い、ロケールごとに Codex タスクを 1 つ起動します(並列実行は最大 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 の代替 URL を追加します。

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) },
  }))
}

重要:ロケールごとに 1 つずつ URL を作成するのではなく、hreflang の代替 URL を設定したページごとの正規 URL を 1 つ生成します。これにより、検索結果での重複コンテンツを防げます。

ステップ 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 の代替 URL があることを確認します
  5. アラビア語の RTL 表示を確認します
  6. ステップ 8 の翻訳検証スクリプトを実行します

よくある落とし穴

  • public/sitemap.xml は、開発モードで動的な src/app/sitemap.ts と競合します — 静的ファイルを削除するか、名前を変更してください
  • ミドルウェアの matcher では、_next、api、sitemap、robots、静的アセットのパスを除外する必要があります
  • localePrefix: 'as-needed' は非常に重要です — SEO の継続性を保つため、デフォルトのロケール URL を簡潔にします
  • localeDetection: false は、SEO を損ない、ユーザーを混乱させる不要なリダイレクトを防ぎます
  • 大きな翻訳ファイル(ロケールごとに 5000 行以上)は、git のプッシュが失敗する原因になることがあります — git config http.postBuffer 524288000 を使用してください
  • 翻訳を十分に検証してください — 自動翻訳では言語が混在した出力になることがよくあります。Codex のタスク完了後は、必ず英単語検出スクリプトで検証してください

ロケール数の参考情報

  • 14 ロケール x N ページ = ビルド時に 14N 個の静的ページ
  • 各ロケールの JSON ファイルは通常、en.json の 2~5 倍のサイズです(CJK 文字、冗長な言語)
  • ロケール数に比例してビルド時間が増加します

コメント