NAME
Mojolicious::Plugin::Buffer::I18N - message catalog for Mojolicious::Plugin::Buffer
VERSION
Version 0.001.0
SYNOPSIS
Translate the text that visitors see when the server is busy (an application using Mojolicious::Plugin::Buffer):
plugin 'Buffer' => {
messages => {
retry_later => "Die Seite wird erstellt, bitte versuchen Sie es erneut.\n",
},
};
Reword a log line, keeping its plural forms. Give every form, because an override replaces the whole message:
plugin 'Buffer' => {
messages => {
log_compressed => {
one => 'packed %{from} -> %{to} byte (%{encoding})',
other => 'packed %{from} -> %{to} bytes (%{encoding})',
},
},
};
Use a code reference for a language whose plural rules need more than zero, one and other, such as Polish:
plugin 'Buffer' => {
messages => {
log_optimised => sub {
my $args = shift;
my $n = $args->{count};
my $word = $n == 1 ? 'bajt'
: ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20)) ? 'bajty'
: 'bajtow';
return "Buffer: HTML z %{from} do %{to} $word";
},
},
};
Use the catalog in your own class. The method reads the overrides from the messages key of the object:
package My::Thing;
use Mojolicious::Plugin::Buffer::I18N qw(i18n);
sub new { my ($class, %args) = @_; return bless {messages => $args{messages}}, $class }
my $thing = My::Thing->new(messages => {unknown_option => q{No such option: %{option}}});
print $thing->i18n('unknown_option', {option => 'wibble'});
# No such option: wibble
List every key, for example to start a translation:
my $catalog = Mojolicious::Plugin::Buffer::I18N->catalog;
print "$_\n" for sort keys %$catalog;
DESCRIPTION
Every error, warning, log line and client-facing string produced by Mojolicious::Plugin::Buffer and its lock backends is looked up here by a message key, so none of them is written into the program logic. An application can replace any of them with the messages option of the plugin, or the messages attribute of a lock backend.
How it works, in plain words
- 1. The program asks for a message by its key, for example
unknown_option, and passes some values, for example{option => 'wibble'}. - 2. The object's own
messageshash is checked first. If it has the key, that text is used. If not, the built-in English text is used. - 3. If the text has several forms (for plural or gender), the right form is chosen from the
countandgendervalues. - 4. Each placeholder, such as
%{option}, is replaced by its value. - 5. If anything goes wrong (an unknown key, a missing form, a code reference that dies), the key itself is returned. So you always see something, never an empty message.
Words used in this document
- key
-
The name of a message, for example
retry_later. Keys are ASCII words. - template
-
The text stored for a key, before placeholders are filled in.
- placeholder
-
A marker in a template, written
%{name}, that is replaced by a value. - variant
-
One form of a message that has several forms, such as the singular form
oneand the plural formother.
Templates
A template is one of these three things:
- a string
-
Placeholders are written
%{name}, or%{name:FORMAT}whereFORMATis a singlesprintfconversion such as%d,%5.1for%x. A placeholder whose argument was not supplied is left in the text unchanged, so a missing argument is visible rather than silently blank. An argument that is present but undefined becomes the empty string. - a hash of variants
-
Used for plurals and grammatical gender. The rules are tried in this order:
- 1. If the
genderargument is a key of the hash, that variant is used. - 2. Otherwise, if
countis a number:zerois used for 0 andonefor 1, when those keys exist. - 3. Otherwise
otheris used.
Variants may be nested, for example gender variants whose values are plural variants. Nesting stops after 4 levels.
- 1. If the
- a code reference
-
Called with the argument hash. It returns a string or a hash of variants. Use it for languages whose plural rules cannot be written with
zero,oneandother. If it dies, the message key is used instead.
METHODS
i18n
my $text = $self->i18n($key, \%args);
Purpose
Turn a message key and its arguments into text. It is exported on request and called as a method, so overrides held by the calling object take precedence over the English catalog.
Arguments
$key(positional, required)-
The message key, a non-empty string such as
'unknown_option'. $args(positional, optional)-
A hash reference of values for the placeholders, plus the optional selectors
countandgender.
Returns
The text of the message. For an unknown key the key itself is returned, so a missing translation never hides that something happened.
Side effects
None. If the invocant is a blessed hash with a messages hash, its entries are consulted first; nothing is written.
Example
use Mojolicious::Plugin::Buffer::I18N qw(i18n);
# Class method: the English catalog
print __PACKAGE__->i18n('unknown_encoding', {encoding => 'lzma'});
# Mojolicious::Plugin::Buffer: unknown encoding 'lzma'
# Plural selection with a sprintf conversion
my $self = bless {messages => {
items => {
one => '%{count} item weighs %{kg:%.1f} kg',
other => '%{count} items weigh %{kg:%.1f} kg',
},
}}, __PACKAGE__;
print $self->i18n('items', {count => 3, kg => 2.25});
# 3 items weigh 2.2 kg
API SPECIFICATION
Input
# Params::Get::get_params([qw(key args)], @_) then
# Params::Validate::Strict::validate_strict
{
key => { type => 'string', min => 1 },
args => { type => 'hashref', optional => 1 },
}
Output
# Return::Set schema
{ type => 'string' }
MESSAGES
Key / Message Meaning Resolution
---------------------------------------- ------------------------------ ----------------------------
i18n_key_missing No key was passed Pass a message key
"a message key is required"
validate_strict: Parameter 'key' ... The key was empty or not a Pass a non-empty string key
string
validate_strict: Parameter 'args' ... The arguments were not a hash Pass a hash reference
All three croak. The last two come from Params::Validate::Strict and are not translated, because they report a programming error in the caller rather than a condition a user can act on.
PSEUDOCODE
croak if there is no key
validate key and args
look the key up in the invocant's overrides, then in the catalog
if it is not found, return the key
while the template is a code reference or a hash of variants,
and the nesting limit has not been reached:
call the code reference, or pick the variant for gender/count
if the result is not a plain string, return the key
replace every %{name} or %{name:FORMAT} whose argument exists
return the text
catalog
my $messages = Mojolicious::Plugin::Buffer::I18N->catalog;
Purpose
Return a copy of the English catalog, for translators who want every key and for tests that check a key exists.
Arguments
None.
Returns
A hash reference mapping each message key to its template. It is a shallow copy: changing it does not change the catalog.
Side effects
None.
Example
my $catalog = Mojolicious::Plugin::Buffer::I18N->catalog;
print "$_\n" for sort keys %$catalog;
API SPECIFICATION
Input
# No arguments besides the invocant
{}
Output
# Return::Set schema
{ type => 'hashref', min => 1 }
MESSAGES
None.
COMMON PITFALLS
An override replaces the whole message; it is not merged. If the built-in message has
oneandotherforms and you override it with a plain string, both forms are replaced by that one string. If you override it with{other => '...'}only, there is nooneform any more. Give every form you need.undef is not "use the default".
messages => {retry_later => undef}makes the message the bare key, so visitors see the wordretry_later. To go back to the built-in text, delete the key from the hash instead.A missing argument stays visible.
i18n('unknown_option')withoutoptionreturns... unknown option '%{option}'. This is on purpose, so that a mistake shows. An argument that exists but is undef becomes the empty string.There is no escape for a literal
%{name}. If a translated text must show%{word}itself, do not pass an argument calledword. A plain%that is not followed by{is always left alone.Gender is checked before count. With both
genderandcountgiven, a matching gender variant wins. Put the plural forms inside the gender variant if you need both.A code reference that dies is silent. The key is returned instead, and no warning is given. Test your code references.
A format is one conversion only.
%{n:%d}works.%{n:%d%%}does not match the placeholder rule and is left in the text unchanged. The conversions%nand vector flags are refused on purpose.Messages are chosen once per application. The plugin does not read
Accept-Language. See "LIMITATIONS".
ENCODING
- Templates and arguments
-
Both may hold any Perl characters: accented letters, any other script, and emoji. Use
use utf8;in a source file that contains such characters literally, or write them as escapes like"\x{1F600}". A%{name:%5s}width counts characters, not bytes. - The returned text
-
The result is a Perl character string. It is not encoded. When you print it, use a handle with an encoding layer, such as
binmode(STDOUT, ':encoding(UTF-8)'), or Perl warns "Wide character". Mojo::Log encodes every line to UTF-8 by itself, so log lines are safe. The plugin encodes theretry_latertext to UTF-8 and sends it withcharset=UTF-8. - Keys
-
Keys are matched exactly. All built-in keys are ASCII; use ASCII for your own keys too.
- Messages from Params::Validate::Strict
-
These are English and are passed through unchanged.
LIMITATIONS
Messages are chosen once per application, not per request. The plugin does not negotiate
Accept-Language, so theretry_latertext sent with a503is the same for every client. A per-language reply needs a custommessagescode reference that reads a value the application set.Plural selection only knows
zero,oneandother. Languages with more categories (Polish, Arabic, Welsh, ...) need a code reference template.Messages produced by Params::Validate::Strict for programming errors (a missing or malformed message key) are passed through in English.
SEE ALSO
AUTHOR
Nigel Horne, <njh at bandsman.co.uk>
FORMAL SPECIFICATION
This section describes the catalog in the Z notation, for readers who want an exact model. You do not need it to use the module. defaults is the built-in English catalog and overrides is the messages hash of the object the method is called on.
[KEY, TEXT, NAME, VALUE]
Args == NAME ⇸ VALUE
Template ::= str⟨⟨TEXT⟩⟩ | variants⟨⟨NAME ⇸ Template⟩⟩ | code⟨⟨Args → Template⟩⟩
┌─ Catalog ───────────────────────────────────
│ defaults : KEY ⇸ Template
│ overrides : KEY ⇸ Template
└─────────────────────────────────────────────
select : Template × Args × ℕ ⇸ TEXT ∪ {⊥}
select(t, a, d) = ⊥ if d ≥ 4 ∧ t ¬in; ran str
select(str s, a, d) = s
select(code f, a, d) = select(f(a), a, d + 1) if f(a) does not die
= ⊥ otherwise
select(variants v, a, d) = select(v(pick(v, a)), a, d + 1)
if pick(v, a) ∈ dom v
= ⊥ otherwise
pick(v, a) = gender a if gender a ∈ dom v
= zero if count a = 0 ∧ zero ∈ dom v
= one if count a = 1 ∧ one ∈ dom v
= other otherwise
i18n
interpolate replaces each placeholder %{n} or %{n:F} for which n ∈ dom args?, and leaves every other placeholder unchanged.
┌─ I18n ──────────────────────────────────────
│ ΞCatalog
│ key? : KEY ; args? : Args ; text! : TEXT
├─────────────────────────────────────────────
│ key? ≠ ∅
│ let m == defaults ⊕ overrides •
│ key? ∈ dom m ∧ select(m key?, args?, 0) ≠ ⊥
│ ⇒ text! = interpolate(select(m key?, args?, 0), args?)
│ key? ¬in; dom m ∨ select(m key?, args?, 0) = ⊥
│ ⇒ text! = key?
└─────────────────────────────────────────────
-- key? undefined or empty, or args? not a hash: croak
catalog
┌─ Catalog_Copy ──────────────────────────────
│ ΞCatalog
│ result! : KEY ⇸ Template
├─────────────────────────────────────────────
│ result! = defaults
└─────────────────────────────────────────────
STATE DIAGRAM
This module keeps no state between calls: the catalog never changes while the program runs, and the overrides belong to the calling object. So instead of states, this diagram shows the path one call to "i18n" takes. Each box is a step; the text on an arrow is the condition for taking it.
i18n(key, args)
|
v
+--------------------+ key undef or empty,
| 1. CHECK ARGUMENTS | or args not a hash
| | ----------------------> croak
+--------------------+
| ok
v
+--------------------+ key in neither hash
| 2. LOOK UP THE KEY | ----------------------> return the key
| overrides first, | (e.g. "retry_later")
| then the defaults |
+--------------------+
| found
v
+--------------------+ undef template, dying
| 3. RESOLVE | code ref, no variant
| code ref: call it | fits, or 4 levels deep
| hash: pick variant | ----------------------> return the key
| repeat until plain |
+--------------------+
| plain string
v
+--------------------+
| 4. FILL IN | This step cannot fail. A placeholder
| %{name} and | with no argument is left as it is.
| %{name:FORMAT} |
+--------------------+
|
v
return the text
LICENSE AND COPYRIGHT
Copyright 2026 Nigel Horne.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.