3 # -n do not include custom_ scripts
4 # -v verbose mode, shows progress stuff
6 # this version of locles processes not only all required .pl files
7 # but also all parse_html_templated files.
15 use List::Util qw(first);
19 use File::Slurp qw(slurp);
21 $OUTPUT_AUTOFLUSH = 1;
32 my $locales_dir = "$basedir/locale/$lang";
33 my $bindir = "$basedir/bin/mozilla";
34 my $dbupdir = "$basedir/sql/Pg-upgrade";
35 my $dbupdir2 = "$basedir/sql/Pg-upgrade2";
36 my $menufile = "menu.ini";
37 my $submitsearch = qr/type\s*=\s*[\"\']?submit/i;
39 my (%referenced_html_files, %locale, %htmllocale, %alllocales, %cached, %submit);
40 my ($ALL_HEADER, $MISSING_HEADER, $LOST_HEADER);
44 opendir DIR, "$bindir" or die "$!";
45 my @progfiles = grep { /\.pl$/ && !/(_custom|^\.)/ } readdir DIR;
47 my @customfiles = grep /_custom/, readdir DIR;
50 # put customized files into @customfiles
55 @menufiles = ($menufile);
57 opendir DIR, "$basedir" or die "$!";
58 @menufiles = grep { /.*?_$menufile$/ } readdir DIR;
60 unshift @menufiles, $menufile;
63 opendir DIR, $dbupdir or die "$!";
64 my @dbplfiles = grep { /\.pl$/ } readdir DIR;
67 opendir DIR, $dbupdir2 or die "$!";
68 my @dbplfiles2 = grep { /\.pl$/ } readdir DIR;
71 # slurp the translations in
77 if (-f "$locales_dir/all") {
78 require "$locales_dir/all";
80 if (-f "$locales_dir/missing") {
81 require "$locales_dir/missing" ;
82 unlink "$locales_dir/missing";
84 if (-f "$locales_dir/lost") {
85 require "$locales_dir/lost";
86 unlink "$locales_dir/lost";
89 my %old_texts = %{ $self->{texts} || {} };
91 map({ handle_file($_, $bindir); } @progfiles);
92 map({ handle_file($_, $dbupdir); } @dbplfiles);
93 map({ handle_file($_, $dbupdir2); } @dbplfiles2);
97 file => "$locales_dir/all",
98 header => $ALL_HEADER,
99 data_name => '$self->{texts}',
100 data_sub => sub { _print_line($_, $self->{texts}{$_}, @_) for sort keys %alllocales },
103 # calc and generate missing
104 push @missing, grep { !$self->{texts}{$_} } sort keys %alllocales;
108 file => "$locales_dir/missing",
109 header => $MISSING_HEADER,
110 data_name => '$missing',
111 data_sub => sub { _print_line($_, '', @_) for @missing },
115 # calc and generate lost
116 while (my ($text, $translation) = each %old_texts) {
117 next if ($alllocales{$text});
118 push @lost, { 'text' => $text, 'translation' => $translation };
122 splice @lost, 0, (scalar @lost - 50) if (scalar @lost > 50);
124 file => "$locales_dir/lost",
125 header => $LOST_HEADER,
127 data_name => '@lost',
129 _print_line($_->{text}, $_->{translation}, @_, template => " { 'text' => %s, 'translation' => %s },") for @lost;
134 my $trlanguage = slurp("$locales_dir/LANGUAGE");
137 search_unused_htmlfiles() if $opt_c;
139 my $count = scalar keys %alllocales;
140 my $notext = scalar @missing;
141 my $per = sprintf("%.1f", ($count - $notext) / $count * 100);
142 print "\n$trlanguage - ${per}%";
143 print " - $notext/$count missing" if $notext;
152 # These are all the texts to build the translations files.
153 # The file has the form of 'english text' => 'foreign text',
154 # you can add the translation in this file or in the 'missing' file
155 # run locales.pl from this directory to rebuild the translation files
157 $MISSING_HEADER = <<EOL;
158 # add the missing texts and run locales.pl to rebuild
160 $LOST_HEADER = <<EOL;
161 # The last 50 texts that have been removed.
162 # This file will be auto-generated by locales.pl. Do not edit it.
170 'no-custom-files' => \$opt_n,
171 'check-files' => \$opt_c,
172 'verbose' => \$opt_v,
184 pod2usage(-exitstatus => 0, -verbose => 2);
188 $lang = shift @ARGV || croak 'need language code as argument';
192 my ($file, $dir) = @_;
193 print "\n$file" if $opt_v;
197 &scanfile("$dir/$file");
199 # scan custom_{module}.pl or {login}_{module}.pl files
200 foreach my $customfile (@customfiles) {
201 if ($customfile =~ /_$file/) {
202 if (-f "$dir/$customfile") {
203 &scanfile("$dir/$customfile");
208 # if this is the menu.pl file
209 if ($file eq 'menu.pl') {
210 foreach my $item (@menufiles) {
211 &scanmenu("$basedir/$item");
215 if ($file eq 'menunew.pl') {
216 foreach my $item (@menufiles) {
217 &scanmenu("$basedir/$item");
224 foreach my $text (keys %$missing) {
225 if ($locale{$text} || $htmllocale{$text}) {
226 unless ($self->{texts}{$text}) {
227 $self->{texts}{$text} = $missing->{$text};
233 sub extract_text_between_parenthesis {
234 my ($fh, $line) = @_;
235 my ($inside_string, $pos, $text, $quote_next) = (undef, 0, "", 0);
238 if (length($line) <= $pos) {
240 return ($text, "") unless ($line);
244 my $cur_char = substr($line, $pos, 1);
246 if (!$inside_string) {
247 if ((length($line) >= ($pos + 3)) && (substr($line, $pos, 2)) eq "qq") {
248 $inside_string = substr($line, $pos + 2, 1);
251 } elsif ((length($line) >= ($pos + 2)) &&
252 (substr($line, $pos, 1) eq "q")) {
253 $inside_string = substr($line, $pos + 1, 1);
256 } elsif (($cur_char eq '"') || ($cur_char eq '\'')) {
257 $inside_string = $cur_char;
259 } elsif (($cur_char eq ")") || ($cur_char eq ',')) {
260 return ($text, substr($line, $pos + 1));
268 } elsif ($cur_char eq '\\') {
272 } elsif ($cur_char eq $inside_string) {
273 undef($inside_string);
286 my $dont_include_subs = shift;
287 my $scanned_files = shift;
292 $scanned_files = {} unless ($scanned_files);
293 return if ($scanned_files->{$file});
294 $scanned_files->{$file} = 1;
296 if (!defined $cached{$file}) {
298 return unless (-f "$file");
300 my $fh = new FileHandle;
301 open $fh, "$file" or die "$! : $file";
303 my ($is_submit, $line_no, $sub_line_no) = (0, 0, 0);
308 # is this another file
309 if (/require\s+\W.*\.pl/) {
311 $newfile =~ s/require\s+\W//;
312 $newfile =~ s|bin/mozilla||;
313 $cached{$file}{scan}{"$bindir/$newfile"} = 1;
314 } elsif (/use\s+SL::([\w:]*)/) {
317 $cached{$file}{scannosubs}{"../../SL/${module}.pm"} = 1;
320 # is this a template call?
321 if (/parse_html_template2?\s*\(\s*[\"\']([\w\/]+)\s*[\"\']/) {
322 my $newfile = "$basedir/templates/webpages/$1.html";
323 if (/parse_html_template2/) {
324 print "E: " . strip_base($file) . " is still using 'parse_html_template2' for " . strip_base($newfile) . ".\n";
327 $cached{$file}{scanh}{$newfile} = 1;
330 print "W: missing HTML template: " . strip_base($newfile) . " (referenced from " . strip_base($file) . ")\n";
339 my ($null, $country) = split /,/;
340 $country =~ s/^ +[\"\']//;
341 $country =~ s/[\"\'].*//;
347 # is it a submit button before $locale->
348 if (/$submitsearch/) {
350 if ($` !~ /locale->text/) {
352 $sub_line_no = $line_no;
356 my ($found) = /locale->text.*?\(/;
361 ($string, $_) = extract_text_between_parenthesis($fh, $postmatch);
364 # if there is no $ in the string record it
365 unless (($string =~ /\$\D.*/) || ("" eq $string)) {
367 # this guarantees one instance of string
368 $cached{$file}{locale}{$string} = 1;
370 # this one is for all the locales
371 $cached{$file}{all}{$string} = 1;
373 # is it a submit button before $locale->
375 $cached{$file}{submit}{$string} = 1;
378 } elsif ($postmatch =~ />/) {
382 # exit loop if there are no more locales on this line
383 ($rc) = ($postmatch =~ /locale->text/);
385 if ( ($postmatch =~ />/)
386 || (!$found && ($sub_line_no != $line_no) && />/)) {
396 map { $alllocales{$_} = 1 } keys %{$cached{$file}{all}};
397 map { $locale{$_} = 1 } keys %{$cached{$file}{locale}};
398 map { $submit{$_} = 1 } keys %{$cached{$file}{submit}};
399 map { &scanfile($_, 0, $scanned_files) } keys %{$cached{$file}{scan}};
400 map { &scanfile($_, 1, $scanned_files) } keys %{$cached{$file}{scannosubs}};
401 map { &scanhtmlfile($_) } keys %{$cached{$file}{scanh}};
403 @referenced_html_files{keys %{$cached{$file}{scanh}}} = (1) x scalar keys %{$cached{$file}{scanh}};
409 my $fh = new FileHandle;
410 open $fh, "$file" or die "$! : $file";
412 my @a = grep m/^\[/, <$fh>;
416 grep { s/(\[|\])//g } @a;
418 foreach my $item (@a) {
419 my @b = split /--/, $item;
420 foreach my $string (@b) {
422 $locale{$string} = 1;
423 $alllocales{$string} = 1;
429 sub unescape_template_string {
440 if (!defined $cached{$file}) {
441 my %plugins = ( 'loaded' => { }, 'needed' => { } );
443 open(IN, $file) || die $file;
448 while (my $line = <IN>) {
451 while ($line =~ m/\[\%[^\w]*use[^\w]+(\w+)[^\w]*?\%\]/gi) {
452 $plugins{loaded}->{$1} = 1;
455 while ($line =~ m/\[\%[^\w]*(\w+)\.\w+\(/g) {
457 $plugins{needed}->{$plugin} = 1 if (first { $_ eq $plugin } qw(HTML LxERP JavaScript MultiColumnIterator));
460 while ($line =~ m/(?: # Start von Variante 1: LxERP.t8('...'); ohne darumliegende [% ... %]-Tags
461 (LxERP\.t8)\( # LxERP.t8( ::Parameter $1::
462 ([\'\"]) # Anfang des zu übersetzenden Strings ::Parameter $2::
463 (.*?) # Der zu übersetzende String ::Parameter $3::
464 (?<!\\)\2 # Ende des zu übersetzenden Strings
465 | # Start von Variante 2: [% '...' | $T8 %]
466 \[\% # Template-Start-Tag
467 [\-~#]? # Whitespace-Unterdrückung
468 \s* # Optional beliebig viele Whitespace
469 ([\'\"]) # Anfang des zu übersetzenden Strings ::Parameter $4::
470 (.*?) # Der zu übersetzende String ::Parameter $5::
471 (?<!\\)\4 # Ende des zu übersetzenden Strings
472 \s*\|\s* # Pipe-Zeichen mit optionalen Whitespace davor und danach
473 (\$T8) # Filteraufruf ::Parameter $6::
474 .*? # Optionale Argumente für den Filter
476 [\-~#]? # Whitespace-Unterdrückung
477 \%\] # Template-Ende-Tag
480 my $module = $1 || $6;
481 my $string = $3 || $5;
482 print "Found filter >>>$string<<<\n" if $debug;
483 substr $line, $LAST_MATCH_START[1], $LAST_MATCH_END[0] - $LAST_MATCH_START[0], '';
485 $string = unescape_template_string($string);
486 $cached{$file}{all}{$string} = 1;
487 $cached{$file}{html}{$string} = 1;
488 $cached{$file}{submit}{$string} = 1 if $PREMATCH =~ /$submitsearch/;
489 $plugins{needed}->{T8} = 1 if $module eq '$T8';
490 $plugins{needed}->{LxERP} = 1 if $module eq 'LxERP.t8';
493 while ($line =~ m/\[\% # Template-Start-Tag
494 [\-~#]? # Whitespace-Unterdrückung
495 \s* # Optional beliebig viele Whitespace
496 (?: # Die erkannten Template-Direktiven
501 \s+ # Mindestens ein Whitespace
502 [\'\"]? # Anfang des Dateinamens
503 ([^\s]+) # Beliebig viele Nicht-Whitespaces -- Dateiname
504 \.html # Endung ".html", ansonsten kann es der Name eines Blocks sein
506 my $new_file_name = "$basedir/templates/webpages/$1.html";
507 $cached{$file}{scanh}{$new_file_name} = 1;
508 substr $line, $LAST_MATCH_START[1], $LAST_MATCH_END[0] - $LAST_MATCH_START[0], '';
514 foreach my $plugin (keys %{ $plugins{needed} }) {
515 next if ($plugins{loaded}->{$plugin});
516 print "E: " . strip_base($file) . " requires the Template plugin '$plugin', but is not loaded with '[\% USE $plugin \%]'.\n";
520 # copy back into global arrays
521 map { $alllocales{$_} = 1 } keys %{$cached{$file}{all}};
522 map { $locale{$_} = 1 } keys %{$cached{$file}{html}};
523 map { $submit{$_} = 1 } keys %{$cached{$file}{submit}};
525 map { scanhtmlfile($_) } keys %{$cached{$file}{scanh}};
527 @referenced_html_files{keys %{$cached{$file}{scanh}}} = (1) x scalar keys %{$cached{$file}{scanh}};
530 sub search_unused_htmlfiles {
531 my @unscanned_dirs = ('../../templates/webpages');
533 while (scalar @unscanned_dirs) {
534 my $dir = shift @unscanned_dirs;
536 foreach my $entry (<$dir/*>) {
538 push @unscanned_dirs, $entry;
540 } elsif (($entry =~ /_master.html$/) && -f $entry && !$referenced_html_files{$entry}) {
541 print "W: unused HTML template: " . strip_base($entry) . "\n";
549 my $s = "$_[0]"; # Create a copy of the string.
552 $s =~ s|templates/webpages/||;
559 $val =~ s/('|\\$)/\\$1/g;
560 return "'" . $val . "'";
564 my $key = _single_quote(shift);
565 my $text = _single_quote(shift);
567 my $template = $params{template} || qq| %-29s => %s,\n|;
568 my $fh = $params{fh} || croak 'need filehandle in _print_line';
570 print $fh sprintf $template, $key, $text;
576 my $file = $params{file} || croak 'need filename in generate_file';
577 my $header = $params{header};
578 my $lines = $params{data_sub};
579 my $data_name = $params{data_name};
580 my @delim = split //, ($params{delim} || '{}');
582 open my $fh, '>', $file or die "$! : $file";
584 print $fh "#!/usr/bin/perl\n\n";
585 print $fh $header, "\n" if $header;
586 print $fh "$data_name = $delim[0]\n" if $data_name;
590 print $fh qq|$delim[1];\n\n1;\n|;
598 locales.pl - Collect strings for translation in Lx-Office
605 -n, --no-custom-files Do not process files whose name contains "_"
606 -c, --check-files Run extended checks on HTML files
607 -v, --verbose Be more verbose
608 -h, --help Show this help
614 =item B<-n>, B<--no-custom-files>
616 Do not process files whose name contains "_", e.g. "custom_io.pl".
618 =item B<-c>, B<--check-files>
620 Run extended checks on the usage of templates. This can be used to
621 discover HTML templates that are never used as well as the usage of
622 non-existing HTML templates.
624 =item B<-v>, B<--verbose>
632 This script collects strings from Perl files, the menu.ini file and
633 HTML templates and puts them into the file "all" for translation. It
634 also distributes those translations back to the individual files.