Bug 9161: Followup: the -f param does not work correctly
[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, $files) = @_;
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                 @$files
342                     ? '-f ' . join ' -f ', @$files
343                     : ''
344             )
345     }
346 }
347
348
349 sub update_tmpl {
350     my ($self, $files) = @_;
351
352     say "Update templates" if $self->{verbose};
353     for my $trans ( @{$self->{interface}} ) {
354         print
355             "  Update templates '$trans->{name}'\n",
356             "    From: $trans->{dir}/en/\n",
357             "    To  : $self->{path_po}/$self->{lang}$trans->{suffix}\n"
358                 if $self->{verbose};
359         my $lang_dir = "$trans->{dir}/$self->{lang}";
360         mkdir $lang_dir unless -d $lang_dir;
361         system
362             "$self->{process} update " .
363             "-i $trans->{dir}/en/ " .
364             "-s $self->{path_po}/$self->{lang}$trans->{suffix} -r " .
365             (
366                 @$files
367                     ? '-f ' . join ' -f ', @$files
368                     : ''
369             )
370     }
371 }
372
373
374 sub create_prefs {
375     my $self = shift;
376
377     if ( -e $self->po_filename ) {
378         say "Preferences .po file already exists. Delete it if you want to recreate it.";
379         return;
380     }
381     $self->get_po_from_prefs();
382     $self->save_po();
383 }
384
385
386 sub create_tmpl {
387     my ($self, $files) = @_;
388
389     say "Create templates\n" if $self->{verbose};
390     for my $trans ( @{$self->{interface}} ) {
391         print
392             "  Create templates .po files for '$trans->{name}'\n",
393             "    From: $trans->{dir}/en/\n",
394             "    To  : $self->{path_po}/$self->{lang}$trans->{suffix}\n"
395                 if $self->{verbose};
396         system
397             "$self->{process} create " .
398             "-i $trans->{dir}/en/ " .
399             "-s $self->{path_po}/$self->{lang}$trans->{suffix} -r " .
400             (
401                 @$files
402                     ? '-f ' . join ' -f ', @$files
403                     : ''
404             )
405     }
406 }
407
408
409 sub install {
410     my ($self, $files) = @_;
411     return unless $self->{lang};
412     $self->install_tmpl($files) unless $self->{pref_only};
413     $self->install_prefs();
414 }
415
416
417 sub get_all_langs {
418     my $self = shift;
419     opendir( my $dh, $self->{path_po} );
420     my @files = grep { $_ =~ /-i-opac-t-prog-v-3006000.po$/ }
421         readdir $dh;
422     @files = map { $_ =~ s/-i-opac-t-prog-v-3006000.po$//; $_ } @files;
423 }
424
425
426 sub update {
427     my ($self, $files) = @_;
428     my @langs = $self->{lang} ? ($self->{lang}) : $self->get_all_langs();
429     for my $lang ( @langs ) {
430         $self->set_lang( $lang );
431         $self->update_tmpl($files) unless $self->{pref_only};
432         $self->update_prefs();
433     }
434 }
435
436
437 sub create {
438     my ($self, $files) = @_;
439     return unless $self->{lang};
440     $self->create_tmpl($files) unless $self->{pref_only};
441     $self->create_prefs();
442 }
443
444
445
446 1;
447
448
449 =head1 NAME
450
451 LangInstaller.pm - Handle templates and preferences translation
452
453 =head1 SYNOPSYS
454
455   my $installer = LangInstaller->new( 'fr-FR' );
456   $installer->create();
457   $installer->update();
458   $installer->install();
459   for my $lang ( @{$installer->{langs} ) {
460     $installer->set_lang( $lan );
461     $installer->install();
462   }
463
464 =head1 METHODS
465
466 =head2 new
467
468 Create a new instance of the installer object. 
469
470 =head2 create
471
472 For the current language, create .po files for templates and preferences based
473 of the english ('en') version.
474
475 =head2 update
476
477 For the current language, update .po files.
478
479 =head2 install
480
481 For the current langage C<$self->{lang}, use .po files to translate the english
482 version of templates and preferences files and copy those files in the
483 appropriate directory.
484
485 =over
486
487 =item translate create F<lang>
488
489 Create 3 .po files in F<po> subdirectory: (1) from opac pages templates, (2)
490 intranet templates, and (3) from preferences.
491
492 =over
493
494 =item F<lang>-opac.po
495
496 Contains extracted text from english (en) OPAC templates found in
497 <KOHA_ROOT>/koha-tmpl/opac-tmpl/prog/en/ directory.
498
499 =item F<lang>-intranet.po
500
501 Contains extracted text from english (en) intranet templates found in
502 <KOHA_ROOT>/koha-tmpl/intranet-tmpl/prog/en/ directory.
503
504 =item F<lang>-pref.po
505
506 Contains extracted text from english (en) preferences. They are found in files
507 located in <KOHA_ROOT>/koha-tmpl/intranet-tmpl/prog/en/admin/preferences
508 directory.
509
510 =back
511
512 =item pref-trans update F<lang>
513
514 Update .po files in F<po> directory, named F<lang>-*.po.
515
516 =item pref-trans install F<lang>
517
518 =back
519
520 =cut
521