| LEFT | RIGHT | 
|---|
| (no file at all) |  | 
| 1 # This Source Code Form is subject to the terms of the Mozilla Public |  | 
| 2 # License, v. 2.0. If a copy of the MPL was not distributed with this |  | 
| 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |  | 
| 4 |  | 
| 5 package LocaleTester; |  | 
| 6 |  | 
| 7 use strict; |  | 
| 8 use warnings; |  | 
| 9 |  | 
| 10 my %keepAccessKeys = map {$_ => $_} ( |  | 
| 11   'ja-JP', |  | 
| 12   'ja', |  | 
| 13   'ko-KR', |  | 
| 14   'ko', |  | 
| 15   'zh-CN', |  | 
| 16   'zh-TW', |  | 
| 17 ); |  | 
| 18 |  | 
| 19 my @placeholders = ( |  | 
| 20   '?1?', |  | 
| 21   '?2?', |  | 
| 22   '?3?', |  | 
| 23   '?4?', |  | 
| 24   '?5?', |  | 
| 25   '?6?', |  | 
| 26   '?7?', |  | 
| 27   '?8?', |  | 
| 28   '?9?', |  | 
| 29   '--', |  | 
| 30   '%S', |  | 
| 31   '[link]', |  | 
| 32   '[/link]', |  | 
| 33 ); |  | 
| 34 |  | 
| 35 sub testLocales |  | 
| 36 { |  | 
| 37   my %params = @_; |  | 
| 38   die "Need at least one locale path to work on" unless exists($params{paths}) &
     & %{$params{paths}}; |  | 
| 39   $params{mustDiffer} = [] unless exists($params{mustDiffer}); |  | 
| 40   $params{mustEqual} = [] unless exists($params{mustEqual}); |  | 
| 41   $params{ignoreUntranslated} = [] unless exists($params{ignoreUntranslated}); |  | 
| 42   $params{lengthRestrictions} = {} unless exists($params{lengthRestrictions}); |  | 
| 43 |  | 
| 44   my @locales = sort {$a cmp $b} (exists($params{locales}) && @{$params{locales}
     } ? @{$params{locales}} : makeLocaleList($params{paths})); |  | 
| 45 |  | 
| 46   my $referenceLocale = readLocaleFiles($params{paths}, "en-US"); |  | 
| 47 |  | 
| 48   foreach my $locale (@locales) |  | 
| 49   { |  | 
| 50     my $currentLocale = $locale eq "en-US" ? $referenceLocale : readLocaleFiles(
     $params{paths}, $locale); |  | 
| 51 |  | 
| 52     compareLocales($locale, $currentLocale, $referenceLocale) unless $currentLoc
     ale == $referenceLocale; |  | 
| 53 |  | 
| 54     foreach my $entry (@{$params{mustDiffer}}) |  | 
| 55     { |  | 
| 56       my %values = (); |  | 
| 57       foreach my $key (@$entry) |  | 
| 58       { |  | 
| 59         my ($dir, $file, $name) = split(/:/, $key); |  | 
| 60         next unless exists($currentLocale->{"$dir:$file"}) && exists($currentLoc
     ale->{"$dir:$file"}{$name}) && $currentLocale->{"$dir:$file"}{$name} =~ /\S/; |  | 
| 61         my $value = lc($currentLocale->{"$dir:$file"}{$name}); |  | 
| 62 |  | 
| 63         print "$locale: Values for '$values{$value}' and '$key' are identical, m
     ust differ\n" if exists $values{$value}; |  | 
| 64         $values{$value} = $key; |  | 
| 65       } |  | 
| 66     } |  | 
| 67 |  | 
| 68     foreach my $entry (@{$params{mustEqual}}) |  | 
| 69     { |  | 
| 70       my $stdValue; |  | 
| 71       my $stdName; |  | 
| 72       foreach my $key (@$entry) |  | 
| 73       { |  | 
| 74         my ($dir, $file, $name) = split(/:/, $key); |  | 
| 75         next unless exists($currentLocale->{"$dir:$file"}) && exists($currentLoc
     ale->{"$dir:$file"}{$name}); |  | 
| 76         my $value = lc($currentLocale->{"$dir:$file"}{$name}); |  | 
| 77 |  | 
| 78         $stdValue = $value unless defined $stdValue; |  | 
| 79         $stdName = $key unless defined $stdName; |  | 
| 80         print "$locale: Values for '$stdName' and '$key' differ, must be equal\n
     " if $value ne $stdValue; |  | 
| 81       } |  | 
| 82     } |  | 
| 83 |  | 
| 84     foreach my $key (keys %{$params{lengthRestrictions}}) |  | 
| 85     { |  | 
| 86       my $maxLength = $params{lengthRestrictions}{$key}; |  | 
| 87       my ($dir, $file, $name) = split(/:/, $key); |  | 
| 88       print "$locale: Value of '$key' is too long, must not be longer than $maxL
     ength characters\n" if exists($currentLocale->{"$dir:$file"}) && exists($current
     Locale->{"$dir:$file"}{$name}) && length($currentLocale->{"$dir:$file"}{$name}) 
     > $maxLength; |  | 
| 89     } |  | 
| 90 |  | 
| 91     foreach my $file (keys %$currentLocale) |  | 
| 92     { |  | 
| 93       my $fileData = $currentLocale->{$file}; |  | 
| 94       foreach my $key (keys %$fileData) |  | 
| 95       { |  | 
| 96         if (($key =~ /\.accesskey$/ || $key =~ /\.key$/) && length($fileData->{$
     key}) != 1) |  | 
| 97         { |  | 
| 98           print "$locale: Length of accesskey '$file:$key' isn't 1 character\n"; |  | 
| 99         } |  | 
| 100 |  | 
| 101         if ($key =~ /\.accesskey$/) |  | 
| 102         { |  | 
| 103           if (exists($keepAccessKeys{$locale})) |  | 
| 104           { |  | 
| 105             if (exists($referenceLocale->{$file}{$key}) && lc($fileData->{$key})
      ne lc($referenceLocale->{$file}{$key})) |  | 
| 106             { |  | 
| 107               print "$locale: Accesskey '$file:$key' should be the same as in th
     e reference locale\n"; |  | 
| 108             } |  | 
| 109           } |  | 
| 110           else |  | 
| 111           { |  | 
| 112             my $labelKey = $key; |  | 
| 113             $labelKey =~ s/\.accesskey$/.label/; |  | 
| 114             if (exists($fileData->{$labelKey}) && $fileData->{$labelKey} !~ /\Q$
     fileData->{$key}/i) |  | 
| 115             { |  | 
| 116               print "$locale: Accesskey '$file:$key' not found in the correspond
     ing label '$file:$labelKey'\n"; |  | 
| 117             } |  | 
| 118           } |  | 
| 119         } |  | 
| 120 |  | 
| 121         if ($currentLocale != $referenceLocale && $locale ne "en-GB" && exists($
     referenceLocale->{$file}{$key}) && length($fileData->{$key}) > 1 && $fileData->{
     $key} eq $referenceLocale->{$file}{$key}) |  | 
| 122         { |  | 
| 123           my $ignore = 0; |  | 
| 124           foreach my $re (@{$params{ignoreUntranslated}}) |  | 
| 125           { |  | 
| 126             $ignore = 1 if "$file:$key" =~ $re; |  | 
| 127           } |  | 
| 128           print "$locale: Value of '$file:$key' is the same as in the reference 
     locale, probably an untranslated string\n" unless $ignore; |  | 
| 129         } |  | 
| 130 |  | 
| 131         if ($currentLocale != $referenceLocale && exists($referenceLocale->{$fil
     e}{$key})) |  | 
| 132         { |  | 
| 133           foreach my $placeholder (@placeholders) |  | 
| 134           { |  | 
| 135             print "$locale: Placeholder '$placeholder' missing in '$file:$key'\n
     " if index($referenceLocale->{$file}{$key}, $placeholder) >= 0 && index($current
     Locale->{$file}{$key}, $placeholder) < 0; |  | 
| 136           } |  | 
| 137         } |  | 
| 138       } |  | 
| 139     } |  | 
| 140   } |  | 
| 141 } |  | 
| 142 |  | 
| 143 sub makeLocaleList |  | 
| 144 { |  | 
| 145   my $paths = shift; |  | 
| 146 |  | 
| 147   my %locales = (); |  | 
| 148   foreach my $dir (keys %$paths) |  | 
| 149   { |  | 
| 150     opendir(local* DIR, $paths->{$dir}) or next; |  | 
| 151     my @locales = grep {!/[^\w\-]/ && !-e("$paths->{$dir}/$_/.incomplete")} read
     dir(DIR); |  | 
| 152     $locales{$_} = 1 foreach @locales; |  | 
| 153     closedir(DIR); |  | 
| 154   } |  | 
| 155   return keys %locales; |  | 
| 156 } |  | 
| 157 |  | 
| 158 sub readFile |  | 
| 159 { |  | 
| 160   my $file = shift; |  | 
| 161 |  | 
| 162   open(local *FILE, "<", $file) || die "Could not read file '$file'"; |  | 
| 163   binmode(FILE); |  | 
| 164   local $/; |  | 
| 165   my $result = <FILE>; |  | 
| 166   close(FILE); |  | 
| 167 |  | 
| 168   print "Byte Order Mark found in file '$file'\n" if $result =~ /\xEF\xBB\xBF/; |  | 
| 169   print "File '$file' is not valid UTF-8\n" unless (utf8::decode($result)); |  | 
| 170 |  | 
| 171   return $result; |  | 
| 172 } |  | 
| 173 |  | 
| 174 sub parseDTDFile |  | 
| 175 { |  | 
| 176   my $file = shift; |  | 
| 177 |  | 
| 178   my %result = (); |  | 
| 179 |  | 
| 180   my $data = readFile($file); |  | 
| 181 |  | 
| 182   my $S = qr/[\x20\x09\x0D\x0A]/; |  | 
| 183   my $Name = qr/[A-Za-z_:][\w.\-:]*/; |  | 
| 184   my $Reference = qr/&$Name;|&#\d+;|&#x[\da-fA-F]+;/; |  | 
| 185   my $PEReference = qr/%$Name;/; |  | 
| 186   my $EntityValue = qr/\"((?:[^%&\"]|$PEReference|$Reference)*)\"|'((?:[^%&']|$P
     EReference|$Reference)*)'/; |  | 
| 187 |  | 
| 188   sub processEntityValue |  | 
| 189   { |  | 
| 190     my $text = shift; |  | 
| 191     $text =~ s/&#(\d+);/chr($1)/ge; |  | 
| 192     $text =~ s/&#x([\da-fA-F]+);/chr(hex($1))/ge; |  | 
| 193     $text =~ s/'/'/g; |  | 
| 194     return $text; |  | 
| 195   } |  | 
| 196 |  | 
| 197   # Remove comments |  | 
| 198   $data =~ s/<!--([^\-]|-[^\-])*-->//gs; |  | 
| 199 |  | 
| 200   # Process entities |  | 
| 201   while ($data =~ /<!ENTITY$S+($Name)$S+$EntityValue$S*>/gs) |  | 
| 202   { |  | 
| 203     my ($name, $value) = ($1, $2 || $3); |  | 
| 204     $result{$name} = processEntityValue($value); |  | 
| 205   } |  | 
| 206 |  | 
| 207   # Remove entities |  | 
| 208   $data =~ s/<!ENTITY$S+$Name$S+$EntityValue$S*>//gs; |  | 
| 209 |  | 
| 210   # Remove spaces |  | 
| 211   $data =~ s/^\s+//gs; |  | 
| 212   $data =~ s/\s+$//gs; |  | 
| 213   $data =~ s/\s+/ /gs; |  | 
| 214 |  | 
| 215   print "Unrecognized data in file '$file': $data\n" if $data ne ''; |  | 
| 216 |  | 
| 217   return \%result; |  | 
| 218 } |  | 
| 219 |  | 
| 220 sub parsePropertiesFile |  | 
| 221 { |  | 
| 222   my $file = shift; |  | 
| 223 |  | 
| 224   my %result = (); |  | 
| 225 |  | 
| 226   my $data = readFile($file); |  | 
| 227   while ($data =~ /^(.*)$/mg) |  | 
| 228   { |  | 
| 229     my $line = $1; |  | 
| 230 |  | 
| 231     # ignore comments |  | 
| 232     next if $line =~ /^\s*[#!]/; |  | 
| 233 |  | 
| 234     if ($line =~ /=/) |  | 
| 235     { |  | 
| 236       my ($key, $value) = split(/=/, $line, 2); |  | 
| 237       $result{$key} = $value; |  | 
| 238     } |  | 
| 239     elsif ($line =~ /\S/) |  | 
| 240     { |  | 
| 241       print "Unrecognized data in file '$file': $line\n"; |  | 
| 242     } |  | 
| 243   } |  | 
| 244   close(FILE); |  | 
| 245 |  | 
| 246   return \%result; |  | 
| 247 } |  | 
| 248 |  | 
| 249 sub readLocaleFiles |  | 
| 250 { |  | 
| 251   my $paths = shift; |  | 
| 252   my $locale = shift; |  | 
| 253 |  | 
| 254   my %result = (); |  | 
| 255   foreach my $dir (keys %$paths) |  | 
| 256   { |  | 
| 257     opendir(local *DIR, "$paths->{$dir}/$locale") or next; |  | 
| 258     foreach my $file (readdir(DIR)) |  | 
| 259     { |  | 
| 260       if ($file =~ /(.*)\.dtd$/) |  | 
| 261       { |  | 
| 262         $result{"$dir:$1"} = parseDTDFile("$paths->{$dir}/$locale/$file"); |  | 
| 263       } |  | 
| 264       elsif ($file =~ /(.*)\.properties$/) |  | 
| 265       { |  | 
| 266         $result{"$dir:$1"} = parsePropertiesFile("$paths->{$dir}/$locale/$file")
     ; |  | 
| 267       } |  | 
| 268     } |  | 
| 269     closedir(DIR); |  | 
| 270   } |  | 
| 271 |  | 
| 272   return \%result; |  | 
| 273 } |  | 
| 274 |  | 
| 275 sub compareLocales |  | 
| 276 { |  | 
| 277   my ($locale, $current, $reference) = @_; |  | 
| 278 |  | 
| 279   my %hasFile = (); |  | 
| 280   foreach my $file (keys %$current) |  | 
| 281   { |  | 
| 282     unless (exists($reference->{$file})) |  | 
| 283     { |  | 
| 284       print "$locale: Extra file '$file'\n"; |  | 
| 285       next; |  | 
| 286     } |  | 
| 287     $hasFile{$file} = 1; |  | 
| 288 |  | 
| 289     my %hasValue = (); |  | 
| 290     foreach my $key (keys %{$current->{$file}}) |  | 
| 291     { |  | 
| 292       unless (exists($reference->{$file}{$key})) |  | 
| 293       { |  | 
| 294         print "$locale: Extra value '$file:$key'\n"; |  | 
| 295         next; |  | 
| 296       } |  | 
| 297       $hasValue{$key} = 1; |  | 
| 298     } |  | 
| 299 |  | 
| 300     foreach my $key (keys %{$reference->{$file}}) |  | 
| 301     { |  | 
| 302       unless (exists($current->{$file}{$key})) |  | 
| 303       { |  | 
| 304         print "$locale: Missing value '$file:$key'\n"; |  | 
| 305         next; |  | 
| 306       } |  | 
| 307     } |  | 
| 308   } |  | 
| 309 |  | 
| 310   foreach my $file (keys %$reference) |  | 
| 311   { |  | 
| 312     unless (exists($current->{$file})) |  | 
| 313     { |  | 
| 314       print "$locale: Missing file '$file'\n"; |  | 
| 315       next; |  | 
| 316     } |  | 
| 317   } |  | 
| 318 } |  | 
| 319 |  | 
| 320 1; |  | 
| LEFT | RIGHT | 
|---|