Yup doesn't work properly with i18n
Asked Answered
E

5

13

I have this piece of a code. I want to add error messages depending on user's locale, but yup throws errors, same if fields are filled in incorrectly

[missing "en.login.emailRequiredError" translation] [missing "en.login.passRequiredError" translation]

const schema = yup.object().shape({
  email: yup
      .string()
      .email(i18n.t('login.emailSpellError'))
      .required(i18n.t('login.emailRequiredError')),
  password: yup
      .string()
      .matches(/^((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,15})$/i, i18n.t('login.passSpellError'))
      .required(i18n.t('login.passRequiredError')),
});

i18n.t('login.passRequiredError') works fine when I put it into a render method for checking it but it does not work with the yup. Any suggestions? Thanks in advance

Ecosystem answered 20/3, 2018 at 16:14 Comment(2)
what is the variable i18n in your example?Pentaprism
perhaps you could pass i18n as a prop to schema? Have you tried turning it into function that accepts i18n as an argument and returns yup object?Aerodyne
E
21

In your schema, replace:

.email(i18n.t('login.emailSpellError'))

with

.email('login.emailSpellError')

then in your render method:

{t(`form.errors.${form.errors.email}`)}

This assumes your translation file has an entry like this:

"form": { "errors": {"login": {"emailSpellError": "Your email is invalid"}}}}

The goal here is to move the t() method into your render method and have all translations happen there.

Electorate answered 21/6, 2019 at 2:55 Comment(3)
The problem with this approach is you can't use variables that are schema specific. For example: a validation of min characters in two different places with each one requiring a different min character count. eg. Min characters: 3 and Min characters 5 on two different form fields cant use the same translation.Kristikristian
It does not work for me tooDefinite
It does work for me on my case with i18n-js 😀Norval
P
7

Yup Validation method,

// You define the key mentioned in the translation file, in my example 'Invalid email' and 'Required'  

    let ForgotPasswordSchema = yup.object().shape({
      email: yup.string().email('Invalid email').required('Required'),
    });

In render method,

// As per your definition

isInvalid={(!!errors.email) && this.context.t(!!errors.email)}
invalidText={(errors.email) && this.context.t(errors.email)}

Translation File

export const translations = {
  "cy": {
    "Required":"Gofynnol",
    "Invalid email":"Nid yw'r cyfeiriad ebost yn ddilys",
}
 };  
Plume answered 30/7, 2019 at 9:44 Comment(0)
C
5

A solution will be to make a function that returns your validation schema. Then call that function in your component with the result memoized. This way, you are guaranteed that translations for validation messages are computed on the fly.

Another advantage here is you translate at the source of the message.

// Translation file
{
  "validation.invalid-email": "Email is invalid",
  "validation.field-required": "Field is required"
}


// Validation schema
const forgotPasswordSchema = () => {
  return yup.object().shape({
    email: yup
      .string()
      .email(i18n.t('validation.invalid-email'))
      .required(i18n.t('validation.field-required')),
  });
};


// Your component
const FormComponent = () => {
  const schema = useMemo(() => forgotPasswordSchema(), [i18n.language]); // NB: `[i18n.language]` is optional and `[]` will suffice depending on how you're handling language change

  return <>...</>;
}
Chaotic answered 24/11, 2021 at 14:25 Comment(1)
It does not work just after changing the language.Definite
D
1

I've created a few custom hooks for this approach

This one to refresh error messages inside schema when is changing app language

import { yupResolver } from '@hookform/resolvers/yup';
import { useRouter } from 'next/router';
import { useMemo } from 'react';

const useSchema = (getSchema) => {
  const { locale } = useRouter();
  const resolver = useMemo(getSchema, [locale]);

  return yupResolver(resolver);
};

export default useSchema;

And this one to set global in App component localised error messages

import { useTranslation } from 'react-i18next';
import { setLocale } from 'yup';

export const useLocalisedYupSchema = () => {
  const { t } = useTranslation('common');

  setLocale({
    mixed: {
      required: t('validation.required')
    },
    string: {
      min: ({ min }) => t('validation.min', { min }),
      max: ({ max }) => t('validation.max', { max })
    },
  });
};

Also usage of schemas inside component with React Hook Form

import { getChangePasswordSchema } from 'static/schemas/changePassword';
import useSchema from 'utils/hooks/useSchema';
import { useForm } from 'react-hook-form';

const AccountContentSecurity = () => {
  ...
  const resolver = useSchema(getChangePasswordSchema);
  const { reset, control, handleSubmit } = useForm({
    defaultValues: {
      'current_password': '',
      'new_password': '',
      'password_confirmation': '',
    },  
    resolver,
  });
  ...

and schema

import { passwordSchema } from 'static/schemas';
import { object } from 'yup';

export const getChangePasswordSchema = () => object({
  'current_password': passwordSchema,
  'new_password': passwordSchema,
  'password_confirmation': passwordSchema,
});
Darien answered 15/2, 2023 at 21:44 Comment(0)
A
0

I encountered this problem when using react-hook-form, I managed to solve the problem by adding useEffect to the form processing hook

export type LoginFormType = {
  captcha: string
  email: string
  password: string
}

export const getLoginFormSchema = (): ObjectSchema<LoginFormType> =>
  yup.object({
    captcha: yup.string().required(i18n.t('Enter code from image')).trim(),
    email: yup
      .string()
      .required(i18n.t('Enter your email'))
      .matches(emailRegex, i18n.t('Enter a valid email address'))
      .trim(),
    password: yup.string().required(i18n.t('Enter the password')).trim(),
  })

export const defaultValues: LoginFormType = {
  captcha: '',
  email: '',
  password: '',
}

export const useLoginForm = (defValue?: Partial<LoginFormType>): UseFormReturn<LoginFormType> => {
  const schema = useMemo(() => getLoginFormSchema(), [i18n.language])

  const form = useForm({
    defaultValues: {
      ...defaultValues,
      ...defValue,
    },
    mode: 'onSubmit',
    resolver: yupResolver(schema),
  })

  useEffect(() => {
    if (form.formState.isSubmitted) {
      form.trigger()
    }
  }, [i18n.language])

  return form
}
Agma answered 16/11, 2023 at 8:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.