File Coverage

blib/lib/Locales.pm
Criterion Covered Total %
statement 530 579 91.5
branch 267 354 75.4
condition 126 206 61.1
subroutine 68 68 100.0
pod 55 55 100.0
total 1046 1262 82.8


line stmt bran cond sub pod time code
1             package Locales;
2              
3 430     430   5189574 use strict;
  430         850  
  430         11795  
4 430     430   2174 use warnings;
  430         734  
  430         13980  
5              
6 430     430   308865 use Module::Want 0.6;
  430         470143  
  430         2845  
7              
8             $Locales::VERSION = '0.34'; # change in POD
9             $Locales::cldr_version = '2.0'; # change in POD
10              
11             $Locales::_UNICODE_STRINGS = 0;
12              
13             sub import {
14 430     430   4536 my ( $c, %opt ) = @_;
15 430 100       2604 if ( exists $opt{unicode} ) {
16 2         4 $Locales::_UNICODE_STRINGS = $opt{unicode};
17             }
18 430         44336 return;
19             }
20              
21             #### class methods ####
22              
23             my %singleton_stash;
24              
25             sub get_cldr_version {
26 3     3 1 21 return $Locales::cldr_version;
27             }
28              
29             sub new {
30 800     800 1 1507755 my ( $class, $tag ) = @_;
31 800   100     2304 $tag = normalize_tag($tag) || 'en';
32              
33 800 100       2769 if ( !exists $singleton_stash{$tag} ) {
34              
35 785         2497 my $locale = {
36             'locale' => $tag,
37             };
38              
39 785 100       2230 if ( my $soft = tag_is_soft_locale($tag) ) {
40              
41             # return if exists $conf->{'soft_locales'} && !$conf->{'soft_locales'};
42 1         4 $locale->{'soft_locale_fallback'} = $soft;
43 1         3 $tag = $soft;
44             }
45              
46 785 50       2033 my $inc_class = ref($class) ? ref($class) : $class;
47 785         2648 $inc_class =~ s{(?:\:\:|\')}{/}g; # per Module::Want::get_inc_key()
48              
49 785 100       3610 have_mod("$class\::DB::Language::$tag") || return;
50 440 50       39160 have_mod("$class\::DB::Territory::$tag") || return;
51              
52 440         23576 my ( $language, $territory ) = split_tag( $locale->{'locale'} );
53              
54 430     430   133786 no strict 'refs'; ## no critic
  430         845  
  430         187327  
55              
56 440         1335 $locale->{'language'} = $language;
57             $locale->{'language_data'} = {
58 440         2440 'VERSION' => \${"$class\::DB::Language::$tag\::VERSION"},
59 440         1797 'cldr_version' => \${"$class\::DB::Language::$tag\::cldr_version"},
60 440         1788 'misc_info' => \%{"$class\::DB::Language::$tag\::misc_info"},
61 440         1725 'code_to_name' => \%{"$class\::DB::Language::$tag\::code_to_name"},
62 440         961 'name_to_code' => \%{"$class\::DB::Language::$tag\::name_to_code"},
  440         3692  
63             };
64              
65 440         1176 $locale->{'territory'} = $territory;
66             $locale->{'territory_data'} = {
67 440         1825 'VERSION' => \${"$class\::DB::Territory::$tag\::VERSION"},
68 440         1646 'cldr_version' => \${"$class\::DB::Territory::$tag\::cldr_version"},
69 440         1640 'code_to_name' => \%{"$class\::DB::Territory::$tag\::code_to_name"},
70 440         814 'name_to_code' => \%{"$class\::DB::Territory::$tag\::name_to_code"},
  440         2842  
71             };
72              
73 440         1511 $locale->{'misc'}{'list_quote_mode'} = 'none';
74              
75 440         1987 $singleton_stash{$tag} = bless $locale, $class;
76             }
77              
78 455         1544 return $singleton_stash{$tag};
79             }
80              
81             #### object methods ####
82              
83             sub get_soft_locale_fallback {
84 653 100   653 1 2131 return $_[0]->{'soft_locale_fallback'} if $_[0]->{'soft_locale_fallback'};
85 648         2863 return;
86             }
87              
88 347192     347192 1 1190840 sub get_locale { shift->{'locale'} }
89              
90 4     4 1 18 sub get_territory { shift->{'territory'} }
91              
92 3     3 1 80 sub get_language { shift->{'language'} }
93              
94             sub get_native_language_from_code {
95 15     15 1 31 my ( $self, $code, $always_return ) = @_;
96              
97 15 50       38 my $class = ref($self) ? ref($self) : $self;
98 15 100       42 if ( !exists $self->{'native_data'} ) {
99 3 50       13 have_mod("$class\::DB::Native") || return;
100 430     430   2471 no strict 'refs'; ## no critic
  430         854  
  430         446311  
101             $self->{'native_data'} = {
102 3         14 'VERSION' => \${"$class\::DB::Native::VERSION"},
103 3         11 'cldr_version' => \${"$class\::DB::Native::cldr_version"},
104 3         59 'code_to_name' => \%{"$class\::DB::Native::code_to_name"},
  3         20  
105             };
106             }
107              
108 15   66     45 $code ||= $self->{'locale'};
109 15         34 $code = normalize_tag($code);
110 15 50       35 return if !defined $code;
111              
112 15 100 50     74 $always_return ||= 1 if $code eq $self->get_locale() && $self->get_soft_locale_fallback(); # force $always_return under soft locale objects
      100        
113 15   100     48 $always_return ||= 0;
114              
115 15 100       49 if ( exists $self->{'native_data'}{'code_to_name'}{$code} ) {
    50          
116 6         37 return $self->{'native_data'}{'code_to_name'}{$code};
117             }
118             elsif ($always_return) {
119 9         20 my ( $l, $t ) = split_tag($code);
120 9         23 my $ln = $self->{'native_data'}{'code_to_name'}{$l};
121 9 100       32 my $tn = defined $t ? $self->{'territory_data'}{'code_to_name'}{$t} : '';
122              
123 9 100 66     44 return $code if !$ln && !$tn;
124              
125 5 50       13 if ( defined $t ) {
126 5         16 my $tmp = Locales->new($l); # if we even get to this point: this is a singleton so it is cheap
127 5 100       591 if ($tmp) {
128 3 100       10 if ( $tmp->get_territory_from_code($t) ) {
129 1         3 $tn = $tmp->get_territory_from_code($t);
130             }
131             }
132             }
133              
134 5   66     15 $ln ||= $l;
135 5   66     14 $tn ||= $t;
136              
137 5   0     15 my $string = get_locale_display_pattern_from_code_fast($code) || $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'locale'} || '{0} ({1})';
138 5         18 $string =~ s/\{0\}/$ln/g;
139 5         14 $string =~ s/\{1\}/$tn/g;
140              
141 5         25 return $string;
142             }
143 0         0 return;
144             }
145              
146             sub numf {
147 4     4 1 7 my ( $self, $always_return ) = @_;
148 4 50       13 my $class = ref($self) ? ref($self) : $self;
149 4   50     17 $always_return ||= 0;
150 4 50       17 $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} = '' if !defined $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'};
151 4 50       11 $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} = '' if !defined $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'};
152              
153 4 50 33     19 if ( !$self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} || !$self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} ) {
154 0 0       0 if ($always_return) {
155 0 0 0     0 if ( $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} || !$self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} ) {
    0 0        
156 0 0       0 return 2 if $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} eq '.';
157 0         0 return 1;
158             }
159             elsif ( !$self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} || $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} ) {
160 0 0       0 return 2 if $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} eq ',';
161 0         0 return 1;
162             }
163             else {
164 0         0 return 1;
165             }
166             }
167             }
168              
169 4 100 33     18 if ( $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'decimal'} eq "\#\,\#\#0\.\#\#\#" ) {
    50 0        
170 3 100 66     25 if ( $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} eq ',' && $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} eq '.' ) {
    100 66        
171 1         6 return 1;
172             }
173             elsif ( $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} eq '.' && $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} eq ',' ) {
174 1         5 return 2;
175             }
176             }
177             elsif ( $always_return && $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} && $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} ) {
178 0 0       0 return 2 if $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} eq ',';
179 0 0       0 return 2 if $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} eq '.';
180 0         0 return 1;
181             }
182              
183             return [
184             $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'decimal'},
185             $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'},
186 2         15 $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'},
187             ];
188             }
189              
190             my $get_locale_display_pattern_from_code_fast = 0;
191              
192             sub get_locale_display_pattern_from_code_fast {
193 115721 100   115721 1 238396 if ( !$get_locale_display_pattern_from_code_fast ) {
194 211         496 $get_locale_display_pattern_from_code_fast++;
195 211         467542 require Locales::DB::LocaleDisplayPattern::Tiny;
196             }
197              
198 115721 100 100     310963 if ( @_ == 1 && ref( $_[0] ) ) {
199 2         9 return Locales::DB::LocaleDisplayPattern::Tiny::get_locale_display_pattern( $_[0]->get_locale() );
200             }
201 115719         325305 return Locales::DB::LocaleDisplayPattern::Tiny::get_locale_display_pattern( $_[-1] ); # last arg so it works as function or class method or object method
202             }
203              
204             sub get_locale_display_pattern_from_code {
205 115717     115717 1 305032 my ( $self, $code, $always_return ) = @_;
206              
207 115717 50       291438 my $class = ref($self) ? ref($self) : $self;
208 115717 100       293088 if ( !exists $self->{'locale_display_pattern_data'} ) {
209 213 50       1254 have_mod("$class\::DB::LocaleDisplayPattern") || return;
210              
211 430     430   2366 no strict 'refs'; ## no critic
  430         762  
  430         185898  
212             $self->{'locale_display_pattern_data'} = {
213 213         1311 'VERSION' => \${"$class\::DB::LocaleDisplayPattern::VERSION"},
214 213         1003 'cldr_version' => \${"$class\::DB::LocaleDisplayPattern::cldr_version"},
215 213         4779 'code_to_pattern' => \%{"$class\::DB::LocaleDisplayPattern::code_to_pattern"},
  213         1737  
216             };
217             }
218              
219 115717   66     246323 $code ||= $self->{'locale'};
220 115717         227846 $code = normalize_tag($code);
221 115717 50       248873 return if !defined $code;
222              
223 115717 100 50     247923 $always_return ||= 1 if $code eq $self->get_locale() && $self->get_soft_locale_fallback(); # force $always_return under soft locale objects
      100        
224 115717   100     444474 $always_return ||= 0;
225              
226 115717 100       318258 if ( exists $self->{'locale_display_pattern_data'}{'code_to_pattern'}{$code} ) {
    50          
227 115716         577428 return $self->{'locale_display_pattern_data'}{'code_to_pattern'}{$code};
228             }
229             elsif ($always_return) {
230 1         3 my ( $l, $t ) = split_tag($code);
231 1 50       4 if ( exists $self->{'locale_display_pattern_data'}{'code_to_pattern'}{$l} ) {
232 1         8 return $self->{'locale_display_pattern_data'}{'code_to_pattern'}{$l};
233             }
234 0         0 return '{0} ({1})';
235             }
236 0         0 return;
237             }
238              
239             my $get_character_orientation_from_code_fast = 0;
240              
241             sub get_character_orientation_from_code_fast {
242 6 100   6 1 378 if ( !$get_character_orientation_from_code_fast ) {
243 1         2 $get_character_orientation_from_code_fast++;
244 1         805 require Locales::DB::CharacterOrientation::Tiny;
245             }
246              
247 6 100 66     27 if ( @_ == 1 && ref( $_[0] ) ) {
248 2         7 return Locales::DB::CharacterOrientation::Tiny::get_orientation( $_[0]->get_locale() );
249             }
250              
251 4         12 return Locales::DB::CharacterOrientation::Tiny::get_orientation( $_[-1] ); # last arg so it works as function or class method or object method
252             }
253              
254             sub get_character_orientation_from_code {
255 231427     231427 1 440840 my ( $self, $code, $always_return ) = @_;
256              
257 231427 50       535644 my $class = ref($self) ? ref($self) : $self;
258 231427 100       533095 if ( !exists $self->{'character_orientation_data'} ) {
259 213 50       1222 have_mod("$class\::DB::CharacterOrientation") || return;
260              
261 430     430   2274 no strict 'refs'; ## no critic
  430         891  
  430         1683262  
262             $self->{'character_orientation_data'} = {
263 213         1239 'VERSION' => \${"$class\::DB::CharacterOrientation::VERSION"},
264 213         1003 'cldr_version' => \${"$class\::DB::CharacterOrientation::cldr_version"},
265 213         4733 'code_to_name' => \%{"$class\::DB::CharacterOrientation::code_to_name"},
  213         2350  
266             };
267             }
268              
269 231427   66     476832 $code ||= $self->{'locale'};
270 231427         435027 $code = normalize_tag($code);
271 231427 50       487228 return if !defined $code;
272              
273 231427 100 50     484393 $always_return ||= 1 if $code eq $self->get_locale() && $self->get_soft_locale_fallback(); # force $always_return under soft locale objects
      100        
274 231427   100     834683 $always_return ||= 0;
275              
276 231427 100       569352 if ( exists $self->{'character_orientation_data'}{'code_to_name'}{$code} ) {
    50          
277 231426         1122056 return $self->{'character_orientation_data'}{'code_to_name'}{$code};
278             }
279             elsif ($always_return) {
280 1         4 my ( $l, $t ) = split_tag($code);
281 1 50       5 if ( exists $self->{'character_orientation_data'}{'code_to_name'}{$l} ) {
282 1         8 return $self->{'character_orientation_data'}{'code_to_name'}{$l};
283             }
284 0         0 return 'left-to-right';
285             }
286 0         0 return;
287             }
288              
289             sub get_plural_form_categories {
290 246     246 1 1136 return @{ $_[0]->{'language_data'}{'misc_info'}{'plural_forms'}{'category_list'} };
  246         1052  
291             }
292              
293             sub supports_special_zeroth {
294 3 100   3 1 9 return 1 if $_[0]->get_plural_form(0) eq 'other';
295 2         8 return;
296             }
297              
298             sub plural_category_count {
299 3     3 1 9 return scalar( $_[0]->get_plural_form_categories() );
300             }
301              
302             sub get_plural_form {
303 31     31 1 137 my ( $self, $n, @category_values ) = @_;
304 31         36 my $category;
305 31         42 my $has_extra_for_zero = 0;
306              
307             # This negative value behavior makes sense but is not defined either way in the CLDR.
308             # We've asked for clarification via http://unicode.org/cldr/trac/ticket/4049
309             # If CLDR introduces negatives then the rule parser needs to factor in those new rules
310             # and also perl's modulus-on-negative-values behavior
311 31         53 my $abs_n = abs($n); # negatives keep same category as positive
312              
313 31 100       105 if ( !$self->{'language_data'}{'misc_info'}{'plural_forms'}{'category_rules_function'} ) {
314 3         12 $self->{'language_data'}{'misc_info'}{'plural_forms'}{'category_rules_function'} = Locales::plural_rule_hashref_to_code( $self->{'language_data'}{'misc_info'}{'plural_forms'} );
315 3 50       12 if ( !defined $self->{'language_data'}{'misc_info'}{'plural_forms'}{'category_rules_function'} ) {
316 0         0 require Carp;
317 0         0 Carp::carp("Could not determine plural logic.");
318             }
319             }
320              
321 31         78 $category = $self->{'language_data'}{'misc_info'}{'plural_forms'}{'category_rules_function'}->($abs_n);
322              
323 31         68 my @categories = $self->get_plural_form_categories();
324              
325 31 100       71 if ( !@category_values ) {
326              
327             # no args will return the category name
328 18         38 @category_values = @categories;
329             }
330             else {
331 13         19 my $cat_len = @categories;
332 13         17 my $val_len = @category_values;
333 13 100 66     44 if ( $val_len == ( $cat_len + 1 ) ) {
    100          
334 6         11 $has_extra_for_zero++;
335             }
336             elsif ( $cat_len != $val_len && $self->{'verbose'} ) {
337 1         6 require Carp;
338 1         6 Carp::carp("The number of given values ($val_len) does not match the number of categories ($cat_len).");
339             }
340             }
341              
342 31 100       356 if ( !defined $category ) {
343 20 100 100     57 my $cat_idx = $has_extra_for_zero && $abs_n != 0 ? -2 : -1;
344 20 100 100     150 return wantarray ? ( $category_values[$cat_idx], $has_extra_for_zero && $abs_n == 0 ? 1 : 0 ) : $category_values[$cat_idx];
    100          
345             }
346             else {
347 11         12 GET_POSITION:
348             my $cat_pos_in_list;
349 11         13 my $index = -1;
350             CATEGORY:
351 11         17 for my $cat (@categories) {
352 11         14 $index++;
353 11 50       25 if ( $cat eq $category ) {
354 11         12 $cat_pos_in_list = $index;
355 11         16 last CATEGORY;
356             }
357             }
358              
359 11 50 33     37 if ( !defined $cat_pos_in_list && $category ne 'other' ) {
    50          
360 0         0 require Carp;
361 0         0 Carp::carp("The category ($category) is not used by this locale.");
362 0         0 $category = 'other';
363 0         0 goto GET_POSITION;
364             }
365             elsif ( !defined $cat_pos_in_list ) {
366 0 0 0     0 my $cat_idx = $has_extra_for_zero && $abs_n != 0 ? -2 : -1;
367 0 0 0     0 return wantarray ? ( $category_values[$cat_idx], $has_extra_for_zero && $abs_n == 0 ? 1 : 0 ) : $category_values[$cat_idx];
    0          
368             }
369             else {
370 11 50 66     35 if ( $has_extra_for_zero && $category eq 'other' ) { # and 'other' is at the end of the list? nah... && $cat_pos_in_list + 1 == $#category_values
371 0 0 0     0 my $cat_idx = $has_extra_for_zero && $abs_n == 0 ? -1 : $cat_pos_in_list;
372 0 0 0     0 return wantarray ? ( $category_values[$cat_idx], $has_extra_for_zero && $abs_n == 0 ? 1 : 0 ) : $category_values[$cat_idx];
    0          
373             }
374             else {
375 11 100       68 return wantarray ? ( $category_values[$cat_pos_in_list], 0 ) : $category_values[$cat_pos_in_list];
376             }
377             }
378             }
379             }
380              
381             # pending http://unicode.org/cldr/trac/ticket/4051
382             sub get_list_or {
383 23     23 1 96 my ( $self, @items ) = @_;
384              
385             # I told you it was stub in the changelog, POD, test, and here!
386 23         54 $self->_quote_get_list_items( \@items );
387              
388 23 100       60 return if !@items;
389 21 100       65 return $items[0] if @items == 1;
390 17 100       44 return "$items[0] or $items[1]" if @items == 2;
391              
392 15         25 my $last = pop(@items);
393 15         92 return join( ', ', @items ) . ", or $last";
394             }
395              
396             sub _quote_get_list_items {
397 46     46   70 my ( $self, $items_ar ) = @_;
398              
399 46         106 my $cnt = 0;
400              
401 46 100 100     349 if ( exists $self->{'misc'}{'list_quote_mode'} && $self->{'misc'}{'list_quote_mode'} ne 'none' ) {
402 14 100       45 if ( $self->{'misc'}{'list_quote_mode'} eq 'all' ) {
    100          
403 6 100       82 @{$items_ar} = ('') if @{$items_ar} == 0;
  2         75  
  6         16  
404              
405 6         12 for my $i ( 0 .. scalar( @{$items_ar} ) - 1 ) {
  6         17  
406 24 100       53 $items_ar->[$i] = '' if !defined $items_ar->[$i];
407 24         91 $items_ar->[$i] = $self->quote( $items_ar->[$i] );
408 24         39 $cnt++;
409             }
410             }
411             elsif ( $self->{'misc'}{'list_quote_mode'} eq 'some' ) {
412 6 100       8 @{$items_ar} = ('') if @{$items_ar} == 0;
  2         5  
  6         19  
413              
414 6         7 for my $i ( 0 .. scalar( @{$items_ar} ) - 1 ) {
  6         13  
415 24 100       51 $items_ar->[$i] = '' if !defined $items_ar->[$i];
416 24 100 100     196 if ( $items_ar->[$i] eq '' || $items_ar->[$i] =~ m/\A(?: |\xc2\xa0)+\z/ ) {
417 10         23 $items_ar->[$i] = $self->quote( $items_ar->[$i] );
418 10         24 $cnt++;
419             }
420             }
421             }
422             else {
423 2         12 require Carp;
424 2         6 Carp::carp('$self->{misc}{list_quote_mode} is set to an unknown value');
425             }
426             }
427              
428 46         743 return $cnt;
429             }
430              
431             sub get_list_and {
432 23     23 1 1767 my ( $self, @items ) = @_;
433              
434 23         53 $self->_quote_get_list_items( \@items );
435              
436 23 100       58 return if !@items;
437 21 100       60 return $items[0] if @items == 1;
438              
439 17 100       33 if ( @items == 2 ) {
440 2         86 my $two = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'list'}{'2'};
441 2         19 $two =~ s/\{([01])\}/$items[$1]/g;
442 2         90 return $two;
443             }
444             else {
445 15         28 @items = map { my $c = $_; $c =~ s/\{([01])\}/__\{__${1}__\}__/g; $c } @items; # I know ick, patches welcome
  70         95  
  70         125  
  70         222  
446              
447 15         47 my $aggregate = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'list'}{'start'};
448 15         113 $aggregate =~ s/\{([01])\}/$items[$1]/g;
449              
450 15         41 for my $i ( 2 .. $#items ) {
451 40 100       94 next if $i == $#items;
452 25         52 my $middle = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'list'}{'middle'};
453 25         146 $middle =~ s/\{0\}/$aggregate/g;
454 25         162 $middle =~ s/\{1\}/$items[$i]/g;
455 25         123 $aggregate = $middle;
456             }
457              
458 15         35 my $end = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'list'}{'end'};
459 15         44 $end =~ s/\{0\}/$aggregate/g;
460 15         122 $end =~ s/\{1\}/$items[-1]/g;
461              
462 15         48 $end =~ s/__\{__([01])__\}__/\{$1\}/g; # See "I know ick, patches welcome" above
463              
464 15         85 return $end;
465             }
466             }
467              
468             sub quote {
469 35     35 1 133 my ( $self, $value ) = @_;
470 35 50       134 $value = '' if !defined $value;
471              
472 35         204 return $self->{'language_data'}{'misc_info'}{'delimiters'}{'quotation_start'} . $value . $self->{'language_data'}{'misc_info'}{'delimiters'}{'quotation_end'};
473             }
474              
475             sub quote_alt {
476 1     1 1 3 my ( $self, $value ) = @_;
477 1 50       3 $value = '' if !defined $value;
478              
479 1         7 return $self->{'language_data'}{'misc_info'}{'delimiters'}{'alternate_quotation_start'} . $value . $self->{'language_data'}{'misc_info'}{'delimiters'}{'alternate_quotation_end'};
480             }
481              
482             sub get_formatted_ellipsis_initial {
483 2     2 1 8 my ( $self, $str ) = @_;
484 2   50     10 my $pattern = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'ellipsis'}{'initial'} || '…{0}';
485 2         7 $pattern =~ s/\{0\}/$str/;
486 2         52 return $pattern;
487             }
488              
489             sub get_formatted_ellipsis_medial {
490 2     2 1 7 my ($self) = @_; # my ($self, $first, $second) = @_;
491 2   50     10 my $pattern = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'ellipsis'}{'medial'} || '{0}…{1}';
492 2         20 $pattern =~ s/\{(0|1)\}/$_[$1 + 1]/g; # use index instead of variable to avoid formatter confusion, e.g. $first contains the string '{1}'
493 2         10 return $pattern;
494             }
495              
496             sub get_formatted_ellipsis_final {
497 2     2 1 4 my ( $self, $str ) = @_;
498 2   50     10 my $pattern = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'ellipsis'}{'final'} || '{0}…';
499 2         7 $pattern =~ s/\{0\}/$str/;
500 2         9 return $pattern;
501             }
502              
503             # TODO get_formatted_percent() get_formatted_permille() other symbols like infinity, plus sign etc
504              
505             sub get_formatted_decimal {
506 49     49 1 476 my ( $self, $n, $max_decimal_places, $_my_pattern ) = @_; # $_my_pattern not documented on purpose, it is only intended for internal use, and may dropepd/changed at any time
507              
508             # Format $n per $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'decimal'}
509             # per http://cldr.unicode.org/translation/number-patterns
510              
511             # TODO: ? NaN from CLDR if undef or not d[.d] ?
512 49 50       116 return if !defined $n;
513              
514             #### ##
515             # 1) Turn $n into [0-9]+(?:\.[0-9]+)? even if scientifically large (or negative, since how negative numbers look is defined by the pattern)
516             #### ##
517              
518             # Regaring $max_decimal_places: Number::Format will "Obtain precision from the length of the decimal part" of the pattern.
519             # but CLDR says "The number of decimals will be set by the program" in our case the caller's input or sprintf()'s default.
520              
521             # this way we can remove any signs and still know if it was negative later on
522 49 100       134 my $is_negative = $n < 0 ? 1 : 0;
523              
524 49 100       78 my $max_len = defined $max_decimal_places ? abs( int($max_decimal_places) ) : 6; # %f default is 6
525 49 100       100 $max_len = 14 if $max_len > 14;
526              
527 49 100 66     203 if ( $n > 10_000_000_000 || $n < -10_000_000_000 ) {
528              
529             # TODO: ? do exponential from CLDR ?
530 12 100       52 return $n if $n =~ m/e/i; # poor man's is exponential check.
531              
532             # Emulate %f on large numbers strings
533             # $n = "$n"; # turn it into a string, trailing zero's go away
534              
535 10 100       125 if ( $n =~ m/\.([0-9]{$max_len})([0-9])?/ ) {
536 4         10 my $trim = $1; # (defined $2 && $2 > 4) ? $1 + 1 : $1;
537              
538 4 100 66     20 if ( defined $2 && $2 > 4 ) {
539 2 50       8 if ( ( $trim + 1 ) !~ m/e/i ) { # poor man's is exponential check.
540 2         4 $trim++;
541             }
542             }
543              
544             # Yes, %f does it but why 0's only to lop them off immediately
545             # while(CORE::length($trim) < $max_len) { $trim .= '0' }
546 4         22 $n =~ s/\.[0-9]+/\.$trim/;
547             }
548             }
549             else {
550 37         317 $n = sprintf( '%.' . $max_len . 'f', $n );
551              
552             # TODO: ? do exponential from CLDR ?
553 37 50       110 return $n if $n =~ m/e/i; # poor man's is exponential check.
554             }
555              
556             # [^0-9]+ will match the off chance of sprintf() using a
557             # separator that is mutiple bytes or mutliple characters or both.
558             # This holds true for both byte strings and Unicode strings.
559              
560 47         182 $n =~ s{([^0-9]+[0-9]*?[1-9])0+$}{$1};
561 47         107 $n =~ s{[^0-9]+0+$}{};
562              
563             # [^0-9]+ will match the off chance of sprintf() using a
564             # negative/positive symbol that is mutiple bytes or mutliple characters or both.
565             # This holds true for both byte strings and Unicode strings.
566 47         106 $n =~ s/^[^0-9]+//; # strip signs since any would be defined in pattern
567              
568             #### ##
569             # 2) Determine working format:
570             #### ##
571              
572 47   33     172 my $format = $_my_pattern || $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'decimal'}; # from http://unicode.org/repos/cldr-tmp/trunk/diff/by_type/number.pattern.html
573              
574 47         125 my ( $zero_positive_pat, $negative_pat, $err ) = split( /(?
575              
576 47 50 100     211 if ($err) {
    100          
    50          
577 0         0 require Carp;
578 0         0 Carp::carp("Format had more than 2 pos/neg sections. Using default pattern.");
579 0         0 $format = '#,##0.###';
580             }
581             elsif ( $is_negative && $negative_pat ) {
582 1         3 $format = $negative_pat;
583             }
584             elsif ($zero_positive_pat) {
585 46         68 $format = $zero_positive_pat;
586             }
587              
588 47         50 my $dec_sec_cnt = 0;
589 47         256 $dec_sec_cnt++ while ( $format =~ m/(?
590 47 50       94 if ( $dec_sec_cnt != 1 ) {
591 0         0 require Carp;
592 0         0 Carp::carp("Format should have one decimal section. Using default pattern.");
593 0         0 $format = '#,##0.###';
594             }
595              
596 47 50 33     313 if ( !defined $format || $format eq '' || $format =~ m/^\s+$/ ) {
      33        
597 0         0 require Carp;
598 0         0 Carp::carp("Format is empty. Using default pattern.");
599 0         0 $format = '#,##0.###';
600             }
601              
602             #### ##
603             # 3) format $n per $format
604             #### ##
605              
606 47         61 my $result = '';
607              
608 47 100       85 if ( $format eq '#,##0.###' ) {
609 44         59 $result = $n;
610 44         306 while ( $result =~ s/^([-+]?\d+)(\d{3})/$1,$2/s ) { 1 } # right from perlfaq5
  114         575  
611             }
612             else {
613              
614             # period that is not literal (?
615             # comma that is not literal (?
616              
617             # !!!! This is sort of where the CLDR documentation gets anemic, patches welcome !!
618              
619             # TODO: ? better efficiency (e.g. less/no array voo doo) w/ same results, patches ... well you know ?
620              
621 3         9 my ( $integer, $decimals ) = split( /\./, $n, 2 );
622              
623 3         13 my ( $i_pat, $d_pat ) = split( /(?
624 3         6 my ( $cur_idx, $trailing_non_n, $cur_d, $cur_pat ) = ( 0, '' ); # buffer
625              
626             # integer: right to left
627 3         13 my @i_pat = reverse( split( /(?
628              
629 3 100       10 my $next_to_last_pattern = @i_pat == 1 ? $i_pat[0] : $i_pat[-2];
630 3         10 $next_to_last_pattern =~ s/0$/#/;
631 3   33     20 while ( $i_pat[0] =~ s/((?:\'.\')+)$// || $i_pat[0] =~ s/([^0#]+)$// ) {
632 0         0 $trailing_non_n = "$1$trailing_non_n";
633             }
634              
635             # my $loop_cnt = 0;
636             # my $loop_max = CORE::length($i_pat . $integer) + 100;
637              
638 3         14 while ( CORE::length( $cur_d = CORE::substr( $integer, -1, 1, '' ) ) ) {
639              
640             # if ($loop_cnt > $loop_max) {
641             # require Carp;
642             # Carp::carp('Integer pattern parsing results in infinite loop.');
643             # last;
644             # }
645             # $loop_cnt++;
646              
647 30 100 100     180 if ( $cur_idx == $#i_pat && !CORE::length( $i_pat[$cur_idx] ) ) {
648 8         14 $i_pat[$cur_idx] = $next_to_last_pattern;
649             }
650              
651 30 100       64 if ( !CORE::length( $i_pat[$cur_idx] ) ) { # this chunk is spent
652 1 50       5 if ( defined $i_pat[ $cur_idx + 1 ] ) { # there are more chunks ...
653 1         3 $cur_idx++; # ... next chunk please
654             }
655             }
656              
657 30 50       64 if ( CORE::length( $i_pat[$cur_idx] ) ) {
658              
659             # if the next thing is a literal:
660 30 50       64 if ( $i_pat[$cur_idx] =~ m/(\',\')$/ ) {
661 0         0 $result = CORE::substr( $i_pat[$cur_idx], -3, 3, '' ) . $result;
662 0         0 redo;
663             }
664              
665 30         50 $cur_pat = CORE::substr( $i_pat[$cur_idx], -1, 1, '' );
666              
667 30 50 66     163 if ( $cur_pat ne '0' && $cur_pat ne '#' ) {
668 0         0 $result = "$cur_pat$result";
669 0         0 redo;
670             }
671             }
672              
673 30 100 100     130 $result = !CORE::length( $i_pat[$cur_idx] ) && @i_pat != 1 ? ",$cur_d$result" : "$cur_d$result";
674              
675 30 100 66     155 if ( $cur_idx == $#i_pat - 1 && $i_pat[$#i_pat] eq '#' && !CORE::length( $i_pat[$cur_idx] ) ) {
      100        
676 2         4 $cur_idx++;
677 2         7 $i_pat[$cur_idx] = $next_to_last_pattern;
678             }
679             }
680 3 100       8 if ( CORE::length( $i_pat[$cur_idx] ) ) {
681 2         10 $i_pat[$cur_idx] =~ s/(?
682 2         6 $result = $result . $i_pat[$cur_idx]; # prepend it (e.g. 0 and -)
683             }
684 3 50       11 if ( substr( $result, 0, 1 ) eq ',' ) {
685 0         0 substr( $result, 0, 1, '' );
686             }
687 3         6 $result .= $trailing_non_n;
688              
689 3 50 33     17 if ( defined $decimals && CORE::length($decimals) ) {
690              
691             # decimal: left to right
692 3         6 my @d_pat = ($d_pat); # TODO ? support sepeartor in decimal, !definedvia CLDR, no patterns have that ATM ? split( /(?
693              
694 3         6 $result .= '.';
695 3         4 $cur_idx = 0;
696 3         4 $trailing_non_n = '';
697              
698 3   66     24 while ( $d_pat[-1] =~ s/((?:\'.\')+)$// || $d_pat[-1] =~ s/([^0#]+)$// ) {
699 1         8 $trailing_non_n = "$1$trailing_non_n";
700             }
701              
702             # $loop_cnt = 0;
703             # $loop_max = CORE::length($d_pat . $decimals) + 100;
704              
705 3         11 while ( CORE::length( $cur_d = CORE::substr( $decimals, 0, 1, '' ) ) ) {
706              
707             # if ($loop_cnt > $loop_max) {
708             # require Carp;
709             # Carp::carp('Decimal pattern parsing results in infinite loop.');
710             # last;
711             # }
712             # $loop_cnt++;
713              
714 15 100       34 if ( !CORE::length( $d_pat[$cur_idx] ) ) { # this chunk is spent
715 6 50       14 if ( !defined $d_pat[ $cur_idx + 1 ] ) { # there are no more chunks
716 6         9 $cur_pat = '#';
717             }
718             else { # next chunk please
719 0         0 $result .= ',';
720 0         0 $cur_idx++;
721             }
722             }
723              
724 15 100       33 if ( CORE::length( $d_pat[$cur_idx] ) ) {
725              
726             # if the next thing is a literal:
727 9 50       18 if ( $d_pat[$cur_idx] =~ m/^(\'.\')/ ) {
728 0         0 $result .= CORE::substr( $d_pat[$cur_idx], 0, 3, '' );
729 0         0 redo;
730             }
731 9         15 $cur_pat = CORE::substr( $d_pat[$cur_idx], 0, 1, '' );
732 9 50 33     41 if ( $cur_pat ne '0' && $cur_pat ne '#' ) {
733 0         0 $result .= $cur_pat;
734 0         0 redo;
735             }
736             }
737              
738 15         37 $result .= $cur_d;
739             }
740 3 50       10 if ( substr( $result, -1, 1 ) eq ',' ) {
741 0         0 substr( $result, -1, 1, '' );
742             }
743 3 50       9 if ( defined $d_pat[$cur_idx] ) {
744 3         4 $d_pat[$cur_idx] =~ s/(?
745 3         5 $result .= $d_pat[$cur_idx]; # append it (e.g. 0 and -)
746             }
747 3         9 $result .= $trailing_non_n;
748             }
749              
750             # END: "This is sort of where the CLDR documentation gets anemic"
751             }
752              
753 47         138 $result =~ s/(?
754 47         303 $result =~ s/(?{language_data}{misc_info}{cldr_formats}{_decimal_format_group}/g;
755 47         169 $result =~ s/_LOCALES-DECIMAL-PLACEHOLDER_/$self->{language_data}{misc_info}{cldr_formats}{_decimal_format_decimal}/g;
756              
757             # TODO ? turn 0-9 into non0-9 digits if defined as such in CLDR ?
758              
759 47 100 100     148 if ( $is_negative && !$negative_pat ) {
760              
761             # This is default since CLDR says to specify a special negative pattern if
762             # "your language uses different formats for negative numbers than just adding "-" at the front"
763 10         23 $result = "-$result";
764             }
765              
766 47         259 return $result;
767             }
768              
769             #### territory ####
770              
771             sub get_territory_codes {
772 1     1 1 3 return keys %{ shift->{'territory_data'}{'code_to_name'} };
  1         166  
773             }
774              
775             sub get_territory_names {
776 1     1 1 4 return values %{ shift->{'territory_data'}{'code_to_name'} };
  1         195  
777             }
778              
779             sub get_territory_lookup {
780 1     1 1 2 return %{ shift->{'territory_data'}{'code_to_name'} };
  1         182  
781             }
782              
783             sub get_territory_from_code {
784 12     12 1 3395 my ( $self, $code, $always_return ) = @_;
785              
786 12   100     36 $code ||= $self->{'territory'};
787 12         25 $code = normalize_tag($code);
788 12 100       33 return if !defined $code;
789              
790             # this is not needed in this method:
791             # $always_return ||= 1 if $code eq $self->get_locale() && $self->get_soft_locale_fallback(); # force $always_return under soft locale objects
792              
793 11 100 33     47 if ( exists $self->{'territory_data'}{'code_to_name'}{$code} ) {
    50          
794 5         22 return $self->{'territory_data'}{'code_to_name'}{$code};
795             }
796             elsif ( !defined $self->{'territory'} || $code ne $self->{'territory'} ) {
797 6         14 my ( $l, $t ) = split_tag($code);
798 6 50 66     19 if ( $t && exists $self->{'territory_data'}{'code_to_name'}{$t} ) {
799 1         7 return $self->{'territory_data'}{'code_to_name'}{$t};
800             }
801             }
802 5 100       20 return $code if $always_return;
803 3         11 return;
804             }
805              
806             sub get_code_from_territory {
807 2     2 1 5 my ( $self, $name ) = @_;
808 2 50       7 return if !$name;
809 2         5 my $key = normalize_for_key_lookup($name);
810 2 100       11 if ( exists $self->{'territory_data'}{'name_to_code'}{$key} ) {
811 1         7 return $self->{'territory_data'}{'name_to_code'}{$key};
812             }
813 1         4 return;
814             }
815              
816             {
817 430     430   3054 no warnings 'once';
  430         853  
  430         209115  
818             *code2territory = \&get_territory_from_code;
819             *territory2code = \&get_code_from_territory;
820             }
821              
822             #### language ####
823              
824             sub get_language_codes {
825 2     2 1 7 return keys %{ shift->{'language_data'}{'code_to_name'} };
  2         685  
826             }
827              
828             sub get_language_names {
829 1     1 1 2 return values %{ shift->{'language_data'}{'code_to_name'} };
  1         365  
830             }
831              
832             sub get_language_lookup {
833 1     1 1 2 return %{ shift->{'language_data'}{'code_to_name'} };
  1         439  
834             }
835              
836             sub get_language_from_code {
837 23     23 1 5723 my ( $self, $code, $always_return ) = @_;
838              
839 23   66     96 $code ||= $self->{'locale'};
840 23         47 $code = normalize_tag($code);
841 23 50       63 return if !defined $code;
842              
843 23 100 50     58 $always_return ||= 1 if $code eq $self->get_locale() && $self->get_soft_locale_fallback(); # force $always_return under soft locale objects
      100        
844 23   100     70 $always_return ||= 0;
845              
846 23 100       83 if ( exists $self->{'language_data'}{'code_to_name'}{$code} ) {
    100          
847 8         37 return $self->{'language_data'}{'code_to_name'}{$code};
848             }
849             elsif ($always_return) {
850 14         30 my ( $l, $t ) = split_tag($code);
851 14         35 my $ln = $self->{'language_data'}{'code_to_name'}{$l};
852 14 100       36 my $tn = defined $t ? $self->{'territory_data'}{'code_to_name'}{$t} : '';
853              
854 14 100 66     69 return $code if !$ln && !$tn;
855 8   66     24 $ln ||= $l;
856 8   66     108 $tn ||= $t;
857              
858 8   50     29 my $string = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'locale'} || '{0} ({1})';
859 8         26 $string =~ s/\{0\}/$ln/g;
860 8         19 $string =~ s/\{1\}/$tn/g;
861              
862 8         47 return $string;
863             }
864 1         4 return;
865             }
866              
867             sub get_code_from_language {
868 2     2 1 4 my ( $self, $name ) = @_;
869 2 50       7 return if !$name;
870 2         6 my $key = normalize_for_key_lookup($name);
871 2 100       8 if ( exists $self->{'language_data'}{'name_to_code'}{$key} ) {
872 1         6 return $self->{'language_data'}{'name_to_code'}{$key};
873             }
874 1         5 return;
875             }
876              
877             {
878 430     430   2361 no warnings 'once';
  430         862  
  430         1049879  
879             *code2language = \&get_language_from_code;
880             *language2code = \&get_code_from_language;
881             }
882              
883             #### utility functions ####
884              
885             sub tag_is_soft_locale {
886 785     785 1 1442 my ($tag) = @_;
887 785         2121 my ( $l, $t ) = split_tag($tag);
888              
889 785 50       2354 return if !defined $l; # invalid tag is not soft
890              
891 785 100       3357 return if !$t; # no territory part means it is not soft
892 32 100       107 return if tag_is_loadable($tag); # if it can be loaded directly then it is not soft
893 3 100       10 return if !territory_code_is_known($t); # if the territory part is not known then it is not soft
894 1 50       3 return if !tag_is_loadable($l); # if the language part is not known then it is not soft
895 1         6 return $l; # it is soft, so return the value suitable for 'soft_locale_fallback'
896             }
897              
898             sub tag_is_loadable {
899 50     50 1 1363 my ( $tag, $as_territory ) = @_; # not documenting internal $as_territory, just use territory_code_is_known() directly
900 50 50       176 have_mod("Locales::DB::Loadable") || return;
901              
902 50 100       1082 if ($as_territory) {
903 10 100       40 return 1 if exists $Locales::DB::Loadable::territory{$tag};
904             }
905             else {
906 40 100       307 return 1 if exists $Locales::DB::Loadable::code{$tag};
907             }
908              
909 17         85 return;
910             }
911              
912             sub get_loadable_language_codes {
913 1 50   1 1 5 have_mod("Locales::DB::Loadable") || return;
914 1         42 return keys %Locales::DB::Loadable::code;
915             }
916              
917             sub territory_code_is_known {
918 10     10 1 32 return tag_is_loadable( $_[0], 1 );
919             }
920              
921             sub split_tag {
922 116149     116149 1 227377 return split( /_/, normalize_tag( $_[0] ), 2 ); # we only do language[_territory]
923             }
924              
925             sub get_i_tag_for_string {
926 2     2 1 5 my $norm = normalize_tag( $_[0] );
927              
928 2 100       8 if ( substr( $norm, 0, 2 ) eq 'i_' ) {
929 1         6 return $norm;
930             }
931             else {
932 1         5 return 'i_' . $norm;
933             }
934             }
935              
936             my %non_locales = (
937             'und' => 1,
938             'zxx' => 1,
939             'mul' => 1,
940             'mis' => 1,
941             'art' => 1,
942             );
943              
944             sub non_locale_list {
945 2     2 1 35 return ( sort keys %non_locales );
946             }
947              
948             sub is_non_locale {
949 3   50 3 1 11 my $tag = normalize_tag( $_[0] ) || return;
950 3 100       15 return 1 if exists $non_locales{$tag};
951 2         11 return;
952             }
953              
954             sub typical_en_alias_list {
955 1     1 1 8 return ( 'en_us', 'i_default' );
956             }
957              
958             sub is_typical_en_alias {
959 3   50 3 1 9 my $tag = normalize_tag( $_[0] ) || return;
960 3 100 66     24 return 1 if $tag eq 'en_us' || $tag eq 'i_default';
961 2         10 return;
962             }
963              
964             sub normalize_tag {
965 464158     464158 1 668131 my $tag = $_[0];
966 464158 100       940710 return if !defined $tag;
967 464155         622711 $tag =~ tr/A-Z/a-z/;
968 464155         842397 $tag =~ s{\s+}{}g;
969 464155         763458 $tag =~ s{[^a-z0-9]+$}{}; # I18N::LangTags::locale2language_tag() does not allow trailing '_'
970 464155         713772 $tag =~ s{[^a-z0-9]+}{_}g;
971              
972             # would like to do this with a single call, backtracking or indexing ? patches welcome!
973 464155         1103859 while ( $tag =~ s/([^_]{8})([^_])/$1\_$2/ ) { } # I18N::LangTags::locale2language_tag() only allows parts bewteen 1 and 8 character
974 464155         1173609 return $tag;
975             }
976              
977             sub normalize_tag_for_datetime_locale {
978 2     2 1 7 my ( $pre, $pst ) = split_tag( $_[0] ); # we only do language[_territory]
979 2 50       10 return if !defined $pre;
980              
981 2 100       7 if ($pst) {
982 1         9 return $pre . '_' . uc($pst);
983             }
984             else {
985 1         6 return $pre;
986             }
987             }
988              
989             sub normalize_tag_for_ietf {
990 2     2 1 8 my ( $pre, $pst ) = split_tag( $_[0] ); # we only do language[_territory]
991 2 50       9 return if !defined $pre;
992              
993 2 100       7 if ($pst) {
994 1         8 return $pre . '-' . uc($pst);
995             }
996             else {
997 1         14 return $pre;
998             }
999             }
1000              
1001             sub normalize_for_key_lookup {
1002 4     4 1 6 my $key = $_[0];
1003 4 50       11 return if !defined $key;
1004 4         8 $key =~ tr/A-Z/a-z/; # lowercase
1005             # $key =~ s{^\s+}{}; # trim WS from begining
1006             # $key =~ s{\s+$}{}; # trim WS from end
1007             # $key =~ s{\s+}{ }g; # collapse multi WS to one space
1008 4         11 $key =~ s{\s+}{}g;
1009 4         8 $key =~ s{[\'\"\-\(\)\[\]\_]+}{}g;
1010 4         9 return $key;
1011             }
1012              
1013             sub plural_rule_string_to_javascript_code {
1014 177     177 1 645 my ( $plural_rule_string, $return ) = @_;
1015 177         385 my $perl = plural_rule_string_to_code( $plural_rule_string, $return );
1016 177         581 $perl =~ s/sub \{ /function (n) {/;
1017 177         784 $perl =~ s/\$_\[0\]/n/g;
1018 177         726 $perl =~ s/ \(n \% ([0-9]+)\) \+ \(n-int\(n\)\) /n % $1/g;
1019 177         411 $perl =~ s/int\(/parseInt\(/g;
1020 177         536 return $perl;
1021             }
1022              
1023             sub plural_rule_string_to_code {
1024 371     371 1 26735 my ( $plural_rule_string, $return ) = @_;
1025 371 100       877 if ( !defined $return ) {
1026 26         31 $return = 1;
1027             }
1028              
1029             # if you have a better way, patches welcome!!
1030              
1031 371         467 my %m;
1032 371         1256 while ( $plural_rule_string =~ m/mod ([0-9]+)/g ) {
1033              
1034             # CLDR plural rules (http://unicode.org/reports/tr35/#Language_Plural_Rules):
1035             # 'mod' (modulus) is a remainder operation as defined in Java; for example, the result of "4.3 mod 3" is 1.3.
1036 161         851 $m{$1} = "( (\$_[0] % $1) + (\$_[0]-int(\$_[0])) )";
1037             }
1038              
1039 371         631 my $perl_code = "sub { if (";
1040              
1041 371         1555 for my $or ( split /\s+or\s+/i, $plural_rule_string ) {
1042 427         543 my $and_exp;
1043 427         1281 for my $and ( split /\s+and\s+/i, $or ) {
1044 498         760 my $copy = $and;
1045 498         777 my $n = '$_[0]';
1046              
1047 498         786 $copy =~ s/ ?n is not / $n \!\= /g;
1048 498         1812 $copy =~ s/ ?n is / $n \=\= /g;
1049              
1050 498         894 $copy =~ s/ ?n mod ([0-9]+) is not / $m{$1} \!\= /g;
1051 498         1145 $copy =~ s/ ?n mod ([0-9]+) is / $m{$1} \=\= /g;
1052              
1053             # 'in' is like 'within' but it has to be an integer
1054 498         720 $copy =~ s/ ?n not in ([0-9]+)\s*\.\.\s*([0-9]+) ?/ int\($n\) \!\= $n \|\| $n < $1 \|\| $n \> $2 /g;
1055 498         821 $copy =~ s/ ?n mod ([0-9]+) not in ([0-9]+)\s*\.\.\s*([0-9]+) ?/ int\($n\) \!\= $n \|\| $m{$1} < $2 \|\| $m{$1} \> $3 /g;
1056              
1057             # 'within' is like 'in' except is inclusive of decimals
1058 498         735 $copy =~ s/ ?n not within ([0-9]+)\s*\.\.\s*([0-9]+) ?/ \($n < $1 \|\| $n > $2\) /g;
1059 498         667 $copy =~ s/ ?n mod ([0-9]+) not within ([0-9]+)\s*\.\.\s*([0-9]+) ?/ \($m{$1} < $2 \|\| $m{$1} > $3\) /g;
1060              
1061             # 'in' is like 'within' but it has to be an integer
1062 498         1080 $copy =~ s/ ?n in ([0-9]+)\s*\.\.\s*([0-9]+) ?/ int\($n\) \=\= $n \&\& $n \>\= $1 \&\& $n \<\= $2 /g;
1063 498         1221 $copy =~ s/ ?n mod ([0-9]+) in ([0-9]+)\s*\.\.\s*([0-9]+) ?/ int\($n\) \=\= $n \&\& $m{$1} \>\= $2 \&\& $m{$1} \<\= $3 /g;
1064              
1065             # 'within' is like 'in' except is inclusive of decimals
1066 498         859 $copy =~ s/ ?n within ([0-9]+)\s*\.\.\s*([0-9]+) ?/ $n \>\= $1 \&\& $n \<\= $2 /g;
1067 498         705 $copy =~ s/ ?n mod ([0-9]+) within ([0-9]+)\s*\.\.\s*([0-9]+) ?/ $m{$1} \>\= $2 \&\& $m{$1} \<\= $3 /g;
1068              
1069 498 100       1081 if ( $copy eq $and ) {
1070 2         12 require Carp;
1071 2         7 Carp::carp("Unknown plural rule syntax");
1072 2         568 return;
1073             }
1074             else {
1075 496         1502 $and_exp .= "($copy) && ";
1076             }
1077             }
1078 425         2528 $and_exp =~ s/\s+\&\&\s*$//;
1079              
1080 425 50       1102 if ($and_exp) {
1081 425         1204 $perl_code .= " ($and_exp) || ";
1082             }
1083             }
1084 369         2273 $perl_code =~ s/\s+\|\|\s*$//;
1085              
1086 369         793 $perl_code .= ") { return '$return'; } return;}";
1087              
1088 369         1024 return $perl_code;
1089             }
1090              
1091             sub plural_rule_hashref_to_code {
1092 5     5 1 9 my ($hr) = @_;
1093              
1094 5 100       18 if ( ref( $hr->{'category_rules'} ) ne 'HASH' ) {
1095              
1096             # this should never happen but if it does lets default to en's version
1097 1         4 $hr->{'category_rules_compiled'} = {
1098             'one' => q{sub { return 'one' if ( ( $n == 1 ) ); return;};},
1099             };
1100              
1101             return sub {
1102              
1103 2     2   700 my ($n) = @_;
1104 2 100       9 return 'one' if $n == 1;
1105 1         4 return;
1106 1         5 };
1107             }
1108             else {
1109 4         14 for my $cat ( get_cldr_plural_category_list(1) ) {
1110 24 100       60 next if !exists $hr->{'category_rules'}{$cat};
1111 5 100       15 next if exists $hr->{'category_rules_compiled'}{$cat};
1112 1         4 $hr->{'category_rules_compiled'}{$cat} = plural_rule_string_to_code( $hr->{'category_rules'}{$cat}, $cat );
1113             }
1114              
1115             return sub {
1116 31     31   45 my ($n) = @_;
1117 31         33 my $match;
1118             PCAT:
1119 31         66 for my $cat ( get_cldr_plural_category_list(1) ) { # use function instead of keys to preserve processing order
1120 142 100       356 next if !exists $hr->{'category_rules_compiled'}{$cat};
1121              
1122             # Does $n match $hr->{$cat} ?
1123              
1124 33 50       89 if ( ref( $hr->{'category_rules_compiled'}{$cat} ) ne 'CODE' ) {
1125              
1126 0         0 local $SIG{__DIE__}; # prevent benign eval from tripping potentially fatal sig handler, moot w/ Module::Want 0.6
1127 0         0 $hr->{'category_rules_compiled'}{$cat} = eval "$hr->{'category_rules_compiled'}{$cat}"; ## no critic # As of 0.22 this will be skipped for modules included w/ the main dist
1128             }
1129              
1130 33 100       94 if ( $hr->{'category_rules_compiled'}{$cat}->($n) ) {
1131 11         13 $match = $cat;
1132 11         20 last PCAT;
1133             }
1134             }
1135              
1136 31 100       81 return $match if $match;
1137 20         39 return;
1138 4         27 };
1139             }
1140             }
1141              
1142             sub get_cldr_plural_category_list {
1143              
1144 37 100   37 1 144 return qw(zero one two few many other) if $_[0]; # check order
1145              
1146             # Order is important for Locale::Maketext::Utils::quant():
1147             # one (singular), two (dual), few (paucal), many, other, zero
1148 1         9 return qw(one two few many other zero); # quant() arg order
1149             }
1150              
1151             sub get_fallback_list {
1152 5     5 1 15 my ( $self, $special_lookup ) = @_;
1153              
1154 5         13 my ( $super, $ter ) = split_tag( $self->{'locale'} );
1155             return (
1156             $self->{'locale'},
1157             ( $super ne $self->{'locale'} && $super ne 'i' ? $super : () ),
1158 5         75 ( @{ $self->{'language_data'}{'misc_info'}{'fallback'} } ),
1159             (
1160             defined $special_lookup && ref($special_lookup) eq 'CODE'
1161 5 100 66     25 ? ( map { my $n = Locales::normalize_tag($_); $n ? ($n) : () } $special_lookup->( $self->{'locale'} ) )
  6 100 66     22  
  6 100       28  
1162             : ()
1163             ),
1164             'en'
1165             );
1166             }
1167              
1168             # get_cldr_$chart_$type_$name or better naming ?
1169             sub get_cldr_number_symbol_decimal {
1170 1   50 1 1 8 return $_[0]->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} || '.';
1171             }
1172              
1173             sub get_cldr_number_symbol_group {
1174 1   50 1 1 13 return $_[0]->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} || ',';
1175             }
1176              
1177             1;
1178              
1179             __END__