Локализация сообщений

Работа с формами в JavaScript-экосистеме часто строится вокруг валидации данных и отображения ошибок пользователю. В связке с react-hook-form одним из наиболее распространённых решений является YupResolver, который позволяет использовать схему валидации на базе библиотеки Yup. Ключевой аспект при построении прикладных интерфейсов — корректная локализация сообщений об ошибках, поскольку пользовательские интерфейсы редко ограничиваются одним языком.


Архитектура цепочки: Yup → Resolver → UI

Понимание локализации невозможно без осознания того, где именно формируются сообщения:

  1. Yup формирует ошибки валидации согласно описанной схеме.
  2. YupResolver преобразует ошибки Yup в формат, понятный react-hook-form.
  3. UI слой отображает текст ошибок пользователю.

Следовательно, локализация может быть реализована на нескольких уровнях:

  • на уровне схем Yup;
  • через динамическую конфигурацию сообщений;
  • через постобработку ошибок в resolver;
  • через внешнюю систему i18n.

Базовый механизм локализации в Yup

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

import * as Yup from 'yup';

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

Особенность этого подхода заключается в том, что сообщения становятся глобальными. Это удобно для небольших приложений, но создаёт ограничения при работе с мультиязычностью.


Локализация на уровне схемы

Более гибкий способ — определение сообщений непосредственно в схеме Yup. Это позволяет контролировать тексты локально и избегать глобальных побочных эффектов.

import * as Yup from 'yup';

const schema = Yup.object({
  email: Yup.string()
    .email('Введите корректный email')
    .required('Email обязателен'),

  password: Yup.string()
    .min(8, 'Пароль должен содержать минимум 8 символов')
    .required('Пароль обязателен'),
});

Такой подход предпочтителен, когда:

  • приложение поддерживает несколько языков;
  • требуется динамическая смена языка;
  • разные формы имеют разные стили сообщений.

Интеграция с YupResolver

YupResolver служит мостом между схемой Yup и react-hook-form. Он не занимается локализацией напрямую, а лишь передаёт уже сформированные сообщения.

import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';

const form = useForm({
  resolver: yupResolver(schema),
});

Важный момент: любые изменения в локализации должны происходить до передачи схемы в yupResolver. Resolver не трансформирует тексты ошибок.


Динамическая смена языка

В реальных приложениях часто используется i18n-библиотека (например, i18next). В этом случае возникает задача синхронизации языка Yup с текущей локалью приложения.

Один из подходов — пересоздание схемы при смене языка.

import * as Yup from 'yup';

const createSchema = (t) =>
  Yup.object({
    email: Yup.string()
      .email(t('validation.emailInvalid'))
      .required(t('validation.emailRequired')),

    password: Yup.string()
      .min(8, t('validation.passwordMin'))
      .required(t('validation.passwordRequired')),
  });

Далее схема пересоздаётся при изменении языка:

const schema = useMemo(() => createSchema(t), [t]);

Такой подход обеспечивает:

  • актуальные переводы без перезагрузки страницы;
  • отсутствие глобальных побочных эффектов;
  • гибкость в управлении текстами.

Использование i18n внутри Yup через фабрики сообщений

При масштабных приложениях часто используется централизованная фабрика сообщений:

const messages = {
  email: {
    invalid: (t) => t('validation.emailInvalid'),
    required: (t) => t('validation.emailRequired'),
  },
};

И затем схема строится на основе этих функций:

const createSchema = (t) =>
  Yup.object({
    email: Yup.string()
      .email(messages.email.invalid(t))
      .required(messages.email.required(t)),
  });

Такой подход позволяет:

  • стандартизировать сообщения;
  • избежать дублирования текста;
  • централизовать управление локализацией.

Локализация сложных типов валидации

Помимо базовых типов (string, number, mixed), Yup поддерживает сложные структуры:

Массивы

Yup.array()
  .of(Yup.string().required('Каждый элемент обязателен'))
  .min(1, 'Добавьте хотя бы один элемент');

Объекты

Yup.object({
  profile: Yup.object({
    firstName: Yup.string().required('Имя обязательно'),
    lastName: Yup.string().required('Фамилия обязательна'),
  }),
});

При локализации вложенных объектов важно сохранять единообразие ключей перевода, иначе поддержка становится затруднительной.


Форматирование сообщений с параметрами

Yup поддерживает подстановку параметров в строках:

  • ${min}
  • ${max}
  • ${path}
  • ${value}

Пример:

Yup.string()
  .min(5, 'Минимум ${min} символов')
  .max(20, 'Максимум ${max} символов');

При локализации важно учитывать, что порядок слов в разных языках может отличаться, поэтому простая строковая интерполяция иногда оказывается недостаточной.


Ошибки как структура данных

YupResolver возвращает ошибки в формате:

{
  email: {
    type: "required",
    message: "Email обязателен"
  }
}

Это означает, что локализация может быть реализована и на уровне отображения, если хранить ключи сообщений вместо готового текста:

{
  email: {
    type: "required",
    messageKey: "validation.emailRequired"
  }
}

И уже UI слой переводит ключ в текст:

t(error.messageKey)

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


Централизованная стратегия локализации

В крупных приложениях используется комбинированная стратегия:

  • Yup хранит ключи сообщений, а не текст;
  • Resolver передаёт структуру ошибок без изменений;
  • UI слой отвечает за перевод;
  • i18n управляет словарями.

Пример схемы:

Yup.string().required('validation.emailRequired');

UI:

const message = t(error.message);

Частые проблемы при локализации

Глобальные сообщения конфликтуют с i18n

Yup.setLocale может неожиданно перекрывать динамические переводы, если используется в мульти-язычных приложениях.

Потеря контекста языка

Если схема создаётся вне React-цикла, она может «запомнить» старый язык.

Разные форматы ошибок

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


Практическая стратегия выбора подхода

Выбор метода локализации зависит от масштаба:

  • небольшой проект → Yup.setLocale;
  • средний проект → сообщения в схеме;
  • крупный проект → ключи + i18n + динамическая схема;
  • enterprise → разделение слоёв: validation / resolver / UI translation.

Работа с контекстом локализации в resolver

Некоторые реализации позволяют передавать контекст в yupResolver:

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

Хотя Yup не использует этот контекст напрямую, его можно применять при построении схем через фабрику:

const createSchema = (lang) => {
  const t = translations[lang];

  return Yup.object({
    email: Yup.string().required(t.required),
  });
};

Итоговая модель взаимодействия

Локализация в связке Yup + YupResolver строится как многоуровневая система:

  • Yup отвечает за генерацию ошибок;
  • Resolver передаёт ошибки без трансформации текста;
  • i18n обеспечивает перевод;
  • UI отображает финальные сообщения.

Такая архитектура позволяет масштабировать систему валидации без привязки к конкретному языку и обеспечивает независимость логики и представления.