source: git/lib/po-to-msg.pl @ a7d4233

line_contentsproj-api-updatestereo
Last change on this file since a7d4233 was 39d2fc7, checked in by Olly Betts <olly@…>, 7 years ago

lib/po-to-msg.pl: Report file and line number for warnings.

  • Property mode set to 100755
File size: 7.8 KB
Line 
1#!/usr/bin/perl -w
2require 5.008;
3use bytes;
4use strict;
5use Locale::PO;
6
7use integer;
8
9# Magic identifier (12 bytes)
10my $magic = "Svx\nMsg\r\n\xfe\xff\0";
11# Designed to be corrupted by ASCII ftp, top bit stripping (or
12# being used for parity).  Contains a zero byte so more likely
13# to be flagged as data (e.g. by perl's "-B" test).
14
15my $srcdir = $0;
16$srcdir =~ s!/[^/]+$!!;
17
18my $major = 0;
19my $minor = 8;
20
21# File format (multi-byte numbers in network order (bigendian)):
22# 12 bytes: Magic identifier
23# 1 byte:   File format major version number (0)
24# 1 byte:   File format minor version number (8)
25# 2 bytes:  Number of messages (N)
26# 4 bytes:  Offset from XXXX to end of file
27# XXXX:
28# N*:
29# <message> NUL
30
31my %msgs = ();
32${$msgs{'en'}}[0] = '©';
33
34# my %uses = ();
35
36my $file;
37
38my %n = ();
39my %loc = ();
40$file = "$srcdir/survex.pot";
41my $num_list = Locale::PO->load_file_asarray($file);
42foreach my $po_entry (@{$num_list}) {
43    my $ref = $po_entry->reference;
44    (defined $ref && $ref =~ /^n:(\d+)$/m) or next;
45    my $msgno = $1;
46    my $key = $po_entry->msgid;
47    my $msg = c_unescape($po_entry->dequote($key));
48    my $where = $file . ":" . $po_entry->loaded_line_number;
49    ${$loc{'en'}}[$msgno] = $where;
50    if (${$msgs{'en'}}[$msgno]) {
51        print STDERR "$where: warning: already had message $msgno for language 'en'\n";
52    }
53    ${$msgs{'en'}}[$msgno] = $msg;
54    ++$n{$msgno};
55}
56my $last = 0;
57for (sort { $a <=> $b } keys %n) {
58    if ($_ > $last + 1) {
59        print STDERR "$file: Unused msg numbers: ", join(" ", $last + 1 .. $_ - 1), "\n";
60    }
61    $last = $_;
62}
63print STDERR "$file: Last used msg number: $last\n";
64%n = ();
65
66for my $po_file (@ARGV) {
67    my $language = $po_file;
68    $language =~ s/\.po$//;
69
70    $file = "$srcdir/$po_file";
71    my $po_hash = Locale::PO->load_file_ashash($file);
72
73    foreach my $po_entry (@{$num_list}) {
74        my $ref = $po_entry->reference;
75        (defined $ref && $ref =~ /^n:(\d+)$/m) or next;
76        my $msgno = $1;
77        my $key = $po_entry->msgid;
78        my $ent = $$po_hash{$key};
79        my $where = $file . ":" . $po_entry->loaded_line_number;
80        ${$loc{$language}}[$msgno] = $where;
81        if (defined $ent) {
82            my $msg = c_unescape($po_entry->dequote($ent->msgstr));
83            next if $msg eq '';
84            if (${$msgs{$language}}[$msgno]) {
85                print STDERR "$where: warning: already had message $msgno for language $language\n";
86            }
87            ${$msgs{$language}}[$msgno] = $msg;
88        }
89    }
90}
91
92my $lang;
93my @langs = sort grep ! /_\*$/, keys %msgs;
94
95my $num_msgs = -1;
96foreach $lang (@langs) {
97   my $aref = $msgs{$lang};
98   $num_msgs = scalar @$aref if scalar @$aref > $num_msgs;
99}
100
101foreach $lang (@langs) {
102   my $fnm = $lang;
103   $file = "$srcdir/$lang.po";
104   $fnm =~ s/(_.*)$/\U$1/;
105   open OUT, ">$fnm.msg" or die $!;
106
107   my $aref = $msgs{$lang};
108
109   my $parentaref;
110   my $mainlang = $lang;
111   $parentaref = $msgs{$mainlang} if $mainlang =~ s/_.*$//;
112
113   print OUT $magic or die $!;
114   print OUT pack("CCn", $major, $minor, $num_msgs) or die $!;
115
116   my $buff = '';
117   my $missing = 0;
118   my $retranslate = 0;
119
120   for my $n (0 .. $num_msgs - 1) {
121      my $warned = 0;
122      my $msg = $$aref[$n];
123      if (!defined $msg) {
124         $msg = $$parentaref[$n] if defined $parentaref;
125         if (!defined $msg) {
126            $msg = ${$msgs{'en'}}[$n];
127            # don't report if we have a parent (as the omission will be
128            # reported there)
129            if (defined $msg && $msg ne '' && !defined $parentaref) {
130               ++$missing;
131               $warned = 1;
132            } else {
133               $msg = '' if !defined $msg;
134            }
135         }
136      } else {
137         if ($lang ne 'en') {
138             sanity_check("Message $n in language $lang", $msg, ${$msgs{'en'}}[$n], ${$loc{$lang}}[$n]);
139         }
140      }
141      $buff .= $msg . "\0";
142   }
143
144   print OUT pack('N',length($buff)), $buff or die $!;
145   close OUT or die $!;
146
147   if ($missing || $retranslate) {
148       print STDERR "Warning: ";
149       if ($missing) {
150           print STDERR "$file: $missing missing message(s)";
151           if ($retranslate) {
152               print STDERR " and $retranslate requiring retranslation";
153           }
154       } else {
155           print STDERR "$file: $retranslate message(s) requiring retranslation";
156       }
157       print STDERR " for $lang\n";
158   }
159}
160
161sub sanity_check {
162   my ($what, $msg, $orig, $where) = @_;
163   # FIXME: Only do this if the message has the "c-format" flag.
164   # check printf-like specifiers match
165   # allow valid printf specifiers, or %<any letter> to support strftime
166   # and other printf-like formats.
167   my @pcent_m = grep /\%/, split /(%(?:[-#0 +'I]*(?:[0-9]*|\*|\*m\$)(?:\.[0-9]*)?(?:hh|ll|[hlLqjzt])?[diouxXeEfFgGaAcsCSpn]|[a-zA-Z]))/, $msg;
168   my @pcent_o = grep /\%/, split /(%(?:[-#0 +'I]*(?:[0-9]*|\*|\*m\$)(?:\.[0-9]*)?(?:hh|ll|[hlLqjzt])?[diouxXeEfFgGaAcsCSpn]|[a-zA-Z]))/, $orig;
169   while (scalar @pcent_m || scalar @pcent_o) {
170       if (!scalar @pcent_m) {
171           print STDERR "$where: warning: $what misses out \%spec $pcent_o[0]\n";
172       } elsif (!scalar @pcent_o) {
173           print STDERR "$where: warning: $what has extra \%spec $pcent_m[0]\n";
174       } elsif ($pcent_m[0] ne $pcent_o[0]) {
175           print STDERR "$where: warning: $what has \%spec $pcent_m[0] instead of $pcent_o[0]\n";
176       }
177       pop @pcent_m;
178       pop @pcent_o;
179   }
180
181   # Check for missing (or added) ellipses (...)
182   if ($msg =~ /\.\.\./ && $orig !~ /\.\.\./) {
183       print STDERR "$where: warning: $what has ellipses but original doesn't\n";
184   } elsif ($msg !~ /\.\.\./ && $orig =~ /\.\.\./) {
185       print STDERR "$where: warning: $what is missing ellipses\n";
186   }
187
188   # Check for missing (or added) menu shortcut (&)
189   if ($msg =~ /\&[A-Za-z\xc2-\xf4]/ && $orig !~ /\&[A-Za-z]/) {
190       print STDERR "$where: warning: $what has menu shortcut but original doesn't\n";
191   } elsif ($msg !~ /\&[A-Za-z\xc2-\xf4]/ && $orig =~ /\&[A-Za-z]/) {
192       print STDERR "$where: warning: $what is missing menu shortcut\n";
193   }
194
195   # Check for missing (or added) double quotes (“ and ”)
196   if (scalar($msg =~ s/(?:“|»)/$&/g) != scalar($orig =~ s/“/$&/g)) {
197       print STDERR "$where: warning: $what has different numbers of “\n";
198       print STDERR "$orig\n$msg\n\n";
199   }
200   if (scalar($msg =~ s/(?:”|«)/$&/g) != scalar($orig =~ s/”/$&/g)) {
201       print STDERR "$where: warning: $what has different numbers of ”\n";
202       print STDERR "$orig\n$msg\n\n";
203   }
204
205   # Check for missing (or added) menu accelerator "##"
206   if ($msg =~ /\#\#/ && $orig !~ /\#\#/) {
207       print STDERR "$where: warning: $what has menu accelerator but original doesn't\n";
208   } elsif ($msg !~ /\#\#/ && $orig =~ /\#\#/) {
209       print STDERR "$where: warning: $what is missing menu accelerator\n";
210   } elsif ($orig =~ /\#\#(.*)/) {
211       my $acc_o = $1;
212       my ($acc_m) = $msg =~ /\#\#(.*)/;
213       if ($acc_o ne $acc_m) {
214           print STDERR "$where: warning: $what has menu accelerator $acc_m instead of $acc_o\n";
215       }
216   }
217
218   # Check for missing (or added) menu accelerator "\t"
219   if ($msg =~ /\t/ && $orig !~ /\t/) {
220       print STDERR "$where: warning: $what has menu accelerator but original doesn't\n";
221   } elsif ($msg !~ /\t/ && $orig =~ /\t/) {
222       print STDERR "$where: warning: $what is missing menu accelerator\n";
223   } elsif ($orig =~ /\t(.*)/) {
224       my $acc_o = $1;
225       my ($acc_m) = $msg =~ /\t(.*)/;
226       if ($acc_o ne $acc_m) {
227           print STDERR "$where: warning: $what has menu accelerator $acc_m instead of $acc_o\n";
228       }
229   }
230}
231
232sub c_unescape {
233   my $str = shift @_;
234   $str =~ s/\\(x..|0|[0-7][0-7][0-7]|.)/&c_unescape_char($1)/ge;
235   return $str;
236}
237
238sub c_unescape_char {
239    my $ch = shift @_;
240    if ($ch eq '0' || $ch eq 'x00' || $ch eq '000') {
241        print STDERR "Nul byte in translation! (\\$ch)\n";
242        exit(1);
243    }
244    return $ch if $ch eq '"' || $ch eq '\\';
245    return "\n" if $ch eq "n";
246    return "\t" if $ch eq "t";
247    return "\r" if $ch eq "r";
248    return "\f" if $ch eq "f";
249    return chr(hex(substr($ch,1))) if $ch =~ /^x../;
250    return chr(oct($ch)) if $ch =~ /^[0-7][0-7][0-7]/;
251    print STDERR "Unknown C-escape in translation! (\\$ch)\n";
252    exit(1);
253}
Note: See TracBrowser for help on using the repository browser.