1
/* GNU gettext - internationalization aids
2
Copyright (C) 1995-1998, 2000-2004 Free Software Foundation, Inc.
3
This file was written by Peter Miller <millerp@canb.auug.org.au>
5
This program is free software; you can redistribute it and/or modify
6
it under the terms of the GNU General Public License as published by
7
the Free Software Foundation; either version 2, or (at your option)
10
This program is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
GNU General Public License for more details.
15
You should have received a copy of the GNU General Public License
16
along with this program; if not, write to the Free Software Foundation,
17
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
35
#include "error-progname.h"
37
#include "relocatable.h"
50
#include "msgl-iconv.h"
51
#include "msgl-equal.h"
52
#include "plural-count.h"
53
#include "backupfile.h"
54
#include "copy-file.h"
57
#define _(str) gettext (str)
59
#define obstack_chunk_alloc xmalloc
60
#define obstack_chunk_free free
63
/* If true do not print unneeded messages. */
66
/* Verbosity level. */
67
static int verbosity_level;
69
/* Force output of PO file even if empty. */
72
/* Apply the .pot file to each of the domains in the PO file. */
73
static bool multi_domain_mode = false;
75
/* Determines whether to use fuzzy matching. */
76
static bool use_fuzzy_matching = true;
78
/* List of user-specified compendiums. */
79
static message_list_list_ty *compendiums;
82
static bool update_mode = false;
83
static const char *version_control_string;
84
static const char *backup_suffix_string;
87
static const struct option long_options[] =
89
{ "add-location", no_argument, &line_comment, 1 },
90
{ "backup", required_argument, NULL, CHAR_MAX + 1 },
91
{ "compendium", required_argument, NULL, 'C', },
92
{ "directory", required_argument, NULL, 'D' },
93
{ "escape", no_argument, NULL, 'E' },
94
{ "force-po", no_argument, &force_po, 1 },
95
{ "help", no_argument, NULL, 'h' },
96
{ "indent", no_argument, NULL, 'i' },
97
{ "multi-domain", no_argument, NULL, 'm' },
98
{ "no-escape", no_argument, NULL, 'e' },
99
{ "no-fuzzy-matching", no_argument, NULL, 'N' },
100
{ "no-location", no_argument, &line_comment, 0 },
101
{ "no-wrap", no_argument, NULL, CHAR_MAX + 4 },
102
{ "output-file", required_argument, NULL, 'o' },
103
{ "properties-input", no_argument, NULL, 'P' },
104
{ "properties-output", no_argument, NULL, 'p' },
105
{ "quiet", no_argument, NULL, 'q' },
106
{ "sort-by-file", no_argument, NULL, 'F' },
107
{ "sort-output", no_argument, NULL, 's' },
108
{ "silent", no_argument, NULL, 'q' },
109
{ "strict", no_argument, NULL, CHAR_MAX + 2 },
110
{ "stringtable-input", no_argument, NULL, CHAR_MAX + 5 },
111
{ "stringtable-output", no_argument, NULL, CHAR_MAX + 6 },
112
{ "suffix", required_argument, NULL, CHAR_MAX + 3 },
113
{ "update", no_argument, NULL, 'U' },
114
{ "verbose", no_argument, NULL, 'v' },
115
{ "version", no_argument, NULL, 'V' },
116
{ "width", required_argument, NULL, 'w', },
130
/* Forward declaration of local functions. */
131
static void usage (int status)
132
#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
133
__attribute__ ((noreturn))
136
static void compendium (const char *filename);
137
static msgdomain_list_ty *merge (const char *fn1, const char *fn2,
138
msgdomain_list_ty **defp);
142
main (int argc, char **argv)
148
msgdomain_list_ty *def;
149
msgdomain_list_ty *result;
150
bool sort_by_filepos = false;
151
bool sort_by_msgid = false;
153
/* Set program name for messages. */
154
set_program_name (argv[0]);
155
error_print_progname = maybe_print_progname;
158
gram_max_allowed_errors = UINT_MAX;
160
#ifdef HAVE_SETLOCALE
161
/* Set locale via LC_ALL. */
162
setlocale (LC_ALL, "");
165
/* Set the text message domain. */
166
bindtextdomain (PACKAGE, relocate (LOCALEDIR));
167
textdomain (PACKAGE);
169
/* Ensure that write errors on stdout are detected. */
170
atexit (close_stdout);
172
/* Set default values for variables. */
177
while ((opt = getopt_long (argc, argv, "C:D:eEFhimNo:pPqsUvVw:",
182
case '\0': /* Long option. */
190
dir_list_append (optarg);
194
message_print_style_escape (false);
198
message_print_style_escape (true);
202
sort_by_filepos = true;
210
message_print_style_indent ();
214
multi_domain_mode = true;
218
use_fuzzy_matching = false;
222
output_file = optarg;
226
message_print_syntax_properties ();
230
input_syntax = syntax_properties;
238
sort_by_msgid = true;
257
value = strtol (optarg, &endp, 10);
259
message_page_width_set (value);
263
case CHAR_MAX + 1: /* --backup */
264
version_control_string = optarg;
267
case CHAR_MAX + 2: /* --strict */
268
message_print_style_uniforum ();
271
case CHAR_MAX + 3: /* --suffix */
272
backup_suffix_string = optarg;
275
case CHAR_MAX + 4: /* --no-wrap */
276
message_page_width_ignore ();
279
case CHAR_MAX + 5: /* --stringtable-input */
280
input_syntax = syntax_stringtable;
283
case CHAR_MAX + 6: /* --stringtable-output */
284
message_print_syntax_stringtable ();
288
usage (EXIT_FAILURE);
292
/* Version information is requested. */
295
printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
296
/* xgettext: no-wrap */
297
printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
298
This is free software; see the source for copying conditions. There is NO\n\
299
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
301
"1995-1998, 2000-2004");
302
printf (_("Written by %s.\n"), "Peter Miller");
306
/* Help is requested. */
308
usage (EXIT_SUCCESS);
310
/* Test whether we have an .po file name as argument. */
313
error (EXIT_SUCCESS, 0, _("no input files given"));
314
usage (EXIT_FAILURE);
316
if (optind + 2 != argc)
318
error (EXIT_SUCCESS, 0, _("exactly 2 input files required"));
319
usage (EXIT_FAILURE);
322
/* Verify selected options. */
325
if (output_file != NULL)
327
error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
328
"--update", "--output-file");
333
if (version_control_string != NULL)
335
error (EXIT_SUCCESS, 0, _("%s is only valid with %s"),
336
"--backup", "--update");
337
usage (EXIT_FAILURE);
339
if (backup_suffix_string != NULL)
341
error (EXIT_SUCCESS, 0, _("%s is only valid with %s"),
342
"--suffix", "--update");
343
usage (EXIT_FAILURE);
347
if (!line_comment && sort_by_filepos)
348
error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
349
"--no-location", "--sort-by-file");
351
if (sort_by_msgid && sort_by_filepos)
352
error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
353
"--sort-output", "--sort-by-file");
355
/* In update mode, --properties-input implies --properties-output. */
356
if (update_mode && input_syntax == syntax_properties)
357
message_print_syntax_properties ();
358
/* In update mode, --stringtable-input implies --stringtable-output. */
359
if (update_mode && input_syntax == syntax_stringtable)
360
message_print_syntax_stringtable ();
362
/* Merge the two files. */
363
result = merge (argv[optind], argv[optind + 1], &def);
365
/* Sort the results. */
367
msgdomain_list_sort_by_filepos (result);
368
else if (sort_by_msgid)
369
msgdomain_list_sort_by_msgid (result);
373
/* Do nothing if the original file and the result are equal. Also do
374
nothing if the original file and the result differ only by the
375
POT-Creation-Date in the header entry; this is needed for projects
376
which don't put the .pot file under CVS. */
377
if (!msgdomain_list_equal (def, result, true))
379
/* Back up def.po. */
380
enum backup_type backup_type;
383
output_file = argv[optind];
385
if (backup_suffix_string == NULL)
387
backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
388
if (backup_suffix_string != NULL
389
&& backup_suffix_string[0] == '\0')
390
backup_suffix_string = NULL;
392
if (backup_suffix_string != NULL)
393
simple_backup_suffix = backup_suffix_string;
395
backup_type = xget_version (_("backup type"), version_control_string);
396
if (backup_type != none)
398
backup_file = find_backup_file_name (output_file, backup_type);
399
copy_file_preserving (output_file, backup_file);
402
/* Write the merged message list out. */
403
msgdomain_list_print (result, output_file, true, false);
408
/* Write the merged message list out. */
409
msgdomain_list_print (result, output_file, force_po, false);
416
/* Display usage information and exit. */
420
if (status != EXIT_SUCCESS)
421
fprintf (stderr, _("Try `%s --help' for more information.\n"),
426
Usage: %s [OPTION] def.po ref.pot\n\
429
/* xgettext: no-wrap */
431
Merges two Uniforum style .po files together. The def.po file is an\n\
432
existing PO file with translations which will be taken over to the newly\n\
433
created file as long as they still match; comments will be preserved,\n\
434
but extracted comments and file positions will be discarded. The ref.pot\n\
435
file is the last created PO file with up-to-date source references but\n\
436
old translations, or a PO Template file (generally created by xgettext);\n\
437
any translations or comments in the file will be discarded, however dot\n\
438
comments and file positions will be preserved. Where an exact match\n\
439
cannot be found, fuzzy matching is used to produce better results.\n\
443
Mandatory arguments to long options are mandatory for short options too.\n"));
446
Input file location:\n"));
448
def.po translations referring to old sources\n"));
450
ref.pot references to new sources\n"));
452
-D, --directory=DIRECTORY add DIRECTORY to list for input files search\n"));
454
-C, --compendium=FILE additional library of message translations,\n\
455
may be specified more than once\n"));
458
Operation mode:\n"));
460
-U, --update update def.po,\n\
461
do nothing if def.po already up to date\n"));
464
Output file location:\n"));
466
-o, --output-file=FILE write output to specified file\n"));
468
The results are written to standard output if no output file is specified\n\
472
Output file location in update mode:\n"));
474
The result is written back to def.po.\n"));
476
--backup=CONTROL make a backup of def.po\n"));
478
--suffix=SUFFIX override the usual backup suffix\n"));
480
The version control method may be selected via the --backup option or through\n\
481
the VERSION_CONTROL environment variable. Here are the values:\n\
482
none, off never make backups (even if --backup is given)\n\
483
numbered, t make numbered backups\n\
484
existing, nil numbered if numbered backups exist, simple otherwise\n\
485
simple, never always make simple backups\n"));
487
The backup suffix is `~', unless set with --suffix or the SIMPLE_BACKUP_SUFFIX\n\
488
environment variable.\n\
492
Operation modifiers:\n"));
494
-m, --multi-domain apply ref.pot to each of the domains in def.po\n"));
496
-N, --no-fuzzy-matching do not use fuzzy matching\n"));
499
Input file syntax:\n"));
501
-P, --properties-input input files are in Java .properties syntax\n"));
503
--stringtable-input input files are in NeXTstep/GNUstep .strings\n\
507
Output details:\n"));
509
-e, --no-escape do not use C escapes in output (default)\n"));
511
-E, --escape use C escapes in output, no extended chars\n"));
513
--force-po write PO file even if empty\n"));
515
-i, --indent indented output style\n"));
517
--no-location suppress '#: filename:line' lines\n"));
519
--add-location preserve '#: filename:line' lines (default)\n"));
521
--strict strict Uniforum output style\n"));
523
-p, --properties-output write out a Java .properties file\n"));
525
--stringtable-output write out a NeXTstep/GNUstep .strings file\n"));
527
-w, --width=NUMBER set output page width\n"));
529
--no-wrap do not break long message lines, longer than\n\
530
the output page width, into several lines\n"));
532
-s, --sort-output generate sorted output\n"));
534
-F, --sort-by-file sort output by file location\n"));
537
Informative output:\n"));
539
-h, --help display this help and exit\n"));
541
-V, --version output version information and exit\n"));
543
-v, --verbose increase verbosity level\n"));
545
-q, --quiet, --silent suppress progress indicators\n"));
547
fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"),
556
compendium (const char *filename)
558
msgdomain_list_ty *mdlp;
561
mdlp = read_po_file (filename);
563
compendiums = message_list_list_alloc ();
564
for (k = 0; k < mdlp->nitems; k++)
565
message_list_list_append (compendiums, mdlp->item[k]->messages);
570
msgfmt_check_pair_fails (const lex_pos_ty *pos,
571
const char *msgid, const char *msgid_plural,
572
const char *msgstr, size_t msgstr_len,
576
struct formatstring_parser *parser = formatstring_parsers[fmt];
577
char *invalid_reason = NULL;
579
parser->parse (msgid_plural != NULL ? msgid_plural : msgid, false,
583
if (msgid_descr != NULL)
585
const char *p_end = msgstr + msgstr_len;
588
for (p = msgstr; p < p_end; p += strlen (p) + 1)
590
void *msgstr_descr = parser->parse (msgstr, true, &invalid_reason);
592
if (msgstr_descr != NULL)
594
failure = parser->check (pos, msgid_descr, msgstr_descr,
595
msgid_plural == NULL, false, NULL);
596
parser->free (msgstr_descr);
601
free (invalid_reason);
608
parser->free (msgid_descr);
611
free (invalid_reason);
618
message_merge (message_ty *def, message_ty *ref)
625
/* Take the msgid from the reference. When fuzzy matches are made,
626
the definition will not be unique, but the reference will be -
627
usually because it has only been slightly changed. */
629
/* Take the msgstr from the definition. The msgstr of the reference
630
is usually empty, as it was generated by xgettext. If we currently
631
process the header entry we have to merge the msgstr by using the
632
Report-Msgid-Bugs-To and POT-Creation-Date fields from the reference. */
633
if (ref->msgid[0] == '\0')
635
/* Oh, oh. The header entry and we have something to fill in. */
642
{ "Project-Id-Version:", sizeof ("Project-Id-Version:") - 1 },
644
{ "Report-Msgid-Bugs-To:", sizeof ("Report-Msgid-Bugs-To:") - 1 },
645
#define REPORT_MSGID_BUGS_TO 1
646
{ "POT-Creation-Date:", sizeof ("POT-Creation-Date:") - 1 },
647
#define POT_CREATION_DATE 2
648
{ "PO-Revision-Date:", sizeof ("PO-Revision-Date:") - 1 },
649
#define PO_REVISION_DATE 3
650
{ "Last-Translator:", sizeof ("Last-Translator:") - 1 },
651
#define LAST_TRANSLATOR 4
652
{ "Language-Team:", sizeof ("Language-Team:") - 1 },
653
#define LANGUAGE_TEAM 5
654
{ "MIME-Version:", sizeof ("MIME-Version:") - 1 },
655
#define MIME_VERSION 6
656
{ "Content-Type:", sizeof ("Content-Type:") - 1 },
657
#define CONTENT_TYPE 7
658
{ "Content-Transfer-Encoding:",
659
sizeof ("Content-Transfer-Encoding:") - 1 }
660
#define CONTENT_TRANSFER 8
667
} header_fields[UNKNOWN + 1];
673
/* Clear all fields. */
674
memset (header_fields, '\0', sizeof (header_fields));
676
/* Prepare a temporary memory pool. */
677
obstack_init (&pool);
682
const char *endp = strchr (cp, '\n');
683
int terminated = endp != NULL;
687
/* Add a trailing newline. */
689
endp = strchr (cp, '\0');
693
copy = (char *) obstack_alloc (&pool, len + 1);
694
stpcpy (stpcpy (copy, cp), "\n");
699
len = (endp - cp) + 1;
703
/* Compare with any of the known fields. */
705
cnt < sizeof (known_fields) / sizeof (known_fields[0]);
707
if (strncasecmp (cp, known_fields[cnt].name, known_fields[cnt].len)
711
if (cnt < sizeof (known_fields) / sizeof (known_fields[0]))
713
header_fields[cnt].string = &cp[known_fields[cnt].len];
714
header_fields[cnt].len = len - known_fields[cnt].len;
718
/* It's an unknown field. Append content to what is already
721
(char *) obstack_alloc (&pool,
722
header_fields[UNKNOWN].len + len + 1);
723
memcpy (extended, header_fields[UNKNOWN].string,
724
header_fields[UNKNOWN].len);
725
memcpy (&extended[header_fields[UNKNOWN].len], cp, len);
726
extended[header_fields[UNKNOWN].len + len] = '\0';
727
header_fields[UNKNOWN].string = extended;
728
header_fields[UNKNOWN].len += len;
735
const char *msgid_bugs_ptr;
737
msgid_bugs_ptr = strstr (ref->msgstr, "Report-Msgid-Bugs-To:");
738
if (msgid_bugs_ptr != NULL)
740
size_t msgid_bugs_len;
743
msgid_bugs_ptr += sizeof ("Report-Msgid-Bugs-To:") - 1;
745
endp = strchr (msgid_bugs_ptr, '\n');
748
/* Add a trailing newline. */
750
endp = strchr (msgid_bugs_ptr, '\0');
751
msgid_bugs_len = (endp - msgid_bugs_ptr) + 1;
752
extended = (char *) obstack_alloc (&pool, msgid_bugs_len + 1);
753
stpcpy (stpcpy (extended, msgid_bugs_ptr), "\n");
754
msgid_bugs_ptr = extended;
757
msgid_bugs_len = (endp - msgid_bugs_ptr) + 1;
759
header_fields[REPORT_MSGID_BUGS_TO].string = msgid_bugs_ptr;
760
header_fields[REPORT_MSGID_BUGS_TO].len = msgid_bugs_len;
765
const char *pot_date_ptr;
767
pot_date_ptr = strstr (ref->msgstr, "POT-Creation-Date:");
768
if (pot_date_ptr != NULL)
773
pot_date_ptr += sizeof ("POT-Creation-Date:") - 1;
775
endp = strchr (pot_date_ptr, '\n');
778
/* Add a trailing newline. */
780
endp = strchr (pot_date_ptr, '\0');
781
pot_date_len = (endp - pot_date_ptr) + 1;
782
extended = (char *) obstack_alloc (&pool, pot_date_len + 1);
783
stpcpy (stpcpy (extended, pot_date_ptr), "\n");
784
pot_date_ptr = extended;
787
pot_date_len = (endp - pot_date_ptr) + 1;
789
header_fields[POT_CREATION_DATE].string = pot_date_ptr;
790
header_fields[POT_CREATION_DATE].len = pot_date_len;
794
/* Concatenate all the various fields. */
796
for (cnt = 0; cnt < UNKNOWN; ++cnt)
797
if (header_fields[cnt].string != NULL)
798
len += known_fields[cnt].len + header_fields[cnt].len;
799
len += header_fields[UNKNOWN].len;
801
cp = newp = (char *) xmalloc (len + 1);
804
#define IF_FILLED(idx) \
805
if (header_fields[idx].string) \
806
newp = stpncpy (stpcpy (newp, known_fields[idx].name), \
807
header_fields[idx].string, header_fields[idx].len)
809
IF_FILLED (PROJECT_ID);
810
IF_FILLED (REPORT_MSGID_BUGS_TO);
811
IF_FILLED (POT_CREATION_DATE);
812
IF_FILLED (PO_REVISION_DATE);
813
IF_FILLED (LAST_TRANSLATOR);
814
IF_FILLED (LANGUAGE_TEAM);
815
IF_FILLED (MIME_VERSION);
816
IF_FILLED (CONTENT_TYPE);
817
IF_FILLED (CONTENT_TRANSFER);
818
if (header_fields[UNKNOWN].string != NULL)
819
stpcpy (newp, header_fields[UNKNOWN].string);
823
/* Free the temporary memory pool. */
824
obstack_free (&pool, NULL);
827
msgstr_len = strlen (cp) + 1;
831
msgstr = def->msgstr;
832
msgstr_len = def->msgstr_len;
835
result = message_alloc (xstrdup (ref->msgid), ref->msgid_plural,
836
msgstr, msgstr_len, &def->pos);
838
/* Take the comments from the definition file. There will be none at
839
all in the reference file, as it was generated by xgettext. */
841
for (j = 0; j < def->comment->nitems; ++j)
842
message_comment_append (result, def->comment->item[j]);
844
/* Take the dot comments from the reference file, as they are
845
generated by xgettext. Any in the definition file are old ones
846
collected by previous runs of xgettext and msgmerge. */
847
if (ref->comment_dot)
848
for (j = 0; j < ref->comment_dot->nitems; ++j)
849
message_comment_dot_append (result, ref->comment_dot->item[j]);
851
/* The flags are mixed in a special way. Some informations come
852
from the reference message (such as format/no-format), others
853
come from the definition file (fuzzy or not). */
854
result->is_fuzzy = def->is_fuzzy;
856
for (i = 0; i < NFORMATS; i++)
858
result->is_format[i] = ref->is_format[i];
860
/* If the reference message is marked as being a format specifier,
861
but the definition message is not, we check if the resulting
862
message would pass "msgfmt -c". If yes, then all is fine. If
863
not, we add a fuzzy marker, because
864
1. the message needs the translator's attention,
865
2. msgmerge must not transform a PO file which passes "msgfmt -c"
866
into a PO file which doesn't. */
867
if (!result->is_fuzzy
868
&& possible_format_p (ref->is_format[i])
869
&& !possible_format_p (def->is_format[i])
870
&& msgfmt_check_pair_fails (&def->pos, ref->msgid, ref->msgid_plural,
871
msgstr, msgstr_len, i))
872
result->is_fuzzy = true;
875
result->do_wrap = ref->do_wrap;
877
/* Take the file position comments from the reference file, as they
878
are generated by xgettext. Any in the definition file are old ones
879
collected by previous runs of xgettext and msgmerge. */
880
for (j = 0; j < ref->filepos_count; ++j)
882
lex_pos_ty *pp = &ref->filepos[j];
883
message_comment_filepos (result, pp->file_name, pp->line_number);
886
/* Special postprocessing is needed if the reference message is a
887
plural form and the definition message isn't, or vice versa. */
888
if (ref->msgid_plural != NULL)
890
if (def->msgid_plural == NULL)
895
if (def->msgid_plural != NULL)
899
/* All done, return the merged message to the caller. */
904
#define DOT_FREQUENCY 10
907
match_domain (const char *fn1, const char *fn2,
908
message_list_list_ty *definitions, message_list_ty *refmlp,
909
message_list_ty *resultmlp,
910
struct statistics *stats, unsigned int *processed)
914
for (j = 0; j < refmlp->nitems; j++, (*processed)++)
919
/* Because merging can take a while we print something to signal
921
if (!quiet && verbosity_level <= 1 && *processed % DOT_FREQUENCY == 0)
924
refmsg = refmlp->item[j];
926
/* See if it is in the other file. */
927
defmsg = message_list_list_search (definitions, refmsg->msgid);
930
/* Merge the reference with the definition: take the #. and
931
#: comments from the reference, take the # comments from
932
the definition, take the msgstr from the definition. Add
933
this merged entry to the output message list. */
934
message_ty *mp = message_merge (defmsg, refmsg);
936
message_list_append (resultmlp, mp);
938
/* Remember that this message has been used, when we scan
939
later to see if anything was omitted. */
943
else if (refmsg->msgid[0] != '\0')
945
/* If the message was not defined at all, try to find a very
946
similar message, it could be a typo, or the suggestion may
948
if (use_fuzzy_matching
950
message_list_list_search_fuzzy (definitions,
951
refmsg->msgid)) != NULL))
955
if (verbosity_level > 1)
957
po_gram_error_at_line (&refmsg->pos, _("\
958
this message is used but not defined..."));
959
po_gram_error_at_line (&defmsg->pos, _("\
960
...but this definition is similar"));
963
/* Merge the reference with the definition: take the #. and
964
#: comments from the reference, take the # comments from
965
the definition, take the msgstr from the definition. Add
966
this merged entry to the output message list. */
967
mp = message_merge (defmsg, refmsg);
971
message_list_append (resultmlp, mp);
973
/* Remember that this message has been used, when we scan
974
later to see if anything was omitted. */
977
if (!quiet && verbosity_level <= 1)
978
/* Always print a dot if we handled a fuzzy match. */
985
if (verbosity_level > 1)
986
po_gram_error_at_line (&refmsg->pos, _("\
987
this message is used but not defined in %s"), fn1);
989
mp = message_copy (refmsg);
991
message_list_append (resultmlp, mp);
997
/* Now postprocess the problematic merges. This is needed because we
998
want the result to pass the "msgfmt -c -v" check. */
1000
/* message_merge sets mp->used to 1 or 2, depending on the problem.
1001
Compute the bitwise OR of all these. */
1002
int problematic = 0;
1004
for (j = 0; j < resultmlp->nitems; j++)
1005
problematic |= resultmlp->item[j]->used;
1009
unsigned long int nplurals = 0;
1011
if (problematic & 1)
1013
/* Need to know nplurals of the result domain. */
1014
message_ty *header_entry = message_list_search (resultmlp, "");
1016
nplurals = get_plural_count (header_entry
1017
? header_entry->msgstr
1021
for (j = 0; j < resultmlp->nitems; j++)
1023
message_ty *mp = resultmlp->item[j];
1025
if ((mp->used & 1) && (nplurals > 0))
1027
/* ref->msgid_plural != NULL but def->msgid_plural == NULL.
1028
Use a copy of def->msgstr for each possible plural form. */
1029
size_t new_msgstr_len;
1034
if (verbosity_level > 1)
1036
po_gram_error_at_line (&mp->pos, _("\
1037
this message should define plural forms"));
1040
new_msgstr_len = nplurals * mp->msgstr_len;
1041
new_msgstr = (char *) xmalloc (new_msgstr_len);
1042
for (i = 0, p = new_msgstr; i < nplurals; i++)
1044
memcpy (p, mp->msgstr, mp->msgstr_len);
1045
p += mp->msgstr_len;
1047
mp->msgstr = new_msgstr;
1048
mp->msgstr_len = new_msgstr_len;
1049
mp->is_fuzzy = true;
1052
if ((mp->used & 2) && (mp->msgstr_len > strlen (mp->msgstr) + 1))
1054
/* ref->msgid_plural == NULL but def->msgid_plural != NULL.
1055
Use only the first among the plural forms. */
1057
if (verbosity_level > 1)
1059
po_gram_error_at_line (&mp->pos, _("\
1060
this message should not define plural forms"));
1063
mp->msgstr_len = strlen (mp->msgstr) + 1;
1064
mp->is_fuzzy = true;
1067
/* Postprocessing of this message is done. */
1074
static msgdomain_list_ty *
1075
merge (const char *fn1, const char *fn2, msgdomain_list_ty **defp)
1077
msgdomain_list_ty *def;
1078
msgdomain_list_ty *ref;
1080
unsigned int processed;
1081
struct statistics stats;
1082
msgdomain_list_ty *result;
1083
message_list_list_ty *definitions;
1084
message_list_ty *empty_list;
1086
stats.merged = stats.fuzzied = stats.missing = stats.obsolete = 0;
1088
/* This is the definitions file, created by a human. */
1089
def = read_po_file (fn1);
1091
/* Create the set of places to look for message definitions: a list
1092
whose first element will be definitions for the current domain, and
1093
whose other elements come from the compendiums. */
1094
definitions = message_list_list_alloc ();
1095
message_list_list_append (definitions, NULL);
1097
message_list_list_append_list (definitions, compendiums);
1098
empty_list = message_list_alloc (false);
1100
/* This is the references file, created by groping the sources with
1101
the xgettext program. */
1102
ref = read_po_file (fn2);
1103
/* Add a dummy header entry, if the references file contains none. */
1104
for (k = 0; k < ref->nitems; k++)
1105
if (message_list_search (ref->item[k]->messages, "") == NULL)
1107
static lex_pos_ty pos = { __FILE__, __LINE__ };
1108
message_ty *refheader = message_alloc ("", NULL, "", 1, &pos);
1110
message_list_prepend (ref->item[k]->messages, refheader);
1113
/* The references file can be either in ASCII or in UTF-8. If it is
1114
in UTF-8, we have to convert the definitions to UTF-8 as well. */
1116
bool was_utf8 = false;
1117
for (k = 0; k < ref->nitems; k++)
1119
message_list_ty *mlp = ref->item[k]->messages;
1121
for (j = 0; j < mlp->nitems; j++)
1122
if (mlp->item[j]->msgid[0] == '\0' && !mlp->item[j]->obsolete)
1124
const char *header = mlp->item[j]->msgstr;
1128
const char *charsetstr = strstr (header, "charset=");
1130
if (charsetstr != NULL)
1134
charsetstr += strlen ("charset=");
1135
len = strcspn (charsetstr, " \t\n");
1136
if (len == strlen ("UTF-8")
1137
&& strncasecmp (charsetstr, "UTF-8", len) == 0)
1144
def = iconv_msgdomain_list (def, "UTF-8", fn1);
1147
result = msgdomain_list_alloc (false);
1150
/* Every reference must be matched with its definition. */
1151
if (!multi_domain_mode)
1152
for (k = 0; k < ref->nitems; k++)
1154
const char *domain = ref->item[k]->domain;
1155
message_list_ty *refmlp = ref->item[k]->messages;
1156
message_list_ty *resultmlp =
1157
msgdomain_list_sublist (result, domain, true);
1159
definitions->item[0] = msgdomain_list_sublist (def, domain, false);
1160
if (definitions->item[0] == NULL)
1161
definitions->item[0] = empty_list;
1163
match_domain (fn1, fn2, definitions, refmlp, resultmlp,
1164
&stats, &processed);
1168
/* Apply the references messages in the default domain to each of
1169
the definition domains. */
1170
message_list_ty *refmlp = ref->item[0]->messages;
1172
for (k = 0; k < def->nitems; k++)
1174
const char *domain = def->item[k]->domain;
1175
message_list_ty *defmlp = def->item[k]->messages;
1177
/* Ignore the default message domain if it has no messages. */
1178
if (k > 0 || defmlp->nitems > 0)
1180
message_list_ty *resultmlp =
1181
msgdomain_list_sublist (result, domain, true);
1183
definitions->item[0] = defmlp;
1185
match_domain (fn1, fn2, definitions, refmlp, resultmlp,
1186
&stats, &processed);
1191
/* Look for messages in the definition file, which are not present
1192
in the reference file, indicating messages which defined but not
1193
used in the program. Don't scan the compendium(s). */
1194
for (k = 0; k < def->nitems; ++k)
1196
const char *domain = def->item[k]->domain;
1197
message_list_ty *defmlp = def->item[k]->messages;
1199
for (j = 0; j < defmlp->nitems; j++)
1201
message_ty *defmsg = defmlp->item[j];
1205
/* Remember the old translation although it is not used anymore.
1206
But we mark it as obsolete. */
1209
mp = message_copy (defmsg);
1210
mp->obsolete = true;
1212
message_list_append (msgdomain_list_sublist (result, domain, true),
1219
/* Determine the known a-priori encoding, if any. */
1220
if (def->encoding == ref->encoding)
1221
result->encoding = def->encoding;
1223
/* Report some statistics. */
1224
if (verbosity_level > 0)
1225
fprintf (stderr, _("%s\
1226
Read %ld old + %ld reference, \
1227
merged %ld, fuzzied %ld, missing %ld, obsolete %ld.\n"),
1228
!quiet && verbosity_level <= 1 ? "\n" : "",
1229
(long) def->nitems, (long) ref->nitems,
1230
(long) stats.merged, (long) stats.fuzzied, (long) stats.missing,
1231
(long) stats.obsolete);
1233
fputs (_(" done.\n"), stderr);
1235
/* Return results. */