著者:
    作成:2025-11-01最終更新:2026-09-27

    2025年版 next-intl を使った Next.js アプリケーションの国際化方法

    目次

    next-intl とは?

    next-intl は、Next.js の App Router 向けに特別に設計された人気の国際化(i18n)ライブラリです。優れた TypeScript サポートと組み込みの最適化機能を備え、多言語対応の Next.js アプリケーションをシームレスに構築する方法を提供します。

    ご希望であれば、next-i18next ガイドや、直接 Intlayer を参照することもできます。
    比較については、next-i18next vs next-intl vs Intlayer をご覧ください。
    これらのライブラリがどのように生まれたのかを知るには、JavaScript i18n の歴史をご覧ください。

    Next.js における next-intl のベンチマーク結果

    翻訳を実装する前に、next-intl のパフォーマンス特性を理解しておくことが重要です。i18n ベンチマーク は、10 ページ・10 ロケールの同じ Next.js アプリケーションをさまざまな構成とライブラリで評価し、実際のバンドルサイズ、文字列の漏れ、ハイドレーションのオーバーヘッドを測定しています。

    指標

    動的な JSON 読み込み

    実行時に翻訳を遅延読み込みします

    スコープ付き JSON (ネームスペース)

    ページごとの翻訳ネームスペース

    この指標は何ですか?

    国際化ライブラリバンドルの合計gzip圧縮サイズ。これには、ツリーシェイキングと縮小化(minification)後のプロバイダーとコンテンツ取得ロジックのみが含まれます。

    なぜ重要なのか?

    ライブラリのサイズが小さければ初期 JavaScript ペイロードが削減され、クライアント側でのダウンロードと実行が高速化されます।

    表示形式

    Next.js における next-intl の主要な数値 (gzip):

    構成ライブラリサイズページ JS 平均他ロケールの漏れ他ページの漏れ
    ベース (i18n なし)-141.0 KB0.0%0.0%
    next-intl14.7 KB153.6 KB4.2%89.8%
    @intlayer/next-intl (compat)8.0 KB148.7 KB0.0%0.0%
    next-intlayer (ネイティブ Intlayer)5.5 KB141.3 KB0.0%0.0%

    ポイント:

    • グローバルなメッセージカタログを避ける: すべてのメッセージをルートレイアウトで読み込む標準的な構成では、ブラウザに送られる翻訳コンテンツの約 89.8% が他のページのものです。ルートごとに pick(messages, ['namespace']) を使えばこの漏れはなくなりますが、手動でのメンテナンスが必要です。
    • ランタイムの重さ: next-intl のランタイムは各ページに約 14.7 KB (gzip) を追加します。既存の next-intl アプリでは、@intlayer/next-intl compat アダプターが同じフック (useTranslations、useFormatter など) を維持したまま、ランタイムを約 8.0 KB、漏れ 0% に抑えます。ネイティブの next-intlayer なら 5.5 KB まで削減できます。
    全データはこちら: Next.js ベンチマークレポート、および ベンチマークリポジトリ。

    Next.js での機能比較

    Next.js App Router プロジェクトで一般的に必要となる機能について、next-intl を next-i18next および Intlayer と比較します:

    機能next-intlayer (Intlayer)next-intlnext-i18next
    コンポーネントの近くに翻訳を配置✅ 各コンポーネントとコンテンツを同じ場所に配置❌ 集中管理された JSON❌ 集中管理された JSON
    TypeScript 統合✅ 自動生成される厳密な型✅ 良好、AppConfig の拡張による⚠️ 基本的
    翻訳漏れの検出✅ TypeScript エラーとビルド時の警告⚠️ ランタイムでのフォールバック⚠️ ランタイムでのフォールバック
    リッチコンテンツ (JSX、Markdown)✅ 直接サポート⚠️ t.rich によるタグ、Markdown なし⚠️ <Trans> によるタグ
    AI 翻訳✅ 独自のプロバイダーと API キー、アプリのコンテキスト付き❌ なし❌ なし
    ビジュアルエディター / CMS✅ ローカルのビジュアルエディター + オプションの CMS❌ 外部プラットフォーム経由❌ 外部プラットフォーム経由
    ローカライズされたルーティング✅ 組み込み (Next.js と Vite)✅ 組み込みの [locale] セグメント✅ 組み込み
    複数形✅ 列挙ベース✅ ICU✅ サフィックスベース (_one、_other)
    フォーマット (日付、数値、通貨)✅ Intl ベースのフォーマッター✅ useFormatter✅ Intl ベース
    コンテンツ形式✅ .ts, .tsx, .js, .json, .md, .yaml✅ .json, .js, .ts⚠️ .json
    ICU MessageFormat✅ format: "icu" 経由✅ ネイティブ⚠️ i18next-icu 経由
    SEO ヘルパー (hreflang、sitemap)✅ メタデータ、sitemap、robots.txt のヘルパー✅ 良好✅ 良好
    Server Components✅ 任意の Server Component で直接アクセス⚠️ コンポーネントごとに t または await getTranslations() を渡す⚠️ コンポーネントツリーに t を渡していく
    コンポーネント単位の tree-shaking✅ ビルド時 (Babel / SWC)⚠️ 手動、ルートごとに pick()⚠️ 手動、ルートごとに名前空間
    遅延読み込み✅ ロケール単位と辞書単位✅ ロケール単位、名前空間は手動管理✅ ロケール単位、名前空間は手動管理
    ランタイムサイズ (gzip、ベンチマーク)4.9 KB14.7 KB19.7 KB
    CI での翻訳漏れ検出✅ npx intlayer test⚠️ 組み込みなし⚠️ 組み込みなし、ランタイムで saveMissing
    エコシステム / コミュニティ⚠️ 小さいが急成長中✅ 大きい✅ 非常に大きい
    ランタイムサイズは Next.js ベンチマーク によるものです。詳しい解説は next-i18next vs next-intl vs Intlayer をご覧ください。

    守るべきプラクティス

    実装に入る前に、以下のプラクティスを守ることをお勧めします:

    • HTMLのlangとdir属性を設定する
      レイアウト内でgetLocaleDirection(locale)を使ってdirを計算し、適切なアクセシビリティとSEOのために<html lang={locale} dir={dir}>を設定します。
    • メッセージをネームスペースごとに分割する
      ロケールとネームスペースごとにJSONファイルを整理し(例:common.json、about.json)、必要なものだけを読み込みます。
    • クライアントのペイロードを最小化する
      ページ上では、必要なネームスペースのみをNextIntlClientProviderに送信します(例:pick(messages, ['common', 'about']))。
    • 静的ページを優先する
      パフォーマンスとSEO向上のために、可能な限り静的ページを使用します。
    • サーバーコンポーネントでのi18n
      サーバーコンポーネントは、ページやclientとしてマークされていないすべてのコンポーネントのように静的であり、ビルド時にプリレンダリングできます。したがって、翻訳関数をプロップとして渡す必要があります。
    • TypeScriptの型設定
      アプリケーション全体で型安全を確保するために、ロケールの型を設定します。
    • リダイレクト用プロキシ
      ロケール検出とルーティングを処理し、ユーザーを適切なロケール接頭辞付きURLにリダイレクトするためにプロキシを使用します。
    • メタデータ、サイトマップ、robots.txtの国際化
      Next.jsが提供するgenerateMetadata関数を使用して、メタデータ、サイトマップ、robots.txtを国際化し、すべてのロケールで検索エンジンによるより良い検出を確保します。
    • リンクのローカライズ
      Linkコンポーネントを使用してリンクをローカライズし、ユーザーを適切なロケール接頭辞付きURLにリダイレクトします。すべてのロケールでページの発見性を確保することが重要です。
    • テストと翻訳の自動化 テストと翻訳の自動化は、多言語アプリケーションのメンテナンスにかかる時間を削減します。
    国際化とSEOに関して知っておくべきすべてをまとめたドキュメントをご覧ください: next-intlによる国際化 (i18n)。

    Next.jsアプリケーションでnext-intlをセットアップするステップバイステップガイド

    ide.intlayer.org
    GitHub の Application Template を参照してください。

    これから作成するプロジェクト構造は以下の通りです:

    bash
    .
    ├── global.ts
    ├── locales
    │   ├── en
    │   │  ├── common.json
    │   │  └── about.json
    │   ├── fr
    │   │  ├── common.json
    │   │  └── about.json
    │   └── es
    │      ├── common.json
    │      └── about.json
    └── src # src はオプションです
        ├── proxy.ts
        ├── app
        │   ├── i18n.ts
        │   └── [locale]
        │       ├── layout.tsx
        │       ├── (home) # / (ホームリソースで全ページを汚染しないためのルートグループ)
        │       │   ├── layout.tsx
        │       │   └── page.tsx
        │       └── about # /about
        │           ├── layout.tsx
        │           └── page.tsx
        └── components
            ├── ClientComponent.tsx
            └── ServerComponent.tsx
    
    1. 依存関係のインストール

      npmを使って必要なパッケージをインストールします:

      bash
      npm install next-intl
      
      • next-intl: Next.js App Router向けのコア国際化ライブラリで、翻訳管理のためのフック、サーバー関数、クライアントプロバイダーを提供します。
    2. プロジェクトの設定

      サポートするロケールを定義し、next-intlのリクエスト設定を行う設定ファイルを作成します。このファイルはi18n設定の単一の信頼できる情報源として機能し、アプリケーション全体で型安全性を保証します。

      ロケール設定を一元化することで不整合を防ぎ、将来的にロケールの追加や削除を容易にします。getRequestConfig関数はすべてのリクエストで実行され、各ページに必要な翻訳のみを読み込むため、コード分割が可能になりバンドルサイズを削減します。

      src/i18n.ts
      import { notFound } from "next/navigation";
      import createMiddleware from "next-intl/middleware";
      import { createNavigation } from "next-intl/navigation";
      
      // 型安全にサポートするロケールを定義
      export const locales = ["en", "fr", "es"] as const;
      export type Locale = (typeof locales)[number];
      export const defaultLocale: Locale = "en";
      
      export function isRTL(locale: Locale | (string & {})) {
        return /^(ar|fa|he|iw|ur|ps|sd|ug|yi|ckb|ku)(-|$)/i.test(locale);
      }
      
      // ロケールごとにメッセージを動的に読み込み、コード分割を可能にする
      // Promise.all はパフォーマンス向上のため名前空間を並列で読み込む
      async function loadMessages(locale: Locale) {
        // レイアウトやページで必要な名前空間のみを読み込む
        const [common, home, about] = await Promise.all([
          import(`../locales/${locale}/common.json`).then((m) => m.default),
          import(`../locales/${locale}/home.json`).then((m) => m.default),
          import(`../locales/${locale}/about.json`).then((m) => m.default),
          // ... 将来的に追加するJSONファイルはここに追加してください
        ]);
      
        return { common, home, about } as const;
      }
      
      // ローカライズされたURLを生成するヘルパー関数(例: /about と /fr/about)
      export function localizedPath(locale: string, path: string) {
        return locale === defaultLocale ? path : `/${locale}${path}`;
      }
      
      // getRequestConfigは各リクエストで実行され、サーバーコンポーネントにメッセージを提供します
      // ここでnext-intlがNext.jsのサーバーサイドレンダリングにフックします
      export default async function getRequestConfig({
        requestLocale,
      }: {
        requestLocale: Promise<string | undefined>;
      }) {
        const requested: Locale = ((await requestLocale) as Locale) ?? defaultLocale;
      
        if (!locales.includes(requested)) notFound();
      
        return {
          locale: requested,
          messages: await loadMessages(requested),
        };
      }
      
      export function getCookie(locale: Locale) {
        return [
          `NEXT_LOCALE=${locale}`,
          "Path=/",
          `Max-Age=${60 * 60 * 24 * 365}`, // 1年
          "SameSite=Lax",
        ].join("; ");
      }
      
      const routingOptions = {
        locales,
        defaultLocale,
        localePrefix: "as-needed", // /en/... のルートを /... に変更
        // オプション: ローカライズされたパス名
        // pathnames: {
        //   '/': '/',
        //   '/about': {en: '/about', fr: '/a-propos', es: '/acerca-de'},
        //   '/blog/[slug]': '/blog/[slug]'
        // }
        //  localeDetection: true, // クッキーからの "/" -> "/en" リダイレクトを防止
      } as const;
      
      export const { Link, redirect, usePathname, useRouter, getPathname } =
        createNavigation(routingOptions);
      
      export const proxy = createMiddleware(routingOptions);
      
    3. 動的ロケールルートの定義

      ロケールごとの動的ルーティングを設定するために、アプリフォルダ内に [locale] ディレクトリを作成します。これにより、Next.js はロケールベースのルーティングを処理でき、各ロケールが URL セグメント(例:/en/about、/fr/about)になります。

      動的ルートを使用することで、Next.js はビルド時にすべてのロケールの静的ページを生成でき、パフォーマンスとSEOが向上します。レイアウトコンポーネントは、ロケールに基づいて HTML の lang と dir 属性を設定し、アクセシビリティや検索エンジンの理解に重要です。

      src/app/[locale]/layout.tsx
      import type { ReactNode } from "react";
      import { locales } from "@/i18n";
      import { getLocaleDirection, setRequestLocale } from "next-intl/server";
      
      // ビルド時にすべてのロケールの静的ページを事前生成(SSG)
      // これによりパフォーマンスとSEOが向上します
      export function generateStaticParams() {
        return locales.map((locale) => ({ locale }));
      }
      
      export default function LocaleLayout({
        children,
        params,
      }: {
        children: ReactNode;
        params: Promise<{ locale: string }>;
      }) {
        // Next.jsのApp Routerでは、paramsはPromise(await可能)
        // これにより、動的ルートセグメントを非同期に解決できる
        const { locale } = await params;
      
        // 重要: setRequestLocaleはnext-intlにこのリクエストで使用するロケールを伝える
        // これがないと、getTranslations()はサーバーコンポーネントでどのロケールを使うか分からない
        setRequestLocale(locale);
      
        // 適切なHTMLレンダリングのためにテキストの方向(LTR/RTL)を取得
        const dir = getLocaleDirection(locale);
      
        return (
          <html lang={locale} dir={dir}>
            <body>{children}</body>
          </html>
        );
      }
      }
      
      src/app/[locale]/about/page.tsx
      import { getTranslations, getMessages, getFormatter } from "next-intl/server";
      import { NextIntlClientProvider } from "next-intl";
      import pick from "lodash/pick";
      import ServerComponent from "@/components/ServerComponent";
      import ClientComponent from "@/components/ClientComponent";
      
      export default async function AboutPage({
        params,
      }: {
        params: Promise<{ locale: string }>;
      }) {
        const { locale } = await params;
      
        // メッセージはサーバー側でロードされます。クライアントには必要なものだけを渡します。
        // これにより、ブラウザに送信されるJavaScriptバンドルが最小化されます
        const messages = await getMessages();
        const clientMessages = pick(messages, ["common", "about"]);
      
        // 完全にサーバー側での翻訳/フォーマット処理
        // これらはサーバー上で実行され、コンポーネントにpropsとして渡すことができます
        const tAbout = await getTranslations("about");
        const tCounter = await getTranslations("about.counter");
        const format = await getFormatter();
      
        const initialFormattedCount = format.number(0);
      
        return (
          // NextIntlClientProviderはクライアントコンポーネントで翻訳を利用可能にします
          // クライアントコンポーネントが実際に使用する名前空間のみを渡します
          <NextIntlClientProvider locale={locale} messages={clientMessages}>
            <main>
              <h1>{tAbout("title")}</h1>
              <ClientComponent />
              <ServerComponent
                formattedCount={initialFormattedCount}
                label={tCounter("label")}
                increment={tCounter("increment")}
              />
            </main>
          </NextIntlClientProvider>
        );
      }
      
    4. 翻訳ファイルを作成する

      各ロケールと名前空間ごとにJSONファイルを作成します。この構造により、翻訳を論理的に整理し、各ページに必要なものだけを読み込むことができます。

      名前空間ごとに翻訳を整理することで(例:common.json、about.json)、コード分割が可能になり、バンドルサイズを削減できます。これにより、各ページに必要な翻訳のみを読み込むため、パフォーマンスが向上します。

      locales/en/common.json
      {
        "welcome": "Welcome",
        "greeting": "Hello, world!"
      }
      
      locales/fr/common.json
      {
        "welcome": "Bienvenue",
        "greeting": "Bonjour le monde!"
      }
      
      locales/en/about.json
      {
        "title": "About",
        "description": "About page description",
        "counter": {
          "label": "Counter",
          "increment": "Increment"
        }
      }
      
      locales/fr/about.json
      {
        "title": "À propos",
        "description": "Description de la page À propos",
        "counter": {
          "label": "Compteur",
          "increment": "Incrémenter"
        }
      }
      
    5. ページで翻訳を利用する

      サーバーで翻訳を読み込み、それをサーバーコンポーネントとクライアントコンポーネントの両方に渡すページコンポーネントを作成します。これにより、レンダリング前に翻訳が読み込まれ、コンテンツのフラッシュを防止します。

      サーバーサイドでの翻訳読み込みはSEOを向上させ、FOUC(未翻訳コンテンツのフラッシュ)を防ぎます。pickを使用して必要な名前空間のみをクライアントプロバイダーに送ることで、ブラウザに送信されるJavaScriptバンドルを最小化します。

      src/app/[locale]/about/page.tsx
      import { getTranslations, getMessages, getFormatter } from "next-intl/server";
      import { NextIntlClientProvider } from "next-intl";
      import pick from "lodash/pick";
      import ServerComponent from "@/components/ServerComponent";
      import ClientComponent from "@/components/ClientComponent";
      
      export default async function AboutPage({
        params,
      }: {
        params: Promise<{ locale: string }>;
      }) {
        const { locale } = await params;
      
        // メッセージはサーバー側で読み込まれます。クライアントには必要なものだけを渡します。
        // これにより、ブラウザに送信されるJavaScriptバンドルが最小化されます
        const messages = await getMessages();
        const clientMessages = pick(messages, ["common", "about"]);
      
        // 完全にサーバー側での翻訳/フォーマット処理
        // これらはサーバー上で実行され、コンポーネントにpropsとして渡すことができます
        const tAbout = await getTranslations("about");
        const tCounter = await getTranslations("about.counter");
        const format = await getFormatter();
      
        const initialFormattedCount = format.number(0);
      
        return (
          // NextIntlClientProvider はクライアントコンポーネントで翻訳を利用可能にします
          // クライアントコンポーネントが実際に使用する名前空間のみを渡してください
          <NextIntlClientProvider locale={locale} messages={clientMessages}>
            <main>
              <h1>{tAbout("title")}</h1>
              <ClientComponent />
              <ServerComponent
                formattedCount={initialFormattedCount}
                label={tCounter("label")}
                increment={tCounter("increment")}
              />
            </main>
          </NextIntlClientProvider>
        );
      }
      
    6. クライアントコンポーネントでの翻訳の使用

      クライアントコンポーネントは、useTranslations と useFormatter フックを使用して翻訳およびフォーマット関数にアクセスできます。これらのフックは NextIntlClientProvider コンテキストから読み取ります。

      クライアントコンポーネントは翻訳にアクセスするために React フックを必要とします。useTranslations と useFormatter フックは next-intl とシームレスに統合されており、ロケールが変更された際にリアクティブに更新されます。

      ページのクライアントメッセージに必要な名前空間を追加することを忘れないでください(クライアントコンポーネントが実際に必要とする名前空間のみを含めてください)。
      src/components/ClientComponent.tsx
      "use client";
      
      import React, { useState } from "react";
      import { useTranslations, useFormatter } from "next-intl";
      
      const ClientComponent = () => {
        // ネストされたオブジェクトに直接スコープを設定
        // useTranslations/useFormatter は NextIntlClientProvider コンテキストから読み取るフックです
        // コンポーネントが NextIntlClientProvider でラップされている場合にのみ動作します
        const t = useTranslations("about.counter");
        const format = useFormatter();
        const [count, setCount] = useState(0);
      
        return (
          <div>
            <p>{format.number(count)}</p>
            <button
              aria-label={t("label")}
              onClick={() => setCount((count) => count + 1)}
            >
              {t("increment")}
            </button>
          </div>
        );
      };
      
    7. サーバーコンポーネントでの翻訳の使用

      サーバーコンポーネントは React フックを使用できないため、親コンポーネントから props 経由で翻訳とフォーマッターを受け取ります。この方法により、サーバーコンポーネントは同期的に保たれ、クライアントコンポーネント内にネストすることが可能になります。

      クライアント境界内にネストされる可能性のあるサーバーコンポーネントは同期的である必要があります。翻訳済みの文字列やフォーマット済みの値をpropsとして渡すことで、非同期処理を回避し、適切なレンダリングを保証します。親のページコンポーネントで翻訳とフォーマットを事前に計算してください。

      src/components/ServerComponent.tsx
      // クライアントコンポーネント内にネストされたサーバーコンポーネントは同期的でなければなりません
      // Reactはサーバー/クライアント境界を越えた非同期関数をシリアライズできません
      // 解決策:親で翻訳/フォーマットを事前計算し、propsとして渡す
      type ServerComponentProps = {
        formattedCount: string;
        label: string;
        increment: string;
      };
      
      const ServerComponent = ({
        formattedCount,
        label,
        increment,
      }: ServerComponentProps) => {
        return (
          <div>
            <p>{formattedCount}</p>
            <button aria-label={label}>{increment}</button>
          </div>
        );
      };
      
      ページやレイアウト内で、next-intl/server から getTranslations と getFormatter を使用して翻訳とフォーマットを事前計算し、それらを props としてサーバーコンポーネントに渡してください。
    8. コンテンツの言語を変更する

      オプション

      next-intl を使ってコンテンツの言語を変更するには、同じパス名を指しながらロケールを切り替えるロケール対応リンクをレンダリングします。プロバイダーが URL を自動的に書き換えるため、現在のルートをターゲットにするだけで済みます。

      src/components/LocaleSwitcher.tsx
      "use client";
      
      import Link from "next/link";
      import { usePathname } from "next/navigation";
      import { useLocale } from "next-intl";
      import { defaultLocale, getCookie, type Locale, locales } from "@/i18n";
      
      const getLocaleLabel = (locale: Locale): string => {
        try {
          const displayNames = new Intl.DisplayNames([locale], { type: "language" });
          return displayNames.of(locale) ?? locale.toUpperCase();
        } catch {
          return locale.toUpperCase();
        }
      };
      
      const localeFlags: Record<Locale, string> = {
        en: "🇬🇧",
        fr: "🇫🇷",
        es: "🇪🇸",
      };
      
      export default function LocaleSwitcher() {
        const activeLocale = useLocale();
        const pathname = usePathname();
      
        // パス名からロケールのプレフィックスを削除してベースパスを取得する
        const getBasePath = (path: string) => {
          for (const locale of locales) {
            if (path.startsWith(`/${locale}`)) {
              return path.slice(locale.length + 1) || "/";
            }
          }
          return path;
        };
      
        const basePath = getBasePath(pathname);
      
        return (
          <nav aria-label="言語セレクター">
            <div>
              {(locales as readonly Locale[]).map((locale) => {
                const isActive = locale === activeLocale;
                // デフォルトロケールかどうかに基づいてhrefを構築
                const href =
                  locale === defaultLocale ? basePath : `/${locale}${basePath}`;
                return (
                  <Link
                    key={locale}
                    href={href}
                    aria-current={isActive ? "page" : undefined}
                    onClick={() => {
                      document.cookie = getCookie(locale);
                    }}
                  >
                    <span>{localeFlags[locale]}</span>
                    <span>{getLocaleLabel(locale)}</span>
                    <span>{locale.toUpperCase()}</span>
                  </Link>
                );
              })}
            </div>
          </nav>
        );
      }
      
    9. ローカライズされたLinkコンポーネントを使用する

      オプション

      next-intlは、アクティブなロケールを自動的に適用するローカライズされたリンクコンポーネントを含むサブパッケージnext-intl/navigationを提供しています。これはすでに@/i18nファイルで抽出してあるので、以下のように使用できます。

      src/components/MyComponent.tsx
      import { Link } from "@/i18n";
      
      return <Link href="/about">t("about.title")</Link>;
      
    10. Server Actions内でアクティブなロケールにアクセスする

      オプション

      Server Actionsはnext-intl/serverを使用して現在のロケールを読み取ることができます。これはローカライズされたメールを送信したり、送信されたデータと共に言語設定を保存したりするのに便利です。

      src/app/actions/get-current-locale.ts
      "use server";
      
      import { getLocale } from "next-intl/server";
      
      export async function getCurrentLocale() {
        return getLocale();
      }
      
      export async function handleContactForm(formData: FormData) {
        const locale = await getCurrentLocale();
      
        // ロケールを使用してテンプレートや分析ラベルなどを選択します。
        console.log(`ロケール ${locale} からの問い合わせフォームを受信しました`);
      }
      
      getLocale は next-intl プロキシによって設定されたロケールを読み取るため、サーバーのどこでも動作します:ルートハンドラー、サーバーアクション、およびエッジ関数。
    11. メタデータの国際化

      オプション

      コンテンツの翻訳は重要ですが、国際化の主な目的はあなたのウェブサイトを世界により見えるようにすることです。I18nは適切なSEOを通じてウェブサイトの可視性を向上させるための強力な手段です。

      適切に国際化されたメタデータは、検索エンジンがあなたのページで利用可能な言語を理解するのに役立ちます。これには、hreflangメタタグの設定、タイトルや説明の翻訳、そして各ロケールに対して正しいカノニカルURLが設定されていることの確認が含まれます。

      src/app/[locale]/about/layout.tsx
      import type { Metadata } from "next";
      import { locales, defaultLocale, localizedPath } from "@/i18n";
      import { getTranslations } from "next-intl/server";
      
      // generateMetadataは各ロケールごとに実行され、SEOに適したメタデータを生成します
      tsx fileName="src/app/[locale]/about/layout.tsx"
      // これは検索エンジンが代替言語バージョンを理解するのに役立ちます
      export async function generateMetadata({
        params,
      }: {
        params: { locale: string };
      }): Promise<Metadata> {
        const { locale } = params;
        const t = await getTranslations({ locale, namespace: "about" });
      
        const url = "/about";
        const languages = Object.fromEntries(
          locales.map((locale) => [locale, localizedPath(locale, url)])
        );
      
        return {
          title: t("title"),
          description: t("description"),
          alternates: {
            canonical: localizedPath(locale, url),
            languages: { ...languages, "x-default": url },
          },
        };
      }
      
      // ... ページの残りのコード
      
    12. サイトマップの国際化

      オプション

      すべてのロケールバージョンのページを含むサイトマップを生成します。これにより、検索エンジンがすべての言語バージョンのコンテンツを検出し、インデックス化するのに役立ちます。

      適切に国際化されたサイトマップは、検索エンジンがすべての言語バージョンのページを見つけてインデックス化できるようにします。これにより、国際的な検索結果での可視性が向上します。

      src/app/sitemap.ts
      import type { MetadataRoute } from "next";
      import { defaultLocale, locales } from "@/i18n";
      
      const origin = "https://example.com";
      
      const formatterLocalizedPath = (locale: string, path: string) =>
        locale === defaultLocale ? `${origin}${path}` : `${origin}/${locale}${path}`;
      
      /**
       * すべてのロケールとそのローカライズされたパスのマップを取得する
       *
       * 出力例:
       * {
       *   "en": "https://example.com",
       *   "fr": "https://example.com/fr",
       *   "es": "https://example.com/es",
       *   "x-default": "https://example.com"
       * }
       */
      const getLocalizedMap = (path: string) =>
        Object.fromEntries([
          ...locales.map((locale) => [locale, formatterLocalizedPath(locale, path)]),
          ["x-default", formatterLocalizedPath(defaultLocale, path)],
        ]);
      
      // すべてのロケールバリアントを含むサイトマップを生成し、SEOを向上させる
      // alternatesフィールドは検索エンジンに言語バージョンを伝える
      export default function sitemap(): MetadataRoute.Sitemap {
        return [
          {
            url: formatterLocalizedPath(defaultLocale, "/"),
            lastModified: new Date(),
            changeFrequency: "monthly",
            priority: 1.0,
            alternates: { languages: getLocalizedMap("/") },
          },
          {
            url: formatterLocalizedPath(defaultLocale, "/about"),
            lastModified: new Date(),
            changeFrequency: "monthly",
            priority: 0.7,
            alternates: { languages: getLocalizedMap("/about") },
          },
        ];
      }
      
    13. robots.txtの多言語対応

      オプション

      保護されたルートのすべてのロケールバージョンを適切に処理するrobots.txtファイルを作成します。これにより、検索エンジンが管理者ページやダッシュボードページをどの言語でもインデックスしないようにできます。

      すべてのロケールに対してrobots.txtを適切に設定することで、ルートがロケールごとに異なる場合でも、検索エンジンが機密ページをインデックスするのを防ぎます。

      src/app/robots.ts
      import type { MetadataRoute } from "next";
      import { locales, defaultLocale } from "@/i18n";
      
      const origin = "https://example.com";
      // すべてのロケールのパスを生成(例: /admin, /fr/admin, /es/admin)
      const withAllLocales = (path: string) => [
        path,
        ...locales
          .filter((locale) => locale !== defaultLocale)
          .map((locale) => "/" + locale + path),
      ];
      
      export default function robots(): MetadataRoute.Robots {
        const disallow = [
          ...withAllLocales("/dashboard"),
          ...withAllLocales("/admin"),
        ];
      
        return {
          rules: { userAgent: "*", allow: ["/"], disallow },
          host: origin,
          sitemap: origin + "/sitemap.xml",
        };
      }
      
    14. ロケールルーティングのためのプロキシ設定

      オプション

      ユーザーの優先ロケールを自動的に検出し、適切なロケールプレフィックス付きURLへリダイレクトするプロキシを作成します。next-intlはこれを自動で処理する便利なプロキシ関数を提供しています。

      プロキシは、ユーザーがサイトを訪れた際に自動的に好みの言語にリダイレクトされることを保証します。また、ユーザーの言語設定を保存し、次回以降の訪問時にユーザーエクスペリエンスを向上させます。

      src/proxy.ts
      import { proxy } from "@/i18n";
      
      // ミドルウェアはルートの前に実行され、ロケール検出とルーティングを処理します
      // localeDetection: true は Accept-Language ヘッダーを使用してロケールを自動検出します
      export default proxy;
      
      export const config = {
        // API、Nextの内部処理、静的アセットをスキップ
        // 正規表現: api、_nextで始まるもの、またはドットを含むファイルを除くすべてのルートにマッチ
        matcher: ["/((?!api|_next|.*\\..*).*)"],
      };
      
    15. ロケール用のTypeScript型を設定する

      オプション

      TypeScriptを設定すると、キーのオートコンプリートと型安全性が得られます。

      そのために、プロジェクトのルートに global.ts ファイルを作成し、以下のコードを追加できます。

      global.ts
      import type { locales } from "@/i18n";
      
      type Messages = {
        common: typeof import("./locales/en/common.json");
        home: typeof import("./locales/en/home.json");
        about: typeof import("./locales/en/about.json");
        // ... 将来的に追加されるJSONファイルもここに追加してください
      };
      
      declare module "next-intl" {
        interface AppConfig {
          Locale: (typeof locales)[number];
          Messages: Messages;
        }
      }
      

      このコードはモジュール拡張(Module Augmentation)を使用して、locales と messages を next-intl の AppConfig 型に追加します。

    16. Intlayerを使って翻訳を自動化する

      オプション

      Intlayerは、アプリケーションのローカリゼーションプロセスを支援するために設計された無料かつオープンソースのライブラリです。next-intlが翻訳の読み込みと管理を担当する一方で、Intlayerは翻訳ワークフローの自動化を支援します。

      翻訳を手動で管理することは時間がかかり、エラーが発生しやすい作業です。Intlayerは翻訳のテスト、生成、管理を自動化し、時間を節約するとともに、アプリケーション全体での一貫性を確保します。

      Intlayerは以下のことを可能にします:

      • コードベース内の好きな場所でコンテンツを宣言する Intlayerは.content.{ts|js|json}ファイルを使用して、コードベース内の好きな場所でコンテンツを宣言することを可能にします。これにより、コンテンツの整理が向上し、コードベースの可読性と保守性が高まります。

      • 不足している翻訳のテスト Intlayerは、CI/CDパイプラインやユニットテストに統合できるテスト機能を提供します。詳細は翻訳のテストをご覧ください。

      • 翻訳の自動化 Intlayerは、翻訳を自動化するためのCLIとVSCode拡張機能を提供します。これらはCI/CDパイプラインに統合可能です。詳細は翻訳の自動化をご覧ください。 ご自身のAPIキーとお好みのAIプロバイダーを使用できます。また、コンテキストに応じた翻訳も提供します。詳細はコンテンツの自動補完をご覧ください。

      • 外部コンテンツの接続 Intlayerは、外部のコンテンツ管理システム(CMS)にコンテンツを接続することを可能にします。最適化された方法でコンテンツを取得し、JSONリソースに挿入します。詳細は外部コンテンツの取得についてをご覧ください。

      • ビジュアルエディター
        Intlayerは、ビジュアルエディターを使用してコンテンツを編集できる無料のビジュアルエディターを提供しています。詳細は翻訳のビジュアル編集についてをご覧ください。

      その他にも多数の機能があります。Intlayerが提供するすべての機能を知るには、Intlayerの利点に関するドキュメントをご参照ください。

      詳細なパフォーマンスベンチマークと比較については、以下を参照してください:

    コメント

    まだコメントはありません。最初のコメントを共有しましょう。

    関連記事

    最新の投稿