3 # Copyright (C) 2010 Tamil s.a.r.l.
5 # This file is part of Koha.
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
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.
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.
23 # WARNING: Any other tested YAML library fails to work properly in this
25 use YAML
::Syck
qw( Dump LoadFile );
27 use FindBin
qw( $Bin );
29 $YAML::Syck::ImplicitTyping = 1;
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"
46 my ($self, $lang) = @_;
48 $self->{lang} = $lang;
49 $self->{po_path_lang} = $self->{context}->config('intrahtdocs') .
50 "/prog/$lang/modules/admin/preferences";
55 my ($class, $lang, $pref_only, $verbose) = @_;
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 $self->{domain} = 'messages';
70 $self->{cp} = `which cp`;
71 $self->{msgmerge} = `which msgmerge`;
72 $self->{xgettext} = `which xgettext`;
73 $self->{sed} = `which sed`;
75 chomp $self->{msgmerge};
76 chomp $self->{xgettext};
79 # Get all .pref file names
80 opendir my $fh, $self->{path_pref_en};
81 my @pref_files = grep { /.pref/ } readdir($fh);
83 $self->{pref_files} = \@pref_files;
85 # Get all available language codes
86 opendir $fh, $self->{path_po};
87 my @langs = map { ($_) =~ /(.*)-i-opac/ }
88 grep { $_ =~ /.*-opac-t-prog/ } readdir($fh);
90 $self->{langs} = \@langs;
92 # Map for both interfaces opac/intranet
93 my $opachtdocs = $context->config('opachtdocs');
94 $self->{interface} = [
97 dir => "$opachtdocs/prog",
98 suffix => '-i-opac-t-prog-v-3006000.po',
101 name => 'Intranet prog UI',
102 dir => $context->config('intrahtdocs') . '/prog',
103 suffix => '-i-staff-t-prog-v-3006000.po',
106 name => 'Intranet prog help',
107 dir => $context->config('intrahtdocs') . '/prog/en/modules/help',
108 suffix => '-staff-help.po',
112 # Alternate opac themes
113 opendir $fh, $context->config('opachtdocs');
114 for ( grep { not /^\.|\.\.|prog|lib$/ } readdir($fh) ) {
115 push @{$self->{interface}}, {
117 dir => "$opachtdocs/$_",
118 suffix => "-opac-$_.po",
129 my $context = C4::Context->new;
130 my $trans_path = $Bin . '/po';
131 my $trans_file = "$trans_path/" . $self->{lang} . "-pref.po";
137 my ($self, $id, $comment) = @_;
138 my $po = $self->{po};
141 $p->comment( $p->comment . "\n" . $comment );
144 $po->{$id} = Locale::PO->new(
145 -comment => $comment,
154 my ($self, $comment, $prefs) = @_;
156 for my $pref ( @$prefs ) {
158 for my $element ( @$pref ) {
159 if ( ref( $element) eq 'HASH' ) {
160 $pref_name = $element->{pref};
164 for my $element ( @$pref ) {
165 if ( ref( $element) eq 'HASH' ) {
166 while ( my ($key, $value) = each(%$element) ) {
167 next unless $key eq 'choices';
168 next unless ref($value) eq 'HASH';
169 for my $ckey ( keys %$value ) {
170 my $id = $self->{file} . "#$pref_name# " . $value->{$ckey};
171 $self->po_append( $id, $comment );
175 elsif ( $element && $pref_name ) {
176 $self->po_append( $self->{file} . "#$pref_name# $element", $comment );
184 my ($self, $id) = @_;
186 my $po = $self->{po}->{$id};
188 return Locale::PO->dequote($po->msgstr);
192 sub update_tab_prefs {
193 my ($self, $pref, $prefs) = @_;
195 for my $p ( @$prefs ) {
198 for my $element ( @$p ) {
199 if ( ref( $element) eq 'HASH' ) {
200 $pref_name = $element->{pref};
204 for my $i ( 0..@$p-1 ) {
205 my $element = $p->[$i];
206 if ( ref( $element) eq 'HASH' ) {
207 while ( my ($key, $value) = each(%$element) ) {
208 next unless $key eq 'choices';
209 next unless ref($value) eq 'HASH';
210 for my $ckey ( keys %$value ) {
211 my $id = $self->{file} . "#$pref_name# " . $value->{$ckey};
212 my $text = $self->get_trans_text( $id );
213 $value->{$ckey} = $text if $text;
217 elsif ( $element && $pref_name ) {
218 my $id = $self->{file} . "#$pref_name# $element";
219 my $text = $self->get_trans_text( $id );
220 $p->[$i] = $text if $text;
227 sub get_po_from_prefs {
230 for my $file ( @{$self->{pref_files}} ) {
231 my $pref = LoadFile( $self->{path_pref_en} . "/$file" );
232 $self->{file} = $file;
233 # Entries for tab titles
234 $self->po_append( $self->{file}, $_ ) for keys %$pref;
235 while ( my ($tab, $tab_content) = each %$pref ) {
236 if ( ref($tab_content) eq 'ARRAY' ) {
237 $self->add_prefs( $tab, $tab_content );
240 while ( my ($section, $sysprefs) = each %$tab_content ) {
241 my $comment = "$tab > $section";
242 $self->po_append( $self->{file} . " " . $section, $comment );
243 $self->add_prefs( $comment, $sysprefs );
253 # Create file header if it doesn't already exist
254 my $po = $self->{po};
255 $po->{''} ||= $default_pref_po_header;
257 # Write .po entries into a file put in Koha standard po directory
258 Locale::PO->save_file_fromhash( $self->po_filename, $po );
259 say "Saved in file: ", $self->po_filename if $self->{verbose};
263 sub get_po_merged_with_en {
266 # Get po from current 'en' .pref files
267 $self->get_po_from_prefs();
268 my $po_current = $self->{po};
270 # Get po from previous generation
271 my $po_previous = Locale::PO->load_file_ashash( $self->po_filename );
273 for my $id ( keys %$po_current ) {
274 my $po = $po_previous->{Locale::PO->quote($id)};
276 my $text = Locale::PO->dequote( $po->msgstr );
277 $po_current->{$id}->msgstr( $text );
284 print "Update '", $self->{lang},
285 "' preferences .po file from 'en' .pref files\n" if $self->{verbose};
286 $self->get_po_merged_with_en();
294 unless ( -r $self->{po_path_lang} ) {
295 print "Koha directories hierarchy for ", $self->{lang}, " must be created first\n";
299 # Get the language .po file merged with last modified 'en' preferences
300 $self->get_po_merged_with_en();
302 for my $file ( @{$self->{pref_files}} ) {
303 my $pref = LoadFile( $self->{path_pref_en} . "/$file" );
304 $self->{file} = $file;
305 # First, keys are replaced (tab titles)
308 $self->get_trans_text( $self->{file} ) || $_ => $pref->{$_}
312 while ( my ($tab, $tab_content) = each %$pref ) {
313 if ( ref($tab_content) eq 'ARRAY' ) {
314 $self->update_tab_prefs( $pref, $tab_content );
317 while ( my ($section, $sysprefs) = each %$tab_content ) {
318 $self->update_tab_prefs( $pref, $sysprefs );
321 for my $section ( keys %$tab_content ) {
322 my $id = $self->{file} . " $section";
323 my $text = $self->get_trans_text($id);
324 my $nsection = $text ? $text : $section;
325 $ntab->{$nsection} = $tab_content->{$section};
327 $pref->{$tab} = $ntab;
329 my $file_trans = $self->{po_path_lang} . "/$file";
330 print "Write $file\n" if $self->{verbose};
331 open my $fh, ">", $file_trans;
332 print $fh Dump($pref);
338 my ($self, $files) = @_;
339 say "Install templates" if $self->{verbose};
340 for my $trans ( @{$self->{interface}} ) {
342 " Install templates '$trans->{name}'\n",
343 " From: $trans->{dir}/en/\n",
344 " To : $trans->{dir}/$self->{lang}\n",
345 " With: $self->{path_po}/$self->{lang}$trans->{suffix}\n"
348 my $trans_dir = ( $trans->{name} =~ /help/ )?"$trans->{dir}":"$trans->{dir}/en/";
349 my $lang_dir = ( $trans->{name} =~ /help/ )?"$trans->{dir}":"$trans->{dir}/$self->{lang}";
350 $lang_dir =~ s|/en/|/$self->{lang}/|;
351 mkdir $lang_dir unless -d $lang_dir;
352 my $excludes = ( $trans->{name} =~ /UI/ )?"-x 'help'":"";
355 "$self->{process} install " .
358 "-s $self->{path_po}/$self->{lang}$trans->{suffix} -r $excludes" .
361 ? ' -f ' . join ' -f ', @$files
369 my ($self, $files) = @_;
371 say "Update templates" if $self->{verbose};
372 for my $trans ( @{$self->{interface}} ) {
374 " Update templates '$trans->{name}'\n",
375 " From: $trans->{dir}/en/\n",
376 " To : $self->{path_po}/$self->{lang}$trans->{suffix}\n"
378 my $lang_dir = "$trans->{dir}/$self->{lang}";
379 mkdir $lang_dir unless -d $lang_dir;
381 my $trans_dir = ( $trans->{name} =~ /help/ )?"$trans->{dir}":"$trans->{dir}/en/";
382 my $excludes = ( $trans->{name} =~ /UI/ )?"-x 'help'":"";
385 "$self->{process} update " .
387 "-s $self->{path_po}/$self->{lang}$trans->{suffix} -r $excludes" .
390 ? ' -f ' . join ' -f ', @$files
400 if ( -e $self->po_filename ) {
401 say "Preferences .po file already exists. Delete it if you want to recreate it.";
404 $self->get_po_from_prefs();
410 my ($self, $files) = @_;
412 say "Create templates\n" if $self->{verbose};
413 for my $trans ( @{$self->{interface}} ) {
415 " Create templates .po files for '$trans->{name}'\n",
416 " From: $trans->{dir}/en/\n",
417 " To : $self->{path_po}/$self->{lang}$trans->{suffix}\n"
420 my $trans_dir = ( $trans->{name} =~ /help/ )?"$trans->{dir}":"$trans->{dir}/en/";
421 my $excludes = ( $trans->{name} =~ /UI/ )?"-x 'help'":"";
424 "$self->{process} create " .
426 "-s $self->{path_po}/$self->{lang}$trans->{suffix} -r $excludes" .
429 ? ' -f ' . join ' -f ', @$files
435 sub create_messages {
438 print "Create messages ($self->{lang})\n" if $self->{verbose};
440 "$self->{cp} $self->{domain}.pot " .
441 "$self->{path_po}/$self->{lang}-$self->{domain}.po";
444 sub update_messages {
447 my $pofile = "$self->{path_po}/$self->{lang}-$self->{domain}.po";
448 print "Update messages ($self->{lang})\n" if $self->{verbose};
449 if ( not -f $pofile ) {
450 print "File $pofile does not exist\n" if $self->{verbose};
451 $self->create_messages();
453 system "$self->{msgmerge} -U $pofile $self->{domain}.pot";
456 sub extract_messages {
459 my $intranetdir = $self->{context}->config('intranetdir');
461 my @directories_to_scan = ('.');
462 my @blacklist = qw(blib koha-tmpl skel tmp t);
463 while (@directories_to_scan) {
464 my $dir = shift @directories_to_scan;
465 opendir DIR
, "$intranetdir/$dir" or die "Unable to open $dir: $!";
466 foreach my $entry (readdir DIR
) {
467 next if $entry =~ /^\./;
468 my $relentry = "$dir/$entry";
469 $relentry =~ s
|^\
./||;
470 if (-d
"$intranetdir/$relentry" and not grep /^$relentry$/, @blacklist) {
471 push @directories_to_scan, "$relentry";
472 } elsif (-f
"$intranetdir/$relentry" and $relentry =~ /(pl
|pm
)$/) {
473 push @files_to_scan, "$relentry";
478 my $xgettext_cmd = "$self->{xgettext} -L Perl --from-code=UTF-8 " .
479 "-o $Bin/$self->{domain}.pot -D $intranetdir";
480 $xgettext_cmd .= " $_" foreach (@files_to_scan);
482 if (system($xgettext_cmd) != 0) {
483 die "system call failed: $xgettext_cmd";
486 if ( -f
"$Bin/$self->{domain}.pot" ) {
487 my $replace_charset_cmd = "$self->{sed} --in-place " .
488 "$Bin/$self->{domain}.pot " .
489 "--expression='s/charset=CHARSET/charset=UTF-8/'";
490 if (system($replace_charset_cmd) != 0) {
491 die "system call failed: $replace_charset_cmd";
494 print "No messages found\n" if $self->{verbose
};
503 unlink "$Bin/$self->{domain}.pot";
507 my ($self, $files) = @_;
508 return unless $self->{lang
};
509 $self->install_tmpl($files) unless $self->{pref_only
};
510 $self->install_prefs();
516 opendir( my $dh, $self->{path_po
} );
517 my @files = grep { $_ =~ /-i-opac-t-prog-v-3006000.po$/ }
519 @files = map { $_ =~ s/-i-opac-t-prog-v-3006000.po$//; $_ } @files;
524 my ($self, $files) = @_;
525 my @langs = $self->{lang
} ?
($self->{lang
}) : $self->get_all_langs();
526 my $extract_ok = $self->extract_messages();
527 for my $lang ( @langs ) {
528 $self->set_lang( $lang );
529 $self->update_tmpl($files) unless $self->{pref_only
};
530 $self->update_prefs();
531 $self->update_messages() if $extract_ok;
533 $self->remove_pot() if $extract_ok;
538 my ($self, $files) = @_;
539 return unless $self->{lang
};
540 $self->create_tmpl($files) unless $self->{pref_only
};
541 $self->create_prefs();
542 if ($self->extract_messages()) {
543 $self->create_messages();
555 LangInstaller.pm - Handle templates and preferences translation
559 my $installer = LangInstaller->new( 'fr-FR' );
560 $installer->create();
561 $installer->update();
562 $installer->install();
563 for my $lang ( @{$installer->{langs} ) {
564 $installer->set_lang( $lan );
565 $installer->install();
572 Create a new instance of the installer object.
576 For the current language, create .po files for templates and preferences based
577 of the english ('en') version.
581 For the current language, update .po files.
585 For the current langage C<$self->{lang}, use .po files to translate the english
586 version of templates and preferences files and copy those files in the
587 appropriate directory.
591 =item translate create F<lang>
593 Create 3 .po files in F<po> subdirectory: (1) from opac pages templates, (2)
594 intranet templates, and (3) from preferences.
598 =item F<lang>-opac.po
600 Contains extracted text from english (en) OPAC templates found in
601 <KOHA_ROOT>/koha-tmpl/opac-tmpl/prog/en/ directory.
603 =item F<lang>-intranet.po
605 Contains extracted text from english (en) intranet templates found in
606 <KOHA_ROOT>/koha-tmpl/intranet-tmpl/prog/en/ directory.
608 =item F<lang>-pref.po
610 Contains extracted text from english (en) preferences. They are found in files
611 located in <KOHA_ROOT>/koha-tmpl/intranet-tmpl/prog/en/admin/preferences
616 =item pref-trans update F<lang>
618 Update .po files in F<po> directory, named F<lang>-*.po.
620 =item pref-trans install F<lang>