1 /* Writing Qt .qm files.
2 Copyright (C) 2003, 2005-2007, 2009, 2016, 2020 Free Software Foundation, Inc.
3 Written by Bruno Haible <bruno@clisp.org>, 2003.
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 3 of the License, or
8 (at your option) 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, see <https://www.gnu.org/licenses/>. */
17
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21
22 /* Specification. */
23 #include "write-qt.h"
24
25 #include <assert.h>
26 #include <errno.h>
27 #include <stdbool.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include "error.h"
33 #include "xerror.h"
34 #include "message.h"
35 #include "po-charset.h"
36 #include "msgl-iconv.h"
37 #include "msgl-header.h"
38 #include "hash-string.h"
39 #include "unistr.h"
40 #include "xalloc.h"
41 #include "obstack.h"
42 #include "mem-hash-map.h"
43 #include "binary-io.h"
44 #include "fwriteerror.h"
45 #include "gettext.h"
46
47 #define _(str) gettext (str)
48
49 /* Qt .qm files are read by the QTranslator::load() function and written
50 by the Qt QTranslator::save() function.
51
52 The Qt tool 'msg2qm' uses the latter function and can convert PO files
53 to .qm files. But since 'msg2qm' is marked as an "old" tool in Qt 3.0.5's
54 i18n.html documentation and therefore likely to disappear, we provide the
55 same functionality here.
56
57 The format of .qm files, as reverse engineered from the functions
58 QTranslator::save(const QString& filename, SaveMode mode)
59 QTranslator::squeeze(SaveMode mode)
60 QTranslatorMessage::write(QDataStream& stream, bool strip, Prefix prefix)
61 elfHash(const char* name)
62 in qt-3.0.5, is as follows:
63
64 It's a binary data format. Elements are u8 (byte), u16, u32. They are
65 written in big-endian order.
66
67 The file starts with a magic string of 16 bytes:
68 3C B8 64 18 CA EF 9C 95 CD 21 1C BF 60 A1 BD DD
69
70 Then come three sections. Each of the three sections is optional. Each
71 has this structure:
72 struct {
73 u8 section_type; // 0x42 = hashes, 0x69 = messages, 0x2f = contexts
74 u32 length; // number of bytes of the data
75 u8 data[length];
76 };
77
78 In the first section, the hashes section, the data has the following
79 structure:
80 It's a sorted array of
81 struct {
82 u32 hashcode; // elfHash of the concatenation of msgid and
83 // disambiguating-comment
84 u32 offset; // offset within the data[] of the messages section
85 };
86 It's sorted in ascending order by hashcode as primary sorting criteria
87 and - when the hashcodes are the same - by offset as secondary criteria.
88
89 In the second section, the messages section, the data has the following
90 structure:
91 It's a sequence of records, each representing a message, in no
92 particular order. Each record is a sequence of subsections, each
93 introduced by a particular subsection tag. The possible subsection tags
94 are (and they usually occur in this order):
95 - 03: Translation. Followed by the msgstr in UCS-2 or UTF-16 format:
96 struct {
97 u32 length;
98 u16 chars[length/2];
99 };
100 - 08: Disambiguating-comment. Followed by the NUL-terminated,
101 ISO-8859-1 encoded, disambiguating-comment string:
102 struct {
103 u32 length; // number of bytes including the NUL at the end
104 u8 chars[length];
105 };
106 - 06: SourceText, i.e. msgid. Followed by the NUL-terminated,
107 ISO-8859-1 encoded, msgid:
108 struct {
109 u32 length; // number of bytes including the NUL at the end
110 u8 chars[length];
111 };
112 - 02: SourceText16, i.e. msgid. Encoded as UCS-2, but must actually
113 be ISO-8859-1.
114 struct {
115 u32 length;
116 u16 chars[length/2];
117 };
118 This subsection tag is obsoleted by SourceText.
119 - 07: Context. Followed by the NUL-terminated, ISO-8859-1 encoded,
120 context string (usually a C++ class name or empty):
121 struct {
122 u32 length; // number of bytes including the NUL at the end
123 u8 chars[length];
124 };
125 - 04: Context16. Encoded as UCS-2, but must actually be ISO-8859-1.
126 struct {
127 u32 length;
128 u16 chars[length/2];
129 };
130 This subsection tag is obsoleted by Context.
131 - 05: Hash. Followed by
132 struct {
133 u32 hashcode; // elfHash of the concatenation of msgid and
134 // disambiguating-comment
135 };
136 - 01: End. Designates the end of the record. No further data.
137 Usually the following subsections are written, but some of them are
138 optional:
139 - 03: Translation.
140 - 08: Disambiguating-comment (optional).
141 - 06: SourceText (optional).
142 - 07: Context (optional).
143 - 05: Hash.
144 - 01: End.
145 A subsection can be omitted if the value to be output is the same as
146 for the previous record.
147
148 The third section, the contexts section, contains the set of all occurring
149 context strings. This section is optional; it is used to speed up the
150 search. The data is a hash table with the following structure:
151 struct {
152 u16 table_size;
153 u16 buckets[table_size];
154 u8 pool[...];
155 };
156 pool[...] contains:
157 u16 zero;
158 for i = 0, ..., table_size:
159 if there are context strings with elfHash(context)%table_size == i:
160 for all context strings with elfHash(context)%table_size == i:
161 len := min(length(context),255); // truncated to length 255
162 struct {
163 u8 len;
164 u8 chars[len];
165 };
166 struct {
167 u8 zero[1]; // signals the end of this bucket
168 u8 padding[0 or 1]; // padding for even number of bytes
169 };
170 buckets[i] is 0 for an empty bucket, or the offset in pool[] where
171 the context strings for this bucket start, divided by 2.
172 This context section must not be used
173 - if the empty context is used, or
174 - if a context of length > 255 is used, or
175 - if the context pool's size would be > 2^17.
176
177 The elfHash function is the same as our hash_string function, except that
178 at the end it maps a hash code of 0x00000000 to 0x00000001.
179
180 When we convert from PO file format, all disambiguating-comments and
181 contexts are empty, and therefore the contexts section can be omitted. */
182
183
184 /* Write a u8 (a single byte) to the output stream. */
185 static inline void
write_u8(FILE * output_file,unsigned char value)186 write_u8 (FILE *output_file, unsigned char value)
187 {
188 putc (value, output_file);
189 }
190
191 /* Write a u16 (two bytes) to the output stream. */
192 static inline void
write_u16(FILE * output_file,unsigned short value)193 write_u16 (FILE *output_file, unsigned short value)
194 {
195 unsigned char data[2];
196
197 data[0] = (value >> 8) & 0xff;
198 data[1] = value & 0xff;
199
200 fwrite (data, 2, 1, output_file);
201 }
202
203 /* Write a u32 (four bytes) to the output stream. */
204 static inline void
write_u32(FILE * output_file,unsigned int value)205 write_u32 (FILE *output_file, unsigned int value)
206 {
207 unsigned char data[4];
208
209 data[0] = (value >> 24) & 0xff;
210 data[1] = (value >> 16) & 0xff;
211 data[2] = (value >> 8) & 0xff;
212 data[3] = value & 0xff;
213
214 fwrite (data, 4, 1, output_file);
215 }
216
217
218 #define obstack_chunk_alloc xmalloc
219 #define obstack_chunk_free free
220
221 /* Add a u8 (a single byte) to an obstack. */
222 static void
append_u8(struct obstack * mempool,unsigned char value)223 append_u8 (struct obstack *mempool, unsigned char value)
224 {
225 unsigned char data[1];
226
227 data[0] = value;
228
229 obstack_grow (mempool, data, 1);
230 }
231
232 /* Add a u16 (two bytes) to an obstack. */
233 static void
append_u16(struct obstack * mempool,unsigned short value)234 append_u16 (struct obstack *mempool, unsigned short value)
235 {
236 unsigned char data[2];
237
238 data[0] = (value >> 8) & 0xff;
239 data[1] = value & 0xff;
240
241 obstack_grow (mempool, data, 2);
242 }
243
244 /* Add a u32 (four bytes) to an obstack. */
245 static void
append_u32(struct obstack * mempool,unsigned int value)246 append_u32 (struct obstack *mempool, unsigned int value)
247 {
248 unsigned char data[4];
249
250 data[0] = (value >> 24) & 0xff;
251 data[1] = (value >> 16) & 0xff;
252 data[2] = (value >> 8) & 0xff;
253 data[3] = value & 0xff;
254
255 obstack_grow (mempool, data, 4);
256 }
257
258 /* Add an ISO-8859-1 encoded string to an obstack. */
259 static void
append_base_string(struct obstack * mempool,const char * string)260 append_base_string (struct obstack *mempool, const char *string)
261 {
262 size_t length = strlen (string) + 1;
263 append_u32 (mempool, length);
264 obstack_grow (mempool, string, length);
265 }
266
267 /* Add an UTF-16 encoded string to an obstack. */
268 static void
append_unicode_string(struct obstack * mempool,const unsigned short * string,size_t length)269 append_unicode_string (struct obstack *mempool, const unsigned short *string,
270 size_t length)
271 {
272 append_u32 (mempool, length * 2);
273 for (; length > 0; string++, length--)
274 append_u16 (mempool, *string);
275 }
276
277 /* Retrieve a 4-byte integer from memory. */
278 static inline unsigned int
peek_u32(const unsigned char * p)279 peek_u32 (const unsigned char *p)
280 {
281 return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
282 }
283
284 /* Convert an UTF-8 string to ISO-8859-1, without error checking. */
285 static char *
conv_to_iso_8859_1(const char * string)286 conv_to_iso_8859_1 (const char *string)
287 {
288 size_t length = strlen (string);
289 const char *str = string;
290 const char *str_limit = string + length;
291 /* Conversion to ISO-8859-1 can only reduce the number of bytes. */
292 char *result = XNMALLOC (length + 1, char);
293 char *q = result;
294
295 while (str < str_limit)
296 {
297 ucs4_t uc;
298 str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
299 /* It has already been verified that the string fits in ISO-8859-1. */
300 if (!(uc < 0x100))
301 abort ();
302 /* Store as ISO-8859-1. */
303 *q++ = (unsigned char) uc;
304 }
305 *q = '\0';
306 assert (q - result <= length);
307
308 return result;
309 }
310
311 /* Convert an UTF-8 string to UTF-16, returning its size (number of UTF-16
312 codepoints) in *SIZEP. */
313 static unsigned short *
conv_to_utf16(const char * string,size_t * sizep)314 conv_to_utf16 (const char *string, size_t *sizep)
315 {
316 size_t length = strlen (string);
317 const char *str = string;
318 const char *str_limit = string + length;
319 /* Conversion to UTF-16 can at most double the number of bytes. */
320 unsigned short *result = XNMALLOC (length, unsigned short);
321 unsigned short *q = result;
322
323 while (str < str_limit)
324 {
325 ucs4_t uc;
326 str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
327 if (uc < 0x10000)
328 /* UCS-2 character. */
329 *q++ = (unsigned short) uc;
330 else
331 {
332 /* UTF-16 surrogate. */
333 *q++ = 0xd800 + ((uc - 0x10000) >> 10);
334 *q++ = 0xdc00 + ((uc - 0x10000) & 0x3ff);
335 }
336 }
337 assert (q - result <= 2 * length);
338
339 *sizep = q - result;
340 return result;
341 }
342
343 /* Return the Qt hash code of a string. */
344 static unsigned int
string_hashcode(const char * str)345 string_hashcode (const char *str)
346 {
347 unsigned int h;
348
349 h = hash_string (str);
350 if (h == 0)
351 h = 1;
352 return h;
353 }
354
355 /* Compare two entries of the hashes section. */
356 static int
cmp_hashes(const void * va,const void * vb)357 cmp_hashes (const void *va, const void *vb)
358 {
359 const unsigned char *a = (const unsigned char *) va;
360 const unsigned char *b = (const unsigned char *) vb;
361 unsigned int a_hashcode = peek_u32 (a);
362 unsigned int b_hashcode = peek_u32 (b);
363
364 if (a_hashcode != b_hashcode)
365 return (a_hashcode >= b_hashcode ? 1 : -1);
366 else
367 {
368 unsigned int a_offset = peek_u32 (a + 4);
369 unsigned int b_offset = peek_u32 (b + 4);
370
371 if (a_offset != b_offset)
372 return (a_offset >= b_offset ? 1 : -1);
373 else
374 return 0;
375 }
376 }
377
378
379 /* Write a section to the output stream. */
380 static void
write_section(FILE * output_file,unsigned char tag,void * data,size_t size)381 write_section (FILE *output_file, unsigned char tag, void *data, size_t size)
382 {
383 /* A section can be omitted if it is empty. */
384 if (size > 0)
385 {
386 write_u8 (output_file, tag);
387 write_u32 (output_file, size);
388 fwrite (data, size, 1, output_file);
389 }
390 }
391
392
393 /* Write an entire .qm file. */
394 static void
write_qm(FILE * output_file,message_list_ty * mlp)395 write_qm (FILE *output_file, message_list_ty *mlp)
396 {
397 static unsigned char magic[16] =
398 {
399 0x3C, 0xB8, 0x64, 0x18, 0xCA, 0xEF, 0x9C, 0x95,
400 0xCD, 0x21, 0x1C, 0xBF, 0x60, 0xA1, 0xBD, 0xDD
401 };
402 struct obstack hashes_pool;
403 struct obstack messages_pool;
404 size_t j;
405
406 obstack_init (&hashes_pool);
407 obstack_init (&messages_pool);
408
409 /* Prepare the hashes section and the messages section. */
410 for (j = 0; j < mlp->nitems; j++)
411 {
412 message_ty *mp = mlp->item[j];
413
414 /* No need to emit the header entry, it's not needed at runtime. */
415 if (!is_header (mp))
416 {
417 char *msgctxt_as_iso_8859_1 =
418 conv_to_iso_8859_1 (mp->msgctxt != NULL ? mp->msgctxt : "");
419 char *msgid_as_iso_8859_1 = conv_to_iso_8859_1 (mp->msgid);
420 size_t msgstr_len;
421 unsigned short *msgstr_as_utf16 =
422 conv_to_utf16 (mp->msgstr, &msgstr_len);
423 unsigned int hashcode = string_hashcode (msgid_as_iso_8859_1);
424 unsigned int offset = obstack_object_size (&messages_pool);
425
426 /* Add a record to the hashes section. */
427 append_u32 (&hashes_pool, hashcode);
428 append_u32 (&hashes_pool, offset);
429
430 /* Add a record to the messages section. */
431
432 append_u8 (&messages_pool, 0x03);
433 append_unicode_string (&messages_pool, msgstr_as_utf16, msgstr_len);
434
435 append_u8 (&messages_pool, 0x08);
436 append_base_string (&messages_pool, "");
437
438 append_u8 (&messages_pool, 0x06);
439 append_base_string (&messages_pool, msgid_as_iso_8859_1);
440
441 append_u8 (&messages_pool, 0x07);
442 append_base_string (&messages_pool, msgctxt_as_iso_8859_1);
443
444 append_u8 (&messages_pool, 0x05);
445 append_u32 (&messages_pool, hashcode);
446
447 append_u8 (&messages_pool, 0x01);
448
449 free (msgstr_as_utf16);
450 free (msgid_as_iso_8859_1);
451 free (msgctxt_as_iso_8859_1);
452 }
453 }
454
455 /* Sort the hashes section. */
456 {
457 size_t nstrings = obstack_object_size (&hashes_pool) / 8;
458 if (nstrings > 0)
459 qsort (obstack_base (&hashes_pool), nstrings, 8, cmp_hashes);
460 }
461
462 /* Write the magic number. */
463 fwrite (magic, sizeof (magic), 1, output_file);
464
465 /* Write the hashes section. */
466 write_section (output_file, 0x42, obstack_base (&hashes_pool),
467 obstack_object_size (&hashes_pool));
468
469 /* Write the messages section. */
470 write_section (output_file, 0x69, obstack_base (&messages_pool),
471 obstack_object_size (&messages_pool));
472
473 /* Decide whether to write a contexts section. */
474 {
475 bool can_write_contexts = true;
476
477 for (j = 0; j < mlp->nitems; j++)
478 {
479 message_ty *mp = mlp->item[j];
480
481 if (!is_header (mp))
482 if (mp->msgctxt == NULL || mp->msgctxt[0] == '\0'
483 || strlen (mp->msgctxt) > 255)
484 {
485 can_write_contexts = false;
486 break;
487 }
488 }
489
490 if (can_write_contexts)
491 {
492 hash_table all_contexts;
493 size_t num_contexts;
494 unsigned long table_size;
495
496 /* Collect the contexts, removing duplicates. */
497 hash_init (&all_contexts, 10);
498 for (j = 0; j < mlp->nitems; j++)
499 {
500 message_ty *mp = mlp->item[j];
501
502 if (!is_header (mp))
503 hash_insert_entry (&all_contexts,
504 mp->msgctxt, strlen (mp->msgctxt) + 1,
505 NULL);
506 }
507
508 /* Compute the number of different contexts. */
509 num_contexts = all_contexts.size;
510
511 /* Compute a suitable hash table size. */
512 table_size = next_prime (num_contexts * 1.7);
513 if (table_size >= 0x10000)
514 table_size = 65521;
515
516 /* Put the contexts into a hash table of size table_size. */
517 {
518 struct list_cell { const char *context; struct list_cell *next; };
519 struct list_cell *list_memory =
520 XNMALLOC (table_size, struct list_cell);
521 struct list_cell *freelist;
522 struct bucket { struct list_cell *head; struct list_cell **tail; };
523 struct bucket *buckets = XNMALLOC (table_size, struct bucket);
524 size_t i;
525
526 freelist = list_memory;
527
528 for (i = 0; i < table_size; i++)
529 {
530 buckets[i].head = NULL;
531 buckets[i].tail = &buckets[i].head;
532 }
533
534 {
535 void *iter;
536 const void *key;
537 size_t keylen;
538 void *null;
539
540 iter = NULL;
541 while (hash_iterate (&all_contexts, &iter, &key, &keylen, &null)
542 == 0)
543 {
544 const char *context = (const char *)key;
545 i = string_hashcode (context) % table_size;
546 freelist->context = context;
547 freelist->next = NULL;
548 *buckets[i].tail = freelist;
549 buckets[i].tail = &freelist->next;
550 freelist++;
551 }
552 }
553
554 /* Determine the total context pool size. */
555 {
556 size_t pool_size;
557
558 pool_size = 2;
559 for (i = 0; i < table_size; i++)
560 if (buckets[i].head != NULL)
561 {
562 const struct list_cell *p;
563
564 for (p = buckets[i].head; p != NULL; p = p->next)
565 pool_size += 1 + strlen (p->context);
566 pool_size++;
567 if ((pool_size % 2) != 0)
568 pool_size++;
569 }
570 if (pool_size <= 0x20000)
571 {
572 /* Prepare the contexts section. */
573 struct obstack contexts_pool;
574 size_t pool_offset;
575
576 obstack_init (&contexts_pool);
577
578 append_u16 (&contexts_pool, table_size);
579 pool_offset = 2;
580 for (i = 0; i < table_size; i++)
581 if (buckets[i].head != NULL)
582 {
583 const struct list_cell *p;
584
585 append_u16 (&contexts_pool, pool_offset / 2);
586 for (p = buckets[i].head; p != NULL; p = p->next)
587 pool_offset += 1 + strlen (p->context);
588 pool_offset++;
589 if ((pool_offset % 2) != 0)
590 pool_offset++;
591 }
592 else
593 append_u16 (&contexts_pool, 0);
594 if (!(pool_offset == pool_size))
595 abort ();
596
597 append_u16 (&contexts_pool, 0);
598 pool_offset = 2;
599 for (i = 0; i < table_size; i++)
600 if (buckets[i].head != NULL)
601 {
602 const struct list_cell *p;
603
604 for (p = buckets[i].head; p != NULL; p = p->next)
605 {
606 append_u8 (&contexts_pool, strlen (p->context));
607 obstack_grow (&contexts_pool,
608 p->context, strlen (p->context));
609 pool_offset += 1 + strlen (p->context);
610 }
611 append_u8 (&contexts_pool, 0);
612 pool_offset++;
613 if ((pool_offset % 2) != 0)
614 {
615 append_u8 (&contexts_pool, 0);
616 pool_offset++;
617 }
618 }
619 if (!(pool_offset == pool_size))
620 abort ();
621
622 if (!(obstack_object_size (&contexts_pool)
623 == 2 + 2 * table_size + pool_size))
624 abort ();
625
626 /* Write the contexts section. */
627 write_section (output_file, 0x2f, obstack_base (&contexts_pool),
628 obstack_object_size (&contexts_pool));
629
630 obstack_free (&contexts_pool, NULL);
631 }
632 }
633
634 free (buckets);
635 free (list_memory);
636 }
637
638 hash_destroy (&all_contexts);
639 }
640 }
641
642 obstack_free (&messages_pool, NULL);
643 obstack_free (&hashes_pool, NULL);
644 }
645
646
647 int
msgdomain_write_qt(message_list_ty * mlp,const char * canon_encoding,const char * domain_name,const char * file_name)648 msgdomain_write_qt (message_list_ty *mlp, const char *canon_encoding,
649 const char *domain_name, const char *file_name)
650 {
651 /* If no entry for this domain don't even create the file. */
652 if (mlp->nitems != 0)
653 {
654 FILE *output_file;
655
656 /* Determine whether mlp has plural entries. */
657 {
658 bool has_plural;
659 size_t j;
660
661 has_plural = false;
662 for (j = 0; j < mlp->nitems; j++)
663 if (mlp->item[j]->msgid_plural != NULL)
664 has_plural = true;
665 if (has_plural)
666 {
667 multiline_error (xstrdup (""),
668 xstrdup (_("\
669 message catalog has plural form translations\n\
670 but the Qt message catalog format doesn't support plural handling\n")));
671 return 1;
672 }
673 }
674
675 /* Convert the messages to Unicode. */
676 iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
677
678 /* Determine whether mlp has non-ISO-8859-1 msgctxt entries. */
679 {
680 size_t j;
681
682 for (j = 0; j < mlp->nitems; j++)
683 {
684 const char *string = mlp->item[j]->msgctxt;
685
686 if (string != NULL)
687 {
688 /* An UTF-8 encoded string fits in ISO-8859-1 if and only if
689 all its bytes are < 0xc4. */
690 for (; *string; string++)
691 if ((unsigned char) *string >= 0xc4)
692 {
693 multiline_error (xstrdup (""),
694 xstrdup (_("\
695 message catalog has msgctxt strings containing characters outside ISO-8859-1\n\
696 but the Qt message catalog format supports Unicode only in the translated\n\
697 strings, not in the context strings\n")));
698 return 1;
699 }
700 }
701 }
702 }
703
704 /* Determine whether mlp has non-ISO-8859-1 msgid entries. */
705 {
706 size_t j;
707
708 for (j = 0; j < mlp->nitems; j++)
709 {
710 const char *string = mlp->item[j]->msgid;
711
712 /* An UTF-8 encoded string fits in ISO-8859-1 if and only if all
713 its bytes are < 0xc4. */
714 for (; *string; string++)
715 if ((unsigned char) *string >= 0xc4)
716 {
717 multiline_error (xstrdup (""),
718 xstrdup (_("\
719 message catalog has msgid strings containing characters outside ISO-8859-1\n\
720 but the Qt message catalog format supports Unicode only in the translated\n\
721 strings, not in the untranslated strings\n")));
722 return 1;
723 }
724 }
725 }
726
727 /* Support for "reproducible builds": Delete information that may vary
728 between builds in the same conditions. */
729 message_list_delete_header_field (mlp, "POT-Creation-Date:");
730
731 if (strcmp (domain_name, "-") == 0)
732 {
733 output_file = stdout;
734 SET_BINARY (fileno (output_file));
735 }
736 else
737 {
738 output_file = fopen (file_name, "wb");
739 if (output_file == NULL)
740 {
741 error (0, errno, _("error while opening \"%s\" for writing"),
742 file_name);
743 return 1;
744 }
745 }
746
747 write_qm (output_file, mlp);
748
749 /* Make sure nothing went wrong. */
750 if (fwriteerror (output_file))
751 error (EXIT_FAILURE, errno, _("error while writing \"%s\" file"),
752 file_name);
753 }
754
755 return 0;
756 }
757