seasonedcc / remix-forms

The full-stack form library for Remix and React Router
https://remix-forms.seasoned.cc
MIT License
491 stars 25 forks source link

Docs: i18n and localization with Zod #67

Closed janhesters closed 2 years ago

janhesters commented 2 years ago

How do you do internationalization for the error messages?

Let's say you have a schema:

const schema = z.object({
  name: z
    .string()
    .min(3, 'A name is required and needs to be at least 3 characters long.'),
});

The leading library for i18n for Remix is currently remix-i18next. It requires the use of a t function, which is available through the useTranslation hook.

However, that t value is in a component. The schema needs to be defined outside of the component, so it can be used in the action function. How would you solve this?

gustavoguichard commented 2 years ago

We don't use remix-i18next or react-i18next and TBH I wouldn't use it unless I had a really good reason. Translating strings require only a pure function:

declare function t(i: string): string

It is not something that should depend on a component. There'll be plenty of strings coming from the server that should not depend on being in a hook... flash messages, for instance, will need to be set on the server and they'll need translation.

What you can do is using the i18next library though. Or any other library / custom function that conforms (i: string): string IMHO

gustavoguichard commented 2 years ago

What I'm really looking after is any function that can implement:

const schema = z.object({
  name: z.string().min(3, t('forms.name')),
});
janhesters commented 2 years ago

My workaround ended up being:

const schema = z.object({
  name: z.string().min(3, 'user-profile:name-required-and-constraints'),
});

where the component then grabs the value like this:

const Error = ({ children, ...props }: JSX.IntrinsicElements['div']) => {
  const { t } = useTranslation();

  return (
    <p className="mt-2 text-sm text-red-600" {...props}>
      {typeof children === 'string' ? t(children, children) : children}
    </p>
  );
};

@gustavoguichard thanks for the suggestion. The problem with Remix-i18next's i18next is, that it only offers i18next.getFixedT, which returns a promise and is therefore not usable for defining the schema.

gustavoguichard commented 2 years ago

I'm glad you were able to solve it somehow.. I'm not familiar with those libraries. Looking forward to having top level await ASAP in Node =)

founderblocks-sils commented 1 year ago

hey, I've used this:

labels={Object.fromEntries( Object.keys(schema.shape).map((key) => [key, t(key)]) )}

to localize the labels in case its helpful to others or worth documenting (I believe i18n is worth documenting!)

localizing errors is still on my todo list