6 # This file is part of LyX, the document processor.
7 # Licence details can be found in the file COPYING.
9 # author: Michael Gerz, michael.gerz@teststep.org
17 pocheck.pl [-acmpqst] po_file [po_file] ...
19 This script performs some consistency checks on po files.
21 We check for everything listed here, unless one or more of these
22 options is given, in which case we checks only for those requested.
23 -a: Check arguments, like %1\$s
24 -c: Check for colons at end
25 -m: Check for menu shortcuts
26 -p: Check for period at end
27 -q: Check Qt shortcuts
28 -s: Check for space at end
29 -t: Check for uniform translation
30 These options can be given with or without other options.
31 -f: Ignore fuzzy translations
32 -w: Only report summary total of errors
33 -i: Silent mode, report only errors
37 getopts(":hacfmpqstwi", \%options);
39 if (defined($options{h})) {
44 my $only_total = defined($options{w});
45 delete $options{w} if $only_total;
46 my $ignore_fuzzy = defined($options{f});
47 delete $options{f} if $ignore_fuzzy;
48 my $silent_mode = defined($options{i});
49 delete $options{i} if $silent_mode;
51 my $check_args = (!%options or defined($options{a}));
52 my $check_colons = (!%options or defined($options{c}));
53 my $check_spaces = (!%options or defined($options{s}));
54 my $check_periods = (!%options or defined($options{p}));
55 my $check_qt = (!%options or defined($options{q}));
56 my $check_menu = (!%options or defined($options{m}));
57 my $check_trans = (!%options or defined($options{t}));
63 foreach my $pofilename ( @ARGV ) {
66 print "Processing po file '$pofilename'...\n";
69 open( INPUT, "<$pofilename" )
70 || die "Cannot read po file '$pofilename'";
75 keys( %trans ) = 10000;
77 my $noOfLines = $#pofile;
82 my ($msgid, $msgstr, $more);
84 while ($i <= $noOfLines) {
86 ( $msgid ) = ( $pofile[$i] =~ m/^msgid "(.*)"/ );
90 my $previous = $pofile[$i - 2];
91 next if $previous =~ m/#,.*fuzzy/;
94 # some msgid's are more than one line long, so add those.
95 while ( ( $more ) = $pofile[$i] =~ m/^"(.*)"/ ) {
96 $msgid = $msgid . $more;
100 # now look for the associated msgstr.
101 until ( ( $msgstr ) = ( $pofile[$i] =~ m/^msgstr "(.*)"/ ) ) { $i++; };
103 # again collect any extra lines.
104 while ( ( $i <= $noOfLines ) &&
105 ( ( $more ) = $pofile[$i] =~ m/^"(.*)"/ ) ) {
106 $msgstr = $msgstr . $more;
110 # nothing to do if one of them is empty.
111 # (surely that is always $msgstr?)
112 next if ($msgid eq "" or $msgstr eq "");
114 # discard [[...]] from the end of msgid, this is used only as hint to translation
115 $msgid =~ s/\[\[.*\]\]$//;
117 # Check for matching %1$s, etc.
119 my @argstrs = ( $msgid =~ m/%(\d)\$s/g );
122 foreach my $arg (@argstrs) { $n = $arg if $arg > $n; }
124 print "$pofilename, line $linenum: Problem finding arguments in:\n $msgid!\n"
126 ++$bad{"Missing arguments"};
129 foreach my $i (1..$n) {
130 my $arg = "%$i\\\$s";
131 if ( $msgstr !~ m/$arg/ ) {
132 print "$pofilename, line $linenum: Missing argument `$arg'\n '$msgid' ==> '$msgstr'\n"
134 ++$bad{"Missing arguments"};
143 # Check colon at the end of a message
144 if ( ( $msgid =~ m/: *(\|.*)?$/ ) != ( $msgstr =~ m/: *(\|.*)?$/ ) ) {
145 print "Line $linenum: Missing or unexpected colon:\n '$msgid' => '$msgstr'\n"
147 ++$bad{"Bad colons"};
152 if ($check_periods) {
153 # Check period at the end of a message; uncomment code if you are paranoid
154 if ( ( $msgid =~ m/\. *(\|.*)?$/ ) != ( $msgstr =~ m/\. *(\|.*)?$/ ) ) {
155 print "Line $linenum: Missing or unexpected period:\n '$msgid' => '$msgstr'\n"
157 ++$bad{"Bad periods"};
163 # Check space at the end of a message
164 if ( ( $msgid =~ m/ *?(\|.*)?$/ ) != ( $msgstr =~ m/ *?(\|.*)?$/ ) ) {
165 print "Line $linenum: Missing or unexpected space:\n '$msgid' => '$msgstr'\n"
167 ++$bad{"Bad spaces"};
173 # Check for "&" shortcuts
174 if ( ( $msgid =~ m/&[^ ]/ ) != ( $msgstr =~ m/&[^ ]/ ) ) {
175 print "Line $linenum: Missing or unexpected Qt shortcut:\n '$msgid' => '$msgstr'\n"
177 ++$bad{"Bad Qt shortcuts"};
183 # Check for "|..." shortcuts
184 if ( ( $msgid =~ m/\|[^ ]/ ) != ( $msgstr =~ m/\|[^ ]/ ) ) {
185 print "Line $linenum: Missing or unexpected menu shortcut:\n '$msgid' => '$msgstr'\n"
187 ++$bad{"Bad menu shortcuts"};
192 next unless $check_trans;
194 # we now collect these translations in a hash.
195 # this will allow us to check below if we have translated
196 # anything more than one way.
197 my $msgid_clean = lc($msgid);
198 my $msgstr_clean = lc($msgstr);
200 $msgid_clean =~ s/(.*)\|.*?$/$1/; # strip menu shortcuts
201 $msgstr_clean =~ s/(.*)\|.*?$/$1/;
202 $msgid_clean =~ s/&([^ ])/$1/; # strip Qt shortcuts
203 $msgstr_clean =~ s/&([^ ])/$1/;
205 # this is a hash of hashes. the keys of the outer hash are
206 # cleaned versions of ORIGINAL strings. the keys of the inner hash
207 # are the cleaned versions of their TRANSLATIONS. The value for the
208 # inner hash is an array of the orignal string and translation.
209 $trans{$msgid_clean}{$msgstr_clean} = [ $msgid, $msgstr, $linenum ];
213 foreach $msgid ( keys %trans ) {
214 # so $ref is a reference to the inner hash.
215 my $ref = $trans{$msgid};
216 # @msgstrkeys is an array of the keys of that inner hash.
217 my @msgstrkeys = keys %$ref;
219 # do we have more than one such key?
220 if ( $#msgstrkeys > 0 ) {
222 print "Different translations for '$msgid':\n";
223 foreach $msgstr ( @msgstrkeys ) {
224 print "Line $ref->{$msgstr}[2]: '" .
225 $ref->{$msgstr}[0] . "' => '" .
226 $ref->{$msgstr}[1] . "'\n";
229 ++$bad{"Inconsistent translations"};
236 while (my ($k, $v) = each %bad) { print "$k: $v\n"; }
237 if (scalar(keys %bad) > 1) {
238 print "Total warnings: $warn\n";
241 print "No warnings!\n";
245 $total_warn += $warn;
248 exit ($total_warn > 0);