Многоязычная валидация в связке Yup Resolver строится вокруг двух ключевых механизмов: локализации сообщений самой схемы Yup и передачи языкового контекста через resolver в момент выполнения проверки формы. При правильной архитектуре система валидации перестаёт быть статической и превращается в динамический слой, адаптирующийся к текущей локали интерфейса.
Библиотека Yup изначально ориентирована на англоязычные сообщения об ошибках, но поддерживает переопределение сообщений на уровне глобальной конфигурации и на уровне отдельных схем.
Базовый механизм — setLocale, который позволяет задать
словарь сообщений для всех типов ошибок:
import * as Yup from 'yup';
Yup.setLocale({
mixed: {
required: 'Поле обязательно для заполнения',
notType: 'Неверный тип значения'
},
string: {
min: 'Минимальная длина ${min} символов',
max: 'Максимальная длина ${max} символов'
},
number: {
min: 'Минимальное значение ${min}',
max: 'Максимальное значение ${max}'
}
});
Этот подход создаёт единый язык ошибок для всей системы, но имеет
ограничение: переключение языка во время работы приложения требует
повторного вызова setLocale, что может привести к
конфликтам в многопользовательских сценариях или при SSR.
Resolver в React Hook Form выполняет функцию посредника между схемой Yup и системой форм. Его задача — преобразовать результат валидации в структуру ошибок, понятную форме.
Ключевой момент: resolver может получать context,
который передаётся в Yup-схему.
const resolver = yupResolver(schema, { context: { lang: 'ru' } });
Это открывает возможность построения многоязычных схем без глобальных побочных эффектов.
Yup поддерживает функцию валидации сообщений, что позволяет
использовать context внутри схемы.
import * as Yup from 'yup';
const schema = Yup.object({
username: Yup.string()
.required(({ path, context }) => {
return context?.lang === 'ru'
? 'Имя пользователя обязательно'
: 'Username is required';
})
});
Такой подход переводит ответственность за локализацию с глобального уровня на уровень конкретного поля.
Наиболее устойчивый подход — генерация схемы на основе текущего языка:
const createSchema = (lang) => {
return Yup.object({
email: Yup.string()
.email(lang === 'ru' ? 'Некорректный email' : 'Invalid email')
.required(lang === 'ru' ? 'Email обязателен' : 'Email is required')
});
};
Использование:
const schema = createSchema(currentLang);
const resolver = yupResolver(schema);
Этот подход полностью изолирует языковую логику от runtime Yup.
Более масштабируемая модель строится вокруг централизованных словарей:
const messages = {
ru: {
required: 'Обязательное поле',
email: 'Некорректный email'
},
en: {
required: 'Required field',
email: 'Invalid email'
}
};
И функция доступа:
const t = (lang, key) => messages[lang][key];
Схема:
const schema = (lang) =>
Yup.object({
email: Yup.string()
.email(t(lang, 'email'))
.required(t(lang, 'required'))
});
При использовании систем вроде i18next или аналогов Yup не должен напрямую знать о переводах. Он получает уже готовую строку.
import i18n from 'i18next';
const schema = Yup.object({
password: Yup.string()
.min(8, () => i18n.t('validation.passwordMin'))
.required(() => i18n.t('validation.required'))
});
Такой подход отделяет слой валидации от слоя интернационализации, снижая связанность.
Проблема динамического переключения языка заключается в том, что Yup схема обычно создаётся один раз. Для корректной реакции на смену языка требуется пересоздание resolver.
const schema = useMemo(() => createSchema(lang), [lang]);
const resolver = useMemo(
() => yupResolver(schema),
[schema]
);
Таким образом, изменение языка приводит к пересборке схемы и всех сообщений об ошибках.
Yup позволяет использовать интерполяцию значений:
Yup.string()
.min(5, 'Минимум ${min} символов')
.max(20, 'Максимум ${max} символов');
В многоязычной системе интерполяция комбинируется с переводами:
const t = (lang) => ({
min: lang === 'ru'
? 'Минимум ${min} символов'
: 'At least ${min} characters'
});
При валидации зависимых полей важно учитывать, что сообщение может зависеть от нескольких факторов:
Yup.string().test('match-lang', function (value) {
const { lang } = this.options.context || {};
if (!value) {
return this.createError({
message: lang === 'ru'
? 'Значение отсутствует'
: 'Value is missing'
});
}
return true;
});
При серверной проверке сообщения могут приходить в исходном языке API и требовать трансформации:
Yup.string().test('server-check', async function (value) {
const { lang } = this.options.context;
const error = await apiValidate(value);
if (error) {
return this.createError({
message: lang === 'ru'
? 'Ошибка сервера'
: error.message
});
}
return true;
});
В больших приложениях многоязычность часто сочетается с модульной архитектурой схем:
/validation
/auth
schema.en.js
schema.ru.js
/profile
schema.en.js
schema.ru.js
Или более гибко:
const schemas = {
auth: {
en: authSchemaEn,
ru: authSchemaRu
}
};
Переключение языка через setLocale в SPA приводит к
состоянию гонки, когда разные части приложения могут использовать разные
языки одновременно.
Смешивание логики и текста снижает переиспользуемость схем и усложняет поддержку.
Игнорирование context приводит к необходимости
пересоздавать схемы даже там, где достаточно параметризации
сообщений.
Комбинированная архитектура обычно включает:
const resolver = yupResolver(
createSchema(lang),
{ context: { lang } }
);
Такая структура сохраняет предсказуемость валидации и исключает глобальные побочные эффекты, сохраняя строгую изоляцию языковой логики от бизнес-правил схем.