Bugzilla – Attachment 81734 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: Allow correct handling of plural translation
Bug-15395-Allow-correct-handling-of-plural-transla.patch (text/plain), 28.94 KB, created by
Jonathan Druart
on 2018-10-31 15:01:32 UTC
(
hide
)
Description:
Bug 15395: Allow correct handling of plural translation
Filename:
MIME Type:
Creator:
Jonathan Druart
Created:
2018-10-31 15:01:32 UTC
Size:
28.94 KB
patch
obsolete
>From b0c987e0ab9d3840464c2908f0fb89d5857a6864 Mon Sep 17 00:00:00 2001 >From: Julian Maurice <julian.maurice@biblibre.com> >Date: Tue, 15 Dec 2015 02:31:03 +0100 >Subject: [PATCH] Bug 15395: Allow correct handling of plural translation >MIME-Version: 1.0 >Content-Type: text/plain; charset=UTF-8 >Content-Transfer-Encoding: 8bit > >Locale::Maketext does not allow correct handling of plural translation >for languages that have more than one plural forms. >Locale::Messages does. > >So Koha::I18N is now a wrapper around Locale::Messages, just like >Locale::TextDomain, and export the same symbols as Locale::TextDomain. >You can refer to documentation of Locale::TextDomain to know how to use >exported subroutines. > >Example usage: > __("Hi") > __x("Hi {name}", name => 'Bob') > __n("item", "items", $num_items) > __nx("one item", "{count} items", $num_items, count => $num_items) > __p("Bibliographic record", "item") > >This patch also brings Koha::I18N power to Template::Toolkit templates >by adding a TT plugin. >This plugin can be used 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. > >Remove dependency to Locale::Maketext and Locale::Maketext::Lexicon >Add dependency to Locale::Messages and PPI > >Test plan for translation in Perl code: >1. Open a .pl script or .pm module with your favorite text editor >2. Add 'use Koha::I18N;' in the beginning of file >3. Use one of the subroutines exported by Koha::I18N and be sure to have > a way to visualize the result (pass result to the template for > example, or simply warn and watch the log file) >4. cd misc/translator && ./translate update fr-FR # try other languages >5. Open misc/translator/po/fr-FR-messages.po and translate your > string(s) > You may need to change the "Plural-Forms" header. See > https://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html >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 >8. prove t/Koha/I18N.t > >Test plan for translation in templates: >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-messages.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> > >Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org> >--- > C4/Installer/PerlDependencies.pm | 12 +- > Koha/I18N.pm | 191 +++++++++++++++++--- > 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 | 201 ++++++++++++++++++--- > t/Koha/I18N.t | 62 +++++++ > t/Koha/I18N/po/xx_XX/LC_MESSAGES/Koha.mo | Bin 0 -> 1370 bytes > 8 files changed, 557 insertions(+), 59 deletions(-) > 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 > create mode 100755 t/Koha/I18N.t > create mode 100644 t/Koha/I18N/po/xx_XX/LC_MESSAGES/Koha.mo > >diff --git a/C4/Installer/PerlDependencies.pm b/C4/Installer/PerlDependencies.pm >index bf8599162b..ab37c150df 100644 >--- a/C4/Installer/PerlDependencies.pm >+++ b/C4/Installer/PerlDependencies.pm >@@ -727,15 +727,15 @@ our $PERL_DEPS = { > required => 1, > min_ver => '2.125', > }, >- 'Locale::Maketext' => { >+ 'Locale::Messages' => { > 'usage' => 'Core', > 'required' => '1', >- 'min_ver' => '1.19', >+ 'min_ver' => '1.20', > }, >- 'Locale::Maketext::Lexicon' => { >- 'usage' => 'Core', >- 'required' => '1', >- 'min_ver' => '0.91', >+ 'PPI' => { >+ 'usage' => 'I18N', >+ 'required' => '0', >+ 'min_ver' => '1.215', > }, > 'LWP::Protocol::https' => { > 'usage' => 'OverDrive integration', >diff --git a/Koha/I18N.pm b/Koha/I18N.pm >index 33730f99b8..44c47132ff 100644 >--- a/Koha/I18N.pm >+++ b/Koha/I18N.pm >@@ -18,41 +18,186 @@ package Koha::I18N; > # along with Koha; if not, see <http://www.gnu.org/licenses>. > > use Modern::Perl; >-use base qw(Locale::Maketext Exporter); > > use CGI; > use C4::Languages; >+use C4::Context; > >-use Locale::Maketext::Lexicon { >- 'en' => ['Auto'], >- '*' => [ >- Gettext => >- C4::Context->config('intranetdir') >- . '/misc/translator/po/*-messages.po' >- ], >- '_AUTO' => 1, >- '_style' => 'gettext', >-}; >+use Encode; >+use Locale::Messages qw(:locale_h nl_putenv setlocale LC_MESSAGES); >+use Koha::Cache::Memory::Lite; > >-our @EXPORT = qw( gettext ); >+use parent 'Exporter'; >+our @EXPORT = qw( >+ __ >+ __x >+ __n >+ __nx >+ __xn >+ __p >+ __px >+ __np >+ __npx >+ N__ >+ N__n >+ N__p >+ N__np >+); > >-my %language_handles; >+our $textdomain = 'Koha'; > >-sub get_language_handle { >- my $cgi = new CGI; >- my $language = C4::Languages::getlanguage; >+sub init { >+ my $cache = Koha::Cache::Memory::Lite->get_instance(); >+ my $cache_key = 'i18n:initialized'; >+ unless ($cache->get_from_cache($cache_key)) { >+ my @system_locales = grep { chomp; not (/^C/ || $_ eq 'POSIX') } qx/locale -a/; >+ if (@system_locales) { >+ # LANG needs to be set to a valid locale, >+ # otherwise LANGUAGE is ignored >+ nl_putenv('LANG=' . $system_locales[0]); >+ setlocale(LC_MESSAGES, ''); > >- if (not exists $language_handles{$language}) { >- $language_handles{$language} = __PACKAGE__->get_handle($language) >- or die "No language handle for '$language'"; >+ my $langtag = C4::Languages::getlanguage; >+ my @subtags = split /-/, $langtag; >+ my ($language, $region) = @subtags; >+ if ($region && length $region == 4) { >+ $region = $subtags[2]; >+ } >+ my $locale = $language; >+ if ($region) { >+ $locale .= '_' . $region; >+ } >+ >+ nl_putenv("LANGUAGE=$locale"); >+ nl_putenv('OUTPUT_CHARSET=UTF-8'); >+ >+ my $directory = _base_directory(); >+ textdomain($textdomain); >+ bindtextdomain($textdomain, $directory); >+ } else { >+ warn "No locale installed. Localization cannot work and is therefore disabled"; >+ } >+ >+ $cache->set_in_cache($cache_key, 1); >+ } >+} >+ >+sub __ { >+ my ($msgid) = @_; >+ >+ $msgid = Encode::encode_utf8($msgid); >+ >+ return _gettext(\&gettext, [ $msgid ]); >+} >+ >+sub __x { >+ my ($msgid, %vars) = @_; >+ >+ $msgid = Encode::encode_utf8($msgid); >+ >+ return _gettext(\&gettext, [ $msgid ], %vars); >+} >+ >+sub __n { >+ my ($msgid, $msgid_plural, $count) = @_; >+ >+ $msgid = Encode::encode_utf8($msgid); >+ $msgid_plural = Encode::encode_utf8($msgid_plural); >+ >+ return _gettext(\&ngettext, [ $msgid, $msgid_plural, $count ]); >+} >+ >+sub __nx { >+ my ($msgid, $msgid_plural, $count, %vars) = @_; >+ >+ $msgid = Encode::encode_utf8($msgid); >+ $msgid_plural = Encode::encode_utf8($msgid_plural); >+ >+ return _gettext(\&ngettext, [ $msgid, $msgid_plural, $count ], %vars); >+} >+ >+sub __xn { >+ return __nx(@_); >+} >+ >+sub __p { >+ my ($msgctxt, $msgid) = @_; >+ >+ $msgctxt = Encode::encode_utf8($msgctxt); >+ $msgid = Encode::encode_utf8($msgid); >+ >+ return _gettext(\&pgettext, [ $msgctxt, $msgid ]); >+} >+ >+sub __px { >+ my ($msgctxt, $msgid, %vars) = @_; >+ >+ $msgctxt = Encode::encode_utf8($msgctxt); >+ $msgid = Encode::encode_utf8($msgid); >+ >+ return _gettext(\&pgettext, [ $msgctxt, $msgid ], %vars); >+} >+ >+sub __np { >+ my ($msgctxt, $msgid, $msgid_plural, $count) = @_; >+ >+ $msgctxt = Encode::encode_utf8($msgctxt); >+ $msgid = Encode::encode_utf8($msgid); >+ $msgid_plural = Encode::encode_utf8($msgid_plural); >+ >+ return _gettext(\&npgettext, [ $msgctxt, $msgid, $msgid_plural, $count ]); >+} >+ >+sub __npx { >+ my ($msgctxt, $msgid, $msgid_plural, $count, %vars) = @_; >+ >+ $msgctxt = Encode::encode_utf8($msgctxt); >+ $msgid = Encode::encode_utf8($msgid); >+ $msgid_plural = Encode::encode_utf8($msgid_plural); >+ >+ return _gettext(\&npgettext, [ $msgctxt, $msgid, $msgid_plural, $count], %vars); >+} >+ >+sub N__ { >+ return @_; >+} >+ >+sub N__n { >+ return @_; >+} >+ >+sub N__p { >+ return @_; >+} >+ >+sub N__np { >+ return @_; >+} >+ >+sub _base_directory { >+ return C4::Context->config('intranetdir') . '/misc/translator/po'; >+} >+ >+sub _gettext { >+ my ($sub, $args, %vars) = @_; >+ >+ init(); >+ >+ my $text = Encode::decode_utf8($sub->(@$args)); >+ if (%vars) { >+ $text = _expand($text, %vars); > } > >- return $language_handles{$language}; >+ return $text; > } > >-sub gettext { >- my $lh = get_language_handle; >- $lh->maketext(@_); >+sub _expand { >+ my ($text, %vars) = @_; >+ >+ my $re = join '|', map { quotemeta $_ } keys %vars; >+ $text =~ s/\{($re)\}/defined $vars{$1} ? $vars{$1} : "{$1}"/ge; >+ >+ return $text; > } > > 1; >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 ff42dcf779..7a621c19ba 100644 >--- a/misc/translator/LangInstaller.pm >+++ b/misc/translator/LangInstaller.pm >@@ -25,6 +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; > >@@ -66,13 +73,17 @@ sub new { > $self->{process} = "$Bin/tmpl_process3.pl " . ($verbose ? '' : '-q'); > $self->{path_po} = "$Bin/po"; > $self->{po} = { '' => $default_pref_po_header }; >- $self->{domain} = 'messages'; >+ $self->{domain} = 'Koha'; > $self->{cp} = `which cp`; > $self->{msgmerge} = `which msgmerge`; >+ $self->{msgfmt} = `which msgfmt`; >+ $self->{msginit} = `which msginit`; > $self->{xgettext} = `which xgettext`; > $self->{sed} = `which sed`; > chomp $self->{cp}; > chomp $self->{msgmerge}; >+ chomp $self->{msgfmt}; >+ chomp $self->{msginit}; > chomp $self->{xgettext}; > chomp $self->{sed}; > >@@ -456,30 +467,134 @@ sub create_tmpl { > } > } > >+sub locale_name { >+ my $self = shift; >+ >+ my ($language, $region, $country) = split /-/, $self->{lang}; >+ $country //= $region; >+ my $locale = $language; >+ if ($country && length($country) == 2) { >+ $locale .= '_' . $country; >+ } >+ >+ return $locale; >+} >+ > sub create_messages { > my $self = shift; > >- print "Create messages ($self->{lang})\n" if $self->{verbose}; >- system >- "$self->{cp} $self->{domain}.pot " . >- "$self->{path_po}/$self->{lang}-$self->{domain}.po"; >+ my $pot = "$self->{domain}.pot"; >+ my $po = "$self->{path_po}/$self->{lang}-messages.po"; >+ >+ unless ( -f $pot ) { >+ $self->extract_messages(); >+ } >+ >+ say "Create messages ($self->{lang})" if $self->{verbose}; >+ my $locale = $self->locale_name(); >+ system "$self->{msginit} -i $pot -o $po -l $locale --no-translator"; >+ >+ # If msginit failed to correctly set Plural-Forms, set a default one >+ system "$self->{sed} --in-place $po " >+ . "--expression='s/Plural-Forms: nplurals=INTEGER; plural=EXPRESSION/Plural-Forms: nplurals=2; plural=(n != 1)/'"; > } > > sub update_messages { > my $self = shift; > >- my $pofile = "$self->{path_po}/$self->{lang}-$self->{domain}.po"; >- print "Update messages ($self->{lang})\n" if $self->{verbose}; >- if ( not -f $pofile ) { >- print "File $pofile does not exist\n" if $self->{verbose}; >+ my $pot = "$self->{domain}.pot"; >+ my $po = "$self->{path_po}/$self->{lang}-messages.po"; >+ >+ unless ( -f $pot ) { >+ $self->extract_messages(); >+ } >+ >+ if ( -f $po ) { >+ say "Update messages ($self->{lang})" if $self->{verbose}; >+ system "$self->{msgmerge} -U $po $pot"; >+ } else { > $self->create_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) { >+ say "Extract messages from $file" if $self->{verbose}; >+ my $template = read_file("$intranetdir/$file"); >+ my $data = $parser->parse($template); >+ unless ($data) { >+ warn "Error at $file : " . $parser->error(); >+ next; >+ } >+ >+ make_path(dirname("$tempdir/$file")); >+ open my $fh, '>', "$tempdir/$file"; >+ >+ my @blocks = ($data->{BLOCK}, values %{ $data->{DEFBLOCKS} }); >+ foreach my $block (@blocks) { >+ my $document = PPI::Document->new(\$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 >+ 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; > >+ say "Extract messages into POT file" if $self->{verbose}; >+ > my $intranetdir = $self->{context}->config('intranetdir'); > my @files_to_scan; > my @directories_to_scan = ('.'); >@@ -499,26 +614,53 @@ sub extract_messages { > } > } > >- my $xgettext_cmd = "$self->{xgettext} -L Perl --from-code=UTF-8 " . >- "-o $Bin/$self->{domain}.pot -D $intranetdir"; >+ 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 " >+ . "--package-name=Koha --package-version='' " >+ . "-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 $tempdir -D $intranetdir"; > $xgettext_cmd .= " $_" foreach (@files_to_scan); > > if (system($xgettext_cmd) != 0) { > die "system call failed: $xgettext_cmd"; > } > >- if ( -f "$Bin/$self->{domain}.pot" ) { >- my $replace_charset_cmd = "$self->{sed} --in-place " . >- "$Bin/$self->{domain}.pot " . >- "--expression='s/charset=CHARSET/charset=UTF-8/'"; >- if (system($replace_charset_cmd) != 0) { >- die "system call failed: $replace_charset_cmd"; >- } >- } else { >- print "No messages found\n" if $self->{verbose}; >- return; >+ my $replace_charset_cmd = "$self->{sed} --in-place " . >+ "$Bin/$self->{domain}.pot " . >+ "--expression='s/charset=CHARSET/charset=UTF-8/'"; >+ if (system($replace_charset_cmd) != 0) { >+ die "system call failed: $replace_charset_cmd"; > } >- return 1; >+} >+ >+sub install_messages { >+ my ($self) = @_; >+ >+ my $locale = $self->locale_name(); >+ my $modir = "$self->{path_po}/$locale/LC_MESSAGES"; >+ my $pofile = "$self->{path_po}/$self->{lang}-messages.po"; >+ my $mofile = "$modir/$self->{domain}.mo"; >+ >+ if ( not -f $pofile ) { >+ $self->create_messages(); >+ } >+ say "Install messages ($locale)" if $self->{verbose}; >+ make_path($modir); >+ system "$self->{msgfmt} -o $mofile $pofile"; > } > > sub remove_pot { >@@ -532,6 +674,8 @@ sub install { > return unless $self->{lang}; > $self->install_tmpl($files) unless $self->{pref_only}; > $self->install_prefs(); >+ $self->install_messages(); >+ $self->remove_pot(); > } > > >@@ -547,14 +691,13 @@ sub get_all_langs { > sub update { > my ($self, $files) = @_; > my @langs = $self->{lang} ? ($self->{lang}) : $self->get_all_langs(); >- my $extract_ok = $self->extract_messages(); > for my $lang ( @langs ) { > $self->set_lang( $lang ); > $self->update_tmpl($files) unless $self->{pref_only}; > $self->update_prefs(); >- $self->update_messages() if $extract_ok; >+ $self->update_messages(); > } >- $self->remove_pot() if $extract_ok; >+ $self->remove_pot(); > } > > >@@ -563,10 +706,8 @@ sub create { > return unless $self->{lang}; > $self->create_tmpl($files) unless $self->{pref_only}; > $self->create_prefs(); >- if ($self->extract_messages()) { >- $self->create_messages(); >- $self->remove_pot(); >- } >+ $self->create_messages(); >+ $self->remove_pot(); > } > > >diff --git a/t/Koha/I18N.t b/t/Koha/I18N.t >new file mode 100755 >index 0000000000..6524e66e80 >--- /dev/null >+++ b/t/Koha/I18N.t >@@ -0,0 +1,62 @@ >+#!/usr/bin/perl >+ >+use Modern::Perl; >+use Test::More tests => 35; >+use Test::MockModule; >+use FindBin qw($Bin); >+use Encode; >+ >+BEGIN { >+ use_ok('Koha::I18N'); >+} >+ >+my $koha_i18n = Test::MockModule->new('Koha::I18N'); >+$koha_i18n->mock('_base_directory', sub { "$Bin/I18N/po" }); >+ >+my $c4_languages = Test::MockModule->new('C4::Languages'); >+$c4_languages->mock('getlanguage', sub { 'xx-XX' }); >+ >+# If you need to modify the MO file to add new tests >+# 1) msgunfmt -o Koha.po t/Koha/I18N/po/xx_XX/LC_MESSAGES/Koha.mo >+# 2) Edit Koha.po >+# 3) msgfmt -o t/Koha/I18N/po/xx_XX/LC_MESSAGES/Koha.mo Koha.po >+my @tests = ( >+ [ __('test') => 'test ã' ], >+ [ __x('Hello {name}', name => 'World') => 'Hello World ã' ], >+ [ __n('Singular', 'Plural', 0) => 'Zero ã' ], >+ [ __n('Singular', 'Plural', 1) => 'Singular ã' ], >+ [ __n('Singular', 'Plural', 2) => 'Plural ã' ], >+ [ __n('Singular', 'Plural', 3) => 'Plural ã' ], >+ [ __nx('one item', '{count} items', 0, count => 0) => 'no item ã' ], >+ [ __nx('one item', '{count} items', 1, count => 1) => 'one item ã' ], >+ [ __nx('one item', '{count} items', 2, count => 2) => '2 items ã' ], >+ [ __nx('one item', '{count} items', 3, count => 3) => '3 items ã' ], >+ [ __xn('one item', '{count} items', 0, count => 0) => 'no item ã' ], >+ [ __xn('one item', '{count} items', 1, count => 1) => 'one item ã' ], >+ [ __xn('one item', '{count} items', 2, count => 2) => '2 items ã' ], >+ [ __xn('one item', '{count} items', 3, count => 3) => '3 items ã' ], >+ [ __p('biblio', 'title') => 'title (biblio) ã' ], >+ [ __p('patron', 'title') => 'title (patron) ã' ], >+ [ __px('biblio', 'Remove item {id}', id => 42) => 'Remove item 42 (biblio) ã' ], >+ [ __px('list', 'Remove item {id}', id => 42) => 'Remove item 42 (list) ã' ], >+ [ __np('ctxt1', 'singular', 'plural', 0) => 'zero (ctxt1) ã' ], >+ [ __np('ctxt1', 'singular', 'plural', 1) => 'singular (ctxt1) ã' ], >+ [ __np('ctxt1', 'singular', 'plural', 2) => 'plural (ctxt1) ã' ], >+ [ __np('ctxt1', 'singular', 'plural', 3) => 'plural (ctxt1) ã' ], >+ [ __np('ctxt2', 'singular', 'plural', 0) => 'zero (ctxt2) ã' ], >+ [ __np('ctxt2', 'singular', 'plural', 1) => 'singular (ctxt2) ã' ], >+ [ __np('ctxt2', 'singular', 'plural', 2) => 'plural (ctxt2) ã' ], >+ [ __np('ctxt2', 'singular', 'plural', 3) => 'plural (ctxt2) ã' ], >+ [ __npx('biblio', 'one item', '{count} items', 0, count => 0) => 'no item (biblio) ã' ], >+ [ __npx('biblio', 'one item', '{count} items', 1, count => 1) => 'one item (biblio) ã' ], >+ [ __npx('biblio', 'one item', '{count} items', 2, count => 2) => '2 items (biblio) ã' ], >+ [ __npx('biblio', 'one item', '{count} items', 3, count => 3) => '3 items (biblio) ã' ], >+ [ __npx('list', 'one item', '{count} items', 0, count => 0) => 'no item (list) ã' ], >+ [ __npx('list', 'one item', '{count} items', 1, count => 1) => 'one item (list) ã' ], >+ [ __npx('list', 'one item', '{count} items', 2, count => 2) => '2 items (list) ã' ], >+ [ __npx('list', 'one item', '{count} items', 3, count => 3) => '3 items (list) ã' ], >+); >+ >+foreach my $test (@tests) { >+ is($test->[0], decode_utf8($test->[1]), $test->[1]); >+} >diff --git a/t/Koha/I18N/po/xx_XX/LC_MESSAGES/Koha.mo b/t/Koha/I18N/po/xx_XX/LC_MESSAGES/Koha.mo >new file mode 100644 >index 0000000000000000000000000000000000000000..99be22f268b3e872ac10b2d2e3e538b1fd8293d6 >GIT binary patch >literal 1370 >zcmV-g1*Q7l5(Je10000D0000S0001l0000H0002&000000000m0RR9D0000n0RR9G >z0000!0RR9O0000^0RR9U0001H0RR9D0001l0RR9M0001y0RR9M0001|0RR9M0002J >z0RR9S0002f0RR9N0002*0RR9D000060ssI60000J0ssJm0RR9P0ssII0001-0{{RZ >z000230{{RW0002a0{{R;0002&0{{RL0000o1ONay0000*1ONay0001f1ONaV0002D >z1ONa(0002f1ONak0000J1pojN0000!1pojD0000{1poj600000000000000D00006 >z000030000C0000A000020000B0000000009000000000400005000070000800000 >zNM&qnZy<YaVQpo708?phXLW30asW_lb#h^B0AgukY-w)<Qe|y#c4Z)GbY*QIdue2S >z0AgukY-w)<Z*FBEX>?_60DEI^b#8QhAZc`EZF2x(X<}??Zv=E{bZlh+V{~|QF$8mI >zZfA9DVR8U)Y;|&BYye|)cyuxZb7^j8b!=gB0B~$|a$#%$Y-w|J1X5*fZ+2xMX>?_6 >zAbV+KeE@7}b94l6Ze<{8bY*P-dt+~PZghPhX>?_6a{zB{WguyEWo-a^V{dhCbbTOc >zbY*RG0B~V+a&K+~bZK;KWdL+#b94Yua&Ky7V{|P^WGz-@a&u{KZaN@KZ)jl(Qe|*& >za&#?Cb7yH}Ekbo?b1hVFIv@&APgE^Ka%Ew3X>V>VL}7GgIv_GIF*hwRG%YbOATcyL >zFf%JKGB7X-P){vVWp-(EX>V>VL}7GgIv_GIF*hwRG%YbOATcyLFf%JKGB7X-Oks0$ >zEmU%0ZgXs5bZ>GxAWC&?X<=?4O<{F%X=7y|JZg1pX<=?IZDDnCX=7zTVrgP*X<~9^ >zE@N+PJ_<}>ZfA92XJsu^WnpbPAb5BnJa~9OY-uiUa%VmYOkr+kbzx^^Iv{v>UszZQ >zO-W5fEmmc6b7^mGIv_DFFbYF&ZggdCbS+eQaAi6mbY*ySFK}#OX>L0pV`yP=b7gcr >zRa8bTI0{2=ZggdCbS+eJVQzC~WpXV=Zewp`X>MmaAUI-abP7;xb#h^BEk<v0ZF4#x >zZg6aMa$#(9Ju^EXaBOvQVQf7pZaqCPAU_~5AUYs!Jv}iXKOiw6Iv_GBI|=|uWo&G3 >zAbW0MZDoBR<Aa0%T4i!?Amf9C08?phXLW30av<Y_gaA-%b#h^BAmf9C08(XbZ+2xM >zX>?_6AbV+KeIO`eX<}??Zz&+-gM<KXZy;%OWo;lRVrgP*X>TbY<Aa0%Z*FBEX>?_6 >zAShyKVr*$|DInv6gaCVEZ*^{TeIRLcWo>gHC}L@1Y-w*PAmf9C0CZ_|Y-J!QVrgP* >zX>TbY<Aa0%dS!BNASh#Wcyuu-Amf9C0CQ<>XLW30av&&Uba-?zDInv6gaB}Cb#h^B >zASh#Wcyuu-Amf9C0D5I|Zy+dRba-?!DInv6gaC7CZfA9DVR9fSV{~|QGASVAgM<KZ >zY;|&BY#=COba-?!DInv6gaA@yZEtpEAZc`EZ6JGTWPKngY-w|JDInv6gaB@DAZc`E >zZ6GLYX>)WbAmf9C0B>$(AZc`EZ6GLYX>)WbAmf9C0DEI^b#8QhAZc`EZF3+fY-w|J >zDInv6gaB@DAZc`EZ6M==gaB`DWguyEWo;nigM<KkV{dhCbbTOcbY*RGAmf9C0CZ_| >cY-J!QaA9<EZ*D0d<Aa0%bY*jNAmf9C0F@bu>;M1& > >literal 0 >KcmV+b0RR6000031 > >-- >2.11.0
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