File Coverage

blib/lib/I22r/Translate.pm
Criterion Covered Total %
statement 82 94 87.2
branch 15 24 62.5
condition 3 6 50.0
subroutine 11 11 100.0
pod 4 6 66.6
total 115 141 81.5


line stmt bran cond sub pod time code
1             package I22r::Translate;
2 23     23   319960 use strict;
  23         52  
  23         872  
3 22     22   144 use warnings;
  22         1111  
  22         3760  
4 19     19   1173 use Carp;
  19         920  
  19         1741  
5 19     19   14732 use I22r::Translate::Request;
  19         87  
  19         28964  
6              
7             our $VERSION = '0.96';
8             our %config;
9             our %backends;
10             my $translate_calls = 0;
11              
12             sub config {
13 16     16 1 14584 my ($class, @options) = @_;
14 16 50       328 if (@options == 0) {
15 0         0 return \%config;
16             }
17 16 50       80 if (@options == 1) {
18 0         0 return $config{ $options[0] };
19             }
20 16         80 my %options = @options;
21 16         76 foreach my $key (keys %options) {
22              
23 25 100       191 if ($key =~ /::/) {
24 14     7   1362 $backends{$key} = eval "use $key; 1";
  7         3054  
  7         33635  
  7         197  
25 14 50       78 if ($@) {
26 0         0 carp "Failed to load back end class $key.\n$@";
27 0         0 next;
28             } else {
29             # warn "Loaded back end $key.\n";
30             }
31 14 50       84 if ('HASH' eq ref $options{$key}) {
32 14         28 $key->config( %{$options{$key}} );
  14         124  
33             } else {
34 0         0 carp "Config not correct.\n";
35             }
36             } else {
37 11         65 $config{$key} = $options{$key};
38             }
39             }
40             }
41              
42             sub translate_string {
43 9     9 1 1513 my ($pkg, %input) = @_;
44 9         26 my $text = delete $input{text};
45 9 50       32 if (!defined $text) {
46 0         0 carp "I22r::Translate::translate_string: missing 'text' specifier";
47 0         0 return;
48             }
49 9         59 my %r = $pkg->translate( %input, text => { string => $text } );
50 9         189 return $r{string};
51             }
52              
53             sub translate_list {
54 6     6 1 1755 my ($pkg, %input) = @_;
55 6         23 my $text = delete $input{text};
56 6 50       31 if (!defined $text) {
57 0         0 carp "I22r::Translate::translate_list: missing 'text' specified";
58 0         0 return;
59             }
60 6         18 my $n = -1;
61 6         21 my $texthash = { map { $n++; "string$n" => $_ } @$text };
  24         28  
  24         108  
62 6         47 my %r = $pkg->translate( %input, text => $texthash );
63 6         278 my @r = map { $r{"string$_"} } 0 .. $n;
  24         79  
64 6         76 return @r;
65             }
66              
67             sub translate_hash {
68 6     6 1 74 my ($pkg, %input) = @_;
69 6         21 my $text = delete $input{text};
70 6 50       25 if (!defined $text) {
71 0         0 carp "I22r::Translate::translate_list: missing 'text' specified";
72 0         0 return;
73             }
74 6         12 my $n = -1;
75 6         35 my %r = $pkg->translate( %input, text => $text );
76 6         190 return %r;
77             }
78              
79             sub translate {
80 21     21 0 77 my ($pkg, %input) = @_;
81 21         49 my $src = delete $input{src};
82 21         70 my $dest = delete $input{dest};
83 21         46 $translate_calls++;
84              
85 21         52 my $text = delete $input{text};
86 21         62 my %options = %input;
87              
88 21         1228 my $req = I22r::Translate::Request->new(
89             src => $src, dest => $dest, text => $text, %options );
90 21         131 $pkg->log($req->{logger}, "Built request object" );
91              
92             # 1. see which backends are capable of translating $src|$dest
93             # 2. iterate through backends
94             # 3. pass untranslated entries in %$text to the backend
95             # 4. get results
96             # 5. return all results
97              
98             my %quality = map {
99 21         92 $_ => $_->can_translate($src,$dest)
  21         129  
100             } keys %backends;
101              
102             # TODO - adjust $quality for request, backend adjustments
103 21         225 my @backends = sort { $quality{$b} <=> $quality{$a} } keys %quality;
  0         0  
104 21         64 foreach my $backend (@backends) {
105 21 50       71 next if $quality{$backend} <= 0;
106              
107 21         110 $pkg->log($req->{logger}, "using backend: $backend");
108              
109 21         826 $req->backend($backend);
110 21         47 $req->{backend_start} = time;
111 21         97 $req->apply_filters;
112 21         103 $pkg->log($req->{logger}, "applied input filters");
113 21         123 my @t = $backend->get_translations($req);
114 21         2000754 $pkg->log($req->{logger}, "got translations for ",
115             scalar(@t), " inputs");
116 21         118 $req->unapply_filters;
117 21         1768 $pkg->log($req->{logger}, "removed filters");
118 21         136 $req->invoke_callbacks(@t);
119 21         92 $pkg->log($req->{logger}, "ran callbacks");
120 21         51 delete $req->{backend_start};
121 21         802 $req->backend( undef );
122              
123 21 100       87 last if $req->translations_complete;
124 3 50       24 last if $req->timed_out;
125             }
126 21         104 return $req->return_results;
127             }
128              
129             sub log {
130 165     165 0 463 my ($pkg, $logger, @msg) = @_;
131 165   66     708 $logger //= $config{logger};
132 165 100       476 return if !defined $logger;
133             return eval { $logger->debug(@msg); 1 } ||
134 6   33     7 eval { $logger->log(@msg); 1 } ||
135             print STDERR "I22r::Translate: ",@msg,"\n";
136             }
137              
138             =head1 NAME
139              
140             I22r::Translate - Translate content with the Internationalizationizer
141              
142             =head1 VERSION
143              
144             Version 0.96
145              
146             =head1 SYNOPSIS
147              
148             use I22r::Translate;
149              
150             I22r::Translate->config( ... );
151              
152             $r = I22r::Translate->translate_string(
153             src => 'en', dest => 'de', text => 'Good morning.' );
154              
155             @r = I22r::Translate->translate_list(
156             src => 'es', dest => 'en',
157             text => [ 'Buenos dias.', 'Tengo hambre.' ],
158             filter => [ 'HTML' ]);
159              
160             %r = I22r::Translate->translate_hash(
161             src => 'en',
162             dest => 'es',
163             text => {
164             field1 => 'hello world',
165             field2 => 'How are you?'
166             },
167             timeout => 10 );
168              
169             =head1 DESCRIPTION
170              
171             C<I22r::Translate> is a feature-ful, flexible, extensible framework
172             for translating content between different languages.
173              
174             You start by calling the package L<"config"> method, where you
175             set some global options and configure one or more
176             L<I22r::Translate::Backend> packages.
177              
178             Pass text to be translated with the L<"translate_string">,
179             L<"translate_list">, or L<"translate_hash"> methods. These
180             methods will choose an available backend class to perform the
181             translation(s).
182              
183             =head1 CONFIG
184              
185             There are three levels of configuration that affect every
186             translation request.
187              
188             First, there is global configuration, set with this package's
189             L<"config"> method.
190              
191             Second, there is configuration for each backend. This is also
192             set with this package's L<"config"> method, or with each backend
193             package's C<config> method.
194              
195             Finally, there is configuration that can be set for each
196             individual translation request.
197              
198             Some configuration options are recognized at all three of these
199             levels. Some other options may only be recognized by some of
200             the backends (API keys, for example).
201              
202             Some of the most commonly used configuration options are:
203              
204             =over 4
205              
206             =item timeout => int
207              
208             Stops processing the translation request if this many seconds have
209             passed since the request / current backend was started. Only the
210             completed translations will be returned. You can have a global timeout
211             setting, a separate timeout for each backend, and a separate timeout
212             for the current request.
213              
214             =item callback => CODE
215              
216             Specifies a code reference or a subroutine name that will be invoked
217             when a new translation result is available.
218              
219             The function will be called with two arguments: the
220             L<request|I22r::Translate::Request> object that is handling the
221             translation, and a hash reference containing the fields and values
222             for the new translation result.
223              
224             You can have separate callbacks in
225             the global configuration, for each backend, and for the current
226             request.
227              
228             =item filter => ARRAY
229              
230             Specifies one or more I<filters> to apply to the translation input
231             before text is passed to the backend(s). See L<"FILTERS">, below,
232             for more information. The global configuration, each backend configuration,
233             and each request can specify different filters to use.
234              
235             =item return_type => C<simple> | C<object> | C<hash>
236              
237             By default, return values (include the values from C<translate_hash>)
238             are simple scalars containing translated text, but supplying a
239             C<return_type> configuration parameter can instruct this module to
240             return either a L<I22r::Translate::Result> object or a hash reference
241             for each translation result, either of which will provide access
242             to additional data about the translation.
243              
244             =back
245              
246             =head1 SUBROUTINES/METHODS
247              
248             =head2 config
249              
250             =head2 I22r::Translate->config( \%opts )
251              
252             You must call the C<config> method before you can use the
253             C<I22r::Translate> package. At least one of the options should be
254             the name of a L<I22r::Translate::Backend> with a hash reference
255             of options to configure the backend. A minimal configuration call
256             that uses the L<Google|I22r::Translate::Google> translation backend
257             might look like:
258              
259             I22r::Translate->config(
260             'I22r::Translate::Google' => {
261             ENABLED => 1,
262             API_KEY => 'abcdefghijklmnopqrstuvwxyz0123456789',
263             REFERER => 'http://mysite.com/'
264             },
265             );
266              
267             See the L<"CONFIG"> section above for other common parameters
268             to pass to the C<config> method. See the documentation for each
269             backend for the configuration parameters recognized and required
270             for that backend.
271              
272             =head2 translate_string
273              
274             =head2 $text_in_lang2 = I22r::Translate->translate_string(
275             src => I<lang1>, dest => I<lang2>, text => I<text in lang1>,
276             I<option> => I<value>, ...)
277              
278             Attempt to translate the text in a single scalar from language
279             I<lang1> to language I<lang2>. The C<src>, C<dest>, and C<text>
280             arguments are required. Any other parameters are interpreted as
281             configuration that applies to the translation. See the L<"CONFIG">
282             section for some possible choices.
283              
284             =head2 translate_list
285              
286             =head2 @text_in_lang2 = I22r::Translate->translate_list(
287             src => I<lang1>, dest => I<lang2>, text => [ I<text1>, I<text2>, ... ],
288             I<option> => I<value>, ...)
289              
290             Translate a list of text strings between languages I<lang1>
291             and I<lang2>, returning the translated list. The C<src> and
292             C<dest> arguments are required, and the C<text> argument must
293             be an array reference. Other parameter are interpreted as
294             configuration that applies to the current translation.
295              
296             The output array will have the same number of elements as the
297             C<text> input. If any of the input string were not translated,
298             some or all of the output elements may be C<undef>.
299              
300             For some backends, it may be much more efficient to translate a list
301             of strings at once rather than to call C<translate_string>
302             repeatedly.
303              
304             =head2 translate_hash
305              
306             =head2 %text_in_lang2 = I22r::Translate->translate_hash(
307             src => I<lang1>, dest => I<lang2>, text => [ I<text1>, I<text2>, ... ],
308             I<option> => I<value>, ...)
309              
310             Translate the text string values of a hash reference
311             between languages I<lang1> and I<lang2>, returning a hash with the
312             same keys as the input and translated text as the values.
313             The C<src> and C<dest> arguments are required,
314             and the C<text> argument must
315             be a hash reference with arbitrary keys and values as text
316             strings in I<lang1>. Other parameter are interpreted as
317             configuration that applies to the current translation.
318              
319             The output will not contain key-value pairs for the
320             some or all of the output elements may be C<undef>.
321              
322             =head1 FILTERS
323              
324             Sometimes you do not want to pass a piece of text directly to
325             a translation engine. The text might contain HTML tags or
326             other markup. It might contain proper nouns or other words that
327             you don't intend to translate.
328              
329             The L<I22r::Translate::Filter> provides a mechanism to
330             adjust the text before it is passed to a translation engine and
331             to unadjust the output from the translator.
332              
333             The C<filter> configuration parameter may be used in
334             global configuration (the L<"config"> method), backend
335             configuration (also the L<"config"> method), or in a
336             translation request (the L<translate_XXX|"translate_string">
337             methods). The filter config value is an array reference of
338             the filters to apply to translator input, in the order
339             they are to be applied. You may either provide the name
340             of the filter class (an implementor of the
341             L<I22r::Translation::Filter> role), an instance of a filter
342             class, or a sinple string. In the latter case, C<I22r::Translate>
343             will attempt to convert it to the name of a filter class by
344             prepending C<I22r::Translate::Filter::> to the string.
345              
346             See L<I22r::Translate::Filter::Literal> and
347             L<I22r::Translate::Filter::HTML> for examples of
348             filters that are included with this distribution,
349             and L<I22r::Translate::Filter> for general information and
350             instructions for creating your own filters.
351              
352             =head1 AUTHOR
353              
354             Marty O'Brien, C<< <mob at cpan.org> >>
355              
356             =head1 BUGS
357              
358             Please report any bugs or feature requests to
359             C<bug-i22r-translate at rt.cpan.org>, or through the web interface at
360             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=I22r-Translate>.
361             I will be notified, and then you'll automatically be notified of
362             progress on your bug as I make changes.
363              
364              
365             =head1 SUPPORT
366              
367             You can find documentation for this module with the perldoc command.
368              
369             perldoc I22r::Translate
370              
371              
372             You can also look for information at:
373              
374             =over 4
375              
376             =item * RT: CPAN's request tracker (report bugs here)
377              
378             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=I22r-Translate>
379              
380             =item * AnnoCPAN: Annotated CPAN documentation
381              
382             L<http://annocpan.org/dist/I22r-Translate>
383              
384             =item * CPAN Ratings
385              
386             L<http://cpanratings.perl.org/d/I22r-Translate>
387              
388             =item * Search CPAN
389              
390             L<http://search.cpan.org/dist/I22r-Translate/>
391              
392             =back
393              
394             =head1 SEE ALSO
395              
396             L<Lingua::Translate>
397              
398             =head1 LICENSE AND COPYRIGHT
399              
400             Copyright 2012-2016 Marty O'Brien.
401              
402             This program is free software; you can redistribute it and/or modify it
403             under the terms of either: the GNU General Public License as published
404             by the Free Software Foundation; or the Artistic License.
405              
406             See http://dev.perl.org/licenses/ for more information.
407              
408             =cut
409              
410             1; # End of I22r::Translate
411              
412             __END__
413              
414             TO DO:
415              
416             quality => { backend1 => float, backend2 => float }
417              
418             adjust the quality calculation for backends
419              
420             src_enc, dest_enc