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