File Coverage

blib/lib/Rose/HTML/Object/Message/Localizer.pm
Criterion Covered Total %
statement 362 412 87.8
branch 151 232 65.0
condition 86 150 57.3
subroutine 56 62 90.3
pod 21 40 52.5
total 676 896 75.4


line stmt bran cond sub pod time code
1              
2             use strict;
3 44     44   99574  
  44         92  
  44         1095  
4             use Carp;
5 44     44   185 use Clone::PP();
  44         67  
  44         1895  
6 44     44   14778 use Scalar::Util();
  44         26662  
  44         723  
7 44     44   225  
  44         84  
  44         641  
8             use Rose::HTML::Object::Errors();
9 44     44   8217 use Rose::HTML::Object::Messages();
  44         96  
  44         797  
10 44     44   211  
  44         119  
  44         856  
11             use base 'Rose::Object';
12 44     44   191  
  44         78  
  44         17856  
13             our $VERSION = '0.606';
14              
15             our $Debug = 0;
16              
17             use constant DEFAULT_VARIANT => 'default';
18 44     44   8396  
  44         81  
  44         2977  
19             #
20             # Object data
21             #
22              
23             use Rose::Object::MakeMethods::Generic
24             (
25             'hash --get_set_init' =>
26 44         497 [
27             'localized_messages_hash',
28             ],
29              
30             'scalar --get_set_init' =>
31             [
32             'locale',
33             'message_class',
34             'messages_class',
35             'errors_class',
36             'error_class',
37             ],
38             );
39 44     44   17560  
  44         209488  
40             #
41             # Class data
42             #
43              
44             use Rose::Class::MakeMethods::Generic
45             (
46             inheritable_hash => 'default_locale_cascade',
47 44         288 );
48 44     44   20649  
  44         154  
49             use Rose::Class::MakeMethods::Generic
50             (
51             inheritable_scalar =>
52 44         461 [
53             'default_locale',
54             '_auto_load_messages',
55             '_auto_load_locales',
56             ],
57             );
58 44     44   7883  
  44         101  
59             __PACKAGE__->default_locale('en');
60             __PACKAGE__->default_locale_cascade('default' => [ 'en' ]);
61              
62             #
63             # Class methods
64             #
65              
66              
67 0     0 1 0 #
68             # Object methods
69             #
70              
71              
72             {
73 27     27 0 307 my($self) = shift;
74             my $class = ref($self) || $self;
75             return $class->default_locale_cascade;
76             }
77 26     26 0 68  
78 26   33     149 {
79 26         131 my($self) = shift;
80              
81             my $hash = $self->{'locale_cascade'} ||= ref($self)->init_locale_cascade;
82              
83             if(@_)
84 704     704 1 1027 {
85             if(@_ == 1)
86 704   66     1443 {
87             return $hash->{$_[0]};
88 704 50       1652 }
89             elsif(@_ % 2 == 0)
90 704 50       1137 {
    0          
91             for(my $i = 0; $i < @_; $i += 2)
92 704         2217 {
93             $hash->{$_[$i]} = $_[$i + 1];
94             }
95             }
96 0         0 else { croak "Odd number of arguments passed to locale_cascade()" }
97             }
98 0         0  
99             return wantarray ? %$hash : $hash;
100             }
101 0         0  
102             {
103             my($self) = shift;
104 0 0       0 my $class = ref($self) || $self;
105             return $class->default_locale;
106             }
107              
108              
109 33     33 0 1463  
110 33   33     125 {
111 33         232 my($self) = shift;
112             return Scalar::Util::weaken($self->{'parent'} = shift) if(@_);
113             return $self->{'parent'};
114 26     26 0 332 }
115 35     35 0 1967  
116 9     9 0 73 {
117 28     28 0 359 my($self, %args) = @_;
118              
119 0     0 0 0 my $message = $args{'message'};
120              
121             return $message unless($message->can('text') && $message->can('id'));
122             return $message->text if($message->is_custom);
123 0     0 1 0  
124 0 0       0 my $parent = $message;
125 0         0  
126             if($parent->can('parent'))
127             {
128             $parent = $parent->parent;
129             }
130 2198     2198 1 7171  
131             if($parent && $parent->isa('Rose::HTML::Object::Error'))
132 2198         3607 {
133             $parent = $parent->parent;
134 2198 50 33     9864 }
135 2198 100       5131  
136             my $calling_class = $parent ? ref($parent) : $args{'caller'} || (caller)[0];
137 351         625  
138             my $first_parent = $parent;
139 351 50       906  
140             my $args = $args{'args'} || $message->args;
141 351         675 my $locale = $args{'locale'} || $message->locale || $self->locale;
142              
143             my $id = $message->id;
144 351 100 100     1464  
145             my $variant = $args{'variant'} ||=
146 132         403 $self->select_variant_for_message(id => $id,
147             args => $args,
148             locale => $locale);
149 351 100 33     850  
150             my $locale_cascade = $self->locale_cascade($locale) ||
151 351         552 $self->locale_cascade('default') || [];
152              
153 351   33     770 foreach my $try_locale ($locale, @$locale_cascade)
154 351   33     861 {
155             my $variant_cascade =
156 351         611 $self->variant_cascade(locale => $try_locale,
157             variant => $variant,
158 351   33     1157 message => $message,
159             args => $args) || [];
160              
161             foreach my $try_variant ($variant, @$variant_cascade)
162             {
163 351   50     767 my $text =
164             $self->get_localized_message_text(
165             id => $id,
166 351         724 locale => $try_locale,
167             variant => $try_variant,
168 376   50     822 from_class => $calling_class);
169              
170             $parent = $first_parent;
171              
172             # Look for messages in parents
173             while(!defined $text && $parent)
174 376         966 {
175             $parent = $parent->can('parent_field') ? $parent->parent_field :
176 391         1011 $parent->can('parent_form') ? $parent->parent_form :
177             $parent->can('parent') ? $parent->parent :
178             undef;
179              
180             if($parent)
181             {
182             $text =
183 391         645 $self->get_localized_message_text(
184             id => $id,
185             locale => $try_locale,
186 391   100     1084 variant => $try_variant,
187             from_class => ref($parent));
188 44 50       201 }
    50          
    100          
189             }
190              
191             return $self->process_placeholders($text, $args) if(defined $text);
192             }
193 44 100       197 }
194              
195 4         18 return undef;
196             }
197              
198             # All this to avoid making Scalar::Defer a prerequisite....sigh.
199             {
200             no warnings 'uninitialized';
201             return $_[0] unless(ref $_[0] eq 'CODE');
202             return $_[0]->();
203             }
204 391 100       1127  
205             {
206             my($self, $text, $args) = @_;
207              
208 0         0 my %args = $args ? %$args : ();
209              
210             # Values will be modified in-place
211             foreach my $value (values %args)
212             {
213             if(my $ref = ref($value))
214 44     44   39599 {
  44         116  
  44         6754  
215 132 100   132   321 if($ref eq 'ARRAY')
216 38         81 {
217             $value = [ map { _evaluate($_) } @$value ];
218             }
219             else
220             {
221 351     351 0 662 $value = _evaluate($value);
222             }
223 351 50       1034 }
224             }
225              
226 351         680 no warnings 'uninitialized';
227              
228 196 100       482 for($text)
229             {
230 68 100       135 # Process [@123(...)] and [@foo(...)] placeholders
231             s{ ( (?:\\.|[^\[]*)* ) \[ \@ (\d+ | [a-zA-Z]\w* ) (?: \( (.*) \) )? \] }
232 28         53 { $1 . join(defined $3 ? $3 : ', ', ref $args{$2} ? @{$args{$2}} : $args{$2}) }gex;
  92         144  
233              
234             # Process [123] and [foo] placeholders
235             s{ ( (?:\\.|[^\[]*)* ) \[ (\d+ | [a-zA-Z]\w* ) \] }{$1$args{$2}}gx;
236 40         69  
237             # Unescape escaped opening square brackets
238             s/\\\[/[/g;
239             }
240              
241 44     44   286 return $text;
  44         92  
  44         30392  
242             }
243 351         614  
244              
245              
246 351 100       897 {
  15 50       91  
  15         81  
247             my($self, %args) = @_;
248              
249 351         1128 my $error_id = $args{'error_id'};
250             my $msg_class = $args{'msg_class'} || $self->message_class;
251             my $args = $args{'args'} || [];
252 351         739  
253             my $messages_class = $self->messages_class;
254              
255 351         3313 if(defined $messages_class->get_message_name($error_id))
256             {
257             return $msg_class->new(id => $error_id, args => $args);
258 395     395 1 1105 }
259 1     1 1 12 elsif($error_id !~ /^\d+$/)
260             {
261 0     0 1 0 croak "Unknown error id: $error_id";
262 4     4 1 25 }
263              
264             return $msg_class->new(args => $args);
265             }
266 70     70 1 230  
267             {
268 70         142 my($self, %args) = @_;
269 70   66     200  
270 70   100     291 my $args = $args{'args'};
271              
272 70         223 return $args->{'variant'} if($args->{'variant'});
273              
274 70 50       593 if(defined(my $count = $args->{'count'}))
    0          
275             {
276 70         885 return $self->select_variant_for_count(%args, count => $count);
277             }
278              
279             return DEFAULT_VARIANT;
280 0         0 }
281              
282             {
283 0         0 my($self, %args) = @_;
284              
285             my $locale = $args{'locale'} || $self->locale;
286             my $count = abs($args{'count'});
287              
288 351     351 1 996 # Possibilities:
289             #
290 351         545 # zero
291             # one (singular)
292 351 100       730 # two (dual)
293             # few (paucal)
294 345 100       679 # many
295             # plural
296 12         58  
297             # No default judgements on "few" and "many"
298             return $count == 0 ? 'zero' :
299 333         980 $count == 1 ? 'one' :
300             $count == 2 ? 'two' :
301             'plural';
302             }
303              
304 12     12 1 48 my %Variant_Cascade =
305             (
306 12   33     46 'zero' => [ 'plural', DEFAULT_VARIANT ],
307 12         28 'one' => [ DEFAULT_VARIANT ],
308             'two' => [ 'plural', DEFAULT_VARIANT ],
309             'few' => [ 'plural', DEFAULT_VARIANT ],
310             'many' => [ 'plural', DEFAULT_VARIANT ],
311             'plural' => [ DEFAULT_VARIANT ],
312             );
313              
314             # Trying to avoid repeated anonymous array generation that
315             # might(?) result from using literal [] below
316             my @None;
317              
318             {
319 12 100       81 my($self, %args) = @_;
    100          
    100          
320             return $Variant_Cascade{$args{'variant'}} ||
321             \@None;
322             }
323              
324             {
325             my($self, $name, $locale, $variant) = @_;
326              
327             my $msgs = $self->localized_messages_hash;
328              
329             $variant ||= DEFAULT_VARIANT;
330              
331             no warnings 'uninitialized';
332             if(exists $msgs->{$name} && exists $msgs->{$name}{$locale})
333             {
334             if(ref $msgs->{$name}{$locale})
335             {
336             return $msgs->{$name}{$locale}{$variant} ? 1 : 0;
337             }
338             elsif($variant eq DEFAULT_VARIANT)
339             {
340             return 1;
341 376     376 1 1039 }
342 376   100     1719 }
343              
344             return 0;
345             }
346              
347             {
348 10293     10293 0 14215 my($self, $name) = @_;
349              
350 10293         18204 my $msgs = $self->localized_messages_hash;
351              
352 10293   50     68643 return wantarray ? () : [] unless(ref $msgs->{$name});
353              
354 44     44   317 return wantarray ? (sort keys %{$msgs->{$name}}) :
  44         98  
  44         43334  
355 10293 100 100     26553 [ sort keys %{$msgs->{$name}} ];
356             }
357 7713 100       13855  
    50          
358              
359 2572 50       5801 {
360             my($self, %args) = @_;
361              
362             my $id = $args{'id'};
363 0         0 my $name = $args{'name'};
364             my $locale = $args{'locale'} || $self->locale;
365             my $text = $args{'text'};
366             my $variant = $args{'variant'};
367 7721         14033  
368             croak "Missing new localized message text" unless(defined $text);
369              
370             if($name =~ /[^A-Z0-9_]/)
371             {
372 0     0 0 0 croak "Message names must be uppercase and may contain only ",
373             "letters, numbers, and underscores";
374 0         0 }
375              
376 0 0       0 if($id && $name)
    0          
377             {
378 0         0 unless($name eq $self->messages_class->get_message_name($id))
379 0 0       0 {
  0         0  
380             croak "The message id '$id' does not match the name '$name'";
381             }
382 7     7 0 67 }
383             elsif(!defined $name)
384             {
385             croak "Missing message id" unless(defined $id);
386 102     102 1 432 $name = $self->messages_class->get_message_name($id)
387             or croak "No such message id - '$id'";
388 102         198 }
389 102         175 elsif(!defined $id)
390 102   66     301 {
391 102         159 croak "Missing message name" unless(defined $name);
392 102         156 $id = $self->messages_class->get_message_id($name)
393             or croak "No such message name - '$name'";
394 102 50       206 }
395              
396 102 50       323 unless(ref $text eq 'HASH')
397             {
398 0         0 $text = { $locale => $text };
399             }
400              
401             my $msgs = $self->localized_messages_hash;
402 102 50 33     449  
    50          
    50          
403             while(my($l, $t) = each(%$text))
404 0 0       0 {
405             $Debug && warn qq($self - Adding text $name),
406 0         0 ($variant ? "($variant)" : ''),
407             qq( [$l] - "$t"\n);
408              
409             if($variant)
410             {
411 0 0       0 if(ref $msgs->{$name}{$l})
412 0 0       0 {
413             $msgs->{$name}{$l}{$variant} = "$t"; # force stringification
414             }
415             else
416             {
417 102 50       247 my $existing = $msgs->{$name}{$l};
418 102 50       299  
419             if(defined $existing)
420             {
421             $msgs->{$name}{$l} = {};
422 102 100       283 $msgs->{$name}{$l}{DEFAULT_VARIANT()} = $existing;
423             }
424 100         285  
425             $msgs->{$name}{$l}{$variant} = "$t"; # force stringification
426             }
427 102         256 }
428             else
429 102         1185 {
430             if(ref ref $msgs->{$name}{$l})
431 104 0       286 {
    50          
432             $msgs->{$name}{$l}{DEFAULT_VARIANT()} = "$t"; # force stringification
433             }
434             else
435 104 100       250 {
436             $msgs->{$name}{$l} = "$t"; # force stringification
437 7 100       20 }
438             }
439 5         26 }
440              
441             return $id;
442             }
443 2         6  
444             {
445 2 50       5 my($self) = shift;
446              
447 0         0 if($Rose::HTML::Object::Exporter::Target_Class)
448 0         0 {
449             $self->messages_class->import(@_);
450             }
451 2         12 else
452             {
453             local $Rose::HTML::Object::Exporter::Target_Class = (caller)[0];
454             $self->messages_class->import(@_);
455             }
456 97 50       267 }
457              
458 0         0 {
459             my($self) = shift;
460              
461             @_ = (':all') unless(@_);
462 97         453  
463             if($Rose::HTML::Object::Exporter::Target_Class)
464             {
465             $self->errors_class->import(@_);
466             }
467 102         362 else
468             {
469             local $Rose::HTML::Object::Exporter::Target_Class = (caller)[0];
470             $self->errors_class->import(@_);
471             }
472 2     2 0 19 }
473              
474 2 50       7 {
475             my($self, %args) = @_;
476 0         0  
477             my $id = $args{'id'} || $self->generate_message_id;
478             my $name = $args{'name'} || croak "Missing name for new localized message";
479             my $locale = $args{'locale'} || $self->locale;
480 2         7 my $text = $args{'text'};
481 2         8  
482             croak "Missing new localized message text" unless(defined $text);
483              
484             if($name =~ /[^A-Z0-9_]/)
485             {
486             croak "Message names must be uppercase and may contain only ",
487 1     1 0 8 "letters, numbers, and underscores";
488             }
489 1 50       4  
490             unless(ref $text eq 'HASH')
491 1 50       3 {
492             $text = { $locale => $text };
493 0         0 }
494              
495             my $msgs = $self->localized_messages_hash;
496             my $msgs_class = $self->messages_class;
497 1         3  
498 1         4 my $const = "${msgs_class}::$name";
499              
500             if(defined &$const)
501             {
502             croak "A constant or subroutine named $name already exists in the class $msgs_class";
503             }
504 18     18 1 499  
505             $msgs_class->add_message($name, $id);
506 18   66     108  
507 18   66     318 while(my($l, $t) = each(%$text))
508 17   33     222 {
509 17         130 $Debug && warn qq($self - Adding message $name ($l) = "$t"\n);
510             $msgs->{$name}{$l} = "$t"; # force stringification
511 17 100       133 }
512              
513 16 50       70 return $id;
514             }
515 0         0  
516             use constant NEW_ID_OFFSET => 100_000;
517              
518             our $Last_Generated_Message_Id = NEW_ID_OFFSET;
519 16 100       65 our $Last_Generated_Error_Id = NEW_ID_OFFSET;
520              
521 5         19 {
522             my($self) = shift;
523              
524 16         61 my $messages_class = $self->messages_class;
525 16         133 my $errors_class = $self->errors_class;
526              
527 16         117 my $new_id = $Last_Generated_Error_Id;
528             $new_id++ while($messages_class->message_id_exists($new_id) ||
529 16 100       140 $errors_class->error_id_exists($new_id));
530              
531 1         168 return $Last_Generated_Message_Id = $new_id;
532             }
533              
534 15         102 {
535             my($self) = shift;
536 15         75  
537             my $errors_class = $self->errors_class;
538 26 50       72 my $messages_class = $self->messages_class;
539 26         111  
540             my $new_id = $Last_Generated_Error_Id;
541             $new_id++ while($errors_class->error_id_exists($new_id) ||
542 15         54 $messages_class->message_id_exists($new_id));
543              
544             return $Last_Generated_Error_Id = $new_id;
545 44     44   399 }
  44         104  
  44         21015  
546              
547             {
548             my($self, %args) = @_;
549              
550             my $id = $args{'id'} || $self->generate_error_id;
551             my $name = $args{'name'} or croak "Missing localized error name";
552 16     16 1 34  
553             my $errors_class = $self->errors_class;
554 16         62  
555 16         96 my $const = "${errors_class}::$name";
556              
557 16         50 if(defined &$const)
558 16   100     101 {
559             croak "A constant or subroutine named $name already exists in the class $errors_class";
560             }
561 16         222  
562             $errors_class->add_error($name, $id);
563              
564             return $id;
565             }
566 3     3 1 8  
567             {
568 3         7 my($self, $code) = @_;
569 3         23 my $msgs = $self->localized_messages_hash;
570             return $code->($msgs) if($code);
571 3         17 require Data::Dumper;
572 3   100     14 return Data::Dumper::Dumper($msgs);
573             }
574              
575 3         27 {
576             my($self, %args) = @_;
577              
578             my $id = $args{'id'};
579             my $name = $args{'name'};
580 6     6 1 57 my $locale = $args{'locale'} || $self->locale;
581             my $variant = $args{'variant'} || DEFAULT_VARIANT;
582 6   66     21 my $from_class = $args{'from_class'};
583 6 100       83  
584             $from_class ||= (caller)[0];
585 5         11  
586             $name ||= $self->get_message_name($id);
587 5         29  
588             my $msgs = $self->localized_messages_hash;
589 5 100       27  
590             # Try this twice: before and after loading messages
591 1         73 foreach my $try (1, 2)
592             {
593             no warnings 'uninitialized';
594 4         16 if(exists $msgs->{$name} && exists $msgs->{$name}{$locale})
595             {
596 4         10 if(ref $msgs->{$name}{$locale} && exists $msgs->{$name}{$locale}{$variant})
597             {
598             return $msgs->{$name}{$locale}{$variant};
599             }
600              
601 0     0 0 0 return $msgs->{$name}{$locale} if($variant eq DEFAULT_VARIANT);
602 0         0 }
603 0 0       0  
604 0         0 last if($try == 2);
605 0         0  
606             $self->load_localized_message($name, $locale, $variant, $from_class);
607             }
608              
609             return undef;
610 395     395 1 1460 }
611              
612 395         683 # ([A-Z0-9_]+) -> ([A-Z0-9_]+) (?: \( \s* (\w[-\w]*) \s* \) )?
613 395         613 # ([A-Z0-9_]+) -> ([A-Z0-9_]+)(?:\(\s*([-\w]+)\s*\))?
614 395   33     790 my $Locale_Declaration = qr{^\s* \[% \s* LOCALE \s* (\S+) \s* %\] \s* (?: \#.*)?$}x;
615 395   50     728 my $Start_Message = qr{^\s* \[% \s* START \s+ ([A-Z0-9_]+)(?:\(\s*([-\w]+)\s*\))? \s* %\] \s* (?: \#.*)?$}x;
616 395         533 my $End_Message = qr{^\s* \[% \s* END \s+ ([A-Z0-9_]+)(?:\(\s*([-\w]+)\s*\))?? \s* %\] \s* (?: \#.*)?$}x;
617             my $Message_Spec = qr{^ \s* ([A-Z0-9_]+)(?:\(\s*([-\w]+)\s*\))? \s* = \s* "((?:[^"\\]+|\\.)*)" \s* (?: \#.*)? $}x;
618 395   33     712 my $Comment_Or_Blank = qr{^ \s* \# | ^ \s* $}x;
619             my $End_Messages = qr{^=\w|^\s*__END__};
620 395   33     1286  
621             my %Data_Pos;
622 395         3926  
623             {
624             my($self, $name, $locale, $variant, $from_class) = @_;
625 395         2989  
626             $from_class ||= $self->messages_class;
627 44     44   333  
  44         101  
  44         21948  
628 490 100 100     1735 if($self->localized_message_exists($name, $locale, $variant))
629             {
630 383 100 100     889 return $self->get_localized_message_text(name => $name,
631             locale => $locale,
632 12         42 variant => $variant);
633             }
634              
635 371 100       1252 no strict 'refs';
636             my $fh = \*{"${from_class}::DATA"};
637              
638 139 100       351 if(fileno($fh))
639             {
640 95         287 local $/ = "\n";
641              
642             if($Data_Pos{$from_class})
643 44         209 {
644             # Rewind to the start of the __DATA__ section
645             seek($fh, $Data_Pos{$from_class}, 0);
646             }
647             else
648             {
649             $Data_Pos{$from_class} = tell($fh);
650             }
651              
652             my $text = $self->load_messages_from_fh(fh => $fh,
653             locales => $locale,
654             variants => $variant,
655             names => $name,
656             force_utf8 => 1);
657             return $text if(defined $text);
658             }
659 10292     10292 0 15562  
660             no strict 'refs';
661 10292   33     15333  
662             my @classes = @{"${from_class}::ISA"};
663 10292 50       15218 my %seen;
664              
665 0         0 while(@classes)
666             {
667             my $class = pop(@classes);
668             next if($seen{$class}++);
669             #$Debug && warn "$self SEARCHING $class FOR $name ($locale)\n";
670 44     44   308 my $msg = $self->load_localized_message($name, $locale, $variant, $class);
  44         84  
  44         4386  
671 10292         13040 return $msg if(defined $msg);
  10292         21792  
672             push(@classes, grep { !$seen{$_} } @{"${class}::ISA"});
673 10292 100       17537 }
674              
675 944         3139 return undef;
676             }
677 944 100       1936  
678             {
679             my($self_or_class) = shift;
680 912         7048  
681             my $class = ref($self_or_class) || $self_or_class;
682              
683             if(@_)
684 32         102 {
685             my $locales = (@_ == 1 && ref $_[0] eq 'ARRAY') ? [ @{$_[0]} ] : [ @_ ];
686             return $class->_auto_load_locales($locales);
687 944         3133 }
688              
689             my $locales = $class->_auto_load_locales;
690             return wantarray ? @$locales : $locales if(defined $locales);
691              
692 944 100       3850 if(my $locales = $ENV{'RHTMLO_LOCALES'})
693             {
694             $locales = [ split(/\s*,\s*/, $locales) ] unless(ref $locales);
695 44     44   241 $class->_auto_load_locales($locales);
  44         111  
  44         20040  
696             return wantarray ? @$locales : $locales;
697 10241         11578 }
  10241         24109  
698 10241         12410  
699             return wantarray ? () : [];
700 10241         16260 }
701              
702 10197         14184 {
703 10197 50       19734 my($self_or_class) = shift;
704              
705 10197         17951 my $class = ref($self_or_class) || $self_or_class;
706 10197 100       15631  
707 10157         11377 if(@_)
  5054         14757  
  10157         22679  
708             {
709             return $class->_auto_load_messages(@_);
710 10201         16847 }
711              
712             my $ret = $class->_auto_load_messages;
713             return $ret if(defined $ret);
714              
715 4     4 0 28 if(($ENV{'MOD_PERL'} && (!defined($ENV{'RHTMLO_PRIME_CACHES'}) || $ENV{'RHTMLO_PRIME_CACHES'})) ||
716             $ENV{'RHTMLO_PRIME_CACHES'})
717 4   33     12 {
718             return $class->_auto_load_messages(1);
719 4 100       10 }
720              
721 1 50 33     8 return undef;
  0         0  
722 1         4 }
723              
724             {
725 3         11 my($class) = shift;
726 3 50       62  
    100          
727             my %args;
728 2 100       8  
729             if(@_ > 1)
730 1 50       9 {
731 1         5 %args = @_;
732 1 50       9 }
733             else
734             {
735 1 50       3 $args{'from_class'} = $_[0];
736             }
737              
738             my $from_class = $args{'from_class'} || (caller)[0];
739              
740 128     128 1 4717 no strict 'refs';
741             my $fh = \*{"${from_class}::DATA"};
742 128   33     435  
743             if(fileno($fh))
744 128 50       362 {
745             local $/ = "\n";
746 0         0  
747             if($Data_Pos{$from_class})
748             {
749 128         807 # Rewind to the start of the __DATA__ section
750 128 50       2969 seek($fh, $Data_Pos{$from_class}, 0);
751             }
752 128 100 0     1341 else
      33        
      66        
753             {
754             $Data_Pos{$from_class} = tell($fh);
755 1         4 }
756              
757             my $locales = $class->auto_load_locales;
758 127         550  
759             $Debug && warn "$class - Loading messages from DATA section of $from_class\n";
760             $class->load_messages_from_fh(fh => $fh, locales => $locales, force_utf8 => 1);
761             }
762             }
763 3     3 1 80  
764             {
765 3         6 my($self) = shift;
766              
767 3 100       9 my %args;
768             if(@_ == 1)
769 1         3 {
770             $args{'file'} = shift;
771             }
772             elsif(@_ > 1)
773 2         5 {
774             croak "Odd number of arguments passed to load_messages_from_file()"
775             if(@_ % 2 != 0);
776 3   66     13 %args = @_;
777             }
778 44     44   324  
  44         92  
  44         20725  
779 3         4 my $file = delete $args{'file'} or croak "Missing file argument";
  3         11  
780              
781 3 50       11 open($args{'fh'}, $file) or croak "Could no open messages file '$file' - $!";
782             $self->load_messages_from_fh(%args);
783 3         14 close($args{'fh'});
784             }
785 3 100       17  
786             {
787             my($self, %args) = @_;
788 2         18  
789             my($fh, $locales, $variants, $msg_names) = @args{qw(fh locales variants names)};
790              
791             binmode($fh, ':utf8') if($args{'force_utf8'});
792 1         4  
793             if(ref $locales eq 'ARRAY')
794             {
795 3         205 $locales = @$locales ? { map { $_ => 1} @$locales } : undef;
796             }
797 3 50       7 elsif($locales && !ref $locales)
798 3         11 {
799             $locales = { $locales => 1 };
800             }
801              
802             if(ref $variants eq 'ARRAY')
803             {
804 2     2 1 60 $variants = @$variants ? { map { $_ => 1} @$variants } : undef;
805             }
806 2         5 elsif($variants && !ref $variants)
807 2 50       6 {
    0          
808             $variants = { $variants => 1 };
809 2         8 }
810              
811             my $msg_re;
812              
813 0 0       0 if($msg_names)
814             {
815 0         0 if(!ref $msg_names)
816             {
817             $msg_names = { $msg_names => 1 };
818 2 50       8 }
819             elsif(ref $msg_names eq 'ARRAY')
820 2 50       86 {
821 2         17 $msg_names = { map { $_ => 1 } @$msg_names };
822 2         27 }
823             elsif(ref $msg_names eq 'Regexp')
824             {
825             $msg_re = $msg_names;
826             $msg_names = undef;
827 950     950 0 3623 }
828             }
829 950         2253  
830             my @text;
831 950 100       4421 my $in_locale = '';
832             my $in_msg = '';
833 950 100 66     3670 my $variant = '';
    100          
834             my $text = '';
835 3 100       9  
  3         9  
836             my $pos = tell($fh);;
837              
838             no strict 'refs';
839 944         2168  
840             local $_;
841              
842 950 50 66     3355 while(<$fh>)
    100          
843             {
844 0 0       0 last if(/$End_Messages/o);
  0         0  
845              
846             #$Debug && warn "PROC: $_";
847              
848 944         1657 if(/$End_Message/o && (!$2 || $2 eq $in_msg))
849             {
850             if(!$msg_names || $msg_names->{$in_msg} || ($msg_re && $in_msg =~ /$msg_re/))
851 950         1259 {
852             for($text)
853 950 100       1441 {
854             s/\A(\s*\n)+//;
855 944 50       1521 s/(\s*\n)+\z//;
    0          
    0          
856             }
857 944         1582  
858             #if($args{'force_utf8'} && !utf8::is_utf8($text))
859             #{
860             # require Encode;
861 0         0 # $text = Encode::decode('UTF-8', $text);
  0         0  
862             #}
863              
864             $self->set_localized_message_text(name => $in_msg,
865 0         0 locale => $in_locale,
866 0         0 variant => $variant,
867             text => $text);
868             }
869              
870 950         1420 $text = '';
871 950         1356 $in_msg = '';
872 950         1134 $variant = '';
873 950         1122 }
874 950         1162 elsif($in_msg)
875             {
876 950         1523 $text .= $_;
877             }
878 44     44   317 elsif(/$Locale_Declaration/o)
  44         96  
  44         20838  
879             {
880 950         1293 $in_locale = $1;
881             }
882 950         11883 elsif(/$Message_Spec/o)
883             {
884 32215 100       131771 if((!$locales || $locales->{$in_locale}) &&
885             (!$variants || $variants->{$2 || DEFAULT_VARIANT}) &&
886             (!$msg_names || $msg_names->{$1}))
887             {
888 31301 100 33     160154 my $name = $1;
    100 66        
    100          
    100          
    100          
    50          
889             $variant = $2;
890 4 0 33     11 my $text = $3;
      0        
      0        
891              
892 4         10 for($text)
893             {
894 4         12 s/\\n/\n/g;
895 4         23 s/\\([^\[])/$1/g;
896             }
897              
898             #if($args{'force_utf8'} && !utf8::is_utf8($text))
899             #{
900             # require Encode;
901             # $text = Encode::decode('UTF-8', $text);
902             #}
903              
904 4         20 $self->set_localized_message_text(name => $name,
905             locale => $in_locale,
906             text => $text,
907             variant => $variant);
908             push(@text, $text) if($msg_names);
909             }
910 4         7 }
911 4         7 elsif(/$Start_Message/o)
912 4         12 {
913             $in_msg = $1;
914             $variant = $2;
915             }
916 4         18 elsif(!/$Comment_Or_Blank/o)
917             {
918             chomp;
919             carp "WARNING: Localized message line not understood: $_";
920 3453         11222 }
921             }
922              
923             # Rewind to the starting position
924 16701 100 100     76702 seek($fh, $pos, 0);
      100        
      100        
      100        
      100        
925              
926             return wantarray ? @text : $text[0];
927             return;
928 91         209 }
929 91         157  
930 91         184 {
931             my($self) = shift;
932 91         231  
933             my %args = @_ == 1 ? (string => shift) : @_;
934 91         220  
935 91         226 require IO::String;
936              
937             $args{'fh'} = IO::String->new(delete $args{'string'});
938              
939             return $self->load_messages_from_fh(%args);
940             }
941              
942             use utf8; # The __END__ section contains UTF-8 text
943              
944 91         468 1;
945              
946              
947             =encoding utf-8
948 91 100       545  
949             =head1 NAME
950              
951             Rose::HTML::Object::Message::Localizer - Message localizer class.
952              
953 4         11 =head1 SYNOPSIS
954 4         11  
955             # The localizer for a given class or object is usually accessibly
956             # via the "localizer" class or object method.
957              
958 0         0 $localizer = Rose::HTML::Object->localizer;
959 0         0 $localizer = $object->localizer;
960              
961             ...
962              
963             # The localizer is rarely used directly. More often, it is
964 950         10264 # subclassed so you can provide your own alternate source for
965             # localized messages. See the LOCALIZATION section of the
966 950 50       7038 # Rose::HTML::Objects documentation for more information.
967 0         0  
968             package My::HTML::Object::Message::Localizer;
969              
970             use base qw(Rose::HTML::Object::Message::Localizer);
971             ...
972 1     1 0 30 sub get_localized_message_text
973             {
974 1 50       7 my($self) = shift;
975              
976 1         477 # Get localized message text from the built-in sources
977             my $text = $self->SUPER::get_localized_message_text(@_);
978 1         4940  
979             unless(defined $text)
980 1         112 {
981             my %args = @_;
982              
983 44     44   18640 # Get message text from some other source
  44         544  
  44         234  
984             ...
985             }
986              
987             return $text;
988             }
989              
990             =head1 DESCRIPTION
991              
992             L<Rose::HTML::Object::Message::Localizer> objects are responsible for managing localized L<messages|Rose::HTML::Object::Messages> and L<errors|Rose::HTML::Object::Errors> which are identified by integer ids and symbolic constant names. See the L<Rose::HTML::Object::Messages> and L<Rose::HTML::Object::Errors> documentation for more infomation on messages and errors.
993              
994             In addition to collecting and providing access to messages and errors, L<Rose::HTML::Object::Message::Localizer> objects also provide appropriately localized text for each message and error.
995              
996             This class inherits from, and follows the conventions of, L<Rose::Object>. See the L<Rose::Object> documentation for more information.
997              
998             =head2 MESSAGES AND ERRORS
999              
1000             L<Messages|Rose::HTML::Object::Messages> and L<errors|Rose::HTML::Object::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.
1001              
1002             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 L<Rose::HTML::Form::Field::Integer> and do something special in response to "invalid integer" errors, you could do this:
1003              
1004             package My::HTML::Form::Field::Integer;
1005              
1006             use base 'Rose::HTML::Form::Field::Integer';
1007              
1008             # Import the symbol for the "invalid integer" error
1009             use Rose::HTML::Object::Errors qw(NUM_INVALID_INTEGER);
1010              
1011             sub validate
1012             {
1013             my($self) = shift;
1014              
1015             my $ret = $self->SUPER::validate(@_);
1016              
1017             unless($ret)
1018             {
1019             if($self->error_id == NUM_INVALID_INTEGER)
1020             {
1021             ... # do something here
1022             }
1023             }
1024              
1025             return $ret;
1026             }
1027              
1028             Note how detecting the exact error did not require regex-matching against error message text or anything similarly unmaintainable.
1029              
1030             When it comes time to display appropriate localized message text for the C<NUM_INVALID_INTEGER> error, the aptly named L<message_for_error_id|/message_for_error_id> method is called. This method exists in the localizer, and also in L<Rose::HTML::Object|Rose::HTML::Object/message_for_error_id> and L<Rose::HTML::Form::Field|Rose::HTML::Form::Field/message_for_error_id>. 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.
1031              
1032             =head2 LOCALIZED TEXT
1033              
1034             Broadly speaking, localized text can come from anywhere. See the L<localization|Rose::HTML::Objects/LOCALIZATION> section of the L<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.
1035              
1036             The base L<Rose::HTML::Object::Message::Localizer> class reads localized text from the C<__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 L<load_all_messages|/load_all_messages> method is called, or on demand in response to requests for localized text. The L<auto_load_messages|/auto_load_messages> flag may be used to distinguish between the two policies. Here's an example C<__DATA__> section and L<load_all_messages|/load_all_messages> call (from the L<Rose::HTML::Form::Field::Integer> source code):
1037              
1038             if(__PACKAGE__->localizer->auto_load_messages)
1039             {
1040             __PACKAGE__->localizer->load_all_messages;
1041             }
1042              
1043             1;
1044              
1045             __DATA__
1046              
1047             [% LOCALE en %]
1048              
1049             NUM_INVALID_INTEGER = "[label] must be an integer."
1050             NUM_INVALID_INTEGER_POSITIVE = "[label] must be a positive integer."
1051             NUM_NOT_POSITIVE_INTEGER = "[label] must be a positive integer."
1052              
1053             [% LOCALE de %]
1054              
1055             NUM_INVALID_INTEGER = "[label] muß eine Ganzzahl sein."
1056             NUM_INVALID_INTEGER_POSITIVE = "[label] muß eine positive Ganzzahl sein."
1057             NUM_NOT_POSITIVE_INTEGER = "[label] muß eine positive Ganzzahl sein."
1058              
1059             [% LOCALE fr %]
1060              
1061             NUM_INVALID_INTEGER = "[label] doit être un entier."
1062             NUM_INVALID_INTEGER_POSITIVE = "[label] doit être un entier positif."
1063             NUM_NOT_POSITIVE_INTEGER = "[label] doit être un entier positif."
1064              
1065             The messages for each locale are set off by C<LOCALE> directives surrounded by C<[%> and C<%]>. All messages until the next such declaration are stored under the specified locale.
1066              
1067             Localized text is provided in double-quoted strings to the right of symbolic L<messages|Rose::HTML::Object::Messages> constant names.
1068              
1069             Placeholders are replaced with text provided at runtime. Placeholder names are surrounded by square brackets. They must start with C<[a-zA-Z]> and may contain only characters that match C<\w>. For an example, see the C<[label]> placeholders in the mssage text above. A C<@> prefix is allowed to specify that the placeholder value is expected to be a reference to an array of values.
1070              
1071             SOME_MESSAGE = "A list of values: [@values]"
1072              
1073             In such a case, the values are joined with ", " to form the text that replaces the placeholder.
1074              
1075             Embedded double quotes in message text must be escaped with a backslash. Embedded newlines may be included using a C<\n> sequence. Literal opening square brackets must be backslash-escaped: C<\[>. Literal backslashes must be doubled: C<\\>. Example:
1076              
1077             SOME_MESSAGE = "Here\[]:\nA backslash \\ and some \"embedded\" double quotes"
1078              
1079             The resulting text:
1080              
1081             Here[]:
1082             A backslash \ and some "embedded" double quotes
1083              
1084             There's also a multi-line format for longer messages:
1085              
1086             [% START SOME_MESSAGE %]
1087             This message has multiple lines.
1088             Here's another one.
1089             [% END SOME_MESSAGE %]
1090              
1091             Leading and trailing spaces and newlines are removed from text provided in the multi-line format.
1092              
1093             Blank lines and any lines beginning with a C<#> character are skipped.
1094              
1095             =head3 VARIANTS
1096              
1097             Any L<message|Rose::HTML::Object::Messages> constant name may be followed immediately by a variant name within parentheses. Variant names may contain only the characters C<[A-Za-z0-9_-]>. If no variant is provided, the variant is assumed to be C<default>. In other words, this:
1098              
1099             SOME_MESSAGE(default) = "..."
1100              
1101             is equivalent to this:
1102              
1103             SOME_MESSAGE = "..."
1104              
1105             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.
1106              
1107             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.
1108              
1109             Requesting a variant explicitly is done using the special C<variant> L<message argument|Rose::HTML::Object::Message::Localized/args>. Example:
1110              
1111             $field->error_id($id, { variant => 'foo' });
1112              
1113             Aside from indicating the message variant, the C<variant> argument is treated just like any other. That is, if you happen to have a placeholder named C<variant>, then the value will be subtituted for it. (This being the case, it's usually a good idea to avoid using C<variant> as a placeholder name.)
1114              
1115             If no explicit C<variant> is specified, the L<select_variant_for_message|/select_variant_for_message> method is called to select an appropriate variant. The default implementation of this method returns the L<default variant|/default_variant> most of the time. But if there is a L<message argument|Rose::HTML::Object::Message::Localized/args> named C<count>, then the L<select_variant_for_count|/select_variant_for_count> method is called in order to select the variant.
1116              
1117             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 L<select_variant_for_count|/select_variant_for_count> roughly follow the CLDR guidelines:
1118              
1119             L<http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html>
1120              
1121             with the exception that C<plural> is used in place of C<other>. (Variants are a general purpose mechanism, whereas the context of pluralization is implied in the case of the CLDR terms. A variant named C<other> has no apparent connection to pluralization.)
1122              
1123             The default implementation of L<select_variant_for_count|/select_variant_for_count> (sanely) makes no judgements about "few" or "many," but does return C<zero> for a C<count> of 0, C<one> for 1, C<two> for 2, and C<plural> for all other values of C<count>.
1124              
1125             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 "L<variant cascade|/variant_cascade>." If the desired variant is not available for the specified message in the requested locale, then the L<variant_cascade|/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.
1126              
1127             The default implementation of L<variant_cascade|/variant_cascade> follows simple English-centric rules, cascading directly to C<plural> except in the case of the C<one> variant, and appending the L<default variant|/default_variant> to the end of all cascades.
1128              
1129             (Incidentally, there is also a L<locale cascade|/locale_cascade>. The L<localize_message|/localize_message> method uses a nested loop: for each locale, for each variant, look for message text. See the L<localize_message|/localize_message> documentation for more information.)
1130              
1131             Here's an example using variants. (Please forgive the poor translations. I don't speak French. Corrections welcome!) First, the message text:
1132              
1133             [% LOCALE en %]
1134              
1135             FIELD_ERROR_TOO_MANY_DAYS = "Too many days."
1136             FIELD_ERROR_TOO_MANY_DAYS(one) = "One day is too many."
1137             FIELD_ERROR_TOO_MANY_DAYS(two) = "Two days is too many."
1138             FIELD_ERROR_TOO_MANY_DAYS(few) = "[count] days is too many (few)."
1139             FIELD_ERROR_TOO_MANY_DAYS(many) = "[count] days is too many (many)."
1140             FIELD_ERROR_TOO_MANY_DAYS(plural) = "[count] days is too many."
1141              
1142             [% LOCALE fr %]
1143              
1144             FIELD_ERROR_TOO_MANY_DAYS = "Trop de jours."
1145             FIELD_ERROR_TOO_MANY_DAYS(one) = "Un jour est un trop grand nombre."
1146             FIELD_ERROR_TOO_MANY_DAYS(plural) = "[count] jours est un trop grand nombre."
1147              
1148             Now some examples of variant selection:
1149              
1150             use My::HTML::Object::Errors qw(FIELD_ERROR_TOO_MANY_DAYS)l
1151             ...
1152              
1153             $id = FIELD_ERROR_TOO_MANY_DAYS; # to make for shorter lines below
1154              
1155             $field->locale('en');
1156              
1157             $field->error_id($id, { count => 0 });
1158              
1159             # No explicit variant given. The select_variant_for_count() called
1160             # and returns variant "zero". No "zero" variant found for this
1161             # message in locale "en", so the variant_cascade() containing
1162             # ('plural', 'default') is considered, in that order. A "plural"
1163             # variant is found.
1164             print $field->error; # "0 days is too many."
1165              
1166             $field->error_id($id, { count => 2 });
1167              
1168             # No explicit variant given. The select_variant_for_count() called and
1169             # returns variant "two". That message variant is found in locale "en"
1170             print $field->error; # "Two days is too many."
1171              
1172             $field->error_id($id, { count => 3, variant => 'few' });
1173              
1174             # Explicit variant given. That message variant is found in locale "en"
1175             print $field->error; # "3 days is too many (few)."
1176              
1177             $field->locale('fr');
1178              
1179             $field->error_id($id, { count => 0 });
1180              
1181             # No explicit variant given. The select_variant_for_count() called
1182             # and returns variant "zero". No "zero" variant found for this
1183             # message in locale "fr", so the variant_cascade() containing
1184             # ('plural', 'default') is considered, in that order. A "plural"
1185             # variant is found.
1186             print $field->error; # "0 jours est un trop grand nombre."
1187              
1188             $field->error_id($id, { count => 3, variant => 'few' });
1189              
1190             # Explicit variant given. No "few" variant found for this message
1191             # in locale "fr", so the variant_cascade() containing ('plural',
1192             # 'default') is considered, in that order. A "plural" variant is
1193             # found.
1194             print $field->error; # "3 jours est un trop grand nombre."
1195              
1196             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 L<private library|Rose::HTML::Objects/"PRIVATE LIBRARIES"> to alter their behavior. An obvious choice is the L<variant_cascade|/variant_cascade> method, which you might want to override to provide more sensible per-locale cascades, replacing the default English-centric rules.
1197              
1198             And even if you don't plan to use the variant system at all, you might want to override L<select_variant_for_message|/select_variant_for_message> to unconditionally return the L<default variant|/default_variant>, which will eliminate the special treatment of message arguments named C<count> and C<variant>.
1199              
1200             =head3 CUSTOMIZATION
1201              
1202             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 L<Rose::HTML::Objects> module distribution. For a real application, it may be preferable to store localized text elsewhere.
1203              
1204             The easiest way to do this is to create your own L<Rose::HTML::Object::Message::Localizer> subclass and override the L<get_localized_message_text|/get_localized_message_text> method, or any other method(s) you desire, and provide your own implementation of localized message storage and retrieval.
1205              
1206             You must then ensure that your new localizer subclass is actually used by all of your HTML objects. You can, of course, set the L<localizer|Rose::HTML::Object/localizer> attribute directly, but a much more comprehensive way to customize your HTML objects is by creating your own, private family tree of L<Rose::HTML::Object>-derived classes. Please see the L<private libraries|Rose::HTML::Objects/"PRIVATE LIBRARIES"> section of the L<Rose::HTML::Objects> documentation for more information.
1207              
1208             =head2 LOCALES
1209              
1210             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 L<Rose::HTML::Objects> modules are lowercase two-letter language codes:
1211              
1212             LOCALE LANGUAGE
1213             ------ --------
1214             en English
1215             de German
1216             fr French
1217             bg Bulgarian
1218              
1219             Localized versions of all built-in messages and errors are provided for all of these locales.
1220              
1221             =head1 CLASS METHODS
1222              
1223             =over 4
1224              
1225             =item B<auto_load_messages [BOOL]>
1226              
1227             Get or set a boolean value indicating whether or not localized message text should be automatically loaded from classes that call their localizer's L<load_all_messages|/load_all_messages> method. The default value is true if either of the C<MOD_PERL> or C<RHTMLO_PRIME_CACHES> environment variables are set to a true value, false otherwise.
1228              
1229             =item B<default_locale [LOCALE]>
1230              
1231             Get or set the default L<locale|/locale> used by objects of this class. Defaults to "en".
1232              
1233             =item B<default_locale_cascade [PARAMS]>
1234              
1235             Get or set the default locale cascade. PARAMS are L<locale|/"LOCALES">/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, C<default>, that's used if no locale cascade exists for a particular locale. The default locale cascade is:
1236              
1237             default => [ 'en' ]
1238              
1239             That is, if message text is not available in the desired locale, C<en> text will be returned instead (assuming it exists).
1240              
1241             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).
1242              
1243             =item B<load_all_messages [PARAMS]>
1244              
1245             Load all localized message text from the C<__DATA__> section of the class specified by PARAMS name/value pairs. Valid PARAMS are:
1246              
1247             =over 4
1248              
1249             =item B<from_class CLASS>
1250              
1251             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.
1252              
1253             =back
1254              
1255             =back
1256              
1257             =head1 CONSTRUCTOR
1258              
1259             =over 4
1260              
1261             =item B<new [PARAMS]>
1262              
1263             Constructs a new L<Rose::HTML::Object::Message::Localizer> object based on PARAMS, where PARAMS are
1264             name/value pairs. Any object method is a valid parameter name.
1265              
1266             =back
1267              
1268             =head1 OBJECT METHODS
1269              
1270             =over 4
1271              
1272             =item B<add_localized_error PARAMS>
1273              
1274             Add a new localized error message. PARAMS are name/value pairs. Valid PARAMS are:
1275              
1276             =over 4
1277              
1278             =item B<id ID>
1279              
1280             An integer L<error|Rose::HTML::Object::Errors> 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 L<generate_error_id|/generate_error_id> method will be called to generate a value.
1281              
1282             =item B<name NAME>
1283              
1284             An L<error|Rose::HTML::Object::Errors> name. This parameter is required. Error names may contain only the characters C<[A-Z0-9_]> and must be unique among all error names.
1285              
1286             =back
1287              
1288             =item B<add_localized_message PARAMS>
1289              
1290             Add a new localized message. PARAMS are name/value pairs. Valid PARAMS are:
1291              
1292             =over 4
1293              
1294             =item B<id ID>
1295              
1296             An integer L<message|Rose::HTML::Object::Messages> 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 L<generate_message_id|/generate_message_id> method will be called to generate a value.
1297              
1298             =item B<name NAME>
1299              
1300             A L<message|Rose::HTML::Object::Messages> name. This parameter is required. Message names may contain only the characters C<[A-Z0-9_]> and must be unique among all message names.
1301              
1302             =back
1303              
1304             =item B<default_variant>
1305              
1306             Returns the name of the default variant: C<default>. See the L<variants|/VARIANTS> subsection of the L<localized text|/"LOCALIZED TEXT"> section above for more information on variants.
1307              
1308             =item B<error_class [CLASS]>
1309              
1310             Get or set the name of the L<Rose::HTML::Object::Error>-derived class used to store each error. The default value is L<Rose::HTML::Object::Error>. To change the default, override the C<init_error_class> method in your subclass and return a different class name.
1311              
1312             =item B<errors_class [CLASS]>
1313              
1314             Get or set the name of the L<Rose::HTML::Object::Errors>-derived class used to store and track error ids and symbolic constant names. The default value is L<Rose::HTML::Object::Errors>. To change the default, override the C<init_errors_class> method in your subclass and return a different class name.
1315              
1316             =item B<locale [LOCALE]>
1317              
1318             Get or set the locale assumed by the localizer in the absence of an explicit locale argument. Defaults to the value returned by the L<default_locale|/default_locale> class method.
1319              
1320             =item B<message_class [CLASS]>
1321              
1322             Get or set the name of the L<Rose::HTML::Object::Message>-derived class used to store each message. The default value is L<Rose::HTML::Object::Message::Localized>. To change the default, override the C<init_message_class> method in your subclass and return a different class name.
1323              
1324             =item B<messages_class [CLASS]>
1325              
1326             Get or set the name of the L<Rose::HTML::Object::Messages>-derived class used to store and track message ids and symbolic constant names. The default value is L<Rose::HTML::Object::Messages>. To change the default, override the C<init_messages_class> method in your subclass and return a different class name.
1327              
1328             =item B<generate_error_id>
1329              
1330             Returns a new integer L<error|Rose::HTML::Object::Errors> id. This method will not return the same value more than once.
1331              
1332             =item B<generate_message_id>
1333              
1334             Returns a new integer L<message|Rose::HTML::Object::Messages> id. This method will not return the same value more than once.
1335              
1336             =item B<get_error_id NAME>
1337              
1338             This method is a proxy for the L<errors_class|/errors_class>'s L<get_error_id|Rose::HTML::Object::Errors/get_error_id> method.
1339              
1340             =item B<get_error_name ID>
1341              
1342             This method is a proxy for the L<errors_class|/errors_class>'s L<get_error_name|Rose::HTML::Object::Errors/get_error_name> method.
1343              
1344             =item B<get_localized_message_text PARAMS>
1345              
1346             Returns localized message text based on PARAMS name/value pairs. Valid PARAMS are:
1347              
1348             =over 4
1349              
1350             =item B<id ID>
1351              
1352             An integer L<message|Rose::HTML::Object::Messages> id. If a C<name> is not passed, then the name corresponding to this message id will be looked up using the L<get_message_name|/get_message_name> method.
1353              
1354             =item B<name NAME>
1355              
1356             The L<message|Rose::HTML::Object::Messages> name. If this parameter is not passed, then the C<id> parameter must be passed.
1357              
1358             =item B<locale LOCALE>
1359              
1360             The L<locale|/LOCALES> of the localized message text. Defaults to the localizer's L<locale()|/locale> if omitted.
1361              
1362             =item B<from_class CLASS>
1363              
1364             The name of the class from which to attempt to L<load the localized message text|/"LOCALIZED TEXT">. If omitted, it defaults to the name of the package from which this method was called.
1365              
1366             =back
1367              
1368             =item B<get_message_id NAME>
1369              
1370             This method is a proxy for the L<messages_class|/messages_class>'s L<get_message_id|Rose::HTML::Object::Messages/get_message_id> method.
1371              
1372             =item B<get_message_name ID>
1373              
1374             This method is a proxy for the L<messages_class|/messages_class>'s L<get_message_name|Rose::HTML::Object::Messages/get_message_name> method.
1375              
1376             =item B<load_messages_from_file [ FILE | PARAMS ]>
1377              
1378             Load localized message text, in the format described in the L<LOCALIZED TEXT|/"LOCALIZED TEXT"> section above, from a file on disk. Note that this method only loads message I<text>. The message ids must already exist in the L<messages_class|/messages_class>.
1379              
1380             If a single FILE argument is passed, it is taken as the value for the C<file> parameter. Otherwise, PARAMS name/value pairs are expected. Valid PARAMS are:
1381              
1382             =over 4
1383              
1384             =item B<file PATH>
1385              
1386             The path to the file. This parameter is required.
1387              
1388             =item B<locales [ LOCALE | ARRAYREF ]>
1389              
1390             A L<locale|/"LOCALES"> 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.
1391              
1392             =item B<names [ NAME | ARRAYREF | REGEX ]>
1393              
1394             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.
1395              
1396             =back
1397              
1398             =item B<locale [LOCALE]>
1399              
1400             Get or set the L<locale|/"LOCALES"> of this localizer. This locale is used by several methods when a locale is not explicitly provided. The default value is determined by the L<default_locale|/default_locale> class method.
1401              
1402             =item B<locale_cascade [PARAMS]>
1403              
1404             Get or set the locale cascade. PARAMS are L<locale|/"LOCALES">/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, C<default>, that's used if no locale cascade exists for a particular locale. The default locale cascade is determined by the L<default_locale_cascade|/default_locale_cascade> class method.
1405              
1406             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).
1407              
1408             =item B<localize_message PARAMS>
1409              
1410             Localize a message, returning the appropriately localized and processed message text. Valid PARAMS name/value pairs are:
1411              
1412             =over 4
1413              
1414             =item B<args HASHREF>
1415              
1416             A reference to a hash of L<message arguments|Rose::HTML::Object::Message::Localized/args>. If omitted, the C<message>'s L<args|Rose::HTML::Object::Message::Localized/args> are used.
1417              
1418             =item B<locale LOCALE>
1419              
1420             The locale. If omitted, the C<message>'s L<locale|Rose::HTML::Object::Message::Localized/locale> is used.
1421              
1422             =item B<message MESSAGE>
1423              
1424             The L<Rose::HTML::Object::Message>-derived message object. This parameter is required.
1425              
1426             =item B<variant VARIANT>
1427              
1428             The message L<variant|/"VARIANTS">. If omitted, the L<select_variant_for_message|/select_variant_for_message> method is called, passing the C<message> L<id|Rose::HTML::Object::Message/id>, C<args>, and C<locale>.
1429              
1430             =back
1431              
1432             This method performs a nested loop to search for localized message text: for each locale (including any L<locale_cascade|/locale_cascade>), for each variant (including any L<variant_cascade|/variant_cascade>), for each parent L<field|Rose::HTML::Form::Field/parent_field>, L<form|Rose::HTML::Form::Field/parent_form>, or generic parent L<object|Rose::HTML::Object/parent> (considered in that order), look for message text by calling the L<get_localized_message_text|/get_localized_message_text> method.
1433              
1434             =item B<message_for_error_id PARAMS>
1435              
1436             Given an L<error|Rose::HTML::Object::Errors> id, return the corresponding L<message_class|/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:
1437              
1438             =over 4
1439              
1440             =item B<error_id ID>
1441              
1442             The integer error id. This parameter is required.
1443              
1444             =item B<args HASHREF>
1445              
1446             A reference to a hash of name/value pairs to be used as the L<message arguments|Rose::HTML::Object::Message/args>.
1447              
1448             =back
1449              
1450             =item B<parent [OBJECT]>
1451              
1452             Get or set a weakened reference to the localizer's parent object.
1453              
1454             =item B<select_variant_for_count PARAMS>
1455              
1456             Select and return a L<variant|/"VARIANTS"> name based on PARAMS name/value pairs. Valid PARAMS are:
1457              
1458             =over 4
1459              
1460             =item B<args HASHREF>
1461              
1462             A reference to a hash of L<message arguments|Rose::HTML::Object::Message::Localized/args>.
1463              
1464             =item B<count INTEGER>
1465              
1466             The count for which to select a variant. This parameter is required.
1467              
1468             =item B<locale LOCALE>
1469              
1470             The L<locale|/LOCALES> of the localized message text. Defaults to the localizer's L<locale()|/locale> if omitted.
1471              
1472             =back
1473              
1474             The default implementation looks only at the C<count> parameter and returns the following values based on it (the C<*> below means "any other value"):
1475              
1476             count variant
1477             ----- -------
1478             0 zero
1479             1 one
1480             2 two
1481             * plural
1482              
1483             See the L<variants|/VARIANTS> section for more information on this and other variant-related methods
1484              
1485             =item B<select_variant_for_message PARAMS>
1486              
1487             Select and return a L<variant|/"VARIANTS"> name based on PARAMS name/value pairs. Valid PARAMS are:
1488              
1489             =over 4
1490              
1491             =item B<args HASHREF>
1492              
1493             A reference to a hash of L<message arguments|Rose::HTML::Object::Message::Localized/args>.
1494              
1495             =item B<id MESSAGEID>
1496              
1497             The L<message id|Rose::HTML::Object::Messages>.
1498              
1499             =item B<locale LOCALE>
1500              
1501             The L<locale|/LOCALES> of the localized message text. Defaults to the localizer's L<locale()|/locale> if omitted.
1502              
1503             =back
1504              
1505             If C<args> contains a C<count> parameter, then the L<select_variant_for_count|/select_variant_for_count> method is called, passing all arguments plus the C<count> value as its own parameter, and the variant it returns is returned from this method.
1506              
1507             If C<args> contains a C<variant> parameter, then the value of that parameter is returned.
1508              
1509             Otherwise, the L<default_variant|/default_variant> is returned.
1510              
1511             =item B<set_localized_message_text PARAMS>
1512              
1513             Set the localized text for a message. Valid PARAMS name/value pairs are:
1514              
1515             =over 4
1516              
1517             =item B<id ID>
1518              
1519             An integer L<message|Rose::HTML::Object::Messages> id. If a C<name> is not passed, then the name corresponding to this message id will be looked up using the L<get_message_name|/get_message_name> method.
1520              
1521             =item B<name NAME>
1522              
1523             The L<message|Rose::HTML::Object::Messages> name. If this parameter is not passed, then the C<id> parameter must be passed.
1524              
1525             =item B<locale LOCALE>
1526              
1527             The L<locale|/LOCALES> of the localized message text. Defaults to the localizer's L<locale|/locale>.
1528              
1529             =item B<text TEXT>
1530              
1531             The localized message text.
1532              
1533             =item B<variant VARIANT>
1534              
1535             The message variant, if any. See the L<LOCALIZED TEXT|/"LOCALIZED TEXT"> section above for more information about variants.
1536              
1537             =back
1538              
1539             =item B<variant_cascade [PARAMS]>
1540              
1541             Return a reference to an array of L<variant|/VARIANTS> 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:
1542              
1543             =over 4
1544              
1545             =item B<args HASHREF>
1546              
1547             A reference to a hash of L<message arguments|Rose::HTML::Object::Message::Localized/args>.
1548              
1549             =item B<locale LOCALE>
1550              
1551             The L<locale|/LOCALES> of the desired localized message text.
1552              
1553             =item B<message MESSAGE>
1554              
1555             The L<Rose::HTML::Object::Message>-derived message object.
1556              
1557             =item B<variant VARIANT>
1558              
1559             The originally requested message L<variant|/"VARIANTS">.
1560              
1561             =back
1562              
1563             The default implementation looks only at the C<variant> parameter and returns references to arrays containing the following variant lists based on it:
1564              
1565             variant variant cascade
1566             ------- ---------------
1567             zero plural, default
1568             one default
1569             two plural, default
1570             few plural, default
1571             many plural, default
1572             plural default
1573              
1574             The array references returned should be treated as read-only.
1575              
1576             =back
1577              
1578             =head1 AUTHOR
1579              
1580             John C. Siracusa (siracusa@gmail.com)
1581              
1582             =head1 LICENSE
1583              
1584             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.