xref: /netbsd-src/external/gpl2/gettext/dist/gettext-tools/src/msgl-cat.c (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
1 /* Message list concatenation and duplicate handling.
2    Copyright (C) 2001-2003, 2005-2006 Free Software Foundation, Inc.
3    Written by Bruno Haible <haible@clisp.cons.org>, 2001.
4 
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)
8    any later version.
9 
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.
14 
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
18 
19 
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23 #include <alloca.h>
24 
25 /* Specification.  */
26 #include "msgl-cat.h"
27 
28 #include <stdbool.h>
29 #include <stddef.h>
30 #include <string.h>
31 
32 #include "error.h"
33 #include "xerror.h"
34 #include "xvasprintf.h"
35 #include "message.h"
36 #include "read-catalog.h"
37 #include "po-charset.h"
38 #include "msgl-ascii.h"
39 #include "msgl-equal.h"
40 #include "msgl-iconv.h"
41 #include "xalloc.h"
42 #include "xallocsa.h"
43 #include "c-strstr.h"
44 #include "basename.h"
45 #include "exit.h"
46 #include "gettext.h"
47 
48 #define _(str) gettext (str)
49 
50 
51 /* These variables control which messages are selected.  */
52 int more_than;
53 int less_than;
54 
55 /* If true, use the first available translation.
56    If false, merge all available translations into one and fuzzy it.  */
57 bool use_first;
58 
59 /* If true, merge like msgcomm.
60    If false, merge like msgcat and msguniq.  */
61 bool msgcomm_mode = false;
62 
63 /* If true, omit the header entry.
64    If false, keep the header entry present in the input.  */
65 bool omit_header = false;
66 
67 
68 static bool
is_message_selected(const message_ty * tmp)69 is_message_selected (const message_ty *tmp)
70 {
71   int used = (tmp->used >= 0 ? tmp->used : - tmp->used);
72 
73   return (is_header (tmp)
74 	  ? !omit_header	/* keep the header entry */
75 	  : (used > more_than && used < less_than));
76 }
77 
78 
79 static bool
is_message_needed(const message_ty * mp)80 is_message_needed (const message_ty *mp)
81 {
82   if (!msgcomm_mode
83       && ((!is_header (mp) && mp->is_fuzzy) || mp->msgstr[0] == '\0'))
84     /* Weak translation.  Needed if there are only weak translations.  */
85     return mp->tmp->used < 0 && is_message_selected (mp->tmp);
86   else
87     /* Good translation.  */
88     return is_message_selected (mp->tmp);
89 }
90 
91 
92 /* The use_first logic.  */
93 static bool
is_message_first_needed(const message_ty * mp)94 is_message_first_needed (const message_ty *mp)
95 {
96   if (mp->tmp->obsolete && is_message_needed (mp))
97     {
98       mp->tmp->obsolete = false;
99       return true;
100     }
101   else
102     return false;
103 }
104 
105 
106 msgdomain_list_ty *
catenate_msgdomain_list(string_list_ty * file_list,catalog_input_format_ty input_syntax,const char * to_code)107 catenate_msgdomain_list (string_list_ty *file_list,
108 			 catalog_input_format_ty input_syntax,
109 			 const char *to_code)
110 {
111   const char * const *files = file_list->item;
112   size_t nfiles = file_list->nitems;
113   msgdomain_list_ty **mdlps;
114   const char ***canon_charsets;
115   const char ***identifications;
116   msgdomain_list_ty *total_mdlp;
117   const char *canon_to_code;
118   size_t n, j;
119 
120   /* Read input files.  */
121   mdlps =
122     (msgdomain_list_ty **) xmalloc (nfiles * sizeof (msgdomain_list_ty *));
123   for (n = 0; n < nfiles; n++)
124     mdlps[n] = read_catalog_file (files[n], input_syntax);
125 
126   /* Determine the canonical name of each input file's encoding.  */
127   canon_charsets = (const char ***) xmalloc (nfiles * sizeof (const char **));
128   for (n = 0; n < nfiles; n++)
129     {
130       msgdomain_list_ty *mdlp = mdlps[n];
131       size_t k;
132 
133       canon_charsets[n] =
134 	(const char **) xmalloc (mdlp->nitems * sizeof (const char *));
135       for (k = 0; k < mdlp->nitems; k++)
136 	{
137 	  message_list_ty *mlp = mdlp->item[k]->messages;
138 	  const char *canon_from_code = NULL;
139 
140 	  if (mlp->nitems > 0)
141 	    {
142 	      for (j = 0; j < mlp->nitems; j++)
143 		if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
144 		  {
145 		    const char *header = mlp->item[j]->msgstr;
146 
147 		    if (header != NULL)
148 		      {
149 			const char *charsetstr = c_strstr (header, "charset=");
150 
151 			if (charsetstr != NULL)
152 			  {
153 			    size_t len;
154 			    char *charset;
155 			    const char *canon_charset;
156 
157 			    charsetstr += strlen ("charset=");
158 			    len = strcspn (charsetstr, " \t\n");
159 			    charset = (char *) xallocsa (len + 1);
160 			    memcpy (charset, charsetstr, len);
161 			    charset[len] = '\0';
162 
163 			    canon_charset = po_charset_canonicalize (charset);
164 			    if (canon_charset == NULL)
165 			      {
166 				/* Don't give an error for POT files, because
167 				   POT files usually contain only ASCII
168 				   msgids.  */
169 				const char *filename = files[n];
170 				size_t filenamelen = strlen (filename);
171 
172 				if (filenamelen >= 4
173 				    && memcmp (filename + filenamelen - 4,
174 					       ".pot", 4) == 0
175 				    && strcmp (charset, "CHARSET") == 0)
176 				  canon_charset = po_charset_ascii;
177 				else
178 				  error (EXIT_FAILURE, 0,
179 					 _("\
180 present charset \"%s\" is not a portable encoding name"),
181 					 charset);
182 			      }
183 
184 			    freesa (charset);
185 
186 			    if (canon_from_code == NULL)
187 			      canon_from_code = canon_charset;
188 			    else if (canon_from_code != canon_charset)
189 			      error (EXIT_FAILURE, 0,
190 				     _("\
191 two different charsets \"%s\" and \"%s\" in input file"),
192 				     canon_from_code, canon_charset);
193 			  }
194 		      }
195 		  }
196 	      if (canon_from_code == NULL)
197 		{
198 		  if (is_ascii_message_list (mlp))
199 		    canon_from_code = po_charset_ascii;
200 		  else if (mdlp->encoding != NULL)
201 		    canon_from_code = mdlp->encoding;
202 		  else
203 		    {
204 		      if (k == 0)
205 			error (EXIT_FAILURE, 0, _("\
206 input file `%s' doesn't contain a header entry with a charset specification"),
207 			       files[n]);
208 		      else
209 			error (EXIT_FAILURE, 0, _("\
210 domain \"%s\" in input file `%s' doesn't contain a header entry with a charset specification"),
211 			       mdlp->item[k]->domain, files[n]);
212 		    }
213 		}
214 	    }
215 	  canon_charsets[n][k] = canon_from_code;
216 	}
217     }
218 
219   /* Determine textual identifications of each file/domain combination.  */
220   identifications = (const char ***) xmalloc (nfiles * sizeof (const char **));
221   for (n = 0; n < nfiles; n++)
222     {
223       const char *filename = basename (files[n]);
224       msgdomain_list_ty *mdlp = mdlps[n];
225       size_t k;
226 
227       identifications[n] =
228 	(const char **) xmalloc (mdlp->nitems * sizeof (const char *));
229       for (k = 0; k < mdlp->nitems; k++)
230 	{
231 	  const char *domain = mdlp->item[k]->domain;
232 	  message_list_ty *mlp = mdlp->item[k]->messages;
233 	  char *project_id = NULL;
234 
235 	  for (j = 0; j < mlp->nitems; j++)
236 	    if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
237 	      {
238 		const char *header = mlp->item[j]->msgstr;
239 
240 		if (header != NULL)
241 		  {
242 		    const char *cp = c_strstr (header, "Project-Id-Version:");
243 
244 		    if (cp != NULL)
245 		      {
246 			const char *endp;
247 
248 			cp += sizeof ("Project-Id-Version:") - 1;
249 
250 			endp = strchr (cp, '\n');
251 			if (endp == NULL)
252 			  endp = cp + strlen (cp);
253 
254 			while (cp < endp && *cp == ' ')
255 			  cp++;
256 
257 			if (cp < endp)
258 			  {
259 			    size_t len = endp - cp;
260 			    project_id = (char *) xmalloc (len + 1);
261 			    memcpy (project_id, cp, len);
262 			    project_id[len] = '\0';
263 			  }
264 			break;
265 		      }
266 		  }
267 	      }
268 
269 	  identifications[n][k] =
270 	    (project_id != NULL
271 	     ? (k > 0 ? xasprintf ("%s:%s (%s)", filename, domain, project_id)
272 		      : xasprintf ("%s (%s)", filename, project_id))
273 	     : (k > 0 ? xasprintf ("%s:%s", filename, domain)
274 		      : xasprintf ("%s", filename)));
275 	}
276     }
277 
278   /* Create list of resulting messages, but don't fill it.  Only count
279      the number of translations for each message.
280      If for a message, there is at least one non-fuzzy, non-empty translation,
281      use only the non-fuzzy, non-empty translations.  Otherwise use the
282      fuzzy or empty translations as well.  */
283   total_mdlp = msgdomain_list_alloc (true);
284   for (n = 0; n < nfiles; n++)
285     {
286       msgdomain_list_ty *mdlp = mdlps[n];
287       size_t k;
288 
289       for (k = 0; k < mdlp->nitems; k++)
290 	{
291 	  const char *domain = mdlp->item[k]->domain;
292 	  message_list_ty *mlp = mdlp->item[k]->messages;
293 	  message_list_ty *total_mlp;
294 
295 	  total_mlp = msgdomain_list_sublist (total_mdlp, domain, true);
296 
297 	  for (j = 0; j < mlp->nitems; j++)
298 	    {
299 	      message_ty *mp = mlp->item[j];
300 	      message_ty *tmp;
301 	      size_t i;
302 
303 	      tmp = message_list_search (total_mlp, mp->msgctxt, mp->msgid);
304 	      if (tmp == NULL)
305 		{
306 		  tmp = message_alloc (mp->msgctxt, mp->msgid, mp->msgid_plural,
307 				       NULL, 0, &mp->pos);
308 		  tmp->is_fuzzy = true; /* may be set to false later */
309 		  for (i = 0; i < NFORMATS; i++)
310 		    tmp->is_format[i] = undecided; /* may be set to yes/no later */
311 		  tmp->do_wrap = yes; /* may be set to no later */
312 		  tmp->obsolete = true; /* may be set to false later */
313 		  tmp->alternative_count = 0;
314 		  tmp->alternative = NULL;
315 		  message_list_append (total_mlp, tmp);
316 		}
317 
318 	      if (!msgcomm_mode
319 		  && ((!is_header (mp) && mp->is_fuzzy)
320 		      || mp->msgstr[0] == '\0'))
321 		/* Weak translation.  Counted as negative tmp->used.  */
322 		{
323 		  if (tmp->used <= 0)
324 		    tmp->used--;
325 		}
326 	      else
327 		/* Good translation.  Counted as positive tmp->used.  */
328 		{
329 		  if (tmp->used < 0)
330 		    tmp->used = 0;
331 		  tmp->used++;
332 		}
333 	      mp->tmp = tmp;
334 	    }
335 	}
336     }
337 
338   /* Remove messages that are not used and need not be converted.  */
339   for (n = 0; n < nfiles; n++)
340     {
341       msgdomain_list_ty *mdlp = mdlps[n];
342       size_t k;
343 
344       for (k = 0; k < mdlp->nitems; k++)
345 	{
346 	  message_list_ty *mlp = mdlp->item[k]->messages;
347 
348 	  message_list_remove_if_not (mlp,
349 				      use_first
350 				      ? is_message_first_needed
351 				      : is_message_needed);
352 
353 	  /* If no messages are remaining, drop the charset.  */
354 	  if (mlp->nitems == 0)
355 	    canon_charsets[n][k] = NULL;
356 	}
357     }
358   {
359     size_t k;
360 
361     for (k = 0; k < total_mdlp->nitems; k++)
362       {
363 	message_list_ty *mlp = total_mdlp->item[k]->messages;
364 
365 	message_list_remove_if_not (mlp, is_message_selected);
366       }
367   }
368 
369   /* Determine the common known a-priori encoding, if any.  */
370   if (nfiles > 0)
371     {
372       bool all_same_encoding = true;
373 
374       for (n = 1; n < nfiles; n++)
375 	if (mdlps[n]->encoding != mdlps[0]->encoding)
376 	  {
377 	    all_same_encoding = false;
378 	    break;
379 	  }
380 
381       if (all_same_encoding)
382 	total_mdlp->encoding = mdlps[0]->encoding;
383     }
384 
385   /* Determine the target encoding for the remaining messages.  */
386   if (to_code != NULL)
387     {
388       /* Canonicalize target encoding.  */
389       canon_to_code = po_charset_canonicalize (to_code);
390       if (canon_to_code == NULL)
391 	error (EXIT_FAILURE, 0,
392 	       _("target charset \"%s\" is not a portable encoding name."),
393 	       to_code);
394     }
395   else
396     {
397       /* No target encoding was specified.  Test whether the messages are
398 	 all in a single encoding.  If so, conversion is not needed.  */
399       const char *first = NULL;
400       const char *second = NULL;
401       bool with_ASCII = false;
402       bool with_UTF8 = false;
403       bool all_ASCII_compatible = true;
404 
405       for (n = 0; n < nfiles; n++)
406 	{
407 	  msgdomain_list_ty *mdlp = mdlps[n];
408 	  size_t k;
409 
410 	  for (k = 0; k < mdlp->nitems; k++)
411 	    if (canon_charsets[n][k] != NULL)
412 	      {
413 		if (canon_charsets[n][k] == po_charset_ascii)
414 		  with_ASCII = true;
415 		else
416 		  {
417 		    if (first == NULL)
418 		      first = canon_charsets[n][k];
419 		    else if (canon_charsets[n][k] != first && second == NULL)
420 		      second = canon_charsets[n][k];
421 
422 		    if (strcmp (canon_charsets[n][k], "UTF-8") == 0)
423 		      with_UTF8 = true;
424 
425 		    if (!po_charset_ascii_compatible (canon_charsets[n][k]))
426 		      all_ASCII_compatible = false;
427 		  }
428 	      }
429 	}
430 
431       if (with_ASCII && !all_ASCII_compatible)
432 	{
433 	  /* assert (first != NULL); */
434 	  if (second == NULL)
435 	    second = po_charset_ascii;
436 	}
437 
438       if (second != NULL)
439 	{
440 	  /* A conversion is needed.  Warn the user since he hasn't asked
441 	     for it and might be surprised.  */
442 	  if (with_UTF8)
443 	    multiline_warning (xasprintf (_("warning: ")),
444 			       xasprintf (_("\
445 Input files contain messages in different encodings, UTF-8 among others.\n\
446 Converting the output to UTF-8.\n\
447 ")));
448 	  else
449 	    multiline_warning (xasprintf (_("warning: ")),
450 			       xasprintf (_("\
451 Input files contain messages in different encodings, %s and %s among others.\n\
452 Converting the output to UTF-8.\n\
453 To select a different output encoding, use the --to-code option.\n\
454 "), first, second));
455 	  canon_to_code = po_charset_utf8;
456 	}
457       else if (first != NULL && with_ASCII && all_ASCII_compatible)
458 	{
459 	  /* The conversion is a no-op conversion.  Don't warn the user,
460 	     but still perform the conversion, in order to check that the
461 	     input was really ASCII.  */
462 	  canon_to_code = first;
463 	}
464       else
465 	{
466 	  /* No conversion needed.  */
467 	  canon_to_code = NULL;
468 	}
469     }
470 
471   /* Now convert the remaining messages to to_code.  */
472   if (canon_to_code != NULL)
473     for (n = 0; n < nfiles; n++)
474       {
475 	msgdomain_list_ty *mdlp = mdlps[n];
476 	size_t k;
477 
478 	for (k = 0; k < mdlp->nitems; k++)
479 	  if (canon_charsets[n][k] != NULL)
480 	    /* If the user hasn't given a to_code, don't bother doing a noop
481 	       conversion that would only replace the charset name in the
482 	       header entry with its canonical equivalent.  */
483 	    if (!(to_code == NULL && canon_charsets[n][k] == canon_to_code))
484 	      if (iconv_message_list (mdlp->item[k]->messages,
485 				      canon_charsets[n][k], canon_to_code,
486 				      files[n]))
487 		{
488 		  multiline_error (xstrdup (""),
489 				   xasprintf (_("\
490 Conversion of file %s from %s encoding to %s encoding\n\
491 changes some msgids or msgctxts.\n\
492 Either change all msgids and msgctxts to be pure ASCII, or ensure they are\n\
493 UTF-8 encoded from the beginning, i.e. already in your source code files.\n"),
494 					      files[n], canon_charsets[n][k],
495 					      canon_to_code));
496 		  exit (EXIT_FAILURE);
497 		}
498       }
499 
500   /* Fill the resulting messages.  */
501   for (n = 0; n < nfiles; n++)
502     {
503       msgdomain_list_ty *mdlp = mdlps[n];
504       size_t k;
505 
506       for (k = 0; k < mdlp->nitems; k++)
507 	{
508 	  message_list_ty *mlp = mdlp->item[k]->messages;
509 
510 	  for (j = 0; j < mlp->nitems; j++)
511 	    {
512 	      message_ty *mp = mlp->item[j];
513 	      message_ty *tmp = mp->tmp;
514 	      size_t i;
515 
516 	      /* No need to discard unneeded weak translations here;
517 		 they have already been filtered out above.  */
518 	      if (use_first || tmp->used == 1 || tmp->used == -1)
519 		{
520 		  /* Copy mp, as only message, into tmp.  */
521 		  tmp->msgstr = mp->msgstr;
522 		  tmp->msgstr_len = mp->msgstr_len;
523 		  tmp->pos = mp->pos;
524 		  if (mp->comment)
525 		    for (i = 0; i < mp->comment->nitems; i++)
526 		      message_comment_append (tmp, mp->comment->item[i]);
527 		  if (mp->comment_dot)
528 		    for (i = 0; i < mp->comment_dot->nitems; i++)
529 		      message_comment_dot_append (tmp,
530 						  mp->comment_dot->item[i]);
531 		  for (i = 0; i < mp->filepos_count; i++)
532 		    message_comment_filepos (tmp, mp->filepos[i].file_name,
533 					     mp->filepos[i].line_number);
534 		  tmp->is_fuzzy = mp->is_fuzzy;
535 		  for (i = 0; i < NFORMATS; i++)
536 		    tmp->is_format[i] = mp->is_format[i];
537 		  tmp->do_wrap = mp->do_wrap;
538 		  tmp->prev_msgctxt = mp->prev_msgctxt;
539 		  tmp->prev_msgid = mp->prev_msgid;
540 		  tmp->prev_msgid_plural = mp->prev_msgid_plural;
541 		  tmp->obsolete = mp->obsolete;
542 		}
543 	      else if (msgcomm_mode)
544 		{
545 		  /* Copy mp, as only message, into tmp.  */
546 		  if (tmp->msgstr == NULL)
547 		    {
548 		      tmp->msgstr = mp->msgstr;
549 		      tmp->msgstr_len = mp->msgstr_len;
550 		      tmp->pos = mp->pos;
551 		      tmp->is_fuzzy = mp->is_fuzzy;
552 		      tmp->prev_msgctxt = mp->prev_msgctxt;
553 		      tmp->prev_msgid = mp->prev_msgid;
554 		      tmp->prev_msgid_plural = mp->prev_msgid_plural;
555 		    }
556 		  if (mp->comment && tmp->comment == NULL)
557 		    for (i = 0; i < mp->comment->nitems; i++)
558 		      message_comment_append (tmp, mp->comment->item[i]);
559 		  if (mp->comment_dot && tmp->comment_dot == NULL)
560 		    for (i = 0; i < mp->comment_dot->nitems; i++)
561 		      message_comment_dot_append (tmp,
562 						  mp->comment_dot->item[i]);
563 		  for (i = 0; i < mp->filepos_count; i++)
564 		    message_comment_filepos (tmp, mp->filepos[i].file_name,
565 					     mp->filepos[i].line_number);
566 		  for (i = 0; i < NFORMATS; i++)
567 		    if (tmp->is_format[i] == undecided)
568 		      tmp->is_format[i] = mp->is_format[i];
569 		  if (tmp->do_wrap == undecided)
570 		    tmp->do_wrap = mp->do_wrap;
571 		  tmp->obsolete = false;
572 		}
573 	      else
574 		{
575 		  /* Copy mp, among others, into tmp.  */
576 		  char *id = xasprintf ("#-#-#-#-#  %s  #-#-#-#-#",
577 					identifications[n][k]);
578 		  size_t nbytes;
579 
580 		  if (tmp->alternative_count == 0)
581 		    tmp->pos = mp->pos;
582 
583 		  i = tmp->alternative_count;
584 		  nbytes = (i + 1) * sizeof (struct altstr);
585 		  tmp->alternative = xrealloc (tmp->alternative, nbytes);
586 		  tmp->alternative[i].msgstr = mp->msgstr;
587 		  tmp->alternative[i].msgstr_len = mp->msgstr_len;
588 		  tmp->alternative[i].msgstr_end =
589 		    tmp->alternative[i].msgstr + tmp->alternative[i].msgstr_len;
590 		  tmp->alternative[i].comment = mp->comment;
591 		  tmp->alternative[i].comment_dot = mp->comment_dot;
592 		  tmp->alternative[i].id = id;
593 		  tmp->alternative_count = i + 1;
594 
595 		  for (i = 0; i < mp->filepos_count; i++)
596 		    message_comment_filepos (tmp, mp->filepos[i].file_name,
597 					     mp->filepos[i].line_number);
598 		  if (!mp->is_fuzzy)
599 		    tmp->is_fuzzy = false;
600 		  for (i = 0; i < NFORMATS; i++)
601 		    if (mp->is_format[i] == yes)
602 		      tmp->is_format[i] = yes;
603 		    else if (mp->is_format[i] == no
604 			     && tmp->is_format[i] == undecided)
605 		      tmp->is_format[i] = no;
606 		  if (mp->do_wrap == no)
607 		    tmp->do_wrap = no;
608 		  /* Don't fill tmp->prev_msgid in this case.  */
609 		  if (!mp->obsolete)
610 		    tmp->obsolete = false;
611 		}
612 	    }
613 	}
614     }
615   {
616     size_t k;
617 
618     for (k = 0; k < total_mdlp->nitems; k++)
619       {
620 	message_list_ty *mlp = total_mdlp->item[k]->messages;
621 
622 	for (j = 0; j < mlp->nitems; j++)
623 	  {
624 	    message_ty *tmp = mlp->item[j];
625 
626 	    if (tmp->alternative_count > 0)
627 	      {
628 		/* Test whether all alternative translations are equal.  */
629 		struct altstr *first = &tmp->alternative[0];
630 		size_t i;
631 
632 		for (i = 0; i < tmp->alternative_count; i++)
633 		  if (!(tmp->alternative[i].msgstr_len == first->msgstr_len
634 		        && memcmp (tmp->alternative[i].msgstr, first->msgstr,
635 				   first->msgstr_len) == 0))
636 		    break;
637 
638 		if (i == tmp->alternative_count)
639 		  {
640 		    /* All alternatives are equal.  */
641 		    tmp->msgstr = first->msgstr;
642 		    tmp->msgstr_len = first->msgstr_len;
643 		  }
644 		else
645 		  {
646 		    /* Concatenate the alternative msgstrs into a single one,
647 		       separated by markers.  */
648 		    size_t len;
649 		    const char *p;
650 		    const char *p_end;
651 		    char *new_msgstr;
652 		    char *np;
653 
654 		    len = 0;
655 		    for (i = 0; i < tmp->alternative_count; i++)
656 		      {
657 			size_t id_len = strlen (tmp->alternative[i].id);
658 
659 			len += tmp->alternative[i].msgstr_len;
660 
661 			p = tmp->alternative[i].msgstr;
662 			p_end = tmp->alternative[i].msgstr_end;
663 			for (; p < p_end; p += strlen (p) + 1)
664 			  len += id_len + 2;
665 		      }
666 
667 		    new_msgstr = (char *) xmalloc (len);
668 		    np = new_msgstr;
669 		    for (;;)
670 		      {
671 			/* Test whether there's one more plural form to
672 			   process.  */
673 			for (i = 0; i < tmp->alternative_count; i++)
674 			  if (tmp->alternative[i].msgstr
675 			      < tmp->alternative[i].msgstr_end)
676 			    break;
677 			if (i == tmp->alternative_count)
678 			  break;
679 
680 			/* Process next plural form.  */
681 			for (i = 0; i < tmp->alternative_count; i++)
682 			  if (tmp->alternative[i].msgstr
683 			      < tmp->alternative[i].msgstr_end)
684 			    {
685 			      if (np > new_msgstr && np[-1] != '\0'
686 				  && np[-1] != '\n')
687 				*np++ = '\n';
688 
689 			      len = strlen (tmp->alternative[i].id);
690 			      memcpy (np, tmp->alternative[i].id, len);
691 			      np += len;
692 			      *np++ = '\n';
693 
694 			      len = strlen (tmp->alternative[i].msgstr);
695 			      memcpy (np, tmp->alternative[i].msgstr, len);
696 			      np += len;
697 			      tmp->alternative[i].msgstr += len + 1;
698 			    }
699 
700 			/* Plural forms are separated by NUL bytes.  */
701 			*np++ = '\0';
702 		      }
703 		    tmp->msgstr = new_msgstr;
704 		    tmp->msgstr_len = np - new_msgstr;
705 
706 		    tmp->is_fuzzy = true;
707 		  }
708 
709 		/* Test whether all alternative comments are equal.  */
710 		for (i = 0; i < tmp->alternative_count; i++)
711 		  if (tmp->alternative[i].comment == NULL
712 		      || !string_list_equal (tmp->alternative[i].comment,
713 					     first->comment))
714 		    break;
715 
716 		if (i == tmp->alternative_count)
717 		  /* All alternatives are equal.  */
718 		  tmp->comment = first->comment;
719 		else
720 		  /* Concatenate the alternative comments into a single one,
721 		     separated by markers.  */
722 		  for (i = 0; i < tmp->alternative_count; i++)
723 		    {
724 		      string_list_ty *slp = tmp->alternative[i].comment;
725 
726 		      if (slp != NULL)
727 			{
728 			  size_t l;
729 
730 			  message_comment_append (tmp, tmp->alternative[i].id);
731 			  for (l = 0; l < slp->nitems; l++)
732 			    message_comment_append (tmp, slp->item[l]);
733 			}
734 		    }
735 
736 		/* Test whether all alternative dot comments are equal.  */
737 		for (i = 0; i < tmp->alternative_count; i++)
738 		  if (tmp->alternative[i].comment_dot == NULL
739 		      || !string_list_equal (tmp->alternative[i].comment_dot,
740 					     first->comment_dot))
741 		    break;
742 
743 		if (i == tmp->alternative_count)
744 		  /* All alternatives are equal.  */
745 		  tmp->comment_dot = first->comment_dot;
746 		else
747 		  /* Concatenate the alternative dot comments into a single one,
748 		     separated by markers.  */
749 		  for (i = 0; i < tmp->alternative_count; i++)
750 		    {
751 		      string_list_ty *slp = tmp->alternative[i].comment_dot;
752 
753 		      if (slp != NULL)
754 			{
755 			  size_t l;
756 
757 			  message_comment_dot_append (tmp,
758 						      tmp->alternative[i].id);
759 			  for (l = 0; l < slp->nitems; l++)
760 			    message_comment_dot_append (tmp, slp->item[l]);
761 			}
762 		    }
763 	      }
764 	  }
765       }
766   }
767 
768   return total_mdlp;
769 }
770