Сообщения и локализация
У каждой ошибки есть код (например, minLength) и сообщение. Сообщение можно задать для конкретного правила, для всего приложения или подключить готовую локализацию.
Сообщение для конкретного правила
Последний аргумент любой схемы и правила — сообщение:
string('Введите имя')
pipe(string(), minLength(8, 'Пароль слишком короткий'))Для схем (string(), object()…) сообщение заменяет все их ошибки — и «обязательное поле», и «неверный тип».
Параметры в тексте
В тексте можно использовать параметры правила в фигурных скобках:
minLength(3, 'Минимум {min} символа') // → «Минимум 3 символа»
oneOf(['a', 'b'], 'Выберите: {options}') // → «Выберите: a, b»Если нужна логика (например, склонение), передайте функцию — она получит параметры:
const plural = (n: number) => (n % 10 === 1 && n % 100 !== 11 ? 'символ' : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 'символа' : 'символов');
minLength(5, (p) => `Минимум ${p.min} ${plural(p.min as number)}`) // → «Минимум 5 символов»Склонения: «3 символа», «5 символов»
Форма слова, зависящая от числа, пишется в фигурных скобках через |: сначала параметр, потом формы. Форма выбирается по правилам языка (Intl.PluralRules), язык берётся из ключа $locale в lang-файле:
setMessages({
minLength: 'Минимум {min} {min|символ|символа|символов}',
});
// 1 символ, 2 символа, 5 символов, 21 символ| Язык | Сколько форм | Пример |
|---|---|---|
| Русский, украинский | 3: одна, несколько, много | {n|файл|файла|файлов} |
| Английский, немецкий | 2: одна, много | {n|file|files} |
| Китайский, японский | 1 | {n|个} или просто текст без форм |
Встроенные русская и английская локали уже используют склонения. Если форм указано меньше, чем нужно языку, берётся последняя.
Сообщения для всего приложения
setMessages меняет тексты по умолчанию. Вызовите один раз при старте приложения:
import { setMessages } from 'tinyval';
import ru from 'tinyval/locales/ru';
setMessages(ru); // всё на русском
setMessages({ required: 'Заполните поле' }); // переопределить отдельные текстыЯзык для одного вызова
setMessages действует на всё приложение. На сервере, где у каждого запроса свой язык, передайте сообщения в сам вызов — они не затронут другие запросы:
import ru from 'tinyval/locales/ru';
import en from 'tinyval/locales/en';
const locales = { ru, en };
app.post('/users', (req, res) => {
const lang = req.acceptsLanguages('ru', 'en') || 'en';
const result = validate(CreateUser, req.body, { messages: locales[lang] });
});Сообщения вызова можно передать частично — недостающие возьмутся из setMessages.
Название поля — {label}
В любом сообщении доступен параметр {label} — как :attribute в Laravel. По умолчанию это имя поля (email), а своё название задаёт label или опция labels:
const Signup = object({
email: label(pipe(string(), email()), 'Почта'),
password: label(pipe(string(), minLength(8)), 'Пароль'),
});
setMessages({ required: 'Поле «{label}» обязательно', minLength: '{label}: минимум {min} символов' });
validate(Signup, { password: '123' }).errors;
// { email: 'Поле «Почта» обязательно', password: 'Пароль: минимум 8 символов' }Перевод на клиенте — params
Если сервер не знает язык пользователя, можно отдать клиенту коды и параметры ошибок, а текст собрать на фронтенде:
validate(Signup, body).issues;
// [{ code: 'minLength', params: { min: 8 }, path: ['password'], message: '...' }]Локализации
| Импорт | Язык |
|---|---|
tinyval/locales/en | Английский (по умолчанию) |
tinyval/locales/ru | Русский |
tinyval/locales/zh | Китайский (упрощённый) |
setMessages(en) возвращает английские тексты.
Чтобы поменять тексты под себя, скопируйте lang-файл в проект командой npx tinyval lang ru — см. Свой lang-файл.
Все коды ошибок
| Код | Где возникает | По умолчанию (en) | В tinyval/locales/ru |
|---|---|---|---|
invalid | check, checkAsync | Invalid value | Некорректное значение |
required | пустое значение в любой схеме | Required | Обязательное поле |
string | string | Must be a string | Должно быть строкой |
number | number | Must be a number | Должно быть числом |
boolean | boolean | Must be a boolean | Должно быть логическим значением |
date | date | Must be a valid date | Некорректная дата |
object | object | Must be an object | Должно быть объектом |
array | array | Must be an array | Должно быть списком |
literal | literal | Must be {expected} | Должно быть {expected} |
oneOf | oneOf, enumOf | Must be one of: {options} | Допустимые значения: {options} |
union | union | Does not match any allowed type | Значение не подходит ни под один из вариантов |
variant | variant | Unknown type, expected one of: {options} | Неизвестный тип, допустимые: {options} |
depth | lazy | Too deeply nested (max {max}) | Слишком глубокая вложенность (максимум {max}) |
unknownKey | object с unknown: 'strict' | Unknown field | Неизвестное поле |
minLength | minLength у строки | Must be at least {min} {min|character|characters} long | Минимум {min} {min|символ|символа|символов} |
maxLength | maxLength у строки | Must be at most {max} {max|character|characters} long | Максимум {max} {max|символ|символа|символов} |
length | length у строки | Must be exactly {length} {length|character|characters} long | Должно быть {length} {length|символ|символа|символов} |
min | min | Must be at least {min} | Не меньше {min} |
max | max | Must be at most {max} | Не больше {max} |
integer | integer | Must be an integer | Должно быть целым числом |
pattern | pattern | Invalid format | Неверный формат |
email | email | Invalid email | Некорректный email |
minItems | minLength у массива | Must have at least {min} {min|item|items} | Минимум {min} {min|элемент|элемента|элементов} |
maxItems | maxLength у массива | Must have at most {max} {max|item|items} | Максимум {max} {max|элемент|элемента|элементов} |
items | length у массива, tuple | Must have exactly {length} {length|item|items} | Должно быть {length} {length|элемент|элемента|элементов} |
between | between | Must be between {min} and {max} | От {min} до {max} |
url | url | Invalid URL | Некорректная ссылка |
uuid | uuid | Invalid UUID | Некорректный UUID |
startsWith | startsWith | Must start with "{value}" | Должно начинаться с «{value}» |
endsWith | endsWith | Must end with "{value}" | Должно заканчиваться на «{value}» |
includes | includes | Must contain "{value}" | Должно содержать «{value}» |
unique | unique | Must not contain duplicates | Значения не должны повторяться |
minDate | minDate | Must be on or after {date} | Не раньше {date} |
maxDate | maxDate | Must be on or before {date} | Не позже {date} |
after | after | Must be after {field} | Должно быть позже, чем {field} |
before | before | Must be before {field} | Должно быть раньше, чем {field} |
inn | inn | Invalid INN | Некорректный ИНН |
snils | snils | Invalid SNILS | Некорректный СНИЛС |
luhn | luhn | Invalid card number | Некорректный номер карты |
prohibited | prohibitedIf | Must be empty | Поле должно быть пустым |
file | file | Must be a file | Выберите файл |
fileSize | file — maxSize | File must be at most {max} | Файл не больше {max} |
fileType | file — types | File type must be one of: {types} | Допустимые типы файлов: {types} |
sameAs | sameAs | Must match {field} | Значения не совпадают |
Своё сообщение с параметрами, которых нет в таблице, тоже работает — неизвестный {параметр} остаётся в тексте как есть.
См. также
- setMessages
- ValidationError — как сообщения попадают в исключение
- Свои правила и схемы — свои коды ошибок