web-i18n-next-intl について
web-i18n-next-intlは、Next.js App Routerでnext-intlを使う開発者向けの国際化パターンガイドです。ルーティング、メッセージ表示、フォーマット、静的レンダリングを扱い、設定上の注意点も示します。Pages Routerや翻訳コンテンツ管理は対象外です。
主な機能
- 01ロケールルーティングと検出の設定
- 02コンポーネントや非同期コンテキストでのメッセージ取得
- 03複数形、補間、リッチテキストの処理
- 04ロケールに応じた日付と数値の表示
- 05ロケール別ページの静的生成
- 06メッセージキーと書式の型付け
活用シーン
- 01ロケールセグメント付きルーティングの構築
- 02ページ、メタデータ、Server Actionsのローカライズ
- 03ロケールに応じた複数形や日付の表示
- 04ロケール別ページの静的生成と型の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-i18n-next-intlインストール後、Claude Code で/web-i18n-next-intlを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.yamlreference.mdexamples/core.mdformatting.mdmarkup.mdpluralization.md詳細
翻訳後に原文が更新されています。最新の内容は原文をご確認ください。
next-intl の国際化パターン
クイックガイド:
useTranslationsはメッセージを、useFormatterは日付、数値、リストをレンダリングし、createMiddlewareはロケールを検出します。ページまたはレイアウトの先頭でsetRequestLocale(locale)を呼び出すことで、静的レンダリングが可能になります。v4.0+ ではAppConfigインターフェースを通じて型を登録し、ユーザーが Accept-Language の設定から切り替えた場合にのみロケール Cookie を設定します。ここで紹介するパターンはすべて App Router 用です。Pages Router との連携は別の API であり、ここでの内容は適用されません。
詳細なリソース:
- examples/core.md — セットアップ、プロバイダー、
useTranslations、複数形、フォーマット、静的レンダリング、ロケールの切り替え、型 - examples/formatting.md — 自動更新される相対時間、リストのフォーマット、組み合わせたフォーマットパターン
- examples/pluralization.md — 序数、select 内にネストされた複数形、ゼロの場合の処理
- examples/markup.md — HTML 文字列向けの
t.markup():メール本文、フィード、サニタイズ - reference.md — 判断ツリー、アンチパターンのコード、ICU 構文の表、セットアップのチェックリスト
該当する方法
- コンポーネント内でのレンダリング —
useTranslationsとuseFormatterを使います。これらを呼び出す Client Component の上位にNextIntlClientProviderが必要です。examples/core.md を参照してください。 - コンポーネントツリーの外部でのレンダリング — メタデータ、Server Actions、その他の非同期コンテキストでは
getTranslations({ locale, namespace })を使います。ロケールを明示的に渡す必要があります。こちらも examples/core.md を参照してください。 - 要素ではなく HTML 文字列を生成する場合 —
t.rich()ではなくt.markup()を使い、あわせてサニタイズを行います。examples/markup.md を参照してください。
<critical_requirements>
next-intl のコードを書く前に
各ページとレイアウトの先頭で、フックを使う前に setRequestLocale(locale) を呼び出してください。 これにより next-intl はリクエストなしでロケールを解決できるようになり、ルートを静的にレンダリングできます。
ロケールを使う前に、hasLocale(routing.locales, locale) で検証してください。 検証されていないセグメントはメッセージローダーに渡され、元のルートから大きく離れた場所でエラーになります。
ツリーを NextIntlClientProvider でラップしてください。 クライアントコンポーネントはこのコンテキストからメッセージを読み取るため、コンテキストがないと何もレンダリングされません。
</critical_requirements>
自動検出: next-intl、useTranslations、useFormatter、useLocale、getTranslations、 setRequestLocale、NextIntlClientProvider、defineRouting、createNavigation、hasLocale、ICU メッセージ フォーマット
対象範囲:
- ロケールセグメントによるルーティング、ロケール検出、ロケール対応ナビゲーション API
- 補間、複数形、埋め込みマークアップを使ったメッセージのレンダリング
- ロケールごとの日付、数値、相対時間、リストのフォーマット
- ビルド時にルートのすべてのロケールバリアントを生成
- メッセージキーとフォーマットを型付けし、キーが見つからない場合にコンパイル時エラーにする
別途扱う内容:
- ロケールセグメント以外のフレームワークのルーティングとレンダリング — このスキルでは next-intl がルートに追加する内容を扱います。ルートの定義方法は扱いません
- 翻訳ファイルの作成と翻訳ベンダーとの同期 — メッセージは JSON として渡され、その出所はこのスキルの対象外です
- ロケール以外のクライアント状態 — ロケールは
useLocale()で読み取り、状態に複製することはありません - 日付の算術処理 —
Dateのフォーマットはこのスキルの役割ですが、Dateの生成は対象外です
翻訳は名前空間付きの JSON で、サーバー上でリクエストごとに解決され、コンテキスト経由でクライアントに渡されます。ここから2つの方針が導かれます。まず、ロケールに応じたレンダリングはデフォルトでサーバー側の役割であり、クライアントツリーにはインタラクティブな動作に必要なものだけを持たせます。また、通常はリクエストからロケールを取得するため、静的レンダリングでは別の方法でロケールを渡す必要があります。そのために使うのが setRequestLocale であり、ロケールを読み取る処理より前に呼び出す必要があります。
基本パターン
パターン 1:プロジェクトのセットアップ
4つのモジュールとプロキシを用意します。routing.ts でロケールを宣言し、request.ts でリクエストごとにロケールを解決し、navigation.ts でロケール対応のナビゲーション API を生成します。プロキシは URL、Cookie、Accept-Language からロケールを検出します。
// i18n/routing.ts
import { defineRouting } from "next-intl/routing";
export const routing = defineRouting({
locales: ["en", "de", "fr"],
defaultLocale: "en",
});
export type Locale = (typeof routing.locales)[number];
Next.js 16 以降ではプロキシファイルは proxy.ts、それより前のバージョンでは middleware.ts です。どちらの場合もエクスポートは createMiddleware(routing) です。
コード全文:examples/core.md
パターン 2:プロバイダーを使うルートレイアウト
ロケールを検証して設定し、メッセージを読み込んで、ツリーをラップします。
if (!hasLocale(routing.locales, locale)) notFound();
setRequestLocale(locale);
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={await getMessages()}>{children}</NextIntlClientProvider>
</body>
</html>
);
v4.0 以降では、プロバイダーはサーバー設定からメッセージを継承するため、messages プロパティは省略可能です。
コード全文:examples/core.md
パターン 3:useTranslations
名前空間でキーの範囲を指定し、値は名前付きプレースホルダーで表します。
const t = useTranslations("Profile");
t("greeting", { name: user.name }); // "Hello, Jane!"
t("unreadCount", { count: messages.length });
コード全文:examples/core.md
パターン 4:ICU 構文による複数形
複数形はロケール固有の CLDR ルールに従って選択され、# はフォーマット済みの数値を表示します。=0 はゼロと完全一致する場合に使われ、CLDR カテゴリの zero とは異なります。
{
"itemCount": "{count, plural, =0 {No items} one {# item} other {# items}}"
}
序数には selectordinal を、列挙値のメッセージには select を使います。
コード全文:examples/core.md、序数とネストについては examples/pluralization.md
パターン 5:t.rich() によるリッチテキスト
メッセージ内のタグは開発者が定義し、コンポーネントに対応付けるため、翻訳者は文全体を一続きの文章として扱えます。
t.rich("terms", {
link: (chunks) => <a href="/terms">{chunks}</a>,
bold: (chunks) => <strong>{chunks}</strong>,
});
コード全文:examples/core.md
パターン 6:useFormatter
日付、数値、リスト、相対時間を1つのフックで扱い、それぞれ対応する Intl フォーマッターを使用します。
const format = useFormatter();
format.dateTime(date, { year: "numeric", month: "long", day: "numeric" });
format.number(amount, { style: "currency", currency });
format.relativeTime(date, useNow({ updateInterval: 60_000 }));
コード全文:examples/core.md、相対時間の自動更新とリストについては examples/formatting.md
パターン 7:静的レンダリング
generateStaticParams がロケールのバリエーションを列挙し、setRequestLocale によってリクエストなしで
それぞれをレンダリングできるようにします。
export function generateStaticParams() {
return routing.locales.flatMap((locale) =>
slugs.map((slug) => ({ locale, slug })),
);
}
コード全体:examples/core.md
パターン 8:ロケールの切り替え
createNavigation のナビゲーション API は、現在のパスを維持したままロケールを切り替えます。
const router = useRouter();
const pathname = usePathname();
router.replace(pathname, { locale: newLocale });
コード全体:examples/core.md
パターン 9:型安全なキー
AppConfig インターフェースにメッセージの形状を登録すると(v4.0 以降)、誤ったキーはコンパイル
エラーになります。
declare module "next-intl" {
interface AppConfig {
Locale: (typeof routing.locales)[number];
Messages: typeof en;
Formats: typeof formats;
}
}
JSON をインポートするには、tsconfig.json で allowArbitraryExtensions: true を設定します。引数の型を
推論するには、プラグインの experimental.createMessagesDeclaration が型を生成します。
コード全体:examples/core.md
パターン 10:非同期コンテキスト
getTranslations はフックを使えない場所でも動作します。メタデータはコンポーネントツリーの外部で実行されるため、ロケールは推論されず、明示的に渡します。
const t = await getTranslations({ locale, namespace: "Metadata" });
return { title: t("title"), description: t("description") };
コード全体:examples/core.md
<red_flags>
注意点
実行時に問題が発生します:
paramsを待機せずに読み取ること。Next.js 15 以降では Promise です- フックがすでにロケールを読み取った後に
setRequestLocaleを呼び出すこと。フックが失敗しますが、エラーには呼び出し順ではなくフックの名前が表示されます NextIntlClientProviderの外部で Client Component をレンダリングすること。メッセージが届きません- 検証されていないロケールセグメントを使用すること。ルートで宣言されていないパスでは、メッセージのインポートに失敗します
- Next.js 16 でプロキシの名前が
middleware.tsのままになっていること。読み込まれないため、ロケールが検出されません - マークアップを含むメッセージに
t()を使うこと。タグがそのままテキストとして返されます generateMetadataまたは Server Action 内でuseTranslationsを使うこと。これはフックであり、どちらもコンポーネントツリーの外部で実行されます。そこで動作するのはgetTranslations({ locale, namespace })です
意外な動作:
setRequestLocaleを省略すると、静的レンダリングが気づかれないまま行われなくなります。ルートは引き続き動作しますが、動的にレンダリングされます。もう一方に必要なのがgenerateStaticParamsです。どちらか一方でも欠けているルートはリクエストごとにレンダリングされますt.rich()のタグ関数が受け取るchunksは、単一の要素ではなく配列ですuseNow()が動作するのはクライアント上だけなので、SSR ではハイドレーションまで初期値が表示されますuseTranslationsで名前空間を省略すると、すべてのキーが一つのグローバルな空間に置かれ、名前が衝突します- v4.0 以降、ロケール Cookie はセッション Cookie であり、ユーザーが
Accept-Languageの設定とは異なるロケールに切り替えた場合にのみ書き込まれます。ルーティング設定のlocaleCookieで両方の動作を変更できます - Next.js 16 では、プロキシは Edge ランタイムではなく Node ランタイムで実行されます
修正方法を示すコード付きのアンチパターン:reference.md。
</red_flags>