服务端使用zod-i18n时Zod错误消息为undefined的问题
在SvelteKit的+page.server.ts中使用Zod Schema校验表单数据,原校验逻辑正常:
const propertySchema = z.object({ rent_type: z.enum(['rent', 'sell']), title: z.string().min(10), typ: z.enum(['house', 'flat', 'garage', 'office', 'hotel']), price: z.coerce.number().positive(), area: z.coerce.number().positive(), rooms: z.coerce.number().positive(), address: z.string().min(10), description: z.string().min(20), beds: z.coerce.number().positive(), bathrooms: z.coerce.number().positive(), garage: z.coerce.number(), constructionYear: z.coerce.number().int().positive().min(1900) });
通过const propertyData = propertySchema.safeParse(formData);执行校验。
为实现错误消息国际化,引入zod-i18n-map包后,在+layout.server.ts中初始化i18next与zodI18nMap:
import i18next from "i18next"; import { z } from "zod"; import { zodI18nMap } from "zod-i18n-map"; // Import your language translation files import translation from "zod-i18n-map/locales/es/zod.json"; // lng and resources key depend on your locale. i18next.init({ lng: "es", resources: { es: { zod: translation }, }, }); z.setErrorMap(zodI18nMap);
但校验时错误消息始终为undefined,比如执行const schema = z.string().email();schema.parse("foo")返回的error.message是undefined。而在+layout.svelte的onMount()生命周期中初始化则可正常工作,需解决服务端初始化的问题。
问题核心是i18next.init是异步操作,你在初始化完成前就设置了Zod的错误映射并执行校验,导致翻译资源未加载完成,错误消息无法正确渲染。在SvelteKit服务端环境中,需确保i18next初始化完成后再执行校验,具体修改如下:
- 修改
+layout.server.ts,将初始化逻辑放入异步load函数:
import i18next from "i18next"; import { z } from "zod"; import { zodI18nMap } from "zod-i18n-map"; import translation from "zod-i18n-map/locales/es/zod.json"; export async function load() { await i18next.init({ lng: "es", resources: { es: { zod: translation }, }, initImmediate: false, // 禁用立即初始化,确保资源加载完成 }); z.setErrorMap(zodI18nMap); }
SvelteKit的load函数会在页面渲染前执行,保证服务端初始化逻辑完成后才处理后续请求。
保留
+page.server.ts原校验逻辑即可
SvelteKit服务端路由会等待+layout.server.ts的load函数执行完毕后,再处理+page.server.ts中的请求逻辑,无需额外修改校验代码。可选:动态切换语言
若需根据请求动态切换语言,可在load函数中获取请求的locale信息:
export async function load({ request }) { // 示例从请求头Accept-Language中提取首选语言 const locale = request.headers.get('Accept-Language')?.split(',')[0] || 'es'; // 按需导入对应语言的翻译文件 const translation = await import(`zod-i18n-map/locales/${locale}/zod.json`); await i18next.init({ lng: locale, resources: { [locale]: { zod: translation.default }, }, initImmediate: false, }); z.setErrorMap(zodI18nMap); }
内容的提问来源于stack exchange,提问作者Jul Pod

