source: subversion/applications/editors/josm/i18n/i18n.pl @ 26338

Last change on this file since 26338 was 26338, checked in by Dirk Stoecker, 8 years ago

add checks of translation quality

  • Property svn:executable set to *
File size: 9.6 KB
Line 
1#! /usr/bin/perl -w
2
3use utf8;
4use encoding "utf8";
5use Term::ReadKey;
6use Encode;
7
8my $waswarn = 0;
9my $maxcount = 0;
10
11main();
12
13sub getdate
14{
15  my @t=gmtime();
16  return sprintf("%04d-%02d-%02d %02d:%02d+0000",
17  1900+$t[5],$t[4]+1,$t[3],$t[2],$t[1]);
18}
19
20sub loadfiles($@)
21{
22  my $desc;
23  my $all;
24  my ($lang,@files) = @_;
25  foreach my $file (@files)
26  {
27    die "Could not open file $file." if(!open FILE,"<:utf8",$file);
28    my $linenum = 0;
29
30    my $cnt = -1; # don't count translators info
31    if($file =~ /\/(.._..)\.po$/ || $file =~ /\/(...?)\.po$/)
32    {
33      my $l = $1;
34      ++$lang->{$l};
35      my %postate = (last => "", type => "");
36      my $linenum = 0;
37      print "Reading file $file\n";
38      while(<FILE>)
39      {
40        ++$linenum;
41        my $fn = "$file:$linenum";
42        chomp;
43        if($_ =~ /^#/ || !$_)
44        {
45          checkpo(\%postate, \%all, $l, "line $linenum in $file", $keys, 1);
46          $postate{fuzzy} = 1 if ($_ =~ /fuzzy/);
47        }
48        elsif($_ =~ /^"(.*)"$/) {$postate{last} .= $1;}
49        elsif($_ =~ /^(msg.+) "(.*)"$/)
50        {
51          my ($n, $d) = ($1, $2);
52          ++$cnt if $n eq "msgid";
53          my $new = !${postate}{fuzzy} && (($n eq "msgid" && $postate{type} ne "msgctxt") || ($n eq "msgctxt"));
54          checkpo(\%postate, \%all, $l, "line $linenum in $file", $keys, $new);
55          $postate{last} = $d;
56          $postate{type} = $n;
57          $postate{src} = $fn if $new;
58        }
59        else
60        {
61          die "Strange line $linenum in $file: $_.";
62        }
63      }
64      checkpo(\%postate, \%all, $l, "line $linenum in $file", $keys, 1);
65    }
66    else
67    {
68      die "File format not supported for file $file.";
69    }
70    $maxcount = $cnt if $cnt > $maxcount;
71    close(FILE);
72  }
73  return %all;
74}
75
76my $alwayspo = 0;
77my $alwaysup = 0;
78my $noask = 0;
79my $conflicts;
80sub copystring($$$$$$$)
81{
82  my ($data, $en, $l, $str, $txt, $context, $ispo) = @_;
83
84  $en = "___${context}___$en" if $context;
85
86  if(exists($data->{$en}{$l}) && $data->{$en}{$l} ne $str)
87  {
88    return if !$str;
89    if($l =~ /^_/)
90    {
91      $data->{$en}{$l} .= ";$str" if !($data->{$en}{$l} =~ /$str/);
92    }
93    elsif(!$data->{$en}{$l})
94    {
95      $data->{$en}{$l} = $str;
96    }
97    else
98    {
99
100      my $f = $data->{$en}{_file} || "";
101      $f = ($f ? "$f;".$data->{$en}{"_src.$l"} : $data->{$en}{"_src.$l"}) if $data->{$en}{"_src.$l"};
102      my $isotherpo = ($f =~ /\.po\:/);
103      my $pomode = ($ispo && !$isotherpo) || (!$ispo && $isotherpo);
104
105      my $mis = "String mismatch for '$en' **$str** ($txt) != **$data->{$en}{$l}** ($f)\n";
106      my $replace = 0;
107
108      if(($conflicts{$l}{$str} || "") eq $data->{$en}{$l}) {}
109      elsif($pomode && $alwaysup) { $replace=$isotherpo; }
110      elsif($pomode && $alwayspo) { $replace=$ispo; }
111      elsif($noask) { print $mis; ++$waswarn; }
112      else
113      {
114        ReadMode 4; # Turn off controls keys
115        my $arg = "(l)eft, (r)ight";
116        $arg .= ", (p)o, (u)pstream[ts/mat], all p(o), all up(s)tream" if $pomode;
117        $arg .= ", e(x)it: ";
118        print "$mis$arg";
119        while((my $c = getc()))
120        {
121          if($c eq "l") { $replace=1; }
122          elsif($c eq "r") {}
123          elsif($c eq "p" && $pomode) { $replace=$ispo; }
124          elsif($c eq "u" && $pomode) { $replace=$isotherpo; }
125          elsif($c eq "o" && $pomode) { $alwayspo = 1; $replace=$ispo; }
126          elsif($c eq "s" && $pomode) { $alwaysup = 1; $replace=$isotherpo; }
127          elsif($c eq "x") { $noask = 1; ++$waswarn; }
128          else { print "\n$arg"; next; }
129          last;
130        }
131        print("\n");
132        ReadMode 0; # Turn on controls keys
133      }
134      if(!$noask)
135      {
136        if($replace)
137        {
138          $data->{$en}{$l} = $str;
139          $conflicts{$l}{$data->{$en}{$l}} = $str;
140        }
141        else
142        {
143          $conflicts{$l}{$str} = $data->{$en}{$l};
144        }
145      }
146    }
147  }
148  else
149  {
150    $data->{$en}{$l} = $str;
151  }
152}
153
154sub checkpo($$$$$$)
155{
156  my ($postate, $data, $l, $txt, $keys, $new) = @_;
157
158  if($postate->{type} eq "msgid") {$postate->{msgid} = $postate->{last};}
159  elsif($postate->{type} eq "msgid_plural") {$postate->{msgid_1} = $postate->{last};}
160  elsif($postate->{type} =~ /^msgstr(\[0\])?$/) {$postate->{msgstr} = $postate->{last};}
161  elsif($postate->{type} =~ /^msgstr\[(.+)\]$/) {$postate->{"msgstr_$1"} = $postate->{last};}
162  elsif($postate->{type} eq "msgctxt") {$postate->{context} = $postate->{last};}
163  elsif($postate->{type}) { die "Strange type $postate->{type} found\n" }
164
165  if($new)
166  {
167    if((!$postate->{fuzzy}) && $postate->{msgstr} && $postate->{msgid})
168    {
169      copystring($data, $postate->{msgid}, $l, $postate->{msgstr},$txt,$postate->{context}, 1);
170      for($i = 1; exists($postate->{"msgstr_$i"}); ++$i)
171      { copystring($data, $postate->{msgid}, "$l.$i", $postate->{"msgstr_$i"},$txt,$postate->{context}, 1); }
172      if($postate->{msgid_1})
173      { copystring($data, $postate->{msgid}, "en.1", $postate->{msgid_1},$txt,$postate->{context}, 1); }
174      copystring($data, $postate->{msgid}, "_src.$l", $postate->{src},$txt,$postate->{context}, 1);
175    }
176    elsif($postate->{msgstr} && !$postate->{msgid})
177    {
178      my %k = ($postate->{msgstr} =~ /(.+?): +(.+?)\\n/g);
179      # take the first one!
180      for $a (sort keys %k)
181      {
182        $keys->{$l}{$a} = $k{$a} if !$keys->{$l}{$a};
183      }
184    }
185    foreach my $k (keys %{$postate})
186    {
187      delete $postate->{$k};
188    }
189    $postate->{type} = $postate->{last} = "";
190  }
191}
192
193sub makestring($)
194{
195  my ($str) = @_;
196  $str =~ s/\\"/"/g;
197  $str =~ s/\\\\/\\/g;
198  $str =~ s/\\n/\n/g;
199  $str = encode("utf8", $str);
200  return $str;
201}
202
203sub checkstring
204{
205  my ($la, $tr, $en, $cnt, $en1) = @_;
206  $tr = makestring($tr);
207  $en = makestring($en);
208  $en1 = makestring($en1) if defined($en1);
209  my $error = 0;
210
211  # Test one - are there single quotes which don't occur twice
212  my $v = $tr;
213  $v =~ s/''//g; # replace all twice occuring single quotes
214  if($v =~ /'/)
215  {
216    #warn "JAVA translation issue for language $la: Mismatching single quotes:\nTranslated text: $tr\n";
217    #$error = 1;
218  }
219  # Test two - check if there are {..} which should not be
220  my @fmt = ();
221  my $fmt;
222  my $fmte;
223  my $fmte1 = "";
224  while($tr =~ /\{(.*?)\}/g) {push @fmt,$1}; $fmt = join("_", sort @fmt); @fmt = ();
225  while($en =~ /\{(.*?)\}/g) {push @fmt,$1}; $fmte = join("_", sort @fmt); @fmt = ();
226  if($en1) {while($en1 =~ /\{(.*?)\}/g) {push @fmt,$1}; $fmte1 = join("_", sort @fmt);}
227  if($fmt ne $fmte && $fmt ne $fmte1)
228  {
229    if(!($fmte eq '0' && $fmt eq "" && $cnt == 1)) # Don't warn when a single value is left for first multi-translation
230    {
231      $cnt == $cnt || 0;
232      warn "JAVA translation issue for language $la ($cnt): Mismatching format entries:\nTranslated text: $tr\nOriginal text: $en\n";
233      $error = 1;
234    }
235  }
236
237  #$tr = "" if($error && $la ne "en");
238
239  return pack("n",length($tr)).$tr;
240}
241
242sub createlang($@)
243{
244  my ($data, @files) = @_;
245  my $maxlen = 0;
246  foreach my $file (@files)
247  {
248    my $len = length($file);
249    $maxlen = $len if $len > $maxlen;
250  }
251  foreach my $file (@files)
252  {
253    my $la;
254    my $cnt = 0;
255    if($file =~ /[-_](.._..)\.lang$/ || $file =~ /^(?:.*\/)?(.._..)\.lang$/ ||
256    $file =~ /[-_](...?)\.lang$/ || $file =~ /^(?:.*\/)?(...?)\.lang$/)
257    {
258      $la = $1;
259    }
260    else
261    {
262      die "Language for file $file unknown.";
263    }
264    die "Could not open outfile $file\n" if !open FILE,">:raw",$file;
265
266    foreach my $en (sort keys %{$data})
267    {
268      next if $data->{$en}{"en.1"};
269      my $val;
270      if($la eq "en")
271      {
272        ++$cnt;
273        $val = $en;
274        $val =~ s/^___(.*)___/_:$1\n/;
275      }
276      else
277      {
278        my $ennoctx = $en;
279        $ennoctx =~ s/^___(.*)___//;
280        $val = (exists($data->{$en}{$la})) ? $data->{$en}{$la} : "";
281        ++$cnt if $val;
282        $val = "" if($ennoctx eq $val);
283      }
284      print FILE checkstring($la, $val, $en);
285    }
286    print FILE pack "n",0xFFFF;
287    foreach my $en (sort keys %{$data})
288    {
289      next if !$data->{$en}{"en.1"};
290      my $num;
291      for($num = 1; exists($data->{$en}{"$la.$num"}); ++$num)
292      { }
293      my $val;
294      if($la eq "en")
295      {
296        ++$cnt;
297        $val = $en;
298        $val =~ s/^___(.*)___/_:$1\n/;
299      }
300      else
301      {
302        $val = (exists($data->{$en}{$la})) ? $data->{$en}{$la} : "";
303        --$num if(!$val);
304        ++$cnt if $val;
305        if($num == 2)
306        {
307          my $ennoctx = $en;
308          $ennoctx =~ s/^___(.*)___//;
309          $num = 0 if $val eq $ennoctx && $data->{$en}{"$la.1"} eq $data->{$en}{"en.1"};
310        }
311      }
312
313      print FILE pack "C",$num;
314      if($num)
315      {
316        print FILE checkstring($la, $val, $en, 1, $data->{$en}{"en.1"});
317        for($num = 1; exists($data->{$en}{"$la.$num"}); ++$num)
318        {
319          print FILE checkstring($la, $data->{$en}{"$la.$num"}, $en, $num+1, $data->{$en}{"en.1"});
320        }
321      }
322    }
323    close FILE;
324    if(!$cnt)
325    {
326      unlink $file;
327      printf "Skipped file %-${maxlen}s: Contained 0 strings out of %5d.\n",$file,$maxcount;
328    }
329    else
330    {
331      printf "Created file %-${maxlen}s: Added %5d strings out of %5d (%5.1f%%).\n",$file,$cnt,$maxcount,,$cnt*100.0/$maxcount;
332    }
333  }
334}
335
336sub main
337{
338  my %lang;
339  my @po;
340  my $basename = shift @ARGV;
341  foreach my $arg (@ARGV)
342  {
343    foreach my $f (glob $arg)
344    {
345      if($f =~ /\*/) { printf "Skipping $f\n"; }
346      elsif($f =~ /\.po$/) { push(@po, $f); }
347      else { die "unknown file extension."; }
348    }
349  }
350  my %data = loadfiles(\%lang,@po);
351  my @clang;
352  foreach my $la (sort keys %lang)
353  {
354    push(@clang, "${basename}$la.lang");
355  }
356  push(@clang, "${basename}en.lang");
357  die "There have been warning. No output.\n" if $waswarn;
358
359  createlang(\%data, @clang);
360}
Note: See TracBrowser for help on using the repository browser.