Article
🌐 Reactの翻訳辞書をTypeScriptで型安全にする:キー漏れ・誤字を防ぐ
日本語・英語に対応する画面を実装していると、翻訳辞書のキーがずれたままレビューを通ってしまうことがあります。例えば日本語にはsaveがあるのに英語側にはsaveがないと、画面に空の表示やundefinedが出る原因になります。
翻訳ライブラリの導入前でも、TypeScriptのsatisfiesを使えば辞書キーの不足・誤字をコンパイル時に見つけられます。本記事は一般的なasとsatisfiesの比較ではなく、実際の翻訳辞書を安全に増やす設計に絞って紹介します。確認日:2026年10月14日。
何も工夫しない辞書の問題
const ja = { title: "商品一覧", save: "保存", cancel: "キャンセル" };
const en = { title: "Products", save: "Save" };
// en.cancel が未定義でも、この時点では辞書同士の比較はされない
キーを文字列で受け取るt(locale, key)のような関数を安易に作ると、存在しないキーを指定してもTypeScriptが指摘できない設計になりがちです。
型とは、コード上で取り得る値・構造に付ける制約です。翻訳辞書のキーを型にすれば、未登録の文字列をコンパイル時に排除できます。
日本語辞書を正本にしてキー型を作る
まず日本語の辞書からキー一覧を導出します。
// messages.ts
export const ja = {
title: "商品一覧",
save: "保存",
cancel: "キャンセル",
} as const;
export type MessageKey = keyof typeof ja;
export type Locale = "ja" | "en";
as constは定義された値をできるだけ具体的な型として扱い、プロパティをreadonly(型チェック上、書き換え不可)にします。keyof typeof jaは"title" | "save" | "cancel"というユニオン型(取り得る候補を|で列挙した型)になります。
辞書を更新しても、キー型を手で二重管理する必要がありません。
satisfiesで英語辞書の不足を検出する
// messages.ts
export const en = {
title: "Products",
save: "Save",
cancel: "Cancel",
} as const satisfies Record<MessageKey, string>;
Record<MessageKey, string>は「すべてのメッセージキーに文字列の値を持つ」という型です。satisfiesは与えたオブジェクトがその型に適合するか検査します。
例えばcancelを消すと、TypeScriptの型チェックで不足が指摘されます。canselのような誤字を追加しても、直接書いたオブジェクトリテラルなら余分なキーとして検出されます。
satisfiesは値の型推論を活かすので、単なる強制的な型アサーション(as Record<...>)に比べて誤りを握りつぶしにくくなります。TypeScript 4.9の公式リリースノートでも、プロパティ名の誤字検出が代表例として挙げられています。
言語とキーを両方型で制限する
// messages.ts(上の定義に追記)
export const messages = {
ja,
en,
} as const satisfies Record<Locale, Record<MessageKey, string>>;
export function t(locale: Locale, key: MessageKey): string {
return messages[locale][key];
}
呼び出し側は次のようになります。
t("ja", "save"); // "保存"
t("en", "save"); // "Save"
// t("en", "unknown"); // 型エラー: 定義されていないキー
// t("fr", "save"); // 型エラー: 対応していない言語
Reactコンポーネントの例です。
import { t, type Locale } from "./messages";
export function SaveButton({ locale }: { locale: Locale }) {
return <button type="button">{t(locale, "save")}</button>;
}
通常のReactだけでなくNext.jsの画面でも使えます。ただし、ここで扱うのは辞書の型安全性です。Next.js側の多言語ルーティング設定そのものは別途行います。
URLやブラウザから来る言語は検証する
URLのパラメータやリクエストヘッダーに入る言語名は、型の保証がない外部入力です。そのままas Localeで決めつけると、実行時にundefinedが出る可能性があります。
import type { Locale } from "./messages";
export function isLocale(value: string): value is Locale {
return value === "ja" || value === "en";
}
export function resolveLocale(raw: string): Locale {
const language = raw.toLowerCase().split("-")[0];
return isLocale(language) ? language : "ja";
}
resolveLocale("en-US"); // "en"
resolveLocale("fr-FR"); // "ja"(例として既定言語へフォールバック)
value is Localeは型ガードといい、条件を通った値の型を絞り込む仕組みです。この例は最小限の実装なので、実運用で地域別ロケールやHTTPのAccept-Languageヘッダーを扱う場合は仕様に沿った解析が必要です。
導入時にハマりやすい点
- 翻訳キー自体を途中で変更する:日本語のキー名を変えると全言語・画面で型エラーが出ます。これは不具合を早期発見するための意図した挙動です。
- 翻訳済みかどうかは確認できない:英語側に
"TODO"が入っていても型は文字列なので通ります。文言の品質はレビューやテストで確認します。 - 外部JSONは別問題:JSONやAPIから受け取った辞書はコンパイル時に構造が確定しません。実行時バリデーションが必要です。
- 辞書が大規模になる:巨大な1ファイルにまとめず、画面や機能単位で辞書を分割し、それぞれでキー型を導出する方法もあります。
型チェックの実行
TypeScriptを導入済みのプロジェクトなら、次のコマンドで型エラーを確認できます。
npx tsc --noEmit
npxはインストール済みのパッケージなどの実行を補助するnpmのコマンド、tscはTypeScriptコンパイラ、--noEmitはJavaScriptファイルを出力せずに検査する指定です。CIにも組み込むと、翻訳キーの漏れをプルリクエスト時に検出できます。
まとめ
- 日本語辞書の
keyof typeofからキー型を導出する。 - 別言語は
satisfies Record<MessageKey, string>で不足・誤字を検出する。 - 呼び出し関数の言語・キーをユニオン型で制限する。
- URLやAPIから来る値は別途実行時に検証する。