xgettext.pl 19.2 KB
Newer Older
1
#!/usr/bin/env perl
2
3
# -*- indent-tabs-mode: nil; -*-
# vim:ft=perl:et:sw=4
4

salaun's avatar
salaun committed
5
use strict;
6
7
use warnings;
use Cwd qw();
IKEDA Soji's avatar
IKEDA Soji committed
8
use English;    # FIXME: Avoid $MATCH usage
9
use Getopt::Long;
salaun's avatar
salaun committed
10
use Pod::Usage;
11

12
13
14
15
16
17
18
19
use constant NUL   => 0;
use constant BEG   => 1;
use constant PAR   => 2;
use constant QUO1  => 3;
use constant QUO2  => 4;
use constant QUO3  => 5;
use constant BEGM  => 6;
use constant PARM  => 7;
20
21
use constant QUOM1 => 8;
use constant QUOM2 => 9;
22
use constant COMM  => 10;
salaun's avatar
salaun committed
23

24
25
## A hash that will contain the strings to translate and their meta
## informations.
26
my %file;
27
## conatins informations if a string is a date string.
28
my %type_of_entries;
29
## Contains unique occurences of each string
30
my %Lexicon;
31
## All the strings, in the order they were found while parsing the files
32
my @ordered_strings = ();
33
34

## Retrieving options.
35
my %opts;
36
37
38
39
40
41
42
43
44
45
46
47
48
49
GetOptions(
    \%opts,                 'add-comments|c:s',
    'copyright-holder=s',   'default-domain|d=s',
    'directory|D=s',        'files-from|f=s',
    'help|h',               'keyword|k:s@',
    'msgid-bugs-address=s', "output|o=s",
    'package-name=s',       'package-version=s',
    'version|v',            't=s@',
) or pod2usage(-verbose => 1, -exitval => 1);

$opts{help} and pod2usage(-verbose => 2, -exitval => 0);
if ($opts{version}) {
    print "sympa-6\n";
    exit;
50
51
}

52
53
if ($opts{'files-from'}) {
    my $ifh;
IKEDA Soji's avatar
IKEDA Soji committed
54
55
    open $ifh, '<', $opts{'files-from'}
        or die sprintf "%s: %s\n", $opts{'files-from'}, $ERRNO;
56
    my @files = grep { /\S/ and !/\A\s*#/ } split /\r\n|\r|\n/,
IKEDA Soji's avatar
IKEDA Soji committed
57
        do { local $RS; <$ifh> };
58
    my $cwd = Cwd::getcwd();
IKEDA Soji's avatar
IKEDA Soji committed
59
    if ($opts{directory}) {
IKEDA Soji's avatar
IKEDA Soji committed
60
61
        chdir $opts{directory}
            or die sprintf "%s: %s\n", $opts{directory}, $ERRNO;
62
    }
63
64
65
66
    @ARGV = map { (glob $_) } @files;
    chdir $cwd;
} elsif (not @ARGV) {
    @ARGV = ('-');
67
}
salaun's avatar
salaun committed
68

69
70
# Gathering strings in the source files.
# They will finally be stored into %file.
71

72
my $cwd = Cwd::getcwd();
IKEDA Soji's avatar
IKEDA Soji committed
73
if ($opts{directory}) {
IKEDA Soji's avatar
IKEDA Soji committed
74
75
    chdir $opts{directory}
        or die sprintf "%s: %s\n", $opts{directory}, $ERRNO;
76
77
}

78
foreach my $file (@ARGV) {
IKEDA Soji's avatar
IKEDA Soji committed
79
    next if $file =~ m{ [.] po.? \z }ix;    # Don't parse po files
IKEDA Soji's avatar
IKEDA Soji committed
80

IKEDA Soji's avatar
IKEDA Soji committed
81
    printf STDOUT "Processing %s...\n", $file;
sympa-authors's avatar
Fixes    
sympa-authors committed
82
    unless (-f $file) {
IKEDA Soji's avatar
IKEDA Soji committed
83
        printf STDERR "Cannot open %s\n", $file;
84
        next;
sympa-authors's avatar
Fixes    
sympa-authors committed
85
    }
86
87
88
89
90
91
92

    # cpanfile
    if ($file eq 'cpanfile') {
        CPANFile::load();
        next;
    }

IKEDA Soji's avatar
IKEDA Soji committed
93
94
    open my $fh, '<', $file or die sprintf "%s: %s\n", $file, $ERRNO;
    $_ = do { local $RS; <$fh> };
95
    close $fh;
IKEDA Soji's avatar
IKEDA Soji committed
96

IKEDA Soji's avatar
IKEDA Soji committed
97
98
    if ($file =~ m{ [.] (pm | pl | fcgi) ([.]in)? \z }x) {
        load_perl($file, $_);
99
100
    }

IKEDA Soji's avatar
IKEDA Soji committed
101
102
    if ($file =~ m{ [.] tt2 \z }x) {
        load_tt2($file, $_, $opts{t});
103
104
    }

IKEDA Soji's avatar
IKEDA Soji committed
105
106
    if ($file =~ m{ / scenari / | [.] task \z | / comment [.] tt2 \z }x) {
        load_title($file, $_);
salaun's avatar
salaun committed
107
108
109
    }
}

110
111
chdir $cwd;

112
113
## Transfers all data from %file to %Lexicon, removing duplicates in the
## process.
114
115
116
117
my $index = 0;
my @ordered_bis;
my %ordered_hash;
foreach my $str (@ordered_strings) {
118
    my $ostr  = $str;
salaun's avatar
salaun committed
119
    my $entry = $file{$str};
120
    my $lexi  = $Lexicon{$ostr} // '';
salaun's avatar
salaun committed
121

sympa-authors's avatar
sympa-authors committed
122
    ## Skip meta information (specific to Sympa)
IKEDA Soji's avatar
IKEDA Soji committed
123
    next if $str =~ /^_\w+\_$/;
sympa-authors's avatar
sympa-authors committed
124

125
    $str =~ s/"/\\"/g;
salaun's avatar
salaun committed
126
    $lexi =~ s/\\/\\\\/g;
127
    $lexi =~ s/"/\\"/g;
salaun's avatar
salaun committed
128

129
130
131
132
    unless ($ordered_hash{$str}) {
        $ordered_bis[$index] = $str;
        $index++;
        $ordered_hash{$str} = 1;
133
    }
salaun's avatar
salaun committed
134
135
136
137
    $Lexicon{$str} ||= '';
    next if $ostr eq $str;

    $Lexicon{$str} ||= $lexi;
138
139
140
    unless ($file{$str}) { $file{$str} = $entry; }
    delete $file{$ostr};
    delete $Lexicon{$ostr};
salaun's avatar
salaun committed
141
142
143
}
exit unless %Lexicon;

144
145
146
147
148
149
150
151
my $output_file =
       $opts{output}
    || ($opts{'default-domain'} and $opts{'default-domain'} . '.pot')
    || "messages.po";

my $out;
my $pot;
if (-r $output_file) {
IKEDA Soji's avatar
IKEDA Soji committed
152
153
    open $pot, '+<', $output_file
        or die sprintf "%s: %s\n", $output_file, $ERRNO;
154
155
156
157
158
159
160
161
162
163
    while (<$pot>) {
        if (1 .. /^$/) { $out .= $_; next }
        last;
    }

    1 while chomp $out;

    seek $pot, 0, 0;
    truncate $pot, 0;
} else {
IKEDA Soji's avatar
IKEDA Soji committed
164
165
    open $pot, '>', $output_file
        or die sprintf "%s: %s\n", $output_file, $ERRNO;
166
167
168
}
select $pot;

salaun's avatar
salaun committed
169
170
171
172
173
174
175
176
177
178
print $out ? "$out\n" : (<< '.');
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
179
"Report-Msgid-Bugs-To: \n"
salaun's avatar
salaun committed
180
181
182
183
"POT-Creation-Date: 2002-07-16 17:27+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
184
"Language: \n"
salaun's avatar
salaun committed
185
186
187
188
189
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
.

190
foreach my $entry (@ordered_bis) {
191
    my %f = (map { ("$_->[0]:$_->[1]" => 1) } @{$file{$entry}});
salaun's avatar
salaun committed
192
193
194
195
196
197
198
    my $f = join(' ', sort keys %f);
    $f = " $f" if length $f;

    my $nospace = $entry;
    $nospace =~ s/ +$//;

    if (!$Lexicon{$entry} and $Lexicon{$nospace}) {
199
200
        $Lexicon{$entry} =
            $Lexicon{$nospace} . (' ' x (length($entry) - length($nospace)));
salaun's avatar
salaun committed
201
202
203
    }

    my %seen;
204
205

    ## Print code/templates references
salaun's avatar
salaun committed
206
    print "\n#:$f\n";
207
208

    ## Print variables if any
209
210
211
212
213
    foreach my $entry (grep { $_->[2] } @{$file{$entry}}) {
        my ($file, $line, $var) = @{$entry};
        $var =~ s/^\s*,\s*//;
        $var =~ s/\s*$//;
        print "#. ($var)\n" unless !length($var) or $seen{$var}++;
salaun's avatar
salaun committed
214
215
    }

216
217
    ## If the entry is a date format, add a developper comment to help
    ## translators
218
    if ($type_of_entries{$entry} and $type_of_entries{$entry} eq 'date') {
219
220
221
        print "#. This entry is a date/time format\n";
        print
            "#. Check the strftime manpage for format details : http://docs.freebsd.org/info/gawk/gawk.info.Time_Functions.html\n";
222
223
    } elsif ($type_of_entries{$entry}
        and $type_of_entries{$entry} eq 'printf') {
224
225
226
        print "#. This entry is a sprintf format\n";
        print
            "#. Check the sprintf manpage for format details : http://perldoc.perl.org/functions/sprintf.html\n";
227
228
    }

229
230
231
232
    print "msgid ";
    output($entry);
    print "msgstr ";
    output($Lexicon{$entry});
salaun's avatar
salaun committed
233
234
}

235
236
237
238
## Add expressions to list of expressions to translate
## parameters : expression, filename, line, vars
sub add_expression {
    my $param = shift;
239

IKEDA Soji's avatar
IKEDA Soji committed
240
    push @ordered_strings, $param->{expression};
IKEDA Soji's avatar
IKEDA Soji committed
241
242
243
244
    push @{$file{$param->{expression}}},
        [$param->{filename}, $param->{line}, $param->{vars}];
    $type_of_entries{$param->{expression}} = $param->{type}
        if $param->{type};
245
246
247

}

IKEDA Soji's avatar
IKEDA Soji committed
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
sub load_tt2 {
    my $filename = shift;
    my $_        = shift;
    my $filters  = shift;

    # Initiliazing filter names with defaults if necessary.
    # Defaults stored separately because GetOptions append arguments to
    # defaults.
    # Building the string to insert into the regexp that will search strings
    # to extract.
    my $tt2_filters = join('|', @{$filters || []}) || 'locdt|loc';

    my ($tag_s, $tag_e);
    if ($filename eq 'default/mhonarc-ressources.tt2') {
        # Template Toolkit with ($tag$%...%$tag$) in mhonarc-ressources.tt2
        # (<=6.2.60; OBSOLETED)
        ($tag_s, $tag_e) = (qr{[(]\$tag\$%}, qr{%\$tag\$[)]});
    } elsif ($filename eq 'default/mhonarc_rc.tt2') {
        # Template Toolkit with <%...%> in mhonarc_rc.tt2 (6.2.61b.1 or later)
        ($tag_s, $tag_e) = (qr{<%}, qr{%>});
    } elsif ($filename =~ /[.]tt2\z/) {
        # Template Toolkit with [%...%]
        ($tag_s, $tag_e) = (qr{[[]%}, qr{%[]]});
    } else {
        die 'bug in logic. Ask developer';
    }

    my $line;

    $line = 1;
    pos($_) = 0;
    while (
        m{
            \G .*?
            (?:
                # Short style: [% "..." | loc(...) %]
                $tag_s [-=~+]? \s*
                (?:
                    \'
                    ((?: \\. | [^'\\])*)
                    \'
                  |
                    \"
                    ((?: \\. | [^"\\])*)
                    \"
                ) \s*
                \| \s*
                ($tt2_filters)
                (.*?)
                \s* [-=~+]? $tag_e
              |
                # Enclosing style: [%|loc(...)%]...[%END%]
                $tag_s [-=~+]? \s*
                \| \s*
                ($tt2_filters)
                (.*?)
                \s* [-=~+]? $tag_e
                (.*?)
                $tag_s [-=~+]? \s*
                END
                \s* [-=~+]? $tag_e
            )
        }gsx
    ) {
        my $is_short = $3;
        my ($this_tag, $vars, $str) =
            $is_short ? ($3, $4, $1 // $2) : ($5, $6, $7);
        $line += (() = ($MATCH =~ /\n/g));    # cryptocontext!
        if ($is_short) {
            $str =~ s{\\(.)}{
                ($1 eq 't') ? "\t" :
                ($1 eq 'n') ? "\n" :
                ($1 eq 'r') ? "\r" :
                $1
            }eg;
            $vars =~ s/^\s*[(](.*?)[)].*/$1/ or $vars = '';
        } else {
            $str =~ s/\\\'/\'/g;
            $vars =~ s/^\s*\(//;
            $vars =~ s/\)\s*$//;
        }

        add_expression(
            {   expression => $str,
                filename   => $filename,
                line       => $line,
                vars       => $vars,
                (($this_tag eq 'locdt') ? (type => 'date') : ())
            }
        );
    }
}

sub load_perl {
    my $filename = shift;
    my $_        = shift;

    my $line;

    # Sympa variables (gettext_comment, gettext_id and gettext_unit)
    $line = 1;
    pos($_) = 0;
    while (
        m{
            \G .*?
            ([\"\']?)
            (gettext_comment | gettext_id | gettext_unit)
            \1
            \s* => \s*
            (?:
                (\") ((?: \\. | [^\"])+) \"
              | (\') ((?: \\. | [^\'])+) \'
            )
        }gsx
    ) {
        my ($quot, $str) = ($3 // $5, $4 // $6);
        $line += (() = ($MATCH =~ /\n/g));    # cryptocontext!
        $str =~ s{(\\.)}{eval "$quot$1$quot"}esg;

        add_expression(
            {   expression => $str,
                filename   => $filename,
                line       => $line
            }
        );
    }

    # Perl source file
    my $state = 0;
    my $str;
    my $vars;
    my $type;

    pos($_) = 0;
    my $orig = 1 + (() = ((my $__ = $_) =~ /\n/g));
PARSER: {
        $_ = substr $_, pos $_ if pos $_;
        my $line = $orig - (() = ((my $__ = $_) =~ /\n/g));
        # maketext or loc or _
        if ($state == NUL
            and m/\b(
                translate
              | gettext(?:_strftime|_sprintf)?
              | maketext
              | __?
              | loc
              | x
            )/cgx
        ) {
            if ($1 eq 'gettext_strftime') {
                $state = BEGM;
                $type  = 'date';
            } elsif ($1 eq 'gettext_sprintf') {
                $state = BEGM;
                $type  = 'printf';
            } else {
                $state = BEG;
                undef $type;
            }
            redo;
        }
        if (($state == BEG or $state == BEGM) and m/^([\s\t\n]*)/cg) {
            redo;
        }
        # begin ()
        if ($state == BEG and m/^([\S\(])/cg) {
            $state = ($1 eq '(') ? PAR : NUL;
            redo;
        }
        if ($state == BEGM and m/^([\(])/cg) {
            $state = PARM;
            redo;
        }

        # begin or end of string
        if ($state == PAR and m/^\s*(\')/cg) {
            $state = QUO1;
            redo;
        }
        if ($state == QUO1 and m/^([^\']+)/cg) {
            $str .= $1;
            redo;
        }
        if ($state == QUO1 and m/^\'/cg) {
            $state = PAR;
            redo;
        }

        if ($state == PAR and m/^\s*\"/cg) {
            $state = QUO2;
            redo;
        }
        if ($state == QUO2 and m/^([^\"]+)/cg) {
            $str .= $1;
            redo;
        }
        if ($state == QUO2 and m/^\"/cg) {
            $state = PAR;
            redo;
        }

        if ($state == PAR and m/^\s*\`/cg) {
            $state = QUO3;
            redo;
        }
        if ($state == QUO3 and m/^([^\`]*)/cg) {
            $str .= $1;
            redo;
        }
        if ($state == QUO3 and m/^\`/cg) {
            $state = PAR;
            redo;
        }

        if ($state == BEGM and m/^(\')/cg) {
            $state = QUOM1;
            redo;
        }
        if ($state == PARM and m/^\s*(\')/cg) {
            $state = QUOM1;
            redo;
        }
        if ($state == QUOM1 and m/^([^\']+)/cg) {
            $str .= $1;
            redo;
        }
        if ($state == QUOM1 and m/^\'/cg) {
            $state = COMM;
            redo;
        }

        if ($state == BEGM and m/^(\")/cg) {
            $state = QUOM2;
            redo;
        }
        if ($state == PARM and m/^\s*(\")/cg) {
            $state = QUOM2;
            redo;
        }
        if ($state == QUOM2 and m/^([^\"]+)/cg) {
            $str .= $1;
            redo;
        }
        if ($state == QUOM2 and m/^\"/cg) {
            $state = COMM;
            redo;
        }

        if ($state == BEGM) {
            $state = NUL;
            redo;
        }

        # end ()
        if (   ($state == PAR and m/^\s*[\)]/cg)
            or ($state == PARM and m/^\s*[\)]/cg)
            or ($state == COMM and m/^\s*,/cg)) {
            $state = NUL;
            $vars =~ s/[\n\r]//g if $vars;

            add_expression(
                {   expression => $str,
                    filename   => $filename,
                    line       => $line - (() = $str =~ /\n/g),
                    vars       => $vars,
                    ($type ? (type => $type) : ())
                }
            ) if $str;
            undef $str;
            undef $vars;
            redo;
        }

        # a line of vars
        if ($state == PAR and m/^([^\)]*)/cg) {
            $vars .= $1 . "\n";
            redo;
        }
        if ($state == PARM and m/^([^\)]*)/cg) {
            $vars .= $1 . "\n";
            redo;
        }
    }

    unless ($state == NUL) {
        my $post = $_;
        $post =~ s/\A(\s*.*\n.*\n.*)\n(.|\n)+\z/$1\n.../;
        warn sprintf "Warning: incomplete state just before ---\n%s\n", $post;
    }
}

sub load_title {
    my $filename = shift;
    my $_        = shift;

    my $line;

    # Titles in scenarios, tasks and comment.tt2 (title.gettext)
    $line = 1;
    pos($_) = 0;
    while (
        m{
            \G .*?
            title [.] gettext \s*
            ([^\n]+)
        }gsx
    ) {
        my $str = $1;
        $line += (() = ($MATCH =~ /\n/g));    # cryptocontext!

        add_expression(
            {   expression => $str,
                filename   => $filename,
                line       => $line
            }
        );
    }
}

salaun's avatar
salaun committed
567
sub output {
568
    my $str = shift // '';
salaun's avatar
salaun committed
569

570
571
572
    ## Normalize
    $str =~ s/\\n/\n/g;

salaun's avatar
salaun committed
573
    if ($str =~ /\n/) {
574
575
576
577
578
579
580
581
        print "\"\"\n";

        ## Avoid additional \n entries
        my @lines = split(/\n/, $str, -1);
        my @output_lines;

        ## Move empty lines to previous line as \n
        my $current_line;
IKEDA Soji's avatar
IKEDA Soji committed
582
583
584
        foreach my $line (@lines) {
            if ($line eq '') {
                unless (@output_lines) {
585
586
587
                    $current_line .= '\n';
                    next;
                } else {
IKEDA Soji's avatar
IKEDA Soji committed
588
                    $output_lines[-1] .= '\n';
589
590
591
                    next;
                }
            } else {
IKEDA Soji's avatar
IKEDA Soji committed
592
                $current_line .= $line;
593
594
595
596
597
            }
            push @output_lines, $current_line;
            $current_line = '';
        }

IKEDA Soji's avatar
IKEDA Soji committed
598
599
        # Add \n unless the last line
        print "\"" . join("\\n\"\n\"", @output_lines) . "\"\n";
600
601
    } else {
        print "\"$str\"\n";
salaun's avatar
salaun committed
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
package CPANFile;

use strict;
use warnings;
use lib qw(.);

my @entries;

sub feature {
    push @entries,
        {
        expression => $_[1],
        filename   => 'cpanfile',
        line       => [caller]->[2],
        };
}
sub on         { $_[1]->() }
sub recommends { }
sub requires   { }

sub load {
    do 'cpanfile';
    die unless @entries;
    foreach my $entry (@entries) {
        main::add_expression($entry);
    }
}

salaun's avatar
salaun committed
633
1;
634
635
636
__END__

=encoding utf-8
salaun's avatar
salaun committed
637

638
639
640
641
642
643
644
645
646
647
648
649
650
=head1 NAME

xgettext.pl - Extract gettext strings from Sympa source

=head1 SYNOPSIS

  xgettext.pl [ options ... ] [ inputfile ... ]

=head1 OPTIONS

=over

=item C<--default-domain> I<domain>, C<-d>I<domain>
salaun's avatar
salaun committed
651

652
653
654
Specifies domain.
If this option is specified but output file is not specified
(see C<--output>), C<I<domain>.pot> is used.
salaun's avatar
salaun committed
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
=item C<--directory> I<path>, C<-D>I<path>

Specifies directory to search input files.

=item C<--files-from> I<path>, C<-f>I<path>

Get list of input files from the file.

=item C<-g>

B<Deprecated>.
Enables GNU gettext interoperability by printing C<#, maketext-format>
before each entry that has C<%> variables.

=item C<--help>, C<-h>

Shows this documentation and exits.

=item C<--output> I<outputfile>, C<-o>I<outputfile>

POT file name to be written or incrementally
updated C<-> means writing to F<STDOUT>.  If neither this option nor
C<--default-domain> option specified,
F<messages.po> is used.

=item C<-t>I<tag1> ...

Specifies which tag(s) must be used to extract Template Toolkit strings.
Default is C<loc> and C<locdt>.
Can be specified multiple times.

=item C<-u>

B<Deprecated>.
Disables conversion from Maketext format to Gettext
format -- i.e. it leaves all brackets alone.  This is useful if you are
also using the Gettext syntax in your program.

=item C<--version>, C<-v>

Prints "C<sympa-6>" and newline, and then exits.

=item C<--add-comments> [ I<tag> ] , C<-c>[ I<tag> ]

=item C<--copyright-holder> I<string>

=item C<--keyword> [ I<word> ], C<-k>[ I<word> ], ...

=item C<--msgid-bugs-address> I<address>

=item C<--package-name> I<name>

=item C<--package-version> I<version>

These options will do nothing.
They are prepared for compatibility to xgettext of GNU gettext.

=back

I<inputfile>... is the files to extract messages from, if C<--files-from>
option is not specified.

=head1 DESCRIPTION

This program extracts translatable strings from given input files, or
STDIN if none are given.

Currently the following formats of input files are supported:

=over

=item Perl source files

Valid localization function names are:
C<gettext>, C<gettext_sprintf> C<gettext_strftime>,
C<maketext>, C<translate>, C<loc> C<x>, C<_> and C<__>.
Hash keys C<gettext_comment>, C<gettext_id> and C<gettext_unit>
are also recognized.

=item Template Toolkit

Texts inside C<[%|loc%]...[%END%]> or C<[%|locdt%]...[%END%]>
are extracted, unless specified otherwise by C<-t> option.

The alternative format C<[%...|loc%]> is also recognized.

=item Scenario sources

Text content of C<title.gettext> line.

=back
salaun's avatar
salaun committed
747
748
749

=head1 SEE ALSO

750
751
752
753
754
755
756
757
758
759
760
L<Sympa::Language>, L<Sympa::Template>.

=head1 HISTORY

This script was initially based on F<xgettext.pl>
by Autrijus Tang E<lt>autrijus@autrijus.orgE<gt>
which was bundled in L<Locale-Maketext-Lexicon>.
Afterward, it has been drastically rewritten to be adopted to Sympa
and original code hardly remains.

Part of changes are as following:
salaun's avatar
salaun committed
761

762
=over
salaun's avatar
salaun committed
763

764
=item [O. Salaun] 12/08/02 :
salaun's avatar
salaun committed
765

766
767
768
769
Also look for gettext() in perl code.
No more escape '\' chars.
Extract gettext_comment, gettext_id and gettext_unit entries from List.pm.
Extract title.gettext entries from scenarios.
salaun's avatar
salaun committed
770

771
=item [D. Verdin] 05/11/2007 :
salaun's avatar
salaun committed
772

773
774
775
776
Strings ordered following the order in which files are read and
the order in which they appear in the files.
Switch to Getopt::Long to allow multiple value parameter.
Added 't' parameter the specifies which tags to explore in TT2.
salaun's avatar
salaun committed
777

778
=back
salaun's avatar
salaun committed
779
780

=cut