Created
November 13, 2024 12:40
-
-
Save dBianchii/d61e95c8552fd79f2000cee67ef665af to your computer and use it in GitHub Desktop.
Automatic zod translations with with next-intl, tRPC, react-hook-form
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//TODO: figure out how to make typed namespaces work. (Both with i18n-ally and next-intl/use-intl) | |
type TranslationKeys = never; | |
export type ServerSideT<S extends TranslationKeys = never> = Awaited< | |
ReturnType<typeof getTranslations<S>> | |
>; | |
export type ClientSideT<S extends TranslationKeys = never> = ReturnType< | |
typeof useTranslations<S> | |
>; | |
export type IsomorficT<S extends TranslationKeys = never> = | |
| ServerSideT<S> | |
| ClientSideT<S>; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This error map is a modified version of the one used by zod-i18n | |
* Checkout the original at: https://github.com/aiji42/zod-i18n | |
*/ | |
import type { useTranslations } from "next-intl"; | |
import type { ZodErrorMap } from "zod"; | |
import { defaultErrorMap, ZodIssueCode, ZodParsedType } from "zod"; | |
const jsonStringifyReplacer = (_: string, value: unknown): unknown => { | |
if (typeof value === "bigint") { | |
return value.toString(); | |
} | |
return value; | |
}; | |
function joinValues<T extends unknown[]>(array: T, separator = " | "): string { | |
return array | |
.map((val) => (typeof val === "string" ? `'${val}'` : val)) | |
.join(separator); | |
} | |
const isRecord = (value: unknown): value is Record<string, unknown> => { | |
if (typeof value !== "object" || value === null) return false; | |
for (const key in value) { | |
if (!Object.prototype.hasOwnProperty.call(value, key)) return false; | |
} | |
return true; | |
}; | |
const getKeyAndValues = ( | |
param: unknown, | |
defaultKey: string, | |
): { | |
values: Record<string, unknown>; | |
key: string; | |
} => { | |
if (typeof param === "string") return { key: param, values: {} }; | |
if (isRecord(param)) { | |
const key = | |
"key" in param && typeof param.key === "string" ? param.key : defaultKey; | |
const values = | |
"values" in param && isRecord(param.values) ? param.values : {}; | |
return { key, values }; | |
} | |
return { key: defaultKey, values: {} }; | |
}; | |
export const zodNs = "zod"; | |
export const formNs = "zod.form"; | |
export const customErrorsNs = "zod.customErrors"; | |
interface ZodI18nMapOption { | |
t: ReturnType<typeof useTranslations<typeof zodNs>>; | |
tForm?: ReturnType<typeof useTranslations<typeof formNs>>; | |
tCustom?: ReturnType<typeof useTranslations<typeof customErrorsNs>>; | |
ns?: string | readonly string[]; | |
} | |
type MakeZodI18nMap = (option: ZodI18nMapOption) => ZodErrorMap; | |
export const makeZodI18nMap: MakeZodI18nMap = (option) => (issue, ctx) => { | |
const { t, tForm, tCustom } = { | |
...option, | |
}; | |
let message: string; | |
message = defaultErrorMap(issue, ctx).message; | |
const path = | |
issue.path.length > 0 && !!tForm | |
? // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
{ path: tForm(issue.path.join(".") as any) } | |
: {}; | |
switch (issue.code) { | |
case ZodIssueCode.invalid_type: | |
if (issue.received === ZodParsedType.undefined) { | |
message = t("errors.invalid_type_received_undefined", { | |
...path, | |
}); | |
} else { | |
message = t("errors.invalid_type", { | |
expected: t(`types.${issue.expected}`), | |
received: t(`types.${issue.received}`), | |
...path, | |
}); | |
} | |
break; | |
case ZodIssueCode.invalid_literal: | |
message = t("errors.invalid_literal", { | |
expected: JSON.stringify(issue.expected, jsonStringifyReplacer), | |
...path, | |
}); | |
break; | |
case ZodIssueCode.unrecognized_keys: | |
message = t("errors.unrecognized_keys", { | |
keys: joinValues(issue.keys, ", "), | |
count: issue.keys.length, | |
...path, | |
}); | |
break; | |
case ZodIssueCode.invalid_union: | |
message = t("errors.invalid_union", { | |
...path, | |
}); | |
break; | |
case ZodIssueCode.invalid_union_discriminator: | |
message = t("errors.invalid_union_discriminator", { | |
options: joinValues(issue.options), | |
...path, | |
}); | |
break; | |
case ZodIssueCode.invalid_enum_value: | |
message = t("errors.invalid_enum_value", { | |
options: joinValues(issue.options), | |
received: issue.received, | |
...path, | |
}); | |
break; | |
case ZodIssueCode.invalid_arguments: | |
message = t("errors.invalid_arguments", { | |
...path, | |
}); | |
break; | |
case ZodIssueCode.invalid_return_type: | |
message = t("errors.invalid_return_type", { | |
...path, | |
}); | |
break; | |
case ZodIssueCode.invalid_date: | |
message = t("errors.invalid_date", { | |
...path, | |
}); | |
break; | |
case ZodIssueCode.invalid_string: | |
if (typeof issue.validation === "object") { | |
if ("startsWith" in issue.validation) { | |
message = t("errors.invalid_string.startsWith", { | |
startsWith: issue.validation.startsWith, | |
...path, | |
}); | |
} else if ("endsWith" in issue.validation) { | |
message = t("errors.invalid_string.endsWith", { | |
endsWith: issue.validation.endsWith, | |
...path, | |
}); | |
} | |
} else { | |
message = t(`errors.invalid_string.${issue.validation}`, { | |
validation: t(`validations.${issue.validation}`), | |
...path, | |
}); | |
} | |
break; | |
case ZodIssueCode.too_small: { | |
const minimum = | |
issue.type === "date" | |
? new Date(issue.minimum as number) | |
: (issue.minimum as number); | |
message = t( | |
`errors.too_small.${issue.type}.${ | |
issue.exact | |
? "exact" | |
: issue.inclusive | |
? "inclusive" | |
: "not_inclusive" | |
}`, | |
{ | |
minimum, | |
count: typeof minimum === "number" ? minimum : undefined, | |
...path, | |
}, | |
); | |
break; | |
} | |
case ZodIssueCode.too_big: { | |
const maximum = | |
issue.type === "date" | |
? new Date(issue.maximum as number) | |
: (issue.maximum as number); | |
message = t( | |
`errors.too_big.${issue.type}.${ | |
issue.exact | |
? "exact" | |
: issue.inclusive | |
? "inclusive" | |
: "not_inclusive" | |
}`, | |
{ | |
maximum, | |
count: typeof maximum === "number" ? maximum : undefined, | |
...path, | |
}, | |
); | |
break; | |
} | |
case ZodIssueCode.custom: { | |
const { key, values } = getKeyAndValues( | |
issue.params?.i18n, | |
"errors.custom", | |
); | |
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing | |
message = (tCustom || t)(key as Parameters<typeof t>[0], { | |
...values, | |
...path, | |
}); | |
break; | |
} | |
case ZodIssueCode.invalid_intersection_types: | |
message = t("errors.invalid_intersection_types", { | |
...path, | |
}); | |
break; | |
case ZodIssueCode.not_multiple_of: | |
message = t("errors.not_multiple_of", { | |
multipleOf: issue.multipleOf as number, | |
...path, | |
}); | |
break; | |
case ZodIssueCode.not_finite: | |
message = t("errors.not_finite", { | |
...path, | |
}); | |
break; | |
default: | |
} | |
return { message }; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { z, ZodSchema } from "zod"; | |
import { cookies } from "next/headers"; | |
import { getTranslations } from "next-intl/server"; | |
import type { locales } from "@kdx/locales"; | |
import { defaultLocale } from "@kdx/locales"; | |
import { createI18nZodErrors } from "@kdx/validators/useI18nZodErrors"; | |
export const getLocaleBasedOnCookie = () => | |
(cookies().get("NEXT_LOCALE")?.value ?? | |
defaultLocale) as (typeof locales)[number]; | |
type SchemaGetterFromT<S extends ZodSchema> = ( | |
t: Awaited<ReturnType<typeof getTranslations>>, | |
) => S; | |
export const T = | |
<S extends ZodSchema>(schemaGetter: SchemaGetterFromT<S>) => | |
async (input: unknown) => { | |
const locale = getLocaleBasedOnCookie(); | |
const t = await getTranslations({ locale }); | |
await createI18nZodErrors({ locale }); | |
// eslint-disable-next-line @typescript-eslint/no-unsafe-return | |
return schemaGetter(t).parse(input) as z.infer<S>; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { T } from "../../../../utils/locales"; | |
export const kodixCareRouter = { | |
doCheckoutForShift: protectedProcedure | |
.input(T(ZDoCheckoutForShiftInputSchema)) // <----- example of how to use the big "T". | |
.use(kodixCareInstalledMiddleware) | |
.mutation(doCheckoutForShiftHandler), | |
} satisfies TRPCRouterRecord; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useTranslations } from "next-intl"; | |
import { getTranslations } from "next-intl/server"; | |
import { z } from "zod"; | |
// import { useTranslations as expo_useTranslations } from "use-intl"; | |
import { | |
customErrorsNs, | |
formNs, | |
makeZodI18nMap, | |
zodNs, | |
} from "./make-zod-error-map"; | |
export const useI18nZodErrors = () => { | |
const t = useTranslations(zodNs); | |
const tForm = useTranslations(formNs); | |
const tCustom = useTranslations(customErrorsNs); | |
z.setErrorMap(makeZodI18nMap({ t, tForm, tCustom })); | |
}; | |
export const createI18nZodErrors = async ({ locale }: { locale: string }) => { | |
const t = await getTranslations({ locale, namespace: zodNs }); | |
const tForm = await getTranslations({ locale, namespace: formNs }); | |
const tCustom = await getTranslations({ locale, namespace: customErrorsNs }); | |
z.setErrorMap(makeZodI18nMap({ t, tForm, tCustom })); | |
}; | |
// export const expo_useI18nZodErrors = () => { | |
// const t = expo_useTranslations(zodNs); | |
// const tForm = expo_useTranslations(formNs); | |
// const tCustom = expo_useTranslations(customErrorsNs); | |
// z.setErrorMap(makeZodI18nMap({ t, tForm, tCustom })); | |
// }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { IsomorficT } from "@kdx/locales"; | |
export const ZDoCheckoutForShiftInputSchema = (t: IsomorficT) => | |
z.object({ | |
notes: z.string().optional(), | |
date: z | |
.date() | |
.max(new Date(), { | |
message: t("validators.Checkout time cannot be in the future"), | |
}) | |
.transform(adjustDateToMinute), | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"zod": { | |
"errors": { | |
"invalid_type": "Expected {expected}, received {received}", | |
"invalid_type_received_undefined": "Required", | |
"invalid_type_received_null": "Required", | |
"invalid_literal": "Invalid literal value, expected {expected}", | |
"unrecognized_keys": "Unrecognized key(s) in object: {keys}", | |
"invalid_union": "Invalid input", | |
"invalid_union_discriminator": "Invalid discriminator value. Expected {options}", | |
"invalid_enum_value": "Invalid enum value. Expected {options}, received '{received}'", | |
"invalid_arguments": "Invalid function arguments", | |
"invalid_return_type": "Invalid function return type", | |
"invalid_date": "Invalid date", | |
"custom": "Invalid input", | |
"invalid_intersection_types": "Intersection results could not be merged", | |
"not_multiple_of": "Number must be a multiple of {multipleOf}", | |
"not_finite": "Number must be finite", | |
"invalid_string": { | |
"email": "Invalid {validation}", | |
"url": "Invalid {validation}", | |
"uuid": "Invalid {validation}", | |
"cuid": "Invalid {validation}", | |
"regex": "Invalid", | |
"datetime": "Invalid {validation}", | |
"date": "Invalid {validation}", | |
"emoji": "Invalid {validation}", | |
"nanoid": "Invalid {validation}", | |
"cuid2": "Invalid {validation}", | |
"ulid": "Invalid {validation}", | |
"time": "Invalid {validation}", | |
"ip": "Invalid {validation}", | |
"base64": "Invalid {validation}", | |
"duration": "Invalid {validation}", | |
"startsWith": "Invalid input: must start with \"{startsWith}\"", | |
"endsWith": "Invalid input: must end with \"{endsWith}\"" | |
}, | |
"too_small": { | |
"array": { | |
"exact": "{path, select, missingTranslation {Array} other {{path}}} must contain exactly {minimum} element(s)", | |
"inclusive": "{path, select, missingTranslation {Array} other {{path}}} must contain at least {minimum} element(s)", | |
"not_inclusive": "{path, select, missingTranslation {Array} other {{path}}} must contain more than {minimum} element(s)" | |
}, | |
"string": { | |
"exact": "{path, select, missingTranslation {String} other {{path}}} must contain exactly {minimum} character(s)", | |
"inclusive": "{path, select, missingTranslation {String} other {{path}}} must contain at least {minimum} character(s)", | |
"not_inclusive": "{path, select, missingTranslation {String} other {{path}}} must contain over {minimum} character(s)" | |
}, | |
"number": { | |
"exact": "{path, select, missingTranslation {Number} other {{path}}} must be exactly {minimum}", | |
"inclusive": "{path, select, missingTranslation {Number} other {{path}}} must be greater than or equal to {minimum}", | |
"not_inclusive": "{path, select, missingTranslation {Number} other {{path}}} must be greater than {minimum}" | |
}, | |
"bigint": { | |
"exact": "{path, select, missingTranslation {Number} other {{path}}} must be exactly {minimum}", | |
"inclusive": "{path, select, missingTranslation {Number} other {{path}}} must be greater than or equal to {minimum}", | |
"not_inclusive": "{path, select, missingTranslation {Number} other {{path}}} must be greater than {minimum}" | |
}, | |
"set": { | |
"exact": "Invalid input", | |
"inclusive": "Invalid input", | |
"not_inclusive": "Invalid input" | |
}, | |
"date": { | |
"exact": "Date must be exactly {minimum, date, short}", | |
"inclusive": "Date must be greater than or equal to {minimum, date, short}", | |
"not_inclusive": "Date must be greater than {minimum, date, short}" | |
} | |
}, | |
"too_big": { | |
"array": { | |
"exact": "{path, select, missingTranslation {Array} other {{path}}} must contain exactly {maximum} element(s)", | |
"inclusive": "{path, select, missingTranslation {Array} other {{path}}} must contain at most {maximum} element(s)", | |
"not_inclusive": "{path, select, missingTranslation {Array} other {{path}}} must contain less than {maximum} element(s)" | |
}, | |
"string": { | |
"exact": "{path, select, missingTranslation {String} other {{path}}} must contain exactly {maximum} character(s)", | |
"inclusive": "{path, select, missingTranslation {String} other {{path}}} must contain at most {maximum} character(s)", | |
"not_inclusive": "{path, select, missingTranslation {String} other {{path}}} must contain under {maximum} character(s)" | |
}, | |
"number": { | |
"exact": "{path, select, missingTranslation {Number} other {{path}}} must be exactly {maximum}", | |
"inclusive": "{path, select, missingTranslation {Number} other {{path}}} must be less than or equal to {maximum}", | |
"not_inclusive": "{path, select, missingTranslation {Number} other {{path}}} must be less than {maximum}" | |
}, | |
"bigint": { | |
"exact": "{path, select, missingTranslation {Number} other {{path}}} must be exactly {maximum}", | |
"inclusive": "{path, select, missingTranslation {Number} other {{path}}} must be less than or equal to {maximum}", | |
"not_inclusive": "{path, select, missingTranslation {Number} other {{path}}} must be less than {maximum}" | |
}, | |
"set": { | |
"exact": "Invalid input", | |
"inclusive": "Invalid input", | |
"not_inclusive": "Invalid input" | |
}, | |
"date": { | |
"exact": "Date must be exactly {maximum, date, short}", | |
"inclusive": "Date must be smaller than or equal to {maximum, date, short}", | |
"not_inclusive": "Date must be smaller than {maximum, date, short}" | |
} | |
} | |
}, | |
"validations": { | |
"email": "email", | |
"url": "url", | |
"uuid": "uuid", | |
"cuid": "cuid", | |
"regex": "regex", | |
"datetime": "datetime", | |
"date": "date", | |
"emoji": "emoji", | |
"nanoid": "nanoid", | |
"cuid2": "cuid2", | |
"ulid": "ulid", | |
"time": "time", | |
"ip": "ip", | |
"base64": "base64", | |
"duration": "duration" | |
}, | |
"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" | |
}, | |
"customErrors": { | |
"admin_username_error": "Nice try!" | |
}, | |
"form": { | |
"teamName": "Team name" | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"zod": { | |
"errors": { | |
"invalid_type": "O dado deve ser do tipo {expected}, porém foi enviado {received}", | |
"invalid_type_received_undefined": "Obrigatório", | |
"invalid_type_received_null": "Obrigatório", | |
"invalid_literal": "Valor literal inválido, era esperado {expected}", | |
"unrecognized_keys": "Chave(s) não reconhecida(s) no objeto: {keys}", | |
"invalid_union": "Entrada inválida", | |
"invalid_union_discriminator": "Valor discriminador inválido. Foi esperado {options}", | |
"invalid_enum_value": "Enum no formato inválido. Foi esperado {options}, porém foi recebido '{received}'", | |
"invalid_arguments": "Argumento de função inválido", | |
"invalid_return_type": "Tipo de retorno de função inválido", | |
"invalid_date": "Data inválida", | |
"custom": "Entrada inválida", | |
"invalid_intersection_types": "Valores de interseção não poderam ser mesclados", | |
"not_multiple_of": "O número deverá ser múltiplo de {multipleOf}", | |
"not_finite": "Número não pode ser infinito", | |
"invalid_string": { | |
"email": "E-mail inválido", | |
"url": "URL inválida", | |
"uuid": "UUID inválido", | |
"cuid": "CUID inválido", | |
"regex": "Combinação inválida", | |
"datetime": "datetime inválido", | |
"date": "Data inválida", | |
"emoji": "Emoji inválido", | |
"nanoid": "Nanoid inválido", | |
"cuid2": "CUID2 inválido", | |
"ulid": "ULID inválido", | |
"time": "Hora inválida", | |
"ip": "IP inválido", | |
"base64": "Base64 inválido", | |
"duration": "Duração inválida", | |
"startsWith": "Entrada inválida: deve iniciar com \"{startsWith}\"", | |
"endsWith": "Entrada inválida: deve terminar com \"{endsWith}\"" | |
}, | |
"too_small": { | |
"array": { | |
"exact": "{path, select, missingTranslation {Array} other {{path}}} deve conter exatamente {minimum} elemento(s)", | |
"inclusive": "{path, select, missingTranslation {Array} other {{path}}} deve conter pelo menos {minimum} elemento(s)", | |
"not_inclusive": "{path, select, missingTranslation {Array} other {{path}}} deve conter mais que {minimum} elemento(s)" | |
}, | |
"string": { | |
"exact": "{path, select, missingTranslation {String} other {{path}}} deve conter exatamente {minimum} character(es)", | |
"inclusive": "{path, select, missingTranslation {String} other {{path}}} deve conter pelo menos {minimum} character(es)", | |
"not_inclusive": "{path, select, missingTranslation {String} other {{path}}} deve conter mais que {minimum} character(es)" | |
}, | |
"number": { | |
"exact": "{path, select, missingTranslation {Number} other {{path}}} deve ser exatamenter {minimum}", | |
"inclusive": "{path, select, missingTranslation {Number} other {{path}}} deve ser maior ou igual que {minimum}", | |
"not_inclusive": "{path, select, missingTranslation {Number} other {{path}}} deve ser maior que {minimum}" | |
}, | |
"bigint": { | |
"exact": "{path, select, missingTranslation {Number} other {{path}}} deve ser exatamente {minimum}", | |
"inclusive": "{path, select, missingTranslation {Number} other {{path}}} deve ser maior ou igual que {minimum}", | |
"not_inclusive": "{path, select, missingTranslation {Number} other {{path}}} deve ser maior que {minimum}" | |
}, | |
"set": { | |
"exact": "Entrada inválida", | |
"inclusive": "Entrada inválida", | |
"not_inclusive": "Entrada inválida" | |
}, | |
"date": { | |
"exact": "Data deve ser exatamente {minimum, date, short}", | |
"inclusive": "Data deve ser maior ou igual a {minimum, date, short}", | |
"not_inclusive": "Data deve ser maior que {minimum, date, short}" | |
} | |
}, | |
"too_big": { | |
"array": { | |
"exact": "{path, select, missingTranslation {Array} other {{path}}} deve conter exatamente {maximum} elemento(s)", | |
"inclusive": "{path, select, missingTranslation {Array} other {{path}}} deve conter no máximo {maximum} elemento(s)", | |
"not_inclusive": "{path, select, missingTranslation {Array} other {{path}}} deve conter menos que {maximum} elemento(s)" | |
}, | |
"string": { | |
"exact": "{path, select, missingTranslation {String} other {{path}}} deve conter exatamente {maximum} caracter(es)", | |
"inclusive": "{path, select, missingTranslation {String} other {{path}}} deve conter no máximo {maximum} caracter(es)", | |
"not_inclusive": "{path, select, missingTranslation {String} other {{path}}} deve conter menos que {maximum} caracter(es)" | |
}, | |
"number": { | |
"exact": "{path, select, missingTranslation {Number} other {{path}}} deve ser exatamente {maximum}", | |
"inclusive": "{path, select, missingTranslation {Number} other {{path}}} deve ser menor ou igual a {maximum}", | |
"not_inclusive": "{path, select, missingTranslation {Number} other {{path}}} deve ser menor que {maximum}" | |
}, | |
"bigint": { | |
"exact": "{path, select, missingTranslation {Number} other {{path}}} deve ser exatamente {maximum}", | |
"inclusive": "{path, select, missingTranslation {Number} other {{path}}} deve ser menor ou igual a {maximum}", | |
"not_inclusive": "{path, select, missingTranslation {Number} other {{path}}} deve ser menor que {maximum}" | |
}, | |
"set": { | |
"exact": "Entrada inválida", | |
"inclusive": "Entrada inválida", | |
"not_inclusive": "Entrada inválida" | |
}, | |
"date": { | |
"exact": "Data deve ser exatamente {maximum, date, short}", | |
"inclusive": "Data deve ser menor ou igual que {maximum, date, short}", | |
"not_inclusive": "Data deve ser menor que {maximum, date, short}" | |
} | |
} | |
}, | |
"validations": { | |
"email": "email", | |
"url": "url", | |
"uuid": "uuid", | |
"cuid": "cuid", | |
"regex": "regex", | |
"datetime": "datetime", | |
"date": "date", | |
"emoji": "emoji", | |
"nanoid": "nanoid", | |
"cuid2": "cuid2", | |
"ulid": "ulid", | |
"time": "time", | |
"ip": "ip", | |
"base64": "base64", | |
"duration": "duration" | |
}, | |
"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" | |
}, | |
"customErrors": { | |
"admin_username_error": "Boa irmao!" | |
}, | |
"form": { | |
"teamName": "Nome da equipe" | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment