1#! /usr/bin/env perl
2eval 'exec perl -S $0 ${1+"$@"}'
3  if $running_under_some_shell;
4
5# po4a -- Update both the po files and translated documents in one shoot
6#
7# Copyright 2002-2020 by SPI, inc.
8#
9# This program is free software; you can redistribute it and/or modify it
10# under the terms of GPL (see COPYING).
11
12=encoding UTF-8
13
14=head1 NAME
15
16po4a - update both the PO files and translated documents in one shot
17
18=head1 SYNOPSIS
19
20B<po4a> [I<options>] I<config_file>
21
22=head1 DESCRIPTION
23
24po4a (PO for anything) eases the maintenance of documentation translation
25using the classical gettext tools. The main feature of po4a is that
26it decouples the translation of content from its document structure.
27Please refer to the page L<po4a(7)> for a gentle introduction to this
28project.
29
30When you run the B<po4a> program for the first time, with only a
31configuration file and the documents to translate (called master
32documents), it produces a POT file (also called translation template)
33that contains all of the translatable strings in the document in a form
34that eases the work of translators.
35
36Those POT files can either be translated with a specific editor such
37as the B<GNOME Translation Editor>, KDE's B<Lokalize> or B<poedit>,
38or they can be integrated in an online localization platform such as
39B<weblate> or B<pootle>.  The translation result is a set of PO files,
40one per language.
41
42When you run the B<po4a> program with both the master documents and
43the PO files, it produces the translated documents by injecting the
44content's translation (found in the PO files) into the structure of
45the original master document.
46
47If the master documents changed in the meanwhile, po4a will update the PO
48and POT files accordingly, so that the translators can easily detect the
49modifications and update their work. Depending on your settings, po4a will
50discard the partially translated documents, or produce a document mixing
51English (for the new or modified paragraphs) and the target language
52(for paragraphs where translation is already in the PO file).
53
54By default, the translated documents are produced when at least 80%
55of their content is translated (see the I<--keep> option below).
56Discarding translations as soon as they are not 100% may be
57discouraging for the translators, while showing "translations" that
58are too incomplete may be troubling for the end users.
59
60=head2 Graphical overview
61
62
63 master documents ---+---->-------->---------+
64  (doc authoring)    |                       |
65                     V   (po4a executions)   >-----+--> translations
66                     |                       |     |
67 existing PO files -->--> updated PO files >-+     |
68      ^                            |               |
69      |                            V               |
70      +----------<---------<-------+               ^
71       (manual translation process)                |
72                                                   |
73 addendum -->--------------------------------------+
74
75The master documents are authored by the documentation writers. Any changes are
76automatically reflected by po4a in the PO files, that are then updated by the
77translators. All changes to the PO files (either manual or by po4a) are
78automatically reflected in translated documents. You can mimic this behavior
79using the L<po4a-updatepo(1)> and L<po4a-translate(1)> scripts in makefiles, but
80this quickly becomes bothersome and repetitive (see L<po4a(7)>). It is highly
81recommended to use the B<po4a> program in your build process.
82
83=head1 OPTIONS
84
85=over 4
86
87=item B<-k>, B<--keep>
88
89Minimal threshold for translation percentage to keep (i.e. write) the
90resulting file (default: 80). I.e. by default, files have to be translated
91at least at 80% to be written on disk.
92
93=item B<-h>, B<--help>
94
95Show a short help message.
96
97=item B<-M>, B<--master-charset>
98
99Charset of the files containing the documents to translate. Note that all
100master documents must use the same charset.
101
102=item B<-L>, B<--localized-charset>
103
104Charset of the files containing the localized documents. Note that all
105translated documents will use the same charset.
106
107=item B<-A>, B<--addendum-charset>
108
109Charset of the addenda. Note that all the addenda should be in the same
110charset.
111
112=item B<-V>, B<--version>
113
114Display the version of the script and exit.
115
116=item B<-v>, B<--verbose>
117
118Increase the verbosity of the program.
119
120=item B<-q>, B<--quiet>
121
122Decrease the verbosity of the program.
123
124=item B<-d>, B<--debug>
125
126Output some debugging information.
127
128=item B<-o>, B<--option>
129
130Extra option(s) to pass to the format plugin. See the documentation of each
131plugin for more information about the valid options and their meanings. For
132example, you could pass '-o tablecells' to the AsciiDoc parser, while the
133text parser would accept '-o tabs=split'.
134
135=item B<-f>, B<--force>
136
137Always generate the POT and PO files, even if B<po4a> considers it is
138not necessary.
139
140The default behavior (when B<--force> is not specified) is the following:
141
142=over
143
144If the POT file already exists, it is regenerated if a master document
145or the configuration file is more recent (unless B<--no-update> is provided).
146The POT file is also written in a temporary document and B<po4a> verifies
147that the changes are really needed.
148
149Also, a translation is regenerated only if its master document, the PO file,
150one of its addenda or the configuration file is more recent.
151To avoid trying to regenerate translations which do not pass the threshold
152test (see B<--keep>), a file with the F<.po4a-stamp> extension can be created
153(see B<--stamp>).
154
155=back
156
157If a master document includes files, you should use the B<--force> flag
158because the modification time of these included files are not taken into
159account.
160
161The PO files are always re-generated based on the POT with B<msgmerge -U>.
162
163=item B<--stamp>
164
165Tells B<po4a> to create stamp files when a translation is not generated
166because it does not reach the threshold. These stamp files are named
167according to the expected translated document, with the F<.po4a-stamp>
168extension.
169
170Note: This only activates the creation of the F<.po4a-stamp> files. The stamp
171files are always used if they exist, and they are removed with
172B<--rm-translations> or when the file is finally translated.
173
174=item B<--no-translations>
175
176Do not generate the translated documents, only update the POT and PO files.
177
178=item B<--no-update>
179
180Do not change the POT and PO files, only the translation may be updated.
181
182=item B<--keep-translations>
183
184Keeps the existing translation files even if the translation doesn't meet the threshold specified by B<--keep>.
185This option does not create new translation files with few content,
186but it will save existing translations which decay because of changes to the master files.
187
188WARNING: This flag changes the po4a behavior in a rather drastic way:
189your translated files will not get updated at all until the
190translation improves. Only use this flag if you prefer shipping an
191outdated translated documentation rather than only shipping an
192accurate untranslated documentation.
193
194=item B<--rm-translations>
195
196Remove the translated files (implies B<--no-translations>).
197
198=item B<--no-backups>
199
200This flag does nothing since 0.41, and may be removed
201in later releases.
202
203=item B<--rm-backups>
204
205This flag does nothing since 0.41, and may be removed
206in later releases.
207
208=item B<--translate-only> I<translated-file>
209
210Translate only the specified file.  It may be useful to speed up
211processing if a configuration file contains a lot of files.  Note that this
212option does not update PO and POT files.
213This option can be used multiple times.
214
215=item B<--variable> I<var>B<=>I<value>
216
217Define a variable that will be expanded in the B<po4a> configuration file.
218Every occurrence of I<$(var)> will be replaced by I<value>.
219This option can be used multiple times.
220
221=item B<--srcdir> I<SRCDIR>
222
223Set the base directory for all input documents specified in the B<po4a>
224configuration file.
225
226If both I<destdir> and I<srcdir> are specified, input files are
227searched in the following directories, in order: I<destdir>, the current
228directory and I<srcdir>. Output files are written to I<destdir> if
229specified, or to the current directory.
230
231=item B<--destdir> I<DESTDIR>
232
233Set the base directory for all the output documents specified in the
234B<po4a> configuration file (see B<--srcdir> above).
235
236=back
237
238=head2 Options modifying the POT header
239
240=over 4
241
242=item B<--porefs> I<type>
243
244Specify the reference format. Argument I<type> can be one of B<never>
245to not produce any reference, B<file> to only specify the file
246without the line number, B<counter> to replace line number by an
247increasing counter, and B<full> to include complete references (default: full).
248
249=item B<--wrap-po> B<no>|B<newlines>|I<number> (default: 76)
250
251Specify how the po file should be wrapped. This gives the choice between either
252files that are nicely wrapped but could lead to git conflicts, or files that are
253easier to handle automatically, but harder to read for humans.
254
255Historically, the gettext suite has reformatted the po files at the 77th column
256for cosmetics. This option specifies the behavior of po4a. If set to a numerical
257value, po4a will wrap the po file after this column and after newlines in the
258content. If set to B<newlines>, po4a will only split the msgid and msgstr after
259newlines in the content. If set to B<no>, po4a will not wrap the po file at all.
260The reference comments are always wrapped by the gettext tools that we use internally.
261
262Note that this option has no impact on how the msgid and msgstr are wrapped, ie
263on how newlines are added to the content of these strings.
264
265=item B<--master-language>
266
267Language of the source files containing the documents to
268translate. Note that all master documents must use the same language.
269
270=item B<--msgid-bugs-address> I<email@address>
271
272Set the report address for msgid bugs. By default, the created POT files
273have no Report-Msgid-Bugs-To fields.
274
275=item B<--copyright-holder> I<string>
276
277Set the copyright holder in the POT header. The default value is
278"Free Software Foundation, Inc."
279
280=item B<--package-name> I<string>
281
282Set the package name for the POT header. The default is "PACKAGE".
283
284=item B<--package-version> I<string>
285
286Set the package version for the POT header. The default is "VERSION".
287
288=back
289
290=head2 Options to modify the PO files
291
292=over 4
293
294=item B<--msgmerge-opt> I<options>
295
296Extra options for B<msgmerge>(1).
297
298Note: B<$lang> will be extended to the current language.
299
300=item B<--no-previous>
301
302This option removes B<--previous> from the options passed to B<msgmerge>.
303This permits to support versions of B<gettext> earlier than 0.16.
304
305=item B<--previous>
306
307This option adds B<--previous> to the options passed to B<msgmerge>.
308It requires B<gettext> 0.16 or later, and is activated by default.
309
310=back
311
312=head1 CONFIGURATION FILE
313
314po4a expects a configuration file as argument. This file must contain
315the following elements:
316
317=over
318
319=item
320
321The path to the PO files and the list of languages existing in the project;
322
323=item
324
325Optionally, some global options and so-called configuration aliases that are
326used as templates to configure individual master files;
327
328=item
329
330The list of each master file to translate, along with specific parameters.
331
332=back
333
334All lines contain a command between square braces, followed by its parameters.
335Comments begin with the char '#' and run until the end of the line. You can
336escape the end of line to spread a command over several lines.
337
338Some full examples are presented on this page, while other examples
339can be found in the C<t/cfg> directory of the source distribution.
340
341=head2 Finding the PO and POT files
342
343The simplest solution is to explicitly give the path to POT and PO files, as follows:
344
345 [po4a_paths] man/po/project.pot de:man/po/de.po fr:man/po/fr.po
346
347This specifies the path to the POT file first, and then the paths to the German
348and French PO files.
349
350The same information can be written as follows to reduce the risk of copy/paste errors:
351
352 [po4a_langs] fr de
353 [po4a_paths] man/po/project.pot $lang:man/po/$lang.po
354
355The C<$lang> component is automatically expanded using the provided languages
356list, reducing the risk of copy/paste error when a new language is added.
357
358You can further compact the same information by only providing the
359path to the directory containing your translation project, as follows.
360
361 [po_directory] man/po/
362
363The provided directory must contain a set of PO files, each named F<XX.po> with
364C<XX> the ISO 639-1 of the language used in this file. The directory must also
365contain a single POT file, with the C<.pot> file extension. For the first run,
366this file can be empty but it must exist (po4a cannot guess the name to use
367before the extension).
368
369Note that you must choose only one between C<po_directory> and
370C<po4a_paths>. The first one (C<po_directory>) is more compact,
371further reduces the risk of copy/paste error, but forces you to use
372the expected project structure and file names. The second one
373(C<po4a_paths>), is more explicit, probably more readable, and
374advised when you setup your first project with po4a.
375
376=head3 Centralized or split PO files?
377
378By default, po4a produces one single PO file per target language, containing the
379whole content of your translation project. As your project grows, the size of
380these files may become problematic. When using weblate, it is possible to specify
381priorities for each translation segment (i.e., msgid) so that the important ones get
382translated first. Still, some translation teams prefer to split the content
383in several files.
384
385To have one PO file per master file, you simply have to use the string
386C<$master> in the name of your PO files on the C<[po4a_paths]> line, as follows.
387
388 [po4a_paths] doc/$master/$master.pot $lang:doc/$master/$lang.po
389
390With this line, po4a will produce separate POT and PO files for each document to translate.
391For example, if you have 3 documents and 5 languages, this will result in 3 POT files and
39215 PO files. These files are named as specified on the C<po4a_paths> template, with
393C<$master> substituted to the basename of each document to translate. In case of name
394conflict, you can specify the POT file to use as follows, with the C<pot=> parameter.
395This feature can also be used to group several translated files into the same POT file.
396
397 [po4a_langs] de fr ja
398 [po4a_paths] l10n/po/$master.pot $lang:l10n/po/$master.$lang.po
399 [type: xml] foo/gui.xml $lang:foo/gui.$lang.xml pot=foo-gui
400 [type: xml] bar/gui.xml $lang:bar/gui.$lang.xml pot=bar
401 [type: xml] bar/cli.xml $lang:bar/cli.$lang.xml pot=bar
402
403In split mode, B<po4a> builds a temporary compendium during the PO update, to
404share the translations between all the PO files. If two PO files have different
405translations for the same string, B<po4a> will mark this string as fuzzy and
406will submit both translations in all the PO files containing this string. When
407unfuzzied by the translator, the translation is automatically used in every PO
408files.
409
410=head2 Specifying the documents to translate
411
412You must also list the documents that should be translated. For each master
413file, you must specify the format parser to use, the location of the translated
414document to produce, and optionally some configuration. Here is an example:
415
416 [type: sgml] doc/my_stuff.sgml fr:doc/fr/mon_truc.sgml \
417              de:doc/de/mein_kram.sgml
418 [type: man] script fr:doc/fr/script.1 de:doc/de/script.1
419 [type: docbook] doc/script.xml fr:doc/fr/script.xml \
420             de:doc/de/script.xml
421
422But again, these complex lines are difficult to read and modify, e.g. when adding a new
423language. It is much simpler to reorganize things using the C<$lang> template as follows:
424
425 [type: sgml]    doc/my_stuff.sgml $lang:doc/$lang/my_stuff.sgml
426 [type: man]     script.1          $lang:po/$lang/script.1
427 [type: docbook] doc/script.xml    $lang:doc/$lang/script.xml
428
429=head2 Specifying options
430
431There is two types of options: I<po4a options> are default values to the po4a
432command line options while I<format options> are used to change the behavior of
433the format parsers. As a I<po4a options>, you could for example specify in your
434configuration file that the default value of the B<--keep> command line
435parameter is 50% instead of 80%. I<Format options> are documented on the
436specific page of each parsing module, e.g. L<Locale::Po4a::Xml(3pm)>. You could
437for example pass B<nostrip> to the XML parser to not strip the spaces around the
438extracted strings.
439
440You can pass these options for a specific master file, or even for a specific
441translation of that file, using C<opt:> and C<opt_XX:> for the C<XX> language.
442In the following example, the B<nostrip> option is passed to the XML parser (for
443all languages), while the threshold will be reduced to 0% for the French
444translation (that is thus always kept).
445
446 [type:xml] toto.xml $lang:toto.$lang.xml opt:"-o nostrip" opt_fr:"--keep 0"
447
448In any case, these configuration chunks must be located at the end of the line.
449The declaration of files must come first, then the addendum if any (see below),
450and then only the options. The grouping of configuration chunks is not very
451important, since elements are internally concatenated as strings. The following
452examples are all equivalent:
453
454  [type:xml] toto.xml $lang:toto.$lang.xml opt:"--keep 20" opt:"-o nostrip" opt_fr:"--keep 0"
455  [type:xml] toto.xml $lang:toto.$lang.xml opt:"--keep 20 -o nostrip" opt_fr:"--keep 0"
456  [type:xml] toto.xml $lang:toto.$lang.xml opt:--keep opt:20 opt:-o opt:nostrip opt_fr:--keep opt_fr:0
457
458Note that language specific options are not used when building the POT file. It
459is for example impossible to pass B<nostrip> to the parser only when building
460the French translation, because the same POT file is used to update every
461languages. So the only options that can be language-specific are the ones that
462are used when producing the translation, as the C<--keep> option.
463
464=head3 Configuration aliases
465
466To pass the same options to several files, the best is to define a type alias as
467follows. In the next example, C<--keep 0> is passed to every Italian translation
468using this C<test> type, that is an extension of the C<man> type.
469
470  [po4a_alias:test] man opt_it:"--keep 0"
471  [type: test] man/page.1 $lang:man/$lang/page.1
472
473You can also extend an existing type reusing the same name for the alias as
474follows. This is not interpreted as as an erroneous recursive definition.
475
476  [po4a_alias:man] man opt_it:"--keep 0"
477  [type: man] man/page.1 $lang:man/$lang/page.1
478
479=head3 Global default options
480
481You can also use C<[options]> lines to define options that must be used for all
482files, regardless of their type.
483
484  [options] --keep 20 --option nostrip
485
486As with the command line options, you can abbreviate the parameters passed in
487the configuration file:
488
489  [options] -k 20 -o nostrip
490
491=head3 Option priorities
492
493The options of every sources are concatenated, ensuring that the default values
494can easily be overridden by more specific options. The order is as follows:
495
496=over
497
498=item
499
500C<[options]> lines provide default values that can be overridden by any other source.
501
502=item
503
504Type aliases are then used. Language specific settings override the ones
505applicable to all languages.
506
507=item
508
509Settings that are specific to a given master file override both the default ones
510and the ones coming from the type alias. In this case also, language specific
511settings override the global ones.
512
513=item
514
515Finally, parameters provided on the B<po4a> command line override any settings
516from the configuration file.
517
518=back
519
520=head3 Example
521
522Here is an example showing how to quote the spaces and quotes:
523
524 [po_directory] man/po/
525
526 [options] --master-charset UTF-8
527
528 [po4a_alias:man] man opt:"-o \"mdoc=NAME,SEE ALSO\""
529 [type:man] t-05-config/test02_man.1 $lang:tmp/test02_man.$lang.1 \
530            opt:"-k 75" opt_it:"-L UTF-8" opt_fr:--verbose
531
532=head2 Addendum: Adding extra content in the translation
533
534If you want to add an extra section to the translation, for example to give
535credit to the translator, then you need to define an addendum to the line
536defining your master file. Please refer to the page L<po4a(7)> for more details
537on the syntax of addendum files.
538
539 [type: pod] script fr:doc/fr/script.1 \
540             add_fr:doc/l10n/script.fr.add
541
542You can also use language templates as follow:
543
544 [type: pod] script $lang:doc/$lang/script.1 \
545             add_$lang:doc/l10n/script.$lang.add
546
547If an addendum fails to apply, the translation is discarded.
548
549=head3 Modifiers for the addendum declaration
550
551Addendum modifiers can simplify the configuration file in the case where not all
552languages provide an addendum, or when the list of addenda changes from one
553language to the other. The modifier is a single char located before the file name.
554
555=over 2
556
557=item B<?>
558
559Include I<addendum_path> if this file does exist, otherwise do nothing.
560
561=item B<@>
562
563I<addendum_path> is not a regular addendum but a file containing a list of
564addenda, one by line.  Each addendum may be preceded by modifiers.
565
566=item B<!>
567
568I<addendum_path> is discarded, it is not loaded and will not be loaded by
569any further addendum specification.
570
571=back
572
573The following includes an addendum in any language, but if only it exists. No
574error is reported if the addendum does not exist.
575
576 [type: pod] script $lang:doc/$lang/script.1  add_$lang:?doc/l10n/script.$lang.add
577
578The following includes a list of addendum for every language:
579
580 [type: pod] script $lang:doc/$lang/script.1  add_$lang:@doc/l10n/script.$lang.add
581
582=head2 Filtering the translated strings
583
584Sometimes, you want to hide some strings from the translation process. To
585that extend, you can give a C<pot_in> parameter to your master file to specify
586the name of the file to use instead of the real master when building the POT
587file. Here is an example:
588
589  [type:docbook] book.xml          \
590          pot_in:book-filtered.xml \
591          $lang:book.$lang.xml
592
593With this setting, the strings to translate will be extracted from the
594F<book-filtered.xml> (that must be produced before calling B<po4a>) while the
595translated files will be built from F<book.xml>. As a result, any string that is
596part of F<book.xml> but not in F<book-filtered.xml> will not be included in the
597PO files, preventing the translators from providing a translation for them. So
598these strings will be left unmodified when producing the translated documents.
599This naturally decreases the level of translation, so you may need the C<--keep>
600option to ensure that the document is produced anyway.
601
602=head2 CONFIGURATION EXAMPLE
603
604TODO: Is this section really useful?
605
606Let's assume you maintain a program named B<foo> which has a man page F<man/foo.1>
607which naturally is maintained in English only. Now you as the upstream or
608downstream maintainer want to create and maintain the translation.
609First you need to create the POT file necessary to send to translators
610using L<po4a-gettextize(1)>.
611
612So for our case we would call
613
614 cd man && po4a-gettextize -f man -m foo.1 -p foo.pot
615
616You would then send this file to the appropriate language lists or offer
617it for download somewhere on your website.
618
619Now let's assume you received three translations before your next release:
620F<de.po> (including an addendum F<de.add>), F<sv.po> and F<pt.po>.
621Since you don't want to change your F<Makefile>(s) whenever a new translation
622arrives you can use B<po4a> with an appropriate configuration file in your F<Makefile>.
623Let's call it F<po4a.cfg>. In our example it would look like the following:
624
625 [po_directory] man/po4a/po/
626
627 [type: man] man/foo.1 $lang:man/translated/$lang/foo.1 \
628            add_$lang:?man/po4a/add_$lang/$lang.add opt:"-k 80"
629
630In this example we assume that your generated man pages (and all PO and addenda
631files) should be stored in F<man/translated/$lang/> (respectively in F<man/po4a/po/> and
632F<man/po4a/add_$lang/>) below the current directory. In our example
633the F<man/po4a/po/> directory would include F<de.po>, F<pt.po> and F<sv.po>,
634and the F<man/po4a/add_de/> directory would include F<de.add>.
635
636Note the use of the modifier B<?> as only the German translation (F<de.po>) is
637accompanied by an addendum.
638
639To actually build the translated man pages you would then (once!) add the
640following line in the B<build> target of the appropriate F<Makefile>:
641
642        po4a po4a.cfg
643
644Once this is set up you don't need to touch the F<Makefile> when a new
645translation arrives, i.e. if the French team sends you F<fr.po> and F<fr.add>
646then you simply drop them respectively in F<man/po4a/po/> and
647F<man/po4a/add_fr/> and the next time the program is built the
648French translation is automatically build as well in F<man/translated/fr/>.
649
650Note that you still need an appropriate target to install localized manual
651pages with English ones.
652
653Finally if you do not store generated files into your version control system,
654you will need a line in your B<clean> target as well:
655        -rm -rf man/translated
656
657=head1 SEE ALSO
658
659L<po4a-gettextize(1)>,
660L<po4a-normalize(1)>,
661L<po4a-translate(1)>,
662L<po4a-updatepo(1)>,
663L<po4a(7)>.
664
665=head1 AUTHORS
666
667 Denis Barbier <barbier@linuxfr.org>
668 Nicolas François <nicolas.francois@centraliens.net>
669 Martin Quinson (mquinson#debian.org)
670
671=head1 COPYRIGHT AND LICENSE
672
673Copyright 2002-2020 by SPI, inc.
674
675This program is free software; you may redistribute it and/or modify it
676under the terms of GPL (see the COPYING file).
677
678=cut
679
680use 5.006;
681use strict;
682use warnings;
683
684use Getopt::Long qw(GetOptions);
685
686use Locale::Po4a::Chooser;
687use Locale::Po4a::TransTractor;
688use Locale::Po4a::Common;
689use Locale::Po4a::Po qw(move_po_if_needed);
690
691use Pod::Usage qw(pod2usage);
692
693use File::Temp;
694use File::Basename;
695use File::Copy;
696use File::Spec;
697use Fcntl;    # sysopen flags
698use Cwd;      # cwd
699use Config;
700
701Locale::Po4a::Common::textdomain('po4a');
702
703sub show_version {
704    Locale::Po4a::Common::show_version("po4a");
705    exit 0;
706}
707
708# keep the command line arguments
709my @ORIGINAL_ARGV = @ARGV;
710
711# Use /NUL instead of /dev/null on Windows
712my $devnull = ( $^O =~ /Win/ ) ? '/NUL' : '/dev/null';
713
714# Parse the options provided on the command line, or in argument
715sub get_options {
716    if ( defined $_[0] ) {
717        @ARGV = @_;
718
719        #        map {print "o: $_\n"} @ARGV;
720    } else {
721        @ARGV = @ORIGINAL_ARGV;
722    }
723
724    # temporary array for GetOptions
725    my @verbose   = ();
726    my @options   = ();
727    my @variables = ();
728    my $previous;
729    my $noprevious;
730    my $msgmerge_opt = '';
731
732    my %opts = (
733        "help"               => 0,
734        "type"               => "",
735        "debug"              => 0,
736        "verbose"            => 0,
737        "quiet"              => 0,
738        "force"              => 0,
739        "no-translations"    => 0,
740        "no-update"          => 0,
741        "keep-translations"  => 0,
742        "rm-translations"    => 0,
743        "no-backups"         => 0,
744        "rm-backups"         => 0,
745        "threshold"          => 80,
746        "mastlang"           => "",
747        "mastchar"           => "",
748        "locchar"            => "",
749        "addchar"            => "",
750        "options"            => { "verbose" => 0, "debug" => 0 },
751        "variables"          => {},
752        "partial"            => [],
753        "porefs"             => "full",
754        "wrap-po"            => undef,
755        "copyright-holder"   => undef,
756        "msgid-bugs-address" => undef,
757        "package-name"       => undef,
758        "package-version"    => undef,
759        "srcdir"             => undef,
760        "destdir"            => undef,
761        "calldir"            => cwd()
762    );
763    Getopt::Long::config( 'bundling', 'no_getopt_compat', 'no_auto_abbrev' );
764    GetOptions(
765        'help|h' => \$opts{"help"},
766
767        'master-language=s'     => \$opts{"mastlang"},
768        'master-charset|M=s'    => \$opts{"mastchar"},
769        'localized-charset|L=s' => \$opts{"locchar"},
770        'addendum-charset|A=s'  => \$opts{"addchar"},
771
772        'verbose|v'            => \@verbose,
773        'debug|d'              => \$opts{"debug"},
774        'force|f'              => \$opts{"force"},
775        'stamp'                => \$opts{"stamp"},
776        'quiet|q'              => \$opts{"quiet"},
777        'keep|k=s'             => \$opts{"threshold"},
778        'no-translations'      => \$opts{"no-translations"},
779        'no-update'            => \$opts{"no-update"},
780        'keep-translations'    => \$opts{"keep-translations"},
781        'rm-translations'      => \$opts{"rm-translations"},
782        'translate-only=s'     => \@{ $opts{"partial"} },
783        'no-backups'           => \$opts{"no-backups"},
784        'rm-backups'           => \$opts{"rm-backups"},
785        'version|V'            => \&show_version,
786        'option|o=s'           => \@options,
787        'variable=s'           => \@variables,
788        'porefs=s'             => \$opts{"porefs"},
789        'wrap-po=s'            => \$opts{"wrap-po"},
790        'copyright-holder=s'   => \$opts{"copyright-holder"},
791        'msgid-bugs-address=s' => \$opts{"msgid-bugs-address"},
792        'package-name=s'       => \$opts{"package-name"},
793        'package-version=s'    => \$opts{"package-version"},
794        'no-previous'          => \$noprevious,
795        'previous'             => \$previous,
796        'msgmerge-opt=s'       => \$msgmerge_opt,
797        'srcdir=s'             => \$opts{"srcdir"},
798        'destdir=s'            => \$opts{"destdir"}
799    ) or pod2usage();
800
801    $opts{"verbose"} = scalar @verbose;
802    $opts{"verbose"} = 0 if $opts{"quiet"};
803    $opts{"verbose"} ||= 1 if $opts{"debug"};
804    $opts{"msgmerge-opt"} = '';
805    $opts{"msgmerge-opt"} .= "--previous " unless $noprevious;
806    $opts{"msgmerge-opt"} .= "--add-location=file " if ( $opts{'porefs'} =~ m/^file/ );
807    $opts{"msgmerge-opt"} .= $msgmerge_opt;
808
809    # options to transmit to the modules
810    $opts{"options"} = {
811        "verbose" => $opts{"verbose"},
812        "debug"   => $opts{"debug"}
813    };
814    foreach (@options) {
815        if (m/^([^=]*)=(.*)$/) {
816            $opts{"options"}{$1} = "$2";
817        } else {
818            $opts{"options"}{$_} = 1;
819        }
820    }
821
822    foreach (@variables) {
823        if (m/^([^=]*)=(.*)$/) {
824            $opts{"variables"}{$1} = "$2";
825        }
826    }
827
828    # The rm- options imply the no-
829    $opts{"no-translations"} = 1 if $opts{"rm-translations"};
830
831    if ( defined $opts{"srcdir"} and not -d $opts{"srcdir"} ) {
832        die wrap_msg( gettext("Option %s invalid. Directory %s does not exist (current directory: %s)."),
833            "srcdir", $opts{"srcdir"}, cwd() );
834    }
835    if ( defined $opts{"destdir"} and not -d $opts{"destdir"} ) {
836        die wrap_msg( gettext("Option %s invalid. Directory %s does not exist (current directory: %s)."),
837            "destdir", $opts{"destdir"}, cwd() );
838    }
839
840    #    print STDERR "msgmerge: ".$opts{"msgmerge-opt"}."\n";
841    return %opts;
842}
843
844# Parse a config line and extract the parameters that correspond to options.
845# These options are appended to the options provided in argument (as a
846# reference to an hash). The options are sorted by category in this hash.
847# The categories are: global and the various languages.
848sub parse_config_options {
849    my $ref       = shift;    # a line reference for the die messages
850    my $line      = shift;    # the line to parse
851    my $orig_line = $line;    # keep the original line for die messages
852    my $options   = shift;    # reference to an hash of options
853
854    while ( defined $line and $line !~ m/^\s*$/ ) {
855        if ( $line =~ m/^\s*(?:opt(?:_(.+?))?:)?\s*(.+?)\s*$/ ) {
856            my $lang = $1 // 'global';
857            $line = $2;
858            my $opt = "";
859            if ( $line =~ m/^\s*"(.+?(?<!\\)(?:\\\\)*)"(?:\s+(.+))?$/ ) {
860
861                # take up to the next " not preceded by an odd number of \
862                $opt  = $1;
863                $line = $2;
864            } elsif ( $line =~ m/^\s*([^\s]+?)(?:\s+(.+))?$/ ) {
865
866                # Use the first space separated arg
867                $opt  = $1;
868                $line = $2;
869            } else {
870
871                # TRANSLATOR: There is two chunks in the following:  opt:"--keep 20" opt:"-o nowrap"
872                die wrap_ref_mod( "$ref", "", gettext("Unparsable option chunk '%s' (%s)."), $line, $orig_line );
873            }
874
875            if ( !defined $options->{$lang} ) {
876                $options->{$lang} = $opt;
877            } else {
878                $options->{$lang} .= " $opt";
879            }
880        } else {
881            print STDERR wrap_ref_mod( "$ref", "", gettext("Skipping option chunk '%s'."), $line );
882            last;
883        }
884    }
885
886    $line = "" unless defined $line;
887    return $line;
888}
889
890my %po4a_opts = get_options(@ARGV);
891
892our ( $destdir, $srcdir, $calldir ) = ( $po4a_opts{'destdir'}, $po4a_opts{'srcdir'}, $po4a_opts{'calldir'} );
893
894sub find_input_file {
895    my $filename = $_[0];
896    my $debug    = $_[1];
897    print STDERR "Search for input file $filename\n" if $debug;
898    return $filename                                 if ( File::Spec->file_name_is_absolute($filename) );
899    foreach ( ( $destdir, $calldir, $srcdir ) ) {
900        next unless defined $_;
901        my $p = File::Spec->catfile( $_, $filename );
902        print STDERR "Test $p: " . ( -e $p ? "OK" : "nope" ) . "\n"
903          if defined $debug;
904        return $p if -e $p;
905    }
906    return $filename;
907}
908
909sub find_output_file {
910    my $filename = $_[0];
911    return $filename if ( File::Spec->file_name_is_absolute($filename) );
912    foreach ( ( $destdir, $calldir ) ) {
913        next unless defined $_;
914        return File::Spec->catfile( $_, $filename ) if -d $_ and -w $_;
915    }
916    return $filename;
917}
918
919# Argument check
920$po4a_opts{"help"} && pod2usage( -verbose => 1, -exitval => 0 );
921
922sub run_cmd {
923    my $cmd = shift;
924    print $cmd. "\n" if $po4a_opts{"debug"};
925
926    my $out = qx/$cmd 2>&1/;
927    unless ( $? == 0 ) {
928        my $err = "";
929        if ( $? == -1 ) {
930            $err = sprintf( gettext("failed to execute '%s': %s."), $cmd, $! );
931        } elsif ( $? & 127 ) {
932            if ( $? & 128 ) {
933                $err = sprintf( gettext("'%s' died with signal %d, with coredump."), $cmd, $? & 127 );
934            } else {
935                $err = sprintf( gettext("'%s' died with signal %d, without coredump."), $cmd, $? & 127 );
936            }
937        } else {
938            $err = sprintf( gettext("'%s' exited with value %d."), $cmd, $? >> 8 );
939        }
940
941        die wrap_msg( gettext("Error: %s"), $err );
942    }
943    return $out;
944}
945
946my $config_file = shift(@ARGV) || pod2usage();
947
948# Check file existence
949-e $config_file || die wrap_msg( gettext("File %s does not exist."), $config_file );
950
951# Parse the config file
952my (@langs);
953my (%aliases);    # module aliases ([po4a_alias:...]
954my $pot_filename     = "";    # In split mode, this is the temp file name for the big POT
955my $pot_filename_cfg = "";    # This is never modified, useful in split mode to find the cfg content
956my (%po_filename);            # po_files: '$lang'=>'$path'
957my (%add_filename);           # Global addendum_files: '$lang'=>array of '$path'
958
959# The document hash table contains all the information about the files to translate.
960# Each key of the hash map a master file, ie a file to translate. For each such master file, we have a sub-hashmap containing:
961#  'format' => '$format': string designating the po4a module to use
962#  '$lang'  => '$path': one string entry per language, the path of the translated file
963# Optional parts:
964#  'add_$lang'=>('$path','$path'): one vector per language, the list of all addendum for that language
965#  'pot_in' => '$master': The master file to use in input when building the POT (allows to filter out some things)
966#  'pot'    => the filename to use when building the POT file in split mode (when not provided, basename of master is used)
967my (%document);
968
969my $doc_count = 0;
970my %partial   = ( 'master' => {}, 'files' => {}, 'lang' => {} );
971open CONFIG, "<", "$config_file" or die wrap_msg( gettext("Cannot open %s: %s"), $config_file, $! );
972my ( $line, $nb ) = ( "", 0 );
973
974while (<CONFIG>) {
975    $nb++;
976    s/#.*//;
977    $line .= $_;
978    $line =~ s/\t/ /g;
979    $line =~ s/ +/ /g;
980    while ( $line =~ m/\$\((\w+)\)/ ) {
981        if ( defined $po4a_opts{"variables"}{$1} ) {
982            $line =~ s/\$\((\Q$1\E)\)/$po4a_opts{"variables"}{$1}/g;
983        } else {
984            die wrap_ref_mod( "$config_file:$nb", "", gettext("Unknown variable: %s"), $1 );
985        }
986    }
987    $line =~ s/^ //;
988    $line =~ s/ $//;
989    chomp($line);
990    next if ( $line =~ s/\\$// );
991    next unless ( $line =~ /\S/ );
992
993    my $args = $line;
994    die wrap_ref_mod( "$config_file:$nb", "", gettext("Syntax error: %s"), $line )
995      unless ( $args =~ s/^\[([^\]]*)\] *// );
996    my $cmd    = $1;
997    my $main   = ( $args =~ s/^(\S+) *// ? $1 : "" );
998    my $pot_in = $main;                                 # Default for POT file input is $main
999
1000    if (@langs) {
1001
1002        # Expand the $lang templates
1003        my ($args2) = "";
1004        foreach my $arg ( split( / /, $args ) ) {
1005            if ( $arg =~ /\$lang\b/ ) {
1006
1007                # Expand for all the langs
1008                foreach my $lang (@langs) {
1009                    my ($arg2) = $arg;
1010                    $arg2 =~ s/\$lang\b/$lang/g;
1011                    $args2 .= $arg2 . " ";
1012                }
1013            } else {
1014
1015                # Leave the argument as is
1016                $args2 .= $arg . " ";
1017            }
1018        }
1019        $args = $args2;
1020    }
1021
1022    print "cmd=[$cmd]; $main / $args\n" if $po4a_opts{"debug"};
1023
1024    if ( $cmd eq "po4a_paths" ) {
1025        die wrap_ref_mod( "$config_file:$nb", "", gettext("'%s' redeclared"), "po4a_path" )
1026          if ( length $pot_filename );
1027        $pot_filename     = $main;
1028        $pot_filename_cfg = $main;
1029        warn wrap_ref_mod(
1030            "$config_file:$nb",
1031            "",
1032            gettext(
1033                "Your POT file '%s' file name does not end with the 'pot' extension. This is unusual and will trouble the translators."
1034            ),
1035            $pot_filename
1036        ) unless $pot_filename =~ m/\.pot$/;
1037        my @split_args = split( / /, $args );
1038        warn wrap_ref_mod(
1039            "$config_file:$nb",
1040            "",
1041            gettext("No PO file provided as subsequent parameters to '%s'. Your documentation will not be translated."),
1042            "po4a_path"
1043        ) unless scalar @split_args > 0;
1044
1045        foreach my $arg (@split_args) {
1046            die wrap_ref_mod( "$config_file:$nb", "", gettext("Unparsable argument '%s'."), $arg )
1047              unless ( $arg =~ /^([^:]*):(.*)/ );
1048            my ( $component, $path ) = ( $1, $2 );
1049            if ( $component =~ m/^add_(.*)$/ ) {    # This is an addendum declaration
1050                my $lang    = $1;
1051                my @addlist = ($path);
1052                while ( scalar @addlist ) {
1053                    my $elem = shift @addlist;
1054                    my $modifiers;
1055                    $elem =~ s/^([@!?]+)// and $modifiers = $1;
1056                    if ( defined $modifiers ) {
1057                        next if ( $modifiers =~ m/!/ );
1058                        next if ( $modifiers =~ m/\?/ and not -e find_input_file($elem) );
1059                        if ( $modifiers =~ m/@/ ) {
1060                            open LIST, "<", find_input_file($elem)
1061                              or die wrap_msg( gettext("Cannot open %s: %s"), $elem, $! );
1062                            while (<LIST>) {
1063                                chomp;
1064                                s/\s+$//;
1065                                next if length($_) == 0 or $_ =~ m/^\s*#/;
1066                                while (m/\$\((\w+)\)/) {
1067                                    if ( defined $po4a_opts{"variables"}{$1} ) {
1068                                        s/\$\((\Q$1\E)\)/$po4a_opts{"variables"}{$1}/g;
1069                                    } else {
1070                                        die wrap_ref_mod( "$config_file:$nb", "", gettext("Unknown variable: %s"), $1 );
1071                                    }
1072                                }
1073                                push @addlist, $_;
1074                            }
1075                            close LIST;
1076                            next;
1077                        }
1078                    }
1079                    push @{ $add_filename{$lang} }, $elem;
1080                }
1081
1082            } else {    # Not an addendum. It must be a po filename
1083                $po_filename{$1} = $path;
1084            }
1085        }
1086
1087    } elsif ( $cmd eq "po4a_langs" ) {
1088        die wrap_ref_mod( "$config_file:$nb", "", gettext("'%s' redeclared"), "po4a_langs" )
1089          if (@langs);
1090        @langs = split( / /, $main . " " . $args );
1091
1092    } elsif ( $cmd eq "po_directory" ) {
1093        die wrap_ref_mod( "$config_file:$nb", "", gettext("The list of languages cannot be set twice.") )
1094          if scalar @langs;
1095        die wrap_ref_mod( "$config_file:$nb", "", gettext("The POT file cannot be set twice.") )
1096          if length $pot_filename;
1097
1098        # Search for the candidate PO and POT files
1099        # The difficulty is that the directory itself must be searched in destdir, calldir, srcdir and then the PO files must be searched in there.
1100        # This mechanism is easy to get wrong (see eg #960892), so let's get cautionous and verbose on error.
1101        # Step 1. collect the files in every candidate po_directory we find.
1102        my $po_directory = $main;
1103        my @po_files;
1104        print STDERR "Searching for po_directory '$po_directory'."
1105          if $po4a_opts{"debug"};
1106        if ( File::Spec->file_name_is_absolute($po_directory) ) {
1107            print STDERR wrap_msg( gettext("Using '%s' as a %s."), $po_directory, "po_directory" )
1108              if $po4a_opts{"verbose"};
1109            die wrap_ref_mod( "$config_file:$nb", "", gettext("'%s' is not a directory (current dir: %s)"),
1110                $po_directory, cwd() )
1111              unless ( -d $po_directory );
1112
1113            opendir PO_DIR, find_input_file($po_directory)
1114              or die wrap_ref_mod( "$config_file:$nb", "", gettext("Cannot list the '%s' directory"), $po_directory );
1115            map { push @po_files, $_ if -e $_; } ( sort readdir PO_DIR );
1116        } else {
1117            my $found = 0;
1118            foreach my $basedir ( $destdir, $calldir // ".", $srcdir ) {
1119                next unless defined $basedir;
1120                my $candidate = "$basedir/$po_directory";
1121                if ( -e $candidate && -d $candidate ) {
1122                    $found = 1;
1123                    print STDERR wrap_msg( gettext("Using '%s' as a %s."), $candidate, "po_directory" )
1124                      if $po4a_opts{"verbose"};
1125
1126                    opendir PO_DIR,
1127                      $candidate
1128                      or die wrap_ref_mod( "$config_file:$nb", "", gettext("Cannot list directory '%s' in '%s' "),
1129                        $po_directory, $basedir );
1130                    map { push @po_files, "$po_directory/$_" if -e "$candidate/$_"; } ( sort readdir PO_DIR );
1131                }
1132            }
1133            unless ($found) {
1134                die wrap_ref_mod( "$config_file:$nb", "", gettext("Directory '%s' not found in '%s'."),
1135                    $po_directory, $calldir )
1136                  if ( not defined $destdir && not defined $srcdir );
1137                die wrap_ref_mod( "$config_file:$nb", "", gettext("Directory '%s' not found in '%s' nor in '%s'."),
1138                    $po_directory, $destdir, $calldir )
1139                  if ( defined $destdir && not defined $srcdir );
1140                die wrap_ref_mod( "$config_file:$nb", "", gettext("Directory '%s' not found in '%s' nor in '%s'."),
1141                    $po_directory, $calldir, $srcdir )
1142                  if ( not defined $destdir && defined $srcdir );
1143                die wrap_ref_mod( "$config_file:$nb", "",
1144                    gettext("Directory '%s' not found in '%s' nor in '%s' nor in '%s'."),
1145                    $po_directory, $destdir, $calldir, $srcdir );
1146            }
1147        }
1148
1149        # Step 2: separate the PO and POT files we found in all traversed directories
1150        my %seen = ();    # the elements of po_files that we saw already (we need to ignore the dupplicates)
1151        foreach my $f (@po_files) {
1152            next if $seen{$f}++;    # we saw it already
1153            if ( $f =~ m|^(.*?)/([^/]*)\.po$| ) {
1154                push @langs, $2;
1155                die wrap_ref_mod( "$config_file:$nb", gettext("Language '%s' found twice in '%s' and '%s'."),
1156                    $po_filename{$2}, $f )
1157                  if defined $po_filename{$2} && $po_filename{$2} ne $f;
1158                $po_filename{$2} = $f;
1159                print wrap_msg( gettext("Found language '%s' in the provided po_directory: %s"), $2, $f )
1160                  if $po4a_opts{"verbose"};
1161            }
1162            if ( $f =~ m/\.pot$/ ) {
1163                die wrap_msg( gettext("%s:%d: too many POT files: '%s' and '%s'."), $config_file, $nb, $pot_filename,
1164                    $f )
1165                  if ( length $pot_filename );
1166                $pot_filename = $f;
1167                print wrap_msg( gettext("Found POT file '%s' in the provided po_directory."), $pot_filename )
1168                  if $po4a_opts{"verbose"};
1169            }
1170        }
1171        undef %seen;
1172
1173        # Step 3: if no PO file found, make a sensible error message.
1174        # We could factorize the error message, but it would break gettext()
1175        if ( not @langs ) {
1176            if ( not defined $destdir && not defined $srcdir ) {
1177                warn wrap_ref_mod( "$config_file:$nb", "", gettext("No PO files found in '%s'/'%s'."), $calldir,
1178                    $main );
1179            } elsif ( defined $destdir && not defined $srcdir ) {
1180                warn wrap_ref_mod( "$config_file:$nb", "", gettext("No PO files found in '%s'/'%s' nor in '%s'/'%s'."),
1181                    $destdir, $main, $calldir, $main );
1182            } elsif ( not defined $destdir && defined $srcdir ) {
1183                warn wrap_ref_mod( "$config_file:$nb", "", gettext("No PO files found in '%s'/'%s' nor in '%s'/'%s'."),
1184                    $calldir, $main, $srcdir, $main );
1185            } else {
1186                warn wrap_ref_mod( "$config_file:$nb", "",
1187                    gettext("No PO files found in '%s'/'%s' nor in '%s'/'%s' nor in '%s'/'%s'."),
1188                    $destdir, $main, $calldir, $main, $srcdir, $main );
1189            }
1190            warn wrap_ref_mod( "$config_file:$nb", "",
1191                gettext("Your project will not be translated to any language.") );
1192        }
1193
1194        # Step 4: if no POT file found, make a sensible error message
1195        if ( length $pot_filename == 0 ) {
1196            if ( not defined $destdir && not defined $srcdir ) {
1197                warn wrap_ref_mod( "$config_file:$nb", "", gettext("No POT files found in '%s'/'%s'."), $calldir,
1198                    $main );
1199            } elsif ( defined $destdir && not defined $srcdir ) {
1200                warn wrap_ref_mod( "$config_file:$nb", "", gettext("No POT files found in '%s'/'%s' nor in '%s'/'%s'."),
1201                    $destdir, $main, $calldir, $main );
1202            } elsif ( not defined $destdir && defined $srcdir ) {
1203                warn wrap_ref_mod( "$config_file:$nb", "", gettext("No POT files found in '%s'/'%s' nor in '%s'/'%s'."),
1204                    $calldir, $main, $srcdir, $main );
1205            } else {
1206                warn wrap_ref_mod( "$config_file:$nb", "",
1207                    gettext("No POT files found in '%s'/'%s' nor in '%s'/'%s' nor in '%s'/'%s'."),
1208                    $destdir, $main, $calldir, $main, $srcdir, $main );
1209            }
1210            warn wrap_ref_mod( "$config_file:$nb", "",
1211                gettext("Please create an empty file with the 'pot' extension (e.g. 'myproject.pot').") );
1212        }
1213
1214    } elsif ( $cmd =~ m/type: *(.*)/ ) {
1215
1216        if ( defined $document{$main}{'format'} ) {
1217            warn wrap_ref_mod(
1218                "$config_file:$nb",
1219                "",
1220                gettext(
1221                    "The master file '%s' was specified earlier in the configuration file. This may cause problems with options."
1222                ),
1223                $main
1224            ) unless ( $po4a_opts{"quiet"} );
1225        } elsif ( not -e find_input_file($main) ) {
1226            die wrap_ref_mod( "$config_file:$nb", "", gettext("The master file '%s' does not exist."),
1227                find_input_file($main) );
1228        }
1229        if ( scalar @{ $po4a_opts{"partial"} } ) {
1230            foreach my $file ( @{ $po4a_opts{"partial"} } ) {
1231                if ( $args =~ m/(\S+):\Q$file\E\b/ ) {
1232                    $partial{'lang'}{$1}      = 1;
1233                    $partial{'master'}{$main} = 1;
1234                    $partial{'files'}{$file}  = 1;
1235                    last;
1236                }
1237            }
1238        }
1239        $document{$main}{'format'} = $1;
1240        $document{$main}{'pot_in'} = $pot_in;
1241        $document{$main}{'pos'}    = $doc_count;
1242        $doc_count++;
1243
1244        # Options
1245        my %options;
1246
1247        # 1. Use the global options ([opt] ...)
1248        #        %options = %{ $document{''}{'options'} }
1249        #          if defined $document{''}{'options'};
1250
1251        # 2. Use the alias options if any
1252        if ( defined $aliases{$1} ) {
1253            $document{$main}{'format'} = $aliases{$1}{"module"};
1254            if ( defined $aliases{$1}{"options"} ) {
1255
1256                #                print STDERR "Override the global options with the alias ones\n";
1257                %options = %{ $aliases{$1}{"options"} };    # XXX not a merge, but overwrite
1258            }
1259        }
1260
1261        # 3. If this file was already specified, reuse the previous
1262        #    options (no merge)
1263        %options = %{ $document{$main}{'options'} }
1264          if defined $document{$main}{'options'};
1265
1266        # 4. Save the name of the master doc, to handle "$master" substitution in strings
1267        warn(
1268            wrap_ref_mod(
1269                "$config_file:$nb", "",
1270                gettext("Option 'master:file' is deprecated. Please use 'pot' instead for the same effect.")
1271            )
1272        ) if ( $args =~ m/ +master:file=\S/ );
1273
1274        if ( $args =~ s/ +master:file=(\S+)// || $args =~ s/ +pot=(\S+)// ) {
1275            $document{$main}{'pot'} = $1;
1276        }
1277
1278        # 5. Merge the document specific options
1279        # separate the end of the line, which contains options.
1280        # Something more clever could be done to allow options in the
1281        # middle of a line.
1282        if ( $args =~ m/^(.*?) +(opt(_.+)?:(.*))$/ ) {
1283            $args = $1 // "";
1284            $args .= " " . parse_config_options( "$config_file:$nb", $2, \%options );
1285        }
1286        %{ $document{$main}{'options'} } = %options;
1287
1288        my %discarded      = ();
1289        my @remaining_args = split( / /, $args );
1290        while (@remaining_args) {
1291            my $arg = shift(@remaining_args);
1292            die wrap_ref_mod( "$config_file:$nb", "", gettext("Unparsable argument '%s' (%s)."), $arg, $line )
1293              unless ( $arg =~ /^([^:]*):(.*)/ );
1294            my ( $lang, $trans ) = ( $1, $2 );
1295            die wrap_ref_mod( "$config_file:$nb", "", gettext("The translated and master file are the same.") )
1296              if ( $main eq $trans );
1297
1298            if ( $lang =~ /^add_/ ) {
1299                my $modifiers;
1300                $trans =~ s/^([@!?]+)// and $modifiers = $1;
1301                next if defined( $discarded{$trans} );
1302                if ( defined $modifiers ) {
1303                    if ( $modifiers =~ m/!/ ) {
1304                        $discarded{$trans} = 1;
1305                        next;
1306                    }
1307                    next if ( $modifiers =~ m/\?/ and not -e find_input_file($trans) );
1308                    if ( $modifiers =~ m/@/ ) {
1309                        open LIST, "<", find_input_file($trans)
1310                          or die wrap_msg( gettext("Cannot open %s: %s"), $trans, $! );
1311                        my @new_list = ();
1312                        while (<LIST>) {
1313                            chomp;
1314                            s/\s+$//;
1315                            next if length($_) == 0 or $_ =~ m/^\s*#/;
1316                            while (m/\$\((\w+)\)/) {
1317                                if ( defined $po4a_opts{"variables"}{$1} ) {
1318                                    s/\$\((\Q$1\E)\)/$po4a_opts{"variables"}{$1}/g;
1319                                } else {
1320                                    die wrap_ref_mod( "$config_file:$nb", "", gettext("Unknown variable: %s"), $1 );
1321                                }
1322                            }
1323                            push( @new_list, "$lang:$_" );
1324                        }
1325                        close LIST;
1326                        unshift( @remaining_args, @new_list );
1327                    } else {
1328                        push @{ $document{$main}{$lang} }, $trans;
1329                    }
1330                } else {
1331                    push @{ $document{$main}{$lang} }, $trans;
1332                }
1333            } elsif ( $lang eq "pot_in" ) {
1334                $document{$main}{$lang} = $trans;    # override POT file input
1335                print " .......... divert pot_in=$trans\n" if $po4a_opts{"debug"};
1336            } else {
1337                die wrap_ref_mod( "$config_file:$nb", "", gettext("Translation of %s in %s redefined"), $main, $lang )
1338                  if ( defined $document{$main}{$lang} );
1339                $document{$main}{$lang} = $trans;
1340            }
1341        }
1342    } elsif ( $cmd =~ m/po4a_alias: *(.*)/ ) {
1343        my $name  = $1;
1344        my %alias = ();
1345        $alias{"module"} = $main;
1346        my %options;
1347        $args = parse_config_options( "$config_file:$nb", $args, \%options );
1348        %{ $alias{"options"} } = %options;
1349        %{ $aliases{$name} } = %alias;
1350    } elsif ( $cmd eq "options" ) {
1351        my %options;
1352        %options = %{ $document{''}{"options"} } if ( defined $document{''}{"options"} );  # Add to the existing options
1353
1354        parse_config_options( "$config_file:$nb", "$main $args", \%options );
1355        %{ $document{''}{"options"} } = %options;
1356    } else {
1357        die wrap_ref_mod( "$config_file:$nb", "", gettext("Unparsable command '%s'."), $cmd );
1358    }
1359
1360    $line = "";
1361}
1362close CONFIG;                                                                              # don't care about error here
1363die wrap_msg( gettext("'po4a_paths' is not defined in the configuration file. Where are the POT and PO files?") )
1364  unless ( length $pot_filename );
1365
1366# Parse a vector of parameters, and split them on spaces.
1367# If passed ('aa', 'bb', 'cc dd'), it splits the last parameter and returns ('aa', 'bb', 'cc', 'dd'),
1368sub split_opts {
1369    my $options      = shift;
1370    my $options_orig = $options;
1371    my @opts         = ();
1372    $options =~ s/\\"/"/g;
1373    while ( length $options ) {
1374        my $o = "";
1375        while ( length $options and $options !~ /^ /s ) {
1376            if (    ( $options =~ m/^(["'])/ )
1377                and ( $options !~ m/^(["'])(?:\\.|(?!\1)[^\\])*\1/ ) )
1378            {
1379                die wrap_msg( gettext("Cannot parse option line (missing >%s<?): %s"), $1, $options_orig );
1380            }
1381
1382            # Extract non quoted parts
1383            $options =~ s/^([^\\"' ]*)//s;
1384            $o .= $1 if defined $1;
1385
1386            # And extract quoted parts
1387            $options =~ s/^(["'])((?:\\.|(?!\1)[^\\])*)\1//s;
1388            $o .= $2 if defined $2;
1389        }
1390        $options =~ s/^ *//s;
1391        push @opts, $o;
1392    }
1393
1394    return @opts;
1395}
1396
1397# Adds the parameters passed in the global section of the config file to the ones passed on the command line
1398if (    defined $document{''}{"options"}
1399    and defined $document{''}{"options"}{"global"} )
1400{
1401    %po4a_opts = get_options( split_opts( $document{''}{"options"}{"global"} ), @ORIGINAL_ARGV );
1402}
1403
1404# make a big pot
1405my $update_pot_file = 0;
1406if ( $pot_filename =~ m/\$master/ ) {
1407    printf( gettext("Split mode, creating a temporary POT:") )
1408      if $po4a_opts{"verbose"};
1409    if ( scalar @{ $po4a_opts{"partial"} } ) {
1410        print wrap_msg( gettext("Disabling --translate-only option, it is not supported in split mode") . "\n" );
1411        $po4a_opts{"partial"} = [];
1412    }
1413
1414    # The POT needs to be generated anyway.
1415    $update_pot_file = 1;
1416    $po4a_opts{"split"} = 1;
1417} else {
1418    if ( scalar @{ $po4a_opts{"partial"} } ) {
1419
1420        # Skip documents not specified, strings are read directly from POT file
1421        foreach my $master ( keys %document ) {
1422            next unless length $master;
1423            delete $document{$master} unless exists $partial{'master'}{$master};
1424        }
1425
1426        # Do not read PO files if no file is processed for this language
1427        foreach my $lang ( keys %po_filename ) {
1428            delete $po_filename{$lang} unless exists $partial{'lang'}{$lang};
1429        }
1430    }
1431
1432    if ( not scalar @{ $po4a_opts{"partial"} } ) {
1433        if ( -e find_input_file($pot_filename) ) {
1434            my $modtime = ( stat find_input_file($pot_filename) )[9];
1435
1436            # The POT needs to be re-generated if a master document is more recent
1437            # than the POT.
1438            foreach my $master ( keys %document ) {
1439                next if ( $master eq '' );
1440                my $pot_in = find_input_file( $document{$master}{'pot_in'} );
1441                if ( ( stat $pot_in )[9] >= $modtime ) {
1442                    $update_pot_file = 1;
1443                    last;
1444                }
1445            }
1446
1447            if ( ( stat $config_file )[9] > $modtime ) {
1448
1449                # The configuration file was modified after the POT.
1450                # Maybe a new document, or new options
1451                $update_pot_file = 1;
1452            }
1453
1454            if ( $po4a_opts{"force"} ) {
1455                $update_pot_file = 1;
1456            }
1457
1458            if ( $po4a_opts{"no-update"} ) {
1459                print wrap_msg( gettext("NOT updating %s as requested (--no-update)."), $pot_filename )
1460                  if ( $update_pot_file and $po4a_opts{"verbose"} );
1461                $update_pot_file = 0;
1462            } else {
1463                printf( gettext("Updating %s:"), $pot_filename )
1464                  if ( $update_pot_file and $po4a_opts{"verbose"} );
1465            }
1466        } elsif ( $po4a_opts{"no-update"} ) {
1467            print wrap_msg( gettext("NOT creating %s as requested (--no-update)."), $pot_filename )
1468              if $po4a_opts{"verbose"};
1469        } else {
1470            printf( gettext("Creating %s:"), $pot_filename )
1471              if $po4a_opts{"verbose"};
1472            $update_pot_file = 1;
1473        }
1474    }
1475}
1476
1477if ($update_pot_file) {
1478    my %pot_options;
1479    foreach (qw(porefs wrap-po msgid-bugs-address copyright-holder package-name package-version)) {
1480        if ( defined $po4a_opts{$_} ) {
1481            $pot_options{$_} = $po4a_opts{$_};
1482        }
1483    }
1484    $pot_options{'pot-language'} = $po4a_opts{"mastlang"};
1485    $pot_options{'pot-charset'}  = $po4a_opts{"mastchar"};
1486    my $potfile = Locale::Po4a::Po->new( \%pot_options );
1487    foreach my $master (
1488        sort {
1489            return -1 if ( $a eq "" );
1490            return 1  if ( $b eq "" );
1491            $document{$a}{'pos'} <=> $document{$b}{'pos'}
1492        } keys %document
1493      )
1494    {
1495        next if ( $master eq '' );
1496        my $pot_in = $document{$master}{'pot_in'};
1497
1498        my %file_opts = %po4a_opts;
1499        my $options =
1500          ( $document{''}{"options"}{"global"} // '' ) . ' ' .        # global options (if any) as default values
1501          ( $document{$master}{"options"}{"global"} // '' ) . ' ';    # file's global options (if any) to override them
1502        $options =~ s/ *$//;
1503        print STDERR "Options for $master while generating pot: $options\n" if $po4a_opts{'debug'};
1504        %file_opts = get_options( split_opts($options), @ORIGINAL_ARGV ) if ( length $options );
1505
1506        my $doc = Locale::Po4a::Chooser::new( $document{$master}{'format'}, %{ $file_opts{"options"} } );
1507
1508        # If we know that the input isn't UTF-8, say so to the TransTractor
1509        if (   length $po4a_opts{"mastchar"}
1510            && ( not $po4a_opts{"mastchar"} =~ /UTF-8/i )
1511            && $po4a_opts{"mastchar"} ne "CHARSET" )
1512        {
1513            print STDERR "mastchar: " . $po4a_opts{"mastchar"} . "\n";
1514            $doc->{TT}{ascii_input} =
1515              0;    # this actually mean "don't mess up with the encoding", not litteraly that it's in ascii
1516        }
1517
1518        # We ensure that the generated po will be in utf-8 if the input document isn't entirely in ascii
1519        $doc->{TT}{utf_mode} = 1;
1520
1521        $doc->setpoout($potfile);
1522        my @file_in_name;
1523        push @file_in_name, $pot_in;
1524
1525        $doc->process(
1526            'file_in_name'    => \@file_in_name,
1527            'file_in_charset' => $file_opts{"mastchar"},
1528            'srcdir'          => $po4a_opts{"srcdir"},
1529            'destdir'         => $po4a_opts{"destdir"},
1530            'calldir'         => $po4a_opts{"calldir"}
1531        );
1532        $potfile = $doc->getpoout();
1533    }
1534
1535    if ( $po4a_opts{"split"} ) {
1536        ( undef, $pot_filename ) = File::Temp::tempfile(
1537            "po4a-temp-pot-XXXX",
1538            DIR    => $ENV{TMPDIR} || "/tmp",
1539            SUFFIX => ".pot",
1540            OPEN   => 0,
1541            UNLINK => 0
1542        ) or die wrap_msg( gettext("Cannot create a temporary POT file: %s"), $! );
1543        $potfile->write($pot_filename);
1544    } else {
1545        if ( $po4a_opts{"force"} ) {
1546            $potfile->write( find_output_file($pot_filename) );
1547        } else {
1548            $potfile->write_if_needed( find_output_file($pot_filename) );
1549        }
1550    }
1551
1552    print wrap_msg( gettext(" (%d entries)"), $potfile->count_entries() )
1553      unless ( $po4a_opts{"quiet"} );
1554} else {
1555    print wrap_msg( gettext("POT file %s already up to date."), $pot_filename )
1556      if ( $po4a_opts{"verbose"} and not $po4a_opts{"no-update"} );
1557}
1558
1559my %split_po;           # po_files: '$lang','$master' => '$path'
1560my %split_pot;          # pot_files: '$master' => '$path'
1561my %split_pot_files;    # List of files in a given POT, as a parameter to msggrep: '$potfile' => "-N file1 -N file2"
1562
1563if ( $po4a_opts{"split"} ) {
1564    foreach my $master ( keys %document ) {
1565        next if ( $master eq '' );
1566        my $m          = $document{$master}{'pot'} || basename $master;
1567        my $master_pot = $pot_filename_cfg;
1568        $master_pot =~ s/\$master/$m/g;
1569        $split_pot{$master} = $master_pot;
1570        my $searched_file = $document{$master}{'pot_in'} // $master;
1571        $split_pot_files{$master_pot} //= '';
1572        $split_pot_files{$master_pot} .= " -N \"$searched_file\"";
1573    }
1574
1575    # Generate a .pot for each document by msggrep on the big POT file.
1576    # These files are used to update the PO files later on (and also by humans to start new translations)
1577    if ( not $po4a_opts{"no-update"} ) {
1578        foreach my $master_pot ( keys %split_pot_files ) {
1579            my $tmp_file;
1580
1581            # Create a temporary POT, and check if the old one needs to be
1582            # updated (unless --force was specified).
1583            unless ( $po4a_opts{"force"} ) {
1584                ( undef, $tmp_file ) = File::Temp::tempfile(
1585                    "po4aXXXX",
1586                    DIR    => $ENV{TMPDIR} || "/tmp",
1587                    SUFFIX => ".pot",
1588                    OPEN   => 0,
1589                    UNLINK => 0
1590                ) or die wrap_msg( gettext("Cannot create a temporary POT file: %s"), $! );
1591            }
1592
1593            my $dir = dirname( find_output_file($master_pot) );
1594            if ( not -d $dir ) {
1595                mkdir $dir or die wrap_msg( gettext("Cannot create directory '%s': %s"), $dir, $! );
1596            }
1597            my $outfile = $po4a_opts{"force"} ? find_output_file($master_pot) : $tmp_file;
1598            my $cmd     = "msggrep$Config{_exe} $split_pot_files{$master_pot} -o $outfile $pot_filename";
1599            run_cmd($cmd);
1600
1601            die wrap_msg(
1602                gettext(
1603                    "msggrep did not create '%s'. Used command:\n  %s\nPlease report that bug, along the information allowing to reproduce it.\n"
1604                ),
1605                $outfile, $cmd
1606            ) unless ( -e $outfile );
1607
1608            move_po_if_needed( $tmp_file, find_output_file($master_pot), 0 )
1609              unless ( $po4a_opts{"force"} );
1610        }
1611    }
1612
1613    # Generate a complete .po for each language, with the translations of all master files
1614    # That's done with msgcat on all existing PO files of a given language
1615    foreach my $lang ( sort keys %po_filename ) {
1616        my $tmp_bigpo;
1617        ( undef, $tmp_bigpo ) = File::Temp::tempfile(
1618            "po4aXXXX",
1619            DIR    => $ENV{TMPDIR} || "/tmp",
1620            SUFFIX => "-$lang.po",
1621            OPEN   => 0,
1622            UNLINK => 0
1623        ) or die wrap_msg( gettext("Cannot create a temporary PO file: %s"), $! );
1624        my $cmd_cat = "";
1625        foreach my $master ( sort keys %document ) {
1626            next if ( $master eq '' );
1627            my $m         = $document{$master}{'pot'} || basename $master;
1628            my $master_po = $po_filename{$lang};
1629            $master_po =~ s/\$master/$m/g;
1630            if ( -e find_input_file($master_po) ) {
1631                $cmd_cat .= " " . find_input_file($master_po);
1632            } else {
1633                print wrap_msg(
1634                    gettext("The translation of master file '%s' in language '%s' is missing (file: %s) -- skipping."),
1635                    $master, $lang, $master_po )
1636                  if ( $po4a_opts{"verbose"} );
1637            }
1638            $split_po{$lang}{$master} = $master_po;
1639        }
1640        if ( length $cmd_cat ) {
1641            $cmd_cat = "msgcat" . $Config{_exe} . " -o $tmp_bigpo $cmd_cat";
1642            run_cmd($cmd_cat);
1643        }
1644
1645        # We do not need to keep the original name with $master
1646        $po_filename{$lang} = $tmp_bigpo;
1647    }
1648}
1649
1650if ( not $po4a_opts{"no-update"} ) {
1651
1652    # update all po files
1653    my $lang;
1654    if ( not scalar @{ $po4a_opts{"partial"} } ) {
1655        foreach $lang ( sort keys %po_filename ) {
1656            my ( $infile, $outfile ) =
1657              ( find_input_file( $po_filename{$lang} ), find_output_file( $po_filename{$lang} ) );
1658            my $updated_potfile = find_input_file($pot_filename);
1659
1660            if ( -e $infile ) {
1661                my $dir = dirname($outfile);
1662                if ( not -d $dir ) {
1663                    mkdir $dir or die wrap_msg( gettext("Cannot create directory '%s': %s"), $dir, $! );
1664                }
1665
1666                my $msgmerge_opt = $po4a_opts{"msgmerge-opt"};
1667                $msgmerge_opt =~ s/\$lang\b/$lang/g if scalar @langs;
1668                my $cmd = "msgmerge" . $Config{_exe} . " $infile $updated_potfile " . $msgmerge_opt;
1669                if ( $infile eq $outfile ) {    # in place
1670                    $cmd .= " --backup=none --update";
1671                } else {
1672                    $cmd .= " --output $outfile";
1673                }
1674                run_cmd($cmd);
1675
1676                if ( $po4a_opts{"verbose"} ) {
1677                    if ( $po4a_opts{'split'} ) {
1678                        printf( gettext("Updating the translation in language %s:"), $lang );
1679                    } else {
1680                        printf( gettext("Updating %s:"), $po_filename{$lang} );
1681                    }
1682
1683                    my $stat = qx(msgfmt$Config{_exe} --statistics -v -o $devnull $outfile 2>&1);
1684                    $stat =~ s/^[^:]*://;
1685                    print $stat;
1686                }
1687
1688            } else {
1689                my $read_pot_filename = find_input_file($pot_filename);
1690                my $cmd =
1691                  "msginit$Config{_exe} -i $read_pot_filename --locale $lang -o $outfile --no-translator >$devnull";
1692                run_cmd($cmd);
1693            }
1694        }
1695
1696        if ( $po4a_opts{"split"} ) {
1697
1698            # Split the complete PO in multiple POs
1699            foreach $lang ( sort keys %po_filename ) {
1700                foreach my $master ( keys %document ) {
1701                    next if ( $master eq '' );
1702                    my $tmp_file;
1703
1704                    # Create a temporary PO, and check if the old one needs to be
1705                    # updated (unless --force was specified).
1706                    ( undef, $tmp_file ) = File::Temp::tempfile(
1707                        "po4aXXXX",
1708                        DIR    => $ENV{TMPDIR} || "/tmp",
1709                        SUFFIX => ".po",
1710                        OPEN   => 0,
1711                        UNLINK => 0
1712                    ) or die wrap_msg( gettext("Cannot create a temporary POT file: %s"), $! );
1713
1714                    my $cmd;
1715
1716                    # Pass msggrep to C (english), but not under windows as this syntax is not understood by the shell
1717                    my $env = ( $^O =~ /Win/ ) ? "" : "LC_ALL=C.UTF-8";
1718
1719                    # Create an empty PO or copy the original PO header (to keep the header)
1720                    if ( -f find_input_file( $split_po{$lang}{$master} ) ) {
1721                        $cmd =
1722                            $env
1723                          . " msggrep$Config{_exe}"
1724                          . " --force-po --invert-match --msgid --regexp '.'"
1725                          . " --output $tmp_file "
1726                          . find_input_file( $split_po{$lang}{$master} );
1727                    } else {
1728                        $cmd =
1729                            "msginit$Config{_exe} "
1730                          . "--no-translator -l $lang --input "
1731                          . find_output_file( $split_pot{$master} )
1732                          . " --output $tmp_file  >$devnull";
1733                    }
1734                    run_cmd($cmd);
1735
1736                    # Update the PO according to the new POT and to the big PO (compendium).
1737                    $cmd =
1738                        "msgmerge$Config{_exe} "
1739                      . " --compendium "
1740                      . $po_filename{$lang}
1741                      . " --update --backup=none "
1742                      . $po4a_opts{"msgmerge-opt"}
1743                      . " $tmp_file "
1744                      . find_output_file( $split_pot{$master} );
1745                    run_cmd($cmd);
1746
1747                    my $dir = dirname( $split_po{$lang}{$master} );
1748                    if ( not -d $dir ) {
1749                        mkdir $dir
1750                          or die wrap_msg( gettext("Cannot create directory '%s': %s"), $dir, $! );
1751                    }
1752                    unless ( $po4a_opts{"force"} ) {
1753                        move_po_if_needed( $tmp_file, $split_po{$lang}{$master}, 0 );
1754                    } else {
1755                        move $tmp_file,
1756                          find_output_file( $split_po{$lang}{$master} )
1757                          or die wrap_msg(
1758                            dgettext( "po4a", "Cannot move %s to %s: %s." ), $tmp_file,
1759                            find_output_file( $split_po{$lang}{$master} ), $!
1760                          );
1761                    }
1762                }
1763            }
1764        }    # if ( $po4a_opts{"split"} )
1765    }    # if ( not scalar @{ $po4a_opts{"partial"} } )
1766}    # not $po4a_opts{"no-update"}
1767
1768if ( $po4a_opts{"split"} ) {
1769
1770    # We don't need the tmp big POT anymore
1771    unlink($pot_filename);
1772    print STDERR "Unlink the big POT file $pot_filename\n" if $po4a_opts{'debug'};
1773}
1774
1775if ( not $po4a_opts{"no-translations"} ) {
1776
1777    # update all translations
1778
1779  LANG: foreach my $lang ( sort keys %po_filename ) {
1780
1781        # Read the $lang PO once, no options for the creation
1782        my $po     = Locale::Po4a::Po->new();
1783        my $pofile = find_input_file( $po_filename{$lang} );
1784
1785        if ( not -e $pofile ) {
1786            if ( $po4a_opts{"verbose"} ) {
1787                if ( File::Spec->file_name_is_absolute( $po_filename{$lang} ) ) {
1788                    print wrap_msg( gettext("PO file for language %s is missing -- skipping."), $lang );
1789                } else {
1790                    print wrap_msg( gettext("PO file %s for language %s is missing -- skipping."),
1791                        $po_filename{$lang}, $lang );
1792                }
1793            }
1794            next LANG;
1795        }
1796        print "Reading PO file $pofile for language $lang: "
1797          if ( $po4a_opts{"debug"} );
1798        $po->read($pofile);
1799        system( "msgfmt" . $Config{_exe} . " --statistics -v -o $devnull $pofile" )
1800          if ( $po4a_opts{"debug"} );
1801
1802      DOC: foreach my $master (
1803            sort {
1804                return -1 if ( $a eq "" );
1805                return 1  if ( $b eq "" );
1806                $document{$a}{'pos'} <=> $document{$b}{'pos'}
1807            } keys %document
1808          )
1809        {
1810            next if ( $master eq '' );
1811            next unless defined $document{$master}{$lang};
1812            if ( scalar @{ $po4a_opts{"partial"} } ) {
1813                next unless defined $partial{'files'}{ $document{$master}{$lang} };
1814            }
1815
1816            unless ( $po4a_opts{"force"} ) {
1817                my $outfile   = find_input_file( $document{$master}{$lang} );
1818                my $stampfile = ( -e $outfile ? $outfile : "$outfile.po4a-stamp" );
1819
1820                $stampfile = File::Spec->rel2abs($stampfile);
1821
1822                my @files = ( $master, $po_filename{$lang}, File::Spec->rel2abs($config_file) );
1823                if ( defined $document{$master}{"add_$lang"} ) {
1824                    push @files, @{ $document{$master}{"add_$lang"} };
1825                }
1826                push @files, @{ $add_filename{$lang} } if ( defined $add_filename{$lang} );
1827
1828                unless ( is_older( $stampfile, @files ) ) {
1829                    print wrap_msg( gettext("%s doesn't need to be updated."), $document{$master}{$lang} )
1830                      if ( $po4a_opts{"verbose"} );
1831                    next DOC;
1832                }
1833            }
1834
1835            my %file_opts = %po4a_opts;
1836            my $options =
1837              ( $document{''}{"options"}{"global"} // '' ) . ' ' .      # global options (if any) as default values
1838              ( $document{$master}{"options"}{"global"} // '' ) . ' '
1839              .                                                 # file's global options (if any) to override them
1840              ( $document{$master}{"options"}{$lang} // '' );   # file's language options (if any) overriding everything
1841            $options =~ s/ *$//;
1842            print STDERR "Options for $master in $lang: $options\n" if $po4a_opts{'debug'};
1843            %file_opts = get_options( split_opts($options), @ORIGINAL_ARGV ) if ( length $options );
1844
1845            my $doc = Locale::Po4a::Chooser::new( $document{$master}{'format'}, %{ $file_opts{"options"} } );
1846
1847            my @file_in_name;
1848            push @file_in_name, $master;
1849
1850            # Reuse the already parsed PO, do not use the po_in_name option of process.
1851            $doc->{TT}{po_in} = $po;
1852            $doc->{TT}{po_in}->stats_clear();
1853
1854            my $translation_out;
1855            if ( $po4a_opts{"keep-translations"}
1856                && -e find_output_file( $document{$master}{$lang} ) )
1857            {
1858                ( undef, $translation_out ) = File::Temp::tempfile(
1859                    "$document{$master}{$lang}.new-XXXX",
1860                    OPEN   => 0,
1861                    UNLINK => 0
1862                );
1863            } else {
1864                $translation_out = $document{$master}{$lang};
1865            }
1866
1867            $doc->process(
1868                'file_in_name'     => \@file_in_name,
1869                'file_out_name'    => $translation_out,
1870                'file_in_charset'  => $file_opts{"mastchar"},
1871                'file_out_charset' => $file_opts{"locchar"},
1872                'addendum_charset' => $file_opts{"addchar"},
1873                'srcdir'           => $po4a_opts{"srcdir"},
1874                'destdir'          => $po4a_opts{"destdir"},
1875                'calldir'          => $po4a_opts{"calldir"}
1876            );
1877
1878            my ( $percent, $hit, $queries ) = $doc->stats();
1879            if ( $percent < $file_opts{"threshold"} ) {
1880                print wrap_msg(
1881                    gettext("Discard %s (%s of %s strings; only %s%% translated; need %s%%)."),
1882                    $document{$master}{$lang},
1883                    $hit, $queries, $percent, $file_opts{"threshold"}
1884                );
1885                unlink( find_output_file( $document{$master}{$lang} ) )
1886                  if ( !$po4a_opts{"keep-translations"} );
1887                unlink( find_output_file($translation_out) )
1888                  if ( $po4a_opts{"keep-translations"} );
1889                if ( $po4a_opts{"stamp"} && ( not $po4a_opts{"force"} ) ) {
1890                    touch( find_output_file( $document{$master}{$lang} ) . ".po4a-stamp" );
1891                    print wrap_msg( gettext("Timestamp %s created."), $document{$master}{$lang} . ".po4a-stamp" )
1892                      if ( $po4a_opts{"verbose"} );
1893                }
1894                next DOC;
1895
1896            } else {    # The translated document reached the threshold and was kept
1897                rename( $translation_out, find_output_file( $document{$master}{$lang} ) );
1898
1899                unless ( $po4a_opts{"force"} ) {
1900                    if ( -e $document{$master}{$lang} . ".po4a-stamp" ) {
1901                        unlink find_output_file( $document{$master}{$lang} ) . ".po4a-stamp";
1902                        print wrap_msg( gettext("Timestamp %s removed."), $document{$master}{$lang} . ".po4a-stamp" )
1903                          if ( $po4a_opts{"verbose"} );
1904                    }
1905                }
1906            }
1907
1908            my @addlist;
1909            push @addlist, @{ $document{$master}{"add_$lang"} }
1910              if ( defined( $document{$master}{"add_$lang"} ) );
1911            push @addlist, @{ $add_filename{$lang} }
1912              if ( defined( $add_filename{$lang} ) );
1913
1914            foreach my $add (@addlist) {
1915                if ( !$doc->addendum( find_input_file($add) ) ) {
1916                    die wrap_msg( gettext("Addendum %s does NOT apply to %s (translation discarded)."),
1917                        $add, $document{$master}{$lang} );
1918                    my $outfile = find_output_file( $document{$master}{$lang} );
1919                    unlink($outfile) if ( -e $outfile );
1920                    next DOC;
1921                }
1922            }
1923            if ( $file_opts{"verbose"} ) {
1924                if ( $percent == 100 ) {
1925                    print wrap_msg( gettext("%s is 100%% translated (%s strings)."),
1926                        $document{$master}{$lang}, $queries );
1927                } else {
1928                    print wrap_msg(
1929                        gettext("%s is %s%% translated (%s of %s strings)."),
1930                        $document{$master}{$lang},
1931                        $percent, $hit, $queries
1932                    );
1933                }
1934            }
1935
1936            $doc->write( find_output_file( $document{$master}{$lang} ) );
1937        }
1938    }
1939}
1940
1941if ( $po4a_opts{"split"} ) {
1942
1943    # We don't need the tmp big POs anymore
1944    foreach my $lang ( keys %po_filename ) {
1945        unlink $po_filename{$lang};
1946    }
1947}
1948
1949if ( $po4a_opts{"rm-translations"} ) {
1950
1951    # Delete the translated documents
1952    foreach my $lang ( keys %po_filename ) {
1953        foreach my $master ( keys %document ) {
1954            next if ( $master eq '' );
1955            my $outfile = find_output_file( $document{$master}{$lang} );
1956            unlink $outfile;
1957            unlink "$outfile.po4a-stamp";
1958        }
1959    }
1960}
1961
1962sub touch {
1963    my $file = shift;
1964    if ( -e $file ) {
1965        utime undef, undef, $file;
1966    } else {
1967        sysopen( FH, $file, O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY )
1968          or croak("Cannot create $file : $!");
1969        close FH or croak("Cannot close $file : $!");
1970    }
1971}
1972
1973sub is_older {
1974    my $file  = shift;
1975    my @files = @_;
1976    return 1 unless ( -e $file );
1977    my $older = 0;
1978
1979    my $modtime = ( stat $file )[9];
1980
1981    for my $f (@files) {
1982        if ( -e $f and ( stat $f )[9] > $modtime ) {
1983            $older = 1;
1984            last;
1985        }
1986    }
1987
1988    return $older;
1989}
1990
1991__END__
1992