Bug 10214 Add header to syspref po files
[koha.git] / misc / translator / LangInstaller.pm
1 package LangInstaller;
2
3 # Copyright (C) 2010 Tamil s.a.r.l.
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 use Modern::Perl;
21
22 use C4::Context;
23 # WARNING: Any other tested YAML library fails to work properly in this
24 # script content
25 use YAML::Syck qw( Dump LoadFile );
26 use Locale::PO;
27 use FindBin qw( $Bin );
28
29 $YAML::Syck::ImplicitTyping = 1;
30
31
32 # Default file header for .po syspref files
33 my $default_pref_po_header = Locale::PO->new(-msgid => '', -msgstr =>
34     "Project-Id-Version: PACKAGE VERSION\\n" .
35     "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\\n" .
36     "Last-Translator: FULL NAME <EMAIL\@ADDRESS>\\n" .
37     "Language-Team: Koha Translate List <koha-translate\@lists.koha-community.org>\\n" .
38     "MIME-Version: 1.0\\n" .
39     "Content-Type: text/plain; charset=UTF-8\\n" .
40     "Content-Transfer-Encoding: 8bit\\n" .
41     "Plural-Forms: nplurals=2; plural=(n > 1);\\n"
42 );
43
44
45 sub set_lang {
46     my ($self, $lang) = @_;
47
48     $self->{lang} = $lang;
49     $self->{po_path_lang} = $self->{context}->config('intrahtdocs') .
50                             "/prog/$lang/modules/admin/preferences";
51 }
52
53
54 sub new {
55     my ($class, $lang, $pref_only, $verbose) = @_;
56
57     my $self                 = { };
58
59     my $context              = C4::Context->new();
60     $self->{context}         = $context;
61     $self->{path_pref_en}    = $context->config('intrahtdocs') .
62                                '/prog/en/modules/admin/preferences';
63     set_lang( $self, $lang ) if $lang;
64     $self->{pref_only}       = $pref_only;
65     $self->{verbose}         = $verbose;
66     $self->{process}         = "$Bin/tmpl_process3.pl " . ($verbose ? '' : '-q');
67     $self->{path_po}         = "$Bin/po";
68     $self->{po}              = { '' => $default_pref_po_header };
69
70     # Get all .pref file names
71     opendir my $fh, $self->{path_pref_en};
72     my @pref_files = grep { /.pref/ } readdir($fh);
73     close $fh;
74     $self->{pref_files} = \@pref_files;
75
76     # Get all available language codes
77     opendir $fh, $self->{path_po};
78     my @langs =  map { ($_) =~ /(.*)-i-opac/ } 
79         grep { $_ =~ /.*-opac-t-prog/ } readdir($fh);
80     closedir $fh;
81     $self->{langs} = \@langs;
82
83     # Map for both interfaces opac/intranet
84     my $opachtdocs = $context->config('opachtdocs');
85     $self->{interface} = [
86         {
87             name   => 'OPAC prog',
88             dir    => "$opachtdocs/prog",
89             suffix => '-i-opac-t-prog-v-3006000.po',
90         },
91         {
92             name   => 'Intranet prog',
93             dir    => $context->config('intrahtdocs') . '/prog',
94             suffix => '-i-staff-t-prog-v-3006000.po',
95         },
96     ];
97
98     # Alternate opac themes
99     opendir $fh, $context->config('opachtdocs');
100     for ( grep { not /^\.|\.\.|prog|lib$/ } readdir($fh) ) {
101         push @{$self->{interface}}, {
102             name   => "OPAC $_",
103             dir    => "$opachtdocs/$_",
104             suffix => "-opac-$_.po",
105         };
106     }
107
108     bless $self, $class;
109 }
110
111
112 sub po_filename {
113     my $self = shift;
114
115     my $context    = C4::Context->new;
116     my $trans_path = $Bin . '/po';
117     my $trans_file = "$trans_path/" . $self->{lang} . "-pref.po";
118     return $trans_file;
119 }
120
121
122 sub po_append {
123     my ($self, $id, $comment) = @_;
124     my $po = $self->{po};
125     my $p = $po->{$id};
126     if ( $p ) {
127         $p->comment( $p->comment . "\n" . $comment );
128     }
129     else {
130         $po->{$id} = Locale::PO->new(
131             -comment => $comment,
132             -msgid   => $id,
133             -msgstr  => ''
134         );
135     }
136 }
137
138
139 sub add_prefs {
140     my ($self, $comment, $prefs) = @_;
141
142     for my $pref ( @$prefs ) {
143         my $pref_name = '';
144         for my $element ( @$pref ) {
145             if ( ref( $element) eq 'HASH' ) {
146                 $pref_name = $element->{pref};
147                 last;
148             }
149         }
150         for my $element ( @$pref ) {
151             if ( ref( $element) eq 'HASH' ) {
152                 while ( my ($key, $value) = each(%$element) ) {
153                     next unless $key eq 'choices';
154                     next unless ref($value) eq 'HASH';
155                     for my $ckey ( keys %$value ) {
156                         my $id = $self->{file} . "#$pref_name# " . $value->{$ckey};
157                         $self->po_append( $id, $comment );
158                     }
159                 }
160             }
161             elsif ( $element && $pref_name ) {
162                 $self->po_append( $self->{file} . "#$pref_name# $element", $comment );
163             }
164         }
165     }
166 }
167
168
169 sub get_trans_text {
170     my ($self, $id) = @_;
171
172     my $po = $self->{po}->{$id};
173     return unless $po;
174     return Locale::PO->dequote($po->msgstr);
175 }
176
177
178 sub update_tab_prefs {
179     my ($self, $pref, $prefs) = @_;
180
181     for my $p ( @$prefs ) {
182         my $pref_name = '';
183         next unless $p;
184         for my $element ( @$p ) {
185             if ( ref( $element) eq 'HASH' ) {
186                 $pref_name = $element->{pref};
187                 last;
188             }
189         }
190         for my $i ( 0..@$p-1 ) {
191             my $element = $p->[$i];
192             if ( ref( $element) eq 'HASH' ) {
193                 while ( my ($key, $value) = each(%$element) ) {
194                     next unless $key eq 'choices';
195                     next unless ref($value) eq 'HASH';
196                     for my $ckey ( keys %$value ) {
197                         my $id = $self->{file} . "#$pref_name# " . $value->{$ckey};
198                         my $text = $self->get_trans_text( $id );
199                         $value->{$ckey} = $text if $text;
200                     }
201                 }
202             }
203             elsif ( $element && $pref_name ) {
204                 my $id = $self->{file} . "#$pref_name# $element";
205                 my $text = $self->get_trans_text( $id );
206                 $p->[$i] = $text if $text;
207             }
208         }
209     }
210 }
211
212
213 sub get_po_from_prefs {
214     my $self = shift;
215
216     for my $file ( @{$self->{pref_files}} ) {
217         my $pref = LoadFile( $self->{path_pref_en} . "/$file" );
218         $self->{file} = $file;
219         # Entries for tab titles
220         $self->po_append( $self->{file}, $_ ) for keys %$pref;
221         while ( my ($tab, $tab_content) = each %$pref ) {
222             if ( ref($tab_content) eq 'ARRAY' ) {
223                 $self->add_prefs( $tab, $tab_content );
224                 next;
225             }
226             while ( my ($section, $sysprefs) = each %$tab_content ) {
227                 my $comment = "$tab > $section";
228                 $self->po_append( $self->{file} . " " . $section, $comment );
229                 $self->add_prefs( $comment, $sysprefs );
230             }
231         }
232     }
233 }
234
235
236 sub save_po {
237     my $self = shift;
238
239     # Create file header if it doesn't already exist
240     my $po = $self->{po};
241     $po->{''} ||= $default_pref_po_header;
242
243     # Write .po entries into a file put in Koha standard po directory
244     Locale::PO->save_file_fromhash( $self->po_filename, $po );
245     say "Saved in file: ", $self->po_filename if $self->{verbose};
246 }
247
248
249 sub get_po_merged_with_en {
250     my $self = shift;
251
252     # Get po from current 'en' .pref files
253     $self->get_po_from_prefs();
254     my $po_current = $self->{po};
255
256     # Get po from previous generation
257     my $po_previous = Locale::PO->load_file_ashash( $self->po_filename );
258
259     for my $id ( keys %$po_current ) {
260         my $po =  $po_previous->{Locale::PO->quote($id)};
261         next unless $po;
262         my $text = Locale::PO->dequote( $po->msgstr );
263         $po_current->{$id}->msgstr( $text );
264     }
265 }
266
267
268 sub update_prefs {
269     my $self = shift;
270     print "Update '", $self->{lang},
271           "' preferences .po file from 'en' .pref files\n" if $self->{verbose};
272     $self->get_po_merged_with_en();
273     $self->save_po();
274 }
275
276
277 sub install_prefs {
278     my $self = shift;
279
280     unless ( -r $self->{po_path_lang} ) {
281         print "Koha directories hierarchy for ", $self->{lang}, " must be created first\n";
282         exit;
283     }
284
285     # Get the language .po file merged with last modified 'en' preferences
286     $self->get_po_merged_with_en();
287
288     for my $file ( @{$self->{pref_files}} ) {
289         my $pref = LoadFile( $self->{path_pref_en} . "/$file" );
290         $self->{file} = $file;
291         # First, keys are replaced (tab titles)
292         $pref = do {
293             my %pref = map { 
294                 $self->get_trans_text( $self->{file} ) || $_ => $pref->{$_}
295             } keys %$pref;
296             \%pref;
297         };
298         while ( my ($tab, $tab_content) = each %$pref ) {
299             if ( ref($tab_content) eq 'ARRAY' ) {
300                 $self->update_tab_prefs( $pref, $tab_content );
301                 next;
302             }
303             while ( my ($section, $sysprefs) = each %$tab_content ) {
304                 $self->update_tab_prefs( $pref, $sysprefs );
305             }
306             my $ntab = {};
307             for my $section ( keys %$tab_content ) {
308                 my $id = $self->{file} . " $section";
309                 my $text = $self->get_trans_text($id);
310                 my $nsection = $text ? $text : $section;
311                 $ntab->{$nsection} = $tab_content->{$section};
312             }
313             $pref->{$tab} = $ntab;
314         }
315         my $file_trans = $self->{po_path_lang} . "/$file";
316         print "Write $file\n" if $self->{verbose};
317         open my $fh, ">", $file_trans;
318         print $fh Dump($pref);
319     }
320 }
321
322
323 sub install_tmpl {
324     my $self = shift;
325     say "Install templates" if $self->{verbose};
326     for my $trans ( @{$self->{interface}} ) {
327         print
328             "  Install templates '$trans->{name}'\n",
329             "    From: $trans->{dir}/en/\n",
330             "    To  : $trans->{dir}/$self->{lang}\n",
331             "    With: $self->{path_po}/$self->{lang}$trans->{suffix}\n"
332                 if $self->{verbose};
333         my $lang_dir = "$trans->{dir}/$self->{lang}";
334         mkdir $lang_dir unless -d $lang_dir;
335         system
336             "$self->{process} install " .
337             "-i $trans->{dir}/en/ " .
338             "-o $trans->{dir}/$self->{lang} ".
339             "-s $self->{path_po}/$self->{lang}$trans->{suffix} -r"
340     }
341 }
342
343
344 sub update_tmpl {
345     my $self = shift;
346
347     say "Update templates" if $self->{verbose};
348     for my $trans ( @{$self->{interface}} ) {
349         print
350             "  Update templates '$trans->{name}'\n",
351             "    From: $trans->{dir}/en/\n",
352             "    To  : $self->{path_po}/$self->{lang}$trans->{suffix}\n"
353                 if $self->{verbose};
354         my $lang_dir = "$trans->{dir}/$self->{lang}";
355         mkdir $lang_dir unless -d $lang_dir;
356         system
357             "$self->{process} update " .
358             "-i $trans->{dir}/en/ " .
359             "-s $self->{path_po}/$self->{lang}$trans->{suffix} -r"
360     }
361 }
362
363
364 sub create_prefs {
365     my $self = shift;
366
367     if ( -e $self->po_filename ) {
368         say "Preferences .po file already exists. Delete it if you want to recreate it.";
369         return;
370     }
371     $self->get_po_from_prefs();
372     $self->save_po();
373 }
374
375
376 sub create_tmpl {
377     my $self = shift;
378
379     say "Create templates\n" if $self->{verbose};
380     for my $trans ( @{$self->{interface}} ) {
381         print
382             "  Create templates .po files for '$trans->{name}'\n",
383             "    From: $trans->{dir}/en/\n",
384             "    To  : $self->{path_po}/$self->{lang}$trans->{suffix}\n"
385                 if $self->{verbose};
386         system
387             "$self->{process} create " .
388             "-i $trans->{dir}/en/ " .
389             "-s $self->{path_po}/$self->{lang}$trans->{suffix} -r"
390     }
391 }
392
393
394 sub install {
395     my $self = shift;
396     return unless $self->{lang};
397     $self->install_tmpl() unless $self->{pref_only};
398     $self->install_prefs();
399 }
400
401
402 sub get_all_langs {
403     my $self = shift;
404     opendir( my $dh, $self->{path_po} );
405     my @files = grep { $_ =~ /-i-opac-t-prog-v-3006000.po$/ }
406         readdir $dh;
407     @files = map { $_ =~ s/-i-opac-t-prog-v-3006000.po$//; $_ } @files;
408 }
409
410
411 sub update {
412     my $self = shift;
413     my @langs = $self->{lang} ? ($self->{lang}) : $self->get_all_langs();
414     for my $lang ( @langs ) {
415         $self->set_lang( $lang );
416         $self->update_tmpl() unless $self->{pref_only};
417         $self->update_prefs();
418     }
419 }
420
421
422 sub create {
423     my $self = shift;
424     return unless $self->{lang};
425     $self->create_tmpl() unless $self->{pref_only};
426     $self->create_prefs();
427 }
428
429
430
431 1;
432
433
434 =head1 NAME
435
436 LangInstaller.pm - Handle templates and preferences translation
437
438 =head1 SYNOPSYS
439
440   my $installer = LangInstaller->new( 'fr-FR' );
441   $installer->create();
442   $installer->update();
443   $installer->install();
444   for my $lang ( @{$installer->{langs} ) {
445     $installer->set_lang( $lan );
446     $installer->install();
447   }
448
449 =head1 METHODS
450
451 =head2 new
452
453 Create a new instance of the installer object. 
454
455 =head2 create
456
457 For the current language, create .po files for templates and preferences based
458 of the english ('en') version.
459
460 =head2 update
461
462 For the current language, update .po files.
463
464 =head2 install
465
466 For the current langage C<$self->{lang}, use .po files to translate the english
467 version of templates and preferences files and copy those files in the
468 appropriate directory.
469
470 =over
471
472 =item translate create F<lang>
473
474 Create 3 .po files in F<po> subdirectory: (1) from opac pages templates, (2)
475 intranet templates, and (3) from preferences.
476
477 =over
478
479 =item F<lang>-opac.po
480
481 Contains extracted text from english (en) OPAC templates found in
482 <KOHA_ROOT>/koha-tmpl/opac-tmpl/prog/en/ directory.
483
484 =item F<lang>-intranet.po
485
486 Contains extracted text from english (en) intranet templates found in
487 <KOHA_ROOT>/koha-tmpl/intranet-tmpl/prog/en/ directory.
488
489 =item F<lang>-pref.po
490
491 Contains extracted text from english (en) preferences. They are found in files
492 located in <KOHA_ROOT>/koha-tmpl/intranet-tmpl/prog/en/admin/preferences
493 directory.
494
495 =back
496
497 =item pref-trans update F<lang>
498
499 Update .po files in F<po> directory, named F<lang>-*.po.
500
501 =item pref-trans install F<lang>
502
503 =back
504
505 =cut
506