プロジェクト開始時の多言語対応は、もう「悩みの種」ではありません
Pages Routerでi18nを扱ったことがある方なら、SEOエラーを防ぐための手動設定や、言語切り替え時のページリロードを回避するのに苦労した記憶があるでしょう。App Routerの登場により、状況は一変しました。新しいアプローチでは、ルーティングやServer Componentsについてこれまでとは異なる考え方が求められます。
現在、数あるライブラリの中でも、next-intlは最も有力な候補として注目されています。軽量(gzippedで約10kb)であり、Server ComponentsとClient Componentsの両方を強力にサポートしています。さらに重要なのは、JSONファイルが散乱することなく、翻訳を体系的に管理できる点です。
5分で完了するクイックセットアップ
まず、プロジェクトにライブラリを追加します:
npm install next-intl
1. 標準的なディレクトリ構造
i18nをスムーズに動作させるために、すべてのルートをダイナミックセグメントである[locale]の中に配置する必要があります。これはNext.jsがURLから直接言語を認識するための標準的な手法です。理想的なディレクトリ構造は以下の通りです:
├── messages (翻訳ファイルを格納)
│ ├── en.json
│ └── ja.json
├── src
│ ├── i18n.ts (設定ローダー)
│ ├── middleware.ts (ルーティングフィルター)
│ └── app
│ └── [locale]
│ ├── layout.tsx
│ └── page.tsx
├── next.config.mjs
2. アプリケーションに設定を認識させる
messages/ja.jsonファイルを作成しましょう。ここがアプリケーションの核となる翻訳データの保存場所です:
{
"Index": {
"title": "itfromzeroへようこそ!",
"description": "ゼロからのプログラミング学習。"
}
}
次に、src/i18n.tsを設定します。このファイルは、ユーザーがアクセスしている言語に対応するデータをロードするための架け橋となります:
import {notFound} from 'next/navigation';
import {getRequestConfig} from 'next-intl/server';
const locales = ['en', 'ja'];
export default getRequestConfig(async ({locale}) => {
if (!locales.includes(locale as any)) notFound();
return {
messages: (await import(`../messages/${locale}.json`)).default
};
});
src/middleware.tsも忘れずに設定しましょう。これにより、ブラウザの言語設定やCookieを自動的に認識し、ユーザーを正しいURL(例:/jaや/en)にリダイレクトできます:
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
locales: ['en', 'ja'],
defaultLocale: 'ja'
});
export const config = {
// システムファイルとAPIをスキップ
matcher: ['/', '/(ja|en)/:path*']
};
コアコンポーネントの詳細
Middleware:スマートな「ゲートキーパー」
ユーザーがyourdomain.comにアクセスすると、Middlewareが即座にaccept-languageヘッダーをチェックします。日本語のブラウザを使用している場合、自動的に/jaへリダイレクトされます。ヒント:常にlocalePrefix: 'always'を設定することをお勧めします。これによりURLが常に明確になり、Googleのボットが各言語バージョンを個別にインデックスできるため、SEOに非常に有利になります。
Server Componentsの威力
next-intlの最大のメリットは、Server Components上で直接動作する点です。単にあいさつ文を表示するためだけに、不要な'use client'を追加する必要はありません。これにより、ブラウザに送信されるJavaScriptの量を削減し、ページの読み込み速度を大幅に向上させることができます。
// src/app/[locale]/page.tsx
import {useTranslations} from 'next-intl';
export default function Index() {
const t = useTranslations('Index');
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
);
}
実務での経験:JSONファイルが数千行に達すると、管理が非常に困難になります。私はよくJSON Formatterを使用して構文を素早くチェックしています。カンマが一つ足りないだけで、アプリ全体がクラッシュする可能性があるからです。
Type-safeへのアップグレード:タイポを未然に防ぐ
キーの入力ミスは非常によくあるエラーです。例えば、JSONではtitleなのに、コード内でtitelと入力してしまうようなケースです。画面に生のキーが表示されるのを防ぐために、TypeScriptを活用してコーディング時にエラーを検知しましょう。
以下の内容でglobal.d.tsファイルを作成するだけです:
// global.d.ts
type Messages = typeof import('./messages/ja.json');
declare interface IntlMessages extends Messages {}
これで、VS Codeが各キーを自動的に補完(Intellisense)してくれるようになります。入力を間違えると、IDEが即座にエラーを表示します。これは大規模な実務プロジェクトにおいて必須の標準設定です。
スムーズな言語切り替え(Language Switcher)の実装
言語の切り替えに<a>タグを使用しないでください。ページがリロードされ、ユーザー体験が途切れてしまうからです。代わりに、next-intlで最適化されたナビゲーション関数を活用しましょう。
再利用可能なsrc/navigation.tsファイルを作成します:
import {createSharedPathnamesNavigation} from 'next-intl/navigation';
export const locales = ['en', 'ja'] as const;
export const {Link, redirect, usePathname, useRouter} = createSharedPathnamesNavigation({locales});
その後、言語選択コンポーネントは以下のようにシンプルになります:
'use client';
import {usePathname, useRouter} from '@/navigation';
export default function LocaleSwitcher() {
const pathname = usePathname();
const router = useRouter();
const changeLocale = (nextLocale: string) => {
// 現在のルートを維持したまま言語を切り替える
router.replace(pathname, {locale: nextLocale});
};
return (
<select onChange={(e) => changeLocale(e.target.value)}>
<option value="ja">日本語</option>
<option value="en">English</option>
</select>
);
}
実装における重要な注意点
- SEOメタデータ:
generateMetadataを使用してメタタグも翻訳しましょう。多言語サイトでどの言語のページタイトルも同じだと、Googleからの評価が下がってしまいます。 - 単数形・複数形の処理:コード内でif-elseを使用しないでください。
next-intlのICUフォーマットを活用しましょう:"items": "{count, plural, =0 {空} one {1項目} other {#項目}}"。 - 数値と日付のフォーマット:
.toLocaleString()の代わりに、常にformat.dateTime関数を使用してください。これにより、タイムゾーンの違いによるServerとClient間のHydrationMismatchエラーを防ぐことができます。
プロジェクトの初日から i18n を導入することで、将来的な工数を大幅に削減できます。初期設定には多少の手間がかかりますが、そのプロフェッショナルな仕上がりとグローバルなユーザーへのアプローチは、それに見合う十分な価値があります。

