Useful for translating zod error messages.
翻译准备中
开发工具TypeScriptGitHub @aiji42自行安装运营者未确认
试用过了吗? 点一下就好,无需登录。
制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗Translate Zod error messages with i18next.
Zod v4 ships built-in locales (z.config(z.locales.ja())). Those are global and use fixed wording. Use this library when you need any of the following:
{{count}}.Using Zod v3? Stay on
zod-i18n-map@2.
npm install zod-i18n-map i18next zod
npx zod-i18n-map add en ja # writes locales/en/zod.json and locales/ja/zod.json
The translation files are not included in the npm package. The CLI copies them into your project, and from then on you edit them like any other resource file.
import i18next from "i18next";
import * as z from "zod";
import { zodI18nMap } from "zod-i18n-map";
import ja from "./locales/ja/zod.json";
await i18next.init({
lng: "ja",
resources: { ja: { zod: ja } },
});
z.config({ customError: zodI18nMap });
z.email().safeParse("foo").error?.issues[0]?.message;
// => "メールアドレスの形式で入力してください。"
If a key is missing, Zod's own message is used instead. That is either English or the built-in Zod locale you configured with z.config(z.locales.xx()).
npx zod-i18n-map list # available locales
npx zod-i18n-map add de fr # -> locales/{locale}/zod.json
npx zod-i18n-map add de --out public/locales/{locale}/zod.json
npx zod-i18n-map add de --force # overwrite an existing file
npx zod-i18n-map check locales/de/zod.json # validate: missing keys, unknown variables, plural forms
npx zod-i18n-map add de --format icu # ICU MessageFormat, for i18next-icu
By default, files come from the Git tag that matches your installed version, so they always match the key layout your version expects. Use --ref <tag|branch|sha> to pick another source.
Use --json to get machine-readable output.
The source files are in locales/. You can also download them directly or load them at runtime from a CDN, e.g. with i18next-http-backend:
https://cdn.jsdelivr.net/gh/aiji42/[email protected]/locales/ja/zod.json
If your app uses i18next-icu, use --format icu. The files are converted to ICU MessageFormat: {name}, {minimum, date}, and plurals merged into {count, plural, ...}. i18next contexts don't work with i18next-icu, so the _with_path variants are not used. {path} is still passed to every message.
Available locales: ar, bg, bn, ca, cs, da, de, el, en, es, et, fa, fi, fr, he, hi, hr, hu, id, is, it, ja, ka, ko, lt, lv, ms, nb, nl, pl, pt-BR, pt-PT, ro, ru, sk, sl, sr, sr-Latn, sv, th, tr, uk, uz, vi, zh-CN, zh-TW. Something wrong or missing? Pull requests are welcome, see CONTRIBUTING.md.
You don't have to set the error map globally. Zod v4 accepts an error map for each parse:
import { makeZodI18nMap } from "zod-i18n-map";
const error = makeZodI18nMap({ t: i18next.getFixedT(request.locale) });
const result = schema.safeParse(body, { error });
t can be any i18next-compatible translate function: i18next.t, getFixedT(...), or react-i18next's useTranslation().t.
Messages are created when a value is parsed. If the user switches languages while errors are on screen (e.g. kept in react-hook-form state), translate the issues again when you render them:
import { translateIssue } from "zod-i18n-map";
{result.error.issues.map((issue) => <p key={issue.path.join(".")}>{translateIssue(issue)}</p>)}
This needs Zod 4.4 or later; on older versions it returns issue.message unchanged. It works because the map stores a small zodI18n field on each issue it handles, with the received type and any i18nKey. So the message can be rebuilt in full, field names included. Issues whose message came from your own error string are returned unchanged. With a custom map, use map.translate(issue).
Use i18nKey (or .key() on a map you created) as the error of any schema or check. All the issue's values (count, minimum, maximum, path, ...) are available for interpolation:
import { i18nKey } from "zod-i18n-map";
// zod.json: { "password": { "too_short": "Use at least {{count}} characters" } }
const password = z.string().min(8, { error: i18nKey("password.too_short") });
// Keys from another namespace use i18next's `ns:key` syntax, extra values go in the 2nd argument
const email = z.email({ error: i18nKey("form:email_invalid", { example: "[email protected]" }) });
For refinements, params.i18n works too:
z.string().refine((v) => v !== "admin", { params: { i18n: "username.reserved" } });
z.string().refine(isAllowed, {
params: { i18n: { key: "username.not_allowed", values: { name: "admin" } } },
});
If the custom key is missing, the default translation for that issue is used.
handlePath)If the issue has a path, the with_path context variant of a message is used when it exists. {{path}} holds the translated field name:
// locales/en/zod.json
{
"errors": {
"invalid_type_received_undefined": "Required",
"invalid_type_received_undefined_with_path": "{{- path}} is required"
},
"email": "Email address"
}
z.object({ email: z.email() }).parse({}); // => "Email address is required"
user.email). If that key is missing, they are looked up again without array indices (items.0.name → items.name). If both are missing, the raw path is used.handlePath: { ns: "form", keyPrefix: "fields" } to keep labels elsewhere. With that setting, form:fields.user.email is looked up.context: "..." to change the context suffix, or handlePath: false to turn this off.Messages that receive {{count}} use i18next plurals, so each language can use the right forms:
{ "inclusive_one": "At least {{count}} character", "inclusive_other": "At least {{count}} characters" }
Messages are looked up in the zod namespace by default. Use a different namespace for each audience, for example end-user forms and developer-facing APIs:
z.config({ customError: makeZodI18nMap({ ns: "formErrors" }) });
// several namespaces are searched in order
makeZodI18nMap({ ns: ["formErrors", "zod"] });
makeZodI18nMap(options?: {
t?: TranslateFunction; // default: i18next.t
ns?: string | readonly string[]; // default: "zod"
handlePath?: false | {
context?: string; // default: "with_path"
ns?: string | readonly string[]; // default: same as ns
keyPrefix?: string;
};
}): ZodI18nMap
zodI18nMap: ZodI18nMap // makeZodI18nMap() with defaults
zodI18nMap.key(key, values?) // error map that tries `key` first
i18nKey(key, values?) // = zodI18nMap.key
zodI18nMap.translate(issue) // re-translate an existing issue in the current language
translateIssue(issue) // = zodI18nMap.translate
ZodI18nMap is a regular Zod v4 error map. You can use it in z.config({ customError }), in a parse's { error }, or in a schema's { error }.
| Zod issue | Key | Variables |
|---|---|---|
invalid_type | errors.invalid_typeerrors.invalid_type_received_undefinederrors.invalid_type_received_null | expected, received (translated via types.*) |
invalid_value | errors.invalid_valueerrors.invalid_value_single (one allowed value, e.g. z.literal) | options / expected |
invalid_format | errors.invalid_format.<format>, otherwise errors.invalid_format.default | format (translated via formats.*), pattern, prefix, suffix, includes |
too_small / too_big | errors.too_small.<origin>.<exact|inclusive|not_inclusive>. Origins are string, number (incl. int, bigint), array, set, map, file and date. | minimum / maximum, count |
not_multiple_of | errors.not_multiple_of | divisor |
unrecognized_keys | errors.unrecognized_keys | keys, count |
invalid_key / invalid_element | errors.invalid_key / errors.invalid_element | origin |
invalid_union | errors.invalid_unionerrors.invalid_union_discriminator | options, discriminator |
custom | errors.custom, or the key given via params.i18n | your values |
Every message also receives path when handlePath is on. See locales/en/zod.json for the full reference file.
[email protected], import from zod/v4.z.setErrorMap(zodI18nMap) with z.config({ customError: zodI18nMap }).import translation from "zod-i18n-map/locales/ja/zod.json" with a file you get from npx zod-i18n-map add ja.invalid_string.* → invalid_format.*validations.* → formats.*invalid_enum_value / invalid_literal → invalid_value / invalid_value_single{{multipleOf}} → {{divisor}}invalid_date, invalid_arguments, invalid_return_type, invalid_intersection_types, not_finite and the too_small.set / too_big.set "Invalid input" placeholders were removed or replaced, because Zod v4 no longer emits them.npx zod-i18n-map check lists the keys you are missing.hr-HR → hr, uk-UA → uk, pt → pt-BR (it was always Brazilian Portuguese; European Portuguese is now pt-PT). (You choose the file name, so any i18next language code works.)The translation files belong to you, so a language that isn't listed is one command away:
npx zod-i18n-map scaffold sw # copies the English file and prints translation rules for Swahili
# ...let your coding agent (or a human) translate locales/sw/zod.json...
npx zod-i18n-map check locales/sw/zod.json # verifies keys, {{variables}} and the plural forms Swahili needs
The rules that scaffold prints are written for agents. They cover which variables must stay intact, which plural suffixes the language requires (from Intl.PluralRules) and how to keep templates grammatical. check turns those rules into a pass/fail result the agent can loop on. Run check after editing any translation file, too.
llms.txt: the npm package includes a short, complete usage guide at node_modules/zod-i18n-map/llms.txt.npx zod-i18n-map skill installs SKILL.md at .claude/skills/zod-i18n-map/. Use --out for other agents' skill directories. With the skill installed, "localize our form validation errors" is something your agent knows how to do with this library.--json, and signals failure through its exit code.See CONTRIBUTING.md. Translation fixes and new locales are especially welcome.