Bugzilla – Attachment 73902 Details for
Bug 15395
Internationalization: plural forms, context, and more
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 15395: Plural translations in templates
Bug-15395-Plural-translations-in-templates.patch (text/plain), 10.97 KB, created by
Julian Maurice
on 2018-04-10 06:26:36 UTC
(
hide
)
Description:
Bug 15395: Plural translations in templates
Filename:
MIME Type:
Creator:
Julian Maurice
Created:
2018-04-10 06:26:36 UTC
Size:
10.97 KB
patch
obsolete
>From 06c126c03905cb8aec730659260add61e40e7fda Mon Sep 17 00:00:00 2001 >From: Julian Maurice <julian.maurice@biblibre.com> >Date: Tue, 15 Dec 2015 09:03:20 +0100 >Subject: [PATCH] Bug 15395: Plural translations in templates >MIME-Version: 1.0 >Content-Type: text/plain; charset=UTF-8 >Content-Transfer-Encoding: 8bit > >Provides a way to handle translation of plural forms in templates > >Add Template::Toolkit plugin for Koha::I18N > >Use like this: > >[%# USE the plugin and define some macros %] >[% PROCESS 'i18n.inc' %] > >[%# tn is the equivalent of __n %] >[%# macro names can't start with underscore, t is for "translate" %] >[% tn('item', 'items', num_items) %] > >Extraction of strings from templates is a bit complicated and use >Template::Parser and PPI. Template is compiled into Perl code and then >analyzed by PPI. It is slow, but should be correct even with complex >constructions. > >Add dependency to PPI > >Test plan: >1. Open a template file (.tt or .inc) with your favorite text editor >2. Add the PROCESS directive mentioned above in the beginning of file >3. Use one of the t* macros defined in i18n.inc. They are used like > their "__" equivalent, with one difference: the 'x' variants take a > hashref instead of a hash as last parameter >4. cd misc/translator && ./translate update fr-FR >5. Open misc/translator/po/fr_FR/LC_MESSAGES/Koha.po and translate your > string(s) >6. ./translate install fr-FR >7. Use your web browser to go to the page that should display the > translation, change language and verify the translation is > correct > >Signed-off-by: Marc Véron <veron@veron.ch> >--- > 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 > >diff --git a/C4/Installer/PerlDependencies.pm b/C4/Installer/PerlDependencies.pm >index cbbc9cedac..ffdceee1ad 100644 >--- a/C4/Installer/PerlDependencies.pm >+++ b/C4/Installer/PerlDependencies.pm >@@ -727,6 +727,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', >diff --git a/Koha/Template/Plugin/I18N.pm b/Koha/Template/Plugin/I18N.pm >new file mode 100644 >index 0000000000..8c95ea6d72 >--- /dev/null >+++ b/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 <http://www.gnu.org/licenses>. >+ >+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; >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/i18n.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/i18n.inc >new file mode 100644 >index 0000000000..4b5da9231b >--- /dev/null >+++ b/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; >+%] >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/i18n.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/i18n.inc >new file mode 100644 >index 0000000000..4b5da9231b >--- /dev/null >+++ b/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; >+%] >diff --git a/misc/translator/LangInstaller.pm b/misc/translator/LangInstaller.pm >index 758f126dab..a5a492bffe 100644 >--- a/misc/translator/LangInstaller.pm >+++ b/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; > >@@ -507,6 +513,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; > >@@ -529,11 +597,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) { >-- >2.14.2
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 15395
:
45797
|
45798
|
45799
|
46337
|
50023
|
50024
|
50025
|
50026
|
61873
|
61874
|
61875
|
61876
|
61934
|
61935
|
61977
|
62008
|
62041
|
62326
|
62327
|
62328
|
62329
|
62330
|
62331
|
62332
|
62333
|
62335
|
62709
|
65894
|
70974
|
70975
|
70976
|
71940
|
71941
|
71942
|
71943
|
71944
|
71945
|
71946
|
71947
|
71948
|
71949
|
71950
|
71951
|
71952
|
73901
|
73902
|
73903
|
73904
|
73905
|
73906
|
73907
|
73908
|
73909
|
73910
|
73911
|
73912
|
73913
|
76328
|
76329
|
76330
|
77475
|
77476
|
81152
|
81153
|
81154
|
81155
|
81156
|
81157
|
81158
|
81159
|
81167
|
81734
|
81735
|
81736
|
81737
|
81738
|
81739
|
81741
|
81742
|
81743