#! /usr/bin/env perl use strict; use Getopt::Long; use IO::Handle; use Locale::PO; use Encode; sub decompose_po; sub decompose_po_sg; sub compose_po; sub german_handler; sub american_english_handler; sub british_english_handler; sub swiss_german_handler; sub austrian_german_handler; sub display_usage; sub usage_error; my ($option_language, $option_input, $option_output, $option_help, $option_verbose); GetOptions ( 'language=s' => \$option_language, 'input=s' => \$option_input, 'output=s' => \$option_output, 'help' => \$option_help, 'verbose' => \$option_verbose, ) or exit 1; display_usage if $option_help; usage_error "the option '--language' is mandatory." unless $option_language; usage_error "the option '--input' is mandatory." unless $option_input; usage_error "the option '--output' is mandatory." unless $option_output; my @filters; if ($option_language =~ /^de_CH$/) { push @filters, \&swiss_german_handler; } elsif ($option_language =~ /^de_AT$/) { push @filters, \&austrian_german_handler; } elsif ($option_language =~ /^en_GB$/) { push @filters, \&british_english_handler; } elsif ($option_language =~ /^en.*$/) { push @filters, \&american_english_handler; } elsif ($option_language =~ /^de/) { push @filters, \&german_handler; } elsif ($option_language =~ /^bg/) { push @filters, \&german_handler; } my $entries = Locale::PO->load_file_asarray ($option_input, 'utf-8') or die "cannot read file '$option_input': $!\n"; my $alpine_entries; my $german_to_alpine = { Samstag => 'Sonnabend', Januar => "J\xe4nner", Februar => "Feber", }; my $german_to_alpine_re_string = join '|', keys %$german_to_alpine; my $german_to_alpine_re = qr /($german_to_alpine_re_string)/o; autoflush STDERR; print STDERR "$option_language\n"; my $count = 0; my $file_dirty; foreach my $entry (@$entries) { ++$count; print STDERR '.' if 0 == $count % 10 && $option_verbose; next if $entry->obsolete; foreach my $filter (@filters) { $file_dirty = 1 if &$filter ($entry); } } print STDERR " done.\n" if $option_verbose; if ($file_dirty) { Locale::PO->save_file_fromarray($option_output, $entries, 'utf-8') or die "cannot write file '$option_output': $!\n"; } sub decompose_po { my ($entry) = @_; my @msgstrs; if ($entry->msgid_plural) { my $msgstr_n = $entry->msgstr_n; foreach my $num (sort keys %$msgstr_n) { push @msgstrs, $entry->dequote($msgstr_n->{$num}); } } else { @msgstrs = $entry->dequote($entry->msgstr); } return @msgstrs; } sub decompose_po_sg { my ($entry) = @_; my @msgids = $entry->dequote($entry->msgid); push @msgids, $entry->dequote($entry->msgid_plural) if $entry->msgid_plural; return @msgids; } sub compose_po { my ($entry, @msgstrs) = @_; my $dirty; if (@msgstrs > 1) { # Plural form. my $msgstr_n = $entry->msgstr_n; foreach my $num (0 .. @msgstrs) { my $old = $entry->dequote($msgstr_n->{$num}); my $new = $msgstrs[$num]; if ($old ne $new) { $dirty = 1; $msgstr_n->{$num} = $new; } } $entry->msgstr_n($msgstr_n) if $dirty; } else { my $old = $entry->dequote($entry->msgstr); if ($old ne $msgstrs[0]) { $entry->msgstr($msgstrs[0]); $dirty = 1; } } return unless $dirty; return 1; } sub _english_quotes { my ($entry, $country) = @_; my $msgid = $entry->dequote($entry->msgid); return 1 if $msgid eq ''; my @msgstrs = decompose_po $entry; return 1 if length $msgstrs[0]; my $open_quote = $country eq 'US' ? "\x{201c}" : "\x{2018}"; my $close_quote = $country eq 'US' ? "\x{201d}" : "\x{2019}"; my $dirty; undef @msgstrs; foreach my $msg (decompose_po_sg $entry) { $msg =~ s/(\w)'(\w)/$1\x{2019}$2/; $msg =~ s/'(.*?)'/${open_quote}$1${close_quote}/g; push @msgstrs, $msg; } return compose_po $entry, @msgstrs; } sub _german_quotes { my ($entry, $country) = @_; my $msgid = $entry->dequote($entry->msgid); return 1 if $msgid eq ''; my @msgstrs = decompose_po $entry; my %open = ( DE => "\x{201e}", CH => "\x{ab}", ); my %closed = ( DE => "\x{201c}", CH => "\x{bb}", ); my $open_quote = $open{$country} || $open{DE}; my $closed_quote = $closed{$country} || $open{DE}; my $dirty; foreach my $msg (@msgstrs) { $msg =~ s/(\w)'(\w)/$1\x{2019}$2/g; $msg =~ s/(["'])(.*?)\1/${open_quote}$2${closed_quote}/g; $msg =~ s/($open{DE})(.*?)$closed{DE}/${open_quote}$2${closed_quote}/g; } return compose_po $entry, @msgstrs; } sub _swiss_sharp_s { my ($entry, $country) = @_; my $msgid = $entry->dequote($entry->msgid); return 1 if $msgid eq ''; my @msgstrs = decompose_po $entry; my $dirty; foreach my $msg (@msgstrs) { $msg =~ s/\x{df}/ss/; } return compose_po $entry, @msgstrs; } sub american_english_handler { return _english_quotes shift, 'US'; } sub british_english_handler { return _english_quotes shift, 'GB'; } sub _alpine_german { my ($entry) = @_; unless ($alpine_entries) { $alpine_entries = Locale::PO->load_file_asarray('de.po', 'utf8') or die "cannot read file 'de.po': $!\n"; } my $msgid = $entry->dequote($entry->msgid); return unless length $msgid; # Already translated? my @msgstrs = decompose_po $entry; return if length $msgstrs[0]; # Get the German translation for it. undef @msgstrs; foreach my $alpine_entry (@$alpine_entries) { my $alpine_msgid = $alpine_entry->dequote($alpine_entry->msgid); next if $alpine_msgid ne $msgid; my $alpine_msgctxt = $alpine_entry->msgctxt; next if $alpine_msgctxt ne $entry->msgctxt; @msgstrs = decompose_po $alpine_entry; return if !length $msgstrs[0]; } return unless @msgstrs; foreach my $msgstr (@msgstrs) { $msgstr =~ s/$german_to_alpine_re/$german_to_alpine->{$1}/gs; } my $dirty = compose_po $entry, @msgstrs; return if !$dirty; return 1; } sub austrian_german_handler { my ($entry) = @_; my $dirty = _alpine_german $entry, 'AT'; $dirty = 1 if _german_quotes $entry, 'AT'; return if !$dirty; return 1; } sub swiss_german_handler { my ($entry) = @_; my $dirty = _alpine_german $entry, 'CH'; $dirty = 1 if _german_quotes $entry, 'CH'; $dirty = 1 if _swiss_sharp_s $entry, 'CH'; return if !$dirty; return 1; } sub german_handler { return _german_quotes shift, 'DE'; } sub display_usage { print <<EOF; Usage: $0 [OPTIONS] Mandatory arguments to long options, are mandatory to short options, too. -l, --language=LANGUAGE The translations are in language LANGUAGE -i, --input=INPUT Read input from file INPUT -o, --output=OUTPUT Write output to file OUTPUT -h, --help Display this help and exit -v, --verbose Display progress on standard error Reads a PO file and writes it back after some language-dependent cosmetic corrections. EOF } sub usage_error { my $message = shift; if ($message) { $message =~ s/\s+$//; $message = "$0: $message\n"; } else { $message = ''; } die <<EOF; ${message}Usage: $0 [OPTIONS] Try '$0 --help' for more information! EOF }