1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Linguist of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file. Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "translator.h"
43
44 #ifndef QT_BOOTSTRAPPED
45 #include <QtCore/QCoreApplication>
46 #endif
47 #include <QtCore/QDebug>
48 #include <QtCore/QDir>
49 #include <QtCore/QFile>
50 #include <QtCore/QFileInfo>
51 #include <QtCore/QMap>
52 #include <QtCore/QString>
53 #include <QtCore/QTextCodec>
54
55 QT_BEGIN_NAMESPACE
56
57 // magic number for the file
58 static const int MagicLength = 16;
59 static const uchar magic[MagicLength] = {
60 0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95,
61 0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd
62 };
63
64
65 namespace {
66
67 enum Tag {
68 Tag_End = 1,
69 Tag_SourceText16 = 2,
70 Tag_Translation = 3,
71 Tag_Context16 = 4,
72 Tag_Obsolete1 = 5,
73 Tag_SourceText = 6,
74 Tag_Context = 7,
75 Tag_Comment = 8,
76 Tag_Obsolete2 = 9
77 };
78
79 enum Prefix {
80 NoPrefix,
81 Hash,
82 HashContext,
83 HashContextSourceText,
84 HashContextSourceTextComment
85 };
86
87 } // namespace anon
88
elfHash(const QByteArray & ba)89 static uint elfHash(const QByteArray &ba)
90 {
91 const uchar *k = (const uchar *)ba.data();
92 uint h = 0;
93 uint g;
94
95 if (k) {
96 while (*k) {
97 h = (h << 4) + *k++;
98 if ((g = (h & 0xf0000000)) != 0)
99 h ^= g >> 24;
100 h &= ~g;
101 }
102 }
103 if (!h)
104 h = 1;
105 return h;
106 }
107
108 class ByteTranslatorMessage
109 {
110 public:
ByteTranslatorMessage(const QByteArray & context,const QByteArray & sourceText,const QByteArray & comment,const QStringList & translations)111 ByteTranslatorMessage(
112 const QByteArray &context,
113 const QByteArray &sourceText,
114 const QByteArray &comment,
115 const QStringList &translations) :
116 m_context(context),
117 m_sourcetext(sourceText),
118 m_comment(comment),
119 m_translations(translations)
120 {}
context() const121 const QByteArray &context() const { return m_context; }
sourceText() const122 const QByteArray &sourceText() const { return m_sourcetext; }
comment() const123 const QByteArray &comment() const { return m_comment; }
translations() const124 const QStringList &translations() const { return m_translations; }
125 bool operator<(const ByteTranslatorMessage& m) const;
126
127 private:
128 QByteArray m_context;
129 QByteArray m_sourcetext;
130 QByteArray m_comment;
131 QStringList m_translations;
132 };
133
134 Q_DECLARE_TYPEINFO(ByteTranslatorMessage, Q_MOVABLE_TYPE);
135
operator <(const ByteTranslatorMessage & m) const136 bool ByteTranslatorMessage::operator<(const ByteTranslatorMessage& m) const
137 {
138 if (m_context != m.m_context)
139 return m_context < m.m_context;
140 if (m_sourcetext != m.m_sourcetext)
141 return m_sourcetext < m.m_sourcetext;
142 return m_comment < m.m_comment;
143 }
144
145 class Releaser
146 {
147 public:
148 struct Offset {
OffsetReleaser::Offset149 Offset()
150 : h(0), o(0)
151 {}
OffsetReleaser::Offset152 Offset(uint hash, uint offset)
153 : h(hash), o(offset)
154 {}
155
operator <Releaser::Offset156 bool operator<(const Offset &other) const {
157 return (h != other.h) ? h < other.h : o < other.o;
158 }
operator ==Releaser::Offset159 bool operator==(const Offset &other) const {
160 return h == other.h && o == other.o;
161 }
162 uint h;
163 uint o;
164 };
165
166 enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88 };
167
Releaser()168 Releaser() : m_codec(0) {}
169
setCodecName(const QByteArray & codecName)170 void setCodecName(const QByteArray &codecName)
171 {
172 m_codec = QTextCodec::codecForName(codecName);
173 }
174
175 bool save(QIODevice *iod);
176
177 void insert(const TranslatorMessage &msg, const QStringList &tlns, bool forceComment);
178 void insertIdBased(const TranslatorMessage &message, const QStringList &tlns);
179
180 void squeeze(TranslatorSaveMode mode);
181
182 void setNumerusRules(const QByteArray &rules);
183
184 private:
185 Q_DISABLE_COPY(Releaser)
186
187 // This should reproduce the byte array fetched from the source file, which
188 // on turn should be the same as passed to the actual tr(...) calls
189 QByteArray originalBytes(const QString &str, bool isUtf8) const;
190
191 void insertInternal(const TranslatorMessage &message, const QStringList &tlns,
192 bool forceComment, bool isUtf8);
193
194 static Prefix commonPrefix(const ByteTranslatorMessage &m1, const ByteTranslatorMessage &m2);
195
196 static uint msgHash(const ByteTranslatorMessage &msg);
197
198 void writeMessage(const ByteTranslatorMessage & msg, QDataStream & stream,
199 TranslatorSaveMode strip, Prefix prefix) const;
200
201 // for squeezed but non-file data, this is what needs to be deleted
202 QByteArray m_messageArray;
203 QByteArray m_offsetArray;
204 QByteArray m_contextArray;
205 QMap<ByteTranslatorMessage, void *> m_messages;
206 QByteArray m_numerusRules;
207
208 // Used to reproduce the original bytes
209 QTextCodec *m_codec;
210 };
211
originalBytes(const QString & str,bool isUtf8) const212 QByteArray Releaser::originalBytes(const QString &str, bool isUtf8) const
213 {
214 if (str.isEmpty()) {
215 // Do not use QByteArray() here as the result of the serialization
216 // will be different.
217 return QByteArray("");
218 }
219 if (isUtf8)
220 return str.toUtf8();
221 return m_codec ? m_codec->fromUnicode(str) : str.toLatin1();
222 }
223
msgHash(const ByteTranslatorMessage & msg)224 uint Releaser::msgHash(const ByteTranslatorMessage &msg)
225 {
226 return elfHash(msg.sourceText() + msg.comment());
227 }
228
commonPrefix(const ByteTranslatorMessage & m1,const ByteTranslatorMessage & m2)229 Prefix Releaser::commonPrefix(const ByteTranslatorMessage &m1, const ByteTranslatorMessage &m2)
230 {
231 if (msgHash(m1) != msgHash(m2))
232 return NoPrefix;
233 if (m1.context() != m2.context())
234 return Hash;
235 if (m1.sourceText() != m2.sourceText())
236 return HashContext;
237 if (m1.comment() != m2.comment())
238 return HashContextSourceText;
239 return HashContextSourceTextComment;
240 }
241
writeMessage(const ByteTranslatorMessage & msg,QDataStream & stream,TranslatorSaveMode mode,Prefix prefix) const242 void Releaser::writeMessage(const ByteTranslatorMessage &msg, QDataStream &stream,
243 TranslatorSaveMode mode, Prefix prefix) const
244 {
245 for (int i = 0; i < msg.translations().count(); ++i)
246 stream << quint8(Tag_Translation) << msg.translations().at(i);
247
248 if (mode == SaveEverything)
249 prefix = HashContextSourceTextComment;
250
251 // lrelease produces "wrong" QM files for QByteArrays that are .isNull().
252 switch (prefix) {
253 default:
254 case HashContextSourceTextComment:
255 stream << quint8(Tag_Comment) << msg.comment();
256 // fall through
257 case HashContextSourceText:
258 stream << quint8(Tag_SourceText) << msg.sourceText();
259 // fall through
260 case HashContext:
261 stream << quint8(Tag_Context) << msg.context();
262 break;
263 }
264
265 stream << quint8(Tag_End);
266 }
267
268
save(QIODevice * iod)269 bool Releaser::save(QIODevice *iod)
270 {
271 QDataStream s(iod);
272 s.writeRawData((const char *)magic, MagicLength);
273
274 if (!m_offsetArray.isEmpty()) {
275 quint32 oas = quint32(m_offsetArray.size());
276 s << quint8(Hashes) << oas;
277 s.writeRawData(m_offsetArray.constData(), oas);
278 }
279 if (!m_messageArray.isEmpty()) {
280 quint32 mas = quint32(m_messageArray.size());
281 s << quint8(Messages) << mas;
282 s.writeRawData(m_messageArray.constData(), mas);
283 }
284 if (!m_contextArray.isEmpty()) {
285 quint32 cas = quint32(m_contextArray.size());
286 s << quint8(Contexts) << cas;
287 s.writeRawData(m_contextArray.constData(), cas);
288 }
289 if (!m_numerusRules.isEmpty()) {
290 quint32 nrs = m_numerusRules.size();
291 s << quint8(NumerusRules) << nrs;
292 s.writeRawData(m_numerusRules.constData(), nrs);
293 }
294 return true;
295 }
296
squeeze(TranslatorSaveMode mode)297 void Releaser::squeeze(TranslatorSaveMode mode)
298 {
299 if (m_messages.isEmpty() && mode == SaveEverything)
300 return;
301
302 QMap<ByteTranslatorMessage, void *> messages = m_messages;
303
304 // re-build contents
305 m_messageArray.clear();
306 m_offsetArray.clear();
307 m_contextArray.clear();
308 m_messages.clear();
309
310 QMap<Offset, void *> offsets;
311
312 QDataStream ms(&m_messageArray, QIODevice::WriteOnly);
313 QMap<ByteTranslatorMessage, void *>::const_iterator it, next;
314 int cpPrev = 0, cpNext = 0;
315 for (it = messages.constBegin(); it != messages.constEnd(); ++it) {
316 cpPrev = cpNext;
317 next = it;
318 ++next;
319 if (next == messages.constEnd())
320 cpNext = 0;
321 else
322 cpNext = commonPrefix(it.key(), next.key());
323 offsets.insert(Offset(msgHash(it.key()), ms.device()->pos()), (void *)0);
324 writeMessage(it.key(), ms, mode, Prefix(qMax(cpPrev, cpNext + 1)));
325 }
326
327 QMap<Offset, void *>::Iterator offset;
328 offset = offsets.begin();
329 QDataStream ds(&m_offsetArray, QIODevice::WriteOnly);
330 while (offset != offsets.end()) {
331 Offset k = offset.key();
332 ++offset;
333 ds << quint32(k.h) << quint32(k.o);
334 }
335
336 if (mode == SaveStripped) {
337 QMap<QByteArray, int> contextSet;
338 for (it = messages.constBegin(); it != messages.constEnd(); ++it)
339 ++contextSet[it.key().context()];
340
341 quint16 hTableSize;
342 if (contextSet.size() < 200)
343 hTableSize = (contextSet.size() < 60) ? 151 : 503;
344 else if (contextSet.size() < 2500)
345 hTableSize = (contextSet.size() < 750) ? 1511 : 5003;
346 else
347 hTableSize = (contextSet.size() < 10000) ? 15013 : 3 * contextSet.size() / 2;
348
349 QMultiMap<int, QByteArray> hashMap;
350 QMap<QByteArray, int>::const_iterator c;
351 for (c = contextSet.constBegin(); c != contextSet.constEnd(); ++c)
352 hashMap.insert(elfHash(c.key()) % hTableSize, c.key());
353
354 /*
355 The contexts found in this translator are stored in a hash
356 table to provide fast lookup. The context array has the
357 following format:
358
359 quint16 hTableSize;
360 quint16 hTable[hTableSize];
361 quint8 contextPool[...];
362
363 The context pool stores the contexts as Pascal strings:
364
365 quint8 len;
366 quint8 data[len];
367
368 Let's consider the look-up of context "FunnyDialog". A
369 hash value between 0 and hTableSize - 1 is computed, say h.
370 If hTable[h] is 0, "FunnyDialog" is not covered by this
371 translator. Else, we check in the contextPool at offset
372 2 * hTable[h] to see if "FunnyDialog" is one of the
373 contexts stored there, until we find it or we meet the
374 empty string.
375 */
376 m_contextArray.resize(2 + (hTableSize << 1));
377 QDataStream t(&m_contextArray, QIODevice::WriteOnly);
378
379 quint16 *hTable = new quint16[hTableSize];
380 memset(hTable, 0, hTableSize * sizeof(quint16));
381
382 t << hTableSize;
383 t.device()->seek(2 + (hTableSize << 1));
384 t << quint16(0); // the entry at offset 0 cannot be used
385 uint upto = 2;
386
387 QMap<int, QByteArray>::const_iterator entry = hashMap.constBegin();
388 while (entry != hashMap.constEnd()) {
389 int i = entry.key();
390 hTable[i] = quint16(upto >> 1);
391
392 do {
393 const char *con = entry.value().constData();
394 uint len = uint(entry.value().length());
395 len = qMin(len, 255u);
396 t << quint8(len);
397 t.writeRawData(con, len);
398 upto += 1 + len;
399 ++entry;
400 } while (entry != hashMap.constEnd() && entry.key() == i);
401 if (upto & 0x1) {
402 // offsets have to be even
403 t << quint8(0); // empty string
404 ++upto;
405 }
406 }
407 t.device()->seek(2);
408 for (int j = 0; j < hTableSize; j++)
409 t << hTable[j];
410 delete [] hTable;
411
412 if (upto > 131072) {
413 qWarning("Releaser::squeeze: Too many contexts");
414 m_contextArray.clear();
415 }
416 }
417 }
418
insertInternal(const TranslatorMessage & message,const QStringList & tlns,bool forceComment,bool isUtf8)419 void Releaser::insertInternal(const TranslatorMessage &message, const QStringList &tlns,
420 bool forceComment, bool isUtf8)
421 {
422 ByteTranslatorMessage bmsg(originalBytes(message.context(), isUtf8),
423 originalBytes(message.sourceText(), isUtf8),
424 originalBytes(message.comment(), isUtf8),
425 tlns);
426 if (!forceComment) {
427 ByteTranslatorMessage bmsg2(
428 bmsg.context(), bmsg.sourceText(), QByteArray(""), bmsg.translations());
429 if (!m_messages.contains(bmsg2)) {
430 m_messages.insert(bmsg2, 0);
431 return;
432 }
433 }
434 m_messages.insert(bmsg, 0);
435 }
436
insert(const TranslatorMessage & message,const QStringList & tlns,bool forceComment)437 void Releaser::insert(const TranslatorMessage &message, const QStringList &tlns, bool forceComment)
438 {
439 insertInternal(message, tlns, forceComment, message.isUtf8());
440 if (message.isUtf8() && message.isNonUtf8())
441 insertInternal(message, tlns, forceComment, false);
442 }
443
insertIdBased(const TranslatorMessage & message,const QStringList & tlns)444 void Releaser::insertIdBased(const TranslatorMessage &message, const QStringList &tlns)
445 {
446 ByteTranslatorMessage bmsg("", originalBytes(message.id(), false), "", tlns);
447 m_messages.insert(bmsg, 0);
448 }
449
setNumerusRules(const QByteArray & rules)450 void Releaser::setNumerusRules(const QByteArray &rules)
451 {
452 m_numerusRules = rules;
453 }
454
read8(const uchar * data)455 static quint8 read8(const uchar *data)
456 {
457 return *data;
458 }
459
read32(const uchar * data)460 static quint32 read32(const uchar *data)
461 {
462 return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | (data[3]);
463 }
464
fromBytes(const char * str,int len,QTextCodec * codec,QTextCodec * utf8Codec,QString * out,QString * utf8Out,bool * isSystem,bool * isUtf8,bool * needs8Bit)465 static void fromBytes(const char *str, int len, QTextCodec *codec, QTextCodec *utf8Codec,
466 QString *out, QString *utf8Out,
467 bool *isSystem, bool *isUtf8, bool *needs8Bit)
468 {
469 for (int i = 0; i < len; ++i)
470 if (str[i] & 0x80) {
471 if (utf8Codec) {
472 QTextCodec::ConverterState cvtState;
473 *utf8Out = utf8Codec->toUnicode(str, len, &cvtState);
474 *isUtf8 = !cvtState.invalidChars;
475 }
476 QTextCodec::ConverterState cvtState;
477 *out = codec->toUnicode(str, len, &cvtState);
478 *isSystem = !cvtState.invalidChars;
479 *needs8Bit = true;
480 return;
481 }
482 *out = QString::fromLatin1(str, len);
483 *isSystem = true;
484 if (utf8Codec) {
485 *utf8Out = *out;
486 *isUtf8 = true;
487 }
488 *needs8Bit = false;
489 }
490
loadQM(Translator & translator,QIODevice & dev,ConversionData & cd)491 bool loadQM(Translator &translator, QIODevice &dev, ConversionData &cd)
492 {
493 QByteArray ba = dev.readAll();
494 const uchar *data = (uchar*)ba.data();
495 int len = ba.size();
496 if (len < MagicLength || memcmp(data, magic, MagicLength) != 0) {
497 cd.appendError(QLatin1String("QM-Format error: magic marker missing"));
498 return false;
499 }
500
501 enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88 };
502
503 // for squeezed but non-file data, this is what needs to be deleted
504 const uchar *messageArray = 0;
505 const uchar *offsetArray = 0;
506 uint offsetLength = 0;
507
508 bool ok = true;
509 const uchar *end = data + len;
510
511 data += MagicLength;
512
513 while (data < end - 4) {
514 quint8 tag = read8(data++);
515 quint32 blockLen = read32(data);
516 //qDebug() << "TAG:" << tag << "BLOCKLEN:" << blockLen;
517 data += 4;
518 if (!tag || !blockLen)
519 break;
520 if (data + blockLen > end) {
521 ok = false;
522 break;
523 }
524
525 if (tag == Hashes) {
526 offsetArray = data;
527 offsetLength = blockLen;
528 //qDebug() << "HASHES: " << blockLen << QByteArray((const char *)data, blockLen).toHex();
529 } else if (tag == Messages) {
530 messageArray = data;
531 //qDebug() << "MESSAGES: " << blockLen << QByteArray((const char *)data, blockLen).toHex();
532 }
533
534 data += blockLen;
535 }
536
537
538 size_t numItems = offsetLength / (2 * sizeof(quint32));
539 //qDebug() << "NUMITEMS: " << numItems;
540
541 QTextCodec *codec = QTextCodec::codecForName(
542 cd.m_codecForSource.isEmpty() ? QByteArray("Latin1") : cd.m_codecForSource);
543 QTextCodec *utf8Codec = 0;
544 if (codec->name() != "UTF-8")
545 utf8Codec = QTextCodec::codecForName("UTF-8");
546
547 QString strProN = QLatin1String("%n");
548 QLocale::Language l;
549 QLocale::Country c;
550 Translator::languageAndCountry(translator.languageCode(), &l, &c);
551 QStringList numerusForms;
552 bool guessPlurals = true;
553 if (getNumerusInfo(l, c, 0, &numerusForms, 0))
554 guessPlurals = (numerusForms.count() == 1);
555
556 QString context, contextUtf8;
557 bool contextIsSystem, contextIsUtf8, contextNeeds8Bit;
558 QString sourcetext, sourcetextUtf8;
559 bool sourcetextIsSystem, sourcetextIsUtf8, sourcetextNeeds8Bit;
560 QString comment, commentUtf8;
561 bool commentIsSystem, commentIsUtf8, commentNeeds8Bit;
562 QStringList translations;
563
564 for (const uchar *start = offsetArray; start != offsetArray + (numItems << 3); start += 8) {
565 //quint32 hash = read32(start);
566 quint32 ro = read32(start + 4);
567 //qDebug() << "\nHASH:" << hash;
568 const uchar *m = messageArray + ro;
569
570 for (;;) {
571 uchar tag = read8(m++);
572 //qDebug() << "Tag:" << tag << " ADDR: " << m;
573 switch(tag) {
574 case Tag_End:
575 goto end;
576 case Tag_Translation: {
577 int len = read32(m);
578 if (len % 1) {
579 cd.appendError(QLatin1String("QM-Format error"));
580 return false;
581 }
582 m += 4;
583 QString str = QString((const QChar *)m, len/2);
584 if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
585 for (int i = 0; i < str.length(); ++i)
586 str[i] = QChar((str.at(i).unicode() >> 8) +
587 ((str.at(i).unicode() << 8) & 0xff00));
588 }
589 translations << str;
590 m += len;
591 break;
592 }
593 case Tag_Obsolete1:
594 m += 4;
595 //qDebug() << "OBSOLETE";
596 break;
597 case Tag_SourceText: {
598 quint32 len = read32(m);
599 m += 4;
600 //qDebug() << "SOURCE LEN: " << len;
601 //qDebug() << "SOURCE: " << QByteArray((const char*)m, len);
602 fromBytes((const char*)m, len, codec, utf8Codec,
603 &sourcetext, &sourcetextUtf8,
604 &sourcetextIsSystem, &sourcetextIsUtf8, &sourcetextNeeds8Bit);
605 m += len;
606 break;
607 }
608 case Tag_Context: {
609 quint32 len = read32(m);
610 m += 4;
611 //qDebug() << "CONTEXT LEN: " << len;
612 //qDebug() << "CONTEXT: " << QByteArray((const char*)m, len);
613 fromBytes((const char*)m, len, codec, utf8Codec,
614 &context, &contextUtf8,
615 &contextIsSystem, &contextIsUtf8, &contextNeeds8Bit);
616 m += len;
617 break;
618 }
619 case Tag_Comment: {
620 quint32 len = read32(m);
621 m += 4;
622 //qDebug() << "COMMENT LEN: " << len;
623 //qDebug() << "COMMENT: " << QByteArray((const char*)m, len);
624 fromBytes((const char*)m, len, codec, utf8Codec,
625 &comment, &commentUtf8,
626 &commentIsSystem, &commentIsUtf8, &commentNeeds8Bit);
627 m += len;
628 break;
629 }
630 default:
631 //qDebug() << "UNKNOWN TAG" << tag;
632 break;
633 }
634 }
635 end:;
636 TranslatorMessage msg;
637 msg.setType(TranslatorMessage::Finished);
638 if (translations.count() > 1) {
639 // If guessPlurals is not false here, plural form discard messages
640 // will be spewn out later.
641 msg.setPlural(true);
642 } else if (guessPlurals) {
643 // This might cause false positives, so it is a fallback only.
644 if (sourcetext.contains(strProN))
645 msg.setPlural(true);
646 }
647 msg.setTranslations(translations);
648 translations.clear();
649 if (contextNeeds8Bit || sourcetextNeeds8Bit || commentNeeds8Bit) {
650 if (utf8Codec && contextIsUtf8 && sourcetextIsUtf8 && commentIsUtf8) {
651 // The message is utf-8, but file is not.
652 msg.setUtf8(true);
653 msg.setContext(contextUtf8);
654 msg.setSourceText(sourcetextUtf8);
655 msg.setComment(commentUtf8);
656 translator.append(msg);
657 continue;
658 }
659 if (!(contextIsSystem && sourcetextIsSystem && commentIsSystem)) {
660 cd.appendError(QLatin1String(
661 "Cannot read file with specified input codec"));
662 return false;
663 }
664 // The message is 8-bit in the file's encoding (utf-8 or not).
665 }
666 msg.setContext(context);
667 msg.setSourceText(sourcetext);
668 msg.setComment(comment);
669 translator.append(msg);
670 }
671 return ok;
672 }
673
674
675
containsStripped(const Translator & translator,const TranslatorMessage & msg)676 static bool containsStripped(const Translator &translator, const TranslatorMessage &msg)
677 {
678 foreach (const TranslatorMessage &tmsg, translator.messages())
679 if (tmsg.sourceText() == msg.sourceText()
680 && tmsg.context() == msg.context()
681 && tmsg.comment().isEmpty())
682 return true;
683 return false;
684 }
685
saveQM(const Translator & translator,QIODevice & dev,ConversionData & cd)686 bool saveQM(const Translator &translator, QIODevice &dev, ConversionData &cd)
687 {
688 Releaser releaser;
689 QLocale::Language l;
690 QLocale::Country c;
691 Translator::languageAndCountry(translator.languageCode(), &l, &c);
692 QByteArray rules;
693 if (getNumerusInfo(l, c, &rules, 0, 0))
694 releaser.setNumerusRules(rules);
695 releaser.setCodecName(translator.codecName());
696
697 int finished = 0;
698 int unfinished = 0;
699 int untranslated = 0;
700 int missingIds = 0;
701 int droppedData = 0;
702
703 for (int i = 0; i != translator.messageCount(); ++i) {
704 const TranslatorMessage &msg = translator.message(i);
705 TranslatorMessage::Type typ = msg.type();
706 if (typ != TranslatorMessage::Obsolete) {
707 if (cd.m_idBased && msg.id().isEmpty()) {
708 ++missingIds;
709 continue;
710 }
711 if (typ == TranslatorMessage::Unfinished) {
712 if (msg.translation().isEmpty() && !cd.m_idBased && cd.m_unTrPrefix.isEmpty()) {
713 ++untranslated;
714 continue;
715 } else {
716 if (cd.ignoreUnfinished())
717 continue;
718 ++unfinished;
719 }
720 } else {
721 ++finished;
722 }
723 QStringList tlns = msg.translations();
724 if (msg.type() == TranslatorMessage::Unfinished
725 && (cd.m_idBased || !cd.m_unTrPrefix.isEmpty()))
726 for (int j = 0; j < tlns.size(); ++j)
727 if (tlns.at(j).isEmpty())
728 tlns[j] = cd.m_unTrPrefix + msg.sourceText();
729 if (cd.m_idBased) {
730 if (!msg.context().isEmpty() || !msg.comment().isEmpty())
731 ++droppedData;
732 releaser.insertIdBased(msg, tlns);
733 } else {
734 // Drop the comment in (context, sourceText, comment),
735 // unless the context is empty,
736 // unless (context, sourceText, "") already exists or
737 // unless we already dropped the comment of (context,
738 // sourceText, comment0).
739 bool forceComment =
740 msg.comment().isEmpty()
741 || msg.context().isEmpty()
742 || containsStripped(translator, msg);
743 releaser.insert(msg, tlns, forceComment);
744 }
745 }
746 }
747
748 if (missingIds)
749 cd.appendError(QCoreApplication::translate("LRelease",
750 "Dropped %n message(s) which had no ID.", 0,
751 QCoreApplication::CodecForTr, missingIds));
752 if (droppedData)
753 cd.appendError(QCoreApplication::translate("LRelease",
754 "Excess context/disambiguation dropped from %n message(s).", 0,
755 QCoreApplication::CodecForTr, droppedData));
756
757 releaser.squeeze(cd.m_saveMode);
758 bool saved = releaser.save(&dev);
759 if (saved && cd.isVerbose()) {
760 int generatedCount = finished + unfinished;
761 cd.appendError(QCoreApplication::translate("LRelease",
762 " Generated %n translation(s) (%1 finished and %2 unfinished)", 0,
763 QCoreApplication::CodecForTr, generatedCount).arg(finished).arg(unfinished));
764 if (untranslated)
765 cd.appendError(QCoreApplication::translate("LRelease",
766 " Ignored %n untranslated source text(s)", 0,
767 QCoreApplication::CodecForTr, untranslated));
768 }
769 return saved;
770 }
771
initQM()772 int initQM()
773 {
774 Translator::FileFormat format;
775
776 format.extension = QLatin1String("qm");
777 format.description = QObject::tr("Compiled Qt translations");
778 format.fileType = Translator::FileFormat::TranslationBinary;
779 format.priority = 0;
780 format.loader = &loadQM;
781 format.saver = &saveQM;
782 Translator::registerFileFormat(format);
783
784 return 1;
785 }
786
787 Q_CONSTRUCTOR_FUNCTION(initQM)
788
789 QT_END_NAMESPACE
790