はじめに
Next.jsで多言語対応をやろうとすると、意外と面倒なんですよね。
i18nextとかreact-intlとか、選択肢はいろいろあるけど、Next.jsのApp Routerにちゃんと対応しているものって限られる。特にServer Componentsで使おうとすると、設定がごちゃごちゃしてきて「これ本当に正しいの?」と不安になることも多い。
そんな中でNext-Intlを試してみたら、「最初からこれ使っておけばよかった」という気持ちになりました。GitHubで4,000スター以上、月間480万ダウンロード。Node.jsやTodoist、Ethereumなど大規模プロジェクトでも採用されている実績があります。
30代になって思うのは、ライブラリ選定で「特化型」は強いということ。Next-Intlは「Next.js専用」に設計されているから、余計な設定や回避策が不要なんですよ。
Next-Intlとは
Next-Intlは、Next.jsアプリケーション専用の国際化(i18n)ライブラリです。
「Internationalization for Next.js」という直球なコンセプトで、App Router、Server Components、静的レンダリングなど、Next.jsの機能にネイティブ対応しています。
現在の最新バージョンはv4.7.0(2026年1月リリース)で、MITライセンス。TypeScriptで94%以上が書かれており、型安全性への本気度が伝わってきます。
特徴・メリット
1. App Router完全対応
これ、意外と重要なんですけど、Next.js 13以降のApp Routerにちゃんと対応しているi18nライブラリって実は少ない。
Next-IntlはServer ComponentsでもClient Componentsでも同じAPIで翻訳が使える。「サーバー側とクライアント側で設定を分ける」みたいな面倒な作業が不要です。
2. 型安全な翻訳キー
TypeScriptで開発していると、翻訳キーのタイポが怖いですよね。
Next-Intlは翻訳キーのオートコンプリートに対応しているので、存在しないキーを参照するとコンパイル時にエラーが出る。これでQOL上がります。
3. ICUメッセージ構文
補間、複数形、性別による文言変更など、複雑な翻訳パターンにも対応。
{
"items": "You have {count, plural, =0 {no items} =1 {1 item} other {# items}}"
}
英語の複数形ルールとか、日本語では意識しないけど必要な処理がちゃんとできる。
4. 日付・数値フォーマット
タイムゾーンを考慮した日付フォーマットや、ロケールに応じた数値表示も標準サポート。サーバーとクライアントで時刻がズレる問題に悩まされなくなります。
5. 国際化ルーティング
言語ごとに異なるURLパスを設定できる。SEO的にも重要な機能ですね。
/en/about → 英語版
/ja/about → 日本語版
インストール方法
前提条件
- Next.js 13以降(App Router推奨)
- React 18以上
インストール
npm install next-intl
これだけ。シンプル。
基本的な使い方
1. 翻訳ファイルの作成
まずは翻訳ファイルを用意します。messages/ディレクトリを作成。
// messages/ja.json
{
"HomePage": {
"title": "ようこそ",
"description": "このサイトへようこそ"
},
"Navigation": {
"home": "ホーム",
"about": "概要",
"contact": "お問い合わせ"
}
}
// messages/en.json
{
"HomePage": {
"title": "Welcome",
"description": "Welcome to this site"
},
"Navigation": {
"home": "Home",
"about": "About",
"contact": "Contact"
}
}
2. 設定ファイルの作成
// i18n/request.ts
import { getRequestConfig } from 'next-intl/server';
export default getRequestConfig(async ({ locale }) => ({
messages: (await import(`../messages/${locale}.json`)).default
}));
3. ミドルウェアの設定
// middleware.ts
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
locales: ['ja', 'en'],
defaultLocale: 'ja'
});
export const config = {
matcher: ['/', '/(ja|en)/:path*']
};
4. レイアウトの設定
// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
export default async function LocaleLayout({
children,
params: { locale }
}: {
children: React.ReactNode;
params: { locale: string };
}) {
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
5. コンポーネントでの使用
// app/[locale]/page.tsx
import { useTranslations } from 'next-intl';
export default function HomePage() {
const t = useTranslations('HomePage');
return (
<main>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</main>
);
}
これだけで多言語対応完了。直感的でしょう?
Server Componentsでの使用
Server Componentsでも同じ書き方が使えるのがポイント。
// Server Component
import { getTranslations } from 'next-intl/server';
export default async function AboutPage() {
const t = await getTranslations('AboutPage');
return (
<main>
<h1>{t('title')}</h1>
</main>
);
}
hookではなくgetTranslationsを使うだけ。統一感があってコードの見通しが良い。
実践的なユースケース
動的な値の補間
const t = useTranslations('Greeting');
// messages/ja.json: { "hello": "こんにちは、{name}さん" }
return <p>{t('hello', { name: 'タロウ' })}</p>;
// 出力: こんにちは、タロウさん
複数形の処理
// messages/en.json
{
"notifications": "You have {count, plural, =0 {no notifications} =1 {1 notification} other {# notifications}}"
}
const t = useTranslations('Dashboard');
return <p>{t('notifications', { count: 5 })}</p>;
// 出力: You have 5 notifications
個人的には、この複数形処理が地味に助かります。自前で実装すると面倒なんですよ。
日付・数値フォーマット
import { useFormatter } from 'next-intl';
export default function PriceDisplay({ price, date }: Props) {
const format = useFormatter();
return (
<div>
<p>価格: {format.number(price, { style: 'currency', currency: 'JPY' })}</p>
<p>更新日: {format.dateTime(date, { dateStyle: 'long' })}</p>
</div>
);
}
// 出力例:
// 価格: ¥1,500
// 更新日: 2026年1月2日
リッチテキスト
HTMLタグを含む翻訳も安全に扱える。
{
"terms": "利用規約に<link>同意</link>する必要があります"
}
const t = useTranslations('Form');
return (
<p>
{t.rich('terms', {
link: (chunks) => <a href="/terms">{chunks}</a>
})}
</p>
);
XSSの心配なく、柔軟な翻訳ができます。
言語切り替え
'use client';
import { useLocale } from 'next-intl';
import { useRouter, usePathname } from 'next/navigation';
export default function LanguageSwitcher() {
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const switchLocale = (newLocale: string) => {
const newPath = pathname.replace(`/${locale}`, `/${newLocale}`);
router.push(newPath);
};
return (
<div>
<button
onClick={() => switchLocale('ja')}
disabled={locale === 'ja'}
>
日本語
</button>
<button
onClick={() => switchLocale('en')}
disabled={locale === 'en'}
>
English
</button>
</div>
);
}
型安全な翻訳キー
TypeScriptの恩恵を最大限に受けるなら、型定義を追加しておくと良い。
// global.d.ts
import ja from './messages/ja.json';
type Messages = typeof ja;
declare global {
interface IntlMessages extends Messages {}
}
これで存在しない翻訳キーを参照するとコンパイルエラーになる。タイポによる本番障害を防げます。
まとめ
Next-Intlを導入して感じた変化:
- セットアップ: 15分程度で完了。ドキュメントが分かりやすい
- 学習コスト: 低め。既存のi18n経験があれば即使える
- App Router対応: Server ComponentsでもClient Componentsでも同じ書き方
- 型安全性: 翻訳キーのオートコンプリートでタイポ防止
- パフォーマンス: 静的レンダリングとの相性も良好
正直なところ、Next.jsで多言語対応するならNext-Intl一択ですね。
「Next.js専用」という割り切りが、かえって使いやすさに繋がっている。汎用的なi18nライブラリをNext.jsに合わせて設定する手間を考えると、最初からNext-Intlを選んだ方がコスパ的に良い。
これから多言語対応を始める人は、ぜひ検討してみてください。特にApp Routerを使っているプロジェクトには最適解だと思います。
