diff --git a/examples/with-next-i18next/next-i18next.config.js b/examples/with-next-i18next/next-i18next.config.js index bb771a2..b618a78 100644 --- a/examples/with-next-i18next/next-i18next.config.js +++ b/examples/with-next-i18next/next-i18next.config.js @@ -7,6 +7,7 @@ module.exports = { defaultLocale: "en", locales: [ "en", + "he", "ja", "fr", "ar", diff --git a/examples/with-next-i18next/pages/index.tsx b/examples/with-next-i18next/pages/index.tsx index 6fcdd31..2df0f3e 100644 --- a/examples/with-next-i18next/pages/index.tsx +++ b/examples/with-next-i18next/pages/index.tsx @@ -78,6 +78,7 @@ export default function HookForm() { + diff --git a/examples/with-next-i18next/public/locales/he/common.json b/examples/with-next-i18next/public/locales/he/common.json new file mode 100644 index 0000000..fff6f98 --- /dev/null +++ b/examples/with-next-i18next/public/locales/he/common.json @@ -0,0 +1,7 @@ +{ + "username": "שם משתמש", + "username_placeholder": "ישראל ישראלי", + "email": "דואר אלקטרוני", + "favoriteNumber": "מספר אהוב", + "submit": "שלח" +} diff --git a/examples/with-next-i18next/public/locales/he/zod.json b/examples/with-next-i18next/public/locales/he/zod.json new file mode 100644 index 0000000..9df923c --- /dev/null +++ b/examples/with-next-i18next/public/locales/he/zod.json @@ -0,0 +1,112 @@ +{ + "errors": { + "invalid_type": "צפוי {{expected}}, קיבלנו {{received}}", + "invalid_type_received_undefined": "נדרש", + "invalid_literal": "ערך לא תקין, צפוי {{expected}}", + "unrecognized_keys": "מפתחות לא מזוהים באובייקט: {{- keys}}", + "invalid_union": "קלט לא תקין", + "invalid_union_discriminator": "ערך מזהה לא תקין. צפוי {{- options}}", + "invalid_enum_value": "ערך לא תקין לסוג enum. ציפיתי ל-{{- options}}, קיבלתי '{{received}}'", + "invalid_arguments": "פרמטרים לפונקציה לא תקינים", + "invalid_return_type": "סוג ההחזרה של הפונקציה לא תקין", + "invalid_date": "תאריך לא תקין", + "custom": "קלט לא תקין", + "invalid_intersection_types": "לא ניתן למזג את הסוגים", + "not_multiple_of": "המספר חייב להיות מכפלה של {{multipleOf}}", + "not_finite": "המספר חייב להיות סופי", + "invalid_string": { + "email": "כתובת דוא\"ל לא תקינה", + "url": "כתובת אינטרנט לא תקינה", + "uuid": "מזהה UUID לא תקין", + "cuid": "מזהה CUID לא תקין", + "regex": "קלט לא תקין", + "datetime": "תאריך ושעה לא תקינים", + "startsWith": "קלט לא תקין: חייב להתחיל ב-\"{{startsWith}}\"", + "endsWith": "קלט לא תקין: חייב להסתיים ב-\"{{endsWith}}\"" + }, + "too_small": { + "array": { + "exact": "המערך חייב להכיל בדיוק {{minimum}} איברים", + "inclusive": "המערך חייב להכיל לפחות {{minimum}} איברים", + "not_inclusive": "המערך חייב להכיל יותר מ-{{minimum}} איברים" + }, + "string": { + "exact": "המחרוזת חייבת להכיל בדיוק {{minimum}} תווים", + "inclusive": "המחרוזת חייבת להכיל לפחות {{minimum}} תווים", + "not_inclusive": "המחרוזת חייבת להכיל מעל {{minimum}} תווים" + }, + "number": { + "exact": "המספר חייב להיות בדיוק {{minimum}}", + "inclusive": "המספר חייב להיות גדול או שווה ל-{{minimum}}", + "not_inclusive": "המספר חייב להיות גדול מ-{{minimum}}" + }, + "set": { + "exact": "קלט לא תקין", + "inclusive": "קלט לא תקין", + "not_inclusive": "קלט לא תקין" + }, + "date": { + "exact": "התאריך חייב להיות בדיוק {{- minimum, datetime}}", + "inclusive": "התאריך חייב להיות גדול או שווה ל-{{- minimum, datetime}}", + "not_inclusive": "התאריך חייב להיות גדול מ-{{- minimum, datetime}}" + } + }, + "too_big": { + "array": { + "exact": "המערך חייב להכיל בדיוק {{maximum}} איברים", + "inclusive": "המערך חייב להכיל לכל היותר {{maximum}} איברים", + "not_inclusive": "המערך חייב להכיל פחות מ-{{maximum}} איברים" + }, + "string": { + "exact": "המחרוזת חייבת להכיל בדיוק {{maximum}} תווים", + "inclusive": "המחרוזת חייבת להכיל לכל היותר {{maximum}} תווים", + "not_inclusive": "המחרוזת חייבת להכיל פחות מ-{{maximum}} תווים" + }, + "number": { + "exact": "המספר חייב להיות בדיוק {{maximum}}", + "inclusive": "המספר חייב להיות קטן או שווה ל-{{maximum}}", + "not_inclusive": "המספר חייב להיות קטן מ-{{maximum}}" + }, + "set": { + "exact": "קלט לא תקין", + "inclusive": "קלט לא תקין", + "not_inclusive": "קלט לא תקין" + }, + "date": { + "exact": "התאריך חייב להיות בדיוק {{- maximum, datetime}}", + "inclusive": "התאריך חייב להיות קטן או שווה ל-{{- maximum, datetime}}", + "not_inclusive": "התאריך חייב להיות קטן מ-{{- maximum, datetime}}" + } + } + }, + "validations": { + "email": "כתובת דוא\"ל", + "url": "כתובת אינטרנט", + "uuid": "מזהה UUID", + "cuid": "מזהה CUID", + "regex": "קלט רגולרי", + "datetime": "תאריך ושעה" + }, + "types": { + "function": "פונקציה", + "number": "מספר", + "string": "מחרוזת", + "nan": "NaN", + "integer": "מספר שלם", + "float": "מספר עשרוני", + "boolean": "בוליאני", + "date": "תאריך", + "bigint": "מספר גדול", + "undefined": "לא מוגדר", + "symbol": "סמל", + "null": "null", + "array": "מערך", + "object": "אובייקט", + "unknown": "לא ידוע", + "promise": "Promise", + "void": "void", + "never": "לעולם לא", + "map": "מפה", + "set": "סט" + } +} diff --git a/packages/core/locales/he/zod.json b/packages/core/locales/he/zod.json new file mode 100644 index 0000000..9df923c --- /dev/null +++ b/packages/core/locales/he/zod.json @@ -0,0 +1,112 @@ +{ + "errors": { + "invalid_type": "צפוי {{expected}}, קיבלנו {{received}}", + "invalid_type_received_undefined": "נדרש", + "invalid_literal": "ערך לא תקין, צפוי {{expected}}", + "unrecognized_keys": "מפתחות לא מזוהים באובייקט: {{- keys}}", + "invalid_union": "קלט לא תקין", + "invalid_union_discriminator": "ערך מזהה לא תקין. צפוי {{- options}}", + "invalid_enum_value": "ערך לא תקין לסוג enum. ציפיתי ל-{{- options}}, קיבלתי '{{received}}'", + "invalid_arguments": "פרמטרים לפונקציה לא תקינים", + "invalid_return_type": "סוג ההחזרה של הפונקציה לא תקין", + "invalid_date": "תאריך לא תקין", + "custom": "קלט לא תקין", + "invalid_intersection_types": "לא ניתן למזג את הסוגים", + "not_multiple_of": "המספר חייב להיות מכפלה של {{multipleOf}}", + "not_finite": "המספר חייב להיות סופי", + "invalid_string": { + "email": "כתובת דוא\"ל לא תקינה", + "url": "כתובת אינטרנט לא תקינה", + "uuid": "מזהה UUID לא תקין", + "cuid": "מזהה CUID לא תקין", + "regex": "קלט לא תקין", + "datetime": "תאריך ושעה לא תקינים", + "startsWith": "קלט לא תקין: חייב להתחיל ב-\"{{startsWith}}\"", + "endsWith": "קלט לא תקין: חייב להסתיים ב-\"{{endsWith}}\"" + }, + "too_small": { + "array": { + "exact": "המערך חייב להכיל בדיוק {{minimum}} איברים", + "inclusive": "המערך חייב להכיל לפחות {{minimum}} איברים", + "not_inclusive": "המערך חייב להכיל יותר מ-{{minimum}} איברים" + }, + "string": { + "exact": "המחרוזת חייבת להכיל בדיוק {{minimum}} תווים", + "inclusive": "המחרוזת חייבת להכיל לפחות {{minimum}} תווים", + "not_inclusive": "המחרוזת חייבת להכיל מעל {{minimum}} תווים" + }, + "number": { + "exact": "המספר חייב להיות בדיוק {{minimum}}", + "inclusive": "המספר חייב להיות גדול או שווה ל-{{minimum}}", + "not_inclusive": "המספר חייב להיות גדול מ-{{minimum}}" + }, + "set": { + "exact": "קלט לא תקין", + "inclusive": "קלט לא תקין", + "not_inclusive": "קלט לא תקין" + }, + "date": { + "exact": "התאריך חייב להיות בדיוק {{- minimum, datetime}}", + "inclusive": "התאריך חייב להיות גדול או שווה ל-{{- minimum, datetime}}", + "not_inclusive": "התאריך חייב להיות גדול מ-{{- minimum, datetime}}" + } + }, + "too_big": { + "array": { + "exact": "המערך חייב להכיל בדיוק {{maximum}} איברים", + "inclusive": "המערך חייב להכיל לכל היותר {{maximum}} איברים", + "not_inclusive": "המערך חייב להכיל פחות מ-{{maximum}} איברים" + }, + "string": { + "exact": "המחרוזת חייבת להכיל בדיוק {{maximum}} תווים", + "inclusive": "המחרוזת חייבת להכיל לכל היותר {{maximum}} תווים", + "not_inclusive": "המחרוזת חייבת להכיל פחות מ-{{maximum}} תווים" + }, + "number": { + "exact": "המספר חייב להיות בדיוק {{maximum}}", + "inclusive": "המספר חייב להיות קטן או שווה ל-{{maximum}}", + "not_inclusive": "המספר חייב להיות קטן מ-{{maximum}}" + }, + "set": { + "exact": "קלט לא תקין", + "inclusive": "קלט לא תקין", + "not_inclusive": "קלט לא תקין" + }, + "date": { + "exact": "התאריך חייב להיות בדיוק {{- maximum, datetime}}", + "inclusive": "התאריך חייב להיות קטן או שווה ל-{{- maximum, datetime}}", + "not_inclusive": "התאריך חייב להיות קטן מ-{{- maximum, datetime}}" + } + } + }, + "validations": { + "email": "כתובת דוא\"ל", + "url": "כתובת אינטרנט", + "uuid": "מזהה UUID", + "cuid": "מזהה CUID", + "regex": "קלט רגולרי", + "datetime": "תאריך ושעה" + }, + "types": { + "function": "פונקציה", + "number": "מספר", + "string": "מחרוזת", + "nan": "NaN", + "integer": "מספר שלם", + "float": "מספר עשרוני", + "boolean": "בוליאני", + "date": "תאריך", + "bigint": "מספר גדול", + "undefined": "לא מוגדר", + "symbol": "סמל", + "null": "null", + "array": "מערך", + "object": "אובייקט", + "unknown": "לא ידוע", + "promise": "Promise", + "void": "void", + "never": "לעולם לא", + "map": "מפה", + "set": "סט" + } +} diff --git a/packages/core/tests/integrations/he.test.ts b/packages/core/tests/integrations/he.test.ts new file mode 100644 index 0000000..d66ebbd --- /dev/null +++ b/packages/core/tests/integrations/he.test.ts @@ -0,0 +1,211 @@ +import { test, expect, beforeAll } from "vitest"; +import { z } from "zod"; +import { init, getErrorMessage, getErrorMessageFromZodError } from "./helpers"; + +const LOCALE = "he"; + +beforeAll(async () => { + await init(LOCALE); +}); + +test("string parser error messages", () => { + const schema = z.string(); + + expect(getErrorMessage(schema.safeParse(undefined))).toEqual("נדרש"); + expect(getErrorMessage(schema.safeParse(1))).toEqual( + "צפוי מחרוזת, קיבלנו מספר" + ); + expect(getErrorMessage(schema.safeParse(true))).toEqual( + "צפוי מחרוזת, קיבלנו בוליאני" + ); + expect(getErrorMessage(schema.safeParse(Date))).toEqual( + "צפוי מחרוזת, קיבלנו פונקציה" + ); + expect(getErrorMessage(schema.safeParse(new Date()))).toEqual( + "צפוי מחרוזת, קיבלנו תאריך" + ); + expect(getErrorMessage(schema.email().safeParse(""))).toEqual( + 'כתובת דוא"ל לא תקינה' + ); + expect(getErrorMessage(schema.url().safeParse(""))).toEqual( + "כתובת אינטרנט לא תקינה" + ); + expect(getErrorMessage(schema.regex(/aaa/).safeParse(""))).toEqual( + "קלט לא תקין" + ); + expect(getErrorMessage(schema.startsWith("foo").safeParse(""))).toEqual( + 'קלט לא תקין: חייב להתחיל ב-"foo"' + ); + expect(getErrorMessage(schema.endsWith("bar").safeParse(""))).toEqual( + 'קלט לא תקין: חייב להסתיים ב-"bar"' + ); + expect(getErrorMessage(schema.min(5).safeParse("a"))).toEqual( + "המחרוזת חייבת להכיל לפחות 5 תווים" + ); + expect(getErrorMessage(schema.max(5).safeParse("abcdef"))).toEqual( + "המחרוזת חייבת להכיל לכל היותר 5 תווים" + ); + expect(getErrorMessage(schema.length(5).safeParse("abcdef"))).toEqual( + "המחרוזת חייבת להכיל בדיוק 5 תווים" + ); + expect( + getErrorMessage(schema.datetime().safeParse("2020-01-01T00:00:00+02:00")) + ).toEqual("תאריך ושעה לא תקינים"); +}); + +test("number parser error messages", () => { + const schema = z.number(); + + expect(getErrorMessage(schema.safeParse(undefined))).toEqual("נדרש"); + expect(getErrorMessage(schema.safeParse(""))).toEqual( + "צפוי מספר, קיבלנו מחרוזת" + ); + expect(getErrorMessage(schema.safeParse(null))).toEqual( + "צפוי מספר, קיבלנו null" + ); + expect(getErrorMessage(schema.safeParse(NaN))).toEqual( + "צפוי מספר, קיבלנו NaN" + ); + expect(getErrorMessage(schema.int().safeParse(0.1))).toEqual( + "צפוי מספר שלם, קיבלנו מספר עשרוני" + ); + expect(getErrorMessage(schema.multipleOf(5).safeParse(2))).toEqual( + "המספר חייב להיות מכפלה של 5" + ); + expect(getErrorMessage(schema.step(0.1).safeParse(0.0001))).toEqual( + "המספר חייב להיות מכפלה של 0.1" + ); + expect(getErrorMessage(schema.lt(5).safeParse(10))).toEqual( + "המספר חייב להיות קטן מ-5" + ); + expect(getErrorMessage(schema.lte(5).safeParse(10))).toEqual( + "המספר חייב להיות קטן או שווה ל-5" + ); + expect(getErrorMessage(schema.gt(5).safeParse(1))).toEqual( + "המספר חייב להיות גדול מ-5" + ); + expect(getErrorMessage(schema.gte(5).safeParse(1))).toEqual( + "המספר חייב להיות גדול או שווה ל-5" + ); + expect(getErrorMessage(schema.nonnegative().safeParse(-1))).toEqual( + "המספר חייב להיות גדול או שווה ל-0" + ); + expect(getErrorMessage(schema.nonpositive().safeParse(1))).toEqual( + "המספר חייב להיות קטן או שווה ל-0" + ); + expect(getErrorMessage(schema.negative().safeParse(1))).toEqual( + "המספר חייב להיות קטן מ-0" + ); + expect(getErrorMessage(schema.positive().safeParse(0))).toEqual( + "המספר חייב להיות גדול מ-0" + ); + expect(getErrorMessage(schema.finite().safeParse(Infinity))).toEqual( + "המספר חייב להיות סופי" + ); +}); + +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( + "צפוי תאריך, קיבלנו מחרוזת" + ); + expect( + getErrorMessage(schema.min(testDate).safeParse(new Date("2022-07-29"))) + ).toEqual( + `התאריך חייב להיות גדול או שווה ל-${testDate.toLocaleDateString(LOCALE)}` + ); + expect( + getErrorMessage(schema.max(testDate).safeParse(new Date("2022-08-02"))) + ).toEqual( + `התאריך חייב להיות קטן או שווה ל-${testDate.toLocaleDateString(LOCALE)}` + ); + try { + await schema.parseAsync(new Date("invalid")); + } catch (err) { + expect((err as z.ZodError).issues[0].message).toEqual("תאריך לא תקין"); + } +}); + +test("array parser error messages", () => { + const schema = z.string().array(); + + expect(getErrorMessage(schema.safeParse(""))).toEqual( + "צפוי מערך, קיבלנו מחרוזת" + ); + expect(getErrorMessage(schema.min(5).safeParse([""]))).toEqual( + "המערך חייב להכיל לפחות 5 איברים" + ); + expect(getErrorMessage(schema.max(2).safeParse(["", "", ""]))).toEqual( + "המערך חייב להכיל לכל היותר 2 איברים" + ); + expect(getErrorMessage(schema.nonempty().safeParse([]))).toEqual( + "המערך חייב להכיל לפחות 1 איברים" + ); + expect(getErrorMessage(schema.length(2).safeParse([]))).toEqual( + "המערך חייב להכיל בדיוק 2 איברים" + ); +}); + +test("function parser error messages", () => { + const functionParse = z + .function(z.tuple([z.string()]), z.number()) + .parse((a: any) => a); + expect(getErrorMessageFromZodError(() => functionParse(""))).toEqual( + "סוג ההחזרה של הפונקציה לא תקין" + ); + expect(getErrorMessageFromZodError(() => functionParse(1 as any))).toEqual( + "פרמטרים לפונקציה לא תקינים" + ); +}); + +test("other parser error messages", () => { + expect( + getErrorMessage( + z + .intersection( + z.number(), + z.number().transform((x) => x + 1) + ) + .safeParse(1234) + ) + ).toEqual("לא ניתן למזג את הסוגים"); + expect(getErrorMessage(z.literal(12).safeParse(""))).toEqual( + "ערך לא תקין, צפוי 12" + ); + expect(getErrorMessage(z.enum(["A", "B", "C"]).safeParse("D"))).toEqual( + "ערך לא תקין לסוג enum. ציפיתי ל-'A' | 'B' | 'C', קיבלתי 'D'" + ); + expect( + getErrorMessage( + z + .object({ dog: z.string() }) + .strict() + .safeParse({ dog: "", cat: "", rat: "" }) + ) + ).toEqual("מפתחות לא מזוהים באובייקט: '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("ערך מזהה לא תקין. צפוי 'a' | 'b'"); + expect( + getErrorMessage(z.union([z.string(), z.number()]).safeParse([true])) + ).toEqual("קלט לא תקין"); + expect( + getErrorMessage( + z + .string() + .refine(() => { + return false; + }) + .safeParse("") + ) + ).toEqual("קלט לא תקין"); +});