File Coverage

blib/lib/Locale/Meta.pm
Criterion Covered Total %
statement 61 68 89.7
branch 15 26 57.6
condition 12 23 52.1
subroutine 9 9 100.0
pod 4 4 100.0
total 101 130 77.6


line stmt bran cond sub pod time code
1             package Locale::Meta;
2              
3             # ABSTRACT: Localization tool based on Locale::Wolowitz.
4              
5 3     3   27118 use strict;
  3         4  
  3         70  
6 3     3   10 use warnings;
  3         5  
  3         60  
7 3     3   548 use utf8;
  3         9  
  3         11  
8 3     3   49 use Carp;
  3         2  
  3         178  
9 3     3   1115 use JSON::MaybeXS qw/JSON/;
  3         14994  
  3         2163  
10              
11             our $VERSION = "0.007";
12              
13             =head1 NAME
14              
15             Locale::Meta - Multilanguage support loading json structures based on Locale::Wolowitz.
16              
17             =head1 VERSION
18              
19             version 0.007
20              
21             =head1 SYNOPSIS
22              
23             #in ./i18n/file.json
24             {
25             "en": {
26             "color": {
27             "trans" : "color"
28             "meta": {
29             "searchable": 1,
30             }
31             }
32             },
33             "en_gb": {
34             "color": {
35             "trans": "colour"
36             }
37             }
38             }
39              
40             # in your app
41             use Locale::Meta
42              
43             my $lm = Locale::Meta->new('./i18n');
44            
45             print $lm->loc('color', 'en_gb'); # prints 'colour'
46              
47             =head1 DESCRIPTION
48              
49             Locale::Meta has been inspired by Locale::Wolowitz, and the base code, documentation,
50             and function has been taken from it. The main goal of Locale::Meta is to
51             provide the same functionality as Locale::Wolowitz, but removing the dependency of the
52             file names as part of the definition of the language, to manage a new json data structure
53             for the .json files definitions, and also, add a meta field in order to be
54             able to extend the use of the locate to other purposes, like search.
55              
56             The objective of the package is to take different json structures, transform the
57             data into key/value structure and build a big repository into memory to be use as
58             base point to localize language definitions.
59              
60             The metadata attribute "meta" defined on the json file is optional and is used
61             to maintain information related to the definition of the term.
62              
63             package Locale::Meta;
64              
65             =head1 CONSTRUCTOR
66              
67             =head2 new( [ $path / $filename, \%options ] )
68              
69             Creates a new instance of this module. A path to a directory in
70             which JSON localization files exist, or a path to a specific localization
71             file. If you pass a directory, all JSON localization files
72             in it will be loaded and merged. If you pass one file, only that file will be loaded.
73              
74             Note that C will ignore dotfiles in the provided path (e.g.
75             hidden files, backups files, etc.).
76              
77              
78             A hash-ref of options can also be provided. The only option currently supported
79             is C, which is on by default. If on, all JSON files are assumed to be in
80             UTF-8 character set and will be automatically decoded. Provide a false value
81             if your files are not UTF-8 encoded, for example:
82              
83             Locale::Meta->new( '/path/to/files', { utf8 => 0 } );
84              
85              
86             =cut
87              
88             sub new {
89 2     2 1 132887 my ($class, $path, $options) = @_;
90              
91 2   50     13 $options ||= {};
92              
93 2         5 my $self = bless {}, $class;
94              
95 2         7 $self->{json} = JSON->new->relaxed;
96 2         38 $self->{json}->utf8;
97              
98 2 100       9 $self->load_path($path)
99             if $path;
100              
101 2         6 return $self;
102             }
103              
104             =head1 OBJECT METHODS
105              
106             =head2 load_path( $path / $filename )
107              
108             Receives a path to a directory in which JSON localization files exist, or a
109             path to a specific localization file, and loads (and merges) the localization
110             data from the file(s). If localization data was already loaded previously,
111             the structure will be merged, with the new data taking precedence.
112              
113             You can call this method and L
114             as much as you want, the data from each call will be merged with existing data.
115              
116             =cut
117              
118             sub load_path {
119 1     1 1 1 my ($self, $path) = @_;
120              
121 1 50       4 croak "You must provide a path to localization directory."
122             unless $path;
123              
124 1   50     7 $self->{locales} ||= {};
125              
126 1         1 my @files;
127              
128 1 50       21 if (-d $path) {
    0          
129             # open the locales directory
130 1 50       29 opendir(PATH, $path)
131             || croak "Can't open localization directory: $!";
132            
133             # get all JSON files
134 1         17 @files = grep {/^[^.].*\.json$/} readdir PATH;
  4         14  
135              
136 1 50       10 closedir PATH
137             || carp "Can't close localization directory: $!";
138             } elsif (-e $path) {
139 0         0 my ($file) = ($path =~ m{/([^/]+)$})[0];
140 0         0 $path = $`;
141 0         0 @files = ($file);
142             } else {
143 0         0 croak "Path must be to a directory or a JSON file.";
144             }
145              
146             # load the files
147 1         4 foreach (@files) {
148             # read the file's contents and parse it as json
149 2 50       41 open(FILE, "$path/$_")
150             || croak "Can't open localization file $_: $!";
151 2         6 local $/;
152 2         31 my $json = ;
153 2 50       11 close FILE
154             || carp "Can't close localization file $_: $!";
155              
156 2         23 my $data = $self->{json}->decode($json);
157              
158             #Get the language definitions
159 2         6 foreach my $lang (keys %$data){
160 4         4 foreach my $key (keys %{$data->{$lang}}){
  4         9  
161 4   33     13 $self->{locales}->{$key}->{$lang} = $data->{$lang}->{$key}->{trans} || $data->{$lang}->{$key};
162 4   100     23 $self->{locales}->{$key}->{meta} = $data->{$lang}->{$key}->{meta} || {};
163             }
164             };
165             }
166              
167 1         2 return 1;
168             }
169              
170              
171             =head2 charge($structure)
172              
173             Load #structure into $self->{locales}
174              
175             =cut
176              
177             sub charge{
178 1     1 1 290 my ($self, $structure) = @_;
179 1   50     6 $self->{locales} ||= {};
180 1 50       5 if ( (ref $structure) =~ /HASH/ ){
181 1         7 foreach my $lang ( keys %{$structure} ){
  1         4  
182 1         1 foreach my $key ( keys %{$structure->{$lang}} ){
  1         2  
183 1   50     5 $self->{locales}->{$key} ||= {};
184              
185 1   50     3 $self->{locales}->{$key} ||= {};
186 1   33     3 $self->{locales}->{$key}->{$lang} = $structure->{$lang}->{$key}->{trans} || $structure->{$lang}->{$key};
187 1   50     4 $self->{locales}->{$key}->{meta} = $structure->{$lang}->{$key}->{meta} || {};
188             }
189             }
190             }
191             else{
192 0         0 croak "Structure received by charge method isn't a Hash";
193             }
194 1         2 return;
195             }
196              
197             =head2 loc( $msg, $lang, [ @args ] )
198              
199             Returns the string C<$msg>, translated to the requested language (if such
200             a translation exists, otherwise no traslation occurs). Any other parameters
201             passed to the method (C<@args>) are injected to the placeholders in the string
202             (if present).
203              
204             =cut
205              
206             sub loc {
207 5     5 1 303 my ($self, $key, $lang, @args) = @_;
208              
209 5 100       15 return unless defined $key; # undef strings are passed back as-is
210 4 50       8 return $key unless $lang;
211              
212 4 100 66     18 my $ret = $self->{locales}->{$key} && $self->{locales}->{$key}->{$lang} ? $self->{locales}->{$key}->{$lang} : $key;
213              
214 4 50       11 if (scalar @args) {
215 0         0 for (my $i = 1; $i <= scalar @args; $i++) {
216 0         0 $ret =~ s/%$i/$args[$i-1]/g;
217             }
218             }
219              
220 4         14 return $ret;
221             }
222              
223             =head1 DIAGNOSTICS
224              
225             The following exceptions are thrown by this module:
226              
227             =over
228              
229             =item C<< "You must provide a path to localization directory." >>
230              
231             This exception is thrown if you haven't provided the C subroutine
232             a path to a localization file, or a directory of localization files. Read
233             the documentation for the C subroutine above.
234              
235             =item C<< "Can't open localization directory: %s" and "Can't close localization directory: %s" >>
236              
237             This exception is thrown if Locale::Meta failed to open/close the directory
238             of the localization files. This will probably happen due to permission
239             problems. The error message should include the actual reason for the failure.
240              
241             =item C<< "Path must be to a directory or a JSON file." >>
242              
243             This exception is thrown if you passed a wrong value to the C subroutine
244             as the path to the localization directory/file. Either the path is wrong and thus
245             does not exist, or the path does exist, but is not a directory and not a file.
246              
247             =item C<< "Can't open localization file %s: %s" and "Can't close localization file %s: %s" >>
248              
249             This exception is thrown if Locale::Wolowitz fails to open/close a specific localization
250             file. This will usually happen because of permission problems. The error message
251             will include both the name of the file, and the actual reason for the failure.
252              
253             =back
254              
255             =head1 CONFIGURATION AND ENVIRONMENT
256            
257             C requires no configuration files or environment variables.
258              
259             =head1 DEPENDENCIES
260              
261             C B on the following CPAN modules:
262              
263             =over
264              
265             =item * L
266              
267             =item * L
268              
269             =back
270              
271             C recommends L or L for faster
272             parsing of JSON files.
273              
274             =head1 INCOMPATIBILITIES WITH OTHER MODULES
275              
276             None reported.
277              
278             =head1 BUGS AND LIMITATIONS
279              
280             No bugs have been reported.
281              
282             Please report any bugs or feature requests to
283             C
284              
285             =head1 DISCLAIMER OF WARRANTY
286              
287             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
288             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
289             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
290             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
291             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
292             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
293             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
294             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
295             NECESSARY SERVICING, REPAIR, OR CORRECTION.
296              
297             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
298             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
299             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
300             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
301             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
302             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
303             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
304             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
305             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
306             SUCH DAMAGES.
307              
308             =cut
309             1;