facf0c92e0
Red Bear OS is a full fork. All sources must be available from git clone with zero network access. Removed gitignore rules that excluded fetched source trees under recipes/*/source/, local/recipes/kde/*/source/, local/recipes/qt/*/source/, and vendor source trees. Build artifacts (target/, build/, source.tar, *.o, *.so) remain excluded. 127291 files added — kernel, relibc, base, bootloader, pkgar, all KDE/Qt frameworks, mesa, wayland, DRM drivers, and every other recipe source.
757 lines
24 KiB
C
757 lines
24 KiB
C
/* Writing Qt .qm files.
|
|
Copyright (C) 2003, 2005-2007, 2009, 2016, 2020 Free Software Foundation, Inc.
|
|
Written by Bruno Haible <bruno@clisp.org>, 2003.
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
/* Specification. */
|
|
#include "write-qt.h"
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "error.h"
|
|
#include "xerror.h"
|
|
#include "message.h"
|
|
#include "po-charset.h"
|
|
#include "msgl-iconv.h"
|
|
#include "msgl-header.h"
|
|
#include "hash-string.h"
|
|
#include "unistr.h"
|
|
#include "xalloc.h"
|
|
#include "obstack.h"
|
|
#include "mem-hash-map.h"
|
|
#include "binary-io.h"
|
|
#include "fwriteerror.h"
|
|
#include "gettext.h"
|
|
|
|
#define _(str) gettext (str)
|
|
|
|
/* Qt .qm files are read by the QTranslator::load() function and written
|
|
by the Qt QTranslator::save() function.
|
|
|
|
The Qt tool 'msg2qm' uses the latter function and can convert PO files
|
|
to .qm files. But since 'msg2qm' is marked as an "old" tool in Qt 3.0.5's
|
|
i18n.html documentation and therefore likely to disappear, we provide the
|
|
same functionality here.
|
|
|
|
The format of .qm files, as reverse engineered from the functions
|
|
QTranslator::save(const QString& filename, SaveMode mode)
|
|
QTranslator::squeeze(SaveMode mode)
|
|
QTranslatorMessage::write(QDataStream& stream, bool strip, Prefix prefix)
|
|
elfHash(const char* name)
|
|
in qt-3.0.5, is as follows:
|
|
|
|
It's a binary data format. Elements are u8 (byte), u16, u32. They are
|
|
written in big-endian order.
|
|
|
|
The file starts with a magic string of 16 bytes:
|
|
3C B8 64 18 CA EF 9C 95 CD 21 1C BF 60 A1 BD DD
|
|
|
|
Then come three sections. Each of the three sections is optional. Each
|
|
has this structure:
|
|
struct {
|
|
u8 section_type; // 0x42 = hashes, 0x69 = messages, 0x2f = contexts
|
|
u32 length; // number of bytes of the data
|
|
u8 data[length];
|
|
};
|
|
|
|
In the first section, the hashes section, the data has the following
|
|
structure:
|
|
It's a sorted array of
|
|
struct {
|
|
u32 hashcode; // elfHash of the concatenation of msgid and
|
|
// disambiguating-comment
|
|
u32 offset; // offset within the data[] of the messages section
|
|
};
|
|
It's sorted in ascending order by hashcode as primary sorting criteria
|
|
and - when the hashcodes are the same - by offset as secondary criteria.
|
|
|
|
In the second section, the messages section, the data has the following
|
|
structure:
|
|
It's a sequence of records, each representing a message, in no
|
|
particular order. Each record is a sequence of subsections, each
|
|
introduced by a particular subsection tag. The possible subsection tags
|
|
are (and they usually occur in this order):
|
|
- 03: Translation. Followed by the msgstr in UCS-2 or UTF-16 format:
|
|
struct {
|
|
u32 length;
|
|
u16 chars[length/2];
|
|
};
|
|
- 08: Disambiguating-comment. Followed by the NUL-terminated,
|
|
ISO-8859-1 encoded, disambiguating-comment string:
|
|
struct {
|
|
u32 length; // number of bytes including the NUL at the end
|
|
u8 chars[length];
|
|
};
|
|
- 06: SourceText, i.e. msgid. Followed by the NUL-terminated,
|
|
ISO-8859-1 encoded, msgid:
|
|
struct {
|
|
u32 length; // number of bytes including the NUL at the end
|
|
u8 chars[length];
|
|
};
|
|
- 02: SourceText16, i.e. msgid. Encoded as UCS-2, but must actually
|
|
be ISO-8859-1.
|
|
struct {
|
|
u32 length;
|
|
u16 chars[length/2];
|
|
};
|
|
This subsection tag is obsoleted by SourceText.
|
|
- 07: Context. Followed by the NUL-terminated, ISO-8859-1 encoded,
|
|
context string (usually a C++ class name or empty):
|
|
struct {
|
|
u32 length; // number of bytes including the NUL at the end
|
|
u8 chars[length];
|
|
};
|
|
- 04: Context16. Encoded as UCS-2, but must actually be ISO-8859-1.
|
|
struct {
|
|
u32 length;
|
|
u16 chars[length/2];
|
|
};
|
|
This subsection tag is obsoleted by Context.
|
|
- 05: Hash. Followed by
|
|
struct {
|
|
u32 hashcode; // elfHash of the concatenation of msgid and
|
|
// disambiguating-comment
|
|
};
|
|
- 01: End. Designates the end of the record. No further data.
|
|
Usually the following subsections are written, but some of them are
|
|
optional:
|
|
- 03: Translation.
|
|
- 08: Disambiguating-comment (optional).
|
|
- 06: SourceText (optional).
|
|
- 07: Context (optional).
|
|
- 05: Hash.
|
|
- 01: End.
|
|
A subsection can be omitted if the value to be output is the same as
|
|
for the previous record.
|
|
|
|
The third section, the contexts section, contains the set of all occurring
|
|
context strings. This section is optional; it is used to speed up the
|
|
search. The data is a hash table with the following structure:
|
|
struct {
|
|
u16 table_size;
|
|
u16 buckets[table_size];
|
|
u8 pool[...];
|
|
};
|
|
pool[...] contains:
|
|
u16 zero;
|
|
for i = 0, ..., table_size:
|
|
if there are context strings with elfHash(context)%table_size == i:
|
|
for all context strings with elfHash(context)%table_size == i:
|
|
len := min(length(context),255); // truncated to length 255
|
|
struct {
|
|
u8 len;
|
|
u8 chars[len];
|
|
};
|
|
struct {
|
|
u8 zero[1]; // signals the end of this bucket
|
|
u8 padding[0 or 1]; // padding for even number of bytes
|
|
};
|
|
buckets[i] is 0 for an empty bucket, or the offset in pool[] where
|
|
the context strings for this bucket start, divided by 2.
|
|
This context section must not be used
|
|
- if the empty context is used, or
|
|
- if a context of length > 255 is used, or
|
|
- if the context pool's size would be > 2^17.
|
|
|
|
The elfHash function is the same as our hash_string function, except that
|
|
at the end it maps a hash code of 0x00000000 to 0x00000001.
|
|
|
|
When we convert from PO file format, all disambiguating-comments and
|
|
contexts are empty, and therefore the contexts section can be omitted. */
|
|
|
|
|
|
/* Write a u8 (a single byte) to the output stream. */
|
|
static inline void
|
|
write_u8 (FILE *output_file, unsigned char value)
|
|
{
|
|
putc (value, output_file);
|
|
}
|
|
|
|
/* Write a u16 (two bytes) to the output stream. */
|
|
static inline void
|
|
write_u16 (FILE *output_file, unsigned short value)
|
|
{
|
|
unsigned char data[2];
|
|
|
|
data[0] = (value >> 8) & 0xff;
|
|
data[1] = value & 0xff;
|
|
|
|
fwrite (data, 2, 1, output_file);
|
|
}
|
|
|
|
/* Write a u32 (four bytes) to the output stream. */
|
|
static inline void
|
|
write_u32 (FILE *output_file, unsigned int value)
|
|
{
|
|
unsigned char data[4];
|
|
|
|
data[0] = (value >> 24) & 0xff;
|
|
data[1] = (value >> 16) & 0xff;
|
|
data[2] = (value >> 8) & 0xff;
|
|
data[3] = value & 0xff;
|
|
|
|
fwrite (data, 4, 1, output_file);
|
|
}
|
|
|
|
|
|
#define obstack_chunk_alloc xmalloc
|
|
#define obstack_chunk_free free
|
|
|
|
/* Add a u8 (a single byte) to an obstack. */
|
|
static void
|
|
append_u8 (struct obstack *mempool, unsigned char value)
|
|
{
|
|
unsigned char data[1];
|
|
|
|
data[0] = value;
|
|
|
|
obstack_grow (mempool, data, 1);
|
|
}
|
|
|
|
/* Add a u16 (two bytes) to an obstack. */
|
|
static void
|
|
append_u16 (struct obstack *mempool, unsigned short value)
|
|
{
|
|
unsigned char data[2];
|
|
|
|
data[0] = (value >> 8) & 0xff;
|
|
data[1] = value & 0xff;
|
|
|
|
obstack_grow (mempool, data, 2);
|
|
}
|
|
|
|
/* Add a u32 (four bytes) to an obstack. */
|
|
static void
|
|
append_u32 (struct obstack *mempool, unsigned int value)
|
|
{
|
|
unsigned char data[4];
|
|
|
|
data[0] = (value >> 24) & 0xff;
|
|
data[1] = (value >> 16) & 0xff;
|
|
data[2] = (value >> 8) & 0xff;
|
|
data[3] = value & 0xff;
|
|
|
|
obstack_grow (mempool, data, 4);
|
|
}
|
|
|
|
/* Add an ISO-8859-1 encoded string to an obstack. */
|
|
static void
|
|
append_base_string (struct obstack *mempool, const char *string)
|
|
{
|
|
size_t length = strlen (string) + 1;
|
|
append_u32 (mempool, length);
|
|
obstack_grow (mempool, string, length);
|
|
}
|
|
|
|
/* Add an UTF-16 encoded string to an obstack. */
|
|
static void
|
|
append_unicode_string (struct obstack *mempool, const unsigned short *string,
|
|
size_t length)
|
|
{
|
|
append_u32 (mempool, length * 2);
|
|
for (; length > 0; string++, length--)
|
|
append_u16 (mempool, *string);
|
|
}
|
|
|
|
/* Retrieve a 4-byte integer from memory. */
|
|
static inline unsigned int
|
|
peek_u32 (const unsigned char *p)
|
|
{
|
|
return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
|
|
}
|
|
|
|
/* Convert an UTF-8 string to ISO-8859-1, without error checking. */
|
|
static char *
|
|
conv_to_iso_8859_1 (const char *string)
|
|
{
|
|
size_t length = strlen (string);
|
|
const char *str = string;
|
|
const char *str_limit = string + length;
|
|
/* Conversion to ISO-8859-1 can only reduce the number of bytes. */
|
|
char *result = XNMALLOC (length + 1, char);
|
|
char *q = result;
|
|
|
|
while (str < str_limit)
|
|
{
|
|
ucs4_t uc;
|
|
str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
|
|
/* It has already been verified that the string fits in ISO-8859-1. */
|
|
if (!(uc < 0x100))
|
|
abort ();
|
|
/* Store as ISO-8859-1. */
|
|
*q++ = (unsigned char) uc;
|
|
}
|
|
*q = '\0';
|
|
assert (q - result <= length);
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Convert an UTF-8 string to UTF-16, returning its size (number of UTF-16
|
|
codepoints) in *SIZEP. */
|
|
static unsigned short *
|
|
conv_to_utf16 (const char *string, size_t *sizep)
|
|
{
|
|
size_t length = strlen (string);
|
|
const char *str = string;
|
|
const char *str_limit = string + length;
|
|
/* Conversion to UTF-16 can at most double the number of bytes. */
|
|
unsigned short *result = XNMALLOC (length, unsigned short);
|
|
unsigned short *q = result;
|
|
|
|
while (str < str_limit)
|
|
{
|
|
ucs4_t uc;
|
|
str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
|
|
if (uc < 0x10000)
|
|
/* UCS-2 character. */
|
|
*q++ = (unsigned short) uc;
|
|
else
|
|
{
|
|
/* UTF-16 surrogate. */
|
|
*q++ = 0xd800 + ((uc - 0x10000) >> 10);
|
|
*q++ = 0xdc00 + ((uc - 0x10000) & 0x3ff);
|
|
}
|
|
}
|
|
assert (q - result <= 2 * length);
|
|
|
|
*sizep = q - result;
|
|
return result;
|
|
}
|
|
|
|
/* Return the Qt hash code of a string. */
|
|
static unsigned int
|
|
string_hashcode (const char *str)
|
|
{
|
|
unsigned int h;
|
|
|
|
h = hash_string (str);
|
|
if (h == 0)
|
|
h = 1;
|
|
return h;
|
|
}
|
|
|
|
/* Compare two entries of the hashes section. */
|
|
static int
|
|
cmp_hashes (const void *va, const void *vb)
|
|
{
|
|
const unsigned char *a = (const unsigned char *) va;
|
|
const unsigned char *b = (const unsigned char *) vb;
|
|
unsigned int a_hashcode = peek_u32 (a);
|
|
unsigned int b_hashcode = peek_u32 (b);
|
|
|
|
if (a_hashcode != b_hashcode)
|
|
return (a_hashcode >= b_hashcode ? 1 : -1);
|
|
else
|
|
{
|
|
unsigned int a_offset = peek_u32 (a + 4);
|
|
unsigned int b_offset = peek_u32 (b + 4);
|
|
|
|
if (a_offset != b_offset)
|
|
return (a_offset >= b_offset ? 1 : -1);
|
|
else
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
/* Write a section to the output stream. */
|
|
static void
|
|
write_section (FILE *output_file, unsigned char tag, void *data, size_t size)
|
|
{
|
|
/* A section can be omitted if it is empty. */
|
|
if (size > 0)
|
|
{
|
|
write_u8 (output_file, tag);
|
|
write_u32 (output_file, size);
|
|
fwrite (data, size, 1, output_file);
|
|
}
|
|
}
|
|
|
|
|
|
/* Write an entire .qm file. */
|
|
static void
|
|
write_qm (FILE *output_file, message_list_ty *mlp)
|
|
{
|
|
static unsigned char magic[16] =
|
|
{
|
|
0x3C, 0xB8, 0x64, 0x18, 0xCA, 0xEF, 0x9C, 0x95,
|
|
0xCD, 0x21, 0x1C, 0xBF, 0x60, 0xA1, 0xBD, 0xDD
|
|
};
|
|
struct obstack hashes_pool;
|
|
struct obstack messages_pool;
|
|
size_t j;
|
|
|
|
obstack_init (&hashes_pool);
|
|
obstack_init (&messages_pool);
|
|
|
|
/* Prepare the hashes section and the messages section. */
|
|
for (j = 0; j < mlp->nitems; j++)
|
|
{
|
|
message_ty *mp = mlp->item[j];
|
|
|
|
/* No need to emit the header entry, it's not needed at runtime. */
|
|
if (!is_header (mp))
|
|
{
|
|
char *msgctxt_as_iso_8859_1 =
|
|
conv_to_iso_8859_1 (mp->msgctxt != NULL ? mp->msgctxt : "");
|
|
char *msgid_as_iso_8859_1 = conv_to_iso_8859_1 (mp->msgid);
|
|
size_t msgstr_len;
|
|
unsigned short *msgstr_as_utf16 =
|
|
conv_to_utf16 (mp->msgstr, &msgstr_len);
|
|
unsigned int hashcode = string_hashcode (msgid_as_iso_8859_1);
|
|
unsigned int offset = obstack_object_size (&messages_pool);
|
|
|
|
/* Add a record to the hashes section. */
|
|
append_u32 (&hashes_pool, hashcode);
|
|
append_u32 (&hashes_pool, offset);
|
|
|
|
/* Add a record to the messages section. */
|
|
|
|
append_u8 (&messages_pool, 0x03);
|
|
append_unicode_string (&messages_pool, msgstr_as_utf16, msgstr_len);
|
|
|
|
append_u8 (&messages_pool, 0x08);
|
|
append_base_string (&messages_pool, "");
|
|
|
|
append_u8 (&messages_pool, 0x06);
|
|
append_base_string (&messages_pool, msgid_as_iso_8859_1);
|
|
|
|
append_u8 (&messages_pool, 0x07);
|
|
append_base_string (&messages_pool, msgctxt_as_iso_8859_1);
|
|
|
|
append_u8 (&messages_pool, 0x05);
|
|
append_u32 (&messages_pool, hashcode);
|
|
|
|
append_u8 (&messages_pool, 0x01);
|
|
|
|
free (msgstr_as_utf16);
|
|
free (msgid_as_iso_8859_1);
|
|
free (msgctxt_as_iso_8859_1);
|
|
}
|
|
}
|
|
|
|
/* Sort the hashes section. */
|
|
{
|
|
size_t nstrings = obstack_object_size (&hashes_pool) / 8;
|
|
if (nstrings > 0)
|
|
qsort (obstack_base (&hashes_pool), nstrings, 8, cmp_hashes);
|
|
}
|
|
|
|
/* Write the magic number. */
|
|
fwrite (magic, sizeof (magic), 1, output_file);
|
|
|
|
/* Write the hashes section. */
|
|
write_section (output_file, 0x42, obstack_base (&hashes_pool),
|
|
obstack_object_size (&hashes_pool));
|
|
|
|
/* Write the messages section. */
|
|
write_section (output_file, 0x69, obstack_base (&messages_pool),
|
|
obstack_object_size (&messages_pool));
|
|
|
|
/* Decide whether to write a contexts section. */
|
|
{
|
|
bool can_write_contexts = true;
|
|
|
|
for (j = 0; j < mlp->nitems; j++)
|
|
{
|
|
message_ty *mp = mlp->item[j];
|
|
|
|
if (!is_header (mp))
|
|
if (mp->msgctxt == NULL || mp->msgctxt[0] == '\0'
|
|
|| strlen (mp->msgctxt) > 255)
|
|
{
|
|
can_write_contexts = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (can_write_contexts)
|
|
{
|
|
hash_table all_contexts;
|
|
size_t num_contexts;
|
|
unsigned long table_size;
|
|
|
|
/* Collect the contexts, removing duplicates. */
|
|
hash_init (&all_contexts, 10);
|
|
for (j = 0; j < mlp->nitems; j++)
|
|
{
|
|
message_ty *mp = mlp->item[j];
|
|
|
|
if (!is_header (mp))
|
|
hash_insert_entry (&all_contexts,
|
|
mp->msgctxt, strlen (mp->msgctxt) + 1,
|
|
NULL);
|
|
}
|
|
|
|
/* Compute the number of different contexts. */
|
|
num_contexts = all_contexts.size;
|
|
|
|
/* Compute a suitable hash table size. */
|
|
table_size = next_prime (num_contexts * 1.7);
|
|
if (table_size >= 0x10000)
|
|
table_size = 65521;
|
|
|
|
/* Put the contexts into a hash table of size table_size. */
|
|
{
|
|
struct list_cell { const char *context; struct list_cell *next; };
|
|
struct list_cell *list_memory =
|
|
XNMALLOC (table_size, struct list_cell);
|
|
struct list_cell *freelist;
|
|
struct bucket { struct list_cell *head; struct list_cell **tail; };
|
|
struct bucket *buckets = XNMALLOC (table_size, struct bucket);
|
|
size_t i;
|
|
|
|
freelist = list_memory;
|
|
|
|
for (i = 0; i < table_size; i++)
|
|
{
|
|
buckets[i].head = NULL;
|
|
buckets[i].tail = &buckets[i].head;
|
|
}
|
|
|
|
{
|
|
void *iter;
|
|
const void *key;
|
|
size_t keylen;
|
|
void *null;
|
|
|
|
iter = NULL;
|
|
while (hash_iterate (&all_contexts, &iter, &key, &keylen, &null)
|
|
== 0)
|
|
{
|
|
const char *context = (const char *)key;
|
|
i = string_hashcode (context) % table_size;
|
|
freelist->context = context;
|
|
freelist->next = NULL;
|
|
*buckets[i].tail = freelist;
|
|
buckets[i].tail = &freelist->next;
|
|
freelist++;
|
|
}
|
|
}
|
|
|
|
/* Determine the total context pool size. */
|
|
{
|
|
size_t pool_size;
|
|
|
|
pool_size = 2;
|
|
for (i = 0; i < table_size; i++)
|
|
if (buckets[i].head != NULL)
|
|
{
|
|
const struct list_cell *p;
|
|
|
|
for (p = buckets[i].head; p != NULL; p = p->next)
|
|
pool_size += 1 + strlen (p->context);
|
|
pool_size++;
|
|
if ((pool_size % 2) != 0)
|
|
pool_size++;
|
|
}
|
|
if (pool_size <= 0x20000)
|
|
{
|
|
/* Prepare the contexts section. */
|
|
struct obstack contexts_pool;
|
|
size_t pool_offset;
|
|
|
|
obstack_init (&contexts_pool);
|
|
|
|
append_u16 (&contexts_pool, table_size);
|
|
pool_offset = 2;
|
|
for (i = 0; i < table_size; i++)
|
|
if (buckets[i].head != NULL)
|
|
{
|
|
const struct list_cell *p;
|
|
|
|
append_u16 (&contexts_pool, pool_offset / 2);
|
|
for (p = buckets[i].head; p != NULL; p = p->next)
|
|
pool_offset += 1 + strlen (p->context);
|
|
pool_offset++;
|
|
if ((pool_offset % 2) != 0)
|
|
pool_offset++;
|
|
}
|
|
else
|
|
append_u16 (&contexts_pool, 0);
|
|
if (!(pool_offset == pool_size))
|
|
abort ();
|
|
|
|
append_u16 (&contexts_pool, 0);
|
|
pool_offset = 2;
|
|
for (i = 0; i < table_size; i++)
|
|
if (buckets[i].head != NULL)
|
|
{
|
|
const struct list_cell *p;
|
|
|
|
for (p = buckets[i].head; p != NULL; p = p->next)
|
|
{
|
|
append_u8 (&contexts_pool, strlen (p->context));
|
|
obstack_grow (&contexts_pool,
|
|
p->context, strlen (p->context));
|
|
pool_offset += 1 + strlen (p->context);
|
|
}
|
|
append_u8 (&contexts_pool, 0);
|
|
pool_offset++;
|
|
if ((pool_offset % 2) != 0)
|
|
{
|
|
append_u8 (&contexts_pool, 0);
|
|
pool_offset++;
|
|
}
|
|
}
|
|
if (!(pool_offset == pool_size))
|
|
abort ();
|
|
|
|
if (!(obstack_object_size (&contexts_pool)
|
|
== 2 + 2 * table_size + pool_size))
|
|
abort ();
|
|
|
|
/* Write the contexts section. */
|
|
write_section (output_file, 0x2f, obstack_base (&contexts_pool),
|
|
obstack_object_size (&contexts_pool));
|
|
|
|
obstack_free (&contexts_pool, NULL);
|
|
}
|
|
}
|
|
|
|
free (buckets);
|
|
free (list_memory);
|
|
}
|
|
|
|
hash_destroy (&all_contexts);
|
|
}
|
|
}
|
|
|
|
obstack_free (&messages_pool, NULL);
|
|
obstack_free (&hashes_pool, NULL);
|
|
}
|
|
|
|
|
|
int
|
|
msgdomain_write_qt (message_list_ty *mlp, const char *canon_encoding,
|
|
const char *domain_name, const char *file_name)
|
|
{
|
|
/* If no entry for this domain don't even create the file. */
|
|
if (mlp->nitems != 0)
|
|
{
|
|
FILE *output_file;
|
|
|
|
/* Determine whether mlp has plural entries. */
|
|
{
|
|
bool has_plural;
|
|
size_t j;
|
|
|
|
has_plural = false;
|
|
for (j = 0; j < mlp->nitems; j++)
|
|
if (mlp->item[j]->msgid_plural != NULL)
|
|
has_plural = true;
|
|
if (has_plural)
|
|
{
|
|
multiline_error (xstrdup (""),
|
|
xstrdup (_("\
|
|
message catalog has plural form translations\n\
|
|
but the Qt message catalog format doesn't support plural handling\n")));
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* Convert the messages to Unicode. */
|
|
iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
|
|
|
|
/* Determine whether mlp has non-ISO-8859-1 msgctxt entries. */
|
|
{
|
|
size_t j;
|
|
|
|
for (j = 0; j < mlp->nitems; j++)
|
|
{
|
|
const char *string = mlp->item[j]->msgctxt;
|
|
|
|
if (string != NULL)
|
|
{
|
|
/* An UTF-8 encoded string fits in ISO-8859-1 if and only if
|
|
all its bytes are < 0xc4. */
|
|
for (; *string; string++)
|
|
if ((unsigned char) *string >= 0xc4)
|
|
{
|
|
multiline_error (xstrdup (""),
|
|
xstrdup (_("\
|
|
message catalog has msgctxt strings containing characters outside ISO-8859-1\n\
|
|
but the Qt message catalog format supports Unicode only in the translated\n\
|
|
strings, not in the context strings\n")));
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Determine whether mlp has non-ISO-8859-1 msgid entries. */
|
|
{
|
|
size_t j;
|
|
|
|
for (j = 0; j < mlp->nitems; j++)
|
|
{
|
|
const char *string = mlp->item[j]->msgid;
|
|
|
|
/* An UTF-8 encoded string fits in ISO-8859-1 if and only if all
|
|
its bytes are < 0xc4. */
|
|
for (; *string; string++)
|
|
if ((unsigned char) *string >= 0xc4)
|
|
{
|
|
multiline_error (xstrdup (""),
|
|
xstrdup (_("\
|
|
message catalog has msgid strings containing characters outside ISO-8859-1\n\
|
|
but the Qt message catalog format supports Unicode only in the translated\n\
|
|
strings, not in the untranslated strings\n")));
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Support for "reproducible builds": Delete information that may vary
|
|
between builds in the same conditions. */
|
|
message_list_delete_header_field (mlp, "POT-Creation-Date:");
|
|
|
|
if (strcmp (domain_name, "-") == 0)
|
|
{
|
|
output_file = stdout;
|
|
SET_BINARY (fileno (output_file));
|
|
}
|
|
else
|
|
{
|
|
output_file = fopen (file_name, "wb");
|
|
if (output_file == NULL)
|
|
{
|
|
error (0, errno, _("error while opening \"%s\" for writing"),
|
|
file_name);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
write_qm (output_file, mlp);
|
|
|
|
/* Make sure nothing went wrong. */
|
|
if (fwriteerror (output_file))
|
|
error (EXIT_FAILURE, errno, _("error while writing \"%s\" file"),
|
|
file_name);
|
|
}
|
|
|
|
return 0;
|
|
}
|