2 # BEGIN BPS TAGGED BLOCK {{{
6 # This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
7 # <sales@bestpractical.com>
9 # (Except where explicitly superseded by other copyright notices)
14 # This work is made available to you under the terms of Version 2 of
15 # the GNU General Public License. A copy of that license should have
16 # been provided with this software, but in any event can be snarfed
19 # This work is distributed in the hope that it will be useful, but
20 # WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 # General Public License for more details.
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27 # 02110-1301 or visit their web page on the internet at
28 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
31 # CONTRIBUTION SUBMISSION POLICY:
33 # (The following paragraph is not intended to limit the rights granted
34 # to you to modify and distribute this software under the terms of
35 # the GNU General Public License and is only of importance to you if
36 # you choose to contribute your changes and enhancements to the
37 # community by submitting them to Best Practical Solutions, LLC.)
39 # By intentionally submitting any modifications, corrections or
40 # derivatives to this work, or any other work intended for use with
41 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
42 # you are the copyright holder for those contributions and you grant
43 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
44 # royalty-free, perpetual, license to use, copy, create derivative
45 # works based on those contributions, and sublicense and distribute
46 # those contributions and any derivatives thereof.
48 # END BPS TAGGED BLOCK }}}
54 use File::Temp 'tempdir';
56 use constant PO_DIR => 'share/po';
58 use constant BOUNDARY => 20;
61 warn @_, "\n\n" if @_;
65 rt-message-catalog stats [po-directory]
66 rt-message-catalog clean
67 rt-message-catalog rosetta download-url
68 rt-message-catalog extract [po-file ...]
70 stats: Print stats for each translation.
72 clean: Remove unused and identity translations
74 rosetta: Merge translations from Launchpad's Rosetta; Requires a
75 Launchpad translations export url.
77 extract: Extract message catalogs from source code and report common errors.
79 If passed a specific translation file, only that file is updated.
80 (Not recommended except for debugging.)
87 usage() unless $command;
88 usage("Unknown command '$command'")
89 unless main->can($command);
91 main->can($command)->( @ARGV );
96 my $dir = shift || PO_DIR;
101 foreach my $po_file (<$dir/*.po>) {
102 my $array = Locale::PO->load_file_asarray( $po_file, "utf-8" );
107 foreach my $entry ( splice @$array, 1 ) {
108 next if $entry->obsolete;
109 next if $entry->reference && $entry->reference =~ /NOT FOUND IN SOURCE/;
111 next unless length $entry->dequote( $entry->msgstr );
114 $max = $size if $max < $size;
117 my $width = length($max);
118 foreach my $po_file ( sort { $res{$b} <=> $res{$a} } keys %res ) {
119 my $tr = $res{$po_file};
120 my $perc = int($tr*1000/$max)/10;
121 printf "%-20s %${width}d/%${width}d (%.1f%%)\n", "$po_file:", $tr, $max, $perc;
126 my $dir = shift || PO_DIR;
128 foreach my $po_file (<$dir/*.po>) {
129 my $array = Locale::PO->load_file_asarray( $po_file, "utf-8" );
130 foreach my $entry ( splice @$array, 1 ) {
131 # Replace identical translations with the empty string
132 $entry->msgstr("") if $entry->msgstr eq $entry->msgid;
134 # Skip NOT FOUND IN SOURCE entries
135 next if $entry->obsolete;
136 next if $entry->reference && $entry->reference =~ /NOT FOUND IN SOURCE/;
138 push @$array, $entry;
140 Locale::PO->save_file_fromarray($po_file, $array, "utf-8");
145 my $url = shift or die 'must provide Rosetta download url or directory with new po files';
148 if ( $url =~ m{^[a-z]+://} ) {
150 my ($fname) = $url =~ m{([^/]+)$};
152 print "Downloading $url\n";
154 LWP::Simple::getstore($url => "$dir/$fname");
156 print "Extracting $dir/$fname\n";
157 require Archive::Extract;
158 my $ae = Archive::Extract->new(archive => "$dir/$fname");
159 my $ok = $ae->extract( to => $dir );
161 elsif ( -e $url && -d _ ) {
165 die "Is not URL or directory: '$url'";
168 my @files = ( <$dir/*/*/*.po>, <$dir/*/*.po>, <$dir/*.po> );
170 print STDERR "No files in $dir/rt/*.po and $dir/*.po\n";
174 for my $file ( @files ) {
175 my ($lang) = $file =~ m/([\w_]+)\.po/;
176 my $fn_orig = PO_DIR . "/$lang.po";
178 my $load_from = $fn_orig;
179 $load_from = PO_DIR . "/rt.pot" unless -e $load_from;
180 my $orig = Locale::PO->load_file_ashash( $fn_orig, "utf-8" );
182 print "$file -> $fn_orig\n";
184 my $rosetta = Locale::PO->load_file_asarray( $file, "utf-8" );
186 # We're merging in the current hash as fallbacks for the rosetta hash
188 foreach my $entry ( splice @$rosetta, 1 ) {
189 # Skip no longer in source entries
190 next if $entry->obsolete;
191 next if $entry->reference && $entry->reference =~ /NOT FOUND IN SOURCE/;
193 # Update to what the old po file had, if we have nothing
194 my $oldval = $orig->{$entry->msgid};
195 if (not length $entry->dequote($entry->msgstr) and $oldval) {
196 $entry->msgstr($oldval->dequote($oldval->msgstr));
199 # Replace identical translations with the empty string
200 $entry->msgstr("") if $entry->msgstr eq $entry->msgid;
202 # Drop "fuzzy" information
203 $entry->fuzzy_msgctxt(undef);
204 $entry->fuzzy_msgid(undef);
205 $entry->fuzzy_msgid_plural(undef);
207 $translated++ if length $entry->dequote($entry->msgstr);
208 push @$rosetta, $entry;
211 my $perc = int($translated/(@$rosetta - 1) * 100 + 0.5);
212 if ( $perc < BOUNDARY and $lang !~ /^en(_[A-Z]{2})?$/) {
217 Locale::PO->save_file_fromarray($fn_orig, $rosetta, "utf-8");
223 system($^X, 'devel/tools/extract-message-catalog', @_);