diff --git a/examples/with-next-i18next/next-i18next.config.js b/examples/with-next-i18next/next-i18next.config.js index 0d01f7f..2a99f31 100644 --- a/examples/with-next-i18next/next-i18next.config.js +++ b/examples/with-next-i18next/next-i18next.config.js @@ -8,6 +8,7 @@ module.exports = { locales: [ "en", "he", + "ko", "ja", "fr", "ar", diff --git a/examples/with-next-i18next/pages/index.tsx b/examples/with-next-i18next/pages/index.tsx index 5fabf25..c955338 100644 --- a/examples/with-next-i18next/pages/index.tsx +++ b/examples/with-next-i18next/pages/index.tsx @@ -83,6 +83,7 @@ export default function HookForm() { + diff --git a/examples/with-next-i18next/public/locales/ko/common.json b/examples/with-next-i18next/public/locales/ko/common.json new file mode 100644 index 0000000..c4a4d9b --- /dev/null +++ b/examples/with-next-i18next/public/locales/ko/common.json @@ -0,0 +1,7 @@ +{ + "username": "사용자 명", + "username_placeholder": "홍길동", + "email": "이메일", + "favoriteNumber": "좋아하는 숫자", + "submit": "제출" +} diff --git a/examples/with-next-i18next/public/locales/ko/zod.json b/examples/with-next-i18next/public/locales/ko/zod.json new file mode 100644 index 0000000..f98bbec --- /dev/null +++ b/examples/with-next-i18next/public/locales/ko/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": "'{{received}}'값은 허용되지 않습니다. 입력 가능한 값은 {{- options}} 입니다.", + "invalid_arguments": "허용되지 않은 파라메터 타입입니다.", + "invalid_return_type": "허용되지 않은 반환 타입입니다.", + "invalid_date": "허용되지 않은 날짜 값 입니다.", + "custom": "허용되지 않은 입력 값 입니다.", + "invalid_intersection_types": "교차 타입을 만족하지 않습니다.", + "not_multiple_of": "{{multipleOf}}의 배수이어야 합니다.", + "not_finite": "유한한 값이어야 합니다.", + "invalid_string": { + "email": "허용되지 않은 {{validation}} 형식 입니다.", + "url": "허용되지 않은 {{validation}} 형식 입니다.", + "uuid": "허용되지 않은 {{validation}} 형식 입니다.", + "cuid": "허용되지 않은 {{validation}} 형식 입니다.", + "regex": "허용되지 않은 형식 입니다.", + "datetime": "허용되지 않은 {{validation}} 형식 입니다.", + "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": "URL", + "uuid": "UUID", + "cuid": "CUID", + "regex": "정규식", + "datetime": "날짜" + }, + "types": { + "function": "함수 타입", + "number": "숫자 타입", + "string": "문자열 타입", + "nan": "NaN 값", + "integer": "정수 타입", + "float": "실수 타입", + "boolean": "논리값 타입", + "date": "날짜 타입", + "bigint": "bigint 타입", + "undefined": "undefined 타입", + "symbol": "symbol 타입", + "null": "null 값", + "array": "배열 타입", + "object": "객체 타입", + "unknown": "unknown 타입", + "promise": "promise 타입", + "void": "void 타입", + "never": "never 타입", + "map": "map 타입", + "set": "set 타입" + } +} diff --git a/packages/core/locales/ko/zod.json b/packages/core/locales/ko/zod.json new file mode 100644 index 0000000..f98bbec --- /dev/null +++ b/packages/core/locales/ko/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": "'{{received}}'값은 허용되지 않습니다. 입력 가능한 값은 {{- options}} 입니다.", + "invalid_arguments": "허용되지 않은 파라메터 타입입니다.", + "invalid_return_type": "허용되지 않은 반환 타입입니다.", + "invalid_date": "허용되지 않은 날짜 값 입니다.", + "custom": "허용되지 않은 입력 값 입니다.", + "invalid_intersection_types": "교차 타입을 만족하지 않습니다.", + "not_multiple_of": "{{multipleOf}}의 배수이어야 합니다.", + "not_finite": "유한한 값이어야 합니다.", + "invalid_string": { + "email": "허용되지 않은 {{validation}} 형식 입니다.", + "url": "허용되지 않은 {{validation}} 형식 입니다.", + "uuid": "허용되지 않은 {{validation}} 형식 입니다.", + "cuid": "허용되지 않은 {{validation}} 형식 입니다.", + "regex": "허용되지 않은 형식 입니다.", + "datetime": "허용되지 않은 {{validation}} 형식 입니다.", + "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": "URL", + "uuid": "UUID", + "cuid": "CUID", + "regex": "정규식", + "datetime": "날짜" + }, + "types": { + "function": "함수 타입", + "number": "숫자 타입", + "string": "문자열 타입", + "nan": "NaN 값", + "integer": "정수 타입", + "float": "실수 타입", + "boolean": "논리값 타입", + "date": "날짜 타입", + "bigint": "bigint 타입", + "undefined": "undefined 타입", + "symbol": "symbol 타입", + "null": "null 값", + "array": "배열 타입", + "object": "객체 타입", + "unknown": "unknown 타입", + "promise": "promise 타입", + "void": "void 타입", + "never": "never 타입", + "map": "map 타입", + "set": "set 타입" + } +} diff --git a/packages/core/tests/integrations/ko.test.ts b/packages/core/tests/integrations/ko.test.ts new file mode 100644 index 0000000..3cdec72 --- /dev/null +++ b/packages/core/tests/integrations/ko.test.ts @@ -0,0 +1,217 @@ +import { test, expect, beforeAll } from "vitest"; +import { z } from "zod"; +import { init, getErrorMessage, getErrorMessageFromZodError } from "./helpers"; + +const LOCALE = "ko"; + +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( + "허용되지 않은 URL 형식 입니다." + ); + 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( + "'D'값은 허용되지 않습니다. 입력 가능한 값은 'A' | 'B' | 'C' 입니다." + ); + 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("허용되지 않은 입력 값 입니다."); +});