File Coverage

blib/lib/Locale/Meta.pm
Criterion Covered Total %
statement 67 74 90.5
branch 15 26 57.6
condition 13 23 56.5
subroutine 9 9 100.0
pod 4 4 100.0
total 108 136 79.4


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   28586 use strict;
  3         4  
  3         70  
6 3     3   9 use warnings;
  3         3  
  3         56  
7 3     3   487 use utf8;
  3         8  
  3         11  
8 3     3   49 use Carp;
  3         3  
  3         166  
9 3     3   1119 use JSON::MaybeXS qw/JSON/;
  3         14772  
  3         2086  
10              
11             our $VERSION = "0.008";
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.008
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 129961 my ($class, $path, $options) = @_;
90              
91 2   50     14 $options ||= {};
92              
93 2         4 my $self = bless {}, $class;
94              
95 2         10 $self->{json} = JSON->new->relaxed;
96 2         47 $self->{json}->utf8;
97              
98 2 100       10 $self->load_path($path)
99             if $path;
100              
101 2         5 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     4 $self->{locales} ||= {};
125              
126 1         1 my @files;
127              
128 1 50       23 if (-d $path) {
    0          
129             # open the locales directory
130 1 50       28 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         13  
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         2 foreach (@files) {
148             # read the file's contents and parse it as json
149 2 50       43 open(FILE, "$path/$_")
150             || croak "Can't open localization file $_: $!";
151 2         5 local $/;
152 2         24 my $json = ;
153 2 50       46 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         7  
161 4   100     12 $self->{locales}->{$key} ||= {};
162 4   33     13 $self->{locales}->{$key}->{$lang} = $data->{$lang}->{$key}->{trans} || $data->{$lang}->{$key};
163 4   100     18 $self->{locales}->{$key}->{meta} ||={};
164 4         4 foreach my $meta_key ( keys %{$data->{$lang}->{$key}->{meta}} ) {
  4         17  
165 1         4 $self->{locales}->{$key}->{meta}->{$meta_key} = $data->{$lang}->{$key}->{meta}->{$meta_key};
166             }
167             }
168             };
169             }
170              
171 1         2 return 1;
172             }
173              
174              
175             =head2 charge($structure)
176              
177             Load #structure into $self->{locales}
178              
179             =cut
180              
181             sub charge{
182 1     1 1 343 my ($self, $structure) = @_;
183 1   50     9 $self->{locales} ||= {};
184 1 50       6 if ( (ref $structure) =~ /HASH/ ){
185 1         12 foreach my $lang ( keys %{$structure} ){
  1         4  
186 1         2 foreach my $key ( keys %{$structure->{$lang}} ){
  1         12  
187 1   50     5 $self->{locales}->{$key} ||= {};
188 1   33     4 $self->{locales}->{$key}->{$lang} = $structure->{$lang}->{$key}->{trans} || $structure->{$lang}->{$key};
189 1   50     4 $self->{locales}->{$key}->{meta} ||={};
190 1         1 foreach my $meta_key ( keys %{$structure->{$lang}->{$key}->{meta}} ) {
  1         4  
191 1         3 $structure->{$lang}->{$key}->{meta}->{$meta_key} = $structure->{$lang}->{$key}->{meta}->{$meta_key};
192             }
193             }
194             }
195             }
196             else{
197 0         0 croak "Structure received by charge method isn't a Hash";
198             }
199 1         3 return;
200             }
201              
202             =head2 loc( $msg, $lang, [ @args ] )
203              
204             Returns the string C<$msg>, translated to the requested language (if such
205             a translation exists, otherwise no traslation occurs). Any other parameters
206             passed to the method (C<@args>) are injected to the placeholders in the string
207             (if present).
208              
209             =cut
210              
211             sub loc {
212 5     5 1 339 my ($self, $key, $lang, @args) = @_;
213              
214 5 100       15 return unless defined $key; # undef strings are passed back as-is
215 4 50       7 return $key unless $lang;
216              
217 4 100 66     20 my $ret = $self->{locales}->{$key} && $self->{locales}->{$key}->{$lang} ? $self->{locales}->{$key}->{$lang} : $key;
218              
219 4 50       10 if (scalar @args) {
220 0         0 for (my $i = 1; $i <= scalar @args; $i++) {
221 0         0 $ret =~ s/%$i/$args[$i-1]/g;
222             }
223             }
224              
225 4         17 return $ret;
226             }
227              
228             =head1 DIAGNOSTICS
229              
230             The following exceptions are thrown by this module:
231              
232             =over
233              
234             =item C<< "You must provide a path to localization directory." >>
235              
236             This exception is thrown if you haven't provided the C subroutine
237             a path to a localization file, or a directory of localization files. Read
238             the documentation for the C subroutine above.
239              
240             =item C<< "Can't open localization directory: %s" and "Can't close localization directory: %s" >>
241              
242             This exception is thrown if Locale::Meta failed to open/close the directory
243             of the localization files. This will probably happen due to permission
244             problems. The error message should include the actual reason for the failure.
245              
246             =item C<< "Path must be to a directory or a JSON file." >>
247              
248             This exception is thrown if you passed a wrong value to the C subroutine
249             as the path to the localization directory/file. Either the path is wrong and thus
250             does not exist, or the path does exist, but is not a directory and not a file.
251              
252             =item C<< "Can't open localization file %s: %s" and "Can't close localization file %s: %s" >>
253              
254             This exception is thrown if Locale::Wolowitz fails to open/close a specific localization
255             file. This will usually happen because of permission problems. The error message
256             will include both the name of the file, and the actual reason for the failure.
257              
258             =back
259              
260             =head1 CONFIGURATION AND ENVIRONMENT
261            
262             C requires no configuration files or environment variables.
263              
264             =head1 DEPENDENCIES
265              
266             C B on the following CPAN modules:
267              
268             =over
269              
270             =item * L
271              
272             =item * L
273              
274             =back
275              
276             C recommends L or L for faster
277             parsing of JSON files.
278              
279             =head1 INCOMPATIBILITIES WITH OTHER MODULES
280              
281             None reported.
282              
283             =head1 BUGS AND LIMITATIONS
284              
285             No bugs have been reported.
286              
287             Please report any bugs or feature requests to
288             C
289              
290             =head1 DISCLAIMER OF WARRANTY
291              
292             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
293             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
294             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
295             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
296             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
297             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
298             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
299             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
300             NECESSARY SERVICING, REPAIR, OR CORRECTION.
301              
302             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
303             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
304             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
305             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
306             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
307             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
308             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
309             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
310             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
311             SUCH DAMAGES.
312              
313             =cut
314             1;