|
NAMERose::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; } DESCRIPTIONRose::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 ERRORSMessages 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 TEXTBroadly 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. LOCALESLocalization 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
CONSTRUCTOR
OBJECT METHODS
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.
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
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.
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. AUTHORJohn C. Siracusa (siracusa@gmail.com)LICENSECopyright (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.
Visit the GSP FreeBSD Man Page Interface. |