File Coverage

lib/Text/PO/Gettext.pm
Criterion Covered Total %
statement 342 386 88.6
branch 81 146 55.4
condition 39 99 39.3
subroutine 78 83 93.9
pod 51 51 100.0
total 591 765 77.2


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## PO Files Manipulation - ~/lib/Text/PO/Gettext.pm
3             ## Version v0.3.0
4             ## Copyright(c) 2022 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2021/07/12
7             ## Modified 2023/01/04
8             ## All rights reserved
9             ##
10             ## This program is free software; you can redistribute it and/or modify it
11             ## under the same terms as Perl itself.
12             ##----------------------------------------------------------------------------
13             package Text::PO::Gettext;
14             BEGIN
15             {
16 2     2   301044 use strict;
  2         11  
  2         60  
17 2     2   10 use warnings;
  2         4  
  2         51  
18 2     2   11 use warnings::register;
  2         4  
  2         224  
19 2     2   11 use parent qw( Module::Generic );
  2         3  
  2         9  
20 2     2   136 use vars qw( $VERSION $L10N $DOMAIN_RE $LOCALE_RE );
  2         4  
  2         127  
21 2     2   422 use I18N::Langinfo qw( langinfo );
  2         708  
  2         116  
22 2     2   12 use POSIX ();
  2         3  
  2         44  
23 2     2   598 use Text::PO;
  2         5  
  2         18  
24             # l10n_id => lang => string => local string
25 2     2   889 our $L10N = {};
26 2         10 our $DOMAIN_RE = qr/^[a-z]+(\.[a-zA-Z0-9\_\-]+)*$/;
27 2         6 our $LOCALE_RE = qr/^
28             (?<locale>
29             (?<locale_lang>
30             [a-z]{2}
31             )
32             (?:
33             [_-](?<locale_country>[A-Z]{2})
34             )?
35             (?:\.(?<locale_encoding>[\w-]+))?
36             )
37             $/x;
38 2         44 our $VERSION = 'v0.3.0';
39             };
40              
41 2     2   13 use strict;
  2         4  
  2         50  
42 2     2   11 use warnings;
  2         4  
  2         1264  
43              
44             sub init
45             {
46 2     2 1 492792 my $self = shift( @_ );
47 2         111 $self->{category} = 'LC_MESSAGES';
48 2         31 $self->{domain} = '';
49             # We also try LANGUAGE because GNU gettext actually only recognise LANGUAGE
50             # For example: LANGUAGE=fr_FR.utf-8 TEXTDOMAINDIR=./t gettext -d "com.example.api" -s "Bad Request"
51 2   33     63 $self->{locale} = $ENV{LANG} || $ENV{LANGUAGE};
52 2         29 $self->{path} = '';
53 2         32 $self->{plural} = [];
54 2         24 $self->{use_json} = 1;
55 2         11 $self->{_init_strict_use_sub} = 1;
56 2         55 $self->{_init_params_order} = [qw( category path domain locale plural use_json )];
57 2 50       44 $self->SUPER::init( @_ ) || return( $self->pass_error );
58 2 50 33     1724 if( !defined( $self->{path} ) || !length( $self->{path} ) )
59             {
60 0         0 return( $self->error( "No directory path was provided for localisation" ) );
61             }
62 2 50       65 $self->textdomain( $self->{domain} ) || return( $self->pass_error );
63 2         19797 return( $self );
64             }
65              
66             sub addItem
67             {
68 0     0 1 0 my $self = shift( @_ );
69 0         0 my( $locale, $key, $value ) = @_;
70 0         0 my $hash = $self->getDomainHash();
71 0 0 0     0 return( $self->error( "No locale was provided." ) ) if( !defined( $locale ) || !length( $locale ) );
72 0 0 0     0 return( $self->error( "No msgid was provided." ) ) if( !defined( $key ) || !length( $key ) );
73 0         0 $locale = $self->locale_unix( $locale );
74 0 0       0 if( !$self->isSupportedLanguage( $locale ) )
75             {
76 0         0 return( $self->error( "Language requested \"${locale}\" to add item is not supported." ) );
77             }
78 0         0 $hash->{ $locale }->{ $key } = { msgid => $key, msgstr => $value };
79 0         0 return( $hash->{ $locale }->{ $key } );
80             }
81              
82 12     12 1 4813 sub category { return( shift->_set_get_scalar_as_object( 'category', @_ ) ); }
83              
84 1     1 1 1645 sub charset { return( shift->_get_po->charset ); }
85              
86 1     1 1 1094 sub contentEncoding { return( shift->_get_po->content_encoding ); }
87              
88 1     1 1 6 sub contentType { return( shift->_get_po->content_type ); }
89              
90 1     1 1 7 sub currentLang { return( shift->_get_po->current_lang ); }
91              
92 1     1 1 710 sub dgettext { return( shift->dngettext( @_ ) ); }
93              
94             sub dngettext
95             {
96 14     14 1 6261 my $self = shift( @_ );
97 14         31 my $opts = {};
98 14 100       67 $opts = pop( @_ ) if( ref( $_[-1] ) eq 'HASH' );
99 14         72 my( $domain, $msgid, $msgidPlural, $count ) = @_;
100 14         38 my $default;
101             my $index;
102 14 100 66     113 if( !defined( $count ) || $count !~ /^\d+$/ )
103             {
104 10   33     67 $default = $msgidPlural || $msgid;
105             }
106 14 100 100     108 if( !exists( $opts->{locale} ) || !length( $opts->{locale} ) )
107             {
108 7         34 $opts->{locale} = $self->locale;
109             }
110 14         5837 my $hash = $self->getDomainHash({ domain => $domain });
111 14         81 my $plural = $self->plural;
112 14 50       11438 if( !exists( $hash->{ $opts->{locale} } ) )
113             {
114 0 0       0 warnings::warn( "No locale \"$opts->{locale}\" found for the domain \"${domain}\".\n" ) if( warnings::enabled() );
115 0         0 return( Text::PO::String->new( $default ) );
116             }
117 14         90 my $l10n = $hash->{ $opts->{locale} };
118 14         85 my $dict = $l10n->{ $msgid };
119 14 50       42 if( $dict )
120             {
121 14 50       85 if( $plural->length == 0 )
122             {
123 0         0 $plural = $self->getPlural();
124             }
125 14 100       509751 if( ref( $dict->{msgstr} ) eq 'ARRAY' )
126             {
127 4 50 33     558 if( $self->_is_number( $count ) &&
128             int( $plural->[0] ) > 0 )
129             {
130 2     2   15 no warnings 'once';
  2         4  
  2         6453  
131 4         1132 my $n = $count;
132 4         11 my $expr = $plural->[1];
133 4         37 $expr =~ s/(?:^|\b)(?<!\$)(n)(?:\b|$)/\$$1/g;
134 4         226 $index = eval( $expr );
135 4         18 $index = int( $index );
136             }
137             else
138             {
139 0         0 $index = 0;
140             }
141             # return( join( '', @{$dict->{msgstr}->[ $index ]} ) || $default );
142 4         38 my $locale_str = join( '', @{$dict->{msgstr}->[ $index ]} );
  4         23  
143 4 50       62 return( Text::PO::String->new( $locale_str => $opts->{locale} ) ) if( length( "$locale_str" ) );
144 0         0 return( Text::PO::String->new( $default ) );
145             }
146 10   33     1477 return( $dict->{msgstr} || $default );
147             }
148             else
149             {
150 0 0       0 warnings::warn( "No dictionary was found for msgid \"${msgid}\" and domain \"${domain}\"" ) if( warnings::enabled() );
151             }
152 0         0 return( $default );
153             }
154              
155             sub domain
156             {
157 40     40 1 2866 my $self = shift( @_ );
158 40 100       151 if( @_ )
159             {
160 2         15 my $v = shift( @_ );
161 2 50       132 if( !$v )
    50          
162             {
163 0         0 return( $self->error( "No domain was provided." ) );
164             }
165             elsif( $v !~ /^$DOMAIN_RE$/ )
166             {
167 0         0 return( $self->error( "Domain provided \"$v\" contains illegal characters." ) );
168             }
169 2         43 my $caller = [caller(1)]->[3];
170             # We do not call textdomain upon init, because we need both domain and locale to be set first
171             # textdomain() is called directly in init()
172 2 50       21 $self->textdomain( $v ) unless( $caller eq 'Module::Generic::init' );
173 2         20 $self->{domain} = $v;
174             }
175 40         158 return( $self->_set_get_scalar_as_object( 'domain' ) );
176             }
177              
178             sub exists
179             {
180 2     2 1 2468 my $self = shift( @_ );
181 2         12 my $lang = shift( @_ );
182 2 50       171 if( !defined( $lang ) )
    50          
    50          
183             {
184 0         0 return( $self->error( "No language to check for existence was provided." ) );
185             }
186             elsif( !length( $lang ) )
187             {
188 0         0 return( $self->error( "Language provided to check for existence is null." ) );
189             }
190             elsif( $lang !~ /^$LOCALE_RE$/ )
191             {
192 0         0 return( $self->error( "Unsupported locale format \"${lang}\"." ) );
193             }
194 2         17 $lang = $self->locale_unix( $lang );
195 2         22 my $hash = $self->getDomainHash();
196 2         43 return( exists( $hash->{ $lang } ) );
197             }
198              
199             sub fetchLocale
200             {
201 3     3 1 17 my $self = shift( @_ );
202 3         11 my $key = shift( @_ );
203 3         14 my $hash = $self->getDomainHash();
204 3         16 my $spans = [];
205             # Browsing through each available locale language
206             # Make it predictable using sort()
207 3         43 foreach my $k ( sort( keys( %$hash ) ) )
208             {
209 6         40 my $locWeb = $self->locale_web( $k );
210 6         34 push( @$spans, "<span lang=\"${locWeb}\">" . $self->dngettext( $self->domain, $key, { locale => $k }) . '</span>' );
211             }
212 3         24 return( $self->new_array( $spans ) );
213             }
214              
215 2     2 1 171050 sub getDataPath { return( $ENV{TEXTDOMAINDIR} ); }
216              
217             sub getDaysLong
218             {
219 2     2 1 75507 my $self = shift( @_ );
220 2         10 my $opts = $self->_get_args_as_hash( @_ );
221 2         145 my $ref = $self->_get_days( $self->locale );
222 2         6 my $days = $ref->[1];
223 2 100       9 if( $opts->{monday_first} )
224             {
225             # Move Sunday at the end
226 1         4 push( @$days, shift( @$days ) );
227             }
228 2         9 return( $days );
229             }
230              
231             sub getDaysShort
232             {
233 2     2 1 76108 my $self = shift( @_ );
234 2         11 my $opts = $self->_get_args_as_hash( @_ );
235 2         148 my $ref = $self->_get_days( $self->locale );
236 2         6 my $days = $ref->[0];
237 2 100       8 if( $opts->{monday_first} )
238             {
239             # Move Sunday at the end
240 1         3 push( @$days, shift( @$days ) );
241             }
242 2         9 return( $days );
243             }
244              
245             sub getDomainHash
246             {
247 38     38 1 8992 my $self = shift( @_ );
248 38         149 my $opts = $self->_get_args_as_hash( @_ );
249 38   66     4271 $opts->{domain} //= $self->domain;
250            
251 38         19318 my $hash = $L10N;
252 38 50       131 if( !exists( $hash->{ $opts->{domain} } ) )
253             {
254 0         0 retrn( $self->error( "No locale data for domain \"$opts->{domain}\"." ) );
255             }
256 38         414 my $l10n = $hash->{ $opts->{domain} };
257 38 100 66     312 if( exists( $opts->{locale} ) &&
258             defined( $opts->{locale} ) )
259             {
260 18         85 $opts->{locale} = $self->locale_unix( $opts->{locale} );
261 18 50       100 if( length( $opts->{locale} ) == 0 )
262             {
263 0         0 return( $self->error( "Locale was provided, but is empty." ) );
264             }
265 18         68 return( $l10n->{ $opts->{locale} } );
266             }
267 20         80 return( $l10n );
268             }
269              
270 2     2 1 132823 sub getLangDataPath { return( $ENV{TEXTLOCALEDIR} ); }
271              
272             sub getLanguageDict
273             {
274 2     2 1 2375 my $self = shift( @_ );
275 2   50     29 my $lang = shift( @_ ) || return( $self->error( "Language provided, to get its dictionary, is undefined or null." ) );
276 2 50       196 if( $lang !~ /^$LOCALE_RE$/ )
277             {
278 0         0 return( $self->error( "Locale provided (${lang}) is in an unsupported format." ) );
279             }
280 2         28 $lang = $self->locale_unix( $lang );
281            
282 2 100       44 if( !$self->isSupportedLanguage( $lang ) )
283             {
284 1         56 return( $self->error( "Language provided (${lang}), to get its dictionary, is unsupported." ) );
285             }
286 1         24 my $hash = $self->getDomainHash();
287 1 50       11 if( !exists( $hash->{ $lang } ) )
288             {
289 0         0 return( $self->error( "Language provided (${lang}), to get its dictionary, could not be found. This is weird. Most likely a configuration mistake." ) );
290             }
291 1         10 return( $hash->{ $lang } );
292             }
293              
294 1     1 1 12285 sub getLocale { return( shift->locale ); }
295              
296             sub getLocales
297             {
298 1     1 1 1494 my $self = shift( @_ );
299 1   50     20 my $key = shift( @_ ) || return( $self->error( "No text provided to get its localised equivalent" ) );
300 1   50     12 my $res = $self->fetchLocale( $key ) || return( $self->pass_error );
301 1 50       48 if( scalar( @$res ) > 0 )
302             {
303 1         14 return( join( "\n", @$res ) );
304             }
305             else
306             {
307 0         0 return( $key );
308             }
309             }
310              
311             sub getLocalesf
312             {
313 1     1 1 810 my $self = shift( @_ );
314 1   50     19 my $key = shift( @_ ) || return( $self->error( "No text provided to get its localised equivalent" ) );
315 1   50     5 my $res = $self->fetchLocale( $key ) || return( $self->pass_error );
316 1 50       56 if( scalar( @$res ) > 0 )
317             {
318 1         33 for( my $i = 0; $i < scalar( @$res ); $i++ )
319             {
320 2         22 $res->[$i] = sprintf( $res->[$i], @_ );
321             }
322 1         12 return( join( "\n", @$res ) );
323             }
324             else
325             {
326 0         0 return( sprintf( $key, @_ ) );
327             }
328             }
329              
330             sub getMetaKeys
331             {
332 1     1 1 876 my $self = shift( @_ );
333 1         16 my $hash = $self->getDomainHash({ locale => $self->locale });
334 1   50     60 my $po = $hash->{_po} || return( $self->error( "Unable to get the po object in the locale data hash" ) );
335 1         26 return( $po->meta_keys );
336             }
337              
338             sub getMetaValue
339             {
340 1     1 1 37612 my $self = shift( @_ );
341 1   50     23 my $field = shift( @_ ) || return( $self->error( "No meta field provided to get its value." ) );
342 1         6 my $hash = $self->getDomainHash({ locale => $self->locale });
343 1   50     19 my $po = $hash->{_po} || return( $self->error( "Unable to get the po object in the locale data hash" ) );
344 1         21 return( $po->meta( $field ) );
345             }
346              
347             sub getMonthsLong
348             {
349 1     1 1 492 my $self = shift( @_ );
350 1         4 my $ref = $self->_get_months( $self->locale );
351 1         5 return( $ref->[1] );
352             }
353              
354             sub getMonthsShort
355             {
356 1     1 1 38884 my $self = shift( @_ );
357 1         5 my $ref = $self->_get_months( $self->locale );
358 1         11 return( $ref->[0] );
359             }
360              
361             sub getNumericDict
362             {
363 1     1 1 37695 my $self = shift( @_ );
364 1         5 my $ref = $self->_get_numeric_dict( $self->locale );
365 1         12 return( $ref->[0] );
366             }
367              
368             sub getNumericPosixDict
369             {
370 1     1 1 4176 my $self = shift( @_ );
371 1         5 my $ref = $self->_get_numeric_dict( $self->locale );
372 1         12 return( $ref->[1] );
373             }
374              
375             sub getPlural
376             {
377 1     1 1 2 my $self = shift( @_ );
378 1   50     6 my $po = $self->_get_po || return( $self->error( "Unable to get the po object in the locale data hash" ) );
379 1         26 return( $po->plural );
380             }
381              
382             sub getText
383             {
384 2     2 1 725 my $self = shift( @_ );
385 2         6 my( $key, $lang ) = @_;
386 2 50 33     23 return( $self->error( "No text to get its localised equivalent was provided." ) ) if( !defined( $key ) || !length( $key ) );
387 2         17 return( $self->dngettext( $self->domain, $key, { locale => $lang }) );
388             }
389              
390             sub getTextf
391             {
392 1     1 1 3 my $self = shift( @_ );
393 1         4 my $opts = {};
394 1 50       5 $opts = pop( @_ ) if( ref( $_[-1] ) eq 'HASH' );
395 1   33     5 $opts->{lang} = $self->locale || $self->currentLang();
396 1         853 my $key = shift( @_ );
397 1         16 my $text = $self->getText( $key, $opts->{lang} );
398 1         13 return( sprintf( $text, @_ ) );
399             }
400              
401             sub gettext
402             {
403 1     1 1 6 my $self = shift( @_ );
404 1         13 return( $self->dngettext( $self->domain, shift( @_ ) ) );
405             }
406              
407 0     0 1 0 sub gettextf { return( shift->getTextf( @_ ) ); }
408              
409             sub isSupportedLanguage
410             {
411 3     3 1 15 my $self = shift( @_ );
412 3   50     27 my $lang = shift( @_ ) || return(0);
413 3         21 $lang = $self->locale_unix( $lang );
414 3         32 my $dom = $self->domain;
415 3 50       2601 return( $self->error( "No domain \"$dom\" set!" ) ) if( !CORE::exists( $L10N->{ $dom } ) );
416 3         44 my $dict = $L10N->{ $dom };
417 3 100       43 if( CORE::exists( $dict->{ $lang } ) )
418             {
419 2         35 return(1);
420             }
421             else
422             {
423 1         6 return(0);
424             }
425             }
426              
427 1     1 1 16 sub language { return( shift->_get_po->language ); }
428              
429 1     1 1 723 sub languageTeam { return( shift->_get_po->language_team ); }
430              
431 1     1 1 732 sub lastTranslator { return( shift->_get_po->last_translator ); }
432              
433 1     1 1 756 sub mimeVersion { return( shift->_get_po->mime_version ); }
434              
435             sub locale
436             {
437 41     41 1 41990 my $self = shift( @_ );
438 41 100       124 if( @_ )
439             {
440 2         12 my $v = shift( @_ );
441 2 50 33     178 if( !defined( $v ) || !length( $v ) )
    50          
442             {
443 0         0 return( $self->error( "No language was set." ) );
444             }
445             elsif( $v =~ /^$LOCALE_RE$/ )
446             {
447 2 50       55 $v = join( '_', $+{locale_lang}, ( $+{locale_country} ? $+{locale_country} : () ) );
448 2 50       36 $v .= '.' . $+{locale_encoding} if( $+{locale_encoding} );
449             }
450             else
451             {
452 0         0 return( $self->error( "Language provided (\"$v\") is in an unsupported format. Use something like \"en_GB\", \"en-GB\" or simply \"en\" or even \"en_GB.utf-8\"." ) );
453             }
454 2 50       21 return( $self->error( "No domain is set or it has disappeared!" ) ) if( !$self->{domain} );
455 2         36 $self->{locale} = $v;
456 2         30 my $caller = [caller(1)]->[3];
457             # We do not call textdomain upon init, because we need both domain and locale to be set first
458             # textdomain() is called directly in init()
459 2 50       19 $self->textdomain( $self->{domain} ) unless( $caller eq 'Module::Generic::init' );
460             }
461 41         177 return( $self->_set_get_scalar_as_object( 'locale' ) );
462             }
463              
464             sub locale_unix
465             {
466 31     31 1 1421 my $self = shift( @_ );
467 31   66     112 my $loc = shift( @_ ) || $self->locale;
468             # Only once
469 31 50       2069 if( $loc =~ /^$LOCALE_RE$/ )
470             {
471 31 100       633 $loc = join( '_', $+{locale_lang}, ( $+{locale_country} ? $+{locale_country} : () ) );
472 31 100       197 $loc .= '.' . $+{locale_encoding} if( $+{locale_encoding} );
473             }
474 31         148 return( $loc );
475             }
476              
477             sub locale_web
478             {
479 10     10 1 28 my $self = shift( @_ );
480 10   33     44 my $loc = shift( @_ ) || $self->locale;
481             # Only once
482 10 50       278 if( $loc =~ /^$LOCALE_RE$/ )
483             {
484 10 100       176 $loc = join( '-', $+{locale_lang}, ( $+{locale_country} ? $+{locale_country} : () ) );
485 10 100       70 $loc .= '.' . $+{locale_encoding} if( $+{locale_encoding} );
486             }
487 10         70 return( $loc );
488             }
489              
490             sub ngettext
491             {
492 2     2 1 988 my $self = shift( @_ );
493 2         10 my( $msgid, $msgidPlural, $count ) = @_;
494 2         15 return( $self->dngettext( $self->domain, $msgid, $msgidPlural, $count ) );
495             }
496              
497 5     5 1 1485 sub path { return( shift->_set_get_file( 'path', @_ ) ); }
498              
499             sub plural
500             {
501 15     15 1 1512 my $self = shift( @_ );
502 15 50       51 if( @_ )
503             {
504 0         0 return( $self->_set_get_array_as_object( 'plural', @_ ) );
505             }
506             else
507             {
508 15 100       25 if( !scalar( @{$self->{plural}} ) )
  15         53  
509             {
510 1         5 $self->{plural} = $self->getPlural();
511             }
512 15         82 return( $self->_set_get_array_as_object( 'plural' ) );
513             }
514             }
515              
516 1     1 1 39785 sub pluralForms { return( shift->_get_po->plural_forms ); }
517              
518 1     1 1 812 sub po_object { return( shift->_get_po ); }
519              
520 1     1 1 418 sub poRevisionDate { return( shift->_get_po->po_revision_date ); }
521              
522 1     1 1 1972 sub potCreationDate { return( shift->_get_po->pot_creation_date ); }
523              
524 1     1 1 1393 sub projectIdVersion { return( shift->_get_po->project_id_version ); }
525              
526 1     1 1 671 sub reportBugsTo { return( shift->_get_po->report_bugs_to ); }
527              
528             sub textdomain
529             {
530 2     2 1 9 my $self = shift( @_ );
531 2   50     14 my $dom = shift( @_ ) || return( $self->error( "No domain was provided." ) );
532 2         35 my $base = $self->path;
533 2         1551 my $lang = $self->locale_unix;
534 2 50       42 my $path_po = $base->join( $base, $lang, ( $self->category ? $self->category : () ), "${dom}.po" );
535 2 50       266621 my $path_json = $base->join( $base, $lang, ( $self->category ? $self->category : () ), "${dom}.json" );
536 2 50       266730 my $path_mo = $base->join( $base, $lang, ( $self->category ? $self->category : () ), "${dom}.mo" );
537 2         265299 my $file;
538             my $po;
539            
540            
541 2 50 33     106 if( $self->use_json && $path_json->exists )
    0          
    0          
542             {
543 2         1721 $file = $path_json;
544 2   50     38 $po = Text::PO->new( domain => $dom, use_json => 1, debug => $self->debug ) ||
545             return( $self->pass_error( Text::PO->error ) );
546 2 50       44 $po->parse2object( $file ) ||
547             return( $self->pass_error( $po->error ) );
548             }
549             elsif( $path_po->exists )
550             {
551 0         0 $file = $path_po;
552 0   0     0 $po = Text::PO->new( domain => $dom, debug => $self->debug ) ||
553             return( $self->pass_error( Text::PO->error ) );
554 0 0       0 $po->parse( $file ) ||
555             return( $self->pass_error( $po->error ) );
556             }
557             elsif( $path_mo->exists )
558             {
559 0         0 $file = $path_mo;
560 0   0     0 my $mo = Text::PO::MO->new( $file, { domain => $dom, debug => $self->debug }) ||
561             return( $self->pass_error( Text::PO::MO->error ) );
562 0   0     0 $po = $mo->as_object ||
563             return( $self->pass_error( $po->error ) );
564             }
565             else
566             {
567 0         0 return( $self->error( "No data file could be found for \"$dom\" for either json, po, or mo file." ) );
568             }
569 2 100       14 $L10N->{ $dom } = {} if( ref( $L10N->{ $dom } ) ne 'HASH' );
570 2 50       42 my $dict = $L10N->{ $dom }->{ $lang } = {} if( ref( $L10N->{ $dom }->{ $lang } ) ne 'HASH' );
571 2         53 $dict->{_po} = $po;
572             $po->elements->foreach(sub
573             {
574 18     18   1564 my $ref = shift( @_ );
575 18         97 $dict->{ $ref->{msgid} } = $ref;
576 2         74 });
577 2         92 return( $self );
578             }
579              
580 3     3 1 912 sub use_json { return( shift->_set_get_boolean( 'use_json', @_ ) ); }
581              
582             sub _get_days
583             {
584 4     4   1632 my $self = shift( @_ );
585 4         13 my $locale = shift( @_ );
586 4         21 my $oldlocale = POSIX::setlocale( &POSIX::LC_ALL );
587 4         15 my $short = $self->new_array;
588 4         78 my $long = $self->new_array;
589              
590 4 50       83 POSIX::setlocale( &POSIX::LC_ALL, $locale ) if( defined( $locale ) );
591              
592 4         56 for (my $i = 1; $i <= 7; $i++)
593             {
594             # my $const = "I18N::Langinfo::ABDAY_${i}";
595 28         129 my $const = I18N::Langinfo->can( "ABDAY_${i}" );
596             # $short->[$i-1] = langinfo( &$const );
597 28         133 $short->[$i-1] = langinfo( $const->() );
598             }
599 4         13 for (my $i = 1; $i <= 7; $i++)
600             {
601             # my $const = "I18N::Langinfo::DAY_${i}";
602 28         121 my $const = I18N::Langinfo->can( "DAY_${i}" );
603             # $long->[$i-1] = langinfo( &$const );
604 28         123 $long->[$i-1] = langinfo( $const->() );
605             }
606              
607 4 50       52 POSIX::setlocale( &POSIX::LC_ALL, $oldlocale) if( defined( $locale ) );
608              
609 4         13 return( [ $short, $long ] );
610             }
611              
612             sub _get_months
613             {
614 2     2   814 my $self = shift( @_ );
615 2         6 my $locale = shift( @_ );
616 2         11 my $oldlocale = POSIX::setlocale( &POSIX::LC_ALL );
617 2         24 my $short = $self->new_array;
618 2         42 my $long = $self->new_array;
619              
620 2 50       43 POSIX::setlocale( &POSIX::LC_ALL, $locale ) if( defined( $locale ) );
621              
622 2         28 for( my $i = 1; $i <= 12; $i++ )
623             {
624             # my $const = "I18N::Langinfo::ABMON_${i}";
625             # $short->[$i-1] = langinfo( &$const );
626 24         150 my $const = I18N::Langinfo->can( "ABMON_${i}" );
627 24         107 $short->[$i-1] = langinfo( $const->() );
628             }
629 2         13 for( my $i = 1; $i <= 12; $i++ )
630             {
631             # my $const = "I18N::Langinfo::MON_${i}";
632             # $long->[$i-1] = langinfo( &$const );
633 24         121 my $const = I18N::Langinfo->can( "MON_${i}" );
634 24         104 $long->[$i-1] = langinfo( $const->() );
635             }
636              
637 2 50       30 POSIX::setlocale( &POSIX::LC_ALL, $oldlocale) if( defined( $locale ) );
638              
639 2         18 return( [ $short, $long ] );
640             }
641              
642             sub _get_numeric_dict
643             {
644 2     2   805 my $self = shift( @_ );
645 2         4 my $locale = shift( @_ );
646 2         12 my $oldlocale = POSIX::setlocale( &POSIX::LC_ALL );
647 2 50       20 POSIX::setlocale( &POSIX::LC_ALL, $locale) if( defined( $locale ) );
648 2         35 my $lconv = POSIX::localeconv();
649 2 50       26 POSIX::setlocale( &POSIX::LC_ALL, $oldlocale) if( defined( $locale ) );
650 2         14 my $def = $self->new_hash;
651             @$def{qw( currency decimal int_currency negative_sign thousand precision )} =
652 2         1019 @$lconv{qw( currency_symbol decimal_point int_curr_symbol negative_sign thousands_sep frac_digits )};
653 2     2   26 use utf8;
  2         5  
  2         20  
654 2 50 33     202 $def->{currency} = '€' if( CORE::exists( $def->{currency} ) && defined( $def->{currency} ) && $def->{currency} eq 'EUR' );
      33        
655 2 0 33     98 $lconv->{currency_symbol} = '€' if( CORE::exists( $lconv->{currency_symbol} ) && defined( $lconv->{currency_symbol} ) && $lconv->{currency_symbol} eq 'EUR' );
      33        
656 2 50 33     6 $lconv->{grouping} = unpack( "C*", $lconv->{grouping} ) if( CORE::exists( $lconv->{grouping} ) && defined( $lconv->{grouping} ) );
657 2 50 33     8 $lconv->{mon_grouping} = unpack( "C*", $lconv->{mon_grouping} ) if( CORE::exists( $lconv->{mon_grouping} ) && defined( $lconv->{mon_grouping} ) );
658 2         7 $lconv = $self->new_hash( $lconv );
659 2         1023 return( [ $def, $lconv ] );
660             }
661              
662             sub _get_po
663             {
664 15     15   47 my $self = shift( @_ );
665 15         55 my $hash = $self->getDomainHash({ locale => $self->locale });
666 15         177 return( $hash->{_po} );
667             }
668              
669             # NOTE: Text::PO::String class
670             {
671             package
672             Text::PO::String;
673             BEGIN
674 0         0 {
675 2     2   484 use strict;
  2         4  
  2         45  
676 2     2   10 use warnings;
  2         3  
  2         70  
677 2     2   12 use parent qw( Module::Generic );
  2         3  
  2         12  
678 2     2   158 use vars qw( $VERSION );
  2         3  
  2         141  
679 2     2   184 our $VERSION = 'v0.1.0';
680             use overload (
681             '""' => 'as_string',
682 0     0   0 'bool' => sub{1},
683 2         26 fallback => 1,
684 2     2   13 );
  2         4  
685             };
686            
687 2     2   33 use strict;
  2         4  
  2         40  
688 2     2   9 use warnings;
  2         3  
  2         351  
689            
690             sub init
691             {
692 4     4   364 my $self = shift( @_ );
693 4         9 my $value = shift( @_ );
694 4         14 my $locale = shift( @_ );
695 4         61 $self->{locale} = $locale;
696 4         22 $self->{value} = $value;
697 4         32 $self->SUPER::init( @_ );
698 4         254 return( $self );
699             }
700            
701 4     4   1640 sub as_string { return( shift->value->scalar ); }
702            
703 0     0   0 sub locale { return( shift->_set_get_scalar_as_object( 'locale', @_ ) ); }
704              
705 4     4   33 sub value { return( shift->_set_get_scalar_as_object( 'value', @_ ) ); }
706            
707 0     0     sub TO_JSON { return( shift->as_string ); }
708             }
709              
710             1;
711             # NOTE: POD
712             __END__
713              
714             =encoding utf-8
715              
716             =head1 NAME
717              
718             Text::PO::Gettext - A GNU Gettext implementation
719              
720             =head1 SYNOPSIS
721              
722             use Text::PO::Gettext;
723             my $po = Text::PO::Gettext->new || die( Text::PO::Gettext->error, "\n" );
724             my $po = Text::PO::Gettext->new({
725             category => 'LC_MESSAGES',
726             debug => 3,
727             domain => "com.example.api",
728             locale => 'ja-JP',
729             path => "/home/joe/locale",
730             use_json => 1,
731             }) || die( Text::PO::Gettext->error, "\n" );
732              
733             =head1 VERSION
734              
735             v0.3.0
736              
737             =head1 DESCRIPTION
738              
739             This module is used to access the data in either C<po>, C<mo> or C<json> file and provides various methods to access those data.
740              
741             The conventional way to use GNU gettext is to set the global environment variable C<LANGUAGE> (not C<LANG> by the way. GNU gettext only uses C<LANGUAGE>), then set the L<POSIX/setlocale> to the language such as:
742              
743             use Locale::gettext;
744             use POSIX ();
745             POSIX::setlocale( &POSIX::LC_ALL, 'ja_JP' );
746             my $d = Locale::gettext->domain( 'com.example.api' );
747              
748             And then in your application, you would write a statement like:
749              
750             print $d->get( 'Hello!' );
751              
752             Or possibly using direct access to the C function:
753              
754             use Locale::gettext;
755             use POSIX ();
756             POSIX::setlocale( &POSIX::LC_ALL, 'ja_JP' );
757             textdomain( 'com.example.api' );
758              
759             And then:
760              
761             print gettext( 'Hello!' );
762              
763             See L<Locale::gettext> for more on this.
764              
765             This works fine, but has the inconvenience that it uses the global C<LANGUAGE> environment variable and makes it less than subpar as to the necessary flexibility when using multiple domains and flipping back and forth among locales.
766              
767             Thus comes a more straightforward object-oriented interface offered by this module.
768              
769             You instantiate an object, passing the domain, the locale and the filesystem path where the locale data resides.
770              
771             my $po = Text::PO::Gettext->new(
772             domain => 'com.example.api',
773             locale => 'ja_JP',
774             path => '/some/where/locale'
775             );
776             print $po->gettext( 'Hello!' );
777              
778             This will load into memory the locale data whether they are stored as C<.po>, C<.mo> or even C<.json> file, thus making calls to L</gettext> super fast since they are in memory.
779              
780             More than one locale can be loaded, each with its own L<Text::PO::Gettext> object
781              
782             This distribution comes with its Javascript library equivalent. See the C<share> folder alone with its own test units.
783              
784             Also, there is a script in C<scripts> that can be used to transcode C<.po> or C<.mo> files into json format and vice versa.
785              
786             Still, it is better to convert the original C<.po> files to json using the C<po.pl> utility that comes in this L<Text::PO> distribution since it would allow the standalone JavaScript library to read json-based po files. For example:
787              
788             ./po.pl --as-json --output /home/joe/www/locale/ja_JP/LC_MESSAGES/com.example.api.json ./ja_JP.po
789              
790             This api supports locale that use hyphens or underscore in them such as C<en-GB> or C<en_GB>. You can use either, it will be converted internally.
791              
792             =head1 CONSTRUCTOR
793              
794             =head2 new
795              
796             Takes the following options and returns a Gettext object.
797              
798             =over 4
799              
800             =item * C<category>
801              
802             If I<category> is defined, such as C<LC_MESSAGES> (by default), it will be used when building the I<path>.
803              
804             Other possible category values are: C<LC_CTYPE>, C<LC_NUMERIC>, C<LC_TIME>, C<LC_COLLATE>, C<LC_MONETARY>
805              
806             See L<GNU documentation for more information|https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html> and L<perllocale/"LOCALE CATEGORIES">
807              
808             On the web, using the path is questionable.
809              
810             See the L<GNU documentation|https://www.gnu.org/software/libc/manual/html_node/Using-gettextized-soft
811             ware.html> for more information on this.
812              
813             =item * C<domain>
814              
815             The portable object domain, such as C<com.example.api>
816              
817             =item * C<locale>
818              
819             The locale, such as C<ja_JP>, or C<en>, or it could even contain a dash instead of an underscore, such as C<en-GB>. Internally, though, this will be converted to underscore.
820              
821             =item * C<path>
822              
823             The uri path where the gettext localised data are.
824              
825             This is used to form a path along with the locale string. For example, with a locale of C<ja_JP> and a domain of C<com/example.api>, if the path were C</locale>, the data po json data would be fetched from C</locale/
826             ja_JP/LC_MESSAGES/com.example.api.json>
827              
828             =back
829              
830             =head1 METHODS
831              
832             =head2 addItem
833              
834             This takes a C<locale>, a message id and its localised version and it will add this to the current dictionary for the current domain.
835              
836             $po->addItem( 'ja_JP', 'Hello!' => "今日は!" );
837              
838             =head2 category
839              
840             The category to use. This defaults to C<LC_MESSAGES>, but if you prefer you can nix its use by making it undefined, or empty:
841              
842             my $po = Text::PO::Gettext->new(
843             category => '',
844             domain => 'com.example.api',
845             locale => 'ja_JP',
846             path => '/some/where/locale'
847             );
848             # Setting category to empty string will have the module get the po data
849             # under C</some/where/locale/ja_JP/com.example.api.json> for example.
850             print $po->gettext( 'Hello!' );
851              
852             =head2 charset
853              
854             Returns a string containing the value of the charset encoding as defined in the C<Content-Type> header.
855              
856             $po->charset()
857              
858             =head2 contentEncoding
859              
860             Returns a string containing the value of the header C<Content-Encoding>.
861              
862             $po->contentEncoding();
863              
864             =head2 contentType
865              
866             Returns a string containing the value of the header C<Content-Type>.
867              
868             $po->contentType(); # text/plain; charset=utf-8
869              
870             =head2 currentLang
871              
872             Return the current globally used locale. This is the value found in environment variables C<LANGUAGE> or C<LANG>. Note that GNU gettext only recognises C<LANGUAGE>
873              
874             and thus, this is different from the C<locale> set in the Gettext class object using </setLocale> or upon class object instantiation.
875              
876             =head2 dgettext
877              
878             Takes a domain and a message id and returns the equivalent localised string if any, otherwise the original message id.
879              
880             $po->dgettext( 'com.example.auth', 'Please enter your e-mail address' );
881             # Assuming the locale currently set is ja_JP, this would return:
882             # 電子メールアドレスをご入力下さい。
883              
884             =head2 dngettext
885              
886             Same as L</ngettext>, but takes also a domain as first argument. For example:
887              
888             $po->ngettext( 'com.example.auth', '%d comment awaiting moderation', '%d comments awaiting moderation', 12 );
889             # Assuming the locale is ru_RU, this would return:
890             # %d комментариев ожидают проверки
891              
892             Note that as of version C<v0.5.0>, this returns a C<Text::PO::String>, which is lightweight and stringifies automatically. It provides the benefit of tagging the string with the locale attached to it.
893              
894             Thus, in the example above, the resulting C<Text::PO::String> would have its method C<locale> value set to C<ru_RU>, and you could do:
895              
896             my $localised = $po->ngettext( 'com.example.auth', '%d comment awaiting moderation', '%d comments awaiting moderation', 12 );
897             say "Locale for this string is: ", $localised->locale;
898              
899             If no locale string was found, C<locale> would be undefined.
900              
901             =head2 domain
902              
903             Sets or gets the domain.
904              
905             $po->domain( 'com.example.api' );
906              
907             By doing so, this will call L</textdomain> and load the associated data from file, if any are found.
908              
909             =head2 exists
910              
911             Provided with a locale, and this returns true if the locale exists in the current domain, or false otherwise.
912              
913             =head2 fetchLocale
914              
915             Given an original string (msgid), this returns an array of <span> html element each for one language and its related localised content. For example:
916              
917             my $array = $po->fetchLocale( "Hello!" );
918             # Returns:
919             <span lang="de-DE">Grüß Gott!</span>
920             <span lang="fr-FR">Salut !</span>
921             <span lang="ja-JP">今日は!</span>
922             <span lang="ko-KR">안녕하세요!</span>
923              
924             This is designed to be added to the html, and based on C<lang> attribute of the C<html> tag, and using the following css trick, this will automatically display the right localised data:
925              
926             [lang=de-DE] [lang=en-GB],
927             [lang=de-DE] [lang=fr-FR],
928             [lang=de-DE] [lang=ja-JP],
929             [lang=de-DE] [lang=ko-KR],
930             [lang=en-GB] [lang=de-DE],
931             [lang=en-GB] [lang=fr-FR],
932             [lang=en-GB] [lang=ja-JP],
933             [lang=en-GB] [lang=ko-KR],
934             [lang=fr-FR] [lang=de-DE],
935             [lang=fr-FR] [lang=en-GB],
936             [lang=fr-FR] [lang=ja-JP],
937             [lang=fr-FR] [lang=ko-KR],
938             [lang=ja-JP] [lang=de-DE],
939             [lang=ja-JP] [lang=en-GB]
940             [lang=ja-JP] [lang=fr-FR],
941             [lang=ja-JP] [lang=ko-KR]
942             {
943             display: none !important;
944             visibility: hidden !important;
945             }
946              
947             =head2 getDataPath
948              
949             This takes no argument and will check for the environment variables C<TEXTDOMAINDIR>. If found, it will use this in lieu of the I<path> option used during object instantiation.
950              
951             It returns the value found. This is just a helper method and does not affect the value of the I<path> property set during object instantiation.
952              
953             =head2 getDaysLong
954              
955             Returns an array reference containing the 7 days of the week in their long representation.
956              
957             my $ref = $po->getDaysLong();
958             # Assuming the locale is fr_FR, this would yield
959             print $ref->[0], "\n"; # dim.
960              
961             =head2 getDaysShort
962              
963             Returns an array reference containing the 7 days of the week in their short representation.
964              
965             my $ref = $po->getDaysShort();
966             # Assuming the locale is fr_FR, this would yield
967             print $ref->[0], "\n"; # dimanche
968              
969             =head2 getDomainHash
970              
971             This takes an optional hash of parameters and return the global hash dictionary used by this class to store the localised data.
972              
973             # Will use the default domain as set in po.domain
974             my $data = $po->getDomainHash();
975             # Explicitly specify another domain
976             my $data = $po->getDomainHash( domain => "net.example.api" );
977             # Specify a domain and a locale
978             my $l10n = $po->getDomainHash( domain => "com.example.api", locale => "ja_JP" );
979              
980             Possible options are:
981              
982             =over 4
983              
984             =item * C<domain> The domain for the data, such as C<com.example.api>
985              
986             =item * C<locale> The locale to return the associated dictionary.
987              
988             =back
989              
990             =head2 getLangDataPath
991              
992             Contrary to its JavaScript equivalent, this takes no parameter. It returns the value of the environment variable C<TEXTLOCALEDIR> if found.
993              
994             This is used internally during object instantiation when the I<path> parameter is not provided.
995              
996             =head2 getLanguageDict
997              
998             Provided with a locale, such as C<ja_JP> and this will return the dictionary for the current domain and the given locale.
999              
1000             =head2 getLocale
1001              
1002             Returns the locale set for the current object, such as C<fr_FR> or C<ja_JP>
1003              
1004             Locale returned are always formatted for the server-side, which means having an underscore rather than an hyphen like in the web environment.
1005              
1006             =head2 getLocales
1007              
1008             Provided with a C<msgid> (i.e. an original text) and this will call L</fetchLocale> and return those C<span> tags as a string containing their respective localised content, joined by a new line
1009              
1010             =head2 getLocalesf
1011              
1012             This is similar to L</getLocale>, except that it does a sprintf internally before returning the resulting value.
1013              
1014             =head2 getMetaKeys
1015              
1016             Returns an array of the meta field names used.
1017              
1018             =head2 getMetaValue
1019              
1020             Provided with a meta field name and this returns its corresponding value.
1021              
1022             =head2 getMonthsLong
1023              
1024             Returns an array reference containing the 12 months in their long representation.
1025              
1026             my $ref = $po->getMonthsLong();
1027             # Assuming the locale is fr_FR, this would yield
1028             print $ref->[0], "\n"; # janvier
1029              
1030             =head2 getMonthsShort
1031              
1032             Returns an array reference containing the 12 months in their short representation.
1033              
1034             my $ref = $po->getMonthsShort();
1035             # Assuming the locale is fr_FR, this would yield
1036             print $ref->[0], "\n"; # janv.
1037              
1038             =head2 getNumericDict
1039              
1040             Returns an hash reference containing the following properties:
1041              
1042             my $ref = $po->getNumericDict();
1043              
1044             =over 4
1045              
1046             =item * C<currency> string
1047              
1048             Contains the usual currency symbol, such as C<€>, or C<$>, or C<¥>
1049              
1050             =item * C<decimal> string
1051              
1052             Contains the character used to separate decimal. In English speaking countries, this would typically be a dot.
1053              
1054             =item * C<int_currency> string
1055              
1056             Contains the 3-letters international currency symbol, such as C<USD>, or C<EUR> or C<JPY>
1057              
1058             =item * C<negative_sign> string
1059              
1060             Contains the negative sign used for negative number
1061              
1062             =item * C<precision> integer
1063              
1064             An integer whose value represents the fractional precision allowed for monetary context.
1065              
1066             For example, in Japanese, this value would be 0 while in many other countries, it would be 2.
1067              
1068             =item * C<thousand> string
1069              
1070             Contains the character used to group and separate thousands.
1071              
1072             For example, in France, it would be a space, such as :
1073              
1074             1 000 000,00
1075              
1076             While in English countries, including Japan, it would be a comma :
1077              
1078             1,000,000.00
1079              
1080             =back
1081              
1082             =head2 getNumericPosixDict
1083              
1084             Returns the full hash reference returned by L<POSIX/lconv>. It contains the following properties:
1085              
1086             Here the values shown as example are for the locale C<en_US>
1087              
1088             =over 4
1089              
1090             =item * C<currency_symbol> string
1091              
1092             The local currency symbol: C<$>
1093              
1094             =item * C<decimal_point> string
1095              
1096             The decimal point character, except for currency values, cannot be an empty string: C<.>
1097              
1098             =item * C<frac_digits> integer
1099              
1100             The number of digits after the decimal point in the local style for currency value: 2
1101              
1102             =item * C<grouping>
1103              
1104             The sizes of the groups of digits, except for currency values. unpack( "C*", $grouping ) will give the number
1105              
1106             =item * C<int_curr_symbol> string
1107              
1108             The standardized international currency symbol: C<USD>
1109              
1110             =item * C<int_frac_digits> integer
1111              
1112             The number of digits after the decimal point in an international-style currency value: 2
1113              
1114             =item * C<int_n_cs_precedes> integer
1115              
1116             Same as n_cs_precedes, but for internationally formatted monetary quantities: 1
1117              
1118             =item * C<int_n_sep_by_space> integer
1119              
1120             Same as n_sep_by_space, but for internationally formatted monetary quantities: 1
1121              
1122             =item * C<int_n_sign_posn> integer
1123              
1124             Same as n_sign_posn, but for internationally formatted monetary quantities: 1
1125              
1126             =item * C<int_p_cs_precedes> integer
1127              
1128             Same as p_cs_precedes, but for internationally formatted monetary quantities: 1
1129              
1130             =item * C<int_p_sep_by_space> integer
1131              
1132             Same as p_sep_by_space, but for internationally formatted monetary quantities: 1
1133              
1134             =item * C<int_p_sign_posn> integer
1135              
1136             Same as p_sign_posn, but for internationally formatted monetary quantities: 1
1137              
1138             =item * C<mon_decimal_point> string
1139              
1140             The decimal point character for currency values: C<.>
1141              
1142             =item * C<mon_grouping>
1143              
1144             Like grouping but for currency values.
1145              
1146             =item * C<mon_thousands_sep> string
1147              
1148             The separator for digit groups in currency values: C<,>
1149              
1150             =item * C<n_cs_precedes> integer
1151              
1152             Like p_cs_precedes but for negative values: 1
1153              
1154             =item * C<n_sep_by_space> integer
1155              
1156             Like p_sep_by_space but for negative values: 0
1157              
1158             =item * C<n_sign_posn> integer
1159              
1160             Like p_sign_posn but for negative currency values: 1
1161              
1162             =item * C<negative_sign> string
1163              
1164             The character used to denote negative currency values, usually a minus sign: C<->
1165              
1166             =item * C<p_cs_precedes> integer
1167              
1168             1 if the currency symbol precedes the currency value for nonnegative values, 0 if it follows: 1
1169              
1170             =item * C<p_sep_by_space> integer
1171              
1172             1 if a space is inserted between the currency symbol and the currency value for nonnegative values, 0 otherwise: 0
1173              
1174             =item * C<p_sign_posn> integer
1175              
1176             The location of the positive_sign with respect to a nonnegative quantity and the currency_symbol, coded as follows:
1177              
1178             0 Parentheses around the entire string.
1179             1 Before the string.
1180             2 After the string.
1181             3 Just before currency_symbol.
1182             4 Just after currency_symbol.
1183              
1184             =item * C<positive_sign> string
1185              
1186             The character used to denote nonnegative currency values, usually the empty string
1187              
1188             =item * C<thousands_sep> string
1189              
1190             The separator between groups of digits before the decimal point, except for currency values: C<,>
1191              
1192             =back
1193              
1194             =head2 getPlural
1195              
1196             Calls L<Text::PO/plural> and returns an array object (L<Module::Generic::Array>) with 2 elements.
1197              
1198             See L<Text::PO/plural> for more details.
1199              
1200             =head2 getText
1201              
1202             Provided with an original string, and this will return its localised equivalent if it exists, or by default, it will return the original string.
1203              
1204             =head2 getTextf
1205              
1206             Provided with an original string, and this will get its localised equivalent that wil be used as a template for the sprintf function. The resulting formatted localised content will be returned.
1207              
1208             =head2 gettext
1209              
1210             Provided with a C<msgid> represented by a string, and this return a localised version of the string, if any is found and is translated, otherwise returns the C<msgid> that was provided.
1211              
1212             $po->gettext( "Hello" );
1213             # With locale of fr_FR, this would return "Bonjour"
1214              
1215             See the global function L</_> for more information.
1216              
1217             Note that as of version C<v0.5.0>, this returns a C<Text::PO::String>, which is lightweight and stringifies automatically. It provides the benefit of tagging the string with the locale attached to it.
1218              
1219             Thus, in the example above, the resulting C<Text::PO::String> would have its method C<locale> value set to C<fr_FR>, and you could do:
1220              
1221             my $localised = $po->gettext( "Hello" );
1222             say "Locale for this string is: ", $localised->locale;
1223              
1224             If no locale string was found, C<locale> would be undefined.
1225              
1226             =head2 gettextf
1227              
1228             This is an alias to L</getTextf>
1229              
1230             =head2 isSupportedLanguage
1231              
1232             Provided with a locale such as C<fr-FR> or C<ja_JP> no matter whether an underscore or a dash is used, and this will return true if the locale has already been loaded and thus is supported. False otherwise.
1233              
1234             =head2 language
1235              
1236             Returns a string containing the value of the header C<Language>.
1237              
1238             $po->language();
1239              
1240             =head2 languageTeam
1241              
1242             Returns a string containing the value of the header C<Language-Team>.
1243              
1244             $po->languageTeam();
1245              
1246             =head2 lastTranslator
1247              
1248             Returns a string containing the value of the header C<Last-Translator>.
1249              
1250             $po->lastTranslator();
1251              
1252             =head2 locale
1253              
1254             Returns the locale set in the object. if sets, this will trigger the (re)load of po data by calling L</textdomain>
1255              
1256             =head2 locale_unix
1257              
1258             Provided with a locale, such as C<en-GB> and this will return its equivalent formatted for server-side such as C<en_GB>
1259              
1260             =head2 locale_web
1261              
1262             Provided with a locale, such as C<en_GB> and this will return its equivalent formatted for the web such as C<en-GB>
1263              
1264             =head2 mimeVersion
1265              
1266             Returns a string containing the value of the header C<MIME-Version>.
1267              
1268             $po->mimeVersion();
1269              
1270             =head2 ngettext
1271              
1272             Takes an original string (a.k.a message id), the plural version of that string, and an integer representing the applicable count. For example:
1273              
1274             $po->ngettext( '%d comment awaiting moderation', '%d comments awaiting moderation', 12 );
1275             # Assuming the locale is ru_RU, this would return:
1276             # %d комментариев ожидают проверки
1277              
1278             =head2 path
1279              
1280             Sets or gets the filesystem path to the base directory containing the locale data:
1281              
1282             $po->path( '/locale' ); # /locale contains en_GB/LC_MESSAGES/com.example.api.mo for example
1283              
1284             =head2 plural
1285              
1286             Sets or gets the definition for plural for the current domain and locale.
1287              
1288             It takes and returns an array reference of 2 elements:
1289              
1290             =over 4
1291              
1292             =item 0. An integer representing the various plural forms available, starting from 1
1293              
1294             =item 1. An expression to be evaluated resulting in an offset for the right plural form. For example:
1295              
1296             n>1
1297              
1298             or more complex for Russian:
1299              
1300             (n==1) ? 0 : (n%10==1 && n%100!=11) ? 3 : ((n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20)) ? 1 : 2)
1301              
1302             =back
1303              
1304             =head2 pluralForms
1305              
1306             Returns a string containing the value of the header C<Plural-Forms>.
1307              
1308             $po->pluralForms();
1309              
1310             =head2 po_object
1311              
1312             Returns the L<Text::PO> object used.
1313              
1314             =head2 poRevisionDate
1315              
1316             Returns a string containing the value of the header C<PO-Revision-Date>.
1317              
1318             $po->poRevisionDate();
1319              
1320             =head2 potCreationDate
1321              
1322             Returns a string containing the value of the header C<POT-Creation-Date>.
1323              
1324             $po->potCreationDate();
1325              
1326             =head2 projectIdVersion
1327              
1328             Returns a string containing the value of the header C<Project-Id-Version>.
1329              
1330             $po->projectIdVersion();
1331              
1332             =head2 reportBugsTo
1333              
1334             Returns a string containing the value of the header C<Report-Msgid-Bugs-To>.
1335              
1336             $po->reportBugsTo();
1337              
1338             =head2 textdomain
1339              
1340             Given a string representing a domain, such as C<com.example.api> and this will load the C<.json> (if the L</use_json> option is enabled), C<.po> or C<.mo> file found in that order.
1341              
1342             =head2 use_json
1343              
1344             Takes a boolean and if set, L<Text::PO::Gettext> will use a json po data if it exists, otherwise it will use a C<.po> file or a C<.mo> file in that order of preference.
1345              
1346             =head2 _get_po
1347              
1348             Returns the L<Text::PO> object used.
1349              
1350             =head1 AUTHOR
1351              
1352             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
1353              
1354             =head1 SEE ALSO
1355              
1356             L<perl>
1357              
1358             =head1 COPYRIGHT & LICENSE
1359              
1360             Copyright(c) 2021 DEGUEST Pte. Ltd. DEGUEST Pte. Ltd.
1361              
1362             =cut