-
Notifications
You must be signed in to change notification settings - Fork 62
/
po4a
executable file
·2187 lines (1787 loc) · 86.6 KB
/
po4a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
863
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#! /usr/bin/env perl
eval 'exec perl -S $0 ${1 "$@"}'
if $running_under_some_shell;
# po4a -- Update both the po files and translated documents in one shoot
#
# Copyright 2002-2023 by SPI, inc.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of GPL v2.0 or later (see COPYING).
=encoding UTF-8
=head1 NAME
po4a - update both the PO files and translated documents in one shot
=head1 SYNOPSIS
B<po4a> [I<options>] I<config_file>
=head1 DESCRIPTION
po4a (PO for anything) eases the maintenance of documentation translation
using the classical gettext tools. The main feature of po4a is that
it decouples the translation of content from its document structure.
Please refer to the page L<po4a(7)> for a gentle introduction to this
project.
Upon execution, B<po4a> parses all documentation files specified in its
configuration file. It updates the PO files (containing the translation) to
reflect any change to the documentation, and produce a translated documentation
by injecting the content's translation (found in the PO files) into the
structure of the original master document.
At first, the PO files only contain the strings to translate from the original
documentation. This file format allows the translators to manually provide a
translation for each paragraph extracted by B<po4a>. If the documentation is
modified after translation, B<po4a> marks the corresponding translations as
"fuzzy" in the PO file to request a manual review by the translators. The
translators can also provide so-called "addendum", that are extra content
stating for example who did the translation and how to report bugs.
master documents --- ---->-------->---------
(doc authoring) | |
V (po4a executions) >----- --> translated
| | | documents
existing PO files -->--> updated PO files >- |
^ | |
| V |
----------<---------<------- ^
(manual translation process) |
|
addendum -->--------------------------------------
The workflow of B<po4a> is asynchronous, as suited to open-source projects. The
documentation writers author the master documents at their own pace. The
translators review and update the translations in the PO files. The maintainers
rerun B<po4a> on need, to reflect any change to the original documentation to
the PO files, and to produce updated documentation translations, by injecting
the latest translation into the latest document structure.
By default, a given translated document is produced when at least 80% of its
content is translated. The untranslated text is kept in the original language.
The produced documentation thus mixes languages if the translation is not
complete. You can change the 80% threshold with the I<--keep> option described
below. Note however that discarding translations as soon as they are not 100%
may be discouraging for the translators whose work will almost never be shown to
the users, while showing "translations" that are too incomplete may be troubling
for the end users.
Storing the translated documentation files in the version control system is
probably a bad idea, since they are automatically generated. The precious files
are the PO files, that contain the hard work of your fellow translators. Also,
some people find it easier to interact with the translators through an online
platform such as S<weblate>, but this is naturally fully optional.
=head2 Quick start tutorial
Let's assume you maintain a program named B<foo> which has a man page
F<man/foo.1> written in English (the bridge language in most open-source
projects, but B<po4a> can be used from or to any language). Some times ago,
someone provided a German translation named F<man/foo.de.1> and disappeared.
This is a problem because you just got a bug report saying that your
documentation contains a gravely misleading information that must be fixed in
all languages, but you don't speak German so you can only modify the original,
not the translation. Now, another contributor wants to contribute a translation
to Japanese, a language that you don't master either.
It is time to convert your documentation to B<po4a> to solve your documentation
maintenance nightmares. You want to update the doc when needed, you want to ease
the work of your fellow translators, and you want to ensure that your users
never see any outdated and thus misleading documentation.
The conversion includes two steps: setup the po4a infrastructure, and convert
the previous German translation to salvage the previous work. This latter part
is done using S<po4a-gettextize>, as follows. As detailed in the documentation
of L<po4a-gettextize(1)>, this process rarely fully automatic, but once it's
done, the B<de.po> file containing the German translation can be integrated in
your po4a workflow.
po4a-gettextize --format man --master foo.1 --localized foo.de.1 --po de.po
Let's now configure po4a. With the appropriate file layout, your configuration
file could be as simple as this:
[po_directory] man/po4a/
[type: man] man/foo.1 $lang:man/translated/foo.$lang.1
It specifies that all PO files (containing the work of the translators) are in the
F<man/po4a/> directory, and that you have only one master file, F<man/foo.1>. If
you had several master files, you would have several lines similar to the second
one. Each such line also specify where to write the corresponding translation
files. Here, the German translation of F<man/foo.1> is in
F<man/translated/foo.de.1>.
The last thing we need to complete the configuration of B<po4a> is a POT file
containing the template material that should be used to start a new translation.
Simply create an empty file with the S<.pot> extension in the specified
S<po_directory> (e.g. F<man/po4a/foo.pot>), and B<po4a> will fill it with the
expected content.
Here is a recap of the files in this setup:
├── man/
│ ├── foo.1 <- The original man page, in English
│ ├── po4a/
│ │ ├── de.po <- The German PO translation, from gettextization
│ │ └── foo.pot <- The POT template of future translations (empty at first)
│ └── translated/ <- Directory where the translations will be created
└── po4a.cfg <- The configuration file
Once setup, executing B<po4a> will parse your documentation, update the POT
template file, use it to update the PO translation files, and use them to update
the documentation translation files. All in one command:
po4a --verbose po4a.cfg
This is it. B<po4a> is now fully configured. Once you've fixed your error in
F<man/foo.1>, the offending paragraph in the German translation will be replaced by
the fixed text in English. Mixing languages is not optimal, but it's the only
way to remove errors in translations that you don't even understand, and ensure
that the content presented to the users is never misleading. Updating the German
translation is also much easier in the corresponding PO file, so the language
mix-up may not last long. Finally, when a Japanese translator wants to contribute a
new translation, she should rename the S<foo.pot> into S<ja.po> and complete the
translation. Once you have this file, just drop it in F<man/po4a/po/>. A translated
page will appear as F<man/translated/foo.ja.1> (provided that enough content is
translated) when you run B<po4a> again.
=head1 OPTIONS
=over 4
=item B<-k>, B<--keep>
Minimal threshold for translation percentage to keep (i.e. write) the
resulting file (default: 80). I.e. by default, files have to be translated
at least at 80% to be written on disk.
=item B<-w>, B<--width>
Column at which we should wrap the resulting file if the format supports it (default: 76).
If the provided value is 0 or negative, then the resulting file will not be wraped at all.
This is useful with some formats that could break when rewraped.
=item B<-h>, B<--help>
Show a short help message.
=item B<-M>, B<--master-charset>
Charset of the files containing the documents to translate. Note that all
master documents must use the same charset.
=item B<-L>, B<--localized-charset>
Charset of the files containing the localized documents. Note that all
translated documents will use the same charset.
=item B<-A>, B<--addendum-charset>
Charset of the addenda. Note that all the addenda should be in the same
charset.
=item B<-V>, B<--version>
Display the version of the script and exit.
=item B<-v>, B<--verbose>
Increase the verbosity of the program.
=item B<-q>, B<--quiet>
Decrease the verbosity of the program.
=item B<-d>, B<--debug>
Output some debugging information.
=item B<-o>, B<--option>
Extra option(s) to pass to the format plugin. See the documentation of each
plugin for more information about the valid options and their meanings. For
example, you could pass '-o tablecells' to the AsciiDoc parser, while the
text parser would accept '-o tabs=split'.
=item B<-f>, B<--force>
Always generate the POT and PO files, even if B<po4a> considers it is
not necessary.
The default behavior (when B<--force> is not specified) is the following:
=over
If the POT file already exists, it is regenerated if a master document
or the configuration file is more recent (unless B<--no-update> is provided).
The POT file is also written in a temporary document and B<po4a> verifies
that the changes are really needed.
Also, a translation is regenerated only if its master document, the PO file,
one of its addenda or the configuration file is more recent.
To avoid trying to regenerate translations which do not pass the threshold
test (see B<--keep>), a file with the F<.po4a-stamp> extension can be created
(see B<--stamp>).
=back
If a master document includes files, you should use the B<--force> flag
because the modification time of these included files are not taken into
account.
The PO files are always re-generated based on the POT with B<msgmerge -U>.
=item B<--stamp>
Tells B<po4a> to create stamp files when a translation is not generated
because it does not reach the threshold. These stamp files are named
according to the expected translated document, with the F<.po4a-stamp>
extension.
Note: This only activates the creation of the F<.po4a-stamp> files. The stamp
files are always used if they exist, and they are removed with
B<--rm-translations> or when the file is finally translated.
=item B<--no-translations>
Do not generate the translated documents, only update the POT and PO files.
=item B<--no-update>
Do not change the POT and PO files, only the translation may be updated.
=item B<--keep-translations>
Keeps the existing translation files even if the translation doesn't meet the threshold specified by B<--keep>.
This option does not create new translation files with few content,
but it will save existing translations which decay because of changes to the master files.
WARNING: This flag changes the po4a behavior in a rather drastic way:
your translated files will not get updated at all until the
translation improves. Only use this flag if you prefer shipping an
outdated translated documentation rather than only shipping an
accurate untranslated documentation.
=item B<--rm-translations>
Remove the translated files (implies B<--no-translations>).
=item B<--no-backups>
This flag does nothing since 0.41, and may be removed
in later releases.
=item B<--rm-backups>
This flag does nothing since 0.41, and may be removed
in later releases.
=item B<--translate-only> I<translated-file>
Translate only the specified file. It may be useful to speed up
processing if a configuration file contains a lot of files. Note that this
option does not update PO and POT files.
This option can be used multiple times.
=item B<--variable> I<var>B<=>I<value>
Define a variable that will be expanded in the B<po4a> configuration file.
Every occurrence of I<$(var)> will be replaced by I<value>.
This option can be used multiple times.
=item B<--target-lang> I<LANG>
Update PO files and generate translated documents only for the
specified language. This option can be used multiple times.
=item B<--srcdir> I<SRCDIR>
Set the base directory for all input documents specified in the B<po4a>
configuration file.
If both I<destdir> and I<srcdir> are specified, input files are
searched in the following directories, in order: I<destdir>, the current
directory and I<srcdir>. Output files are written to I<destdir> if
specified, or to the current directory.
=item B<--destdir> I<DESTDIR>
Set the base directory for all the output documents specified in the
B<po4a> configuration file (see B<--srcdir> above).
=back
=head2 Options modifying the POT header
=over 4
=item B<--porefs> I<type>
Specify the reference format. Argument I<type> can be one of B<never>
to not produce any reference, B<file> to only specify the file
without the line number, B<counter> to replace line number by an
increasing counter, and B<full> to include complete references (default: full).
=item B<--wrap-po> B<no>|B<newlines>|I<number> (default: 76)
Specify how the po file should be wrapped. This gives the choice between either
files that are nicely wrapped but could lead to git conflicts, or files that are
easier to handle automatically, but harder to read for humans.
Historically, the gettext suite has reformatted the po files at the 77th column
for cosmetics. This option specifies the behavior of po4a. If set to a numerical
value, po4a will wrap the po file after this column and after newlines in the
content. If set to B<newlines>, po4a will only split the msgid and msgstr after
newlines in the content. If set to B<no>, po4a will not wrap the po file at all.
The reference comments are always wrapped by the gettext tools that we use internally.
Note that this option has no impact on how the msgid and msgstr are wrapped, i.e.
on how newlines are added to the content of these strings.
=item B<--master-language>
Language of the source files containing the documents to
translate. Note that all master documents must use the same language.
=item B<--msgid-bugs-address> I<email@address>
Set the report address for msgid bugs. By default, the created POT files
have no Report-Msgid-Bugs-To fields.
=item B<--copyright-holder> I<string>
Set the copyright holder in the POT header. The default value is
"Free Software Foundation, Inc."
=item B<--package-name> I<string>
Set the package name for the POT header. The default is "PACKAGE".
=item B<--package-version> I<string>
Set the package version for the POT header. The default is "VERSION".
=back
=head2 Options to modify the PO files
=over 4
=item B<--msgmerge-opt> I<options>
Extra options for B<msgmerge>(1).
Note: B<$lang> will be extended to the current language.
=item B<--no-previous>
This option removes B<--previous> from the options passed to B<msgmerge>.
This is necessary to support versions of B<gettext> earlier than 0.16.
=item B<--previous>
This option adds B<--previous> to the options passed to B<msgmerge>.
It requires B<gettext> 0.16 or later, and is activated by default.
=back
=head1 CONFIGURATION FILE
po4a expects a configuration file as argument. This file must contain
the following elements:
=over
=item
The path to the PO files and the list of languages existing in the project;
=item
Optionally, some global options and so-called configuration aliases that are
used as templates to configure individual master files;
=item
The list of each master file to translate, along with specific parameters.
=back
All lines contain a command between square braces, followed by its parameters.
Comments begin with the char '#' and run until the end of the line. You can
escape the end of line to spread a command over several lines.
Some full examples are presented on this page, while other examples
can be found in the C<t/cfg> directory of the source distribution.
=head2 Finding the PO and POT files
The simplest solution is to explicitly give the path to POT and PO files, as follows:
[po4a_paths] man/po/project.pot de:man/po/de.po fr:man/po/fr.po
This specifies the path to the POT file first, and then the paths to the German
and French PO files.
The same information can be written as follows to reduce the risk of copy/paste errors:
[po4a_langs] fr de
[po4a_paths] man/po/project.pot $lang:man/po/$lang.po
The C<$lang> component is automatically expanded using the provided languages
list, reducing the risk of copy/paste error when a new language is added.
You can further compact the same information by only providing the
path to the directory containing your translation project, as follows.
[po_directory] man/po/
The provided directory must contain a set of PO files, each named F<XX.po> with
C<XX> the ISO 639-1 of the language used in this file. The directory must also
contain a single POT file, with the C<.pot> file extension. For the first run,
this file can be empty but it must exist (po4a cannot guess the name to use
before the extension).
Note that you must choose only one between C<po_directory> and
C<po4a_paths>. The first one (C<po_directory>) is more compact,
further reduces the risk of copy/paste error, but forces you to use
the expected project structure and file names. The second one
(C<po4a_paths>), is more explicit, probably more readable, and
advised when you setup your first project with po4a.
=head3 Centralized or split PO files?
By default, po4a produces one single PO file per target language, containing the
whole content of your translation project. As your project grows, the size of
these files may become problematic. When using weblate, it is possible to specify
priorities for each translation segment (i.e., msgid) so that the important ones get
translated first. Still, some translation teams prefer to split the content
in several files.
To have one PO file per master file, you simply have to use the string
C<$master> in the name of your PO files on the C<[po4a_paths]> line, as follows.
[po4a_paths] doc/$master/$master.pot $lang:doc/$master/$lang.po
With this line, po4a will produce separate POT and PO files for each document to translate.
For example, if you have 3 documents and 5 languages, this will result in 3 POT files and
15 PO files. These files are named as specified on the C<po4a_paths> template, with
C<$master> substituted to the basename of each document to translate. In case of name
conflict, you can specify the POT file to use as follows, with the C<pot=> parameter.
This feature can also be used to group several translated files into the same
POT file. The following example only produces 2 POT files: F<l10n/po/foo.pot>
(containing the material from F<foo/gui.xml>) and F<l10n/po/bar.pot> (containing
the material from both F<bar/gui.xml> and F<bar/cli.xml>).
[po4a_langs] de fr ja
[po4a_paths] l10n/po/$master.pot $lang:l10n/po/$master.$lang.po
[type: xml] foo/gui.xml $lang:foo/gui.$lang.xml pot=foo
[type: xml] bar/gui.xml $lang:bar/gui.$lang.xml pot=bar
[type: xml] bar/cli.xml $lang:bar/cli.$lang.xml pot=bar
In split mode, B<po4a> builds a temporary compendium during the PO update, to
share the translations between all the PO files. If two PO files have different
translations for the same string, B<po4a> will mark this string as fuzzy and
will submit both translations in all the PO files containing this string. When
unfuzzied by the translator, the translation is automatically used in every PO
files.
=head2 Specifying the documents to translate
You must also list the documents that should be translated. For each master
file, you must specify the format parser to use, the location of the translated
document to produce, and optionally some configuration. File names should be quoted
or escaped if they contain spaces. Here is an example:
[type: sgml] "doc/my stuff.sgml" "fr:doc/fr/mon truc.sgml" de:doc/de/mein\ kram.sgml
[type: man] script fr:doc/fr/script.1 de:doc/de/script.1
[type: docbook] doc/script.xml fr:doc/fr/script.xml \
de:doc/de/script.xml
But again, these complex lines are difficult to read and modify, e.g. when adding a new
language. It is much simpler to reorganize things using the C<$lang> template as follows:
[type: sgml] doc/my_stuff.sgml $lang:doc/$lang/my_stuff.sgml
[type: man] script.1 $lang:po/$lang/script.1
[type: docbook] doc/script.xml $lang:doc/$lang/script.xml
=head2 Specifying options
There is two types of options: I<po4a options> are default values to the po4a
command line options while I<format options> are used to change the behavior of
the format parsers. As a I<po4a options>, you could for example specify in your
configuration file that the default value of the B<--keep> command line
parameter is 50% instead of 80%. I<Format options> are documented on the
specific page of each parsing module, e.g. L<Locale::Po4a::Xml(3pm)>. You could
for example pass B<nostrip> to the XML parser to not strip the spaces around the
extracted strings.
You can pass these options for a specific master file, or even for a specific
translation of that file, using C<opt:> and C<opt_XX:> for the C<XX> language.
In the following example, the B<nostrip> option is passed to the XML parser (for
all languages), while the threshold will be reduced to 0% for the French
translation (that is thus always kept).
[type:xml] toto.xml $lang:toto.$lang.xml opt:"-o nostrip" opt_fr:"--keep 0"
In any case, these configuration chunks must be located at the end of the line.
The declaration of files must come first, then the addendum if any (see below),
and then only the options. The grouping of configuration chunks is not very
important, since elements are internally concatenated as strings. The following
examples are all equivalent:
[type:xml] toto.xml $lang:toto.$lang.xml opt:"--keep 20" opt:"-o nostrip" opt_fr:"--keep 0"
[type:xml] toto.xml $lang:toto.$lang.xml opt:"--keep 20 -o nostrip" opt_fr:"--keep 0"
[type:xml] toto.xml $lang:toto.$lang.xml opt:--keep opt:20 opt:-o opt:nostrip opt_fr:--keep opt_fr:0
Note that language specific options are not used when building the POT file. It
is for example impossible to pass B<nostrip> to the parser only when building
the French translation, because the same POT file is used to update every
languages. So the only options that can be language-specific are the ones that
are used when producing the translation, as the C<--keep> option.
=head3 Configuration aliases
To pass the same options to several files, the best is to define a type alias as
follows. In the next example, C<--keep 0> is passed to every Italian translation
using this C<test> type, that is an extension of the C<man> type.
[po4a_alias:test] man opt_it:"--keep 0"
[type: test] man/page.1 $lang:man/$lang/page.1
You can also extend an existing type reusing the same name for the alias as
follows. This is not interpreted as as an erroneous recursive definition.
[po4a_alias:man] man opt_it:"--keep 0"
[type: man] man/page.1 $lang:man/$lang/page.1
=head3 Global default options
You can also use C<[options]> lines to define options that must be used for all
files, regardless of their type.
[options] --keep 20 --option nostrip
As with the command line options, you can abbreviate the parameters passed in
the configuration file:
[options] -k 20 -o nostrip
=head3 Option priorities
The options of every sources are concatenated, ensuring that the default values
can easily be overridden by more specific options. The order is as follows:
=over
=item
C<[options]> lines provide default values that can be overridden by any other source.
=item
Type aliases are then used. Language specific settings override the ones
applicable to all languages.
=item
Settings that are specific to a given master file override both the default ones
and the ones coming from the type alias. In this case also, language specific
settings override the global ones.
=item
Finally, parameters provided on the B<po4a> command line override any settings
from the configuration file.
=back
=head3 Example
Here is an example showing how to quote the spaces and quotes:
[po_directory] man/po/
[options] --master-charset UTF-8
[po4a_alias:man] man opt:"-o \"mdoc=NAME,SEE ALSO\""
[type:man] t-05-config/test02_man.1 $lang:tmp/test02_man.$lang.1 \
opt:"-k 75" opt_it:"-L UTF-8" opt_fr:--verbose
=head2 Addendum: Adding extra content in the translation
If you want to add an extra section to the translation, for example to give
credit to the translator, then you need to define an addendum to the line
defining your master file. Please refer to the page L<po4a(7)> for more details
on the syntax of addendum files.
[type: pod] script fr:doc/fr/script.1 \
add_fr:doc/l10n/script.fr.add
You can also use language templates as follow:
[type: pod] script $lang:doc/$lang/script.1 \
add_$lang:doc/l10n/script.$lang.add
If an addendum fails to apply, the translation is discarded.
=head3 Modifiers for the addendum declaration
Addendum modifiers can simplify the configuration file in the case where not all
languages provide an addendum, or when the list of addenda changes from one
language to the other. The modifier is a single char located before the file name.
=over 2
=item B<?>
Include I<addendum_path> if this file does exist, otherwise do nothing.
=item B<@>
I<addendum_path> is not a regular addendum but a file containing a list of
addenda, one by line. Each addendum may be preceded by modifiers.
=item B<!>
I<addendum_path> is discarded, it is not loaded and will not be loaded by
any further addendum specification.
=back
The following includes an addendum in any language, but if only it exists. No
error is reported if the addendum does not exist.
[type: pod] script $lang:doc/$lang/script.1 add_$lang:?doc/l10n/script.$lang.add
The following includes a list of addendum for every language:
[type: pod] script $lang:doc/$lang/script.1 add_$lang:@doc/l10n/script.$lang.add
=head2 Filtering the translated strings
Sometimes, you want to hide some strings from the translation process. To
that extend, you can give a C<pot_in> parameter to your master file to specify
the name of the file to use instead of the real master when building the POT
file. Here is an example:
[type:docbook] book.xml \
pot_in:book-filtered.xml \
$lang:book.$lang.xml
With this setting, the strings to translate will be extracted from the
F<book-filtered.xml> (that must be produced before calling B<po4a>) while the
translated files will be built from F<book.xml>. As a result, any string that is
part of F<book.xml> but not in F<book-filtered.xml> will not be included in the
PO files, preventing the translators from providing a translation for them. So
these strings will be left unmodified when producing the translated documents.
This naturally decreases the level of translation, so you may need the C<--keep>
option to ensure that the document is produced anyway.
=head1 SEE ALSO
L<po4a-gettextize(1)>,
L<po4a(7)>.
=head1 AUTHORS
Denis Barbier <[email protected]>
Nicolas François <[email protected]>
Martin Quinson (mquinson#debian.org)
=head1 COPYRIGHT AND LICENSE
Copyright 2002-2023 by SPI, inc.
This program is free software; you may redistribute it and/or modify it
under the terms of GPL v2.0 or later (see the COPYING file).
=cut
use 5.16.0;
use strict;
use warnings;
use Getopt::Long qw(GetOptions);
use Locale::Po4a::Chooser;
use Locale::Po4a::TransTractor;
use Locale::Po4a::Common qw(wrap_msg wrap_ref_mod gettext dgettext);
use Locale::Po4a::Po qw(move_po_if_needed);
use Pod::Usage qw(pod2usage);
use File::Temp;
use File::Basename;
use File::Copy;
use File::Spec;
use Fcntl; # sysopen flags
use Cwd; # cwd
use Config;
Locale::Po4a::Common::textdomain('po4a');
sub show_version {
Locale::Po4a::Common::show_version("po4a");
exit 0;
}
# keep the command line arguments
my @ORIGINAL_ARGV = @ARGV;
# Use /NUL instead of /dev/null on Windows
my $devnull = ( $^O =~ /Win/ ) ? '/NUL' : '/dev/null';
# Parse the options provided on the command line, or in argument
sub get_options {
if ( defined $_[0] ) {
@ARGV = @_;
# map {print "o: $_\n"} @ARGV;
} else {
@ARGV = @ORIGINAL_ARGV;
}
# temporary array for GetOptions
my @verbose = ();
my @options = ();
my @variables = ();
my $previous;
my $noprevious;
my $msgmerge_opt = '';
my %opts = (
"help" => 0,
"type" => "",
"debug" => 0,
"verbose" => 0,
"quiet" => 0,
"force" => 0,
"no-translations" => 0,
"no-update" => 0,
"keep-translations" => 0,
"rm-translations" => 0,
"no-backups" => 0,
"rm-backups" => 0,
"threshold" => 80,
"width" => 76,
"mastlang" => "",
"mastchar" => "",
"locchar" => "",
"addchar" => "",
"options" => { "verbose" => 0, "debug" => 0 },
"variables" => {},
"partial" => [],
"porefs" => "full",
"wrap-po" => undef,
"copyright-holder" => undef,
"msgid-bugs-address" => undef,
"package-name" => undef,
"package-version" => undef,
"srcdir" => undef,
"destdir" => undef,
"target-lang" => [],
"calldir" => cwd()
);
Getopt::Long::config( 'bundling', 'no_getopt_compat', 'no_auto_abbrev' );
GetOptions(
'help|h' => \$opts{"help"},
'master-language=s' => \$opts{"mastlang"},
'master-charset|M=s' => \$opts{"mastchar"},
'localized-charset|L=s' => \$opts{"locchar"},
'addendum-charset|A=s' => \$opts{"addchar"},
'verbose|v' => \@verbose,
'debug|d' => \$opts{"debug"},
'force|f' => \$opts{"force"},
'stamp' => \$opts{"stamp"},
'quiet|q' => \$opts{"quiet"},
'keep|k=s' => \$opts{"threshold"},
'width|w=s' => \$opts{"width"},
'no-translations' => \$opts{"no-translations"},
'no-update' => \$opts{"no-update"},
'keep-translations' => \$opts{"keep-translations"},
'rm-translations' => \$opts{"rm-translations"},
'translate-only=s' => \@{ $opts{"partial"} },
'no-backups' => \$opts{"no-backups"},
'rm-backups' => \$opts{"rm-backups"},
'version|V' => \&show_version,
'option|o=s' => \@options,
'variable=s' => \@variables,
'porefs=s' => \$opts{"porefs"},
'wrap-po=s' => \$opts{"wrap-po"},
'copyright-holder=s' => \$opts{"copyright-holder"},
'msgid-bugs-address=s' => \$opts{"msgid-bugs-address"},
'package-name=s' => \$opts{"package-name"},
'package-version=s' => \$opts{"package-version"},
'no-previous' => \$noprevious,
'previous' => \$previous,
'msgmerge-opt=s' => \$msgmerge_opt,
'srcdir=s' => \$opts{"srcdir"},
'destdir=s' => \$opts{"destdir"},
'target-lang=s' => \@{ $opts{"target-lang"} }
) or pod2usage();
$opts{"verbose"} = scalar @verbose;
$opts{"verbose"} = 0 if $opts{"quiet"};
$opts{"verbose"} ||= 1 if $opts{"debug"};
$opts{"msgmerge-opt"} = '';
$opts{"msgmerge-opt"} .= "--previous " unless $noprevious;
$opts{"msgmerge-opt"} .= "--add-location=file " if ( $opts{'porefs'} =~ m/^file/ );
$opts{"msgmerge-opt"} .= $msgmerge_opt;
# options to transmit to the modules
$opts{"options"} = {
"verbose" => $opts{"verbose"},
"debug" => $opts{"debug"},
"wrapcol" => $opts{"width"}
};
foreach (@options) {
if (m/^([^=]*)=(.*)$/) {
$opts{"options"}{$1} = "$2";
} else {
$opts{"options"}{$_} = 1;
}
}
foreach (@variables) {
if (m/^([^=]*)=(.*)$/) {
$opts{"variables"}{$1} = "$2";
}
}
# The rm- options imply the no-
$opts{"no-translations"} = 1 if $opts{"rm-translations"};
if ( defined $opts{"srcdir"} and not -d $opts{"srcdir"} ) {
die wrap_msg( gettext("Option %s invalid. Directory %s does not exist (current directory: %s)."),
"srcdir", $opts{"srcdir"}, cwd() );
}
if ( defined $opts{"destdir"} and not -d $opts{"destdir"} ) {
die wrap_msg( gettext("Option %s invalid. Directory %s does not exist (current directory: %s)."),
"destdir", $opts{"destdir"}, cwd() );
}
# print STDERR "msgmerge: ".$opts{"msgmerge-opt"}."\n";
return %opts;
}
# Split a line into an array of lines. This should correctly deal with quotes and escaped spaces.
# - Simple and double quotes can be mixed: {'"tutu truc" blah' "'blah tutu' truc"} => {"tutu truc" blah}{'blah tutu' truc}
# - Quotes can be in the middle of words: {h ij'k l' m} => {h}{ijk l}{m}
# - Spaces can be escaped everywhere: {\ a b\ c d\ e} => { a}{b c}{d }{e}
# - Quotes and space escaping can be mixed: {'a b'c\ d} => {a bc d}
#
# Submitted as https://stackoverflow.com/questions/29041011/perl-split-string-that-has-double-quotes-and-space/76404839#76404839
sub split_line {
my ($string) = @_;
my @result = ();
my @quote = (); # stack of quotes we've seen so far, to handle nested single and double quotes
my $accumulated = ""; # the token we are building
my $seen_quotes = 0
; # Whether we've seen at least a quote or whether we should pass a chunk of only spaces (to pass separating spaces)
# eat the string piece by piece, separating a quote, some spaces, or a chunk w/o quotes and spaces.
while ( $string =~ s/('|"|\s |[^"'\s] )(.*)$/$2/ ) {
my $token = $1;
# print "Seen $token (last quote: ".(scalar @quote>0?$quote[-1]:"none")."; accumulated:$accumulated) -- ";
if ( $token eq "'" || $token eq '"' ) {
$seen_quotes = 1;
if ( scalar @quote > 0 ) {
if ( $quote[-1] eq $token ) {
pop @quote;
} else {
$accumulated .= $token;
}
} else {
push @quote, $token;
}
} elsif ( $token =~ /\\$/ ) {
if ( defined( my $next_char = substr( $string, 0, 1 ) ) ) {
# print " steal '$next_char' ";
$token =~ s/.$/$next_char/;
$string = substr( $string, 1 );
} else {
die "Error: backslash escape at end of string\n";
}
$accumulated .= $token;
} elsif ( $token =~ /^\s*$/ ) {
if ( scalar @quote > 0 ) {
$accumulated .= $token;
}
} else {
$accumulated .= $token;
}
# print " (last quote: ".(scalar @quote>0?$quote[-1]:"none")."; accumulated:$accumulated)";
# Push the string if we're out of quotes, and if the coming chunk is a separating space
if ( scalar @quote == 0 && ( $string eq '' || substr( $string, 0, 1 ) eq ' ' ) ) {
# print " Push\n";
# Pass the separating quotes, that are space only and never contained any quote
if ( $accumulated ne '' && $seen_quotes || $accumulated =~ /\S/ ) {
push @result, $accumulated;
}
$accumulated = '';
$seen_quotes = 0;
# } else {
# print " Pass\n";
}
}
if ( scalar @quote > 0 ) {
die "Error: unclosed quote at end of string\n";
}
return @result;
}
# Test the split_line function
#my $testline = "a 'b c c d ' 'titi' \"toto\" ' ' \" \" '\"' \"'\" '\"tutu truc\" blah' \"'blah tutu' truc\" e\\ f g\\ h ij'k l' m\"n \" 'l ' "
# ."\\\" \\' '\\'' \"'\\''\" BHA\\RO \\ n \"o p\"q\\ r";
#print "$testline\n";
#print join "#", split_line($testline);
#print "#\n";
#__END__
# Parse a config line and extract the parameters that correspond to options.
# These options are appended to the options provided in argument (as a
# reference to an hash). The options are sorted by category in this hash.
# The categories are: global and the various languages.
sub parse_config_options {
my $ref = shift; # a line reference for the die messages
my $line = shift; # the line to parse
my $options = shift; # reference to an hash of options
if ( $line =~ m/^(opt(?:_(. ?))?:)?(.*)/ ) {
my $prefixed = $1;
my $lang = $2 // 'global';
my $opt = $3;
if ( !$prefixed and $opt =~ m/ / ) {
if ( $opt =~ m/'/ ) {
$opt = qq{"$opt"};
} else {
$opt = qq{'$opt'};
}
}
if ( !defined $options->{$lang} ) {
$options->{$lang} = $opt;
} else {
$options->{$lang} .= " $opt";
}
return "";
} else {
return $line;
}
}
my %po4a_opts = get_options(@ARGV);
sub find_input_file {
my $filename = $_[0];
my $debug = $_[1];
print STDERR "Search for input file $filename\n" if $debug;
return $filename if ( File::Spec->file_name_is_absolute($filename) );
foreach ( ( $po4a_opts{'destdir'}, $po4a_opts{'calldir'}, $po4a_opts{'srcdir'} ) ) {
next unless defined $_;
my $p = File::Spec->catfile( $_, $filename );