NAME

Rose::HTML::Object::Message::Localizer - Message localizer class.

SYNOPSIS

# The localizer for a given class or object is usually accessibly
# via the "localizer" class or object method.

$localizer = Rose::HTML::Object->localizer;    
$localizer = $object->localizer;

...

# The localizer is rarely used directly.  More often, it is
# subclassed so you can provide your own alternate source for
# localized messages. See the LOCALIZATION section of the
# Rose::HTML::Objects documentation for more information.

package My::HTML::Object::Message::Localizer;

use base qw(Rose::HTML::Object::Message::Localizer);
...
sub get_localized_message_text
{
  my($self) = shift;

  # Get localized message text from the built-in sources
  my $text = $self->SUPER::get_localized_message_text(@_);

  unless(defined $text)
  {
    my %args = @_;

    # Get message text from some other source
    ...
  }

  return $text;
}

DESCRIPTION

Rose::HTML::Object::Message::Localizer objects are responsible for managing localized messages and errors which are identified by integer ids and symbolic constant names. See the Rose::HTML::Object::Messages and Rose::HTML::Object::Errors documentation for more infomation on messages and errors.

In addition to collecting and providing access to messages and errors, Rose::HTML::Object::Message::Localizer objects also provide appropriately localized text for each message and error.

This class inherits from, and follows the conventions of, Rose::Object. See the Rose::Object documentation for more information.

MESSAGES AND ERRORS

Messages and errors are stored and tracked separately, but are intimately related. Both entities have integer ids which may be imported as symbolic constants, but only messages have associated localized text.

The integer message and error ids are convenient, compact, and easily comparable. Using these constants in your code allows you to refer to messages and errors in a way that is divorced from any actual message text. For example, if you wanted to subclass Rose::HTML::Form::Field::Integer and do something special in response to "invalid integer" errors, you could do this:

package My::HTML::Form::Field::Integer;

use base 'Rose::HTML::Form::Field::Integer';

# Import the symbol for the "invalid integer" error
use Rose::HTML::Object::Errors qw(NUM_INVALID_INTEGER);

sub validate
{
  my($self) = shift;

  my $ret = $self->SUPER::validate(@_);

  unless($ret)
  {
    if($self->error_id == NUM_INVALID_INTEGER)
    {
      ... # do something here
    }
  }

  return $ret;
}

Note how detecting the exact error did not require regex-matching against error message text or anything similarly unmaintainable.

When it comes time to display appropriate localized message text for the NUM_INVALID_INTEGER error, the aptly named message_for_error_id method is called. This method exists in the localizer, and also in Rose::HTML::Object and Rose::HTML::Form::Field. The localizer's incarnation of the method is usually only called if the other two are not available (e.g., in the absence of any HTML object or field). The mapping between error ids and message ids is direct by default (i.e., error id 123 maps to message id 123) but can be entirely aribtrary.

LOCALIZED TEXT

Broadly speaking, localized text can come from anywhere. See the localization section of the Rose::HTML::Objects documentaton for a description of how to create your own localizer subclass that loads localized message text from the source of your choosing.

The base Rose::HTML::Object::Message::Localizer class reads localized text from the __DATA__ sections of Perl source code files and stores it in memory within the localizer object itself. Such text is read in en masse when the load_all_messages method is called, or on demand in response to requests for localized text. The auto_load_messages flag may be used to distinguish between the two policies. Here's an example __DATA__ section and load_all_messages call (from the Rose::HTML::Form::Field::Integer source code):

if(__PACKAGE__->localizer->auto_load_messages)
{
  __PACKAGE__->localizer->load_all_messages;
}

1;

__DATA__

[% LOCALE en %]

NUM_INVALID_INTEGER          = "[label] must be an integer."
NUM_INVALID_INTEGER_POSITIVE = "[label] must be a positive integer."
NUM_NOT_POSITIVE_INTEGER     = "[label] must be a positive integer."

[% LOCALE de %]

NUM_INVALID_INTEGER          = "[label] muß eine Ganzzahl sein."
NUM_INVALID_INTEGER_POSITIVE = "[label] muß eine positive Ganzzahl sein."
NUM_NOT_POSITIVE_INTEGER     = "[label] muß eine positive Ganzzahl sein."

[% LOCALE fr %]

NUM_INVALID_INTEGER          = "[label] doit être un entier."
NUM_INVALID_INTEGER_POSITIVE = "[label] doit être un entier positif."
NUM_NOT_POSITIVE_INTEGER     = "[label] doit être un entier positif."

The messages for each locale are set off by LOCALE directives surrounded by [% and %]. All messages until the next such declaration are stored under the specified locale.

Localized text is provided in double-quoted strings to the right of symbolic messages constant names.

Placeholders are replaced with text provided at runtime. Placeholder names are surrounded by square brackets. They must start with [a-zA-Z] and may contain only characters that match \w. For an example, see the [label] placeholders in the mssage text above. A @ prefix is allowed to specify that the placeholder value is expected to be a reference to an array of values.

SOME_MESSAGE = "A list of values: [@values]"

In such a case, the values are joined with ", " to form the text that replaces the placeholder.

Embedded double quotes in message text must be escaped with a backslash. Embedded newlines may be included using a \n sequence. Literal opening square brackets must be backslash-escaped: \[. Literal backslashes must be doubled: \\. Example:

SOME_MESSAGE = "Here\[]:\nA backslash \\ and some \"embedded\" double quotes"

The resulting text:

Here[]:
A backslash \ and some "embedded" double quotes

There's also a multi-line format for longer messages:

[% START SOME_MESSAGE %]
This message has multiple lines.
Here's another one.
[% END SOME_MESSAGE %]

Leading and trailing spaces and newlines are removed from text provided in the multi-line format.

Blank lines and any lines beginning with a # character are skipped.

VARIANTS

Any message constant name may be followed immediately by a variant name within parentheses. Variant names may contain only the characters [A-Za-z0-9_-]. If no variant is provided, the variant is assumed to be default. In other words, this:

SOME_MESSAGE(default) = "..."

is equivalent to this:

SOME_MESSAGE = "..."

Before going any further, the key thing to remember about variants is that you can ignore them entirely, if you wish. Don't use any variants in your message text and don't specify any variants when asking for localized message text and you can pretend that they do not exist.

With that out of the way, there are some good reasons why you might want to use variants. But first, let's examine how they work. We've already seen the syntax for specifying variants using the built-in localized message text format. The next piece of the puzzle is the ability to specify a particular variant for a message. That can be done either explicitly or indirectly. First, the explicit approach.

Requesting a variant explicitly is done using the special variant message argument. Example:

$field->error_id($id, { variant => 'foo' });

Aside from indicating the message variant, the variant argument is treated just like any other. That is, if you happen to have a placeholder named variant, then the value will be subtituted for it. (This being the case, it's usually a good idea to avoid using variant as a placeholder name.)

If no explicit variant is specified, the select_variant_for_message method is called to select an appropriate variant. The default implementation of this method returns the default variant most of the time. But if there is a message argument named count, then the select_variant_for_count method is called in order to select the variant.

This leads to the primary intended use of variants: pluralization. English has relatively simple pluralization rules, but other languages have special grammar for not just singular and plural, but also "dual," and sometimes even "many" and "few." The pluralization variant names expected by the default implementation of select_variant_for_count roughly follow the CLDR guidelines:

http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html

with the exception that plural is used in place of other. (Variants are a general purpose mechanism, whereas the context of pluralization is implied in the case of the CLDR terms. A variant named other has no apparent connection to pluralization.)

The default implementation of select_variant_for_count (sanely) makes no judgements about "few" or "many," but does return zero for a count of 0, one for 1, two for 2, and plural for all other values of count.

But since English has no special pluralization grammar for two items, how is this expected to work in the general case? The answer is the so-called "variant cascade." If the desired variant is not available for the specified message in the requested locale, then the variant_cascade method is called. It is passed the locale, the desired variant, the message itself, and the message arguments. It returns a list of other variants to try based on the arguments it was passed.

The default implementation of variant_cascade follows simple English-centric rules, cascading directly to plural except in the case of the one variant, and appending the default variant to the end of all cascades.

(Incidentally, there is also a locale cascade. The localize_message method uses a nested loop: for each locale, for each variant, look for message text. See the localize_message documentation for more information.)

Here's an example using variants. (Please forgive the poor translations. I don't speak French. Corrections welcome!) First, the message text:

[% LOCALE en %]

FIELD_ERROR_TOO_MANY_DAYS = "Too many days."
FIELD_ERROR_TOO_MANY_DAYS(one) = "One day is too many."
FIELD_ERROR_TOO_MANY_DAYS(two) = "Two days is too many."
FIELD_ERROR_TOO_MANY_DAYS(few) = "[count] days is too many (few)."
FIELD_ERROR_TOO_MANY_DAYS(many) = "[count] days is too many (many)."
FIELD_ERROR_TOO_MANY_DAYS(plural) = "[count] days is too many."

[% LOCALE fr %]

FIELD_ERROR_TOO_MANY_DAYS = "Trop de jours."
FIELD_ERROR_TOO_MANY_DAYS(one) = "Un jour est un trop grand nombre."
FIELD_ERROR_TOO_MANY_DAYS(plural) = "[count] jours est un trop grand nombre."

Now some examples of variant selection:

use My::HTML::Object::Errors qw(FIELD_ERROR_TOO_MANY_DAYS)l
...

$id = FIELD_ERROR_TOO_MANY_DAYS; # to make for shorter lines below

$field->locale('en');

$field->error_id($id, { count => 0 });

# No explicit variant given.  The select_variant_for_count() called
# and returns variant "zero".  No "zero" variant found for this
# message in locale "en", so the variant_cascade() containing
# ('plural', 'default') is considered, in that order.  A "plural"
# variant is found.
print $field->error; # "0 days is too many."

$field->error_id($id, { count => 2 });

# No explicit variant given.  The select_variant_for_count() called and 
# returns variant "two".  That message variant is found in locale "en"
print $field->error; # "Two days is too many."

$field->error_id($id, { count => 3, variant => 'few'  });

# Explicit variant given.  That message variant is found in locale "en"
print $field->error; # "3 days is too many (few)."

$field->locale('fr');

$field->error_id($id, { count => 0 });

# No explicit variant given.  The select_variant_for_count() called
# and returns variant "zero".  No "zero" variant found for this
# message in locale "fr", so the variant_cascade() containing
# ('plural', 'default') is considered, in that order.  A "plural"
# variant is found.
print $field->error; # "0 jours est un trop grand nombre."

$field->error_id($id, { count => 3, variant => 'few' });

# Explicit variant given.  No "few" variant found for this message
# in locale "fr", so the variant_cascade() containing ('plural',
# 'default') is considered, in that order.  A "plural" variant is
# found.
print $field->error; # "3 jours est un trop grand nombre."

I hope you get the idea. Remember that what's described above is merely the default implementation. You are fully expected to override any and all public methods in the localizer in you private library to alter their behavior. An obvious choice is the variant_cascade method, which you might want to override to provide more sensible per-locale cascades, replacing the default English-centric rules.

And even if you don't plan to use the variant system at all, you might want to override select_variant_for_message to unconditionally return the default variant, which will eliminate the special treatment of message arguments named count and variant.

CUSTOMIZATION

The implementation of localized message storage described above exists primarily because it's the most convenient way to store and distribute the localized messages that ship with the Rose::HTML::Objects module distribution. For a real application, it may be preferable to store localized text elsewhere.

The easiest way to do this is to create your own Rose::HTML::Object::Message::Localizer subclass and override the get_localized_message_text method, or any other method(s) you desire, and provide your own implementation of localized message storage and retrieval.

You must then ensure that your new localizer subclass is actually used by all of your HTML objects. You can, of course, set the localizer attribute directly, but a much more comprehensive way to customize your HTML objects is by creating your own, private family tree of Rose::HTML::Object-derived classes. Please see the private libraries section of the Rose::HTML::Objects documentation for more information.

LOCALES

Localization is done based on a "locale", which is an arbitrary string containing one or more non-space characters. The locale string must evaluate to a true value (i.e., the string "0" is not allowed as a locale). The default set of locales used by the Rose::HTML::Objects modules are lowercase two-letter language codes:

LOCALE      LANGUAGE
------      --------
en          English
de          German
fr          French
bg          Bulgarian

Localized versions of all built-in messages and errors are provided for all of these locales.

CLASS METHODS

auto_load_messages [BOOL]

Get or set a boolean value indicating whether or not localized message text should be automatically loaded from classes that call their localizer's load_all_messages method. The default value is true if either of the MOD_PERL or RHTMLO_PRIME_CACHES environment variables are set to a true value, false otherwise.

default_locale [LOCALE]

Get or set the default locale used by objects of this class. Defaults to "en".

default_locale_cascade [PARAMS]

Get or set the default locale cascade. PARAMS are locale/arrayref pairs. Each referenced array contains a list of locales to check, in the order specified, when message text is not available in the desired locale. There is one special locale name, default, that's used if no locale cascade exists for a particular locale. The default locale cascade is:

default => [ 'en' ]

That is, if message text is not available in the desired locale, en text will be returned instead (assuming it exists).

This method returns the default locale cascade as a reference to a hash of locale/arrayref pairs (in scalar context) or a list of locale/arrayref pairs (in list context).

load_all_messages [PARAMS]

Load all localized message text from the __DATA__ section of the class specified by PARAMS name/value pairs. Valid PARAMS are:

from_class CLASS

The name of the class from which to load localized message text. Defaults to the name of the class from which this method was called.

CONSTRUCTOR

new [PARAMS]

Constructs a new Rose::HTML::Object::Message::Localizer object based on PARAMS, where PARAMS are name/value pairs. Any object method is a valid parameter name.

OBJECT METHODS

add_localized_error PARAMS

Add a new localized error message. PARAMS are name/value pairs. Valid PARAMS are:

id ID

An integer error id. Error ids from 0 to 29,999 are reserved for built-in errors. Negative error ids are reserved for internal use. Please use error ids 30,000 or higher for your errors. If omitted, the generate_error_id method will be called to generate a value.

name NAME

An error name. This parameter is required. Error names may contain only the characters [A-Z0-9_] and must be unique among all error names.

add_localized_message PARAMS

Add a new localized message. PARAMS are name/value pairs. Valid PARAMS are:

id ID

An integer message id. Message ids from 0 to 29,999 are reserved for built-in messages. Negative message ids are reserved for internal use. Please use message ids 30,000 or higher for your messages. If omitted, the generate_message_id method will be called to generate a value.

name NAME

A message name. This parameter is required. Message names may contain only the characters [A-Z0-9_] and must be unique among all message names.

default_variant

Returns the name of the default variant: default. See the variants subsection of the localized text section above for more information on variants.

error_class [CLASS]

Get or set the name of the Rose::HTML::Object::Error-derived class used to store each error. The default value is Rose::HTML::Object::Error. To change the default, override the init_error_class method in your subclass and return a different class name.

errors_class [CLASS]

Get or set the name of the Rose::HTML::Object::Errors-derived class used to store and track error ids and symbolic constant names. The default value is Rose::HTML::Object::Errors. To change the default, override the init_errors_class method in your subclass and return a different class name.

locale [LOCALE]

Get or set the locale assumed by the localizer in the absence of an explicit locale argument. Defaults to the value returned by the default_locale class method.

message_class [CLASS]

Get or set the name of the Rose::HTML::Object::Message-derived class used to store each message. The default value is Rose::HTML::Object::Message::Localized. To change the default, override the init_message_class method in your subclass and return a different class name.

messages_class [CLASS]

Get or set the name of the Rose::HTML::Object::Messages-derived class used to store and track message ids and symbolic constant names. The default value is Rose::HTML::Object::Messages. To change the default, override the init_messages_class method in your subclass and return a different class name.

generate_error_id

Returns a new integer error id. This method will not return the same value more than once.

generate_message_id

Returns a new integer message id. This method will not return the same value more than once.

get_error_id NAME

This method is a proxy for the errors_class's get_error_id method.

get_error_name ID

This method is a proxy for the errors_class's get_error_name method.

get_localized_message_text PARAMS

Returns localized message text based on PARAMS name/value pairs. Valid PARAMS are:

id ID

An integer message id. If a name is not passed, then the name corresponding to this message id will be looked up using the get_message_name method.

name NAME

The message name. If this parameter is not passed, then the id parameter must be passed.

locale LOCALE

The locale of the localized message text. Defaults to the localizer's locale() if omitted.

from_class CLASS

The name of the class from which to attempt to load the localized message text. If omitted, it defaults to the name of the package from which this method was called.

get_message_id NAME

This method is a proxy for the messages_class's get_message_id method.

get_message_name ID

This method is a proxy for the messages_class's get_message_name method.

load_messages_from_file [ FILE | PARAMS ]

Load localized message text, in the format described in the LOCALIZED TEXT section above, from a file on disk. Note that this method only loads message text. The message ids must already exist in the messages_class.

If a single FILE argument is passed, it is taken as the value for the file parameter. Otherwise, PARAMS name/value pairs are expected. Valid PARAMS are:

file PATH

The path to the file. This parameter is required.

locales [ LOCALE | ARRAYREF ]

A locale or a reference to an array of locales. If provided, only message text for the specified locales will be loaded. If omitted, all locales will be loaded.

names [ NAME | ARRAYREF | REGEX ]

Only load text for the specified messages. Pass either a single message NAME, a reference to an array of names, or a regular expression that matches the names of the messages you want to load.

locale [LOCALE]

Get or set the locale of this localizer. This locale is used by several methods when a locale is not explicitly provided. The default value is determined by the default_locale class method.

locale_cascade [PARAMS]

Get or set the locale cascade. PARAMS are locale/arrayref pairs. Each referenced array contains a list of locales to check, in the order specified, when message text is not available in the desired locale. There is one special locale name, default, that's used if no locale cascade exists for a particular locale. The default locale cascade is determined by the default_locale_cascade class method.

This method returns the locale cascade as a reference to a hash of locale/arrayref pairs (in scalar context) or a list of locale/arrayref pairs (in list context).

localize_message PARAMS

Localize a message, returning the appropriately localized and processed message text. Valid PARAMS name/value pairs are:

args HASHREF

A reference to a hash of message arguments. If omitted, the message's args are used.

locale LOCALE

The locale. If omitted, the message's locale is used.

message MESSAGE

The Rose::HTML::Object::Message-derived message object. This parameter is required.

variant VARIANT

The message variant. If omitted, the select_variant_for_message method is called, passing the message id, args, and locale.

This method performs a nested loop to search for localized message text: for each locale (including any locale_cascade), for each variant (including any variant_cascade), for each parent field, form, or generic parent object (considered in that order), look for message text by calling the get_localized_message_text method.

message_for_error_id PARAMS

Given an error id, return the corresponding message_class object. The default implementation simply looks for a message with the same integer id as the error. Valid PARAMS name/value pairs are:

error_id ID

The integer error id. This parameter is required.

args HASHREF

A reference to a hash of name/value pairs to be used as the message arguments.

parent [OBJECT]

Get or set a weakened reference to the localizer's parent object.

select_variant_for_count PARAMS

Select and return a variant name based on PARAMS name/value pairs. Valid PARAMS are:

args HASHREF

A reference to a hash of message arguments.

count INTEGER

The count for which to select a variant. This parameter is required.

locale LOCALE

The locale of the localized message text. Defaults to the localizer's locale() if omitted.

The default implementation looks only at the count parameter and returns the following values based on it (the * below means "any other value"):

count   variant
-----   -------
0       zero
1       one
2       two
*       plural

See the variants section for more information on this and other variant-related methods

select_variant_for_message PARAMS

Select and return a variant name based on PARAMS name/value pairs. Valid PARAMS are:

args HASHREF

A reference to a hash of message arguments.

id MESSAGEID

The message id.

locale LOCALE

The locale of the localized message text. Defaults to the localizer's locale() if omitted.

If args contains a count parameter, then the select_variant_for_count method is called, passing all arguments plus the count value as its own parameter, and the variant it returns is returned from this method.

If args contains a variant parameter, then the value of that parameter is returned.

Otherwise, the default_variant is returned.

set_localized_message_text PARAMS

Set the localized text for a message. Valid PARAMS name/value pairs are:

id ID

An integer message id. If a name is not passed, then the name corresponding to this message id will be looked up using the get_message_name method.

name NAME

The message name. If this parameter is not passed, then the id parameter must be passed.

locale LOCALE

The locale of the localized message text. Defaults to the localizer's locale.

text TEXT

The localized message text.

variant VARIANT

The message variant, if any. See the LOCALIZED TEXT section above for more information about variants.

variant_cascade [PARAMS]

Return a reference to an array of variant names under which to look for localized text, assuming the requested variant is not available in the context specified in PARAMS name/value pairs. Valid params are:

args HASHREF

A reference to a hash of message arguments.

locale LOCALE

The locale of the desired localized message text.

message MESSAGE

The Rose::HTML::Object::Message-derived message object.

variant VARIANT

The originally requested message variant.

The default implementation looks only at the variant parameter and returns references to arrays containing the following variant lists based on it:

variant   variant cascade
-------   ---------------
zero      plural, default
one       default
two       plural, default
few       plural, default
many      plural, default
plural    default

The array references returned should be treated as read-only.

AUTHOR

John C. Siracusa (siracusa@gmail.com)

LICENSE

Copyright (c) 2010 by John C. Siracusa. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.