Валидационные сообщения в Vest строятся вокруг принципа декларативных правил, где каждое правило может возвращать текст ошибки. Поддержка многоязычности в такой модели не сводится к простой подмене строк — она требует архитектурного разделения логики валидации и слоя представления сообщений.
Основная проблема интернационализации валидации заключается в том,
что сообщения об ошибках часто оказываются «зашиты» в бизнес-логику
правил. В Vest это решается через функцию test, где
сообщение может формироваться динамически, а также через внешние словари
локализации.
Базовый подход строится на разделении:
Такой подход позволяет избегать дублирования логики и упрощает добавление новых языков без изменения самих правил.
Ключи ошибок являются центральным элементом интернационализации. Вместо возврата готового текста правило возвращает идентификатор:
import { test, enforce } from "vest";
test("username", "required", () => {
enforce(data.username).isNotEmpty();
});
Здесь "required" — это не текст, а код ошибки. На уровне
интерфейса этот код преобразуется в строку на нужном языке.
Преимущество такого подхода заключается в том, что логика валидации не зависит от языка отображения.
Слой локализации представляет собой словарь, сопоставляющий ключи и строки:
const messages = {
en: {
required: "Field is required",
min_length: "Minimum length is not met"
},
ru: {
required: "Поле обязательно для заполнения",
min_length: "Не достигнута минимальная длина"
}
};
Выбор языка выполняется на уровне отображения результатов валидации:
function translate(error, lang = "en") {
return messages[lang][error.key] || error.key;
}
Такой механизм позволяет централизованно управлять всеми текстами без вмешательства в правила.
Часто ошибки требуют контекстных данных: минимальной длины, допустимого диапазона или значения ограничения. В этом случае используется параметризация.
test("password", "min_length", () => {
enforce(data.password).longerThan(8);
});
Ошибка может содержать метаданные:
{
key: "min_length",
meta: {
min: 8
}
}
Локализационный слой использует эти данные:
const messages = {
ru: {
min_length: ({ min }) => `Минимальная длина — ${min} символов`
}
};
Функциональный формат позволяет строить гибкие сообщения без усложнения логики правил.
В приложениях часто требуется поддержка нескольких языков одновременно, например в многошаговых формах или административных панелях. В таком случае язык передаётся как часть контекста обработки результатов.
function formatErrors(errors, lang) {
return errors.map(err => ({
field: err.field,
message: messages[lang][err.key](err.meta)
}));
}
Такой подход делает систему независимой от глобальных настроек и позволяет отображать разные языки для разных пользователей одновременно.
При масштабировании системы валидации словари переводов выделяются в отдельные модули. Это позволяет использовать одинаковые ключи ошибок в разных приложениях:
export const validationDictionary = {
required: "required",
min_length: "min_length",
email: "email_invalid"
};
Правила начинают ссылаться только на ключи:
test("email", validationDictionary.email, () => {
enforce(data.email).matches(/.+@.+\..+/);
});
Это устраняет зависимость от конкретного языка и упрощает поддержку библиотек.
При наличии большого количества модулей валидации словари разбиваются на домены:
Объединение выполняется на уровне конфигурации приложения:
const messages = {
ru: {
...commonRu,
...formRu,
...businessRu
}
};
Такая композиция уменьшает связность и облегчает расширение системы.
В некоторых языках требуется различная форма слов в зависимости от числа. Это решается через функции форматирования:
const pluralize = (count) =>
count === 1 ? "символ" : "символов";
Использование в сообщениях:
min_length: ({ min }) => `Минимум ${min} ${pluralize(min)}`
Для сложных языков подключаются специализированные библиотеки ICU-форматирования.
Перевод сообщений часто откладывается до момента отображения, а не выполняется в момент валидации. Это позволяет:
Структура ошибки остаётся неизменной:
{
key: "required",
field: "username"
}
Локализация применяется только на этапе рендера.
В крупных приложениях система сообщений интегрируется с библиотеками интернационализации, такими как ICU MessageFormat-подобные решения. В этом случае ключ ошибки становится частью общего пространства переводов приложения.
intl.formatMessage({ id: error.key }, error.meta);
Это позволяет унифицировать валидацию и интерфейсные сообщения в одной системе локализации.
При росте количества форм и правил становится критичным отсутствие текстов внутри валидаторов. Любое включение строк напрямую в правила приводит к:
Поэтому в архитектуре Vest многоязычность строится вокруг неизменяемых ключей и внешних словарей, а не вокруг строковых сообщений внутри тестов.