Next.js の i18n、やり方を間違えています
JavaScript の国際化では、欠陥のある慣習がすっかり定着しています。ユーザー向けの文字列をすべて JSON ファイルに切り出し、キーを割り当てて、コンポーネント内ではそのキーを参照するやり方です。テキストそのものを書く代わりに、t('home.hero.title') と書くわけです。UI は一方にあり、コンテンツは別の場所にあります。
この方法でも動きます。実際、何千ものアプリがこのやり方でリリースされています。とはいえ、開発者体験として優れているわけではありません。
コードレビューで t('checkout.summary.total') を見ても、何が変わったのかはわかりません。確認するには JSON ファイルを開く必要があります。キーは考え、名前空間を設計し、整合性を保たなければなりません。どのキーがまだ使われているのか誰にもわからないので、古い翻訳がたまっていきます。この問題は広く浸透していて、それを管理するためだけのツール群が丸ごと存在するほどです。キーを自動提案する IDE 拡張、キーを検証する型ジェネレーター、未使用のキーを警告するリンターなどです。こうしたツールは、設計のまずいパラダイムが生み出した問題の後始末をしているにすぎません。
<T> コンポーネント
コンテンツは、使う場所から切り離すべきではありません。見出し、段落、ボタンを描画するコンポーネントは、それらの要素に何を表示するかを定義する唯一の信頼できる情報源であるべきで、どこか別の場所に保存された文字列を参照するだけの仲介役であってはいけません。コードと翻訳が並行して存在する2つのシステムになると、必ずずれが生じます。
では、ライブラリが逆の発想で動くとしたらどうでしょうか。コードの構造変更を求めるのではなく、コードに合わせて適応するのです。i18n は本来こうあるべきです。
import { T } from 'gt-next';
function Hero() {
return (
<T>
<h1>Ship your product worldwide</h1>
<p>Reach every market without rewriting your app.</p>
</T>
);
}JSX を <T> で囲んでください。英語のテキストは、書いた場所にそのまま残ります。ユーザーがスペイン語や日本語で表示すると、<T> の中の内容が構造や書式も含めて翻訳されます。
キーは不要。JSON ファイルも不要。相互参照も不要です。コードが唯一の信頼できる情報源です。
セットアップ
上記の構文は、Next.js App Router 向けのオープンソース i18n ライブラリ gt-next のものです。使い始めるのに必要なのは、1 つのコマンドだけです。
npx gt@latest initセットアップウィザードでは、依存関係のインストール、withGTConfigによる Next.js 設定のラップ、ルートレイアウトへの GTProvider の追加、ロケールを含む gt.config.json の作成、翻訳のホットリロード用の開発 API キーの設定、CDN 翻訳ストレージの構成を、すべて対話形式で行えます。
完了したら、コンテンツを <T> で囲み、開発サーバーを起動して、<LocaleSelector> コンポーネントで言語を切り替えてください。
import { LocaleSelector } from 'gt-next';
function Header() {
return (
<header>
<nav>{/* ... */}</nav>
<LocaleSelector />
</header>
);
}開発中は翻訳が on-demand で行われるため、アプリを任意の言語ですぐに確認できます。
デプロイ
本番環境では、翻訳は事前に生成されます。
-
本番用の API キーを取得します。 dash.generaltranslation.com から取得してください。本番用のキーは
gtx-api-で始まります (ローカルで使用するgtx-dev-キーとは異なります) 。 -
ビルドに translate ステップを追加します。
{
"scripts": {
"build": "npx gt translate --publish && next build"
}
}translate コマンドは、コードベース内のすべての <T> の使用箇所を検出し、翻訳を生成して CDN に公開します。アプリのビルド時には、すべてのロケールを利用できる状態になります。
次のステップ
- 変数コンポーネント —
<Var>、<Num>、<Currency>を使って、<T>内の動的な内容を扱います - 分岐コンポーネント —
<Plural>と<Branch>を使って、ロケールに応じて条件付きでコンテンツをレンダリングします useGTとgetGT— 属性、プレースホルダー、メタデータ向けのプレーンな文字列を翻訳します- スタンドアロンモード — General Translation プラットフォームを使わずに gt-next を利用します