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.