Библиотека Yup позволяет централизованно управлять текстами ошибок валидации. Локализация особенно важна в крупных приложениях, где:
По умолчанию Yup возвращает сообщения на английском языке:
import * as yup from 'yup';
const schema = yup.object({
email: yup.string().email().required(),
});
Результат:
email must be a valid email
или:
email is a required field
Для полноценной локализации используется механизм
setLocale.
Метод setLocale() изменяет глобальные сообщения
библиотеки.
import * as yup from 'yup';
yup.setLocale({
mixed: {
required: 'Поле обязательно',
},
});
Теперь все схемы будут использовать новое сообщение:
const schema = yup.object({
name: yup.string().required(),
});
Ошибка:
Поле обязательно
Объект локализации делится по типам схем:
yup.setLocale({
mixed: {},
string: {},
number: {},
date: {},
boolean: {},
array: {},
object: {},
});
Каждый раздел содержит набор правил валидации.
mixed содержит общие сообщения, применимые ко всем типам
данных.
yup.setLocale({
mixed: {
required: 'Это поле обязательно',
},
});
Пример:
const schema = yup.string().required();
Используется при ограничении допустимых значений.
yup.setLocale({
mixed: {
oneOf: 'Недопустимое значение',
},
});
Пример:
const schema = yup
.string()
.oneOf(['admin', 'user']);
yup.setLocale({
mixed: {
notOneOf: 'Значение запрещено',
},
});
yup.setLocale({
mixed: {
defined: 'Поле должно быть определено',
},
});
Сообщение по умолчанию для неизвестных ошибок.
yup.setLocale({
mixed: {
default: 'Некорректное значение',
},
});
Раздел string отвечает за строковые значения.
yup.setLocale({
string: {
min: 'Минимум ${min} символов',
},
});
Пример:
const schema = yup.string().min(5);
Ошибка:
Минимум 5 символов
yup.setLocale({
string: {
max: 'Максимум ${max} символов',
},
});
yup.setLocale({
string: {
email: 'Некорректный email',
},
});
yup.setLocale({
string: {
url: 'Некорректный URL',
},
});
yup.setLocale({
string: {
matches: 'Неверный формат',
},
});
yup.setLocale({
string: {
length: 'Длина должна быть ${length} символов',
},
});
yup.setLocale({
string: {
lowercase: 'Только строчные буквы',
uppercase: 'Только заглавные буквы',
},
});
yup.setLocale({
string: {
trim: 'Уберите пробелы в начале и конце',
},
});
yup.setLocale({
number: {
min: 'Минимальное значение: ${min}',
max: 'Максимальное значение: ${max}',
},
});
yup.setLocale({
number: {
integer: 'Требуется целое число',
},
});
yup.setLocale({
number: {
positive: 'Число должно быть положительным',
negative: 'Число должно быть отрицательным',
},
});
yup.setLocale({
number: {
lessThan: 'Значение должно быть меньше ${less}',
moreThan: 'Значение должно быть больше ${more}',
},
});
yup.setLocale({
date: {
min: 'Дата должна быть позже ${min}',
},
});
yup.setLocale({
date: {
max: 'Дата должна быть раньше ${max}',
},
});
yup.setLocale({
array: {
min: 'Минимум элементов: ${min}',
},
});
yup.setLocale({
array: {
max: 'Максимум элементов: ${max}',
},
});
yup.setLocale({
array: {
length: 'Количество элементов должно быть ${length}',
},
});
yup.setLocale({
boolean: {
isValue: 'Неверное логическое значение',
},
});
Yup поддерживает интерполяцию значений через ${}.
Пример:
yup.setLocale({
string: {
min: 'Минимум ${min} символов',
},
});
Доступные параметры зависят от типа проверки.
${min}
${max}
${length}
${values}
Пример:
yup.setLocale({
mixed: {
oneOf: 'Допустимые значения: ${values}',
},
});
Сообщения можно генерировать динамически.
yup.setLocale({
string: {
min: ({ min }) => `Минимум ${min} символов`,
},
});
Полезно при интеграции с системами i18n.
yup.setLocale({
mixed: {
required: () => ({
key: 'validation.required',
}),
},
});
Популярный вариант локализации — связка Yup и i18next.
import i18next from 'i18next';
import * as yup from 'yup';
yup.setLocale({
mixed: {
required: () => i18next.t('validation.required'),
},
string: {
email: () => i18next.t('validation.email'),
},
});
{
"validation": {
"required": "Поле обязательно",
"email": "Введите корректный email"
}
}
При использовании React и react-i18next локализацию часто выносят в отдельный модуль.
// validationLocale.js
import * as yup from 'yup';
import i18n from './i18n';
export function setupYupLocale() {
yup.setLocale({
mixed: {
required: () => i18n.t('required'),
},
string: {
email: () => i18n.t('email'),
},
});
}
Проблема глобальной локализации заключается в том, что
setLocale() меняет сообщения сразу для всех схем.
При смене языка требуется повторная инициализация.
import i18n from './i18n';
i18n.on('languageChanged', () => {
setupYupLocale();
});
Глобальная локализация не обязательна.
Сообщения можно задавать локально:
const schema = yup.object({
password: yup
.string()
.required('Введите пароль')
.min(8, 'Минимум 8 символов'),
});
Локальное сообщение имеет приоритет.
yup.setLocale({
mixed: {
required: 'Поле обязательно',
},
});
const schema = yup.object({
email: yup
.string()
.required('Введите email'),
});
Результат:
Введите email
Крупные проекты часто выносят сообщения в отдельный файл.
// yupLocaleRu.js
export default {
mixed: {
required: 'Поле обязательно',
default: 'Некорректное значение',
},
string: {
email: 'Некорректный email',
min: 'Минимум ${min} символов',
},
number: {
min: 'Минимум ${min}',
},
};
Подключение:
import * as yup from 'yup';
import ruLocale from './yupLocaleRu';
yup.setLocale(ruLocale);
Структура каталогов:
/locales
/ru
yup.js
/en
yup.js
/de
yup.js
export default {
mixed: {
required: 'Поле обязательно',
},
};
export default {
mixed: {
required: 'Field is required',
},
};
async function loadLocale(lang) {
const locale = await import(`./locales/${lang}/yup.js`);
yup.setLocale(locale.default);
}
Пользовательские проверки через test() также
поддерживают локализацию.
const schema = yup.string().test(
'latin-only',
'Разрешены только латинские буквы',
value => /^[a-z]+$/i.test(value)
);
const schema = yup.string().test(
'password-strength',
({ value }) => `Пароль "${value}" слишком простой`,
value => validatePassword(value)
);
Для сложных сценариев применяется createError.
const schema = yup.string().test({
name: 'custom',
test(value, context) {
if (!value.startsWith('A')) {
return context.createError({
message: 'Строка должна начинаться с A',
});
}
return true;
},
});
const schema = yup.object({
user: yup.object({
email: yup
.string()
.email()
.required(),
}),
});
Ошибка:
Некорректный email
Локализация применяется автоматически ко всем вложенным схемам.
Formik тесно интегрируется с Yup.
const validationSchema = yup.object({
email: yup
.string()
.email()
.required(),
});
После настройки setLocale() ошибки автоматически
отображаются на нужном языке.
При использовании React Hook Form вместе с
@hookform/resolvers/yup сообщения Yup также передаются
автоматически.
const schema = yup.object({
name: yup.string().required(),
});
setLocale() изменяет глобальную конфигурацию
библиотеки.
В SSR-приложениях это может привести к конфликтам между запросами пользователей с разными языками.
Вместо глобальной локализации можно хранить сообщения рядом со схемой.
function createSchema(t) {
return yup.object({
email: yup
.string()
.required(t('required'))
.email(t('email')),
});
}
В Next.js часто используют фабрики схем.
export const buildSchema = (t) =>
yup.object({
login: yup.string().required(t('required')),
});
Для единообразия сообщений удобно создавать набор констант.
export const validationMessages = {
required: 'Поле обязательно',
invalidEmail: 'Некорректный email',
};
export const messages = {
minLength: min => `Минимум ${min} символов`,
};
Использование:
yup.string().min(8, messages.minLength(8));
В TypeScript можно описать тип словаря локализации.
interface ValidationLocale {
mixed: {
required: string;
};
string: {
email: string;
};
}
import * as yup from 'yup';
yup.setLocale({
mixed: {
default: 'Некорректное значение',
required: 'Поле обязательно',
oneOf: 'Недопустимое значение',
notOneOf: 'Запрещённое значение',
},
string: {
length: 'Должно быть ${length} символов',
min: 'Минимум ${min} символов',
max: 'Максимум ${max} символов',
email: 'Некорректный email',
url: 'Некорректный URL',
trim: 'Удалите лишние пробелы',
lowercase: 'Только строчные буквы',
uppercase: 'Только заглавные буквы',
},
number: {
min: 'Минимум ${min}',
max: 'Максимум ${max}',
lessThan: 'Должно быть меньше ${less}',
moreThan: 'Должно быть больше ${more}',
positive: 'Введите положительное число',
negative: 'Введите отрицательное число',
integer: 'Введите целое число',
},
date: {
min: 'Дата слишком ранняя',
max: 'Дата слишком поздняя',
},
array: {
min: 'Минимум ${min} элементов',
max: 'Максимум ${max} элементов',
},
});