翻訳の読み込み
メッセージを管理するためのプロバイダーの使用
問題
'Hello World'のようなテキストをアプリケーションのコンポーネントに直接ハードコーディングすると、コンテンツとコードが密結合してしまいます。異なる言語を表示するには、開発者はコンポーネントを複製するか、if/elseロジックを追加する必要があり、翻訳が拡張できなくなり、新しいテキストごとにコード全体の変更が必要になります。
解決策
react-intlのIntlProviderを使用して翻訳を提供します。サーバー上のルートレイアウトで翻訳メッセージを読み込み、それをクライアント側のプロバイダーコンポーネントに渡し、他のクライアントコンポーネントでuseIntlフックを使用して消費します。
ステップ
1. react-intlをインストールする
まず、プロジェクトにreact-intlを依存関係として追加します。
npm install react-intl
2. フラットな翻訳ファイルを作成する
dictionariesフォルダを作成します。その中に、各言語のJSONファイルを追加します。react-intlはフラットなキーバリュー構造で最もよく機能します。
// dictionaries/en.json
{
"home.title": "Home Page",
"home.welcome": "Hello, welcome to our site!",
"about.title": "About Us"
}
// dictionaries/es.json
{
"home.title": "Página de Inicio",
"home.welcome": "¡Hola, bienvenido a nuestro sitio!",
"about.title": "Sobre Nosotros"
}
3. 辞書を読み込む関数を作成する
langパラメータに基づいてサーバー上で正しい辞書ファイルを読み込むヘルパー関数を作成します。
// app/get-dictionary.ts
import 'server-only';
// フラットなメッセージオブジェクトの型を定義
type Messages = Record<string, string>;
const dictionaries: { [key: string]: () => Promise<Messages> } = {
en: () => import('@/dictionaries/en.json').then((module) => module.default),
es: () => import('@/dictionaries/es.json').then((module) => module.default),
// fr: () => import('@/dictionaries/fr.json').then((module) => module.default),
};
export const getDictionary = async (lang: string) => {
const load = dictionaries[lang];
if (load) {
return load();
}
// 英語にフォールバック
return dictionaries.en();
};
4. クライアントサイドプロバイダーを作成する
IntlProviderはReactのContextを使用するクライアントコンポーネントです。サーバーからロードされたメッセージを受け取ることができるラッパーを作成する必要があります。
// app/components/IntlClientProvider.tsx
'use client';
import { IntlProvider } from 'react-intl';
type Props = {
children: React.ReactNode;
locale: string;
messages: Record<string, string>; // フラットなメッセージオブジェクト
};
export default function IntlClientProvider({
children,
locale,
messages,
}: Props) {
return (
<IntlProvider messages={messages} locale={locale} defaultLocale="en">
{children}
</IntlProvider>
);
}
5. ルートレイアウトを更新する
app/[lang]/layout.tsxをasyncコンポーネントに変更します。これによりメッセージをロードし、IntlClientProviderに渡します。
// app/[lang]/layout.tsx
import { getDictionary } from '@/app/get-dictionary';
import IntlClientProvider from '@/app/components/IntlClientProvider';
export async function generateStaticParams() {
return [{ lang: 'en' }, { lang: 'es' }];
}
export default async function RootLayout({
children,
params,
}: {
children: React.ReactNode;
params: { lang: string };
}) {
// サーバー上でメッセージをロード
const messages = await getDictionary(params.lang);
return (
<html lang={params.lang}>
<body>
{/* メッセージをクライアントプロバイダーに渡す */}
<IntlClientProvider locale={params.lang} messages={messages}>
{children}
</IntlClientProvider>
</body>
</html>
);
}
6. クライアントコンポーネントで翻訳を使用する
これで任意のクライアントコンポーネントでuseIntlフックを使用できます。サーバーコンポーネントではこのフックを使用できません。
翻訳されたテキストを表示する新しいクライアントコンポーネントを作成します:
// app/components/HomePageContent.tsx
'use client';
import { useIntl } from 'react-intl';
export default function HomePageContent() {
const intl = useIntl();
return (
<div>
<h1>{intl.formatMessage({ id: 'home.title' })}</h1>
<p>{intl.formatMessage({ id: 'home.welcome' })}</p>
</div>
);
}
7. コンポーネントをページに追加する
最後に、新しいクライアントコンポーネントをページに追加します。
// app/[lang]/page.tsx
import HomePageContent from '@/app/components/HomePageContent';
export default function Home() {
// このページはサーバーコンポーネントです
return (
<div>
{/* クライアントコンポーネントをレンダリングします */}
<HomePageContent />
</div>
);
}