Next.js(Pages Router)v16でファイルから翻訳データを読み込む方法
翻訳対象の文言をコードから分離しよう
問題
ユーザー向けのテキストをコンポーネント内に直接ハードコードすると、コンテンツとコードが強く結び付いてしまいます。文言を変更するたびに、開発者が実装ファイルを探して修正する必要があります。2言語目を追加する時は、すべてのハードコードされたテキストを見つけて条件分岐で囲まなければなりません。3言語目になるとその論理がさらに複雑になります。こうした方法では翻訳ワークフローがコードのデプロイに依存するため、非技術メンバーが独自にコンテンツを更新することができません。
アプリケーションが大きくなるにつれ、翻訳管理はどんどん難しくなります。多くのコンポーネントに散らばるテキストは一括で確認・更新や重複管理がしにくいです。翻訳者はコードベースへのアクセスが必要なため、開発者と並行して作業することもできません。
解決策
ユーザー向けのテキストをすべて外部のリソースファイルにまとめ、言語ごとに1つのJSONファイルに整理します。各文言は原文ではなく固有のキーで管理し、コンポーネントはハードコードするのではなくそのキーを参照します。
Next.jsから受け取ったロケール情報に基づいて、該当言語の翻訳ファイルを読み込み、そのメッセージをreact-intlのプロバイダーに渡します。言語を切り替えたい場合も、ファイルを差し替えるだけでコンポーネントのコードを変更せずに対応できます。これによりコンテンツと実装が分離され、翻訳者は通常のJSONファイルで編集でき、開発者は安定したメッセージキーを参照できるようになります。
手順
1. ロケールごとに翻訳ファイルを作成する
翻訳テキストをロケールごとに分けてJSONファイルに保存しましょう。各ファイルには、安定した識別子(キー)と、その言語向けに翻訳された値をペアで記述します。
{
"welcome": "Welcome back",
"greeting": "Hello, {name}",
"itemCount": "You have {count, plural, one {# item} other {# items}}"
}
サポートする各ロケールごとに 1 つずつファイルを作成します(例: messages/en.json、messages/es.json、messages/fr.json)。これらのファイルはプロジェクトのルートにある messages ディレクトリ内に配置してください。すべてのファイルで同じキー名を使うことで、react-intl がアクティブなロケールに合わせて正しい翻訳を取得できます。
2. getStaticProps でメッセージを読み込む
getStaticProps で Next.js から渡されるロケール情報に基づいて翻訳ファイルを読み込みます。これにより、メッセージがサーバーサイドで利用可能となり、プロパティとしてページに渡されます。
import { GetStaticProps } from "next";
export const getStaticProps: GetStaticProps = async (context) => {
const locale = context.locale || "en";
const messages = (await import(`../messages/${locale}.json`)).default;
return {
props: {
messages,
},
};
};
動的インポートにより、現在のロケール用ファイルだけが読み込まれます。Next.js は URL やユーザーの設定に応じて自動で locale の値を用意します。
3. _app で IntlProvider にメッセージを渡す
ルートコンポーネントを IntlProvider でラップし、ユーザーの現在のロケールと対応する翻訳済みメッセージで設定します。各ページから自分用のメッセージを提供できるように、pageProps からメッセージを取得してください。
import { AppProps } from "next/app";
import { IntlProvider } from "react-intl";
import { useRouter } from "next/router";
export default function App({ Component, pageProps }: AppProps) {
const { locale, defaultLocale } = useRouter();
return (
<IntlProvider
locale={locale || "en"}
defaultLocale={defaultLocale || "en"}
messages={pageProps.messages}
>
<Component {...pageProps} />
</IntlProvider>
);
}
プロバイダーによって、ツリー内のすべてのコンポーネントでメッセージが利用できるようになります。各ページは getStaticProps で独自のメッセージファイルを読み込み、_app はそのメッセージを pageProps 経由で受け取ります。
4. コンポーネント内でキーによってメッセージを参照する
react-intl の FormattedMessage コンポーネントや useIntl フックを利用して、翻訳テキストを表示します。文字列を直接埋め込むのではなく、必ずキーでメッセージを参照しましょう。
import { FormattedMessage, useIntl } from "react-intl";
export default function HomePage() {
const intl = useIntl();
const userName = "Alice";
return (
<div>
<h1>
<FormattedMessage id="welcome" />
</h1>
<p>
<FormattedMessage id="greeting" values={{ name: userName }} />
</p>
<input placeholder={intl.formatMessage({ id: "searchPlaceholder" })} />
</div>
);
}
react-intl は指定した id の翻訳メッセージを検索して整形します。翻訳が見つからない場合、defaultMessage が指定されていればそれがフォールバックされます。values プロパティで渡した変数は、メッセージ文字列に埋め込まれます。