QuiiBz / next-international

Type-safe internationalization (i18n) for Next.js
https://next-international.vercel.app
MIT License
1.26k stars 59 forks source link

Zod support? #213

Closed iStorry closed 10 months ago

iStorry commented 11 months ago

Is there anyway i can use this zod library?

export const signInSchema = z.object({
  email: z.string().email({ message: t("signIn.login") }),
  password: z.string().min(6, { message: t("signIn.password") }),
});
QuiiBz commented 11 months ago

I'm not too familiar with Zod, but I don't see why it wouldn't be possible. The only thing is that you have to create the schema when rendering a component, because you can only access the t() function there. Probably a good idea to wrap the schema in a memo too:

function Component() {
  const t = useI18n()

  const signInSchema = useMemo(() => z.object({
    email: z.string().email({ message: t('signIn.login') }),
    password: z.string().min(6, { message: t('signIn.password') })
  }), [t])

  return ...
}
iStorry commented 11 months ago

Hey thanks for reply. but signInSchema needs to be exported as well. This will not be issue if signInSchema is only getting used by that component

This is how zod translation are supported in i18next

https://github.com/aiji42/zod-i18n

QuiiBz commented 11 months ago

Thanks for the link. When do you usually call i18next.init with the correct language? You'll likely have to do this inside a React component, right?

i18next.init({
  lng: "es",
  ...
})

If so, I believe it should be possible to do the same for next-international. Unfortunately, I don't have enough knowledge with Zod to add this feature, but I'd welcome any PR/help from the community!

Yovach commented 11 months ago

Hi, To be "compatible" with zod, I did something like the following

import type { useI18n } from "~/locales/client";

export type TranslateFunc = Awaited<ReturnType<typeof useI18n>>;
export const validation = (t: TranslateFunc) =>
  z.object({
    username: z.string().min(3, t("...")),
    password: z.string().min(3, t("...")),
  });

I know it's not the best way to proceed, but it works well and so far I haven't encountered any problems.

iStorry commented 11 months ago

Hi, To be "compatible" with zod, I did something like the following

import type { useI18n } from "~/locales/client";

export type TranslateFunc = Awaited<ReturnType<typeof useI18n>>;
export const validation = (t: TranslateFunc) =>
  z.object({
    username: z.string().min(3, t("...")),
    password: z.string().min(3, t("...")),
  });

I know it's not the best way to proceed, but it works well and so far I haven't encountered any problems.

Thanks for the solution! Have you tried getting typesof validation?

type Inputs = z.infer<typeof validation>; // This throw an error. 

also react-hook-form throw an error

const form = useForm<Inputs>({
    resolver: zodResolver(validation), // This throw an error. 
})

@Yovach

Yovach commented 11 months ago

Hi, To be "compatible" with zod, I did something like the following

import type { useI18n } from "~/locales/client";

export type TranslateFunc = Awaited<ReturnType<typeof useI18n>>;
export const validation = (t: TranslateFunc) =>
  z.object({
    username: z.string().min(3, t("...")),
    password: z.string().min(3, t("...")),
  });

I know it's not the best way to proceed, but it works well and so far I haven't encountered any problems.

Thanks for the solution! Have you tried getting typesof validation?

type Inputs = z.infer<typeof validation>; // This throw an error. 

also react-hook-form throw an error

const form = useForm<Inputs>({
    resolver: zodResolver(validation), // This throw an error. 
})

@Yovach

Hi,

You can do this for the first :

type Inputs = z.infer<ReturnType<typeof validation>>;

and this for react-hook-form, I think it'll works

const t = useI18n();
const form = useForm<Inputs>({
    resolver: zodResolver(validation(t)),
})
iStorry commented 11 months ago

@Yovach Thanks

QuiiBz commented 10 months ago

Closing as it seems resolved, thanks for the help!

pavelsushkov commented 6 months ago

Custom hook works for me in case of custom error map:

'use client';

import { useEffect } from 'react';

import { z } from 'zod';

import { useScopedI18n } from '@/../locales/client';

export const useZodI18n = () => {
  const scopedT = useScopedI18n('Messages');

  const zodI18nErrorMap: z.ZodErrorMap = useCallback((issue, ctx) => {
    if (issue.code === z.ZodIssueCode.invalid_type || issue.code === z.ZodIssueCode.too_small) {
      return { message: scopedT('requiredField') };
    }

    if (issue.code === z.ZodIssueCode.invalid_string && issue.validation === 'email') {
      return { message: scopedT('invalidEmail') };
    }

    return { message: ctx.defaultError };
  }, [scopedT]);

  useEffect(() => {
    z.setErrorMap(zodI18nErrorMap);

  }, [zodI18nErrorMap]);

  return {
    zodI18nErrorMap,
  }
}

And then I use it like this:

export const SomeClientComponent = () => {
  useZodI18n();
...