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