TypeScript i18nextの動的な翻訳キーを抽出対象に含める
i18nextでステータスに応じた文言を表示する場合、翻訳キーを動的に切り替えることがあります。
ただし、実行時に正しく表示できることと、翻訳キーの抽出ツールが使用箇所を検出できることは別です。
今回は、翻訳キーの対応表から抽出用ファイルを生成する方法を紹介します。例では、i18nextとTypeScriptを使用する環境に、対応表と生成処理を追加します。
文字列を組み立てる場合の問題
以下のようにキーを組み立てると、コードは短くなります。
動的にキーを組み立てる例t(`status.${status}`);しかし、この式だけではstatusにどの値が入るのか分かりません。
抽出ツールが候補を把握できないと、使用中のキーを未使用と判定する可能性があります。型チェックが通る場合でも、抽出結果まで正しいとは限りません。
候補が決まっている場合は、翻訳キーを対応表に列挙します。
翻訳リソースの型を定義する
まず、例で使用する翻訳リソースを定義します。
src/resources.tsexport const resources = {
ja: {
common: {
status: {
pending: '処理待ち',
completed: '完了',
canceled: 'キャンセル',
},
},
},
en: {
common: {
status: {
pending: 'Pending',
completed: 'Completed',
canceled: 'Canceled',
},
},
},
} as const;このリソースをi18next.init({ resources, lng: 'ja', defaultNS: 'common' })などで登録して使用します。Reactから使用する場合は、通常どおりreact-i18nextとの連携も設定します。
さらに、CustomTypeOptionsへリソースの型を登録します。
src/i18next.d.tsimport 'i18next';
import type { resources } from './resources';
declare module 'i18next' {
interface CustomTypeOptions {
defaultNS: 'common';
resources: typeof resources['ja'];
}
}この宣言ファイルはTypeScriptのコンパイル対象に含めます。リソースの型から翻訳キーを検査する仕組みは、i18nextのTypeScriptドキュメントでも説明されています。
実行時に使う対応表を定義する
ステータスと翻訳キーの対応表を作成します。
src/statusKeys.tsimport type { ParseKeys } from 'i18next';
export type Status = 'pending' | 'completed' | 'canceled';
export const statusKeys = {
pending: 'status.pending',
completed: 'status.completed',
canceled: 'status.canceled',
} as const satisfies Record<Status, ParseKeys<'common'>>;Recordでステータスに対応する項目がそろっているか確認し、ParseKeysで翻訳キーを検査します。
例えば、Statusに新しい状態を追加したのに対応表を更新しなかった場合や、翻訳キーをstatus.compeletedと書き間違えた場合に、型エラーとして検出できます。
表示時は、この対応表からキーを選びます。
StatusLabel.tsximport { useTranslation } from 'react-i18next';
import { statusKeys, type Status } from './statusKeys';
export const StatusLabel = ({ status }: { status: Status }) => {
const { t } = useTranslation('common');
return <span>{t(statusKeys[status])}</span>;
};APIから任意の文字列を受け取る場合は、対応している状態か確認してから渡します。型アサーションだけでは、実行時の値を検証できません。
対応表から抽出用ファイルを生成する
t(statusKeys[status])という呼び出しを、抽出ツールが対応表まで追跡するとは限りません。
そこで、対応表の値から固定キーのt()呼び出しを生成します。以下では、Node.js上で生成スクリプトを実行します。
generate-i18n-keys.tsimport { mkdirSync, writeFileSync } from 'node:fs';
import { statusKeys } from './src/statusKeys';
const keys = [...new Set(Object.values(statusKeys))].sort();
const calls = keys.map((key) => `t(${JSON.stringify(`common:${key}`)});`);
mkdirSync('src/generated', { recursive: true });
writeFileSync(
'src/generated/i18n-keys.ts',
[
'// 自動生成ファイル。直接編集しないでください。',
"import { t } from 'i18next';",
'',
...calls,
'',
].join('\n'),
'utf8',
);生成スクリプトをTypeScriptのまま実行するため、ここではtsxを使用します。Node.jsの型定義と一緒に開発用依存関係へ追加します。
npm install --save-dev tsx @types/node
npx tsx generate-i18n-keys.tsコマンドはgenerate-i18n-keys.tsを置いたディレクトリで実行します。
出力されるファイルは以下のようになります。
src/generated/i18n-keys.ts// 自動生成ファイル。直接編集しないでください。
import { t } from 'i18next';
t("common:status.canceled");
t("common:status.completed");
t("common:status.pending");抽出ツールから見ると、名前空間付きの固定キーを渡す通常のt()呼び出しになります。
このファイルは抽出用なので、画面からimportして実行する必要はありません。使用する抽出ツールの入力対象へ、生成先のsrc/generated/i18n-keys.tsを含めます。
型チェックと静的抽出を分けて確認する
| 確認対象 | 確認する内容 |
|---|---|
| 対応表の型チェック | ステータスと翻訳キーが正しいか |
| 生成ファイル | 対応表のキーが固定のt()呼び出しになっているか |
| 抽出結果 | 生成ファイルにあるキーが抽出対象へ含まれているか |
tsxによるスクリプトの実行だけでは、TypeScriptの型チェックは行われません。型チェックはtsc --noEmitなど、アプリケーション側のチェック手順で別途実行します。
また、対応表を更新したあとに生成し忘れると、抽出ツールへ渡す内容が古いままになります。
翻訳の抽出前に生成処理を実行するよう、npm scriptsやCIの手順へ組み込みます。
対応表が増えた場合
この例は1つの対応表を明示的にimportするため、生成処理を短く記述できます。
対応表が増えた場合も、各対応表と名前空間を生成スクリプトに登録し、同じ形式へ変換できます。
ただし、対応表をimportすると、そのモジュールのトップレベルの処理も実行されます。対応表のファイルには定数と型だけを置き、画面の初期化や通信処理を含めないようにします。
大量の対応表を自動で探したい場合は、専用の関数で囲んだ定義をTypeScriptの構文木から収集する方法もあります。その場合は、文字列リテラルや配列など、収集できる書き方を決める必要があります。
翻訳キーを削除するときの注意点
サーバー側で選ばれるキーや通知用のキーは、画面内に固定のt()呼び出しがない場合があります。
未使用候補が出たら、別の生成処理や外部データから参照されていないか確認します。
今回の方法は、候補が有限の動的キーを対象にしています。
実行時の表示と抽出用ファイルを同じ対応表から作ることで、抽出のためだけにキーを手書きで二重管理する必要がなくなります。