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