File Coverage

blib/lib/Dancer/Plugin/I18N.pm
Criterion Covered Total %
statement 46 156 29.4
branch 2 66 3.0
condition 0 32 0.0
subroutine 15 28 53.5
pod n/a
total 63 282 22.3


line stmt bran cond sub pod time code
1             package Dancer::Plugin::I18N;
2              
3 2     2   49177 use strict;
  2         5  
  2         86  
4 2     2   11 use warnings;
  2         4  
  2         68  
5              
6 2     2   1956 use Dancer::Plugin;
  2         223357  
  2         138  
7 2     2   16 use Dancer::Config;
  2         3  
  2         54  
8 2     2   1773 use Dancer ':syntax';
  2         822708  
  2         11  
9              
10 2     2   940 use Encode;
  2         6  
  2         193  
11 2     2   2649 use POSIX qw(locale_h);
  2         23551  
  2         85  
12              
13 2     2   5434 use I18N::LangTags;
  2         6986  
  2         114  
14 2     2   9164 use I18N::LangTags::Detect;
  2         5143  
  2         62  
15 2     2   2471 use I18N::LangTags::List;
  2         54279  
  2         189  
16              
17 2     2   6470 use Locale::Maketext::Simple ();
  2         4837  
  2         3001  
18              
19             our $VERSION = '0.42';
20             our %options = (
21             Decode => 1,
22             Export => '_loc',
23              
24             #Encoding => 'utf-8',
25             );
26              
27             # Handler of struct
28             my $handle = undef;
29              
30             # Own subs definition
31             our @array_subs = ();
32              
33             # Settings
34             my $settings = undef;
35              
36             sub _load_i18n_settings {
37 2     2   9 $settings = plugin_setting();
38 2         47 my $n = $settings->{func};
39 2 50 0     10 if (!defined($n)) {
    0          
    0          
40             } elsif (!ref($n) && length($n)) {
41 0     0   0 register $n => sub { _localize(@_); };
  0         0  
42 0         0 push(@array_subs, $n);
43             } elsif (ref($n) eq "ARRAY") {
44 0         0 foreach my $k (@$n) {
45 0     0   0 register $k => sub { _localize(@_); };
  0         0  
46 0         0 push(@array_subs, $k);
47             }
48             }
49 2 50       9 if ($settings->{setlocale}) {
50 0           eval { require Locale::Util; 1 };
  0            
  0            
51 0 0         if ($@) {
52 0           $settings->{setlocale} = undef;
53 0           error("Couldn't initialize Locale::Util ... ", "$@");
54             }
55             }
56             }
57              
58             # Hook definitions
59             add_hook(
60             before => sub {
61             _setup_i18n();
62              
63             # Changing and setting language
64             my $np = $settings->{name_param} || "lang";
65             my $ns = $settings->{name_session} || "language";
66             if ($np && $ns) {
67             if (my $p = param $np) {
68              
69             #my $s = session($ns);
70             #if ((!$s || $p ne $s) && (installed_languages($p))) {
71             if (installed_languages($p)) {
72             session $ns => $p;
73             languages($p);
74             }
75             } elsif (!session($ns)) {
76             session $ns => language_tag();
77             } elsif (!language_tag(session($ns))) {
78             languages(session($ns));
79             }
80             }
81             },
82             );
83              
84             add_hook(
85             before_template => sub {
86             my $tokens = shift;
87             $tokens->{l} = sub { l(@_) };
88             $tokens->{localize} = sub { localize(@_) };
89             $tokens->{language} = sub { language(@_) };
90             $tokens->{language_tag} = sub { language_tag(@_) };
91             $tokens->{languages} = sub { languages(@_) };
92             $tokens->{installed_languages} = sub { installed_languages(@_) };
93              
94             foreach my $k (@{Dancer::Plugin::I18N::array_subs}) {
95             $tokens->{$k} = sub { localize(@_) };
96             }
97             },
98             );
99              
100             =encoding utf8
101              
102             =head1 NAME
103              
104             Dancer::Plugin::I18N - Internationalization for Dancer
105              
106             =head1 SYNOPSIS
107              
108             # MyApp/I18N/de.po
109             msgid "Hello Dancer"
110             msgstr "Hallo Tänzerin"
111            
112             # MyApp/I18N/i_default.po
113             msgid "messages.hello.dancer"
114             msgstr "Hello Dancer - fallback translation"
115            
116             # MyApp/I18N/fr.pm
117             package myapp::I18N::fr;
118             use base 'myapp::I18N';
119             our %Lexicon = ( hello => 'bonjour' );
120             1;
121              
122             package myapp;
123             use Dancer;
124             use Dancer::Plugin::I18N;
125             get '/' => sub {
126             my $lang = languages ;
127             print @$lang . "\n";
128             languages( ['de'] );
129             print STDERR localize('Hello Dancer');
130              
131             template 'index'
132             };
133              
134             # index.tt
135             hello in <% languages %> => <% l('hello') %>
136             # or
137             <% languages('fr') %>This is an <% l('hello') %>
138             # or
139             <% l('Hello Dancer') %>
140             <% l('Hello [_1]', 'Dancer') %>
141             <% l('lalala[_1]lalala[_2]', ['test', 'foo']) %>
142             <% l('messages.hello.dancer') %>
143             # or for big texts
144             <% IF language_tag('fr') %>
145             ...
146             <% ELSE %>
147             ...
148             <% ENDIF %>
149              
150              
151             =head1 DESCRIPTION
152              
153             Supports mo/po files and Maketext classes under your application's I18N namespace.
154              
155             Dancer::Plugin::I18N add L to your L application
156              
157             =cut
158              
159             sub _setup_i18n {
160              
161 0 0 0 0     return if (defined($handle) && ref($handle) eq "HASH");
162              
163 0 0         _load_i18n_settings() if (!$settings);
164              
165 0   0       my $lang_path = $settings->{directory} || path(setting('appdir'), 'I18N');
166              
167 0   0       my $user_opts = $settings->{maketext_options} || {};
168              
169             # Option should be defined as local, because we don't want to change global definition for this
170 0           local %options = (%options, Path => $lang_path, %$user_opts);
171              
172             #Locale::Maketext::Simple->import( %options );
173 0           my $self = __PACKAGE__;
174 0           eval <
175             package $self;
176             Locale::Maketext::Simple->import( \%Dancer\::Plugin\::I18N\::options );
177             END
178              
179 0 0         if ($@) {
180 0           error("Couldn't initialize i18n", "$@");
181             } else {
182 0           debug("Initialized i18n");
183             }
184              
185             =head1 CONFIGURATION
186              
187             You can override any parameter sent to L by specifying
188             a C hashref to the C in you Dancer application
189             config file section. For example, the following configuration will override
190             the C parameter which normally defaults to C<1>:
191              
192             plugins:
193             I18N:
194             directory: I18N
195             lang_default: en
196             maketext_options:
197             Decode: 0
198              
199             All languages fallback to MyApp::I18N which is mapped onto the i-default
200             language tag or change this via options 'language_default'.
201             If you use arbitrary message keys, use i_default.po to translate
202             into English, otherwise the message key itself is returned.
203              
204             Standart directory is in C. In this directory are stored every lang files (*.pm|po|mo).
205              
206             You can defined own function for call locale via settings name C.
207              
208             plugins:
209             I18N:
210             func: "N_"
211              
212             Or defined as array:
213              
214             plugins:
215             I18N:
216             func: ["N_", "_"]
217              
218             Now you can call this function in template or in libs.
219              
220             # index.tt
221             hello in <% languages %> => <% N_('hello') %>
222              
223              
224             Automaticaly change language via param 'lang', can be change in setting
225             via 'name_param' and will be stored in session in tag 'language' or
226             can be changed via 'name_session'. When you use this settings, this plugin automaticaly
227             setting language when you call param 'name_param'. Now if you call every page with
228             param 'lang=en' now plugin automatically set new locale.
229              
230             plugins:
231             I18N:
232             name_param: lang
233             name_session: language
234              
235              
236             Automaticaly settings locales must installed L in version 1.17 or newer.
237              
238             plugins:
239             I18N:
240             setlocale: "LC_TIME"
241              
242             Or defined as array:
243              
244             plugins:
245             I18N:
246             setlocale: ["LC_TIME","LC_NUMERIC"]
247              
248             When you set LC_TIME and use time function for print day name or month name, then will be printed in localed name.
249              
250             =cut
251              
252             # We re-read the list of files in $lang_path
253             # Originally tried to detect via namespaces, but this lists the currently set LANG envvar, which may not
254             # be a supported language. Also misses out .pm files
255             # Is acceptable to re-read this directory once on setup
256 0           my $languages_list = {};
257 0 0         if (opendir my $langdir, $lang_path) {
258 0           foreach my $entry (readdir $langdir) {
259 0 0         next unless $entry =~ m/\A (\w+)\.(?:pm|po|mo) \z/xms;
260 0           my $langtag = $1;
261 0 0         next if $langtag eq "i_default";
262 0           my $language_tag = $langtag;
263              
264             #my $language_tag = "$class\::I18N"->get_handle( $langtag )->language_tag;
265             # Did use the get_handle, but that caused problems because en became "Default (Fallthru) Language"
266             # Just do a simple convert instead
267 0           $language_tag =~ s/_/-/g;
268 0           $languages_list->{$langtag} = I18N::LangTags::List::name($language_tag);
269             }
270 0           closedir $langdir;
271             }
272              
273 0           $handle = {};
274 0           $handle->{installed_languages} = $languages_list;
275              
276             # Setting language
277 0           my $request = request;
278 0           my @languages = ();
279 0           push @languages,
280             I18N::LangTags::implicate_supers(I18N::LangTags::Detect->http_accept_langs(scalar $request->accept_language));
281 0   0       push(@languages, $settings->{lang_default} || 'i-default');
282 0           $handle->{languages} = \@languages;
283 0           _setup_lang();
284             }
285              
286             # Problem is where settings is codepage UTF8 and must be encode to ASCII
287             sub _txt2ascii {
288 0 0   0     return $_[0] ? Encode::encode("ISO-8859-1", $_[0]) : '';
289             }
290              
291             # Setting locale
292             sub _set_locale {
293 0   0 0     my $lang = shift || return;
294 0           my $charset = shift;
295              
296 0           foreach my $l (@_) {
297 0           my $s = &_txt2ascii($l);
298 0           foreach my $k ("ALL", "COLLATE", "CTYPE", "MESSAGES", "MONETARY", "NUMERIC", "TIME") {
299 0 0         if ($s eq ("LC_" . $k)) {
300 2     2   20 no strict 'refs';
  2         4  
  2         414  
301 0           my $c = &{"POSIX::LC_" . $k};
  0            
302 0 0         Locale::Util::web_set_locale($lang, $charset, $c)
303             if (defined($c));
304 0           last;
305             }
306             }
307             }
308             }
309              
310             sub _setup_lang {
311 0 0 0 0     return if (!$handle || !exists($handle->{languages}));
312 2     2   13 no strict 'refs';
  2         7  
  2         2346  
313 0           my $c = __PACKAGE__;
314 0           &{$c . '::_loc_lang'}(@{$handle->{languages}});
  0            
  0            
315              
316             #loc_lang( @{ $handle->{languages} } );
317              
318 0 0         _load_i18n_settings() if (!$settings);
319              
320             # Set locale from config
321 0 0         if (my $s = $settings->{setlocale}) {
322 0 0 0       &_set_locale(language_tag(), &_txt2ascii(setting('charset')) || undef, (ref($s) eq "ARRAY" ? @$s : $s));
323             }
324             }
325              
326             =head1 METHODS
327              
328             =head2 languages
329              
330             Contains languages.
331              
332             languages(['de_DE']);
333             my $lang = languages;
334             print join '', @$lang;
335              
336             =head3 1. Putting new language as first in finded
337              
338             languages('de_DE');
339              
340             =head3 2. Erase all and putting new languages as in arrayref
341              
342             languages(['de_DE',....,'en']);
343              
344             =head3 3. Return putted languages
345              
346             languages();
347              
348             =cut
349              
350             register languages => sub {
351 0     0     my $lang = shift;
352              
353 0           _setup_i18n();
354 0 0         return if (!$handle);
355              
356 0 0         if ($lang) {
357 0 0         if (ref($lang) eq "ARRAY") {
358 0           $handle->{languages} = $lang;
359             } else {
360 0           for (my $i = 0; $i < scalar(@{$handle->{languages}}); $i++) {
  0            
361 0 0         if ($handle->{languages}->[$i] eq $lang) {
362 0           splice(@{$handle->{languages}}, $i, 1);
  0            
363 0           last;
364             }
365             }
366 0           unshift(@{$handle->{languages}}, $lang);
  0            
367             }
368              
369 0           _setup_lang();
370             } else {
371 0           return $handle->{languages};
372             }
373 0           return;
374             };
375              
376             =head2 language
377              
378             return selected locale in your locales list or check if given locale is used(same as language_tag).
379              
380             =cut
381              
382             register language => sub {
383 0     0     my $lang_test = shift;
384              
385 0 0         return language_tag($lang_test) if (defined($lang_test));
386              
387 0           my $c = __PACKAGE__;
388 0   0       my $class = ref $c || $c;
389 0 0         my $lang = $handle ? "$class\::I18N"->get_handle(@{$handle->{languages}}) : "";
  0            
390 0           $lang =~ s/.*:://;
391              
392 0           return $lang;
393             };
394              
395             =head2 language_tag
396              
397             return language tag for current locale. The most notable difference from this
398             method in comparison to C is typically that languages and regions
399             are joined with a dash and not an underscore.
400              
401             language(); # en_us
402             language_tag(); # en-us
403              
404             =head3 1. Returning selected locale
405              
406             print language_tag();
407              
408             =head3 2. Test if given locale used
409              
410             if (language_tag('en')) {}
411              
412             =cut
413              
414             register language_tag => sub {
415 0     0     my $lang_test = shift;
416              
417 0           my $c = __PACKAGE__;
418 0   0       my $class = ref $c || $c;
419 0           my $ret =
420             $handle
421 0 0         ? "$class\::I18N"->get_handle(@{$handle->{languages}})->language_tag
422             : "";
423              
424 0 0         if (defined($lang_test)) {
425 0 0 0       return 1 if ($ret eq $lang_test || $ret =~ /^$lang_test/);
426 0           return 0;
427             }
428              
429 0           return $ret;
430             };
431              
432             =head2 installed_languages
433              
434             Returns a hash of { langtag => "descriptive name for language" } based on language files
435             in your application's I18N directory. The descriptive name is based on I18N::LangTags::List information.
436             If the descriptive name is not available, will be undef.
437              
438             =head3 1. Returning hashref installed language files
439              
440             my $l = installed_languages();
441              
442             =head3 2. Test if given locale is installed in hashref
443              
444             my $t = installed_languages('en');
445            
446              
447             =cut
448              
449             register installed_languages => sub {
450 0 0   0     if (defined($handle)) {
451 0 0         if (defined($_[0])) {
452 0 0 0       return 1
453             if ( $handle->{installed_languages}
454             && $handle->{installed_languages}->{$_[0]});
455 0           return 0;
456             }
457 0           return $handle->{installed_languages};
458             }
459             };
460              
461             =head2 localize | l
462              
463             Localize text.
464              
465             print localize( 'Welcome to Dancer, [_1]', 'sri' );
466              
467             is same as
468              
469             print l( 'Welcome to Dancer, [_1]', 'sri' );
470              
471             or in template
472            
473             <% l('Welcome to Dancer, [_1]', 'sri' ) %>
474              
475             =cut
476              
477 0     0     register localize => sub { _localize(@_); };
478 0     0     register l => sub { _localize(@_); };
479              
480             sub _localize {
481 0     0     _setup_i18n();
482 0 0         return '' if (scalar(@_) == 0);
483 0 0         return join '', @_ if (!defined($handle));
484              
485             #return loc( $_[0], @{ $_[1] } ) if ( ref $_[1] eq 'ARRAY' );
486             #return loc(@_);
487 2     2   39 no strict 'refs';
  2         5  
  2         405  
488 0           my $c = __PACKAGE__;
489 0 0         return &{$c . '::_loc'}($_[0], @{$_[1]}) if (ref $_[1] eq 'ARRAY');
  0            
  0            
490 0           return &{$c . '::_loc'}(@_);
  0            
491             }
492              
493             =head1 OUTLINE
494              
495             $ dancer -a MyAPP
496             $ cd MyAPP
497             $ mkdir I18N
498             $ xgettext.pl --output=I18N/messages.pot --directory=lib/
499             $ ls I18N/
500             messages.pot
501              
502             $ msginit --input=messages.pot --output=sv.po --locale=sv.utf8
503             Created I18N/sv.po.
504              
505             $ vim I18N/sv.po
506              
507             "Content-Type: text/plain; charset=utf-8\n"
508              
509             #: lib/MyApp.pm:50
510             msgid "Guest"
511             msgstr "Gäst"
512            
513             #. ($name)
514             #: lib/MyApp.pm:54
515             msgid "Welcome %1!"
516             msgstr "Välkommen %1!"
517              
518             $ xgettext.pl --output=I18N/messages.pot --directory=view/
519             $ msgmerge --update I18N/sv.po I18N/messages.pot
520             . done.
521              
522             # compile message catalog to binary format
523             $ msgfmt --output-file=I18N/sv.mo I18N/sv.po
524              
525             =head1 SEE ALSO
526              
527             L
528              
529             L
530              
531             =head1 AUTHOR
532              
533             Igor Bujna Eigor.bujna@post.czE
534              
535             =head1 ACKNOWLEDGEMENTS
536              
537             Thanks for authors of L with idea how make it.
538              
539             Franck Cuny Efranck@lumberjaph.netE for L
540              
541             Alexandre (Midnite) Jousset
542              
543             =head1 LICENSE
544              
545             This library is free software; you can redistribute it and/or modify
546             it under the same terms as Perl itself.
547              
548             =cut
549              
550             _load_i18n_settings() if (!$settings);
551             register_plugin;
552              
553             1;