Next.jsで国際化を実装する方法
Next.jsはv10.0.0から国際化(i18n)ルーティングをサポートしています。ロケールのリスト、デフォルトロケール、ドメイン固有のロケールを指定すると、Next.jsが自動的にルーティングを処理します。
i18nルーティングサポートは、react-intl、react-i18next、lingui、rosetta、next-intl、next-translate、next-multilingual、tolgee、paraglide-next、next-intlayer、gt-reactなどの既存のi18nライブラリソリューションを補完するために設計されており、ルートとロケール解析を効率化します。
はじめに
まず、next.config.jsファイルにi18n設定を追加します。
ロケールはUTSロケール識別子で、ロケールを定義するための標準化された形式です。
一般的にロケール識別子は、言語、地域、スクリプトから構成され、ダッシュで区切られます。language-region-scriptの形式です。地域とスクリプトはオプションです。例を挙げます。
en-US- アメリカで話されている英語nl-NL- オランダで話されているオランダ語nl- オランダ語、特定の地域なし
ユーザーのロケールがnl-BEであり、設定に記載されていない場合、利用可能であればnlにリダイレクトされます。利用できない場合はデフォルトロケールにリダイレクトされます。
国のすべての地域をサポートしない場合は、フォールバックとして機能する国のロケールを含めるのが良い実践です。
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',
// httpsの代わりにhttpを使用してロケールドメインを
// ローカルでテストするためのオプションのhttpフィールド
http: true,
},
],
},
}ロケール戦略
ロケール処理には2つの戦略があります。サブパスルーティングとドメインルーティングです。
サブパスルーティング
サブパスルーティングはURLパスにロケールを配置します。
module.exports = {
i18n: {
locales: ['en-US', 'fr', 'nl-NL'],
defaultLocale: 'en-US',
},
}上記の設定によりen-US、fr、nl-NLがルーティング可能になり、en-USがデフォルトロケールになります。pages/blog.jsがある場合、以下のURLが利用可能になります。
/blog/fr/blog/nl-nl/blog
デフォルトロケールには接頭辞がありません。
ドメインルーティング
ドメインルーティングを使用すると、異なるドメインからロケールを提供するように設定できます。
module.exports = {
i18n: {
locales: ['en-US', 'fr', 'nl-NL', 'nl-BE'],
defaultLocale: 'en-US',
domains: [
{
// 注:サブドメインはマッチするようにドメイン値に含める必要があります。
// 例:予想されるホスト名が www.example.com の場合、
// 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/blogwww.example.com/blogexample.fr/blogexample.nl/blogexample.nl/nl-BE/blog
自動ロケール検出
ユーザーがアプリケーションのルート(通常は/)にアクセスすると、Next.jsはAccept-Languageヘッダーと現在のドメインに基づいて、ユーザーが優先するロケールを自動的に検出しようとします。
デフォルトロケール以外のロケールが検出された場合、ユーザーは以下のいずれかにリダイレクトされます。
- **サブパスルーティング使用時:**ロケール接頭辞付きパス
- **ドメインルーティング使用時:**そのロケールをデフォルトとして指定したドメイン
ドメインルーティングを使用する場合、Accept-Languageヘッダーがfr;q=0.9であるユーザーがexample.comを訪問すると、そのドメインがfrロケールをデフォルトで処理するため、example.frにリダイレクトされます。
サブパスルーティングを使用する場合、ユーザーは/frにリダイレクトされます。
デフォルトロケールへの接頭辞付け
Next.js 12とProxyを使用すると、回避方法でデフォルトロケールに接頭辞を追加できます。
例えば、いくつかの言語をサポートするnext.config.jsファイルを以下に示します。"default"ロケールが意図的に追加されていることに注意してください。
module.exports = {
i18n: {
locales: ['default', 'en', 'de', 'fr'],
defaultLocale: 'default',
localeDetection: false,
},
trailingSlash: true,
}次に、Proxyを使用してカスタムルーティングルールを追加できます。
import { NextRequest, NextResponse } from 'next/server'
const PUBLIC_FILE = /\.(.*)$/
export async function proxy(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)
)
}
}このProxyはAPIルートと、フォントや画像などのpublicファイルへのデフォルト接頭辞の追加をスキップします。デフォルトロケールへのリクエストが行われた場合、接頭辞/enにリダイレクトします。
自動ロケール検出を無効にする
自動ロケール検出は以下で無効にできます。
module.exports = {
i18n: {
localeDetection: false,
},
}localeDetectionがfalseに設定されている場合、Next.jsはユーザーの優先ロケールに基づいて自動的にリダイレクトしなくなり、上記で説明したロケールベースのドメインまたはロケールパスから検出されたロケール情報のみを提供します。
ロケール情報へのアクセス
Next.jsルーターを通じてロケール情報にアクセスできます。例えば、useRouter()フックを使用して、以下のプロパティが利用可能です。
localeは現在アクティブなロケールを含みます。localesはすべての設定されたロケールを含みます。defaultLocaleは設定されたデフォルトロケールを含みます。
getStaticPropsまたはgetServerSidePropsを使用してページをプリレンダリングする場合、ロケール情報は関数に提供されるコンテキストで提供されます。
getStaticPathsを活用する場合、設定されたロケールは関数のコンテキストパラメータのlocalesの下で提供され、設定されたdefaultLocaleはdefaultLocaleの下で提供されます。
ロケール間の遷移
ロケール間を遷移するには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">
/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' })
}}
>
/fr/anotherへ
</div>
)
}localeのみを切り替え、動的ルートクエリ値や非表示のhrefクエリ値など、すべてのルーティング情報を保持するには、hrefパラメータをオブジェクトとして提供できることに注意してください。
import { useRouter } from 'next/router'
const router = useRouter()
const { pathname, asPath, query } = router
// localeのみを変更し、hrefのクエリを含むすべての他のルート情報を保持
router.push({ pathname, query }, asPath, { locale: nextLocale })router.pushのオブジェクト構造の詳細については、こちらをご覧ください。
localeをすでに含むhrefがある場合、localeプレフィックスの自動処理をオプトアウトできます。
import Link from 'next/link'
export default function IndexPage(props) {
return (
<Link href="/fr/another" locale={false}>
/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 Webmasterドキュメントをご覧ください。
これは静的生成でどのように機能しますか?
国際化ルーティングは
output: 'export'と統合されないことに注意してください。Next.jsルーティングレイヤーを活用していないためです。output: 'export'を使用しないハイブリッドNext.jsアプリケーションは完全にサポートされています。
動的ルートとgetStaticPropsページ
動的ルートを持つgetStaticPropsを使用するページの場合、プリレンダリングするページのすべてのロケール変種はgetStaticPathsから返される必要があります。返されたparamsオブジェクトに加えて、pathsのlocaleフィールドも返すことができ、レンダリングするロケールを指定します。例を挙げます。
export const getStaticPaths = ({ locales }) => {
return {
paths: [
// `locale`が提供されない場合、defaultLocaleのみが生成されます
{ params: { slug: 'post-1' }, locale: 'en-US' },
{ params: { slug: 'post-1' }, locale: 'fr' },
],
fallback: true,
}
}自動的に静的最適化されたページと非動的なgetStaticPropsページの場合、各ロケール用のページバージョンが生成されます。これは、設定されたロケール数に応じてビルド時間が増加する可能性があるため、考慮することが重要です。
例えば、50個のロケールがgetStaticPropsで設定されており、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のProxyを使用したカスタムルーティングでこれらの制限を回避できます。