国際化 (i18n) ルーティング

Next.js は v10.0.0 以降、国際化 (i18n) ルーティングを組み込みでサポートしています。ロケールのリスト、デフォルトロケール、ドメイン固有のロケールを提供すると、Next.js が自動的にルーティングを処理します。

i18n ルーティングサポートは現在、react-intlreact-i18nextlinguirosettanext-intlnext-translatenext-multilingualtypesafe-i18ntolgee などの既存の i18n ライブラリソリューションを補完することを目的としており、ルートとロケール解析を合理化します。

はじめに

開始するには、next.config.js ファイルに i18n 設定を追加します。

ロケールは UTS ロケール識別子 で、ロケールを定義するための標準化された形式です。

一般的に、ロケール識別子は言語、地域、スクリプトをダッシュで区切って構成されます: language-region-script。地域とスクリプトはオプションです。例:

  • en-US - アメリカで話される英語
  • nl-NL - オランダで話されるオランダ語
  • nl - オランダ語、特定の地域なし

ユーザーのロケールが nl-BE で、設定にリストされていない場合、利用可能であれば nl にリダイレクトされ、それ以外の場合はデフォルトロケールにリダイレクトされます。 国のすべての地域をサポートする予定がない場合、フォールバックとして機能する国ロケールを含めることが良いプラクティスです。

next.config.js
module.exports = {
  i18n: {
    // アプリケーションでサポートするすべてのロケール
    locales: ['en-US', 'fr', 'nl-NL'],
    // ロケールプレフィックスがないパス(例: `/hello`)にアクセスした際に使用されるデフォルトロケール
    defaultLocale: 'en-US',
    // ドメインルーティングを設定する際に必要なロケールドメインとそのデフォルトロケールのリスト
    // 注: サブドメインはマッチングされるためにドメイン値に含める必要があります(例: "fr.example.com")。
    domains: [
      {
        domain: 'example.com',
        defaultLocale: 'en-US',
      },
      {
        domain: 'example.nl',
        defaultLocale: 'nl-NL',
      },
      {
        domain: 'example.fr',
        defaultLocale: 'fr',
        // オプションの http フィールドを使用して、ローカルで https ではなく http でロケールドメインをテストできます
        http: true,
      },
    ],
  },
}

ロケール戦略

ロケール処理には2つの戦略があります: サブパスルーティングとドメインルーティングです。

サブパスルーティング

サブパスルーティングでは、ロケールをURLパスに配置します。

next.config.js
module.exports = {
  i18n: {
    locales: ['en-US', 'fr', 'nl-NL'],
    defaultLocale: 'en-US',
  },
}

上記の設定では、en-USfrnl-NL がルーティング可能で、en-US がデフォルトロケールです。pages/blog.js がある場合、以下のURLが利用可能になります:

  • /blog
  • /fr/blog
  • /nl-nl/blog

デフォルトロケールにはプレフィックスがありません。

ドメインルーティング

ドメインルーティングを使用すると、異なるドメインからロケールを提供するように設定できます:

next.config.js
module.exports = {
  i18n: {
    locales: ['en-US', 'fr', 'nl-NL', 'nl-BE'],
    defaultLocale: 'en-US',

    domains: [
      {
        // 注: サブドメインはマッチングされるためにドメイン値に含める必要があります
        // 例: www.example.com は期待されるホスト名である場合に使用する必要があります
        domain: 'example.com',
        defaultLocale: 'en-US',
      },
      {
        domain: 'example.fr',
        defaultLocale: 'fr',
      },
      {
        domain: 'example.nl',
        defaultLocale: 'nl-NL',
        // このドメインにリダイレクトされるべき他のロケールを指定
        locales: ['nl-BE'],
      },
    ],
  },
}

例えば pages/blog.js がある場合、以下のURLが利用可能になります:

  • example.com/blog
  • www.example.com/blog
  • example.fr/blog
  • example.nl/blog
  • example.nl/nl-BE/blog

自動ロケール検出

ユーザーがアプリケーションのルート(通常は /)にアクセスすると、Next.js は Accept-Language ヘッダーと現在のドメインに基づいて、ユーザーが好むロケールを自動的に検出しようとします。

デフォルトロケール以外のロケールが検出された場合、ユーザーは次のいずれかにリダイレクトされます:

  • サブパスルーティングを使用している場合: ロケールプレフィックス付きのパス
  • ドメインルーティングを使用している場合: そのロケールがデフォルトとして指定されているドメイン

ドメインルーティングを使用している場合、Accept-Language ヘッダーが fr;q=0.9 のユーザーが example.com にアクセスすると、example.fr にリダイレクトされます。これはそのドメインが fr ロケールをデフォルトで処理するためです。

サブパスルーティングを使用している場合、ユーザーは /fr にリダイレクトされます。

デフォルトロケールのプレフィックス設定

Next.js 12 と Middleware を使用すると、回避策 でデフォルトロケールにプレフィックスを追加できます。

例えば、以下はいくつかの言語をサポートする next.config.js ファイルです。意図的に "default" ロケールが追加されていることに注意してください。

next.config.js
module.exports = {
  i18n: {
    locales: ['default', 'en', 'de', 'fr'],
    defaultLocale: 'default',
    localeDetection: false,
  },
  trailingSlash: true,
}

次に、Middleware を使用してカスタムルーティングルールを追加できます:

middleware.ts
import { NextRequest, NextResponse } from 'next/server'

const PUBLIC_FILE = /\.(.*)$/

export async function middleware(req: NextRequest) {
  if (
    req.nextUrl.pathname.startsWith('/_next') ||
    req.nextUrl.pathname.includes('/api/') ||
    PUBLIC_FILE.test(req.nextUrl.pathname)
  ) {
    return
  }

  if (req.nextUrl.locale === 'default') {
    const locale = req.cookies.get('NEXT_LOCALE')?.value || 'en'

    return NextResponse.redirect(
      new URL(`/${locale}${req.nextUrl.pathname}${req.nextUrl.search}`, req.url)
    )
  }
}

この Middleware は、API ルートpublic ファイル(フォントや画像など)にデフォルトプレフィックスを追加するのをスキップします。デフォルトロケールへのリクエストがある場合、プレフィックス /en にリダイレクトします。

自動ロケール検出の無効化

自動ロケール検出は以下のように無効化できます:

next.config.js
module.exports = {
  i18n: {
    localeDetection: false,
  },
}

localeDetectionfalse に設定すると、Next.js はユーザーの優先ロケールに基づいて自動的にリダイレクトしなくなり、上記で説明したロケールベースのドメインまたはロケールパスから検出されたロケール情報のみを提供します。

ロケール情報へのアクセス

Next.js ルーターを介してロケール情報にアクセスできます。例えば、useRouter() フックを使用すると、以下のプロパティが利用可能です:

  • locale には現在アクティブなロケールが含まれます。
  • locales には設定されたすべてのロケールが含まれます。
  • defaultLocale には設定されたデフォルトロケールが含まれます。

getStaticProps または getServerSideProps でページを プリレンダリング する場合、ロケール情報は関数に提供される コンテキスト で提供されます。

getStaticPaths を活用する場合、設定されたロケールは関数のコンテキストパラメータの locales の下に、設定された defaultLocaledefaultLocale の下に提供されます。

ロケール間の遷移

next/link または next/router を使用してロケール間を遷移できます。

next/link の場合、現在アクティブなロケールから異なるロケールに遷移するために locale プロパティを提供できます。locale プロパティが提供されない場合、クライアント遷移中に現在アクティブな locale が使用されます。例えば:

import Link from 'next/link'

export default function IndexPage(props) {
  return (
    <Link href="/another" locale="fr">
      To /fr/another へ
    </Link>
  )
}

next/router メソッドを直接使用する場合、遷移オプションを介して使用する locale を指定できます。例えば:

import { useRouter } from 'next/router'

export default function IndexPage(props) {
  const router = useRouter()

  return (
    <div
      onClick={() => {
        router.push('/another', '/another', { locale: 'fr' })
      }}
    >
      to /fr/another へ
    </div>
  )
}

動的ルート クエリ値や非表示の href クエリ値など、すべてのルーティング情報を保持しながらロケールのみを切り替える処理を行うには、href パラメータをオブジェクトとして提供できます:

import { useRouter } from 'next/router'
const router = useRouter()
const { pathname, asPath, query } = router
// ロケールのみを変更し、hrefのクエリを含む他のすべてのルート情報を維持
router.push({ pathname, query }, asPath, { locale: nextLocale })

router.push のオブジェクト構造についての詳細は こちら を参照してください。

ロケールがすでに含まれている href がある場合、ロケールプレフィックスの自動処理をオプトアウトできます:

import Link from 'next/link'

export default function IndexPage(props) {
  return (
    <Link href="/fr/another" locale={false}>
      To /fr/another へ
    </Link>
  )
}

NEXT_LOCALE クッキーの活用

Next.js は NEXT_LOCALE=the-locale クッキーで accept-language ヘッダーを上書きすることをサポートしています。このクッキーは言語スイッチャーを使用して設定でき、ユーザーがサイトに戻ってきた際に / から正しいロケールの場所にリダイレクトする際に指定されたロケールが使用されます。

例えば、ユーザーの accept-language ヘッダーが fr を好むが、NEXT_LOCALE=en クッキーが設定されている場合、/ にアクセスすると、クッキーが削除または期限切れになるまで en ロケールの場所にリダイレクトされます。

検索エンジン最適化

Next.js はユーザーが訪問している言語を知っているため、<html> タグに自動的に lang 属性を追加します。

Next.js はページのバリアントを知らないため、next/head を使用して hreflang メタタグを追加するのはあなたの責任です。hreflang についての詳細は Google Webmasters ドキュメント で学べます。

静的生成との連携方法

国際化ルーティングは Next.js のルーティングレイヤーを利用しないため、output: 'export' とは統合されません。output: 'export' を使用しないハイブリッド Next.js アプリケーションは完全にサポートされています。

動的ルートと getStaticProps ページ

動的ルート を使用する getStaticProps ページの場合、プリレンダリングするすべてのロケールバリアントのページを getStaticPaths から返す必要があります。paths に対して返される params オブジェクトと共に、レンダリングしたいロケールを指定する locale フィールドも返せます。例えば:

pages/blog/[slug].js
export const getStaticPaths = ({ locales }) => {
  return {
    paths: [
      // `locale` が提供されない場合、デフォルトロケールのみが生成されます
      { params: { slug: 'post-1' }, locale: 'en-US' },
      { params: { slug: 'post-1' }, locale: 'fr' },
    ],
    fallback: true,
  }
}

自動静的最適化 および非動的 getStaticProps ページの場合、各ロケールに対してページのバージョンが生成されます。これは、getStaticProps 内で設定されているロケールの数に応じてビルド時間が増加する可能性があるため、考慮することが重要です。

例えば、50のロケールが設定されており、getStaticProps を使用する10の非動的ページがある場合、getStaticProps は500回呼び出されます。ビルドごとに10ページの50バージョンが生成されます。

getStaticProps を使用する動的ページのビルド時間を短縮するには、fallback モード を使用します。これにより、ビルド時にプリレンダリングする最も人気のあるパスとロケールのみを getStaticPaths から返すことができます。その後、Next.js はリクエスト時に残りのページをビルドします。

自動静的最適化ページ

自動静的最適化 ページの場合、各ロケールに対してページのバージョンが生成されます。

非動的 getStaticProps ページ

非動的 getStaticProps ページの場合、上記のように各ロケールに対してバージョンが生成されます。getStaticProps はレンダリングされる各 locale で呼び出されます。特定のロケールをプリレンダリングから除外したい場合は、getStaticProps から notFound: true を返すと、そのバージョンのページは生成されません。

export async function getStaticProps({ locale }) {
  // 外部APIエンドポイントを呼び出して投稿を取得
  // 任意のデータ取得ライブラリを使用できます
  const res = await fetch(`https://.../posts?locale=${locale}`)
  const posts = await res.json()

  if (posts.length === 0) {
    return {
      notFound: true,
    }
  }

  // { props: posts } を返すことで、Blog コンポーネントはビルド時に `posts` を prop として受け取ります
  return {
    props: {
      posts,
    },
  }
}

i18n 設定の制限

  • locales: 合計100ロケール
  • domains: 合計100ロケールドメイン項目

知っておくと良いこと: これらの制限は、ビルド時の潜在的なパフォーマンス問題 を防ぐために最初に追加されました。Next.js 12 では Middleware を使用したカスタムルーティングでこれらの制限を回避できます。