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