5 # This file is part of LyX, the document processor.
6 # Licence details can be found in the file COPYING.
8 # author: Michael Gerz, michael.gerz@teststep.org
16 pocheck.pl [-acmpqst] po_file [po_file] ...
18 This script performs some consistency checks on po files.
20 We check for everything listed here, unless one or more of these
21 options is given, in which case we checks only for those requested.
22 -a: Check arguments, like %1\$s
23 -c: Check for colons at end
24 -m: Check for menu shortcuts
25 -p: Check for period at end
26 -q: Check Qt shortcuts
27 -s: Check for space at end
28 -t: Check for uniform translation
29 These options can be given with or without other options.
30 -f: Ignore fuzzy translations
31 -w: Only report summary total of errors
32 -i: Silent mode, report only errors
36 getopts(":hacfmpqstwi", \%options);
38 if (defined($options{h})) {
43 my $only_total = defined($options{w});
44 delete $options{w} if $only_total;
45 my $ignore_fuzzy = defined($options{f});
46 delete $options{f} if $ignore_fuzzy;
47 my $silent_mode = defined($options{i});
48 delete $options{i} if $silent_mode;
50 my $check_args = (!%options or defined($options{a}));
51 my $check_colons = (!%options or defined($options{c}));
52 my $check_spaces = (!%options or defined($options{s}));
53 my $check_periods = (!%options or defined($options{p}));
54 my $check_qt = (!%options or defined($options{q}));
55 my $check_menu = (!%options or defined($options{m}));
56 my $check_trans = (!%options or defined($options{t}));
62 foreach my $pofilename ( @ARGV ) {
65 print "Processing po file '$pofilename'...\n";
68 open( INPUT, "<$pofilename" )
69 || die "Cannot read po file '$pofilename'";
74 keys( %trans ) = 10000;
76 my $noOfLines = $#pofile;
81 my ($msgid, $msgstr, $more);
83 while ($i <= $noOfLines) {
85 ( $msgid ) = ( $pofile[$i] =~ m/^msgid "(.*)"/ );
89 my $previous = $pofile[$i - 2];
90 next if $previous =~ m/#,.*fuzzy/;
93 # some msgid's are more than one line long, so add those.
94 while ( ( $more ) = $pofile[$i] =~ m/^"(.*)"/ ) {
95 $msgid = $msgid . $more;
99 # now look for the associated msgstr.
100 until ( ( $msgstr ) = ( $pofile[$i] =~ m/^msgstr "(.*)"/ ) ) { $i++; };
102 # again collect any extra lines.
103 while ( ( $i <= $noOfLines ) &&
104 ( ( $more ) = $pofile[$i] =~ m/^"(.*)"/ ) ) {
105 $msgstr = $msgstr . $more;
109 # nothing to do if one of them is empty.
110 # (surely that is always $msgstr?)
111 next if ($msgid eq "" or $msgstr eq "");
113 # discard [[...]] from the end of msgid, this is used only as hint to translation
114 $msgid =~ s/\[\[.*\]\]$//;
116 # Check for matching %1$s, etc.
118 my @argstrs = ( $msgid =~ m/%(\d)\$s/g );
121 foreach my $arg (@argstrs) { $n = $arg if $arg > $n; }
123 print "$pofilename, line $linenum: Problem finding arguments in:\n $msgid!\n"
125 ++$bad{"Missing arguments"};
128 foreach my $i (1..$n) {
129 my $arg = "%$i\\\$s";
130 if ( $msgstr !~ m/$arg/ ) {
131 print "$pofilename, line $linenum: Missing argument `$arg'\n '$msgid' ==> '$msgstr'\n"
133 ++$bad{"Missing arguments"};
142 # Check colon at the end of a message
143 if ( ( $msgid =~ m/: *(\|.*)?$/ ) != ( $msgstr =~ m/: *(\|.*)?$/ ) ) {
144 print "Line $linenum: Missing or unexpected colon:\n '$msgid' => '$msgstr'\n"
146 ++$bad{"Bad colons"};
151 if ($check_periods) {
152 # Check period at the end of a message; uncomment code if you are paranoid
153 if ( ( $msgid =~ m/\. *(\|.*)?$/ ) != ( $msgstr =~ m/\. *(\|.*)?$/ ) ) {
154 print "Line $linenum: Missing or unexpected period:\n '$msgid' => '$msgstr'\n"
156 ++$bad{"Bad periods"};
162 # Check space at the end of a message
163 if ( ( $msgid =~ m/ *?(\|.*)?$/ ) != ( $msgstr =~ m/ *?(\|.*)?$/ ) ) {
164 print "Line $linenum: Missing or unexpected space:\n '$msgid' => '$msgstr'\n"
166 ++$bad{"Bad spaces"};
172 # Check for "&" shortcuts
173 if ( ( $msgid =~ m/&[^ ]/ ) != ( $msgstr =~ m/&[^ ]/ ) ) {
174 print "Line $linenum: Missing or unexpected Qt shortcut:\n '$msgid' => '$msgstr'\n"
176 ++$bad{"Bad Qt shortcuts"};
182 # Check for "|..." shortcuts
183 if ( ( $msgid =~ m/\|[^ ]/ ) != ( $msgstr =~ m/\|[^ ]/ ) ) {
184 print "Line $linenum: Missing or unexpected menu shortcut:\n '$msgid' => '$msgstr'\n"
186 ++$bad{"Bad menu shortcuts"};
191 next unless $check_trans;
193 # we now collect these translations in a hash.
194 # this will allow us to check below if we have translated
195 # anything more than one way.
196 my $msgid_clean = lc($msgid);
197 my $msgstr_clean = lc($msgstr);
199 $msgid_clean =~ s/(.*)\|.*?$/$1/; # strip menu shortcuts
200 $msgstr_clean =~ s/(.*)\|.*?$/$1/;
201 $msgid_clean =~ s/&([^ ])/$1/; # strip Qt shortcuts
202 $msgstr_clean =~ s/&([^ ])/$1/;
204 # this is a hash of hashes. the keys of the outer hash are
205 # cleaned versions of ORIGINAL strings. the keys of the inner hash
206 # are the cleaned versions of their TRANSLATIONS. The value for the
207 # inner hash is an array of the orignal string and translation.
208 $trans{$msgid_clean}{$msgstr_clean} = [ $msgid, $msgstr, $linenum ];
212 foreach $msgid ( keys %trans ) {
213 # so $ref is a reference to the inner hash.
214 my $ref = $trans{$msgid};
215 # @msgstrkeys is an array of the keys of that inner hash.
216 my @msgstrkeys = keys %$ref;
218 # do we have more than one such key?
219 if ( $#msgstrkeys > 0 ) {
221 print "Different translations for '$msgid':\n";
222 foreach $msgstr ( @msgstrkeys ) {
223 print "Line $ref->{$msgstr}[2]: '" .
224 $ref->{$msgstr}[0] . "' => '" .
225 $ref->{$msgstr}[1] . "'\n";
228 ++$bad{"Inconsistent translations"};
235 while (my ($k, $v) = each %bad) { print "$k: $v\n"; }
236 if (scalar(keys %bad) > 1) {
237 print "Total warnings: $warn\n";
240 print "No warnings!\n";
244 $total_warn += $warn;
247 exit ($total_warn > 0);