Локализация ошибок

Локализация ошибок в Joi

Валидация данных в приложениях на Node.js часто сопровождается необходимостью отображать сообщения об ошибках на разных языках. Библиотека Joi предоставляет гибкий механизм управления текстами ошибок, позволяя адаптировать их под требования интернационализации (i18n), бизнес-логику и формат API-ответов.


В Joi каждая ошибка валидации описывается объектом ValidationError, содержащим массив details. Каждый элемент включает:

  • message — текст ошибки
  • path — путь к полю
  • type — тип нарушения правила
  • context — дополнительные данные

По умолчанию сообщения генерируются на английском языке и зависят от типа правила:

import Joi fr om 'joi';

const schema = Joi.object({
  age: Joi.number().min(18).required()
});

const result = schema.validate({ age: 12 });

console.log(result.error.details[0].message);
// "age must be greater than or equal to 18"

Переопределение сообщений на уровне правил

Самый прямой способ локализации — использование метода messages() на уровне конкретного правила.

const schema = Joi.object({
  age: Joi.number()
    .min(18)
    .messages({
      'number.min': 'Возраст должен быть не меньше 18 лет',
      'number.base': 'Возраст должен быть числом'
    })
});

Каждое правило имеет собственный ключ ошибки (number.min, string.empty, any.required и т.д.), что позволяет точно управлять текстом.


Глобальная локализация через messages на схеме

При работе с большими схемами удобнее централизованно задавать переводы:

const schema = Joi.object({
  username: Joi.string().min(3).required(),
  password: Joi.string().min(6).required()
}).messages({
  'string.min': 'Значение слишком короткое',
  'any.required': 'Поле обязательно для заполнения'
});

Такой подход задаёт единый словарь сообщений для всей схемы.


Использование контекста в сообщениях

Joi позволяет внедрять динамические значения через context. Это важно для локализации, так как сообщения могут содержать параметры:

const schema = Joi.object({
  password: Joi.string().min(8).messages({
    'string.min': 'Пароль должен содержать минимум {#limit} символов'
  })
});

Здесь limit автоматически подставляется из контекста ошибки.

Дополнительно доступны:

  • {#value} — текущее значение
  • {#label} — имя поля
  • {#key} — ключ объекта

Переименование полей для локализации

Локализованные сообщения часто требуют корректных названий полей. Для этого используется label():

const schema = Joi.object({
  firstName: Joi.string().required().label('Имя'),
  lastName: Joi.string().required().label('Фамилия')
});

Теперь сообщения будут содержать локализованные названия:

"Имя" is required

При необходимости можно также переопределять форматирование глобально через errors().


Глобальная трансформация сообщений

Joi поддерживает постобработку ошибок через функцию errors():

const schema = Joi.object({
  age: Joi.number().min(18)
}).error(errors => {
  return errors.map(err => {
    return new Error(`Ошибка в поле ${err.path.join('.')}: ${err.message}`);
  });
});

Этот подход позволяет внедрять:

  • централизованную локализацию
  • форматирование под API
  • унификацию структуры ошибок

Подключение словарей переводов

При масштабной интернационализации используется внешний словарь:

const messages = {
  ru: {
    'string.empty': 'Поле не должно быть пустым',
    'string.email': 'Некорректный email'
  },
  en: {
    'string.empty': 'Field must not be empty',
    'string.email': 'Invalid email'
  }
};

const locale = 'ru';

const schema = Joi.object({
  email: Joi.string().email().messages(messages[locale])
});

Такой подход позволяет динамически переключать язык без изменения схемы.


Интеграция с системой i18n

В реальных приложениях Joi часто интегрируется с библиотеками интернационализации, например i18next или custom translation services.

Пример адаптера:

function translateJoiErrors(errors, t) {
  return errors.details.map(err => ({
    field: err.path.join('.'),
    message: t(err.type, err.context)
  }));
}

Где t — функция перевода:

t('string.min', { lim it: 5 })

Форматирование сложных ошибок

При глубокой вложенности объектов полезно нормализовать путь и применять локализацию к каждому уровню:

const schema = Joi.object({
  user: Joi.object({
    profile: Joi.object({
      age: Joi.number().min(18)
    })
  })
});

Ошибка будет иметь путь user.profile.age, что позволяет строить локализованные структуры вида:

Пользователь → Профиль → Возраст: значение слишком маленькое

Отключение стандартных сообщений

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

const schema = Joi.object({
  email: Joi.string().email().required().messages({
    'string.email': '',
    'string.empty': ''
  })
});

В таких случаях используется только внешний слой локализации, а Joi выступает исключительно как валидатор структуры.


Использование меток для API-ответов

Для REST и GraphQL API локализованные ошибки часто преобразуются в единый формат:

function formatError(err) {
  return {
    code: err.type,
    field: err.path.join('.'),
    message: err.message
  };
}

Это обеспечивает независимость клиентской части от языка сервера.


Ключевые аспекты локализации

  • сообщения задаются через messages()
  • поддерживаются параметры контекста {#limit}, {#value}
  • имена полей задаются через label()
  • возможна глобальная трансформация через error()
  • допускается интеграция с внешними i18n-системами
  • структура ошибок остаётся неизменной, меняется только представление