File Coverage

lib/Text/PO/MO.pm
Criterion Covered Total %
statement 275 450 61.1
branch 61 316 19.3
condition 23 109 21.1
subroutine 32 45 71.1
pod 14 23 60.8
total 405 943 42.9


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## PO Files Manipulation - ~/lib/Text/PO/MO.pm
3             ## Version v0.2.0
4             ## Copyright(c) 2021 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2021/06/25
7             ## Modified 2022/07/06
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::MO;
14             BEGIN
15             {
16 2     2   19155889 use strict;
  2         10  
  2         55  
17 2     2   9 use warnings;
  2         3  
  2         56  
18 2     2   9 use warnings::register;
  2         4  
  2         178  
19 2     2   10 use parent qw( Module::Generic );
  2         2  
  2         9  
20 2     2   107 use vars qw( $VERSION @META $DEF_META );
  2         3  
  2         103  
21 2     2   10 use Encode ();
  2         2  
  2         31  
22 2     2   27 use IO::File;
  2         2  
  2         246  
23 2     2   12 use Nice::Try;
  2         7  
  2         17  
24 2     2   1593320 use Text::PO;
  2         6  
  2         52  
25 2     2   793 our $VERSION = 'v0.2.0';
26             };
27              
28 2     2   11 use strict;
  2         3  
  2         48  
29 2     2   9 use warnings;
  2         4  
  2         2640  
30              
31             our @META = @Text::PO::META;
32             our $DEF_META = $Text::PO::DEF_META;
33              
34             sub init
35             {
36 3     3 1 40760 my $self = shift( @_ );
37 3         15 my $file;
38 3         12 $file = shift( @_ );
39 3         114 $self->{auto_decode} = 1;
40 3         14 $self->{default_encoding} = 'utf-8';
41 3         13 $self->{domain} = '';
42 3         12 $self->{encoding} = '';
43 3         12 $self->{file} = $file;
44 3         16 $self->{use_cache} = 1;
45 3         14 $self->{_init_strict_use_sub} = 1;
46 3         21 $self->SUPER::init( @_ );
47 3         307 $self->{revision} = 0;
48 3         18 $self->{magic} = '0x950412de';
49 3         17 $self->{_last_modified} = '';
50 3         11 return( $self );
51             }
52              
53             sub as_object
54             {
55 1     1 1 531 my $self = shift( @_ );
56 1         4 my( $ref, $order ) = $self->read;
57 1 50       3 return( $self->pass_error ) if( !defined( $ref ) );
58             # Get the raw meta element
59 1         2 my $raw = $ref->{ '' };
60 1         12 my $arr = [split( "\\n", $raw )];
61 1         5 my $po = Text::PO->new( debug => $self->debug, encoding => $self->encoding, domain => $self->domain );
62 1         5 my $meta = {};
63 1         2 my $meta_keys = [];
64 1         2 foreach my $s ( @$arr )
65             {
66 11 100       28 if( $s =~ /^([^\x00-\x1f\x80-\xff :=]+):[[:blank:]]*(.*?)$/ )
67             {
68 1         6 my( $k, $v ) = ( lc( $1 ), $2 );
69 1         2 $meta->{ $k } = $v;
70 1         3 push( @$meta_keys, $k );
71             }
72             }
73 1         5 my $rv = $po->meta( $meta );
74 1         2758075 $po->meta_keys( $meta_keys );
75 1         42 my $e = $po->new_element({
76             is_meta => 1,
77             msgid => '',
78             msgstr => $arr,
79             });
80 1         3 push( @{$po->{elements}}, $e );
  1         4  
81 1         3 foreach my $k ( @$order )
82             {
83 9 100       15 next if( !length( $k ) );
84             my $e = $po->new_element({
85             msgid => $k,
86 8         35 msgstr => $ref->{ $k },
87             });
88 8         18 push( @{$po->{elements}}, $e );
  8         22  
89             }
90 1         6 return( $po );
91             }
92              
93 1     1 1 9 sub auto_decode { return( shift->_set_get_boolean( 'auto_decode', @_ ) ); }
94              
95             sub decode
96             {
97 1     1 1 2 my $self = shift( @_ );
98 1         2 my $hash = shift( @_ );
99 1   33     4 my $enc = shift( @_ ) || $self->encoding;
100 1 50       94 return( $self->error( "Data provided is not an hash reference." ) ) if( ref( $hash ) ne 'HASH' );
101 1 50       4 return( $self->error( "No character encoding was provided to decode the mo file data." ) ) if( !CORE::length( $enc ) );
102 1 50 33     4 try
  1         1  
  1         2  
  1         15  
  0         0  
  1         2  
  1         4  
  1         1  
103 1     1   1 {
104 1         9 foreach my $k ( sort( keys( %$hash ) ) )
105             {
106 9         11 my $v = $hash->{ $k };
107 9         26 my $k2 = Encode::decode( $enc, $k, Encode::FB_CROAK );
108 9         386 my $v2 = Encode::decode( $enc, $v, Encode::FB_CROAK );
109 9 50       216 CORE::delete( $hash->{ $k } ) if( CORE::length( $k ) );
110 9         22 $hash->{ $k2 } = $v2;
111             }
112             }
113 1 50 50     7 catch( $e )
  1 0 33     8  
  0 0       0  
  1 0       2  
  1 0       2  
  1 0       1  
  1 0       2  
  1 0       4  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  1         4  
  0         0  
  0         0  
  1         3  
  1         3  
  1         4  
  1         4  
  1         2  
  1         5  
  0         0  
  0         0  
  0         0  
  0         0  
114 0     0   0 {
115 0         0 return( $self->error( "An error occurred while trying to decode mo data using character encoding \"$enc\": $e" ) );
116 2 0 0 2   13 }
  2 0 0     3  
  2 0 0     2099  
  0 0 33     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  1 0       8  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  1         3  
  0         0  
  0         0  
  0         0  
  0         0  
  1         3  
117 1         2 return( $hash );
118             }
119              
120 0     0 1 0 sub default_encoding { return( shift->_set_get_scalar( 'default_encoding', @_ ) ); }
121              
122 3     3 1 422 sub domain { return( shift->_set_get_scalar( 'domain', @_ ) ); }
123              
124             sub encode
125             {
126 9     9 1 16 my $self = shift( @_ );
127 9         15 my $hash = shift( @_ );
128 9   33     28 my $enc = shift( @_ ) || $self->encoding;
129 9 50       24 return( $self->error( "Data provided is not an hash reference." ) ) if( ref( $hash ) ne 'HASH' );
130 9 50       23 return( $self->error( "No character encoding was provided to encode data." ) ) if( !CORE::length( $enc ) );
131 9 50 33     32 try
  9         13  
  9         17  
  9         42  
  0         0  
  9         74  
  9         23  
  9         20  
132 9     9   11 {
133 9         29 foreach my $k ( keys( %$hash ) )
134             {
135 36         37 my $v = $hash->{ $k };
136 36 100       68 if( $self->_is_array( $hash->{ $k } ) )
    50          
137             {
138 1         20 for( my $i = 0; $i < scalar( @{$hash->{ $k }} ); $i++ )
  1         11  
139             {
140 0 0       0 $hash->{ $k }->[$i] = Encode::encode( $enc, $hash->{ $k }->[$i], Encode::FB_CROAK ) if( Encode::is_utf8( $hash->{ $k }->[$i] ) );
141             }
142             }
143             elsif( !ref( $hash->{ $k } ) )
144             {
145 35 100       288 my $v2 = Encode::is_utf8( $v ) ? Encode::encode( $enc, $v, Encode::FB_CROAK ) : $v;
146 35         337 $hash->{ $k } = $v2;
147             }
148             }
149             }
150 9 0 50     69 catch( $e )
  9 0 33     25  
  9 0       25  
  9 0       13  
  9 0       10  
  9 0       12  
  9 0       15  
  9 0       29  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  9         31  
  0         0  
  9         16  
  0         0  
  0         0  
  9         19  
  9         23  
  9         17  
  9         20  
  0         0  
  0         0  
  0         0  
  0         0  
151 0     0   0 {
152 0         0 return( $self->error( "An error occurred while trying to encode data using character encoding \"$enc\": $e" ) );
153 2 0 0 2   13 }
  2 0 0     5  
  2 0 33     3458  
  0 0 33     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  9 0       165  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  9         27  
  0         0  
  0         0  
  0         0  
  0         0  
  9         23  
154 9         24 return( $hash );
155             }
156              
157 7     7 1 183 sub encoding { return( shift->_set_get_scalar( 'encoding', @_ ) ); }
158              
159 2     2 1 15 sub file { return( shift->_set_get_file( 'file', @_ ) ); }
160              
161             sub read
162             {
163 1     1 1 2 my $self = shift( @_ );
164 1         2 my $file = $self->file;
165 1         115 my $opts = $self->_get_args_as_hash( @_ );
166             # Caching mechanism
167 1 0 33     10 if( !$self->{use_cache} &&
      33        
      33        
      0        
      0        
168             !$opts->{no_cache} &&
169             -e( $file ) &&
170             ref( $self->{_cache} ) eq 'ARRAY' &&
171             $self->{_last_modified} &&
172             [CORE::stat( $file )]->[9] > $self->{_last_modified} )
173             {
174 0 0       0 return( wantarray() ? @{$self->{_cache}} : $self->{_cache}->[0] );
  0         0  
175             }
176 1 50       20 return( $self->error( "mo file \"$file\" does not exist." ) ) if( !-e( $file ) );
177 1 50       14 return( $self->error( "mo file \"$file\" is not readable." ) ) if( !-r( $file ) );
178 1   50     11 my $io = IO::File->new( "<$file" ) || return( $self->error( "Unable to open mo file \"$file\": $!" ) );
179 1         97 $io->binmode;
180 1         9 my $data;
181 1         18 $io->read( $data, -s( $file ) );
182 1         41 $io->close;
183 1         17 my $byte_order = substr( $data, 0, 4 );
184 1         2 my $tmpl;
185             # Little endian
186 1 50       4 if( $byte_order eq "\xde\x12\x04\x95" )
    50          
187             {
188 0         0 $tmpl = "V";
189             }
190             # Big endian
191             elsif( $byte_order eq "\x95\x04\x12\xde" )
192             {
193 1         3 $tmpl = "N";
194             }
195             else
196             {
197 0         0 return( $self->error( "Provided file \"$file\" is not a valid mo file." ) );
198             }
199             # Check the MO format revision number
200 1         6 my $rev_num = unpack( $tmpl, substr( $data, 4, 4 ) );
201             # There is only one revision now: revision 0.
202 1 50       4 return if( $rev_num > 0 );
203 1         3 $self->{revision} = $rev_num;
204              
205             # Total messages
206 1         2 my $total = unpack( $tmpl, substr( $data, 8, 4 ) );
207             # Offset to the beginning of the original messages
208 1         2 my $off_msgid = unpack( $tmpl, substr( $data, 12, 4 ) );
209             # Offset to the beginning of the translated messages
210 1         2 my $off_msgstr = unpack( $tmpl, substr( $data, 16, 4 ) );
211 1         2 my $hash = {};
212 1         2 my $order = [];
213 1         3 for( my $i = 0; $i < $total; $i++ )
214             {
215 9         10 my( $len, $off, $msgid, $msgstr );
216             # The first word is the length of the message
217 9         13 $len = unpack( $tmpl, substr( $data, $off_msgid + $i * 8, 4 ) );
218             # The second word is the offset of the message
219 9         11 $off = unpack( $tmpl, substr( $data, $off_msgid + $i * 8 + 4, 4 ) );
220             # Original message
221 9         14 $msgid = substr( $data, $off, $len );
222            
223             # The first word is the length of the message
224 9         10 $len = unpack( $tmpl, substr( $data, $off_msgstr + $i * 8, 4 ) );
225             # The second word is the offset of the message
226 9         10 $off = unpack( $tmpl, substr( $data, $off_msgstr + $i * 8 + 4, 4 ) );
227             # Translated message
228 9         10 $msgstr = substr( $data, $off, $len );
229            
230 9         16 $hash->{ $msgid } = $msgstr;
231 9         15 push( @$order, $msgid );
232             }
233            
234 1 50 33     4 if( $self->auto_decode || $opts->{auto_decode} )
235             {
236 1 50       218 unless( my $enc = $self->encoding )
237             {
238             # Find the encoding of that MO file
239 1 50       108 if( $hash->{ '' } =~ /Content-Type:[[:blank:]\h]*text\/plain;[[:blank:]\h]*charset[[:blank:]\h]*=[[:blank:]\h]*(?<quote>["'])?(?<encoding>[\w\-]+)\g{quote}?/is )
240             {
241 1         11 $enc = $+{encoding};
242 1         3 $self->encoding( $enc );
243             }
244             # Default to US-ASCII
245             else
246             {
247 0   0     0 $enc = $self->default_encoding || $opts->{default_encoding};
248             }
249 1         102 $self->encoding( $enc );
250             }
251 1         98 $self->decode( $hash );
252             }
253 1         19 $self->{_last_modified} = [CORE::stat( $file )]->[9];
254 1         5 $self->{_cache} = [ $hash, $order ];
255 1 50       8 return( wantarray() ? ( $hash, $order ) : $hash );
256             }
257              
258             sub reset
259             {
260 0     0 1 0 my $self = shift( @_ );
261 0         0 $self->{_cache} = [];
262 0         0 $self->{_last_modified} = '';
263 0         0 return( $self );
264             }
265              
266 0     0 1 0 sub revision { return( shift->_set_get_scalar( 'revision', @_ ) ); }
267              
268 0     0 1 0 sub use_cache { return( shift->_set_get_boolean( 'use_cache', @_ ) ); }
269              
270             sub write
271             {
272 1     1 1 37 my $self = shift( @_ );
273 1         13 my $po = shift( @_ );
274 1         15 my $opts = $self->_get_args_as_hash( @_ );
275 1 50 33     32 return( $self->error( "I was expecting a Text::PO object, and instead got '$po'." ) ) if( !$self->_is_object( $po ) || !$po->isa( 'Text::PO' ) );
276 1         37 my $ref = {};
277 1         11 my $keys = [];
278 1   50     20 $opts->{encoding} //= '';
279 1   50     61 my $enc = $opts->{encoding} || $self->encoding || $self->default_encoding || 'utf-8';
280             my $add = sub
281             {
282 9     9   140 my $this = shift( @_ );
283             $self->encode( $this => $enc ) || do
284 9 50       29 {
285 0 0       0 warnings::warn( "An error occurred trying to encode value for key '${this}': ", $self->error, "\n" ) if( warnings::enabled() );
286             };
287 9         15 my $msgstr;
288 9 50       18 if( $this->{msgid_plural} )
289             {
290 0         0 my $res = [];
291 0         0 my $multi = $this->{msgstr};
292 0         0 for( my $i = 0; $i < scalar( @$multi ); $i++ )
293             {
294 0         0 push( @$res, join( null(), @{$multi->[$i]} ) );
  0         0  
295             }
296 0         0 $msgstr = join( null(), @$res );
297             }
298             else
299             {
300 9 100       26 $msgstr = $self->_is_array( $this->{msgstr} ) ? join( null(), @{$this->{msgstr}} ) : $this->{msgstr};
  1         6  
301             }
302 9 100       66 return if( !length( $msgstr ) );
303 8         15 my $ctx = '';
304 8         11 my $plural = '';
305 8 50       18 if( $this->{context} )
306             {
307 0         0 $ctx = $this->{context} . eot();
308             }
309 8 50       16 if( $this->{msgid_plural} )
310             {
311 0         0 $plural = null() . $this->{msgid_plural};
312             }
313 8         108 $ref->{ $ctx . $this->{msgid} . $plural } = $msgstr;
314 8         69 push( @$keys, $ctx . $this->{msgid} . $plural );
315 1         160 };
316              
317 1         21 my $elems = $po->elements;
318 1         188 my $metaKeys = [@Text::PO::META];
319 1         6 my $metas = [];
320 1         18 my $meta = $po->meta;
321 1 50       148 if( scalar( @$metaKeys ) )
322             {
323 1         12 foreach my $k ( @$metaKeys )
324             {
325 11         157 my $k2 = lc( $k );
326 11         12 $k2 =~ tr/-/_/;
327 11 50       42 next if( !CORE::exists( $meta->{ $k2 } ) );
328 0         0 my $v2 = $po->meta( $k );
329 0         0 push( @$metas, sprintf( "\"%s: %s\\n\"\n", $po->normalise_meta( $k ), $v2 ) );
330             }
331 1         38 $add->({
332             context => '',
333             msgid => '',
334             msgid_plural => '',
335             msgstr => $metas,
336             });
337             }
338            
339 1         8 foreach my $e ( @$elems )
340             {
341 9 100       189 next if( $e->is_meta );
342 8         1016 $add->({
343             context => $e->context,
344             msgid => $e->msgid,
345             msgid_plural => $e->msgid_plural,
346             msgstr => $e->msgstr,
347             });
348             }
349            
350 1         8 my $cnt = scalar( keys( %$ref ) );
351 1         7 my $mem = 28 + ( $cnt * 16 );
352 1         13 my $l10n = [map( $ref->{ $_ }, @$keys )];
353              
354 1         8 my $fh;
355             my $file = ( CORE::exists( $opts->{file} ) && length( $opts->{file} ) )
356             ? $opts->{file}
357 1 50 33     17 : $self->file;
358 1 50       143 if( $file eq '-' )
359             {
360 0         0 $fh = IO::File->new;
361 0         0 $fh->fdopen( fileno( STDOUT ), 'w' );
362             }
363             else
364             {
365 1 50       14 my $mode = length( $opts->{mode} ) ? $opts->{mode} : '>';
366 1   50     25 $fh = IO::File->new( $file, $mode ) ||
367             return( $self->error( "Unable to open file \"$file\" in write mode: $!" ) );
368             }
369 1         295 $fh->binmode;
370 1         29 $fh->autoflush(1);
371 1         115 $fh->print( from_hex( $self->{magic} ) );
372 1         52 $fh->print( character( $self->{revision} ) );
373 1         24 $fh->print( character( $cnt ) );
374 1         16 $fh->print( character(28) );
375 1         86 $fh->print( character( 28 + ( $cnt * 8 ) ) );
376 1         16 $fh->print( character(0) );
377 1         15 $fh->print( character(0) );
378 1         18 foreach my $k ( @$keys )
379             {
380 8         14 my $len = length( $k );
381 8         13 $fh->print( character( $len ) );
382 8         116 $fh->print( character( $mem ) );
383 8         108 $mem += $len + 1;
384             }
385 1         6 foreach my $v ( @$l10n )
386             {
387 8         13 my $len = length( $v );
388 8         26 $fh->print( character( $len ) );
389 8         115 $fh->print( character( $mem ) );
390 8         108 $mem += $len + 1;
391             }
392 1         10 foreach my $k ( @$keys )
393             {
394 8         106 $fh->print( null_terminate( $k ) );
395             }
396 1         15 foreach my $v ( @$l10n )
397             {
398 8         100 $fh->print( null_terminate( $v ) );
399             }
400              
401 1 0 33     34 $fh->close unless( CORE::exists( $opts->{file} ) && defined( $opts->{file} ) && $opts->{file} eq '-' );
      33        
402 1         102 return( $self );
403             }
404              
405             # NOTE: helper functions
406             # Credits to Ryan Niebur
407             sub character
408             {
409 39     39 0 55 return( map{ pack( "N*", $_ ) } @_ );
  39         210  
410             }
411              
412             sub eot
413             {
414 0     0 0 0 return( chr(4) );
415             }
416              
417             sub from_character
418             {
419 0     0 0 0 return( character( _from_character( @_ ) ) );
420             }
421              
422             sub from_hex
423             {
424 1     1 0 9 return( character( _from_hex( @_ ) ) );
425             }
426              
427             sub from_string
428             {
429 0     0 0 0 return( join_string( from_character( _from_string( @_ ) ) ) );
430             }
431              
432             sub join_string
433             {
434 0     0 0 0 return( join( '', @_ ) );
435             }
436              
437             sub null
438             {
439 1     1 0 31 return( null_terminate( '' ) );
440             }
441              
442             sub null_terminate
443             {
444 17     17 0 81 return( pack( "Z*", shift( @_ ) ) );
445             }
446              
447             sub number_to_s
448             {
449 0     0 0 0 return( sprintf( "%d", shift( @_ ) ) );
450             }
451              
452             sub _from_character
453             {
454 0     0   0 return( map( ord( $_ ), @_ ) );
455             }
456              
457             sub _from_hex
458             {
459 1     1   12 return( map( hex( $_ ), @_ ) );
460             }
461              
462             sub _from_string
463             {
464 0     0     return( split( //, join( '', @_ ) ) );
465             }
466              
467             1;
468             # NOTE: POD
469             __END__
470              
471             =head1 NAME
472              
473             Text::PO::MO - Machine Object File Read, Write
474              
475             =head1 SYNOPSIS
476              
477             use Text::PO::MO;
478             my $mo = Text::PO::MO->new( '/home/joe/locale/com.example.mo',
479             {
480             auto_decode => 1,
481             encoding => 'utf-8',
482             default_encoding => 'utf-8',
483             });
484             my $mo = Text::PO::MO->new(
485             file => '/home/joe/locale/com.example.mo',
486             auto_decode => 1,
487             encoding => 'utf-8',
488             default_encoding => 'utf-8',
489             );
490             my $hash = $mo->read;
491              
492             =head1 VERSION
493              
494             v0.2.0
495              
496             =head1 DESCRIPTION
497              
498             This is the class for read from and writing to GNU C<.mo> (machine object) files.
499              
500             =head2 CONSTRUCTOR
501              
502             =head2 new
503              
504             Create a new Text::PO::MO object.
505              
506             It accepts the following options:
507              
508             =over 4
509              
510             =item I<auto_decode>
511              
512             Takes a boolean value and enables or disables auto decoding of data.
513              
514             =item I<default_encoding>
515              
516             Sets the default encoding. This is used when I<auto_decode> is enabled.
517              
518             =item I<encoding>
519              
520             Sets the value of the encoding to use when I<auto_decode> is enabled.
521              
522             =item I<file>
523              
524             Sets or gets the C<.mo> file to read.
525              
526             =item I<use_cache>
527              
528             Takes a boolean value. If true, this will cache the data read by L</read>
529              
530             =back
531              
532             =head1 METHODS
533              
534             =head2 as_object
535              
536             Returns the data read from the machine object file as a L<Text::PO> object.
537              
538             =head2 auto_decode
539              
540             Takes a boolean value and enables or disables auto decode of data read from C<.mo> file.
541              
542             This is used in L</read>
543              
544             =head2 decode
545              
546             Provided with an hash reference of key-value pairs and a string representing an encoding and this will decode all its keys and values.
547              
548             It returns the hash reference, although being a reference, this is not necessary.
549              
550             =head2 default_encoding
551              
552             Sets the default encoding to revert to if no encoding is set with L</encoding> and L</auto_decode> is enabled.
553              
554             Otherwise, L</read> will attempt to find out the encoding used by looking at the meta information C<Content-type>
555              
556             =head2 domain
557              
558             Sets or gets the po file domain, such as C<com.example.api>
559              
560             =head2 encoding
561              
562             Sets or gets the encoding to use for decoding the data read from the C<.mo> file.
563              
564             =head2 file
565              
566             Sets or gets the gnu C<.mo> file to be read from or written to.
567              
568             =head2 read
569              
570             Provided with a file path to a gnu C<.mo> file and this returns an hash reference of key-value pairs corresponding to the msgid to msgstr or original text to localised text.
571              
572             Note that there is one blank key corresponding to the meta informations.
573              
574             It takes the following optional parameters:
575              
576             =over 4
577              
578             =item I<auto_decode>
579              
580             Boolean value. If true, the data will be automatically decoded using either the character encoding specified with L</encoding> or the one found in the C<Content-type> field in the file meta information.
581              
582             =item I<default_encoding>
583              
584             The default encoding to use if no encoding was set using L</encoding> and none could be found in the C<.mo> file meta information.
585              
586             =item I<no_cache>
587              
588             Boolean value. If true, this will ignore any cached data and re-read the C<.mo> file.
589              
590             =back
591              
592             If caching is enabled with L</use_cache>, then L</read> will return the cache instead of actually reading the C<.mo> unless the last modification time has changed and increased.
593              
594             =head2 reset
595              
596             Resets the cached data. This will have the effect of reading the C<.mo> file next time L</read> is called.
597              
598             Returns the current object.
599              
600             =head2 revision
601              
602             Sets or gets the revision number. This should not be changed, or you might break things.
603              
604             It defaults to 0
605              
606             =head2 use_cache
607              
608             Takes a boolean value.
609              
610             If true, this will enable caching based on the C<.mo> file last modification timestamp.
611              
612             Default to true.
613              
614             =head2 write
615              
616             Provided with a L<Text::PO> object and this will write the C<.mo> file.
617              
618             It takes an hash reference of parameters:
619              
620             =over 4
621              
622             =item I<file>
623              
624             The output file to write the data to.
625              
626             This should be a file path, or C<-> if you want to write to STDOUT.
627              
628             =back
629              
630             =head1 AUTHOR
631              
632             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
633              
634             =head1 SEE ALSO
635              
636             L<Text::PO>, L<Text::PO::Element>
637              
638             L<https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html>
639              
640             L<http://www.gnu.org/software/gettext/manual/html_node/MO-Files.html#MO-Files>
641              
642             =head1 COPYRIGHT & LICENSE
643              
644             Copyright (c) 2020-2021 DEGUEST Pte. Ltd.
645              
646             You can use, copy, modify and redistribute this package and associated files under the same terms as Perl itself.
647              
648             =cut