Messages & locales
Validation messages are templates. When a validator fails it renders a template into a string, substituting tokens drawn from the record and the validator options. The template is chosen in this order:
- An explicit
messageoption on the validator declaration. - A template registered for the current locale (see Locales).
- The built-in default for that error type.
Message tokens#
Every template is interpolated with the tokens below. An undefined token is left untouched, so a template that does not mention a token is unaffected.
| Token | Substituted with |
|---|---|
{model} |
The model's class name (e.g. User) |
{attribute} |
The attribute name, or the as label when one is set |
{value} |
The bound value for the failing validator (the length, the threshold) |
{count} |
The numeric bound for length, numericality, and comparison validators |
| any option | Any extra option passed to errors.add (for example {count}) |
use ORM::ActiveRecord::Model;
class User is Model {
submethod BUILD {
self.validate: 'name', { length => { max => 8 }, message => '{model}.{attribute} allows {count} characters' }
}
}
my $u = User.build({name => 'overlong'});
$u.is-valid;
say $u.errors.name[0]; # User.name allows 8 characters
Locales#
ORM::ActiveRecord::Support::I18n holds per-locale message templates and the
currently active locale. Register templates with store, then switch the active
locale with set-locale.
use ORM::ActiveRecord::Support::I18n;
I18n.store('fr', {
errors => {
messages => {
blank => 'doit être rempli',
'too-long' => 'est trop long (maximum {count} caractères)',
'greater-than' => 'doit être supérieur à {count}',
},
},
});
I18n.set-locale('fr');
Error types use the same hyphenated names the validators record: blank,
too-long, too-short, wrong-length, greater-than, less-than, taken,
accepted, confirmation, inclusion, exclusion, invalid, and the rest
listed in The errors collection.
Lookup order#
For a given model, attribute, and error type, the most specific registered template wins. The keys are searched in this order:
activerecord.errors.models.MODEL.attributes.ATTRIBUTE.TYPEactiverecord.errors.models.MODEL.TYPEactiverecord.errors.messages.TYPEerrors.attributes.ATTRIBUTE.TYPEerrors.messages.TYPE
MODEL is the lower-cased class name and ATTRIBUTE is the column name. A
model-and-attribute override lets one attribute read differently from the rest:
I18n.store('fr', {
activerecord => {
errors => {
models => {
user => {
attributes => {
email => { blank => "l'adresse e-mail est obligatoire" },
},
},
},
},
},
});
When no template is registered for the active locale, the lookup retries under
default-locale (also en by default), then falls back to the built-in
defaults. Nothing breaks if a locale is partially translated.
Switching locale for a block#
with-locale runs a block under a temporary locale and restores the previous
one afterwards, even if the block dies.
my @messages;
I18n.with-locale('fr', sub {
my $u = User.build({email => ''});
$u.is-valid;
@messages = $u.errors.full-messages;
});
Managing the registry#
I18n.locale; # current locale, default 'en'
I18n.set-locale('de'); # set the active locale
I18n.default-locale; # fallback locale, default 'en'
I18n.set-default-locale('en');
I18n.available-locales; # sorted list of registered locales
I18n.reset; # clear all templates, reset to 'en'
errors.add resolves through the same locale store, so messages added by hand or
from a custom validator are localised too:
I18n.set-locale('fr');
my $u = User.build({});
$u.errors.add('email', 'blank');
say $u.errors.email[0]; # doit être rempli