File Coverage

blib/lib/I18N/Handle.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package I18N::Handle;
2 5     5   2487 use warnings;
  5         10  
  5         153  
3 5     5   27 use strict;
  5         9  
  5         156  
4 5     5   4829 use Moose;
  0            
  0            
5             use I18N::Handle::Locale;
6             use File::Find::Rule;
7             use Locale::Maketext::Lexicon ();
8              
9             our $VERSION = '0.051';
10              
11             has base => ( is => 'rw' );
12              
13             has accept_langs => (
14             is => 'rw',
15             isa => 'ArrayRef',
16             traits => [ 'Array' ],
17             handles => {
18             'add_accept' => 'push',
19             'accepted' => 'elements'
20             },
21             default => sub { [] } );
22              
23             has langs => (
24             is => 'rw' ,
25             isa => 'ArrayRef' ,
26             traits => [ 'Array' ],
27             handles => {
28             add_lang => 'push',
29             can_speak => 'elements'
30             },
31             default => sub { [] }
32             ); # can speaks
33              
34             has current => ( is => 'rw' ); # current language
35              
36             has fallback_lang => ( is => 'rw' );
37              
38             our $singleton;
39              
40             sub BUILDARGS {
41             my $self = shift;
42             my %args = @_;
43             return \%args;
44             }
45              
46             sub BUILD {
47             my $self = shift;
48             my %args = %{ +shift };
49              
50             my %import;
51             if( $args{po} ) {
52             # XXX: check po for ref
53             $args{po} = ( ref $args{po} eq 'ARRAY' ) ? $args{po} : [ $args{po} ];
54              
55             my %langs = $self->_scan_po_files( $args{po} );
56              
57             # $self->{_langs} = [ keys %langs ];
58              
59             $self->add_lang( keys %langs );
60              
61             %import = ( %import, %langs );
62             }
63              
64             if( $args{locale} ) {
65             $args{locale} = ( ref $args{po} eq 'ARRAY' ) ? $args{locale} : [ $args{locale} ];
66             my %langs = $self->_scan_locale_files( $args{locale} );
67              
68             # $self->{_langs} = [ keys %langs ];
69              
70             $self->add_lang( keys %langs );
71              
72             %import = ( %import, %langs );
73             }
74              
75             %import = ( %import, %{ $args{import} } ) if( $args{import} );
76              
77             for my $format ( qw(Gettext Msgcat Slurp Tie) ) {
78             next unless $args{ $format };
79             my $list = $args{ $format };
80             while ( my ($tag,$arg) = each %$list ) {
81              
82             $tag = $self->_unify_langtag( $tag );
83              
84             if ( ! ref $arg ) {
85             $import{ $tag } = [ $format => $arg ]
86             }
87             elsif ( ref $arg eq 'ARRAY' ) {
88             $import{ $tag } = [ map { $format => $_ } @$arg ]
89             }
90              
91             # push @{ $self->{_langs} }, $self->_unify_langtag( $tag );
92             $self->add_lang( $tag );
93             }
94             }
95              
96             $import{_style} = $args{style} if( $args{style} );
97              
98             $self->base( I18N::Handle::Locale->new( \%import ) );
99             $self->base->init;
100              
101             return $self if $args{no_global_loc};
102              
103             my $loc_name = $args{'loc'} || '_';
104             if( $args{loc_func} ) {
105             my $loc_func = $args{loc_func};
106             {
107             no strict 'refs';
108             no warnings 'redefine';
109             *{ '::'.$loc_name } = sub {
110             return $loc_func->( $self, $self->base->get_current_handle );
111             };
112             }
113             } else {
114             $self->install_global_loc( $loc_name , $self->base->get_current_handle );
115             }
116             return $self;
117             }
118              
119              
120             sub singleton {
121             my ($class,%args) = @_;
122             return $singleton ||= $class->new( %args );
123             }
124              
125             # translate zh_TW => zh-tw
126             # see Locale::Maketext ,
127             # · $lh = YourProjClass->get_handle( ...langtags... ) || die "lg-handle?";
128             # This tries loading classes based on the language-tags you give (like "("en-US", "sk", "kon", "es-MX", "ja", "i-klingon")",
129             # and for the first class that succeeds, returns YourProjClass::language->new().
130              
131             sub _unify_langtag {
132             my ($self,$tag) = @_;
133             $tag =~ tr<_A-Z><-a-z>; # lc, and turn _ to -
134             $tag =~ tr<-a-z0-9><>cd; # remove all but a-z0-9-
135             return $tag;
136             }
137              
138             sub _scan_po_files {
139             my ($self,$dir) = @_;
140             my @files = File::Find::Rule->file->name("*.po")->in(@$dir);
141             my %langs;
142             for my $file ( @files ) {
143             my ($tag) = ($file =~ m{([a-z]{2}(?:_[a-zA-Z]{2})?)\.po$}i );
144             $langs{ $self->_unify_langtag($tag ) } = [ Gettext => $file ];
145             }
146             return %langs;
147             }
148              
149             sub _scan_locale_files {
150             my ($self,$dir) = @_;
151             my @files = File::Find::Rule->file->name("*.mo")->in( @$dir );
152             my %langs;
153             for my $file ( @files ) {
154             my ($tag) = ($file =~ m{([a-z]{2}(?:_[a-zA-Z]{2})?)/LC_MESSAGES/}i );
155             $langs{ $self->_unify_langtag($tag ) } = [ Gettext => $file ];
156             }
157             return %langs;
158             }
159              
160             sub speaking {
161             my $self = shift;
162             return $self->current();
163             }
164              
165             sub speak {
166             my ($self,$lang) = @_;
167             if( grep { $lang eq $_ } $self->can_speak ) {
168             $self->current( $lang );
169             $self->base->speak( $lang );
170             } else {
171             if ( $self->fallback_lang ) {
172             $self->current( $self->fallback_lang );
173             $self->base->speak( $self->fallback_lang );
174             }
175             }
176             return $self;
177             }
178              
179             sub accept {
180             my ($self,@langs) = @_;
181             for my $lang ( map { $self->_unify_langtag( $_ ) } @langs ) {
182             if( grep { $lang eq $_ } $self->can_speak ) {
183             $self->add_accept( $lang );
184             } else {
185             warn "Not accept language $lang..";
186             }
187             }
188             return $self;
189             }
190              
191             # XXX: check locale::maketext fallback option.
192             sub fallback {
193             my ($self,$lang) = @_;
194             $self->fallback_lang( $lang );
195             return $self;
196             }
197              
198             sub install_global_loc {
199             my ( $self, $loc_name ) = @_;
200             my $loc_method = $self->base->get_loc_method();
201             {
202             no strict 'refs';
203             no warnings 'redefine';
204             *{ '::'.$loc_name } = $loc_method;
205             }
206             }
207              
208             __PACKAGE__->meta->make_immutable;
209             1;
210             __END__
211              
212             =head1 NAME
213              
214             I18N::Handle - A common i18n handler for web frameworks and applications.
215              
216             =head1 DESCRIPTION
217              
218             B<***THIS MODULE IS STILL IN DEVELOPMENT***>
219              
220             L<I18N::Handle> is a common handler for web frameworks and applications.
221              
222             I18N::Handle also provides exporting a global loc function to make localization,
223             the default loc function name is C<"_">. To change the exporting loc function name
224             , please use C<loc> option.
225              
226             The difference between I18N::Handle and L<Locale::Maketext> is that
227             I18N::Handle automatically does most things for you, and it provides simple API
228             like C<speak>, C<can_speak> instead of C<get_handle>, C<languages>.
229              
230             To generate po/mo files, L<App::I18N> is an utility for this, App::I18N is a
231             command-line tool for parsing, exporting, managing, editing, translating i18n
232             messages. See also L<App::I18N>.
233              
234             =head1 SYNOPSIS
235              
236             Ideas are welcome. just drop me a line.
237              
238             option C<import> takes the same arguments as L<Locale::Maketext::Lexicon> takes.
239             it's I<language> => [ I<format> => I<source> ].
240            
241             use I18N::Handle;
242             my $hl = I18N::Handle->new(
243             import => {
244             en => [ Gettext => 'po/en.po' ],
245             fr => [ Gettext => 'po/fr.po' ],
246             ja => [ Gettext => 'po/ja.po' ],
247             })->accept( qw(en fr) )->speak( 'en' );
248              
249             Or a simple way to import gettext po files:
250             This will transform the args to the args that C<import> option takes:
251              
252             use I18N::Handle;
253             my $hl = I18N::Handle->new(
254             Gettext => {
255             en => 'po/en.po',
256             fr => 'po/fr.po',
257             ja => [ 'po/ja.po' , 'po2/ja.po' ],
258             })->accept( qw(en fr) )->speak( 'en' );
259              
260              
261             print _('Hello world');
262              
263             $hl->speak( 'fr' );
264             $hl->speak( 'ja' );
265             $hl->speaking; # return 'ja'
266              
267             my @langs = $hl->can_speak(); # return 'en', 'fr', 'ja'
268              
269             =head1 OPTIONS
270              
271             =over 4
272              
273             =item I<format> => { I<language> => I<source> , ... }
274              
275             Format could be I<Gettext | Msgcat | Slurp | Tie>.
276              
277             use I18N::Handle;
278             my $hl = I18N::Handle->new(
279             Gettext => {
280             en => 'po/en.po',
281             fr => 'po/fr.po',
282             ja => [ 'po/ja.po' , 'po2/ja.po' ],
283             });
284             $hl->speak( 'en' );
285              
286             =item C<po> => 'I<path>' | [ I<path1> , I<path2> ]
287              
288             Suppose you have these files:
289              
290             po/en.po
291             po/zh_TW.po
292              
293             When using:
294              
295             I18N::Handle->new( po => 'po' );
296              
297             will be found. can you can get these langauges:
298              
299             [ en , zh-tw ]
300              
301             =item C<locale> => 'path' | [ path1 , path2 ]
302              
303              
304             =item C<import> => Arguments to L<Locale::Maketext::Lexicon>
305              
306             =back
307              
308             =head1 OPTIONAL OPTIONS
309              
310             =over 4
311              
312             =item no_global_loc => bool
313              
314             Do not install global locale method C<"_">.
315              
316             =item style => style ... (Optional)
317              
318             The style could be C<gettext>.
319              
320             =item loc => global loc function name (Optional)
321              
322             The default global loc function name is C<_>.
323              
324             loc => 'loc'
325              
326             =item C<loc_func> => I<CodeRef> (Optional)
327              
328             Use a custom global localization function instead of default localization
329             function.
330              
331             loc_func => sub {
332             my ($self,$lang_handle) = @_;
333              
334             ...
335              
336             return $text;
337             }
338              
339             =back
340              
341             =head1 USE CASES
342              
343             =head2 Handling po files
344              
345             $hl = I18N::Handle->new(
346             po => 'path/to/po',
347             style => 'gettext' # use gettext style format (default)
348             )->speak( 'en' );
349              
350             print _('Hello world');
351              
352              
353             =head2 Handling locale
354              
355             If you need to bind the locale directory structure like this:
356              
357             path/to/locale/en/LC_MESSAGES/app.po
358             path/to/locale/en/LC_MESSAGES/app.mo
359             path/to/locale/zh_tw/LC_MESSAGES/app.po
360             path/to/locale/zh_tw/LC_MESSAGES/app.mo
361              
362             You can just pass the C<locale> option:
363              
364             $hl = I18N::Handle->new(
365             locale => 'path/to/locale'
366             )->speak( 'en_US' );
367              
368             or just use C<import>:
369              
370             $hl = I18N::Handle->new(
371             import => { '*' => 'locale/*/LC_MESSAGES/hello.mo' } );
372              
373             =head2 Handling json files
374              
375             B<not implemented yet>
376              
377             Ensure you have json files:
378              
379             json/en.json
380             json/fr.json
381             json/ja.json
382              
383             Then specify the C<json> option:
384              
385             $hl = I18N::Handle->new( json => 'json' );
386              
387             =head2 Singleton
388              
389             If you need a singleton L<I18N::Handle>, this is a helper function to return
390             the singleton object:
391              
392             $hl = I18N::Handle->singleton( locale => 'path/to/locale' );
393              
394             In your applications, might be like this:
395              
396             sub get_i18n {
397             my $class = shift;
398             return I18N::Handle->singleton( ... options ... )
399             }
400              
401              
402             =head2 Connect to a remote i18n server
403              
404             B<not implemented yet>
405              
406             Connect to a translation server:
407              
408             $handle = I18N::Handle->new(
409             server => 'translate.me' )->speak( 'en_US' );
410              
411              
412             =head2 Binding with database
413              
414             B<not implemented yet>
415              
416             Connect to a database:
417              
418             $handle = I18N::Handle->new(
419             dsn => 'DBI:mysql:database=$database;host=$hostname;port=$port;'
420             );
421              
422             =head2 Binding with Google translation service
423              
424             B<not implemented yet>
425              
426             Connect to google translation:
427              
428             $handle = I18N::Handle->new( google => "" );
429              
430             =head2 Exporting loc function to Text::Xslate
431              
432             my $tx = Text::Xslate->new(
433             path => ['templates'],
434             cache_dir => ".xslate_cache",
435             cache => 1,
436             function => { "_" => \&_ } );
437              
438             Then you can use C<_> function inside your L<Text::Xslate> templates:
439              
440             <: _('Hello') :>
441              
442             =head1 PUBLIC METHODS
443              
444             =head2 new
445              
446             =head2 singleton( I<options> )
447              
448             If you need a singleton L<I18N::Handle>, this is a helper function to return
449             the singleton object.
450              
451             =head2 speak( I<language> )
452              
453             setup current language. I<language>, can be C<en>, C<fr> and so on..
454              
455             =head2 speaking()
456              
457             get current speaking language name.
458              
459             =head2 can_speak()
460              
461             return a list that currently supported.
462              
463             =head2 accept( I<language name list> )
464              
465             setup accept languages.
466              
467             $hl->accpet( qw(en fr) );
468              
469             =head2 fallback( I<language> )
470              
471             setup fallback language. when speak() fails , fallback to this language.
472              
473             $hl->fallback( 'en' );
474              
475             =head1 PRIVATE METHODS
476              
477             =head2 _unify_langtag
478              
479             =head2 _scan_po_files
480              
481             =head2 _scan_locale_files
482              
483              
484              
485              
486              
487              
488             =head1 AUTHOR
489              
490             Yoan Lin E<lt>cornelius.howl {at} gmail.comE<gt>
491              
492             =head1 SEE ALSO
493              
494             L<App::I18N>
495              
496             =head1 LICENSE
497              
498             This library is free software; you can redistribute it and/or modify
499             it under the same terms as Perl itself.
500              
501             =cut