From 7ff2ddde834cabeba58ea5e1f3c0dbf9da2cd75e Mon Sep 17 00:00:00 2001 From: Julian Maurice Date: Tue, 15 Dec 2015 02:31:03 +0100 Subject: [PATCH] Bug 15395: Allow correct handling of plural translation 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. The PO file moves from misc/translator/po/xx-XX-messages.po to misc/translator/po/xx_XX/LC_MESSAGES/Koha.po and now needs to be compiled to MO in order to be used by Koha. Compilation of PO file is done by running: ./translate install xx-XX Remove dependency to Locale::Maketext and Locale::Maketext::Lexicon Add dependency to Locale::Messages Test plan: 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/LC_MESSAGES/Koha.po and translate your string(s) You will 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 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") --- C4/Installer/PerlDependencies.pm | 9 +-- Koha/I18N.pm | 141 ++++++++++++++++++++++++++++++++------- misc/translator/LangInstaller.pm | 60 ++++++++++++++--- 3 files changed, 171 insertions(+), 39 deletions(-) diff --git a/C4/Installer/PerlDependencies.pm b/C4/Installer/PerlDependencies.pm index 09dafc4..59fe65f 100644 --- a/C4/Installer/PerlDependencies.pm +++ b/C4/Installer/PerlDependencies.pm @@ -692,15 +692,10 @@ our $PERL_DEPS = { required => 1, min_ver => '2.125', }, - 'Locale::Maketext' => { + 'Locale::Messages' => { 'usage' => 'Core', 'required' => '1', - 'min_ver' => '1.19', - }, - 'Locale::Maketext::Lexicon' => { - 'usage' => 'Core', - 'required' => '1', - 'min_ver' => '0.91', + 'min_ver' => '1.20', }, 'LWP::Protocol::https' => { 'usage' => 'OverDrive integration', diff --git a/Koha/I18N.pm b/Koha/I18N.pm index 33730f9..56d48d8 100644 --- a/Koha/I18N.pm +++ b/Koha/I18N.pm @@ -18,41 +18,136 @@ package Koha::I18N; # along with Koha; if not, see . 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::Util qw(set_locale); +use Locale::Messages qw(:locale_h :libintl_h nl_putenv); -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; +my $textdomain; -sub get_language_handle { - my $cgi = new CGI; - my $language = C4::Languages::getlanguage; +BEGIN { + $textdomain = 'Koha'; - 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 = set_locale(LC_ALL, $language, $region, 'utf-8'); + unless ($locale) { + set_locale(LC_MESSAGES, 'C'); + Locale::Messages->select_package('gettext_pp'); + $locale = $language; + if ($region) { + $locale .= '_' . $region; + } + nl_putenv("LANGUAGE=$locale"); + nl_putenv("LANG=$locale"); + nl_putenv('OUTPUT_CHARSET=utf-8'); + } + + my $directory = C4::Context->config('intranetdir') . '/misc/translator/po'; + textdomain($textdomain); + bindtextdomain($textdomain, $directory); +} + +sub __ { + my ($msgid) = @_; + my $text = dgettext($textdomain, $msgid); + return __decode($text); +} + +sub __x { + my ($msgid, %vars) = @_; + return __expand(__($msgid), %vars); +} + +sub __n { + my ($msgid, $msgid_plural, $count) = @_; + my $text = dngettext($textdomain, $msgid, $msgid_plural, $count); + return __decode($text); +} + +sub __nx { + my ($msgid, $msgid_plural, $count, %vars) = @_; + return __expand(__n($msgid, $msgid_plural, $count), %vars); +} + +sub __xn { + return __nx(@_); +} + +sub __p { + my ($msgctxt, $msgid) = @_; + my $text = dpgettext($textdomain, $msgctxt, $msgid); + return __decode($text); +} + +sub __px { + my ($msgctxt, $msgid, %vars) = @_; + return __expand(__p($msgctxt, $msgid), %vars); +} + +sub __np { + my ($msgctxt, $msgid, $msgid_plural, $count) = @_; + my $text = dnpgettext($textdomain, $msgctxt, $msgid, $msgid_plural, $count); + return __decode($text); +} + +sub __npx { + my ($msgctxt, $msgid, $msgid_plural, $count, %vars) = @_; + return __expand(__np($msgctxt, $msgid, $msgid_plural, $count), %vars); +} + +sub N__ { + return @_; +} + +sub N__n { + return @_; +} + +sub N__p { + return @_; +} + +sub N__np { + return @_; +} + +sub __expand { + my ($text, %vars) = @_; + + my $re = join '|', map { quotemeta $_ } keys %vars; + $text =~ s/\{($re)\}/defined $vars{$1} ? $vars{$1} : "{$1}"/ge; - return $language_handles{$language}; + return $text; } -sub gettext { - my $lh = get_language_handle; - $lh->maketext(@_); +sub __decode { + return Encode::decode_utf8(shift); } 1; diff --git a/misc/translator/LangInstaller.pm b/misc/translator/LangInstaller.pm index 683897d..42f844c 100644 --- a/misc/translator/LangInstaller.pm +++ b/misc/translator/LangInstaller.pm @@ -25,6 +25,7 @@ use C4::Context; use YAML::Syck qw( Dump LoadFile ); use Locale::PO; use FindBin qw( $Bin ); +use File::Path qw( make_path ); $YAML::Syck::ImplicitTyping = 1; @@ -66,13 +67,15 @@ 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->{xgettext} = `which xgettext`; $self->{sed} = `which sed`; chomp $self->{cp}; chomp $self->{msgmerge}; + chomp $self->{msgfmt}; chomp $self->{xgettext}; chomp $self->{sed}; @@ -466,19 +469,34 @@ sub create_tmpl { sub create_messages { my $self = shift; - print "Create messages ($self->{lang})\n" if $self->{verbose}; + my ($language, $country) = split /-/, $self->{lang}; + my $locale = $language; + if ($country) { + $locale .= '_' . $country; + } + my $podir = "$self->{path_po}/$locale/LC_MESSAGES"; + + make_path($podir); + say "Create messages ($locale)" if $self->{verbose}; system "$self->{cp} $self->{domain}.pot " . - "$self->{path_po}/$self->{lang}-$self->{domain}.po"; + "$podir/$self->{domain}.po"; } 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}; + my ($language, $country) = split /-/, $self->{lang}; + my $locale = $language; + if ($country) { + $locale .= '_' . $country; + } + my $podir = "$self->{path_po}/$locale/LC_MESSAGES"; + my $pofile = "$podir/$self->{domain}.po"; + + say "Update messages ($locale)" if $self->{verbose}; if ( not -f $pofile ) { - print "File $pofile does not exist\n" if $self->{verbose}; + say "File $pofile does not exist" if $self->{verbose}; $self->create_messages(); } system "$self->{msgmerge} -U $pofile $self->{domain}.pot"; @@ -506,8 +524,11 @@ sub extract_messages { } } - my $xgettext_cmd = "$self->{xgettext} -L Perl --from-code=UTF-8 " . - "-o $Bin/$self->{domain}.pot -D $intranetdir"; + 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"; $xgettext_cmd .= " $_" foreach (@files_to_scan); if (system($xgettext_cmd) != 0) { @@ -522,12 +543,32 @@ sub extract_messages { die "system call failed: $replace_charset_cmd"; } } else { - print "No messages found\n" if $self->{verbose}; + say "No messages found" if $self->{verbose}; return; } return 1; } +sub install_messages { + my ($self) = @_; + + my ($language, $country) = split /-/, $self->{lang}; + my $locale = $language; + if ($country) { + $locale .= '_' . $country; + } + my $podir = "$self->{path_po}/$locale/LC_MESSAGES"; + my $pofile = "$podir/$self->{domain}.po"; + my $mofile = "$podir/$self->{domain}.mo"; + + say "Install messages ($locale)" if $self->{verbose}; + if ( not -f $pofile ) { + say "File $pofile does not exist" if $self->{verbose}; + $self->create_messages(); + } + system "$self->{msgfmt} -o $mofile $pofile"; +} + sub remove_pot { my $self = shift; @@ -539,6 +580,7 @@ sub install { return unless $self->{lang}; $self->install_tmpl($files) unless $self->{pref_only}; $self->install_prefs(); + $self->install_messages(); } -- 2.1.4