File Coverage

blib/lib/Locale/Utils/PluralForms.pm
Criterion Covered Total %
statement 40 47 85.1
branch 4 6 66.6
condition 2 6 33.3
subroutine 8 9 88.8
pod n/a
total 54 68 79.4


line stmt bran cond sub pod time code
1              
2             use Moose;
3 6     6   480417 use MooseX::StrictConstructor;
  6         2477832  
  6         31  
4 6     6   40134  
  6         152930  
  6         19  
5             use namespace::autoclean;
6 6     6   48233  
  6         13  
  6         22  
7             use English qw(-no_match_vars $EVAL_ERROR);
8 6     6   2957 use HTML::Entities qw(decode_entities);
  6         18595  
  6         29  
9 6     6   3540 require LWP::UserAgent;
  6         32055  
  6         4259  
10             require Safe;
11              
12             our $VERSION = '0.003';
13              
14             has language => (
15             is => 'rw',
16             isa => 'Str',
17             trigger => \&_language,
18             );
19              
20             has _all_plural_forms_url => (
21             is => 'rw',
22             isa => 'Str',
23             default => 'http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html',
24             );
25              
26             has _all_plural_forms_html => (
27             is => 'rw',
28             isa => 'Str',
29             default => \&_get_all_plural_forms_html,
30             lazy => 1,
31             clearer => 'clear_all_plural_forms_html',
32             );
33              
34             has all_plural_forms => (
35             is => 'rw',
36             isa => 'HashRef',
37             default => \&_get_all_plural_forms,
38             lazy => 1,
39             );
40              
41             has plural_forms => (
42             is => 'rw',
43             isa => 'Str',
44             default => 'nplurals=1; plural=0',
45             lazy => 1,
46             trigger => \&_calculate_plural_forms,
47             );
48              
49             has nplurals => (
50             is => 'rw',
51             isa => 'Int',
52             default => 1,
53             lazy => 1,
54             init_arg => undef,
55             writer => '_nplurals',
56             );
57              
58             has plural_code => (
59             is => 'rw',
60             isa => 'CodeRef',
61             default => sub { return sub { return 0 } },
62             lazy => 1,
63             init_arg => undef,
64             writer => '_plural_code',
65             );
66              
67             my $self = shift;
68              
69 0     0   0 my $url = $self->_all_plural_forms_url;
70             my $ua = LWP::UserAgent->new;
71 0         0 $ua->env_proxy;
72 0         0 my $response = $ua->get($url);
73 0         0 $response->is_success
74 0         0 or confess "$url $response->status_line";
75 0 0       0  
76             return $response->decoded_content;
77             }
78 0         0  
79             my $self = shift;
80              
81             ## no critic (ComplexRegexes)
82 1     1   2 my @match = $self->_all_plural_forms_html =~ m{
83             <tr \s+ class="row- (?: even | odd ) ">
84             \s*
85 1         28 <td> ( [^<>]+ ) </td>
86             \s*
87             <td> ( [^<>]+ ) .*? </td>
88             \s*
89             <td> ( nplurals [^<>]+? ) [;]? </td>
90             \s*
91             </tr>
92             }xmsg;
93             ## use critic(ComplexRegexes)
94             $self->clear_all_plural_forms_html;
95             my %all_plural_forms;
96             while ( my ($iso, $english_name, $plural_forms) = splice @match, 0, 3 ) { ## no critic (MagicNumbers)
97 1         35 $english_name =~ s{ \s+ \z }{}xms;
98 1         1 $all_plural_forms{ decode_entities($iso) } = {
99 1         6 english_name => decode_entities($english_name),
100 3         9 plural_forms => decode_entities($plural_forms),
101 3         30 };
102             }
103              
104             return \%all_plural_forms;
105             }
106              
107 1         27 my ($self, $language) = @_;
108              
109             my $all_plural_forms = $self->all_plural_forms;
110             if ( exists $all_plural_forms->{$language} ) {
111 4     4   9 return $self->plural_forms(
112             $all_plural_forms->{$language}->{plural_forms}
113 4         106 );
114 4 100       9 }
115             $language =~ s{_ .* \z}{}xms;
116             if ( exists $all_plural_forms->{$language} ) {
117 2         53 return $self->plural_forms(
118             $all_plural_forms->{$language}->{plural_forms}
119 2         7 );
120 2 100       6 }
121              
122             return confess
123 1         26 "Missing plural forms for language $language in all_plural_forms";
124             }
125              
126 1         15 my $self = shift;
127              
128             my $plural_forms = $self->plural_forms;
129             $plural_forms =~ s{\b ( nplurals | plural | n ) \b}{\$$1}xmsg;
130             my $safe = Safe->new;
131 6     6   8 my $nplurals_code = <<"EOC";
132             my \$n = 0;
133 6         157 my (\$nplurals, \$plural);
134 6         90 $plural_forms;
135 6         32 \$nplurals;
136 6         5133 EOC
137             $self->_nplurals(
138             $safe->reval($nplurals_code)
139             or confess
140             "Code of Plural-Forms $plural_forms is not safe, $EVAL_ERROR"
141             );
142 6   33     18 my $plural_code = <<"EOC";
143             sub {
144             my \$n = shift;
145             my (\$nplurals, \$plural);
146             $plural_forms;
147 6         19 return 0 + (\$plural || 0);
148             }
149             EOC
150             $self->_plural_code(
151             $safe->reval($plural_code)
152             or confess "Code $plural_forms is not safe, $EVAL_ERROR"
153             );
154              
155 6   33     17 return $self;
156             }
157              
158             __PACKAGE__->meta->make_immutable;
159              
160 6         517 1;
161              
162              
163             =head1 NAME
164              
165             Locale::Utils::PluralForms - Utils to use plural forms
166              
167             =head1 VERSION
168              
169             0.003
170              
171             =head1 SYNOPSIS
172              
173             use Locale::Utils::PluralForms;
174              
175             my $obj = Locale::Utils::PluralForms->new;
176              
177             Data downloaded from web
178              
179             $obj = Locale::Utils::PluralForms->new(
180             language => 'en_GB', # fallbacks from given en_GB to en
181             );
182              
183             Data of given data structure
184              
185             $obj = Locale::Utils::PluralForms->new(
186             all_plural_forms => {
187             'en' => {
188             english_name => 'English',
189             plural_forms => 'nplurals=2; plural=(n != 1)',
190             },
191             # next languages
192             },
193             );
194              
195             Getter
196              
197             my $language = $obj->language;
198             my $all_plural_forms = $obj->all_plural_forms;
199             my $plural_forms = $obj->plural_forms;
200             my $nplurals = $obj->nplurals;
201             my $plural_code = $obj->plural_code;
202             my $plural = $obj->plural_code->($number);
203              
204             =head1 DESCRIPTION
205              
206             =head2 Find plural forms for the language
207              
208             This module helps to find the plural forms for all languages.
209             It downloads the plural forms for all languages from web.
210             Then it stores the extracted data
211             into a data structure named "all_plural_forms".
212              
213             It is possible to fill that data structure
214             before method "language" is called first time
215             or to cache after first method call "language".
216              
217             =head2 "plural" as subroutine
218              
219             In the header of a PO- or MO-File
220             is an entry is called "Plural-Forms".
221             How many plural forms the language has, is described there in "nplurals".
222             The second Information in "Plural-Forms" describes as a formula,
223             how to choose the "plural".
224              
225             This module compiles plural forms
226             to a code references in a secure way.
227              
228             =head1 SUBROUTINES/METHODS
229              
230             =head2 Find plural forms for the language
231              
232             =head3 method language
233              
234             Set the language to switch to the plural forms of that language.
235             "plural_forms" is set then and "nplurals" and "plural_code" will be calculated.
236              
237             $obj->language('de_AT'); # A fallback finds plural forms of language de
238             # because de_AT is not different.
239              
240             Read the language back.
241              
242             $obj->language eq 'de_AT';
243              
244             =head3 method all_plural_forms
245              
246             Set the data structure.
247              
248             $obj->all_plural_forms({
249             'de' => {
250             english_name => 'German',
251             plural_forms => 'nplurals=2; plural=(n != 1)',
252             },
253             # next languages
254             });
255              
256             Read the data structure back.
257              
258             my $hash_ref = $obj->all_plural_forms;
259              
260             =head2 executable plural forms
261              
262             =head3 method plural_forms
263              
264             Set "plural_forms" if no "language" is set.
265             After that "nplurals" and "plural_code" will be calculated in a safe way.
266              
267             $obj->plural_forms('nplurals=1; plural=0');
268              
269             Or read it back.
270              
271             my $plural_forms = $obj->plural_forms;
272              
273             =head3 method nplurals
274              
275             This method get back the calculated count of plurals.
276              
277             If no "language" and no "plural_forms" is set,
278             the defaults for "nplurals" is:
279              
280             my $count = $obj->nplurals # returns: 1
281              
282             There is no public setter for "nplurals"
283             and it is not possible to set them in the constructor.
284             Call method "language" or "plural_forms"
285             or set attribute "language" or "plural_forms" in the constructor.
286             After that "nplurals" will be calculated automaticly and safe.
287              
288             =head2 method plural_code
289              
290             This method get back the calculated code for the "plural"
291             to choose the correct "plural".
292              
293             If no "language" and no "plural_forms" is set,
294             the defaults for plural_code is:
295              
296             my $code_ref = $obj->plural_code # returns: sub { return 0 }
297             my $plural = $obj->plural_code->($number); # returns 0
298              
299             There is no public setter for "plural_code"
300             and it is not possible to set them in the constructor.
301             Call method "language" or "plural_forms"
302             or set attribute "language" or "plural_forms" in the constructor.
303             After that "plural_code" will be calculated automaticly and safe.
304              
305             For the example plural forms C<'nplurals=2; plural=(n != 1)'>:
306              
307             $plural = $obj->plural_code->(0), # $plural is 1
308             $plural = $obj->plural_code->(1), # $plural is 0
309             $plural = $obj->plural_code->(2), # $plural is 1
310             $plural = $obj->plural_code->(3), # $plural is 1
311             ...
312              
313             =head1 EXAMPLE
314              
315             Inside of this distribution is a directory named example.
316             Run the *.pl files.
317              
318             =head1 DIAGNOSTICS
319              
320             none
321              
322             =head1 CONFIGURATION AND ENVIRONMENT
323              
324             none
325              
326             =head1 DEPENDENCIES
327              
328             L<Moose|Moose>
329              
330             L<MooseX::StrictConstructor|MooseX::StrictConstructor>
331              
332             L<namespace::autoclean|namespace::autoclean>
333              
334             L<English|English>
335              
336             L<HTML::Entities|HTML::Entities>
337              
338             L<LWP::UserAgent|LWP::UserAgent>
339              
340             L<Safe|Safe>
341              
342             =head1 INCOMPATIBILITIES
343              
344             not known
345              
346             =head1 BUGS AND LIMITATIONS
347              
348             not known
349              
350             =head1 SEE ALSO
351              
352             L<http://en.wikipedia.org/wiki/Gettext>
353              
354             L<http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html>
355              
356             L<Locele::TextDomain|Locele::TextDomain>
357              
358             =head1 AUTHOR
359              
360             Steffen Winkler
361              
362             =head1 LICENSE AND COPYRIGHT
363              
364             Copyright (c) 2011 - 2022,
365             Steffen Winkler
366             C<< <steffenw at cpan.org> >>.
367             All rights reserved.
368              
369             This module is free software;
370             you can redistribute it and/or modify it
371             under the same terms as Perl itself.