@@ -, +, @@ their "__" equivalent, with one difference: the 'x' variants take a hashref instead of a hash as last parameter string(s) translation, change language and verify the translation is correct --- C4/Installer/PerlDependencies.pm | 5 ++ Koha/Template/Plugin/I18N.pm | 72 +++++++++++++++++++ koha-tmpl/intranet-tmpl/prog/en/includes/i18n.inc | 39 ++++++++++ koha-tmpl/opac-tmpl/bootstrap/en/includes/i18n.inc | 39 ++++++++++ misc/translator/LangInstaller.pm | 83 +++++++++++++++++++++- 5 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 Koha/Template/Plugin/I18N.pm create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/i18n.inc create mode 100644 koha-tmpl/opac-tmpl/bootstrap/en/includes/i18n.inc --- a/C4/Installer/PerlDependencies.pm +++ a/C4/Installer/PerlDependencies.pm @@ -722,6 +722,11 @@ our $PERL_DEPS = { 'required' => '1', 'min_ver' => '1.20', }, + 'PPI' => { + 'usage' => 'I18N', + 'required' => '0', + 'min_ver' => '1.215', + }, 'LWP::Protocol::https' => { 'usage' => 'OverDrive integration', 'required' => '0', --- a/Koha/Template/Plugin/I18N.pm +++ a/Koha/Template/Plugin/I18N.pm @@ -0,0 +1,72 @@ +package Koha::Template::Plugin::I18N; + +# Copyright BibLibre 2015 +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use base qw( Template::Plugin ); + +use C4::Context; +use Koha::I18N; + +sub t { + my ($self, $msgid) = @_; + return __($msgid); +} + +sub tx { + my ($self, $msgid, $vars) = @_; + return __x($msgid, %$vars); +} + +sub tn { + my ($self, $msgid, $msgid_plural, $count) = @_; + return __n($msgid, $msgid_plural, $count); +} + +sub tnx { + my ($self, $msgid, $msgid_plural, $count, $vars) = @_; + return __nx($msgid, $msgid_plural, $count, %$vars); +} + +sub txn { + my ($self, $msgid, $msgid_plural, $count, $vars) = @_; + return __xn($msgid, $msgid_plural, $count, %$vars); +} + +sub tp { + my ($self, $msgctxt, $msgid) = @_; + return __p($msgctxt, $msgid); +} + +sub tpx { + my ($self, $msgctxt, $msgid, $vars) = @_; + return __px($msgctxt, $msgid, %$vars); +} + +sub tnp { + my ($self, $msgctxt, $msgid, $msgid_plural, $count) = @_; + return __np($msgctxt, $msgid, $msgid_plural, $count); +} + +sub tnpx { + my ($self, $msgctxt, $msgid, $msgid_plural, $count, $vars) = @_; + return __np($msgctxt, $msgid, $msgid_plural, $count, %$vars); +} + +1; --- a/koha-tmpl/intranet-tmpl/prog/en/includes/i18n.inc +++ a/koha-tmpl/intranet-tmpl/prog/en/includes/i18n.inc @@ -0,0 +1,39 @@ +[% + USE I18N; + + MACRO t(msgid) BLOCK; + I18N.t(msgid); + END; + + MACRO tx(msgid, vars) BLOCK; + I18N.tx(msgid, vars); + END; + + MACRO tn(msgid, msgid_plural, count) BLOCK; + I18N.tn(msgid, msgid_plural, count); + END; + + MACRO tnx(msgid, msgid_plural, count, vars) BLOCK; + I18N.tnx(msgid, msgid_plural, count, vars); + END; + + MACRO txn(msgid, msgid_plural, count, vars) BLOCK; + I18N.txn(msgid, msgid_plural, count, vars); + END; + + MACRO tp(msgctxt, msgid) BLOCK; + I18N.tp(msgctxt, msgid); + END; + + MACRO tpx(msgctxt, msgid, vars) BLOCK; + I18N.tpx(msgctxt, msgid, vars); + END; + + MACRO tnp(msgctxt, msgid, msgid_plural, count) BLOCK; + I18N.tnp(msgctxt, msgid, msgid_plural, count); + END; + + MACRO tnpx(msgctxt, msgid, msgid_plural, count, vars) BLOCK; + I18N.tnpx(msgctxt, msgid, msgid_plural, count, vars); + END; +%] --- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/i18n.inc +++ a/koha-tmpl/opac-tmpl/bootstrap/en/includes/i18n.inc @@ -0,0 +1,39 @@ +[% + USE I18N; + + MACRO t(msgid) BLOCK; + I18N.t(msgid); + END; + + MACRO tx(msgid, vars) BLOCK; + I18N.tx(msgid, vars); + END; + + MACRO tn(msgid, msgid_plural, count) BLOCK; + I18N.tn(msgid, msgid_plural, count); + END; + + MACRO tnx(msgid, msgid_plural, count, vars) BLOCK; + I18N.tnx(msgid, msgid_plural, count, vars); + END; + + MACRO txn(msgid, msgid_plural, count, vars) BLOCK; + I18N.txn(msgid, msgid_plural, count, vars); + END; + + MACRO tp(msgctxt, msgid) BLOCK; + I18N.tp(msgctxt, msgid); + END; + + MACRO tpx(msgctxt, msgid, vars) BLOCK; + I18N.tpx(msgctxt, msgid, vars); + END; + + MACRO tnp(msgctxt, msgid, msgid_plural, count) BLOCK; + I18N.tnp(msgctxt, msgid, msgid_plural, count); + END; + + MACRO tnpx(msgctxt, msgid, msgid_plural, count, vars) BLOCK; + I18N.tnpx(msgctxt, msgid, msgid_plural, count, vars); + END; +%] --- a/misc/translator/LangInstaller.pm +++ a/misc/translator/LangInstaller.pm @@ -25,7 +25,13 @@ use C4::Context; use YAML::Syck qw( Dump LoadFile ); use Locale::PO; use FindBin qw( $Bin ); +use File::Basename; +use File::Find; use File::Path qw( make_path ); +use File::Slurp; +use File::Temp qw( tempdir ); +use Template::Parser; +use PPI; $YAML::Syck::ImplicitTyping = 1; @@ -502,6 +508,68 @@ sub update_messages { system "$self->{msgmerge} -U $pofile $self->{domain}.pot"; } +sub extract_messages_from_templates { + my ($self, $tempdir, @files) = @_; + + my $intranetdir = $self->{context}->config('intranetdir'); + my @keywords = qw(t tx tn txn tnx tp tpx tnp tnpx); + my $parser = Template::Parser->new(); + + foreach my $file (@files) { + my $template = read_file("$intranetdir/$file"); + my $data = $parser->parse($template); + my $document = PPI::Document->new(\$data->{BLOCK}); + + # [% t('foo') %] is compiled to $output .= $stash->get(['t', ['foo']]); + # We try to find all nodes corresponding to keyword (here 't') + my $nodes = $document->find(sub { + my ($topnode, $element) = @_; + + # Filter out non-valid keywords + return 0 unless ($element->isa('PPI::Token::Quote::Single')); + return 0 unless (grep {$element->content eq qq{'$_'}} @keywords); + + # keyword (e.g. 't') should be the first element of the arrayref + # passed to $stash->get() + return 0 if $element->sprevious_sibling; + + return 0 unless $element->snext_sibling + && $element->snext_sibling->snext_sibling + && $element->snext_sibling->snext_sibling->isa('PPI::Structure::Constructor'); + + # Check that it's indeed a call to $stash->get() + my $statement = $element->statement->parent->statement->parent->statement; + return 0 unless grep { $_->isa('PPI::Token::Symbol') && $_->content eq '$stash' } $statement->children; + return 0 unless grep { $_->isa('PPI::Token::Operator') && $_->content eq '->' } $statement->children; + return 0 unless grep { $_->isa('PPI::Token::Word') && $_->content eq 'get' } $statement->children; + + return 1; + }); + + next unless $nodes; + + # Write the Perl equivalent of calls to t* functions family, so xgettext + # can extract the strings correctly + make_path(dirname("$tempdir/$file")); + open my $fh, '>', "$tempdir/$file"; + + foreach my $node (@$nodes) { + my @args = map { + $_->significant && !$_->isa('PPI::Token::Operator') ? $_->content : () + } $node->snext_sibling->snext_sibling->find_first('PPI::Statement')->children; + + my $keyword = $node->content; + $keyword =~ s/^'t(.*)'$/__$1/; + + say $fh "$keyword(" . join(', ', @args) . ");"; + } + + close $fh; + } + + return $tempdir; +} + sub extract_messages { my $self = shift; @@ -524,11 +592,24 @@ sub extract_messages { } } + my @tt_files; + find(sub { + if ($File::Find::dir =~ m|/en/| && $_ =~ m/\.(tt|inc)$/) { + my $filename = $File::Find::name; + $filename =~ s|^$intranetdir/||; + push @tt_files, $filename; + } + }, "$intranetdir/koha-tmpl"); + + my $tempdir = tempdir('Koha-translate-XXXX', TMPDIR => 1, CLEANUP => 1); + $self->extract_messages_from_templates($tempdir, @tt_files); + push @files_to_scan, @tt_files; + my $xgettext_cmd = "$self->{xgettext} -L Perl --from-code=UTF-8 " . "-k -k__ -k__x -k__n:1,2 -k__nx:1,2 -k__xn:1,2 -k__p:1c,2 " . "-k__px:1c,2 -k__np:1c,2,3 -k__npx:1c,2,3 -kN__ -kN__n:1,2 " . "-kN__p:1c,2 -kN__np:1c,2,3 " - . "-o $Bin/$self->{domain}.pot -D $intranetdir"; + . "-o $Bin/$self->{domain}.pot -D $tempdir -D $intranetdir"; $xgettext_cmd .= " $_" foreach (@files_to_scan); if (system($xgettext_cmd) != 0) { --