IntlayerとNext.js 14のApp Routerを使用した国際化(i18n)の開始方法
Intlayerとは?
Intlayerは、モダンなWebアプリケーションで多言語対応を簡素化するために設計された革新的なオープンソースの国際化(i18n)ライブラリです。Intlayerは、最新のNext.js 14フレームワークとその強力なApp Routerとシームレスに統合されます。効率的なレンダリングのためにServer Componentsに最適化されており、Turbopack(Next.js >= 15から)とも完全に互換性があります。
Intlayerを使用すると、以下のことが可能です:
- コンポーネントレベルで宣言的な辞書を使用して翻訳を簡単に管理。
- メタデータ、ルート、コンテンツを動的にローカライズ。
- クライアントサイドおよびサーバーサイドコンポーネントの両方で翻訳にアクセス。
- 自動生成された型でTypeScriptサポートを確保し、オートコンプリートやエラー検出を改善。
- 動的なロケール検出や切り替えなどの高度な機能を活用。
IntlayerはNext.js 12、13、14、15に対応しています。Next.js Page Routerを使用している場合は、このガイドを参照してください。Next.js 15(Turbopackの有無にかかわらず)については、このガイドを参照してください。
Next.jsアプリケーションでIntlayerをセットアップするためのステップバイステップガイド
ステップ1: 依存関係をインストールする
npmを使用して必要なパッケージをインストールします:
npm install intlayer next-intlayer
intlayer
next-intlayer
IntlayerをNext.jsと統合するパッケージ。Next.jsの国際化のためのコンテキストプロバイダーとフックを提供します。また、WebpackまたはTurbopackとの統合のためのNext.jsプラグインや、ユーザーの優先ロケールを検出し、クッキーを管理し、URLリダイレクトを処理するミドルウェアも含まれています。
ステップ2: プロジェクトを設定する
アプリケーションの言語を設定するための設定ファイルを作成します:
import { Locales, type IntlayerConfig } from "intlayer";const config: IntlayerConfig = { internationalization: { locales: [ Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH, // 他のロケール ], defaultLocale: Locales.ENGLISH, },};export default config;
この設定ファイルを通じて、ローカライズされたURL、ミドルウェアリダイレクト、クッキー名、コンテンツ宣言の場所と拡張子、コンソールでのIntlayerログの無効化などを設定できます。利用可能なパラメータの完全なリストについては、設定ドキュメントを参照してください。
ステップ3: Next.js設定にIntlayerを統合する
Next.jsの設定をIntlayerを使用するように構成します:
import { withIntlayer } from "next-intlayer/server";/** @type {import('next').NextConfig} */const nextConfig = {};export default withIntlayer(nextConfig);
withIntlayer() Next.jsプラグインは、IntlayerをNext.jsと統合するために使用されます。コンテンツ宣言ファイルのビルドを確保し、開発モードでそれらを監視します。WebpackまたはTurbopack環境内でIntlayer環境変数を定義します。また、パフォーマンスを最適化するためのエイリアスを提供し、サーバーコンポーネントとの互換性を確保します。
ステップ4: ロケール検出用のミドルウェアを設定する
ユーザーの優先ロケールを検出するためのミドルウェアを設定します:
export { intlayerMiddleware as middleware } from "next-intlayer/middleware";export const config = { matcher: "/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\..*|_next).*)",};
intlayerMiddlewareは、ユーザーの優先ロケールを検出し、設定で指定された適切なURLにリダイレクトします。また、ユーザーの優先ロケールをクッキーに保存することも可能です。
matcherパラメータをアプリケーションのルートに合わせて調整してください。詳細については、Next.jsのミドルウェア設定に関するドキュメントを参照してください。
ステップ5: 動的ロケールルートを定義する
RootLayoutからすべてを削除し、次のコードに置き換えます:
import type { PropsWithChildren, FC } from "react";import "./globals.css";const RootLayout: FC<PropsWithChildren> = ({ children }) => children;export default RootLayout;
ロケールのパスを提供するために、[locale]ディレクトリに新しいレイアウトを追加します:
import type { Next14LayoutIntlayer } from "next-intlayer";import { Inter } from "next/font/google";import { getHTMLTextDir } from "intlayer";const inter = Inter({ subsets: ["latin"] });const LocaleLayout: Next14LayoutIntlayer = ({ children, params: { locale },}) => ( <html lang={locale} dir={getHTMLTextDir(locale)}> <body className={inter.className}>{children}</body> </html>);export default LocaleLayout;
[locale]パスセグメントはロケールを定義するために使用されます。例:/en-US/aboutはen-USを参照し、/fr/aboutはfrを参照します。
次に、アプリケーションレイアウトにgenerateStaticParams関数を実装します。
export { generateStaticParams } from "next-intlayer"; // 挿入する行const LocaleLayout: Next14LayoutIntlayer = ({ children, params: { locale },}) => { /*... 残りのコード*/};export default LocaleLayout;
generateStaticParamsは、すべてのロケールに必要なページを事前にビルドし、ランタイム計算を削減し、ユーザーエクスペリエンスを向上させます。詳細については、Next.jsのgenerateStaticParamsに関するドキュメントを参照してください。
ステップ6: コンテンツを宣言する
翻訳を保存するためのコンテンツ宣言を作成および管理します:
import { t, type Dictionary } from "intlayer";const pageContent = { key: "page", content: { getStarted: { main: t({ en: "Get started by editing", fr: "Commencez par éditer", es: "Comience por editar", }), pageLink: "src/app/page.tsx", }, },} satisfies Dictionary;export default pageContent;
コンテンツ宣言は、contentDirディレクトリ(デフォルトでは./src)に含まれている限り、アプリケーション内のどこにでも定義できます。また、コンテンツ宣言ファイルの拡張子(デフォルトでは.content.{ts,tsx,js,jsx,mjs,cjs})と一致する必要があります。詳細については、コンテンツ宣言ドキュメントを参照してください。
ステップ7: コード内でコンテンツを利用する
アプリケーション全体でコンテンツ辞書にアクセスします:
import { ClientComponentExample } from "@components/ClientComponentExample";import { ServerComponentExample } from "@components/ServerComponentExample";import { type Next14PageIntlayer, IntlayerClientProvider } from "next-intlayer";import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";const Page: Next14PageIntlayer = ({ params: { locale } }) => { const content = useIntlayer("page", locale); return ( <> <p> {content.getStarted.main} <code>{content.getStarted.pageLink}</code> </p> <IntlayerServerProvider locale={locale}> <IntlayerClientProvider locale={locale}> <ServerComponentExample /> <ClientComponentExample /> </IntlayerClientProvider> </IntlayerServerProvider> </> );};export default Page;
- IntlayerClientProviderは、クライアントサイドコンポーネントにロケールを提供するために使用されます。任意の親コンポーネント(レイアウトを含む)に配置できます。ただし、Next.jsがレイアウトコードをページ間で共有するため、レイアウトに配置することをお勧めします。これにより、各ページで再初期化する必要がなくなり、パフォーマンスが向上し、アプリケーション全体で一貫したローカライズコンテキストが維持されます。
IntlayerServerProviderは、サーバーの子にロケールを提供するために使用されます。レイアウトに設定することはできません。
レイアウトとページは共通のサーバーコンテキストを共有できません。これは、サーバーコンテキストシステムがリクエストごとのデータストア(Reactのキャッシュメカニズムを介して)に基づいているためです。これにより、アプリケーションの異なるセグメントに対して各「コンテキスト」が再作成されます。プロバイダーを共有レイアウトに配置すると、この分離が壊れ、サーバーコンポーネントにサーバーコンテキスト値が正しく伝播されなくなります。
"use client";import type { FC } from "react";import { useIntlayer } from "next-intlayer";const ClientComponentExample: FC = () => { const content = useIntlayer("client-component-example"); // 関連するコンテンツ宣言を作成 return ( <div> <h2>{content.title} </h2> <p>{content.content}</p> </div> );};
import type { FC } from "react";import { useIntlayer } from "next-intlayer/server";const ServerComponentExample: FC = () => { const content = useIntlayer("server-component-example"); // 関連するコンテンツ宣言を作成 return ( <div> <h2>{content.title} </h2> <p>{content.content}</p> </div> );};
alt、title、href、aria-labelなどのstring属性でコンテンツを使用する場合は、関数の値を呼び出す必要があります。例:
jsx<img src={content.image.src.value} alt={content.image.value} />
useIntlayerフックの詳細については、ドキュメントを参照してください。
(オプション) ステップ8: メタデータの国際化
ページのタイトルなどのメタデータを国際化したい場合は、Next.jsが提供するgenerateMetadata関数を使用できます。この関数内でgetTranslation関数を使用してメタデータを翻訳します。
import { type IConfigLocales, getTranslation, getMultilingualUrls,} from "intlayer";import type { Metadata } from "next";import type { LocalParams } from "next-intlayer";export const generateMetadata = ({ params: { locale },}: LocalParams): Metadata => { const t = <T>(content: IConfigLocales<T>) => getTranslation(content, locale); /** * 各ロケールのすべてのURLを含むオブジェクトを生成します。 * * 例: * ```ts * getMultilingualUrls('/about'); * * // 戻り値 * // { * // en: '/about', * // fr: '/fr/about', * // es: '/es/about', * // } * ``` */ const multilingualUrls = getMultilingualUrls("/"); return { title: t<string>({ en: "My title", fr: "Mon titre", es: "Mi título", }), description: t({ en: "My description", fr: "Ma description", es: "Mi descripción", }), alternates: { canonical: "/", languages: { ...multilingualUrls, "x-default": "/" }, }, openGraph: { url: multilingualUrls[locale], }, };};// ... 残りのコード
メタデータ最適化について詳しくは、公式Next.jsドキュメントを参照してください。
(オプション) ステップ9: sitemap.xmlとrobots.txtの国際化
sitemap.xmlとrobots.txtを国際化するには、Intlayerが提供するgetMultilingualUrls関数を使用できます。この関数を使用すると、サイトマップ用の多言語URLを生成できます。
import { getMultilingualUrls } from "intlayer";import type { MetadataRoute } from "next";const sitemap = (): MetadataRoute.Sitemap => [ { url: "https://example.com", alternates: { languages: getMultilingualUrls("https://example.com"), }, }, { url: "https://example.com/login", alternates: { languages: getMultilingualUrls("https://example.com/login"), }, }, { url: "https://example.com/register", alternates: { languages: getMultilingualUrls("https://example.com/register"), }, },];export default sitemap;
import type { MetadataRoute } from "next";import { getMultilingualUrls } from "intlayer";const getAllMultilingualUrls = (urls: string[]) => urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);const robots = (): MetadataRoute.Robots => ({ rules: { userAgent: "*", allow: ["/"], disallow: getAllMultilingualUrls(["/login", "/register"]), }, host: "https://example.com", sitemap: `https://example.com/sitemap.xml`,});export default robots;
サイトマップ最適化について詳しくは、公式Next.jsドキュメントを参照してください。robots.txt最適化について詳しくは、公式Next.jsドキュメントを参照してください。
(オプション) ステップ10: コンテンツの言語を変更する
コンテンツの言語を変更するには、useLocaleフックが提供するsetLocale関数を使用します。この関数を使用すると、アプリケーションのロケールを設定し、それに応じてコンテンツを更新できます。
"use client";import { Locales, getHTMLTextDir, getLocaleName, getLocalizedUrl,} from "intlayer";import { useLocale } from "next-intlayer";import { type FC } from "react";import Link from "next/link";const LocaleSwitcher: FC = () => { const { locale, pathWithoutLocale, availableLocales, setLocale } = useLocale(); return ( <div> <button popoverTarget="localePopover">{getLocaleName(locale)}</button> <div id="localePopover" popover="auto"> {availableLocales.map((localeItem) => ( <Link href={getLocalizedUrl(pathWithoutLocale, localeItem)} hrefLang={localeItem} key={localeItem} aria-current={locale === localeItem ? "page" : undefined} onClick={(e) => { e.preventDefault(); setLocale(localeItem); }} > <span> {/* ロケール - 例: FR */} {localeItem} </span> <span> {/* 自身のロケールでの言語 - 例: Français */} {getLocaleName(localeItem, locale)} </span> <span dir={getHTMLTextDir(localeItem)} lang={localeItem}> {/* 現在のロケールでの言語 - 例: Francés(現在のロケールがLocales.SPANISHに設定されている場合) */} {getLocaleName(localeItem)} </span> <span dir="ltr" lang={Locales.ENGLISH}> {/* 英語での言語 - 例: French */} {getLocaleName(localeItem, Locales.ENGLISH)} </span> </Link> ))} </div> </div> );};
ドキュメント参照:
(オプション) ステップ11: ローカライズされたリンクコンポーネントの作成
アプリケーションのナビゲーションが現在のロケールを尊重するようにするために、カスタムLinkコンポーネントを作成できます。このコンポーネントは、内部URLを自動的に現在の言語でプレフィックスします。たとえば、フランス語を話すユーザーが「About」ページへのリンクをクリックすると、/aboutではなく/fr/aboutにリダイレクトされます。
この動作は以下の理由で便利です:
- SEOとユーザーエクスペリエンス:ローカライズされたURLは、検索エンジンが言語固有のページを正しくインデックスし、ユーザーに好みの言語でコンテンツを提供するのに役立ちます。
- 一貫性:アプリケーション全体でローカライズされたリンクを使用することで、ナビゲーションが現在のロケール内に留まり、予期しない言語切り替えを防ぎます。
- 保守性:ローカリゼーションロジックを単一のコンポーネントに集中させることで、URLの管理が簡素化され、アプリケーションが成長するにつれてコードベースの保守性が向上します。
以下は、TypeScriptでローカライズされたLinkコンポーネントを実装した例です:
"use client";import { getLocalizedUrl } from "intlayer";import NextLink, { type LinkProps as NextLinkProps } from "next/link";import { useLocale } from "next-intlayer";import { forwardRef, PropsWithChildren, type ForwardedRef } from "react";/** * 与えられたURLが外部リンクかどうかを確認するユーティリティ関数。 * URLがhttp://またはhttps://で始まる場合、外部リンクと見なされます。 */export const checkIsExternalLink = (href?: string): boolean => /^https?:///.test(href ?? "");/** * 現在のロケールに基づいてhref属性を適応させるカスタムLinkコンポーネント。 * 内部リンクの場合、`getLocalizedUrl`を使用してURLにロケールをプレフィックスします(例:/fr/about)。 * これにより、ナビゲーションが同じロケールコンテキスト内に留まることが保証されます。 */export const Link = forwardRef< HTMLAnchorElement, PropsWithChildren<NextLinkProps>>(({ href, children, ...props }, ref: ForwardedRef<HTMLAnchorElement>) => { const { locale } = useLocale(); const isExternalLink = checkIsExternalLink(href.toString()); // リンクが内部リンクで有効なhrefが提供されている場合、ローカライズされたURLを取得します。 const hrefI18n: NextLinkProps["href"] = href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href; return ( <NextLink href={hrefI18n} ref={ref} {...props}> {children} </NextLink> );});Link.displayName = "Link";
動作の仕組み
外部リンクの検出:
ヘルパー関数checkIsExternalLinkは、URLが外部リンクかどうかを判定します。外部リンクは変更されず、そのまま残されます。現在のロケールの取得:
useLocaleフックは現在のロケール(例:frはフランス語)を提供します。URLのローカライズ:
内部リンク(つまり、外部リンクではない場合)については、getLocalizedUrlを使用してURLに現在のロケールを自動的にプレフィックスします。これにより、ユーザーがフランス語を使用している場合、/aboutをhrefとして渡すと、/fr/aboutに変換されます。リンクの返却:
コンポーネントはローカライズされたURLを持つ<a>要素を返し、ロケールに一貫性のあるナビゲーションを保証します。
このLinkコンポーネントをアプリケーション全体で統合することで、SEOとユーザビリティが向上し、一貫性のある言語対応のユーザーエクスペリエンスを維持できます。
TypeScriptの設定
Intlayerは、TypeScriptのメリットを活用し、コードベースを強化するためにモジュール拡張を使用します。
自動生成された型を含めるようにTypeScript設定を確認してください。
{ // ... 既存のTypeScript設定 "include": [ // ... 既存のTypeScript設定 ".intlayer/**/*.ts", // 自動生成された型を含める ],}
Gitの設定
Intlayerによって生成されたファイルを無視することをお勧めします。これにより、これらのファイルをGitリポジトリにコミットすることを回避できます。
これを行うには、以下の指示を.gitignoreファイルに追加します:
# Intlayerによって生成されたファイルを無視.intlayer
さらに進む
このドキュメントを改善するアイデアがある場合は、GitHubでプルリクエストを送信することで自由に貢献してください。
ドキュメントへのGitHubリンク