0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Reactの翻訳辞書をTypeScriptで型安全にする:キー漏れ・誤字を防ぐ

0
Last updated at Posted at 2026-10-14

日本語・英語に対応する画面を実装していると、翻訳辞書のキーがずれたままレビューを通ってしまうことがあります。例えば日本語には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ヘッダーを扱う場合は仕様に沿った解析が必要です。

導入時にハマりやすい点

  1. 翻訳キー自体を途中で変更する:日本語のキー名を変えると全言語・画面で型エラーが出ます。これは不具合を早期発見するための意図した挙動です。
  2. 翻訳済みかどうかは確認できない:英語側に"TODO"が入っていても型は文字列なので通ります。文言の品質はレビューやテストで確認します。
  3. 外部JSONは別問題:JSONやAPIから受け取った辞書はコンパイル時に構造が確定しません。実行時バリデーションが必要です。
  4. 辞書が大規模になる:巨大な1ファイルにまとめず、画面や機能単位で辞書を分割し、それぞれでキー型を導出する方法もあります。

型チェックの実行

TypeScriptを導入済みのプロジェクトなら、次のコマンドで型エラーを確認できます。

npx tsc --noEmit

npxはインストール済みのパッケージなどの実行を補助するnpmのコマンド、tscはTypeScriptコンパイラ、--noEmitはJavaScriptファイルを出力せずに検査する指定です。CIにも組み込むと、翻訳キーの漏れをプルリクエスト時に検出できます。

まとめ

  • 日本語辞書のkeyof typeofからキー型を導出する。
  • 別言語はsatisfies Record<MessageKey, string>で不足・誤字を検出する。
  • 呼び出し関数の言語・キーをユニオン型で制限する。
  • URLやAPIから来る値は別途実行時に検証する。

公式参考資料(2026年10月14日確認)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?