diff --git a/.all-contributorsrc b/.all-contributorsrc
index ca3d78a..63916f4 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -67,6 +67,16 @@
"translation",
"test"
]
+ },
+ {
+ "login": "Arturo-Lopez",
+ "name": "Arturo",
+ "avatar_url": "https://avatars.githubusercontent.com/u/35613775?v=4",
+ "profile": "http://arturolopez.co",
+ "contributions": [
+ "translation",
+ "test"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/README.md b/README.md
index 7eb74f6..5b2e052 100644
--- a/README.md
+++ b/README.md
@@ -69,7 +69,7 @@ This project is licensed under the MIT License - see the [LICENSE](https://githu
## Contributors ✨
-[![All Contributors](https://img.shields.io/badge/all_contributors-6-orange.svg?style=flat-square)](#contributors-)
+[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@@ -86,6 +86,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Luiz Oliveira Montedonio 🌍 ⚠️ |
Izayoi Hibiki 🌍 ⚠️ |
Hrafnkell Baldursson 🌍 ⚠️ |
+ Arturo 🌍 ⚠️ |
diff --git a/examples/with-next-i18next/next-i18next.config.js b/examples/with-next-i18next/next-i18next.config.js
index 9fbe9de..524069d 100644
--- a/examples/with-next-i18next/next-i18next.config.js
+++ b/examples/with-next-i18next/next-i18next.config.js
@@ -5,7 +5,7 @@ const path = require("path");
module.exports = {
i18n: {
defaultLocale: "en",
- locales: ["en", "ja", "fr", "ar", "pt", "zh-CN", "is"],
+ locales: ["en", "ja", "fr", "ar", "pt", "zh-CN", "is", "es"],
},
localePath: path.resolve("./public/locales"),
interpolation: {
diff --git a/examples/with-next-i18next/pages/index.tsx b/examples/with-next-i18next/pages/index.tsx
index a325ead..e95bb90 100644
--- a/examples/with-next-i18next/pages/index.tsx
+++ b/examples/with-next-i18next/pages/index.tsx
@@ -77,6 +77,7 @@ export default function HookForm() {
borderLeftRadius={0}
>
+
diff --git a/examples/with-next-i18next/public/locales/es/common.json b/examples/with-next-i18next/public/locales/es/common.json
new file mode 100644
index 0000000..ee1b30a
--- /dev/null
+++ b/examples/with-next-i18next/public/locales/es/common.json
@@ -0,0 +1,7 @@
+{
+ "User name": "Nombre de usuario",
+ "John Doe": "John Doe",
+ "Email": "Correo",
+ "Favorite number": "NĂşmero favorito",
+ "Submit": "Enviar"
+}
diff --git a/examples/with-next-i18next/public/locales/es/zod.json b/examples/with-next-i18next/public/locales/es/zod.json
new file mode 100644
index 0000000..7299947
--- /dev/null
+++ b/examples/with-next-i18next/public/locales/es/zod.json
@@ -0,0 +1,112 @@
+{
+ "errors": {
+ "invalid_type": "Esperado {{expected}}, recibido {{received}}",
+ "invalid_type_received_undefined": "Requerido",
+ "invalid_literal": "Valor literal inválido, era esperado {{expected}}",
+ "unrecognized_keys": "Llave(s) no reconocida(s) en el objeto: {{- keys}}",
+ "invalid_union": "Entrada inválida",
+ "invalid_union_discriminator": "Valor discriminador inválido. Era esperado {{- options}}",
+ "invalid_enum_value": "Valor de enum inválido. Era esperado {{- options}}, fue recibido '{{received}}'",
+ "invalid_arguments": "Argumentos de función inválido",
+ "invalid_return_type": "Tipo de retorno de función inválido",
+ "invalid_date": "Fecha inválida",
+ "custom": "Entrada inválida",
+ "invalid_intersection_types": "Valores de intersecciĂłn no pudieron ser mezclados",
+ "not_multiple_of": "NĂşmero debe ser multiplo de {{multipleOf}}",
+ "not_finite": "NĂşmero no puede ser infinito",
+ "invalid_string": {
+ "email": "{{validation}} inválido",
+ "url": "{{validation}} inválida",
+ "uuid": "{{validation}} inválido",
+ "cuid": "{{validation}} inválido",
+ "regex": "Inválido",
+ "datetime": "{{validation}} inválido",
+ "startsWith": "Entrada inválida: debe comenzar con \"{{startsWith}}\"",
+ "endsWith": "Entrada inválida: debe finalizar con \"{{endsWith}}\""
+ },
+ "too_small": {
+ "array": {
+ "exact": "El array debe contener exactamente {{minimum}} elemento(s)",
+ "inclusive": "El array debe contener al menos {{minimum}} elemento(s)",
+ "not_inclusive": "El array debe contener más de {{minimum}} elemento(s)"
+ },
+ "string": {
+ "exact": "El texto debe contener exactamente {{minimum}} carácter(es)",
+ "inclusive": "El texto debe contener al menos {{minimum}} carácter(es)",
+ "not_inclusive": "El texto debe contener más de {{minimum}} carácter(es)"
+ },
+ "number": {
+ "exact": "El nĂşmero debe ser exactamente {{minimum}}",
+ "inclusive": "El nĂşmero debe ser mayor o igual a {{minimum}}",
+ "not_inclusive": "El nĂşmero debe ser mayor a {{minimum}}"
+ },
+ "set": {
+ "exact": "Entrada inválida",
+ "inclusive": "Entrada inválida",
+ "not_inclusive": "Entrada inválida"
+ },
+ "date": {
+ "exact": "La fecha debe ser exactamente {{- minimum, datetime}}",
+ "inclusive": "La fecha debe ser mayor o igual a {{- minimum, datetime}}",
+ "not_inclusive": "La fecha debe ser mayor a {{- minimum, datetime}}"
+ }
+ },
+ "too_big": {
+ "array": {
+ "exact": "El array debe contener exactamente {{maximum}} elemento(s)",
+ "inclusive": "El array debe contener como máximo {{maximum}} elemento(s)",
+ "not_inclusive": "El array debe contener menos que {{maximum}} elemento(s)"
+ },
+ "string": {
+ "exact": "El texto debe contener exactamente {{maximum}} carácter(es)",
+ "inclusive": "El texto debe contener como máximo {{maximum}} carácter(es)",
+ "not_inclusive": "El texto debe contener menos que {{maximum}} carácter(es)"
+ },
+ "number": {
+ "exact": "El nĂşmero debe ser exactamente {{maximum}}",
+ "inclusive": "El nĂşmero debe ser menor o igual a {{maximum}}",
+ "not_inclusive": "El nĂşmero debe ser menor a {{maximum}}"
+ },
+ "set": {
+ "exact": "Entrada inválida",
+ "inclusive": "Entrada inválida",
+ "not_inclusive": "Entrada inválida"
+ },
+ "date": {
+ "exact": "La fecha debe ser exactamente {{- maximum, datetime}}",
+ "inclusive": "La fecha debe ser menor o igual a {{- maximum, datetime}}",
+ "not_inclusive": "La fecha debe ser menor a {{- maximum, datetime}}"
+ }
+ }
+ },
+ "validations": {
+ "email": "correo",
+ "url": "url",
+ "uuid": "uuid",
+ "cuid": "cuid",
+ "regex": "expresiĂłn regular",
+ "datetime": "datetime"
+ },
+ "types": {
+ "function": "function",
+ "number": "number",
+ "string": "string",
+ "nan": "nan",
+ "integer": "integer",
+ "float": "float",
+ "boolean": "boolean",
+ "date": "date",
+ "bigint": "bigint",
+ "undefined": "undefined",
+ "symbol": "symbol",
+ "null": "null",
+ "array": "array",
+ "object": "object",
+ "unknown": "unknown",
+ "promise": "promise",
+ "void": "void",
+ "never": "never",
+ "map": "map",
+ "set": "set"
+ }
+}
diff --git a/packages/core/locales/es/zod.json b/packages/core/locales/es/zod.json
new file mode 100644
index 0000000..7299947
--- /dev/null
+++ b/packages/core/locales/es/zod.json
@@ -0,0 +1,112 @@
+{
+ "errors": {
+ "invalid_type": "Esperado {{expected}}, recibido {{received}}",
+ "invalid_type_received_undefined": "Requerido",
+ "invalid_literal": "Valor literal inválido, era esperado {{expected}}",
+ "unrecognized_keys": "Llave(s) no reconocida(s) en el objeto: {{- keys}}",
+ "invalid_union": "Entrada inválida",
+ "invalid_union_discriminator": "Valor discriminador inválido. Era esperado {{- options}}",
+ "invalid_enum_value": "Valor de enum inválido. Era esperado {{- options}}, fue recibido '{{received}}'",
+ "invalid_arguments": "Argumentos de función inválido",
+ "invalid_return_type": "Tipo de retorno de función inválido",
+ "invalid_date": "Fecha inválida",
+ "custom": "Entrada inválida",
+ "invalid_intersection_types": "Valores de intersecciĂłn no pudieron ser mezclados",
+ "not_multiple_of": "NĂşmero debe ser multiplo de {{multipleOf}}",
+ "not_finite": "NĂşmero no puede ser infinito",
+ "invalid_string": {
+ "email": "{{validation}} inválido",
+ "url": "{{validation}} inválida",
+ "uuid": "{{validation}} inválido",
+ "cuid": "{{validation}} inválido",
+ "regex": "Inválido",
+ "datetime": "{{validation}} inválido",
+ "startsWith": "Entrada inválida: debe comenzar con \"{{startsWith}}\"",
+ "endsWith": "Entrada inválida: debe finalizar con \"{{endsWith}}\""
+ },
+ "too_small": {
+ "array": {
+ "exact": "El array debe contener exactamente {{minimum}} elemento(s)",
+ "inclusive": "El array debe contener al menos {{minimum}} elemento(s)",
+ "not_inclusive": "El array debe contener más de {{minimum}} elemento(s)"
+ },
+ "string": {
+ "exact": "El texto debe contener exactamente {{minimum}} carácter(es)",
+ "inclusive": "El texto debe contener al menos {{minimum}} carácter(es)",
+ "not_inclusive": "El texto debe contener más de {{minimum}} carácter(es)"
+ },
+ "number": {
+ "exact": "El nĂşmero debe ser exactamente {{minimum}}",
+ "inclusive": "El nĂşmero debe ser mayor o igual a {{minimum}}",
+ "not_inclusive": "El nĂşmero debe ser mayor a {{minimum}}"
+ },
+ "set": {
+ "exact": "Entrada inválida",
+ "inclusive": "Entrada inválida",
+ "not_inclusive": "Entrada inválida"
+ },
+ "date": {
+ "exact": "La fecha debe ser exactamente {{- minimum, datetime}}",
+ "inclusive": "La fecha debe ser mayor o igual a {{- minimum, datetime}}",
+ "not_inclusive": "La fecha debe ser mayor a {{- minimum, datetime}}"
+ }
+ },
+ "too_big": {
+ "array": {
+ "exact": "El array debe contener exactamente {{maximum}} elemento(s)",
+ "inclusive": "El array debe contener como máximo {{maximum}} elemento(s)",
+ "not_inclusive": "El array debe contener menos que {{maximum}} elemento(s)"
+ },
+ "string": {
+ "exact": "El texto debe contener exactamente {{maximum}} carácter(es)",
+ "inclusive": "El texto debe contener como máximo {{maximum}} carácter(es)",
+ "not_inclusive": "El texto debe contener menos que {{maximum}} carácter(es)"
+ },
+ "number": {
+ "exact": "El nĂşmero debe ser exactamente {{maximum}}",
+ "inclusive": "El nĂşmero debe ser menor o igual a {{maximum}}",
+ "not_inclusive": "El nĂşmero debe ser menor a {{maximum}}"
+ },
+ "set": {
+ "exact": "Entrada inválida",
+ "inclusive": "Entrada inválida",
+ "not_inclusive": "Entrada inválida"
+ },
+ "date": {
+ "exact": "La fecha debe ser exactamente {{- maximum, datetime}}",
+ "inclusive": "La fecha debe ser menor o igual a {{- maximum, datetime}}",
+ "not_inclusive": "La fecha debe ser menor a {{- maximum, datetime}}"
+ }
+ }
+ },
+ "validations": {
+ "email": "correo",
+ "url": "url",
+ "uuid": "uuid",
+ "cuid": "cuid",
+ "regex": "expresiĂłn regular",
+ "datetime": "datetime"
+ },
+ "types": {
+ "function": "function",
+ "number": "number",
+ "string": "string",
+ "nan": "nan",
+ "integer": "integer",
+ "float": "float",
+ "boolean": "boolean",
+ "date": "date",
+ "bigint": "bigint",
+ "undefined": "undefined",
+ "symbol": "symbol",
+ "null": "null",
+ "array": "array",
+ "object": "object",
+ "unknown": "unknown",
+ "promise": "promise",
+ "void": "void",
+ "never": "never",
+ "map": "map",
+ "set": "set"
+ }
+}
diff --git a/packages/core/tests/integrations/es.test.ts b/packages/core/tests/integrations/es.test.ts
new file mode 100644
index 0000000..50d9c86
--- /dev/null
+++ b/packages/core/tests/integrations/es.test.ts
@@ -0,0 +1,199 @@
+import { test, expect, beforeAll } from "vitest";
+import { z } from "zod";
+import { init, getErrorMessage, getErrorMessageFromZodError } from "./helpers";
+
+const LOCALE = "es";
+
+beforeAll(async () => {
+ await init(LOCALE);
+});
+
+test("string parser error messages", () => {
+ const schema = z.string();
+
+ expect(getErrorMessage(schema.safeParse(undefined))).toEqual("Requerido");
+ expect(getErrorMessage(schema.safeParse(1))).toEqual(
+ "Esperado string, recibido number"
+ );
+ expect(getErrorMessage(schema.safeParse(true))).toEqual(
+ "Esperado string, recibido boolean"
+ );
+ expect(getErrorMessage(schema.safeParse(Date))).toEqual(
+ "Esperado string, recibido function"
+ );
+ expect(getErrorMessage(schema.safeParse(new Date()))).toEqual(
+ "Esperado string, recibido date"
+ );
+ expect(getErrorMessage(schema.email().safeParse(""))).toEqual(
+ "correo inválido"
+ );
+ expect(getErrorMessage(schema.url().safeParse(""))).toEqual("url inválida");
+ expect(getErrorMessage(schema.regex(/aaa/).safeParse(""))).toEqual(
+ "Inválido"
+ );
+ expect(getErrorMessage(schema.startsWith("foo").safeParse(""))).toEqual(
+ 'Entrada inválida: debe comenzar con "foo"'
+ );
+ expect(getErrorMessage(schema.endsWith("bar").safeParse(""))).toEqual(
+ 'Entrada inválida: debe finalizar con "bar"'
+ );
+ expect(getErrorMessage(schema.min(5).safeParse("a"))).toEqual(
+ "El texto debe contener al menos 5 carácter(es)"
+ );
+ expect(getErrorMessage(schema.max(5).safeParse("abcdef"))).toEqual(
+ "El texto debe contener como máximo 5 carácter(es)"
+ );
+ expect(getErrorMessage(schema.length(5).safeParse("abcdef"))).toEqual(
+ "El texto debe contener exactamente 5 carácter(es)"
+ );
+ expect(
+ getErrorMessage(schema.datetime().safeParse("2020-01-01T00:00:00+02:00"))
+ ).toEqual("datetime inválido");
+});
+
+test("number parser error messages", () => {
+ const schema = z.number();
+
+ expect(getErrorMessage(schema.safeParse(undefined))).toEqual("Requerido");
+ expect(getErrorMessage(schema.safeParse(""))).toEqual(
+ "Esperado number, recibido string"
+ );
+ expect(getErrorMessage(schema.safeParse(null))).toEqual(
+ "Esperado number, recibido null"
+ );
+ expect(getErrorMessage(schema.safeParse(NaN))).toEqual(
+ "Esperado number, recibido nan"
+ );
+ expect(getErrorMessage(schema.int().safeParse(0.1))).toEqual(
+ "Esperado integer, recibido float"
+ );
+ expect(getErrorMessage(schema.multipleOf(5).safeParse(2))).toEqual(
+ "NĂşmero debe ser multiplo de 5"
+ );
+ expect(getErrorMessage(schema.step(0.1).safeParse(0.0001))).toEqual(
+ "NĂşmero debe ser multiplo de 0.1"
+ );
+ expect(getErrorMessage(schema.lt(5).safeParse(10))).toEqual(
+ "El nĂşmero debe ser menor a 5"
+ );
+ expect(getErrorMessage(schema.lte(5).safeParse(10))).toEqual(
+ "El nĂşmero debe ser menor o igual a 5"
+ );
+ expect(getErrorMessage(schema.gt(5).safeParse(1))).toEqual(
+ "El nĂşmero debe ser mayor a 5"
+ );
+ expect(getErrorMessage(schema.gte(5).safeParse(1))).toEqual(
+ "El nĂşmero debe ser mayor o igual a 5"
+ );
+ expect(getErrorMessage(schema.nonnegative().safeParse(-1))).toEqual(
+ "El nĂşmero debe ser mayor o igual a 0"
+ );
+ expect(getErrorMessage(schema.nonpositive().safeParse(1))).toEqual(
+ "El nĂşmero debe ser menor o igual a 0"
+ );
+ expect(getErrorMessage(schema.negative().safeParse(1))).toEqual(
+ "El nĂşmero debe ser menor a 0"
+ );
+ expect(getErrorMessage(schema.positive().safeParse(0))).toEqual(
+ "El nĂşmero debe ser mayor a 0"
+ );
+ expect(getErrorMessage(schema.finite().safeParse(Infinity))).toEqual(
+ "NĂşmero no puede ser infinito"
+ );
+});
+
+test("date parser error messages", async () => {
+ const testDate = new Date("2022-08-01");
+ const schema = z.date();
+
+ expect(getErrorMessage(schema.safeParse("2022-12-01"))).toEqual(
+ "Esperado date, recibido string"
+ );
+ expect(
+ getErrorMessage(schema.min(testDate).safeParse(new Date("2022-07-29")))
+ ).toEqual(
+ `La fecha debe ser mayor o igual a ${testDate.toLocaleDateString(LOCALE)}`
+ );
+ expect(
+ getErrorMessage(schema.max(testDate).safeParse(new Date("2022-08-02")))
+ ).toEqual(
+ `La fecha debe ser menor o igual a ${testDate.toLocaleDateString(LOCALE)}`
+ );
+ try {
+ await schema.parseAsync(new Date("invalid"));
+ } catch (err) {
+ expect((err as z.ZodError).issues[0].message).toEqual("Fecha inválida");
+ }
+});
+
+test("array parser error messages", () => {
+ const schema = z.string().array();
+
+ expect(getErrorMessage(schema.safeParse(""))).toEqual(
+ "Esperado array, recibido string"
+ );
+ expect(getErrorMessage(schema.min(5).safeParse([""]))).toEqual(
+ "El array debe contener al menos 5 elemento(s)"
+ );
+ expect(getErrorMessage(schema.max(2).safeParse(["", "", ""]))).toEqual(
+ "El array debe contener como máximo 2 elemento(s)"
+ );
+ expect(getErrorMessage(schema.nonempty().safeParse([]))).toEqual(
+ "El array debe contener al menos 1 elemento(s)"
+ );
+ expect(getErrorMessage(schema.length(2).safeParse([]))).toEqual(
+ "El array debe contener exactamente 2 elemento(s)"
+ );
+});
+
+test("function parser error messages", () => {
+ const functionParse = z
+ .function(z.tuple([z.string()]), z.number())
+ .parse((a: any) => a);
+ expect(getErrorMessageFromZodError(() => functionParse(""))).toEqual(
+ "Tipo de retorno de función inválido"
+ );
+ expect(getErrorMessageFromZodError(() => functionParse(1 as any))).toEqual(
+ "Argumentos de función inválido"
+ );
+});
+
+test("other parser error messages", () => {
+ expect(
+ getErrorMessage(
+ z
+ .intersection(
+ z.number(),
+ z.number().transform((x) => x + 1)
+ )
+ .safeParse(1234)
+ )
+ ).toEqual("Valores de intersecciĂłn no pudieron ser mezclados");
+ expect(getErrorMessage(z.literal(12).safeParse(""))).toEqual(
+ "Valor literal inválido, era esperado 12"
+ );
+ expect(getErrorMessage(z.enum(["A", "B", "C"]).safeParse("D"))).toEqual(
+ "Valor de enum inválido. Era esperado 'A' | 'B' | 'C', fue recibido 'D'"
+ );
+ expect(
+ getErrorMessage(
+ z
+ .object({ dog: z.string() })
+ .strict()
+ .safeParse({ dog: "", cat: "", rat: "" })
+ )
+ ).toEqual("Llave(s) no reconocida(s) en el objeto: 'cat', 'rat'");
+ expect(
+ getErrorMessage(
+ z
+ .discriminatedUnion("type", [
+ z.object({ type: z.literal("a"), a: z.string() }),
+ z.object({ type: z.literal("b"), b: z.string() }),
+ ])
+ .safeParse({ type: "c", c: "abc" })
+ )
+ ).toEqual("Valor discriminador inválido. Era esperado 'a' | 'b'");
+ expect(
+ getErrorMessage(z.union([z.string(), z.number()]).safeParse([true]))
+ ).toEqual("Entrada inválida");
+});