Messages
Pliant separates validation logic from message resolution. This makes it easy to support multiple languages, customize messages, and create dynamic content.
Creating a Message Resolver
ts
import { createMessageResolver } from '@pliant/core';
const messages = createMessageResolver({
required: 'This field is required',
email: 'Please enter a valid email',
length: 'Invalid length'
});Message Types
Static Strings
Simple string messages for straightforward errors:
ts
{
required: 'This field is required',
email: 'Invalid email format',
numeric: 'Must be a number'
}Dynamic Functions
Functions receive the error detail and can return contextual messages:
ts
{
length: (detail) => {
if (detail.actual < detail.min) {
return `Must be at least ${detail.min} characters`;
}
return `Must be no more than ${detail.max} characters`;
},
range: (detail) => `Must be between ${detail.min} and ${detail.max}`,
min: (detail) => `Must be at least ${detail.min}`,
max: (detail) => `Must be no more than ${detail.max}`
}Template Literals
Use template literals for clean, readable messages:
ts
{
length: (d) => `Must be ${d.min}-${d.max} characters (you entered ${d.actual})`,
range: ({ min, max, actual }) => `Value ${actual} is outside range ${min}-${max}`
}Applying Messages
Use applyMessages to add messages to error objects:
ts
import { applyMessages } from '@pliant/core';
const errors = evaluateRules(registry, '', {}, ['required', 'email']);
// { required: { code: 'required' } }
const resolved = applyMessages(errors, {}, messages);
// { required: { code: 'required', message: 'This field is required' } }Message Priority
Messages are resolved in this order:
- Rule-level message - Defined in the rule itself
- Resolver message - From
createMessageResolver - Fallback - Error code used as message
ts
// Rule with built-in message
addRules(registry, {
specialEmail: {
...emailRule(),
message: 'Please use your company email' // Highest priority
}
});
// Resolver message (used if no rule message)
const messages = createMessageResolver({
email: 'Invalid email format'
});i18n Support
Simple Approach
Create message resolvers per language:
ts
const en = createMessageResolver({
required: 'This field is required',
email: 'Invalid email'
});
const es = createMessageResolver({
required: 'Este campo es obligatorio',
email: 'Correo electrónico inválido'
});
// Use based on locale
const messages = locale === 'es' ? es : en;With i18n Library
Integrate with your i18n solution:
ts
import { t } from 'your-i18n-library';
const messages = createMessageResolver({
required: () => t('validation.required'),
email: () => t('validation.email'),
length: (d) => t('validation.length', { min: d.min, max: d.max })
});Error Codes for Lookup
Use error codes as translation keys:
ts
// errors.json
{
"required": "This field is required",
"email": "Please enter a valid email",
"length": "Must be {min}-{max} characters"
}
// Usage
const messages = createMessageResolver(
Object.fromEntries(
Object.entries(translations).map(([code, template]) => [
code,
(detail) => interpolate(template, detail)
])
)
);Field-Specific Messages
Override messages per field using context:
ts
const messages = createMessageResolver({
required: (detail, ctx) => {
if (ctx.field === 'email') return 'Email is required';
if (ctx.field === 'password') return 'Password is required';
return 'This field is required';
}
});Combining with Rule Messages
Rules can define their own messages:
ts
addRules(registry, {
passwordStrength: {
validate: (value) => {
if (!/[A-Z]/.test(value)) return { code: 'password_uppercase' };
if (!/[0-9]/.test(value)) return { code: 'password_number' };
return null;
},
message: (error) => {
if (error.code === 'password_uppercase') return 'Must contain uppercase';
if (error.code === 'password_number') return 'Must contain a number';
return 'Invalid password';
}
}
});