Локализация ошибок в 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 и т.д.), что позволяет точно управлять
текстом.
При работе с большими схемами удобнее централизованно задавать переводы:
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}`);
});
});
Этот подход позволяет внедрять:
При масштабной интернационализации используется внешний словарь:
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])
});
Такой подход позволяет динамически переключать язык без изменения схемы.
В реальных приложениях 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 выступает исключительно как валидатор структуры.
Для REST и GraphQL API локализованные ошибки часто преобразуются в единый формат:
function formatError(err) {
return {
code: err.type,
field: err.path.join('.'),
message: err.message
};
}
Это обеспечивает независимость клиентской части от языка сервера.
messages(){#limit},
{#value}label()error()