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