👏
Next.js v13でのi18n対応を試してみた
概要
以下ポストでNext.js v13のi18n対応が紹介されていたため、実際に試してみました。
1. 多言語対応用のJSONを作成
srcフォルダ直下にdictionaries
を作成し、ja.json
、en.json
を置く。
ja.json
{
"hello": {
"title": "こんにちは、世界",
"text": "Next.jsを使ったi18n対応"
}
}
en.json
{
"hello": {
"title": "Hello World!",
"text": "i18n support using Next.js"
}
}
2. 言語に応じたjsonをインポートする関数を定義
jsonファイルと同じ階層に渡された言語に応じたjsonをインポートするための関数を定義する。
index.ts
import 'server-only';
// 辞書の型を定義
type Dictionary = {
[key: string]: { [key: string]: string };
};
// 辞書のインポート関数の型を定義
type DictionaryImportFunction = () => Promise<Dictionary>;
const dictionaries: Record<string, DictionaryImportFunction> = {
ja: () => import('@/dictionaries/ja.json').then((module) => module.default),
en: () => import('@/dictionaries/en.json').then((module) => module.default),
};
// 文字列("ja","en")を引数に取り、対応するjsonをインポートして返す
export const getDictionaries = async (locale: string) => dictionaries[locale]();
3. page.tsxにgetDictionariesを追加
/ja/hello
のような形でアクセスしたときにjsonで定義したものを表示するためにpage.tsxにgetDictionariesを追加する。
jaの部分は動的に変わるためDynamic Routesを使う。
[lang]/hello/page.tsx
import Link from 'next/link';
import { getDictionaries } from '@/dictionaries';
export default async function Page({ params }: { params: { lang: string } }) {
const dict = await getDictionaries(params.lang);
return (
<main className='flex min-h-screen flex-col items-center'>
<div className='flex gap-8'>
<Link href='/ja/hello'>日本語</Link>
<Link href='/en/hello'>英語</Link>
</div>
<div className='flex flex-col items-center justify-center'>
{/* dict.hello.xxxxでjsonに定義した値を表示 */}
<h1>{dict.hello.title}</h1>
<p>{dict.hello.text}</p>
</div>
</main>
);
}
4. 言語が指定されていない場合の考慮
今のままだとhttp://localhost:3000/hello
のように言語が指定されていない場合にエラーとなってしまうためmiddlewareを使ってリダイレクト処理を入れる。
middlewareの定義はプロジェクトのルート(pagesやappと同じ階層か、srcフォルダがあればsrcの中)にmiddleware.ts(または.js)で行う。
middlewareで定義するリダイレクト処理は以下の通り。
middleware.ts
import { NextResponse } from 'next/server';
import { NextRequest } from 'next/server';
const locales = ['ja', 'en'];
export function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
const pathnameIsMissingLocale = locales.every(
(locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
);
if (pathnameIsMissingLocale) {
return NextResponse.redirect(new URL(`/ja/${pathname}`, request.url));
}
}
export const config = {
matcher: ['/((?!_next).*)'],
};
最後に
かなり簡単にi18n対応できそうですが、dictionaries/index.ts内でimport 'server-only';
としてあり、これだとクライアントコンポーネントで使えないためこのまま使うのは難しそう。
この記事のコードは以下にあります。
Discussion