summaryrefslogtreecommitdiffstats
path: root/po4a-updatepo
diff options
context:
space:
mode:
Diffstat (limited to 'po4a-updatepo')
-rwxr-xr-xpo4a-updatepo337
1 files changed, 337 insertions, 0 deletions
diff --git a/po4a-updatepo b/po4a-updatepo
new file mode 100755
index 0000000..0f56e3c
--- /dev/null
+++ b/po4a-updatepo
@@ -0,0 +1,337 @@
+#! /usr/bin/env perl
+eval 'exec perl -S $0 ${1+"$@"}'
+ if $running_under_some_shell;
+
+# pod-updatepo -- update the PO translation of POD data.
+#
+# Copyright 2002-2023 by SPI, inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of GPL v2.0 or later (see COPYING).
+
+=encoding UTF-8
+
+=head1 NAME
+
+po4a-updatepo - update the translation (in PO format) of documentation
+
+=head1 SYNOPSIS
+
+B<po4a-updatepo> B<-f> I<fmt> (B<-m> I<master.doc>)+ (B<-p> I<XX.po>)+
+
+(I<XX.po> are the outputs, all others are inputs)
+
+=head1 DESCRIPTION
+
+The po4a (PO for anything) project goal is to ease translations (and more
+interestingly, the maintenance of translations) using gettext tools on
+areas where they were not expected like documentation.
+
+The B<po4a-updatepo> script is in charge of updating PO files to make
+them reflect the changes made to the original documentation file. For that,
+it converts the documentation file to a POT file, and call L<msgmerge(1)>
+on this new POT and on the provided PO files.
+
+It is possible to give more than one PO file (if you want to update several
+languages at once), and several documentation files (if you want to store
+the translations of several documents in the same PO file).
+
+If the master document has non-ASCII characters, this script will convert the PO files
+to UTF-8 (if they weren't already), for a transparent handling of non-standard characters.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-f>, B<--format>
+
+Format of the documentation you want to handle. Use the B<--help-format>
+option to see the list of available formats.
+
+=item B<-m>, B<--master>
+
+File(s) containing the master document to translate.
+
+=item B<-M>, B<--master-charset>
+
+Charset of the files containing the document to translate. Note that all
+files must have the same charset.
+
+=item B<-p>, B<--po>
+
+PO file(s) to update. If these files do not exist, they are created by
+B<po4a-updatepo>.
+
+=item B<-o>, B<--option>
+
+Extra option(s) to pass to the format plugin. See the documentation of each
+plugin for more information about the valid options and their meanings. For
+example, you could pass '-o tablecells' to the AsciiDoc parser, while the
+text parser would accept '-o tabs=split'.
+
+=item B<--no-previous>
+
+This option removes B<--previous> from the options passed to B<msgmerge>.
+This helps supporting old versions of B<gettext> (before v0.16).
+
+=item B<--previous>
+
+This option adds B<--previous> to the options passed to B<msgmerge>.
+It requires B<gettext> 0.16 or later, and is activated by default.
+
+=item B<--msgmerge-opt> I<options>
+
+Extra options for B<msgmerge>(1).
+
+=item B<-h>, B<--help>
+
+Show a short help message.
+
+=item B<--help-format>
+
+List the documentation formats understood by po4a.
+
+=item B<-V>, B<--version>
+
+Display the version of the script and exit.
+
+=item B<-v>, B<--verbose>
+
+Increase the verbosity of the program.
+
+=item B<-d>, B<--debug>
+
+Output some debugging information.
+
+=item B<--porefs> I<type>[,B<wrap>|B<nowrap>]
+
+Specify the reference format. Argument I<type> can be one of B<never>
+to not produce any reference, B<file> to only specify the file
+without the line number, B<counter> to replace line number by an
+increasing counter, and B<full> to include complete references (default: full).
+
+Argument can be followed by a comma and either B<wrap> or B<nowrap> keyword.
+References are written by default on a single line. The B<wrap> option wraps
+references on several lines, to mimic B<gettext> tools (B<xgettext> and
+B<msgmerge>). This option will become the default in a future release, because
+it is more sensible. The B<nowrap> option is available so that users who want
+to keep the old behavior can do so.
+
+=item B<--wrap-po> B<no>|B<newlines>|I<number> (default: 76)
+
+Specify how the po file should be wrapped. This gives the choice between either
+files that are nicely wrapped but could lead to git conflicts, or files that are
+easier to handle automatically, but harder to read for humans.
+
+Historically, the gettext suite has reformatted the po files at the 77th column
+for cosmetics. This option specifies the behavior of po4a. If set to a numerical
+value, po4a will wrap the po file after this column and after newlines in the
+content. If set to B<newlines>, po4a will only split the msgid and msgstr after
+newlines in the content. If set to B<no>, po4a will not wrap the po file at all.
+The wrapping of the reference comments is controlled by the B<--porefs> option.
+
+Note that this option has no impact on how the msgid and msgstr are wrapped, i.e.
+on how newlines are added to the content of these strings.
+
+=item B<--msgid-bugs-address> I<email@address>
+
+Set the report address for msgid bugs. By default, the created POT files
+have no Report-Msgid-Bugs-To fields.
+
+=item B<--copyright-holder> I<string>
+
+Set the copyright holder in the POT header. The default value is
+"Free Software Foundation, Inc."
+
+=item B<--package-name> I<string>
+
+Set the package name for the POT header. The default is "PACKAGE".
+
+=item B<--package-version> I<string>
+
+Set the package version for the POT header. The default is "VERSION".
+
+=back
+
+=head1 SEE ALSO
+
+L<po4a-gettextize(1)>,
+L<po4a-normalize(1)>,
+L<po4a-translate(1)>,
+L<po4a(7)>
+
+=head1 AUTHORS
+
+ Denis Barbier <barbier@linuxfr.org>
+ Nicolas François <nicolas.francois@centraliens.net>
+ Martin Quinson (mquinson#debian.org)
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2002-2023 by SPI, inc.
+
+This program is free software; you may redistribute it and/or modify it
+under the terms of GPL v2.0 or later (see the COPYING file).
+
+=cut
+
+use 5.16.0;
+use strict;
+use warnings;
+use utf8;
+
+use Getopt::Long qw(GetOptions);
+use Locale::Po4a::Po;
+
+use Locale::Po4a::Chooser;
+use Locale::Po4a::TransTractor;
+use Locale::Po4a::Common;
+
+use Pod::Usage qw(pod2usage);
+
+use File::Temp;
+use File::Copy qw(copy);
+use Config;
+
+Locale::Po4a::Common::textdomain('po4a');
+
+sub show_version {
+ Locale::Po4a::Common::show_version("po4a-updatepo");
+ exit 0;
+}
+
+# init commandline parser
+Getopt::Long::config( 'bundling', 'no_getopt_compat', 'no_auto_abbrev' );
+
+# Parse our options
+my ( @masterfiles, @pofiles );
+my ( $help, $help_fmt, $verbose, $debug, $format, @options );
+my ( $copyright_holder, $msgid_bugs_address, $package_name, $package_version, $no_deprecation );
+my $mastchar;
+my $previous;
+my $noprevious;
+my $msgmerge_opt = "";
+my $porefs = "full";
+my $wrappo;
+GetOptions(
+ 'help|h' => \$help,
+ 'help-format' => \$help_fmt,
+
+ 'master|m=s' => \@masterfiles,
+ 'po|p=s' => \@pofiles,
+ 'format|f=s' => \$format,
+
+ 'master-charset|M=s' => \$mastchar,
+
+ # undocumented option to silence the warning about po4a-updatepo being deprecated
+ 'no-deprecation' => \$no_deprecation,
+
+ 'option|o=s' => \@options,
+
+ 'no-previous' => \$noprevious,
+ 'wrap-po=s' => \$wrappo,
+ 'previous' => \$previous,
+ 'msgmerge-opt=s' => \$msgmerge_opt,
+ 'copyright-holder=s' => \$copyright_holder,
+ 'msgid-bugs-address=s' => \$msgid_bugs_address,
+ 'package-name=s' => \$package_name,
+ 'package-version=s' => \$package_version,
+ 'porefs=s' => \$porefs,
+
+ 'verbose|v' => \$verbose,
+ 'debug|d' => \$debug,
+ 'version|V' => \&show_version
+) or pod2usage();
+
+$help && pod2usage( -verbose => 1, -exitval => 0 );
+$help_fmt && Locale::Po4a::Chooser::list(0);
+pod2usage() if scalar @masterfiles < 1 || scalar @pofiles < 1;
+
+$msgmerge_opt .= " --previous" unless $noprevious;
+$msgmerge_opt .= " --add-location=file" if ( $porefs =~ m/^file/ );
+$msgmerge_opt =~ s/^\s+//;
+
+my %options = (
+ "verbose" => $verbose,
+ "debug" => $debug,
+ "copyright-holder" => $copyright_holder,
+ "msgid-bugs-address" => $msgid_bugs_address,
+ "package-name" => $package_name,
+ "package-version" => $package_version,
+ "porefs" => $porefs,
+ "wrap-po" => $wrappo
+);
+
+foreach (@options) {
+ if (m/^([^=]*)=(.*)$/) {
+ $options{$1} = "$2";
+ } else {
+ $options{$_} = 1;
+ }
+}
+
+unless ($no_deprecation) {
+ print wrap_msg(
+ gettext(
+ "po4a-updatepo is deprecated. The unified po4a(1) program is more convenient and less error prone. "
+ . "Once configured, `po4a --no-translations` can be used as a drop-in replacement to `po4a-updatepo`."
+ )
+ );
+}
+
+# parser
+my $doc = Locale::Po4a::Chooser::new( $format, %options );
+
+map { -e $_ || die wrap_msg( gettext("File %s does not exist."), $_ ) } @masterfiles;
+map { die wrap_msg( gettext("po4a-updatepo can't take the input PO from stdin.") ) if $_ eq '-' && !-e '-' } @pofiles;
+
+my ($pot_filename);
+( undef, $pot_filename ) = File::Temp::tempfile(
+ "po4a-updatepoXXXX",
+ DIR => File::Spec->tmpdir(),
+ SUFFIX => ".pot",
+ OPEN => 0,
+ UNLINK => 0
+) or die wrap_msg( gettext("Cannot create a temporary POT file: %s"), $! );
+
+print STDERR wrap_msg( gettext("Parse input files... ") ) if $verbose;
+
+$doc->process(
+ 'file_in_name' => \@masterfiles,
+ 'file_in_charset' => $mastchar,
+ 'po_out_name' => $pot_filename
+);
+
+print STDERR wrap_msg( gettext("done.") ) if $verbose;
+
+while ( my $po_filename = shift @pofiles ) {
+ my $usable_pofile = 0;
+ if ( -e $po_filename ) {
+
+ # Check that the po file is not empty, as msgmerge has issues in this case (see Debian's #1022216 and GitHub's #442)
+ my $pofile = Locale::Po4a::Po->new();
+ $pofile->read($po_filename);
+ $usable_pofile = 1 if ( $pofile->count_entries() > 0 );
+ }
+ if ($usable_pofile) {
+ print STDERR wrap_msg( gettext("Updating %s:"), $po_filename )
+ if $verbose;
+ my @cmd = ( "msgmerge" . $Config{_exe} );
+ push( @cmd, split( /\s+/, $msgmerge_opt ) ) if length($msgmerge_opt);
+ push @cmd, ( "-U", $po_filename, $pot_filename );
+ print STDERR "Running " . join( ' ', @cmd ) . "\n" if $debug;
+ system(@cmd) == 0
+ or die wrap_msg( gettext("Could not run msgmerge: %s\nThe command was: %s"), $!, join( ' ', @cmd ) );
+ @cmd = ( "msgfmt" . $Config{_exe}, "--statistics", "-v", "-o", File::Spec->devnull(), $po_filename );
+ print STDERR "Running " . join( ' ', @cmd ) . "\n" if $debug;
+ system(@cmd)
+ if $verbose;
+ } else {
+ print STDERR wrap_msg( gettext("Creating %s:"), $po_filename )
+ if $verbose;
+ copy( $pot_filename, $po_filename )
+ or die wrap_msg( gettext("Could not copy the PO file (%s -> %s): %s"), $pot_filename, $po_filename, $! );
+ }
+}
+
+unlink($pot_filename);