File Coverage

blib/lib/Locales.pm
Criterion Covered Total %
statement 530 579 91.5
branch 268 354 75.7
condition 128 206 62.1
subroutine 68 68 100.0
pod 55 55 100.0
total 1049 1262 83.1


line stmt bran cond sub pod time code
1             package Locales;
2              
3 430     430   4291825 use strict;
  430         781  
  430         18434  
4 430     430   3378 use warnings;
  430         1221  
  430         14897  
5              
6 430     430   222012 use Module::Want 0.6;
  430         412170  
  430         3967  
7              
8             $Locales::VERSION = '0.33'; # 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   4809 my ( $c, %opt ) = @_;
15 430 100       3078 if ( exists $opt{unicode} ) {
16 2         5 $Locales::_UNICODE_STRINGS = $opt{unicode};
17             }
18 430         46968 return;
19             }
20              
21             #### class methods ####
22              
23             my %singleton_stash;
24              
25             sub get_cldr_version {
26 3     3 1 15 return $Locales::cldr_version;
27             }
28              
29             sub new {
30 797     797 1 1259077 my ( $class, $tag ) = @_;
31 797   100     2157 $tag = normalize_tag($tag) || 'en';
32              
33 797 100       2284 if ( !exists $singleton_stash{$tag} ) {
34              
35 782         2050 my $locale = {
36             'locale' => $tag,
37             };
38              
39 782 100       1946 if ( my $soft = tag_is_soft_locale($tag) ) {
40              
41             # return if exists $conf->{'soft_locales'} && !$conf->{'soft_locales'};
42 1         11 $locale->{'soft_locale_fallback'} = $soft;
43 1         2 $tag = $soft;
44             }
45              
46 782 50       1758 my $inc_class = ref($class) ? ref($class) : $class;
47 782         2046 $inc_class =~ s{(?:\:\:|\')}{/}g; # per Module::Want::get_inc_key()
48              
49 782 100       2905 have_mod("$class\::DB::Language::$tag") || return;
50 437 50       40296 have_mod("$class\::DB::Territory::$tag") || return;
51              
52 437         23160 my ( $language, $territory ) = split_tag( $locale->{'locale'} );
53              
54 430     430   121567 no strict 'refs'; ## no critic
  430         1379  
  430         151426  
55              
56 437         1181 $locale->{'language'} = $language;
57 437         2287 $locale->{'language_data'} = {
58 437         1595 'VERSION' => \${"$class\::DB::Language::$tag\::VERSION"},
59 437         1534 'cldr_version' => \${"$class\::DB::Language::$tag\::cldr_version"},
60 437         1528 'misc_info' => \%{"$class\::DB::Language::$tag\::misc_info"},
61 437         3191 'code_to_name' => \%{"$class\::DB::Language::$tag\::code_to_name"},
62 437         756 'name_to_code' => \%{"$class\::DB::Language::$tag\::name_to_code"},
63             };
64              
65 437         946 $locale->{'territory'} = $territory;
66 437         1622 $locale->{'territory_data'} = {
67 437         1338 'VERSION' => \${"$class\::DB::Territory::$tag\::VERSION"},
68 437         1253 'cldr_version' => \${"$class\::DB::Territory::$tag\::cldr_version"},
69 437         2366 'code_to_name' => \%{"$class\::DB::Territory::$tag\::code_to_name"},
70 437         728 'name_to_code' => \%{"$class\::DB::Territory::$tag\::name_to_code"},
71             };
72              
73 437         1257 $locale->{'misc'}{'list_quote_mode'} = 'none';
74              
75 437         3208 $singleton_stash{$tag} = bless $locale, $class;
76             }
77              
78 452         1399 return $singleton_stash{$tag};
79             }
80              
81             #### object methods ####
82              
83             sub get_soft_locale_fallback {
84 650 100   650 1 1876 return $_[0]->{'soft_locale_fallback'} if $_[0]->{'soft_locale_fallback'};
85 645         2373 return;
86             }
87              
88 347189     347189 1 941069 sub get_locale { shift->{'locale'} }
89              
90 4     4 1 17 sub get_territory { shift->{'territory'} }
91              
92 3     3 1 18 sub get_language { shift->{'language'} }
93              
94             sub get_native_language_from_code {
95 15     15 1 33 my ( $self, $code, $always_return ) = @_;
96              
97 15 50       40 my $class = ref($self) ? ref($self) : $self;
98 15 100       45 if ( !exists $self->{'native_data'} ) {
99 3 50       12 have_mod("$class\::DB::Native") || return;
100 430     430   3071 no strict 'refs'; ## no critic
  430         1430  
  430         359556  
101 3         14 $self->{'native_data'} = {
102 3         10 'VERSION' => \${"$class\::DB::Native::VERSION"},
103 3         17 'cldr_version' => \${"$class\::DB::Native::cldr_version"},
104 3         48 'code_to_name' => \%{"$class\::DB::Native::code_to_name"},
105             };
106             }
107              
108 15   66     33 $code ||= $self->{'locale'};
109 15         32 $code = normalize_tag($code);
110 15 50       28 return if !defined $code;
111              
112 15 100 50     61 $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     33 $always_return ||= 0;
114              
115 15 100       52 if ( exists $self->{'native_data'}{'code_to_name'}{$code} ) {
    50          
116 6         35 return $self->{'native_data'}{'code_to_name'}{$code};
117             }
118             elsif ($always_return) {
119 9         15 my ( $l, $t ) = split_tag($code);
120 9         14 my $ln = $self->{'native_data'}{'code_to_name'}{$l};
121 9 100       22 my $tn = defined $t ? $self->{'territory_data'}{'code_to_name'}{$t} : '';
122              
123 9 100 66     37 return $code if !$ln && !$tn;
124              
125 5 50       10 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       454 if ($tmp) {
128 3 100       6 if ( $tmp->get_territory_from_code($t) ) {
129 1         2 $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   50     10 my $string = get_locale_display_pattern_from_code_fast($code) || $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'locale'} || '{0} ({1})';
138 5         13 $string =~ s/\{0\}/$ln/g;
139 5         11 $string =~ s/\{1\}/$tn/g;
140              
141 5         21 return $string;
142             }
143 0         0 return;
144             }
145              
146             sub numf {
147 4     4 1 19 my ( $self, $always_return ) = @_;
148 4 50       11 my $class = ref($self) ? ref($self) : $self;
149 4   50     15 $always_return ||= 0;
150 4 50       15 $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       10 $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     17 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     15 if ( $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'decimal'} eq "\#\,\#\#0\.\#\#\#" ) {
    50 33        
170 3 100 66     20 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         5 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         4 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 2         14 $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'decimal'},
185             $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'},
186             $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 187326 if ( !$get_locale_display_pattern_from_code_fast ) {
194 211         409 $get_locale_display_pattern_from_code_fast++;
195 211         610651 require Locales::DB::LocaleDisplayPattern::Tiny;
196             }
197              
198 115721 100 100     250923 if ( @_ == 1 && ref( $_[0] ) ) {
199 2         6 return Locales::DB::LocaleDisplayPattern::Tiny::get_locale_display_pattern( $_[0]->get_locale() );
200             }
201 115719         293565 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 293450 my ( $self, $code, $always_return ) = @_;
206              
207 115717 50       245346 my $class = ref($self) ? ref($self) : $self;
208 115717 100       282467 if ( !exists $self->{'locale_display_pattern_data'} ) {
209 213 50       1246 have_mod("$class\::DB::LocaleDisplayPattern") || return;
210              
211 430     430   3749 no strict 'refs'; ## no critic
  430         598  
  430         158489  
212 213         1282 $self->{'locale_display_pattern_data'} = {
213 213         1071 'VERSION' => \${"$class\::DB::LocaleDisplayPattern::VERSION"},
214 213         1707 'cldr_version' => \${"$class\::DB::LocaleDisplayPattern::cldr_version"},
215 213         5195 'code_to_pattern' => \%{"$class\::DB::LocaleDisplayPattern::code_to_pattern"},
216             };
217             }
218              
219 115717   66     194880 $code ||= $self->{'locale'};
220 115717         199426 $code = normalize_tag($code);
221 115717 50       191379 return if !defined $code;
222              
223 115717 100 50     199105 $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     373071 $always_return ||= 0;
225              
226 115717 100       287952 if ( exists $self->{'locale_display_pattern_data'}{'code_to_pattern'}{$code} ) {
    50          
227 115716         536537 return $self->{'locale_display_pattern_data'}{'code_to_pattern'}{$code};
228             }
229             elsif ($always_return) {
230 1         4 my ( $l, $t ) = split_tag($code);
231 1 50       4 if ( exists $self->{'locale_display_pattern_data'}{'code_to_pattern'}{$l} ) {
232 1         6 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 455 if ( !$get_character_orientation_from_code_fast ) {
243 1         2 $get_character_orientation_from_code_fast++;
244 1         879 require Locales::DB::CharacterOrientation::Tiny;
245             }
246              
247 6 100 66     25 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 362383 my ( $self, $code, $always_return ) = @_;
256              
257 231427 50       406414 my $class = ref($self) ? ref($self) : $self;
258 231427 100       452791 if ( !exists $self->{'character_orientation_data'} ) {
259 213 50       1228 have_mod("$class\::DB::CharacterOrientation") || return;
260              
261 430     430   4108 no strict 'refs'; ## no critic
  430         587  
  430         1386085  
262 213         1326 $self->{'character_orientation_data'} = {
263 213         918 'VERSION' => \${"$class\::DB::CharacterOrientation::VERSION"},
264 213         1862 'cldr_version' => \${"$class\::DB::CharacterOrientation::cldr_version"},
265 213         5471 'code_to_name' => \%{"$class\::DB::CharacterOrientation::code_to_name"},
266             };
267             }
268              
269 231427   66     339842 $code ||= $self->{'locale'};
270 231427         323119 $code = normalize_tag($code);
271 231427 50       345808 return if !defined $code;
272              
273 231427 100 50     343267 $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     651780 $always_return ||= 0;
275              
276 231427 100       451908 if ( exists $self->{'character_orientation_data'}{'code_to_name'}{$code} ) {
    50          
277 231426         1020237 return $self->{'character_orientation_data'}{'code_to_name'}{$code};
278             }
279             elsif ($always_return) {
280 1         2 my ( $l, $t ) = split_tag($code);
281 1 50       3 if ( exists $self->{'character_orientation_data'}{'code_to_name'}{$l} ) {
282 1         5 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 950 return @{ $_[0]->{'language_data'}{'misc_info'}{'plural_forms'}{'category_list'} };
  246         1013  
291             }
292              
293             sub supports_special_zeroth {
294 3 100   3 1 10 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 12 return scalar( $_[0]->get_plural_form_categories() );
300             }
301              
302             sub get_plural_form {
303 31     31 1 126 my ( $self, $n, @category_values ) = @_;
304 31         27 my $category;
305 31         26 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         51 my $abs_n = abs($n); # negatives keep same category as positive
312              
313 31 100       101 if ( !$self->{'language_data'}{'misc_info'}{'plural_forms'}{'category_rules_function'} ) {
314 3         10 $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         64 $category = $self->{'language_data'}{'misc_info'}{'plural_forms'}{'category_rules_function'}->($abs_n);
322              
323 31         53 my @categories = $self->get_plural_form_categories();
324              
325 31 100       63 if ( !@category_values ) {
326              
327             # no args will return the category name
328 18         26 @category_values = @categories;
329             }
330             else {
331 13         9 my $cat_len = @categories;
332 13         11 my $val_len = @category_values;
333 13 100 66     46 if ( $val_len == ( $cat_len + 1 ) ) {
    100          
334 6         6 $has_extra_for_zero++;
335             }
336             elsif ( $cat_len != $val_len && $self->{'verbose'} ) {
337 1         6 require Carp;
338 1         8 Carp::carp("The number of given values ($val_len) does not match the number of categories ($cat_len).");
339             }
340             }
341              
342 31 100       420 if ( !defined $category ) {
343 20 100 100     59 my $cat_idx = $has_extra_for_zero && $abs_n != 0 ? -2 : -1;
344 20 100 100     120 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         10 GET_POSITION:
348             my $cat_pos_in_list;
349 11         11 my $index = -1;
350             CATEGORY:
351 11         12 for my $cat (@categories) {
352 11         7 $index++;
353 11 50       23 if ( $cat eq $category ) {
354 11         11 $cat_pos_in_list = $index;
355 11         10 last CATEGORY;
356             }
357             }
358              
359 11 50 33     41 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     31 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       65 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 87 my ( $self, @items ) = @_;
384              
385             # I told you it was stub in the changelog, POD, test, and here!
386 23         41 $self->_quote_get_list_items( \@items );
387              
388 23 100       45 return if !@items;
389 21 100       48 return $items[0] if @items == 1;
390 17 100       35 return "$items[0] or $items[1]" if @items == 2;
391              
392 15         16 my $last = pop(@items);
393 15         75 return join( ', ', @items ) . ", or $last";
394             }
395              
396             sub _quote_get_list_items {
397 46     46   47 my ( $self, $items_ar ) = @_;
398              
399 46         42 my $cnt = 0;
400              
401 46 100 100     240 if ( exists $self->{'misc'}{'list_quote_mode'} && $self->{'misc'}{'list_quote_mode'} ne 'none' ) {
402 14 100       37 if ( $self->{'misc'}{'list_quote_mode'} eq 'all' ) {
    100          
403 6 100       3 @{$items_ar} = ('') if @{$items_ar} == 0;
  2         3  
  6         14  
404              
405 6         7 for my $i ( 0 .. scalar( @{$items_ar} ) - 1 ) {
  6         12  
406 24 100       32 $items_ar->[$i] = '' if !defined $items_ar->[$i];
407 24         28 $items_ar->[$i] = $self->quote( $items_ar->[$i] );
408 24         26 $cnt++;
409             }
410             }
411             elsif ( $self->{'misc'}{'list_quote_mode'} eq 'some' ) {
412 6 100       5 @{$items_ar} = ('') if @{$items_ar} == 0;
  2         4  
  6         15  
413              
414 6         5 for my $i ( 0 .. scalar( @{$items_ar} ) - 1 ) {
  6         14  
415 24 100       34 $items_ar->[$i] = '' if !defined $items_ar->[$i];
416 24 100 100     93 if ( $items_ar->[$i] eq '' || $items_ar->[$i] =~ m/\A(?: |\xc2\xa0)+\z/ ) {
417 10         13 $items_ar->[$i] = $self->quote( $items_ar->[$i] );
418 10         14 $cnt++;
419             }
420             }
421             }
422             else {
423 2         8 require Carp;
424 2         4 Carp::carp('$self->{misc}{list_quote_mode} is set to an unknown value');
425             }
426             }
427              
428 46         794 return $cnt;
429             }
430              
431             sub get_list_and {
432 23     23 1 1276 my ( $self, @items ) = @_;
433              
434 23         50 $self->_quote_get_list_items( \@items );
435              
436 23 100       52 return if !@items;
437 21 100       45 return $items[0] if @items == 1;
438              
439 17 100       29 if ( @items == 2 ) {
440 2         8 my $two = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'list'}{'2'};
441 2         17 $two =~ s/\{([01])\}/$items[$1]/g;
442 2         10 return $two;
443             }
444             else {
445 15         19 @items = map { my $c = $_; $c =~ s/\{([01])\}/__\{__${1}__\}__/g; $c } @items; # I know ick, patches welcome
  70         55  
  70         86  
  70         111  
446              
447 15         39 my $aggregate = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'list'}{'start'};
448 15         160 $aggregate =~ s/\{([01])\}/$items[$1]/g;
449              
450 15         36 for my $i ( 2 .. $#items ) {
451 40 100       76 next if $i == $#items;
452 25         43 my $middle = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'list'}{'middle'};
453 25         55 $middle =~ s/\{0\}/$aggregate/g;
454 25         65 $middle =~ s/\{1\}/$items[$i]/g;
455 25         45 $aggregate = $middle;
456             }
457              
458 15         29 my $end = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'list'}{'end'};
459 15         31 $end =~ s/\{0\}/$aggregate/g;
460 15         38 $end =~ s/\{1\}/$items[-1]/g;
461              
462 15         33 $end =~ s/__\{__([01])__\}__/\{$1\}/g; # See "I know ick, patches welcome" above
463              
464 15         74 return $end;
465             }
466             }
467              
468             sub quote {
469 35     35 1 36 my ( $self, $value ) = @_;
470 35 50       47 $value = '' if !defined $value;
471              
472 35         85 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 7 my ( $self, $str ) = @_;
484 2   50     9 my $pattern = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'ellipsis'}{'initial'} || '…{0}';
485 2         7 $pattern =~ s/\{0\}/$str/;
486 2         42 return $pattern;
487             }
488              
489             sub get_formatted_ellipsis_medial {
490 2     2 1 6 my ($self) = @_; # my ($self, $first, $second) = @_;
491 2   50     11 my $pattern = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'ellipsis'}{'medial'} || '{0}…{1}';
492 2         22 $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         8 return $pattern;
494             }
495              
496             sub get_formatted_ellipsis_final {
497 2     2 1 5 my ( $self, $str ) = @_;
498 2   50     9 my $pattern = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'ellipsis'}{'final'} || '{0}…';
499 2         7 $pattern =~ s/\{0\}/$str/;
500 2         8 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 557 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       97 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       117 my $is_negative = $n < 0 ? 1 : 0;
523              
524 49 100       74 my $max_len = defined $max_decimal_places ? abs( int($max_decimal_places) ) : 6; # %f default is 6
525 49 100       79 $max_len = 14 if $max_len > 14;
526              
527 49 100 66     167 if ( $n > 10_000_000_000 || $n < -10_000_000_000 ) {
528              
529             # TODO: ? do exponential from CLDR ?
530 12 100       67 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       118 if ( $n =~ m/\.([0-9]{$max_len})([0-9])?/ ) {
536 4         8 my $trim = $1; # (defined $2 && $2 > 4) ? $1 + 1 : $1;
537              
538 4 100 66     20 if ( defined $2 && $2 > 4 ) {
539 2 50       9 if ( ( $trim + 1 ) !~ m/e/i ) { # poor man's is exponential check.
540 2         3 $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         19 $n =~ s/\.[0-9]+/\.$trim/;
547             }
548             }
549             else {
550 37         364 $n = sprintf( '%.' . $max_len . 'f', $n );
551              
552             # TODO: ? do exponential from CLDR ?
553 37 50       95 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         181 $n =~ s{([^0-9]+[0-9]*?[1-9])0+$}{$1};
561 47         91 $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         94 $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     199 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         105 my ( $zero_positive_pat, $negative_pat, $err ) = split( /(?
575              
576 47 50 100     182 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         2 $format = $negative_pat;
583             }
584             elsif ($zero_positive_pat) {
585 46         46 $format = $zero_positive_pat;
586             }
587              
588 47         41 my $dec_sec_cnt = 0;
589 47         230 $dec_sec_cnt++ while ( $format =~ m/(?
590 47 50       86 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     295 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         47 my $result = '';
607              
608 47 100       93 if ( $format eq '#,##0.###' ) {
609 44         43 $result = $n;
610 44         278 while ( $result =~ s/^([-+]?\d+)(\d{3})/$1,$2/s ) { 1 } # right from perlfaq5
  114         403  
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         8 my ( $integer, $decimals ) = split( /\./, $n, 2 );
622              
623 3         10 my ( $i_pat, $d_pat ) = split( /(?
624 3         4 my ( $cur_idx, $trailing_non_n, $cur_d, $cur_pat ) = ( 0, '' ); # buffer
625              
626             # integer: right to left
627 3         20 my @i_pat = reverse( split( /(?
628              
629 3 100       7 my $next_to_last_pattern = @i_pat == 1 ? $i_pat[0] : $i_pat[-2];
630 3         7 $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         15 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     80 if ( $cur_idx == $#i_pat && !CORE::length( $i_pat[$cur_idx] ) ) {
648 8         6 $i_pat[$cur_idx] = $next_to_last_pattern;
649             }
650              
651 30 100       49 if ( !CORE::length( $i_pat[$cur_idx] ) ) { # this chunk is spent
652 1 50       4 if ( defined $i_pat[ $cur_idx + 1 ] ) { # there are more chunks ...
653 1         2 $cur_idx++; # ... next chunk please
654             }
655             }
656              
657 30 50       43 if ( CORE::length( $i_pat[$cur_idx] ) ) {
658              
659             # if the next thing is a literal:
660 30 50       41 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         34 $cur_pat = CORE::substr( $i_pat[$cur_idx], -1, 1, '' );
666              
667 30 50 66     82 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     139 $result = !CORE::length( $i_pat[$cur_idx] ) && @i_pat != 1 ? ",$cur_d$result" : "$cur_d$result";
674              
675 30 100 66     101 if ( $cur_idx == $#i_pat - 1 && $i_pat[$#i_pat] eq '#' && !CORE::length( $i_pat[$cur_idx] ) ) {
      100        
676 2         2 $cur_idx++;
677 2         5 $i_pat[$cur_idx] = $next_to_last_pattern;
678             }
679             }
680 3 100       8 if ( CORE::length( $i_pat[$cur_idx] ) ) {
681 2         9 $i_pat[$cur_idx] =~ s/(?
682 2         4 $result = $result . $i_pat[$cur_idx]; # prepend it (e.g. 0 and -)
683             }
684 3 50       8 if ( substr( $result, 0, 1 ) eq ',' ) {
685 0         0 substr( $result, 0, 1, '' );
686             }
687 3         4 $result .= $trailing_non_n;
688              
689 3 50 33     12 if ( defined $decimals && CORE::length($decimals) ) {
690              
691             # decimal: left to right
692 3         7 my @d_pat = ($d_pat); # TODO ? support sepeartor in decimal, !definedvia CLDR, no patterns have that ATM ? split( /(?
693              
694 3         4 $result .= '.';
695 3         3 $cur_idx = 0;
696 3         4 $trailing_non_n = '';
697              
698 3   66     19 while ( $d_pat[-1] =~ s/((?:\'.\')+)$// || $d_pat[-1] =~ s/([^0#]+)$// ) {
699 1         5 $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         12 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       21 if ( !CORE::length( $d_pat[$cur_idx] ) ) { # this chunk is spent
715 6 50       11 if ( !defined $d_pat[ $cur_idx + 1 ] ) { # there are no more chunks
716 6         6 $cur_pat = '#';
717             }
718             else { # next chunk please
719 0         0 $result .= ',';
720 0         0 $cur_idx++;
721             }
722             }
723              
724 15 100       23 if ( CORE::length( $d_pat[$cur_idx] ) ) {
725              
726             # if the next thing is a literal:
727 9 50       16 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         10 $cur_pat = CORE::substr( $d_pat[$cur_idx], 0, 1, '' );
732 9 50 33     49 if ( $cur_pat ne '0' && $cur_pat ne '#' ) {
733 0         0 $result .= $cur_pat;
734 0         0 redo;
735             }
736             }
737              
738 15         26 $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       7 if ( defined $d_pat[$cur_idx] ) {
744 3         4 $d_pat[$cur_idx] =~ s/(?
745 3         4 $result .= $d_pat[$cur_idx]; # append it (e.g. 0 and -)
746             }
747 3         6 $result .= $trailing_non_n;
748             }
749              
750             # END: "This is sort of where the CLDR documentation gets anemic"
751             }
752              
753 47         109 $result =~ s/(?
754 47         291 $result =~ s/(?{language_data}{misc_info}{cldr_formats}{_decimal_format_group}/g;
755 47         106 $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     114 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         14 $result = "-$result";
764             }
765              
766 47         267 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         194  
773             }
774              
775             sub get_territory_names {
776 1     1 1 2 return values %{ shift->{'territory_data'}{'code_to_name'} };
  1         144  
777             }
778              
779             sub get_territory_lookup {
780 1     1 1 3 return %{ shift->{'territory_data'}{'code_to_name'} };
  1         140  
781             }
782              
783             sub get_territory_from_code {
784 12     12 1 2601 my ( $self, $code, $always_return ) = @_;
785              
786 12   100     32 $code ||= $self->{'territory'};
787 12         20 $code = normalize_tag($code);
788 12 100       25 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     56 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         8 my ( $l, $t ) = split_tag($code);
798 6 100 66     22 if ( $t && exists $self->{'territory_data'}{'code_to_name'}{$t} ) {
799 1         5 return $self->{'territory_data'}{'code_to_name'}{$t};
800             }
801             }
802 5 100       14 return $code if $always_return;
803 3         8 return;
804             }
805              
806             sub get_code_from_territory {
807 2     2 1 4 my ( $self, $name ) = @_;
808 2 50       7 return if !$name;
809 2         5 my $key = normalize_for_key_lookup($name);
810 2 100       8 if ( exists $self->{'territory_data'}{'name_to_code'}{$key} ) {
811 1         6 return $self->{'territory_data'}{'name_to_code'}{$key};
812             }
813 1         5 return;
814             }
815              
816             {
817 430     430   3504 no warnings 'once';
  430         2036  
  430         175589  
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 6 return keys %{ shift->{'language_data'}{'code_to_name'} };
  2         529  
826             }
827              
828             sub get_language_names {
829 1     1 1 2 return values %{ shift->{'language_data'}{'code_to_name'} };
  1         306  
830             }
831              
832             sub get_language_lookup {
833 1     1 1 2 return %{ shift->{'language_data'}{'code_to_name'} };
  1         384  
834             }
835              
836             sub get_language_from_code {
837 20     20 1 4014 my ( $self, $code, $always_return ) = @_;
838              
839 20   66     42 $code ||= $self->{'locale'};
840 20         31 $code = normalize_tag($code);
841 20 50       36 return if !defined $code;
842              
843 20 100 50     32 $always_return ||= 1 if $code eq $self->get_locale() && $self->get_soft_locale_fallback(); # force $always_return under soft locale objects
      100        
844 20   100     46 $always_return ||= 0;
845              
846 20 100       62 if ( exists $self->{'language_data'}{'code_to_name'}{$code} ) {
    100          
847 5         23 return $self->{'language_data'}{'code_to_name'}{$code};
848             }
849             elsif ($always_return) {
850 14         23 my ( $l, $t ) = split_tag($code);
851 14         30 my $ln = $self->{'language_data'}{'code_to_name'}{$l};
852 14 100       31 my $tn = defined $t ? $self->{'territory_data'}{'code_to_name'}{$t} : '';
853              
854 14 100 66     56 return $code if !$ln && !$tn;
855 8   66     21 $ln ||= $l;
856 8   66     85 $tn ||= $t;
857              
858 8   50     27 my $string = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'locale'} || '{0} ({1})';
859 8         24 $string =~ s/\{0\}/$ln/g;
860 8         15 $string =~ s/\{1\}/$tn/g;
861              
862 8         40 return $string;
863             }
864 1         5 return;
865             }
866              
867             sub get_code_from_language {
868 2     2 1 5 my ( $self, $name ) = @_;
869 2 50       6 return if !$name;
870 2         5 my $key = normalize_for_key_lookup($name);
871 2 100       10 if ( exists $self->{'language_data'}{'name_to_code'}{$key} ) {
872 1         5 return $self->{'language_data'}{'name_to_code'}{$key};
873             }
874 1         4 return;
875             }
876              
877             {
878 430     430   2998 no warnings 'once';
  430         1448  
  430         883096  
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 782     782 1 1067 my ($tag) = @_;
887 782         1806 my ( $l, $t ) = split_tag($tag);
888              
889 782 50       1918 return if !defined $l; # invalid tag is not soft
890              
891 782 100       2948 return if !$t; # no territory part means it is not soft
892 32 100       100 return if tag_is_loadable($tag); # if it can be loaded directly then it is not soft
893 3 100       11 return if !territory_code_is_known($t); # if the territory part is not known then it is not soft
894 1 50       4 return if !tag_is_loadable($l); # if the language part is not known then it is not soft
895 1         5 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 1322 my ( $tag, $as_territory ) = @_; # not documenting internal $as_territory, just use territory_code_is_known() directly
900 50 50       161 have_mod("Locales::DB::Loadable") || return;
901              
902 50 100       1028 if ($as_territory) {
903 10 100       33 return 1 if exists $Locales::DB::Loadable::territory{$tag};
904             }
905             else {
906 40 100       275 return 1 if exists $Locales::DB::Loadable::code{$tag};
907             }
908              
909 17         54 return;
910             }
911              
912             sub get_loadable_language_codes {
913 1 50   1 1 3 have_mod("Locales::DB::Loadable") || return;
914 1         31 return keys %Locales::DB::Loadable::code;
915             }
916              
917             sub territory_code_is_known {
918 10     10 1 45 return tag_is_loadable( $_[0], 1 );
919             }
920              
921             sub split_tag {
922 116143     116143 1 171308 return split( /_/, normalize_tag( $_[0] ), 2 ); # we only do language[_territory]
923             }
924              
925             sub get_i_tag_for_string {
926 2     2 1 7 my $norm = normalize_tag( $_[0] );
927              
928 2 100       9 if ( substr( $norm, 0, 2 ) eq 'i_' ) {
929 1         5 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 27 return ( sort keys %non_locales );
946             }
947              
948             sub is_non_locale {
949 3   50 3 1 8 my $tag = normalize_tag( $_[0] ) || return;
950 3 100       11 return 1 if exists $non_locales{$tag};
951 2         7 return;
952             }
953              
954             sub typical_en_alias_list {
955 1     1 1 5 return ( 'en_us', 'i_default' );
956             }
957              
958             sub is_typical_en_alias {
959 3   50 3 1 8 my $tag = normalize_tag( $_[0] ) || return;
960 3 100 66     17 return 1 if $tag eq 'en_us' || $tag eq 'i_default';
961 2         7 return;
962             }
963              
964             sub normalize_tag {
965 464146     464146 1 456263 my $tag = $_[0];
966 464146 100       691672 return if !defined $tag;
967 464143         479969 $tag =~ tr/A-Z/a-z/;
968 464143         631400 $tag =~ s{\s+}{}g;
969 464143         643231 $tag =~ s{[^a-z0-9]+$}{}; # I18N::LangTags::locale2language_tag() does not allow trailing '_'
970 464143         461325 $tag =~ s{[^a-z0-9]+}{_}g;
971              
972             # would like to do this with a single call, backtracking or indexing ? patches welcome!
973 464143         802764 while ( $tag =~ s/([^_]{8})([^_])/$1\_$2/ ) { } # I18N::LangTags::locale2language_tag() only allows parts bewteen 1 and 8 character
974 464143         904769 return $tag;
975             }
976              
977             sub normalize_tag_for_datetime_locale {
978 2     2 1 6 my ( $pre, $pst ) = split_tag( $_[0] ); # we only do language[_territory]
979 2 50       6 return if !defined $pre;
980              
981 2 100       3 if ($pst) {
982 1         6 return $pre . '_' . uc($pst);
983             }
984             else {
985 1         4 return $pre;
986             }
987             }
988              
989             sub normalize_tag_for_ietf {
990 2     2 1 6 my ( $pre, $pst ) = split_tag( $_[0] ); # we only do language[_territory]
991 2 50       5 return if !defined $pre;
992              
993 2 100       4 if ($pst) {
994 1         5 return $pre . '-' . uc($pst);
995             }
996             else {
997 1         8 return $pre;
998             }
999             }
1000              
1001             sub normalize_for_key_lookup {
1002 4     4 1 6 my $key = $_[0];
1003 4 50       8 return if !defined $key;
1004 4         6 $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         12 $key =~ s{\s+}{}g;
1009 4         4 $key =~ s{[\'\"\-\(\)\[\]\_]+}{}g;
1010 4         13 return $key;
1011             }
1012              
1013             sub plural_rule_string_to_javascript_code {
1014 177     177 1 579 my ( $plural_rule_string, $return ) = @_;
1015 177         324 my $perl = plural_rule_string_to_code( $plural_rule_string, $return );
1016 177         506 $perl =~ s/sub { /function (n) {/;
1017 177         588 $perl =~ s/\$_\[0\]/n/g;
1018 177         515 $perl =~ s/ \(n \% ([0-9]+)\) \+ \(n-int\(n\)\) /n % $1/g;
1019 177         323 $perl =~ s/int\(/parseInt\(/g;
1020 177         432 return $perl;
1021             }
1022              
1023             sub plural_rule_string_to_code {
1024 371     371 1 31361 my ( $plural_rule_string, $return ) = @_;
1025 371 100       746 if ( !defined $return ) {
1026 26         29 $return = 1;
1027             }
1028              
1029             # if you have a better way, patches welcome!!
1030              
1031 371         323 my %m;
1032 371         1126 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         605 $m{$1} = "( (\$_[0] % $1) + (\$_[0]-int(\$_[0])) )";
1037             }
1038              
1039 371         426 my $perl_code = "sub { if (";
1040              
1041 371         1240 for my $or ( split /\s+or\s+/i, $plural_rule_string ) {
1042 427         360 my $and_exp;
1043 427         962 for my $and ( split /\s+and\s+/i, $or ) {
1044 498         562 my $copy = $and;
1045 498         401 my $n = '$_[0]';
1046              
1047 498         569 $copy =~ s/ ?n is not / $n \!\= /g;
1048 498         1419 $copy =~ s/ ?n is / $n \=\= /g;
1049              
1050 498         653 $copy =~ s/ ?n mod ([0-9]+) is not / $m{$1} \!\= /g;
1051 498         767 $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         539 $copy =~ s/ ?n not in ([0-9]+)\s*\.\.\s*([0-9]+) ?/ int\($n\) \!\= $n \|\| $n < $1 \|\| $n \> $2 /g;
1055 498         567 $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         510 $copy =~ s/ ?n not within ([0-9]+)\s*\.\.\s*([0-9]+) ?/ \($n < $1 \|\| $n > $2\) /g;
1059 498         500 $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         878 $copy =~ s/ ?n in ([0-9]+)\s*\.\.\s*([0-9]+) ?/ int\($n\) \=\= $n \&\& $n \>\= $1 \&\& $n \<\= $2 /g;
1063 498         838 $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         671 $copy =~ s/ ?n within ([0-9]+)\s*\.\.\s*([0-9]+) ?/ $n \>\= $1 \&\& $n \<\= $2 /g;
1067 498         560 $copy =~ s/ ?n mod ([0-9]+) within ([0-9]+)\s*\.\.\s*([0-9]+) ?/ $m{$1} \>\= $2 \&\& $m{$1} \<\= $3 /g;
1068              
1069 498 100       831 if ( $copy eq $and ) {
1070 2         11 require Carp;
1071 2         6 Carp::carp("Unknown plural rule syntax");
1072 2         771 return;
1073             }
1074             else {
1075 496         1297 $and_exp .= "($copy) && ";
1076             }
1077             }
1078 425         1865 $and_exp =~ s/\s+\&\&\s*$//;
1079              
1080 425 50       819 if ($and_exp) {
1081 425         885 $perl_code .= " ($and_exp) || ";
1082             }
1083             }
1084 369         1637 $perl_code =~ s/\s+\|\|\s*$//;
1085              
1086 369         589 $perl_code .= ") { return '$return'; } return;}";
1087              
1088 369         811 return $perl_code;
1089             }
1090              
1091             sub plural_rule_hashref_to_code {
1092 5     5 1 8 my ($hr) = @_;
1093              
1094 5 100       19 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         3 $hr->{'category_rules_compiled'} = {
1098             'one' => q{sub { return 'one' if ( ( $n == 1 ) ); return;};},
1099             };
1100              
1101             return sub {
1102              
1103 2     2   629 my ($n) = @_;
1104 2 100       11 return 'one' if $n == 1;
1105 1         8 return;
1106 1         6 };
1107             }
1108             else {
1109 4         11 for my $cat ( get_cldr_plural_category_list(1) ) {
1110 24 100       49 next if !exists $hr->{'category_rules'}{$cat};
1111 5 100       16 next if exists $hr->{'category_rules_compiled'}{$cat};
1112 1         3 $hr->{'category_rules_compiled'}{$cat} = plural_rule_string_to_code( $hr->{'category_rules'}{$cat}, $cat );
1113             }
1114              
1115             return sub {
1116 31     31   34 my ($n) = @_;
1117 31         22 my $match;
1118             PCAT:
1119 31         49 for my $cat ( get_cldr_plural_category_list(1) ) { # use function instead of keys to preserve processing order
1120 142 100       250 next if !exists $hr->{'category_rules_compiled'}{$cat};
1121              
1122             # Does $n match $hr->{$cat} ?
1123              
1124 33 50       73 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       74 if ( $hr->{'category_rules_compiled'}{$cat}->($n) ) {
1131 11         12 $match = $cat;
1132 11         18 last PCAT;
1133             }
1134             }
1135              
1136 31 100       103 return $match if $match;
1137 20         25 return;
1138 4         27 };
1139             }
1140             }
1141              
1142             sub get_cldr_plural_category_list {
1143              
1144 37 100   37 1 128 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 13 my ( $self, $special_lookup ) = @_;
1153              
1154 5         9 my ( $super, $ter ) = split_tag( $self->{'locale'} );
1155             return (
1156 5         45 $self->{'locale'},
1157             ( $super ne $self->{'locale'} && $super ne 'i' ? $super : () ),
1158 6         18 ( @{ $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     23  
    100          
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__