Log::R...essage(3pm)

πŸ’– Log::Report::Message(3pm) β€” a whole vibe πŸ’–

okay bestie, welcome to Log::Report::Message(3pm) from the User Contributed Perl Documentation collection πŸŽ€ this is the page that explains the little message object that carries all your translated text around. it's a lot, so strap in, hydrate, and let's go ✨


🌸 NAME 🌸

Log::Report::Message β€” it's literally just a piece of text to be translated. that's the whole gig. a lil container for words that need to go on a whole language journey πŸ’…


πŸ¦‹ INHERITANCE πŸ¦‹

so the family tree: Log::Report::Message gets extended by

  • Dancer2::Plugin::LogReport::Message

that's it, that's the lineage. one glow-up child πŸ‘‘


✨ SYNOPSIS ✨

here's the whole mood board. these are objects made by Log::Report's __ functions β€” full deets live down in the DESCRIPTION and beyond, but here's the teaser trailer:

# Objects created by Log::Report's __ functions
# Full feature description in the DETAILS section

# no interpolation
__"Hello, World";

# with interpolation
__x"age {years}", years => 12;

# interpolation for one or many
my $nr_files = @files;
__nx"one file", "{_count} files", $nr_files;
__nx"one file", "{_count} files", \@files;

# interpolation of arrays
__x"price-list: {prices%.2f}", prices => \@prices, _join => ', ';

# white-spacing on msgid preserved
print __"\tCongratulations,\n";
print "\t", __("Congratulations,"), "\n";  # same

# Object serialization support
my $clone = Log::Report::Message->thaw($msg->freeze);
not me looking at __"Hello, World"; like it's the cutest thing ever 😭 that double-underscore is a real function name bestie, it's not a typo, it's a lifestyle.

πŸ’œ DESCRIPTION πŸ’œ

okay so. any time you use a constructor function exported by Log::Report β€” like __() (yes the function is literally named underscore-underscore, iconic) or __x() (underscore-underscore-x) β€” you get one of these Message objects back. and it's not just the text, it also captures some environmental information too 🌈 like it's quietly taking notes on its surroundings.

here's the part where it gets ✨ strategic ✨: the optional translation is delayed until it is really needed. so the object just sits there, holding the un-translated words, until somebody actually asks for the stringified version.

and ngl, creating an object first and translating it later might be slower than translating it immediately. lowkey perf trade-off, noted. but β€” and this is by design decisions of Log::Report, not an accident β€” at the location where the message is produced, we do not yet know what language to translate it to. that depends on the actual log dispatcher configurations in the main program 🌟 so it's not being lazy, it's being patient. main character energy fr fr.


πŸ‘‘ OVERLOADED πŸ‘‘

perl overloading, but make it fashion πŸ’… here's what happens when you use this object in different contexts:

  • overload: '""' stringification β€” when the object gets used in string context, it gets translated. implemented as toString().
  • overload: '&{}' used as function β€” when you call the object like it's a function, a new object is created with the data from the original one, but updated with the new parameters. implemented in clone().
  • overload: '.' concatenation β€” this one is a defensive little queen πŸ›‘οΈ an (accidental) use of concatenation β€” like, a dot where a comma should've been β€” would immediately stringify the object. so to avoid that happening by surprise, that operation is overloaded. she said "not on my watch" πŸ’–
bestie listen: that dot-vs-comma thing is a real trap. if you meant to pass more parameters but you typed . instead of ,, this overload is the thing standing between you and a mysteriously-early translation. respect the overload πŸ™

and that's part 1! there's more coming, so don't go anywhere πŸ’œπŸ¦‹πŸŒΈ

✨ METHODS ✨

okay bestie, buckle up, this is the part where the actual doing stuff happens πŸ’…


🧸 Constructors

$obj->clone(%options, $variables)

Spits out a brand new object that copies all the info from the original and then updates it with whatever %options and $variables you hand it. The slay here? The cached translations are shared between the two objects, so you're not redoing work. iconic behavior honestly 🫢

Β» example: use of clone()
my $s = __x "found {nr} files", nr => 5;
my $t = $s->clone(nr => 3);
my $t = $s->(nr => 3);      # equivalent
print $s;     # found 5 files
print $t;     # found 3 files

$class->new(%options)

Hey end-users β€” do not call this method directly, bestie, I'm begging 😭 use Log::Report::__(), Log::Report::__x() and their lil friend group instead. The %options is a mixed-up list of two things: object initiation parameters (all of which start with a leading underscore) and the variables that get filled into the translated _msgid string.

so the default lineup is giving:

  • _append β€” default: undef
  • _category β€” default: undef
  • _class β€” default: undef
  • _classes β€” default: undef
  • _context β€” default: undef
  • _count β€” default: undef
  • _domain β€” default: <from "use Log::Report">
  • _expand β€” default: false
  • _join β€” default: $" $LIST_SEPARATOR
  • _lang β€” default: <from locale>
  • _msgctxt β€” default: undef
  • _msgid β€” default: undef
  • _plural β€” default: undef
  • _prepend β€” default: undef
  • _tag β€” default: []
  • _tags β€” default: []
  • _to β€” default: undef

and now the ✨deep dive✨ on each one:

  • _append => $text|$message β€” some $text or another $message object that needs to get paste'd after this message object.
  • _category => INTEGER β€” the category used when the real gettext library is in play, like LC_MESSAGES for instance.
  • _class => $tags|\@tags β€” a deprecated alternative for _tag. (we don't do that here anymore, bestie 🚩)
  • _classes => $tags|\@tags β€” also a deprecated alternative for _tag.
  • _context => $keyword|\@keywords β€” [1.00] sets the @keywords you can use to pick between different translations. Go read the DETAILS section in Log::Report::Translator::Context.
  • _count => INTEGER|ARRAY|HASH β€” when this one's defined, _plural needs to be defined too, no exceptions. If you pass an ARRAY, the length of the ARRAY is what counts. If you pass a HASH, it's the number of keys in the HASH.
  • _domain => $name|$object β€” the text-domain (aka the translation table) that this _msgid belongs to.

With this parameter you can "borrow" translations from other textdomains. Be very careful with this one (although yes, there are good use-cases). The xgettext msgid extractor may add the used msgid to this namespace as well. To dodge that, add a harmless '+':

print __x(+"errors", _domain => 'global');

The extractor will not take the msgid when it's an expression. The '+' has no effect on the string at runtime.

  • _expand => BOOLEAN β€” tells you whether variables are gonna get filled in, i.e. whether __x or __ was used to define the message.
  • _join => $separator β€” which $separator string to use when an ARRAY is being filled in.
  • _lang => ISO β€” [1.00] overrides the language setting from the locale, like when it's not configured correctly (yet). Heads up: this does not extend to a prepended or appended translated message object.
  • _msgctxt => $context β€” [1.22] the message $context in the translation file, the traditional use. Cannot be combined with _context on the same msgids.
  • _msgid => $msgid β€” the message label, which points to some translation info. Usually a string that's close to the English version of the message. This also gets used if there's no translation possible/known.

Leading white-space "\s" will be added to _prepend. Trailing white-space will be added before _append.

  • _plural => $msgid β€” can be used together with _count. This plural form of the _msgid text exists to make translators' lives easier, and it acts as the fallback when no translation is possible β€” so it's best if it resembles an English message.

White-space at the start and end of the string gets stripped off. The white-space provided by the _msgid will be used instead.

  • _prepend => $text|$message β€” some $text or another $message object that needs to be glued before this message object.
  • _tag => $tags|\@tags β€” when messages are used for exception-based programming, you add a _tag parameter to the argument list. Later on, using something like Log::Report::Dispatcher::Try::wasFatal(tag), you can check the category (group, class) of the exception.

The $tags gets interpreted as a comma- and/or blank separated list of class tokens (barewords); the ARRAY lists all tags separately. See tags().

  • _tags => $tags|\@tags β€” alternative name for _tag.
  • _to => $dispatcher β€” specify the $dispatcher as destination explicitly. Short for report {to => NAME}, ... See to().

πŸŽ€ Attributes

$obj->addTags(@tags)

[1.45] adds zero or more tags to this message. zero is fine, we're not judging πŸ’–

$obj->append()

Returns the string or Log::Report::Message object that gets appended after this one. Usually undef.

$obj->classes()

Deprecated alternative for tags(). moving on πŸšΆβ€β™€οΈ

$obj->context()

Returns a HASH if there's a context defined for this message.

$obj->count()

Returns the count, which is what's used to pick between the translation alternatives.

$obj->domain()

Returns the domain of the first translatable string in the structure.

$obj->errno( [$errno] )

[1.38] returns the value of the _errno key, to indicate the error number (the one to be returned from your script). Usually this method returns undef. For FAILURE, FAULT, and ALERT, the errno is by default taken from $! and $?.

$obj->msgctxt()

The message context for the translation table lookup.

$obj->msgid()

Returns the msgid which will get translated later.

$obj->prepend()

Returns the string that gets prepended to this one. Usually undef.

$obj->tags()

[1.44] returns the LIST of tags defined for this message β€” those message group indicators, as often found in exception-based programming.

$obj->to( [$name] )

Returns the $name of a dispatcher if it was explicitly specified with the _to key. Can also be used to set it. Usually this returns undef, because usually all dispatchers get all messages. no gatekeeping here 🫢

$obj->valueOf($parameter)

Lookup the named $parameter for the message. All pre-defined names have their own method, and you should prefer those, bestie.

Β» example:

When the message was produced with

my @files = qw/one two three/;
my $msg = __xn
   "found one file: {file}",
   "found {nrfiles} files: {files}",
   scalar @files,
   file    => $files[0],
   files   => \@files,
   nrfiles => @files+0,  # or scalar(@files)
   _tags   => [ 'IO', 'files' ],
   _join   => ', ';

then the values can be takes from the produced message as

my $files = $msg->valueOf('files');  # returns ARRAY reference
print @$files;                 # 3
my $count = $msg->count;       # 3
my @tags  = $msg->tags;        # 'IO', 'files'
if($msg->taggedWith('files'))  # true

Simplified, the above example can also be written as:

local $" = ', ';  # Perl default
my $msg  = __xn
   "found one file: {files}",
   "found {_count} files: {files}",
   @files,      # has scalar context
   files   => \@files,
   _tags   => 'IO, files';

πŸ¦‹ Processing

$obj->concat( $text|$message, [$prepend] )

This method implements the overloading of concatenation, which exists to delay translations even longer (yes, longer, it's giving patience πŸ‘‘). When $prepend is true, the $text or $message (another Log::Report::Message) gets prepended; otherwise it's appended in the final display.

Β» example: of concatenation
print __"Hello" . ' ' . __"World!\n";
print __("Hello")->concat(' ')->concat(__"World!")->concat("\n");

$obj->freeze(%options)

Converts the data of this message into a (nested) HASH which optimally preserves its content. Hand that to thaw() and you get the object back. it's giving ✨preservation✨

The returned HASH can be serialized safely with (for instance) YAML or JSON. Then it can be reconstructed from the de-serialized data. At the moment, all facts can be preserved. no cap πŸ’―

  • serializer β€” default: undef

serializer => 'JSON'|'Serial'|...

In the future, the specific serializer may influence the frozen output. (key word: future, bestie, we're not there yet 🌸)

Β» example: to thaw a frozen
my $clone = Log::Report::Message->thaw($msg->freeze);

$obj->inClass($tag|Regexp)

Deprecated alternative for taggedWith().

$obj->taggedWith($tag|Regexp)

[1.44] returns true if the message carries the specified $tag (string) or matches the Regexp. The trueth value is the (first matching) tag.

$class->thaw(\%data, %options)

yep, that's the whole thing, that's the method πŸ’… (pair it with freeze() and you've got a whole lil cycle going)

$obj->toHTML( [$locale] )

[1.11] translates the message, and then entity encodes the HTML volatile characters.

[1.20] when you're using this in combination with a templating system, you may want to use "<content_for =" 'HTML'>> in Log::Report::Domain::configure(formatter).

Β» example:
print $msg->toHTML('NL');

$obj->toString( [$locale] )

Translates a message. If you don't specify one, the default locale is used.

$obj->untranslated()

Returns the concatenation of the prepend, msgid, and append strings. Variable expansions within the msgid are not performed. so raw, so real, no edits πŸ™ˆ

πŸ’– DETAILS πŸ’–

so the message that the Log::Report exception framework casts can just be plain strings, bestie. fr fr, sometimes that's all you need β€” like, why would you stress about the content of a trace message? nobody's reading those. but messages headed for actual end-users or log-files? those need care, they need a higher quality. in that case you're way better off using cast message objects, and those support interpolation. slay.


πŸŽ€ Message-IDs πŸŽ€

an exception message as a string is, ngl, pretty inflexible. it can't be translated anymore, and it can't carry related info like an error code. an exception message as an object (which is what this module implements) is able to contain additional information. main character behavior honestly.

the easiest way to create Log::Report::Message objects is via the __x() and friends functions, which get exported by Log::Report. these two are nearly equivalent:

my $msg = Log::Report::Message->new(_msgid => "Hello, World!");
my $msg = __"Hello, World!";

the name msgid is used because this object can do translations. in the GNU gettext library for translations, the term msgid describes the string that has to be looked-up in translation tables. when there are no tables, or the msgid isn't found, then the output string is equivalent to the msgid. periodt.

so with zero translation configuration, this is what goes down:

my $msg = __"Hello, World!";
print "$msg\n";      # Hello, World!⏎

the __() function can be used with a static string, although you could do this:

my $msg = __"Hello $name!";      #XXX Don't do this!
print "$msg\n";      # Hello Mark!⏎

with __x() or __nx(), interpolation takes place on the (optionally) translated msgid string. so here's how you'd actually write the thing above:

my $msg = __x"Hello {name}!", name => $name;

so: the msgid is usually a format string. only when you use formats correctly will you be able to introduce translation later. iykyk.

✨ Why use format strings? ✨

simple perl scripts will use print() with variables right inside the string. but when the content of the variable gets more unpredictable or needs some pre-processing, it gets tricky. and when you want to introduce translations (in the far future of your successful project, bestie πŸ‘‘) it gets impossible. let me give you some examples:

print "product: $name\n";    # simple perl

# Will not work because "$name" is interpolated too early
print translate("product: $name"), "\n";

# This is the gettext solution, with formats
printf translate("product: %s\n"), $name;

# With named in stead of positional parameters
print translate("product: {p}\n", p => $name);

# With Log::Report, the translate() is hidden in __x()
print __x"product: {p}\n", p => $name;

besides making translation possible, interpolation via format strings is so much cleaner than the simplest perl way. for instance, these cases:

# Safety measures while interpolation
my $name = undef;
print "product: $name\n";   # uninitialized warning
print __x"product: {p}\n", p => $name;  # --> product: undef

# Interpolation of more complex data
my @names = qw/a b c/;
print "products: ", join(', ', @names), "\n";
print __x"products: {p}\n", p => \@names;

# Padded values hard to do without format strings
print "padded counter: ", ' ' x (6-length $c), "$c\n";
printf "padded counter: %6d\n", $counter;
print __x"padded counter: {c%6d}\n", c => $counter;

so: using formats has many advantages. the advice: use simple perl only in trace and assert messages, maybe also with panics. for serious output of your program, use formatted output. no cap.

πŸ¦‹ Messages with plural forms πŸ¦‹

the Log::Report::__xn() message constructor is what you reach for when you need a different translation based on the count of one of the inserted fields.

fault __x"cannot read {file}", file => $fn;
# --> FAULT: cannot read /etc/shadow: Permission denied\n

print __xn"directory {dir} contains one file",
          "directory {dir} contains {nr_files} files",
          scalar(@files),            # (1) (2) (3)
          nr_files => scalar @files, # (4)
          dir      => $dir;
  1. this required third parameter is used to switch between the different plural forms. English has only two forms, but some languages have many more.
  2. the scalar keyword is not needed, because the third parameter is in SCALAR context. you may also pass \@files there, because ARRAYs will be converted into their length. a HASH will be converted into the number of keys in the HASH.
  3. you could also simply pass a reference to the ARRAY: it will take the length as counter. with a HASH, it will count the number of keys.
  4. the scalar keyword is required here, because it is LIST context: otherwise all filenames will be filled-in as parameters to __xn(). see below for the available _count value, to see how the nr_files parameter can disappear. πŸ’…
print __xn"directory {dir} contains one file",
          "directory {dir} contains {_count} files",
          \@files, dir => $dir;

some languages need more than two translations based on the counter. this is solved by the translation table definition. the two msgids given here are simply the fallback, for when there is not translation table active.

🌸 Interpolation with String::Print 🌸

there is no way of checking beforehand whether you have provided all the required values to be interpolated in the translated string. πŸ™ˆ

this Log::Report::Message uses String::Print to handle formatted strings. an object of that module is hidden in the logic of __x() and friends.

String::Print is a very capable format string processor, and you should really read its manual page to see how it can help you. it would be possible to support an other formatter (pretty simple even), but this is not (yet) supported.

Β» Example: using format features
# This tries to display the $param as useful and safe as possible,
# where you have totally no idea what its contents is.
error __x"illegal parameter {p UNKNOWN}.", p => $param;
# ---> "illegal parameter 'accidentally passed text'."
# ---> "illegal parameter Unexpected::Object::Type."

# fault() adds ": $!", with $! translated when configured.
open my($fh), "<:encoding(utf-8)", $filename
       or fault __x"cannot read {file}", file => $filename;

# Auto-abbreviation
trace __x"first lines: '{text EL}'\n", text => $t;
# ---> "first lines: 'This text is long, we shoβ‹―'.\n"

trace __x"first lines: {text CHOP}\n", text => $t;
# ---> "This text is long, we [+3712 chars]\n"

info __x"file {file} size {size BYTES}\n", file => $fn, size => -s $fn;
# --> "/etc/passwd size 23kB\n"

# HASH or object values
print __x"Name: {user.first} {user.surname}\n", user => $login;

there are more nice standard interpolation modifiers, and you can add your own. besides, you can add serializers which determine how objects are inlined. iconic tbh.

πŸ’œ Automatic parameters πŸ’œ

besides the parameters which you specify yourself, Log::Report will add a few which can also be interpolated. they all start with an underscore (_). these are collected when this Message object is instantiated, see the %options of new(). these parameters have a purpose, but you are also permitted tp interpolate them in your message. this may simplify your coding. 🫢

the useful names are:

  • _msgid β€” the MSGID as provided with Log::Report::__() and Log::Report::__x()
  • _plural, _count β€” the plural (second) msgid, respectively the counter value as used with Log::Report::__n() and Log::Report::__nx()
  • _textdomain β€” the label of the textdomain in which the translation takes place.
  • _join β€” the string which is used between elements of an ARRAY, when it gets interpolated in a single field.
  • _tags β€” [1.44] tags are to be used to group exceptions, and can be queried with taggedWith(), Log::Report::Exception::taggedWith(), or Log::Report::Dispatcher::Try::wasFatal(tag).
Β» Example: using the _count

with Locale::TextDomain, you have to do

use Locale::TextDomain;
print __nx(
   "One file has been deleted.\n",
   "{num} files have been deleted.\n",
   $num_files,
   num => $num_files,
);

with Log::Report, you can do

use Log::Report;
print __nx(
   "One file has been deleted.\n",
   "{_count} files have been deleted.\n",
   $num_files,
);

of course, you need to be aware that the name used to reference the counter is fixed to _count. the first example works as well, but is more verbose.

🧚 Handling white-spaces 🧚

in the above examples, the msgid and plural form have a trailing new-line. in general, it is much easier to write

print __x"Hello, World!\n";

than

print __x("Hello, World!") . "\n";

for the translation tables, however, that trailing new-line is "ignorable information"; it is an layout issue, not a translation issue.

therefore, the first form will automatically be translated into the second. all leading and trailing white-space (blanks, new-lines, tabs, ...) are removed from the msgid before the look-up, and then added to the translated string.

leading and trailing white-space on the plural form will also be removed. however, after translation the spacing of the msgid will be used. the more you know ✨

πŸ“ Avoiding repetative translations πŸ“

this way of translating is somewhat expensive, because an object to handle the Log::Report::__x() is created each time.

for my $i (1..100_000)
{   print __x "Hello World {i}\n", i => $i;
}

the suggestion that Locale::TextDomain makes to improve performance, is to get the translation outside the loop, which only works without interpolation:

use Locale::TextDomain;
my $i = 42;
my $s = __x("Hello World {i}\n", i => $i);
foreach $i (1..100_000)
{   print $s;
}

oops, not what you mean, because the first value of $i is captured in the initial message object. πŸ’€ with Log::Report, you can do it (except when you use contexts)

use Log::Report;
my $i;
my $s = __x("Hello World {i}\n", i => \$i);
foreach $i (1..100_000)
{   print $s;
}

mind you not to write: for my $i in above case!!!! genuinely, don't. 😭

you can also write an incomplete translation:

use Log::Report;
my $s = __x "Hello World {i}\n";
foreach my $i (1..100_000)
{   print $s->(i => $i);
}

in either case, the translation will be looked-up only once. efficient queen πŸ‘‘


🌈 SEE ALSO 🌈

this module is part of Log-Report version 1.46, built on June 16, 2026.

website: http://perl.overmeer.net/CPAN/

πŸ’– LICENSE πŸ’–

okay bestie, legal era, but make it cute ✨

  • contributing? πŸ‘€ bestie the receipts live in the file ChangeLog β€” go check it, iykyk 🫢

this software is copyright (c) 2007-2026 by Mark Overmeer πŸ’…

and fr fr, it's free software 🌈 β€” you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. no cap, that's the whole vibe ✨


and that's a wrap on Log::R...essage(3pm) πŸŽ€ you ate that whole page, queen πŸ‘‘ go forth and log responsibly πŸ’–

sign-off tea β˜•: this one's from perl v5.40.1, dated 2026-07-05 β€” the source, the version, the date, all of it, periodt πŸ’œ
πŸ“¦ where this page comes from