1 /* GNU gettext - internationalization aids
2 Copyright (C) 1995-1998, 2000-2005 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"
47 #include "c-strcase.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-2005");
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 (msgid_descr
, msgstr_descr
,
595 msgid_plural
== NULL
, NULL
, 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 (c_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
)
912 message_ty
*header_entry
;
913 unsigned long int nplurals
;
914 char *untranslated_plural_msgstr
;
917 header_entry
= message_list_search (definitions
->item
[0], "");
918 nplurals
= get_plural_count (header_entry
? header_entry
->msgstr
: NULL
);
919 untranslated_plural_msgstr
= (char *) xmalloc (nplurals
);
920 memset (untranslated_plural_msgstr
, '\0', nplurals
);
922 for (j
= 0; j
< refmlp
->nitems
; j
++, (*processed
)++)
927 /* Because merging can take a while we print something to signal
929 if (!quiet
&& verbosity_level
<= 1 && *processed
% DOT_FREQUENCY
== 0)
932 refmsg
= refmlp
->item
[j
];
934 /* See if it is in the other file. */
935 defmsg
= message_list_list_search (definitions
, refmsg
->msgid
);
938 /* Merge the reference with the definition: take the #. and
939 #: comments from the reference, take the # comments from
940 the definition, take the msgstr from the definition. Add
941 this merged entry to the output message list. */
942 message_ty
*mp
= message_merge (defmsg
, refmsg
);
944 message_list_append (resultmlp
, mp
);
946 /* Remember that this message has been used, when we scan
947 later to see if anything was omitted. */
951 else if (refmsg
->msgid
[0] != '\0')
953 /* If the message was not defined at all, try to find a very
954 similar message, it could be a typo, or the suggestion may
956 if (use_fuzzy_matching
958 message_list_list_search_fuzzy (definitions
,
959 refmsg
->msgid
)) != NULL
))
963 if (verbosity_level
> 1)
965 po_gram_error_at_line (&refmsg
->pos
, _("\
966 this message is used but not defined..."));
967 po_gram_error_at_line (&defmsg
->pos
, _("\
968 ...but this definition is similar"));
971 /* Merge the reference with the definition: take the #. and
972 #: comments from the reference, take the # comments from
973 the definition, take the msgstr from the definition. Add
974 this merged entry to the output message list. */
975 mp
= message_merge (defmsg
, refmsg
);
979 message_list_append (resultmlp
, mp
);
981 /* Remember that this message has been used, when we scan
982 later to see if anything was omitted. */
985 if (!quiet
&& verbosity_level
<= 1)
986 /* Always print a dot if we handled a fuzzy match. */
992 bool is_untranslated
;
996 if (verbosity_level
> 1)
997 po_gram_error_at_line (&refmsg
->pos
, _("\
998 this message is used but not defined in %s"), fn1
);
1000 mp
= message_copy (refmsg
);
1002 if (mp
->msgid_plural
!= NULL
)
1004 /* Test if mp is untranslated. (It most likely is.) */
1005 is_untranslated
= true;
1006 for (p
= mp
->msgstr
, pend
= p
+ mp
->msgstr_len
; p
< pend
; p
++)
1009 is_untranslated
= false;
1012 if (is_untranslated
)
1014 /* Change mp->msgstr_len consecutive empty strings into
1015 nplurals consecutive empty strings. */
1016 if (nplurals
> mp
->msgstr_len
)
1017 mp
->msgstr
= untranslated_plural_msgstr
;
1018 mp
->msgstr_len
= nplurals
;
1022 message_list_append (resultmlp
, mp
);
1028 /* Now postprocess the problematic merges. This is needed because we
1029 want the result to pass the "msgfmt -c -v" check. */
1031 /* message_merge sets mp->used to 1 or 2, depending on the problem.
1032 Compute the bitwise OR of all these. */
1033 int problematic
= 0;
1035 for (j
= 0; j
< resultmlp
->nitems
; j
++)
1036 problematic
|= resultmlp
->item
[j
]->used
;
1040 unsigned long int nplurals
= 0;
1042 if (problematic
& 1)
1044 /* Need to know nplurals of the result domain. */
1045 message_ty
*header_entry
= message_list_search (resultmlp
, "");
1047 nplurals
= get_plural_count (header_entry
1048 ? header_entry
->msgstr
1052 for (j
= 0; j
< resultmlp
->nitems
; j
++)
1054 message_ty
*mp
= resultmlp
->item
[j
];
1056 if ((mp
->used
& 1) && (nplurals
> 0))
1058 /* ref->msgid_plural != NULL but def->msgid_plural == NULL.
1059 Use a copy of def->msgstr for each possible plural form. */
1060 size_t new_msgstr_len
;
1065 if (verbosity_level
> 1)
1067 po_gram_error_at_line (&mp
->pos
, _("\
1068 this message should define plural forms"));
1071 new_msgstr_len
= nplurals
* mp
->msgstr_len
;
1072 new_msgstr
= (char *) xmalloc (new_msgstr_len
);
1073 for (i
= 0, p
= new_msgstr
; i
< nplurals
; i
++)
1075 memcpy (p
, mp
->msgstr
, mp
->msgstr_len
);
1076 p
+= mp
->msgstr_len
;
1078 mp
->msgstr
= new_msgstr
;
1079 mp
->msgstr_len
= new_msgstr_len
;
1080 mp
->is_fuzzy
= true;
1083 if ((mp
->used
& 2) && (mp
->msgstr_len
> strlen (mp
->msgstr
) + 1))
1085 /* ref->msgid_plural == NULL but def->msgid_plural != NULL.
1086 Use only the first among the plural forms. */
1088 if (verbosity_level
> 1)
1090 po_gram_error_at_line (&mp
->pos
, _("\
1091 this message should not define plural forms"));
1094 mp
->msgstr_len
= strlen (mp
->msgstr
) + 1;
1095 mp
->is_fuzzy
= true;
1098 /* Postprocessing of this message is done. */
1105 static msgdomain_list_ty
*
1106 merge (const char *fn1
, const char *fn2
, msgdomain_list_ty
**defp
)
1108 msgdomain_list_ty
*def
;
1109 msgdomain_list_ty
*ref
;
1111 unsigned int processed
;
1112 struct statistics stats
;
1113 msgdomain_list_ty
*result
;
1114 message_list_list_ty
*definitions
;
1115 message_list_ty
*empty_list
;
1117 stats
.merged
= stats
.fuzzied
= stats
.missing
= stats
.obsolete
= 0;
1119 /* This is the definitions file, created by a human. */
1120 def
= read_po_file (fn1
);
1122 /* Create the set of places to look for message definitions: a list
1123 whose first element will be definitions for the current domain, and
1124 whose other elements come from the compendiums. */
1125 definitions
= message_list_list_alloc ();
1126 message_list_list_append (definitions
, NULL
);
1128 message_list_list_append_list (definitions
, compendiums
);
1129 empty_list
= message_list_alloc (false);
1131 /* This is the references file, created by groping the sources with
1132 the xgettext program. */
1133 ref
= read_po_file (fn2
);
1134 /* Add a dummy header entry, if the references file contains none. */
1135 for (k
= 0; k
< ref
->nitems
; k
++)
1136 if (message_list_search (ref
->item
[k
]->messages
, "") == NULL
)
1138 static lex_pos_ty pos
= { __FILE__
, __LINE__
};
1139 message_ty
*refheader
= message_alloc ("", NULL
, "", 1, &pos
);
1141 message_list_prepend (ref
->item
[k
]->messages
, refheader
);
1144 /* The references file can be either in ASCII or in UTF-8. If it is
1145 in UTF-8, we have to convert the definitions to UTF-8 as well. */
1147 bool was_utf8
= false;
1148 for (k
= 0; k
< ref
->nitems
; k
++)
1150 message_list_ty
*mlp
= ref
->item
[k
]->messages
;
1152 for (j
= 0; j
< mlp
->nitems
; j
++)
1153 if (mlp
->item
[j
]->msgid
[0] == '\0' && !mlp
->item
[j
]->obsolete
)
1155 const char *header
= mlp
->item
[j
]->msgstr
;
1159 const char *charsetstr
= strstr (header
, "charset=");
1161 if (charsetstr
!= NULL
)
1165 charsetstr
+= strlen ("charset=");
1166 len
= strcspn (charsetstr
, " \t\n");
1167 if (len
== strlen ("UTF-8")
1168 && c_strncasecmp (charsetstr
, "UTF-8", len
) == 0)
1175 def
= iconv_msgdomain_list (def
, "UTF-8", fn1
);
1178 result
= msgdomain_list_alloc (false);
1181 /* Every reference must be matched with its definition. */
1182 if (!multi_domain_mode
)
1183 for (k
= 0; k
< ref
->nitems
; k
++)
1185 const char *domain
= ref
->item
[k
]->domain
;
1186 message_list_ty
*refmlp
= ref
->item
[k
]->messages
;
1187 message_list_ty
*resultmlp
=
1188 msgdomain_list_sublist (result
, domain
, true);
1190 definitions
->item
[0] = msgdomain_list_sublist (def
, domain
, false);
1191 if (definitions
->item
[0] == NULL
)
1192 definitions
->item
[0] = empty_list
;
1194 match_domain (fn1
, fn2
, definitions
, refmlp
, resultmlp
,
1195 &stats
, &processed
);
1199 /* Apply the references messages in the default domain to each of
1200 the definition domains. */
1201 message_list_ty
*refmlp
= ref
->item
[0]->messages
;
1203 for (k
= 0; k
< def
->nitems
; k
++)
1205 const char *domain
= def
->item
[k
]->domain
;
1206 message_list_ty
*defmlp
= def
->item
[k
]->messages
;
1208 /* Ignore the default message domain if it has no messages. */
1209 if (k
> 0 || defmlp
->nitems
> 0)
1211 message_list_ty
*resultmlp
=
1212 msgdomain_list_sublist (result
, domain
, true);
1214 definitions
->item
[0] = defmlp
;
1216 match_domain (fn1
, fn2
, definitions
, refmlp
, resultmlp
,
1217 &stats
, &processed
);
1222 /* Look for messages in the definition file, which are not present
1223 in the reference file, indicating messages which defined but not
1224 used in the program. Don't scan the compendium(s). */
1225 for (k
= 0; k
< def
->nitems
; ++k
)
1227 const char *domain
= def
->item
[k
]->domain
;
1228 message_list_ty
*defmlp
= def
->item
[k
]->messages
;
1230 for (j
= 0; j
< defmlp
->nitems
; j
++)
1232 message_ty
*defmsg
= defmlp
->item
[j
];
1236 /* Remember the old translation although it is not used anymore.
1237 But we mark it as obsolete. */
1240 mp
= message_copy (defmsg
);
1241 mp
->obsolete
= true;
1243 message_list_append (msgdomain_list_sublist (result
, domain
, true),
1250 /* Determine the known a-priori encoding, if any. */
1251 if (def
->encoding
== ref
->encoding
)
1252 result
->encoding
= def
->encoding
;
1254 /* Report some statistics. */
1255 if (verbosity_level
> 0)
1256 fprintf (stderr
, _("%s\
1257 Read %ld old + %ld reference, \
1258 merged %ld, fuzzied %ld, missing %ld, obsolete %ld.\n"),
1259 !quiet
&& verbosity_level
<= 1 ? "\n" : "",
1260 (long) def
->nitems
, (long) ref
->nitems
,
1261 (long) stats
.merged
, (long) stats
.fuzzied
, (long) stats
.missing
,
1262 (long) stats
.obsolete
);
1264 fputs (_(" done.\n"), stderr
);
1266 /* Return results. */