=head1 NAME
-tmpl_process3.pl - Experimental version of tmpl_process.pl
+tmpl_process3.pl - Alternative version of tmpl_process.pl
using gettext-compatible translation files
=cut
use Locale::PO;
use File::Temp qw( :POSIX );
use TmplTokenizer;
-use VerboseWarnings qw( error_normal warn_normal );
+use VerboseWarnings qw( :warn :die );
###############################################################################
sub find_translation ($) {
my($s) = @_;
- my $key = TmplTokenizer::quote_po($s) if $s =~ /\S/;
- $key = TmplTokenizer::charset_convert($key, $charset_in, $charset_out);
+ my $key = $s;
+ if ($s =~ /\S/s) {
+ $key = TmplTokenizer::string_canon($key);
+ $key = TmplTokenizer::charset_convert($key, $charset_in, $charset_out);
+ $key = TmplTokenizer::quote_po($key);
+ }
return defined $href->{$key}
&& !$href->{$key}->fuzzy
&& length Locale::PO->dequote($href->{$key}->msgstr)?
if ($attr->{$a}) {
next if $a eq 'content' && $tag ne 'meta';
next if $a eq 'value' && ($tag ne 'input'
- || (ref $attr->{'type'} && $attr->{'type'}->[1] =~ /^(?:hidden|radio)$/)); # FIXME
+ || (ref $attr->{'type'} && $attr->{'type'}->[1] =~ /^(?:hidden|radio|text)$/)); # FIXME
my($key, $val, $val_orig, $order) = @{$attr->{$a}}; #FIXME
- my($pre, $trimmed, $post) = TmplTokenizer::trim $val;
if ($val =~ /\S/s) {
- my $s = $pre . find_translation($trimmed) . $post;
+ my $s = find_translation($val);
if ($attr->{$a}->[1] ne $s) { #FIXME
$attr->{$a}->[1] = $s; # FIXME
$attr->{$a}->[2] = ($s =~ /"/s)? "'$s'": "\"$s\""; #FIXME
last unless defined $s;
my($kind, $t, $attr) = ($s->type, $s->string, $s->attributes);
if ($kind eq TmplTokenType::TEXT) {
- my($pre, $trimmed, $post) = TmplTokenizer::trim $t;
- print $output $pre, find_translation($trimmed), $post;
+ print $output find_translation($t);
} elsif ($kind eq TmplTokenType::TEXT_PARAMETRIZED) {
my $fmt = find_translation($s->form);
- print $output TmplTokenizer::parametrize($fmt, [ map {
+ print $output TmplTokenizer::parametrize($fmt, 1, $s, sub {
+ $_ = $_[0];
my($kind, $t, $attr) = ($_->type, $_->string, $_->attributes);
$kind == TmplTokenType::TAG && %$attr?
- text_replace_tag($t, $attr): $t } $s->parameters ], [ $s->anchors ]);
+ text_replace_tag($t, $attr): $t });
} elsif ($kind eq TmplTokenType::TAG && %$attr) {
print $output text_replace_tag($t, $attr);
+ } elsif ($s->has_js_data) {
+ for my $t (@{$s->js_data}) {
+ # FIXME for this whole block
+ if ($t->[0]) {
+ printf $output "%s%s%s", $t->[2], find_translation $t->[3],
+ $t->[2];
+ } else {
+ print $output $t->[1];
+ }
+ }
} elsif (defined $t) {
print $output $t;
}
###############################################################################
+sub usage ($) {
+ my($exitcode) = @_;
+ my $h = $exitcode? *STDERR: *STDOUT;
+ print $h <<EOF;
+Usage: $0 create [OPTION]
+ or: $0 update [OPTION]
+ or: $0 install [OPTION]
+ or: $0 --help
+Create or update PO files from templates, or install translated templates.
+
+ -i, --input=SOURCE Get or update strings from SOURCE file.
+ SOURCE is a directory if -r is also specified.
+ -o, --outputdir=DIRECTORY Install translation(s) to specified DIRECTORY
+ --pedantic-warnings Issue warnings even for detected problems
+ which are likely to be harmless
+ -r, --recursive SOURCE in the -i option is a directory
+ -s, --str-file=FILE Specify FILE as the translation (po) file
+ for input (install) or output (create, update)
+ -x, --exclude=REGEXP Exclude files matching the given REGEXP
+ --help Display this help and exit
+
+The -o option is ignored for the "create" and "update" actions.
+Try `perldoc $0' for perhaps more information.
+EOF
+ exit($exitcode);
+}
+
+###############################################################################
+
sub usage_error (;$) {
for my $msg (split(/\n/, $_[0])) {
print STDERR "$msg\n";
'str-file|s=s' => \$str_file,
'exclude|x=s' => \@excludes,
'pedantic-warnings|pedantic' => sub { $pedantic_p = 1 },
+ 'help' => \&usage,
) || usage_error;
VerboseWarnings::set_application_name $0;
VerboseWarnings::set_pedantic_mode $pedantic_p;
-# try to make sure .po files are backed up (see BUGS)
-$ENV{VERSION_CONTROL} = 't';
-
# keep the buggy Locale::PO quiet if it says stupid things
$SIG{__WARN__} = sub {
my($s) = @_;
# guess the charsets. HTML::Templates defaults to iso-8859-1
if (defined $href) {
+ die "$str_file: PO file is corrupted, or not a PO file\n"
+ unless defined $href->{'""'};
$charset_out = TmplTokenizer::charset_canon $2
if $href->{'""'}->msgstr =~ /\bcharset=(["']?)([^;\s"'\\]+)\1/;
for my $msgid (keys %$href) {
my $candidate = TmplTokenizer::charset_canon $2;
die "Conflicting charsets in msgid: $charset_in vs $candidate\n"
if defined $charset_in && $charset_in ne $candidate;
- $charset_in = $2;
+ $charset_in = $candidate;
}
}
}
warn "Warning: Can't determine original templates' charset, defaulting to $charset_in\n";
}
+my $xgettext = './xgettext.pl'; # actual text extractor script
+my $st;
+
if ($action eq 'create') {
# updates the list. As the list is empty, every entry will be added
- die "$str_file: Output file already exists" if -f $str_file;
+ if (!-s $str_file) {
+ warn "Removing empty file $str_file\n";
+ unlink $str_file || die "$str_file: $!\n";
+ }
+ die "$str_file: Output file already exists\n" if -f $str_file;
my($tmph, $tmpfile) = tmpnam();
# Generate the temporary file that acts as <MODULE>/POTFILES.in
for my $input (@in_files) {
}
close $tmph;
# Generate the specified po file ($str_file)
- system ('xgettext.pl', '-s', '-f', $tmpfile, '-o', $str_file);
- unlink $tmpfile || warn_normal "$tmpfile: unlink failed: $!\n", undef;
+ $st = system ($xgettext, '-s', '-f', $tmpfile, '-o', $str_file);
+ warn_normal "Text extraction failed: $xgettext: $!\n", undef if $st != 0;
+# unlink $tmpfile || warn_normal "$tmpfile: unlink failed: $!\n", undef;
} elsif ($action eq 'update') {
my($tmph1, $tmpfile1) = tmpnam();
}
close $tmph1;
# Generate the temporary file that acts as <MODULE>/<LANG>.pot
- system('./xgettext.pl', '-s', '-f', $tmpfile1, '-o', $tmpfile2,
+ $st = system($xgettext, '-s', '-f', $tmpfile1, '-o', $tmpfile2,
+ '--po-mode',
(defined $charset_in? ('-I', $charset_in): ()),
(defined $charset_out? ('-O', $charset_out): ()));
- # Merge the temporary "pot file" with the specified po file ($str_file)
- # FIXME: msgmerge(1) is a Unix dependency
- # FIXME: need to check the return value
- system('msgmerge', '-U', '-s', $str_file, $tmpfile2);
- unlink $tmpfile1 || warn_normal "$tmpfile1: unlink failed: $!\n", undef;
- unlink $tmpfile2 || warn_normal "$tmpfile2: unlink failed: $!\n", undef;
+ if ($st == 0) {
+ # Merge the temporary "pot file" with the specified po file ($str_file)
+ # FIXME: msgmerge(1) is a Unix dependency
+ # FIXME: need to check the return value
+ $st = system('msgmerge', '-U', '-s', $str_file, $tmpfile2);
+ } else {
+ error_normal "Text extraction failed: $xgettext: $!\n", undef;
+ error_additional "Will not run msgmerge\n", undef;
+ }
+# unlink $tmpfile1 || warn_normal "$tmpfile1: unlink failed: $!\n", undef;
+# unlink $tmpfile2 || warn_normal "$tmpfile2: unlink failed: $!\n", undef;
} elsif ($action eq 'install') {
if(!defined($out_dir)) {
} else {
usage_error('Unknown action specified.');
}
+
+if ($st == 0) {
+ printf "The %s seems to be successful.\n", $action;
+} else {
+ printf "%s FAILED.\n", "\u$action";
+}
exit 0;
###############################################################################
=head1 DESCRIPTION
-This is an experimental version of the tmpl_process.pl script,
-using standard gettext-style PO files. Note that the behaviour
-of this script should still be considered unstable.
+This is an alternative version of the tmpl_process.pl script,
+using standard gettext-style PO files. While there still might
+be changes made to the way it extracts strings, at this moment
+it should be stable enough for general use; it is already being
+used for the Chinese and Polish translations.
Currently, the create, update, and install actions have all been
reimplemented and seem to work.
+=head2 Features
+
+=over
+
+=item -
+
+Translation files in standard Uniforum PO format.
+All standard tools including all gettext tools,
+plus PO file editors like kbabel(1) etc.
+can be used.
+
+=item -
+
+Minor changes in whitespace in source templates
+do not generally require strings to be re-translated.
+
+=item -
+
+Able to handle <TMPL_VAR> variables in the templates;
+<TMPL_VAR> variables are usually extracted in proper context,
+represented by a short %s placeholder.
+
+=item -
+
+Able to handle text input and radio button INPUT elements
+in the templates; these INPUT elements are also usually
+extracted in proper context,
+represented by a short %S or %p placeholder.
+
+=item -
+
+Automatic comments in the generated PO files to provide
+even more context (line numbers, and the names and types
+of the variables).
+
+=item -
+
+The %I<n>$s (or %I<n>$p, etc.) notation can be used
+for change the ordering of the variables,
+if such a reordering is required for correct translation.
+
+=item -
+
+If a particular <TMPL_VAR> should not appear in the
+translation, it can be suppressed with the %0.0s notation.
+
+=item -
+
+Using the PO format also means translators can add their
+own comments in the translation files, if necessary.
+
+=item -
+
+Create, update, and install actions are all based on the
+same scanner module. This ensures that update and install
+have the same idea of what is a translatable string;
+attribute names in tags, for example, will not be
+accidentally translated.
+
+=back
+
+=head1 NOTES
+
+Anchors are represented by an <AI<n>> notation.
+The meaning of this non-standard notation might not be obvious.
+
The create action calls xgettext.pl to do the actual work;
the update action calls xgettext.pl and msgmerge(1) to do the
actual work.
-The script can detect <TMPL_VAR> directives embedded inside what
-appears to be a full sentence (this actual work being done by
-TmplTokenizer(3)); these larger patterns appear in the translation
-file as c-format strings with %s.
-
=head1 BUGS
-The --help option has not been implemented yet.
-
-If an extracted string contain actual text (versus tags or
-TMPL_VAR directives), the strings are extracted verbatim,
-resulting in unwieldy things like multiple spaces, tabs,
-and/or newlines which are semantically indistinguishable
-from single blanks. If the template writer changes the
-spacing just a little bit, the new formatting would be
-considered new strings. This is arguably wrong, and in any
-case counter-productive.
-
xgettext.pl must be present in the current directory; the
msgmerge(1) command must also be present in the search path.
The script currently does not check carefully whether these
dependent commands are present.
-If xgettext.pl is interrupted by the user, a corrupted po file
-will result. This is very seriously wrong.
-
Locale::PO(3) has a lot of bugs. It can neither parse nor
generate GNU PO files properly; a couple of workarounds have
been written in TmplTokenizer and more is likely to be needed
(e.g., to get rid of the "Strange line" warning for #~).
+This script may not work in Windows.
+
There are probably some other bugs too, since this has not been
tested very much.
=head1 SEE ALSO
xgettext.pl,
+TmplTokenizer.pm,
msgmerge(1),
Locale::PO(3),
translator_doc.txt
+http://www.saas.nsw.edu.au/koha_wiki/index.php?page=DifficultTerms
+
=cut