はじめに/ガイド/国際化

Next.jsでの国際化 (i18n) 実装方法

サンプル

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

i18nルーティングサポートは現在、react-intlreact-i18nextlinguirosettanext-intlnext-translatenext-multilingualtolgeeparaglide-nextnext-intlayerなどの既存の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にアクセスすると、そのドメインがデフォルトでfrロケールを処理するため、example.frにリダイレクトされます。

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

デフォルトロケールにプレフィックスを追加

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

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

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

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

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

このミドルウェアは、APIルートとフォントや画像などのパブリックファイルにデフォルトプレフィックスを追加するのをスキップします。デフォルトロケールへのリクエストがある場合、プレフィックス/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ウェブマスター向けドキュメントで学べます。

静的生成との連携方法

国際化ルーティングは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`をプロップとして受け取ります
  return {
    props: {
      posts,
    },
  }
}

i18n設定の制限

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

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