Zodã®ã¹ãã¼ãå®ç¾©ã¯å¥ãã¡ã¤ã«ã§ç®¡çãããï¼
Zodã®ã¹ãã¼ãå®ç¾©ã¯ã¯ã©ã¤ã¢ã³ãå´ã®ãã§ãã¯ã§ããµã¼ãã¼å´ã®ãã§ãã¯ã§ãå©ç¨ãããã®ã§ãã³ã³ãã¼ãã³ãå ã§å®ç¾©ããã®ã§ã¯ãªãå¥ãã¡ã¤ã«ã§ç®¡çããããã³ãªã®ã§ãããreact-intlãæ¡ç¨ãããããã¸ã§ã¯ãã§ãããããã¨ããã¨ããä¸çç¸ã§ã¯è¡ããªãã£ãã®ã§ã解決çãæ®ãã¦ããã¾ãã
ã¡ãªã¿ã«é¢é£ããã±ã¼ã¸ã®ãã¼ã¸ã§ã³ã¯ä»¥ä¸ã®ã¨ããã§ãã
"@hookform/resolvers": "5.2.1", "next": "16.0.7", "react": "19.2.1", "react-intl": "7.1.11", "zod": "3.25.58",
åé¡ç¹
ã¹ãã¼ãå®ç¾©ã¯å¤è¨èªå¯¾å¿ããªãå ´åã¯ä»¥ä¸ã®ããã«å®ç¾©ãããã¨ãã§ãã¾ãã
export const editUserSchema = z.object({ name: z .string() .min(1, "å¿ é é ç®ã§ã") .max(100, "ååã¯100æå以ä¸ã§å ¥åãã¦ãã ãã"), });
ããã以ä¸ã®ããã«ã¡ãã»ã¼ã¸å®ç¾©ãã¦ã
const message = defineMessage({ required: 'å¿ é é ç®ã§ã', maxLength: 'ååã¯{length}æå以ä¸ã§å ¥åãã¦ãã ãã', });
ããintl.formatMessage(message.required)ã¨æ¸ããã¨ããã¨ã
intlã¯ã©ããããã ï¼
ã¨ãªã£ã¦ãã¾ãã¾ãã
ã¯ã©ã¤ã¢ã³ãå´ã§ããã°useIntl()ããã¯ã使ã£ã¦åå¾ã§ãã¾ããããµã¼ãã¼å´ã¯createIntl颿°ã§çæãããã¨ã§intlãåå¾ã§ãã¾ãããã¹ãã¼ãå®ç¾©ãå¥ãã¡ã¤ã«ã«ããã®ã§ããã°ã©ã¡ããã«ä¾åãã¦ã¯æå³ãããã¾ããã
ããè¡ããã解決ç
zodã®è¿å´ããã¨ã©ã¼ã¡ãã»ã¼ã¸ã¯ãã ã®stringãªã®ã§ãã¡ãã»ã¼ã¸ã«ã¯MessageDescriptorã®idãè¨å®ããã¨ã©ã¼ã¡ãã»ã¼ã¸ã®è¡¨ç¤ºå´ã§ããããã¡ãã»ã¼ã¸æè¨ã«å¤æããæ¹æ³ãç¾ç¶ããå©ç¨ããã¦ããããã§ãã
å ·ä½çã«ã¯schemas.tsã«ã¯ä»¥ä¸ã®ããã«å®ç¾©ãã
const message = defineMessage({ required: 'å¿ é é ç®ã§ã', }); export const editUserSchema = z.object({ name: z .string() .min(1, message.required.id), });
ä¾ãã°ã¯ã©ã¤ã¢ã³ãå´ã§ã¯ã以ä¸ã®ããã«å©ç¨ããã¨ããæãã§ãã
const intl = useIntl(); const { register, formState: { errors }, } = useForm<z.infer<typeof editUserSchema>>({ resolver: zodResolver(editUserSchema), defaultValues: { name: '' }, }); const errorMessage = intl.formatMessage({ id: errors.name?.message });
ã¡ãªã¿ã«zodã®è©±ã ãã§ããã°ãzod-i18nã使ãã¨ç°¡åããããã¾ãã
æ¬æ ¼çãªè§£æ±º
ä¸è¨ã®æ¹æ³ã§ã¯ããã©ã¡ã¼ã¿ãã¡ãã»ã¼ã¸ã«æ¸¡ããã¨ãã§ãã¾ããã
ç¾ç¶Zodã®è¿å´å¤ã¯stringã ããªã®ã§ãå¾ã¯åæã§è§£æ±ºãããã¨ã«ãã¾ããã
å ·ä½çã«ã¯ã
- ã¹ãã¼ãå®ç¾©å ã®ã¡ãã»ã¼ã¸ã«ã¯å¿ è¦æ å ±ãå ¥ã£ãJSONæååãå®ç¾©ãã
- å©ç¨å´ã§ã¯JSONæååããã¼ã¹ãã¦formatMessageã«è¨å®ãã
ã§ãã
ã¾ãã¯ãã¦ã¼ãã£ãªãã£ã¨ãã¦ä»¥ä¸ã®2ã¤ã®é¢æ°ãå®ç¾©ãã¾ãã
getJsonErrorMessageãJSONæåå使ç¨ãgetSchemaCheckMessageãå®éã«åºãããã¡ãã»ã¼ã¸åºåç¨ã§ãã
import { z } from 'zod'; import { IntlShape } from 'react-intl'; export const getJsonErrorMessage = ( messageId: string | undefined, params?: {} ): string => { return JSON.stringify({ id: messageId, ...params }); }; export const getSchemaCheckMessage = ( intl: IntlShape, value: string | undefined ): string => { if (!value) { return ''; } try { const message = JSON.parse(value); if (!message?.id) { return message; } else if (intl.messages[message.id] === '') { return value; } else { const { id, ...rest } = message; return intl.formatMessage({ id: message.id }, rest); } } catch (e) { if (e instanceof SyntaxError) { return value; } throw e; } };
ã¹ãã¼ãå®ç¾©ã§ã¯ä»¥ä¸ã®ããã«è¨è¿°ãã¾ãã
const message = defineMessage({ required: 'å¿ é é ç®ã§ã', maxLength: 'ååã¯{length}æå以ä¸ã§å ¥åãã¦ãã ãã', }); export const editUserSchema = z.object({ name: z .string() .min(1, getJsonErrorMessage(message.required.id)) .max(100, getJsonErrorMessage(message.maxLength.id, { length: 100, }) ), });
ããã¦ä½¿ãå´ã¯ä»¥ä¸ã®ããã«ãªãã¾ãã
const intl = useIntl(); const { register, formState: { errors }, } = useForm<z.infer<typeof editUserSchema>>({ resolver: zodResolver(editUserSchema), defaultValues: { name: '' }, }); const errorMessage = getSchemaCheckMessage(intl, errors.name?.message);
ãµã¼ãã¼ãµã¤ãã§ã¯ä»¥ä¸ã®ãããªæãã§ããããããã£ã¡ã¯ããã¾ã§ç¡çãã使ã£ãé°å²æ°ã³ã¼ãã§ãã
"use server"; import z from "zod"; import { createIntl, createIntlCache } from 'react-intl'; const cache = createIntlCache(); export const editUser = async (props: z.infer<typeof editUserSchema>) => { const locale = await getServerLocale(); const intl = createIntl({ locale, messages: i18nMessages, defaultLocale: 'ja-JP' }, cache); const parsed = editUserSchema.safeParse(props); if (!parsed.success) { const nameErrors = z.flattenError(parsed.error).fieldErrors.name; if (nameErrors?.length) { for (const err of nameErrors) { console.error("Name error:", getSchemaCheckMessage(intl, err)); } } ... } ... };
ããã§åé¡ãªãreact-intlã使ããã¹ãã¼ãå®ç¾©ãå¥ãã¡ã¤ã«ã«ãããã¨ãã§ããããã«ãªãã¾ããã
å¤è¨èªå¯¾å¿ã¯å¤§å¤ããã¾ãããæ¥æ¬èªã«å¯¾å¿ãã¦ããã¦ãããµã¤ãã«ã¯æè¬ãããªãã§ãã
é¢é£ãªã³ã¯
- Api | FormatJS
- [Question] How to check if translation string/id exists? · Issue #747 · formatjs/formatjs · GitHub
- Opting out of the "[React Intl] Missing message" in developement · Issue #465 · formatjs/formatjs · GitHub
- i18next - Zod dynamic error messages based on active locale in Next.js - Stack Overflow



