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:

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.

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 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:

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 variantmessage 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.)

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:

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.

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.

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.

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.

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.

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.

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).

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.

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.

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.

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.

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.

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:

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).

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.

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: