i18n: Internationalization
Valdix is built with first-class multi-language error messages. Out of the box you get 17 locales (en, id, jp, zh, zh-TW, ko, fr, pt, nl, es, de, ar, ru, it, vi, hi, th). Adding your own locale is straightforward.
Switching the global language
import v from "@denisetiya/valdix";
v.useLang("id"); // any subsequent parse() will use Indonesian
The default is en. The setting is process-global. Pass per-parse options for fine-grained control.
Per-parse language
schema.parse(input, { lang: "jp" });
schema.safeParse(input, { lang: "en" });
{ lang } overrides the global setting for that single call.
Issue structure
Every error from safeParse is a ValdixIssue with these fields:
| Field | Description |
|---|---|
code |
Machine-readable error code (required, too_small, invalid_type, etc.) |
path |
Array of path segments leading to the failing field |
field |
Human-readable field name derived from the path |
description |
Set via .describe(text). Falls back to the field label. |
message |
The localized, casual text. Ready to display to the user |
expected, received |
Type-mismatch context |
minimum, maximum, inclusive, exact |
Bounds context |
validation |
String-format context ("email", "url", etc.) |
keys |
Unknown keys (for .strict()) |
options |
Allowed values (for enum) |
literal |
Required literal (for v.literal) |
discriminator, allowedDiscriminators |
Discriminated union context |
const r = v.object({
userName: v.string().min(3).describe("Nama user"),
}).safeParse({ userName: "ab" });
r.errors[0]
// → {
// code: "too_small",
// path: ["userName"],
// field: "userName",
// description: "Nama user",
// message: "Minimal 3 karakter",
// minimum: 3,
// inclusive: true,
// kind: "string"
// }
Custom message per rule
v.string().min(3, "Minimal 3 karakter ya")
v.string().email("Email-nya gak valid")
v.string().regex(/^\d+$/, "Harus angka")
Use {{field}} to interpolate the humanized field name:
v.object({
userName: v.string().min(3, "{{field}} minimal 3 karakter"),
}).safeParse({ userName: "ab" }, { lang: "id" });
// → message: "user name minimal 3 karakter"
Available interpolation variables: {{field}}, {{path}}, {{expected}}, {{received}}, {{minimum}}, {{maximum}}, {{validation}}, {{keys}}, {{options}}, {{literal}}, {{discriminator}}.
Global error map
For full control, install a function that runs on every issue:
import v, { setErrorMap } from "@denisetiya/valdix";
setErrorMap((issue, ctx) => {
return `[${issue.code}] ${issue.field}: ${ctx.defaultError}`;
});
ctx.defaultError is the resolved locale message. ctx.lang is the active language.
Adding a custom locale
Every locale is a LocaleCatalog: an object mapping error codes to either a string template or a function (issue) => string.
import v, { registerLocale } from "@denisetiya/valdix";
registerLocale("fr", {
required: () => "Ce champ est obligatoire",
invalid_type: (i) => `Type attendu : ${i.expected ?? "?"}, reçu : ${i.received ?? "?"}`,
too_small: (i) => {
if (i.kind === "string") return `Au moins ${i.minimum} caractères requis`;
if (i.kind === "number") return `Au moins ${i.minimum} requis`;
if (i.kind === "array") return `Au moins ${i.minimum} élément(s) requis`;
return "Valeur trop petite";
},
too_big: (i) => {
if (i.kind === "string") return `Au plus ${i.maximum} caractères autorisés`;
return "Valeur trop grande";
},
invalid_string: (i) => {
if (i.validation === "email") return "Adresse email invalide";
if (i.validation === "url") return "URL invalide";
return "Format invalide";
},
// ... other codes
});
v.useLang("fr");
Locale catalog reference
| Code | Issue fields | When it’s used |
|---|---|---|
required |
none | Missing required field |
invalid_type |
expected, received |
Type mismatch |
invalid_literal |
literal |
v.literal(x) mismatch |
invalid_enum_value |
options |
v.enum / v.nativeEnum |
too_small |
kind, minimum, inclusive, exact |
Below min bound |
too_big |
kind, maximum, inclusive, exact |
Above max bound |
invalid_string |
validation |
String format failure |
invalid_number |
validation |
Number constraint failure |
invalid_date |
none | Date parse failure |
invalid_array |
none | Not an array |
invalid_union |
none | Union exhausted |
invalid_intersection |
none | Intersection failed |
invalid_discriminator |
discriminator, allowedDiscriminators |
Discriminated union miss |
unknown_keys |
keys |
Strict object with unknown key |
invalid_tuple_length |
minimum, maximum |
Tuple length mismatch |
custom |
none | .superRefine / .refine |
{{field}} and other variables are interpolated automatically. Use them inside strings or return them from functions.