Множественные языки

Многоязычная валидация в связке Yup Resolver строится вокруг двух ключевых механизмов: локализации сообщений самой схемы Yup и передачи языкового контекста через resolver в момент выполнения проверки формы. При правильной архитектуре система валидации перестаёт быть статической и превращается в динамический слой, адаптирующийся к текущей локали интерфейса.

Библиотека Yup изначально ориентирована на англоязычные сообщения об ошибках, но поддерживает переопределение сообщений на уровне глобальной конфигурации и на уровне отдельных схем.

Глобальная локализация через setLocale

Базовый механизм — setLocale, который позволяет задать словарь сообщений для всех типов ошибок:

import * as Yup from 'yup';

Yup.setLocale({
  mixed: {
    required: 'Поле обязательно для заполнения',
    notType: 'Неверный тип значения'
  },
  string: {
    min: 'Минимальная длина ${min} символов',
    max: 'Максимальная длина ${max} символов'
  },
  number: {
    min: 'Минимальное значение ${min}',
    max: 'Максимальное значение ${max}'
  }
});

Этот подход создаёт единый язык ошибок для всей системы, но имеет ограничение: переключение языка во время работы приложения требует повторного вызова setLocale, что может привести к конфликтам в многопользовательских сценариях или при SSR.

Архитектура YupResolver и языковой контекст

Resolver в React Hook Form выполняет функцию посредника между схемой Yup и системой форм. Его задача — преобразовать результат валидации в структуру ошибок, понятную форме.

Ключевой момент: resolver может получать context, который передаётся в Yup-схему.

const resolver = yupResolver(schema, { context: { lang: 'ru' } });

Это открывает возможность построения многоязычных схем без глобальных побочных эффектов.

Динамические сообщения через context

Yup поддерживает функцию валидации сообщений, что позволяет использовать context внутри схемы.

import * as Yup from 'yup';

const schema = Yup.object({
  username: Yup.string()
    .required(({ path, context }) => {
      return context?.lang === 'ru'
        ? 'Имя пользователя обязательно'
        : 'Username is required';
    })
});

Такой подход переводит ответственность за локализацию с глобального уровня на уровень конкретного поля.

Стратегии организации многоязычных схем

1. Фабрика схем

Наиболее устойчивый подход — генерация схемы на основе текущего языка:

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.

2. Использование словарей переводов

Более масштабируемая модель строится вокруг централизованных словарей:

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'))
  });

Интеграция с i18n библиотеками

При использовании систем вроде 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
  }
};

Ошибки архитектуры многоязычных схем

1. Глобальный setLocale в динамических приложениях

Переключение языка через setLocale в SPA приводит к состоянию гонки, когда разные части приложения могут использовать разные языки одновременно.

2. Захардкоженные строки в схемах

Смешивание логики и текста снижает переиспользуемость схем и усложняет поддержку.

3. Отсутствие context в resolver

Игнорирование context приводит к необходимости пересоздавать схемы даже там, где достаточно параметризации сообщений.

Оптимальная модель многоязычности

Комбинированная архитектура обычно включает:

  • фабрику схем (schema factory)
  • контекст языка через resolver
  • словарь переводов вне Yup
  • локальные функции сообщений вместо глобального setLocale
  • пересоздание resolver при смене языка
const resolver = yupResolver(
  createSchema(lang),
  { context: { lang } }
);

Такая структура сохраняет предсказуемость валидации и исключает глобальные побочные эффекты, сохраняя строгую изоляцию языковой логики от бизнес-правил схем.