Zodのバリデーションエラーメッセージを日本語化・カスタム表示する方法
TypeScript Zod Validation Frontend
結論
一括日本語化は z.setErrorMap() を設定し、個別は z.string({ required_error: "必須です" }) を指定します。
// Zod公式仕様:グローバル ErrorMap によるエラーメッセージ共通化
import { z } from "zod";
const customErrorMap: z.ZodErrorMap = (issue, ctx) => {
if (issue.code === z.ZodIssueCode.invalid_type) {
if (issue.received === "undefined") {
return { message: "必須項目です" };
}
return { message: "入力形式が正しくありません" };
}
if (issue.code === z.ZodIssueCode.too_small) {
return { message: `${issue.minimum}文字以上で入力してください` };
}
return { message: ctx.defaultError };
};
z.setErrorMap(customErrorMap);
Zod公式のメッセージ評価優先順位
Zod(v3/v4)におけるエラーメッセージ決定の優先順位は以下の通り厳格に規定されています。
- 最優先: 各スキーマメソッドの個別メッセージ (
z.string().min(5, "5文字以上")) - 第2優先: 型宣言時のオプション (
z.string({ required_error: "必須入力" })) - 第3優先: グローバル設定 (
z.setErrorMap(customMap)) - 最低: Zod デフォルト英語メッセージ
実際に起こるトラブル:setErrorMap を書いたのに英語のままになる罠
GitHub Issues(colinhacks/zod #2653 等)で最も頻出するトラブルと解決策です。
原因1: インポートのタイミング依存(インスタンス化の遅延評価漏れ)
エントリーポイントで z.setErrorMap を呼び出す前に、別ファイルから z.object({...}) スキーマがモジュール読み込み(import)されていると、そのスキーマには旧デフォルトの ErrorMap がバインドされたままになります。
// ❌ 反映されないダメな順序
import { userSchema } from "./schema"; // この時点で Zod インスタンスが初期化完了
import { z } from "zod";
z.setErrorMap(myCustomMap); // 遅すぎる
原因2: zod-i18n-map 使用時の i18next 初期化漏れ
コミュニティ標準の zod-i18n-map を使用する場合、i18next.init() 呼び出しより前に z.setErrorMap(zodI18nMap) を呼ぶとメッセージが undefined またはフォールバック表示になります。
トラブル回避・正しい設定手順
- アプリケーションのエントリーポイントの最上部(他のスキーマファイルの import より先)で
z.setErrorMapを実行する zod-i18n-mapを使う場合はi18next.init()の完了直後にz.setErrorMapをセットする- 個別フィールドの文言を上書きしたい場合のみ、スキーマ定義側に
{ message: "..." }を付与する
// ⭕ 正しいインポート・初期化順序
import i18next from "i18next";
import { z } from "zod";
import { zodI18nMap } from "zod-i18n-map";
// 1. 先に i18next を初期化
i18next.init({
lng: "ja",
resources: { ja: { zod: require("zod-i18n-map/locales/ja/zod.json") } },
});
// 2. その後即座に setErrorMap を登録
z.setErrorMap(zodI18nMap);
// 3. 最後にスキーマを読み込む
import { userSchema } from "./schema";