我在 Zod 中遇到 i18n 问题。基本上,我有一个由react-hook-form和Zod处理的简单表单进行验证,但是当我更改区域设置时,错误消息似乎不会根据活动区域设置进行翻译。 如果有人可以帮助我使 zod 错误消息动态化。我正在使用 Next.js,next-i18next:
// my zod schema
import { z } from 'zod';
import { i18n } from 'next-i18next';
export const mailSchema = z.object({
email: z
.string({
invalid_type_error: i18n?.t('zod-errors.invalid_type_error') ?? '',
required_error: i18n?.t('zod-errors.required_error') ?? '',
})
.email(i18n?.t('zod-errors.email') ?? ''),
subject: z
.string({
invalid_type_error: i18n?.t('zod-errors.invalid_type_error') ?? '',
required_error: i18n?.t('zod-errors.required_error') ?? '',
})
.min(3, i18n?.t('zod-errors.min') ?? '')
.max(60, i18n?.t('zod-errors.max') ?? ''),
body: z
.string({
invalid_type_error: i18n?.t('zod-errors.invalid_type_error') ?? '',
required_error: i18n?.t('zod-errors.required_error' ?? ''),
})
.min(10, i18n?.t('zod-errors.min') ?? '')
.max(500, i18n?.t('zod-errors.max') ?? ''),
});
我有同样的问题,我找到了一个为 zod 错误消息提供自动翻译的库:
我认为问题在于消息的异步加载,这并没有真正解释,但在 i18next 的配置文档中提到过。
我想要自己的错误消息,而不是由另一个依赖项翻译的 zod。所以我这样做了:
const validationSchema = z.object({
place: z.object({
route: z.string().min(1, "field::Address.Req").default(""),
streetNumber: z.string().min(1, "field::Street number.Req").default(""),
postalCode: z.string().min(1, "field::Postal code.Req").default(""),
})
});
所以错误消息是 i18next 翻译机的关键。当渲染/呈现给用户时,会获取实际的消息。
在
field
命名空间中,我得到了这个:
{
"IsReq": "ist erforderlich",
"Address.Req": "$t(common::Address) $t(field::IsReq)",
"Street number.Req": "$t(common::Street number) $t(field::IsReq)",
"Postal code.Req": "$t(common::Postal code) $t(field::IsReq)"
}
好处是它只是使用系统(zod | i18next),翻译者可以忽略插值并根据需要写出更好的东西。
(将架构添加到组件中也可以解决问题,但不知何故感觉不太对..)
我遇到了同样的问题并且非常简单地解决了!在 Zod 对象中,作为错误消息,我只放置翻译键,实际的 t('key') 在错误消息组件中完成。不需要图书馆,根本不需要额外的工作。希望您喜欢这个简单的解决方法:)
❌错了
email: z.string().email({ message: t('emailError)})
✅正确!
email: z.string().email({ message: 'emailError'})
然后像这样使用:
<HelperText type='error'>
{t(errors.email.message)}
</HelperText>
大家好!
我不确定我的方法是否完全正确,但我找到了一种使用 Zod 管理 next-intl 翻译值/消息参数以解决验证错误的方法。
让我们从 zod 模式开始
export const signupFormStepUsernameSchema = z.object({
username: z
.string()
.trim()
.superRefine((val, ctx) => {
const minChars = 4
const maxChars = 20
// ... other validation
if (val.length < minChars) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `errors.invalid.username.minLength|{"min": ${minChars}}`,
})
return z.NEVER
}
if (val.length > maxChars) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `errors.invalid.username.maxLength|{"max": ${maxChars}}`,
})
return z.NEVER
}
// ... other validation
}),
}).strict().required();
让我们分解一下我们传递的内容或以什么格式传递错误消息:
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `errors.invalid.username.maxLength|{"max": ${maxChars}}`
});
在消息中,我们定义了特定的格式,并用
|
符号分隔消息键和翻译值/消息参数。
在实现部分,我们通过以下方法提取密钥和翻译值/消息参数。
首先让我提一下必要的导入和类型声明
import { MessageKeys, TranslationValues } from "next-intl"
type ComponentSignupMessages =
typeof import("./i18n/en-US/components/signup.json");
type Messages = ComponentSignupMessages;
type ZNIMNestedValueOf<
T,
K extends Extract<keyof T, string>
> = K extends keyof T
? T[K] extends Record<string, any>
? `${K}.${ZNIMNestedKeys<T[K]>}`
: `${K}`
: never;
type ZNIMNestedKeys<T> = NestedValueOf<T, Extract<keyof T, string>>;
interface IntlMessages extends Messages {}
好的,现在让我们开始工作吧。
const dirtyErrorKeyAndTranslationValues: Array<string> = errors.username ? (errors.username.message as string).split("|"): "|".split("|");
上面的代码行检查是否存在与用户名字段关联的错误消息。如果存在,它将按
|
拆分错误消息,并将结果部分存储在名为 dirtyErrorKeyAndTranslationValues 的数组中。如果没有错误,它会创建一个包含空字符串的数组。
let refinedErrorKey = "" as MessageKeys<
ComponentSignupMessages["Signup"]["StepUsername"],
ZNIMNestedKeys<ComponentSignupMessages["Signup"]["StepUsername"]>
>;
if (
(dirtyErrorKeyAndTranslationValues[0] as string) &&
(dirtyErrorKeyAndTranslationValues[0] as string).length > 0
) {
refinedErrorKey = dirtyErrorKeyAndTranslationValues[0] as MessageKeys<
ComponentSignupMessages["Signup"]["StepUsername"],
ZNIMNestedKeys<ComponentSignupMessages["Signup"]["StepUsername"]>
>;
}
上面的代码将一个名为refineErrorKey的变量初始化为空字符串。然后,它检查 dirtyErrorKeyAndTranslationValues 数组的第一个元素(假设为字符串)是否存在并且长度是否大于零。如果为 true,则会将 dirtyErrorKeyAndTranslationValues 的第一个元素的值分配给refineErrorKey。
let refinedTranslationValues = {} as TranslationValues;
if (
(dirtyErrorKeyAndTranslationValues[1] as string) &&
(dirtyErrorKeyAndTranslationValues[1] as string).length > 0
) {
refinedTranslationValues = JSON.parse(
dirtyErrorKeyAndTranslationValues[1]
) as TranslationValues;
}
上面的代码将名为refineTranslationValues的变量初始化为空对象。然后,它检查 dirtyErrorKeyAndTranslationValues 数组的第二个元素(应为字符串)是否存在且长度是否大于零。如果为 true,则会将 dirtyErrorKeyAndTranslationValues 的第二个元素解析为 JSON,并将结果对象分配给精炼的翻译值。
<p>
{errors.username
? t(
refinedErrorKey,
Object.keys(refinedTranslationValues).length === 0
? refinedTranslationValues
: undefined
)
: " "}
</p>
在这里,如果存在与用户名 (errors.username) 关联的错误消息,它将调用带有参数精炼错误键和精炼翻译值的翻译函数 t(),除非精炼翻译值为空。如果用户名没有错误消息,它会呈现一个空白空间。
helperText
中所有上述实现的实际示例<TextField/>
谢谢!