source: trunk/tools/linguist/shared/qm.cpp@ 385

Last change on this file since 385 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 24.4 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information (qt-info@nokia.com)
5**
6** This file is part of the Qt Linguist of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you are unsure which license is appropriate for your use, please
37** contact the sales department at qt-sales@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "translator.h"
43
44#include <QtCore/QCoreApplication>
45#include <QtCore/QDebug>
46#include <QtCore/QDir>
47#include <QtCore/QFile>
48#include <QtCore/QFileInfo>
49#include <QtCore/QMap>
50#include <QtCore/QString>
51#include <QtCore/QTextCodec>
52
53QT_BEGIN_NAMESPACE
54
55// magic number for the file
56static const int MagicLength = 16;
57static const uchar magic[MagicLength] = {
58 0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95,
59 0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd
60};
61
62
63namespace {
64
65enum Tag {
66 Tag_End = 1,
67 Tag_SourceText16 = 2,
68 Tag_Translation = 3,
69 Tag_Context16 = 4,
70 Tag_Obsolete1 = 5,
71 Tag_SourceText = 6,
72 Tag_Context = 7,
73 Tag_Comment = 8,
74 Tag_Obsolete2 = 9
75};
76
77enum Prefix {
78 NoPrefix,
79 Hash,
80 HashContext,
81 HashContextSourceText,
82 HashContextSourceTextComment
83};
84
85} // namespace anon
86
87static uint elfHash(const QByteArray &ba)
88{
89 const uchar *k = (const uchar *)ba.data();
90 uint h = 0;
91 uint g;
92
93 if (k) {
94 while (*k) {
95 h = (h << 4) + *k++;
96 if ((g = (h & 0xf0000000)) != 0)
97 h ^= g >> 24;
98 h &= ~g;
99 }
100 }
101 if (!h)
102 h = 1;
103 return h;
104}
105
106class ByteTranslatorMessage
107{
108public:
109 ByteTranslatorMessage(
110 const QByteArray &context,
111 const QByteArray &sourceText,
112 const QByteArray &comment,
113 const QStringList &translations) :
114 m_context(context),
115 m_sourcetext(sourceText),
116 m_comment(comment),
117 m_translations(translations)
118 {}
119 const QByteArray &context() const { return m_context; }
120 const QByteArray &sourceText() const { return m_sourcetext; }
121 const QByteArray &comment() const { return m_comment; }
122 const QStringList &translations() const { return m_translations; }
123 bool operator<(const ByteTranslatorMessage& m) const;
124
125private:
126 QByteArray m_context;
127 QByteArray m_sourcetext;
128 QByteArray m_comment;
129 QStringList m_translations;
130};
131
132Q_DECLARE_TYPEINFO(ByteTranslatorMessage, Q_MOVABLE_TYPE);
133
134bool ByteTranslatorMessage::operator<(const ByteTranslatorMessage& m) const
135{
136 if (m_context != m.m_context)
137 return m_context < m.m_context;
138 if (m_sourcetext != m.m_sourcetext)
139 return m_sourcetext < m.m_sourcetext;
140 return m_comment < m.m_comment;
141}
142
143class Releaser
144{
145public:
146 struct Offset {
147 Offset()
148 : h(0), o(0)
149 {}
150 Offset(uint hash, uint offset)
151 : h(hash), o(offset)
152 {}
153
154 bool operator<(const Offset &other) const {
155 return (h != other.h) ? h < other.h : o < other.o;
156 }
157 bool operator==(const Offset &other) const {
158 return h == other.h && o == other.o;
159 }
160 uint h;
161 uint o;
162 };
163
164 enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88 };
165
166 Releaser() : m_codec(0) {}
167
168 void setCodecName(const QByteArray &codecName)
169 {
170 m_codec = QTextCodec::codecForName(codecName);
171 }
172
173 bool save(QIODevice *iod);
174
175 void insert(const TranslatorMessage &msg, bool forceComment);
176
177 void squeeze(TranslatorSaveMode mode);
178
179 void setNumerusRules(const QByteArray &rules);
180
181private:
182 Q_DISABLE_COPY(Releaser)
183
184 // This should reproduce the byte array fetched from the source file, which
185 // on turn should be the same as passed to the actual tr(...) calls
186 QByteArray originalBytes(const QString &str, bool isUtf8) const;
187
188 void insertInternal(const TranslatorMessage &message, bool forceComment, bool isUtf8);
189
190 static Prefix commonPrefix(const ByteTranslatorMessage &m1, const ByteTranslatorMessage &m2);
191
192 static uint msgHash(const ByteTranslatorMessage &msg);
193
194 void writeMessage(const ByteTranslatorMessage & msg, QDataStream & stream,
195 TranslatorSaveMode strip, Prefix prefix) const;
196
197 // for squeezed but non-file data, this is what needs to be deleted
198 QByteArray m_messageArray;
199 QByteArray m_offsetArray;
200 QByteArray m_contextArray;
201 QMap<ByteTranslatorMessage, void *> m_messages;
202 QByteArray m_numerusRules;
203
204 // Used to reproduce the original bytes
205 QTextCodec *m_codec;
206};
207
208QByteArray Releaser::originalBytes(const QString &str, bool isUtf8) const
209{
210 if (str.isEmpty()) {
211 // Do not use QByteArray() here as the result of the serialization
212 // will be different.
213 return QByteArray("");
214 }
215 if (isUtf8)
216 return str.toUtf8();
217 return m_codec ? m_codec->fromUnicode(str) : str.toLatin1();
218}
219
220uint Releaser::msgHash(const ByteTranslatorMessage &msg)
221{
222 return elfHash(msg.sourceText() + msg.comment());
223}
224
225Prefix Releaser::commonPrefix(const ByteTranslatorMessage &m1, const ByteTranslatorMessage &m2)
226{
227 if (msgHash(m1) != msgHash(m2))
228 return NoPrefix;
229 if (m1.context() != m2.context())
230 return Hash;
231 if (m1.sourceText() != m2.sourceText())
232 return HashContext;
233 if (m1.comment() != m2.comment())
234 return HashContextSourceText;
235 return HashContextSourceTextComment;
236}
237
238void Releaser::writeMessage(const ByteTranslatorMessage &msg, QDataStream &stream,
239 TranslatorSaveMode mode, Prefix prefix) const
240{
241 for (int i = 0; i < msg.translations().count(); ++i) {
242 QString str = msg.translations().at(i);
243 str.replace(QChar(Translator::DefaultVariantSeparator),
244 QChar(Translator::InternalVariantSeparator));
245 stream << quint8(Tag_Translation) << str;
246 }
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
269bool 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
297void 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
419void Releaser::insertInternal(const TranslatorMessage &message, bool forceComment, bool isUtf8)
420{
421 ByteTranslatorMessage bmsg(originalBytes(message.context(), isUtf8),
422 originalBytes(message.sourceText(), isUtf8),
423 originalBytes(message.comment(), isUtf8),
424 message.translations());
425 if (!forceComment) {
426 ByteTranslatorMessage bmsg2(
427 bmsg.context(), bmsg.sourceText(), QByteArray(""), bmsg.translations());
428 if (!m_messages.contains(bmsg2)) {
429 m_messages.insert(bmsg2, 0);
430 return;
431 }
432 }
433 m_messages.insert(bmsg, 0);
434}
435
436void Releaser::insert(const TranslatorMessage &message, bool forceComment)
437{
438 insertInternal(message, forceComment, message.isUtf8());
439 if (message.isUtf8() && message.isNonUtf8())
440 insertInternal(message, forceComment, false);
441}
442
443void Releaser::setNumerusRules(const QByteArray &rules)
444{
445 m_numerusRules = rules;
446}
447
448static quint8 read8(const uchar *data)
449{
450 return *data;
451}
452
453static quint32 read32(const uchar *data)
454{
455 return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | (data[3]);
456}
457
458static void fromBytes(const char *str, int len, QTextCodec *codec, QTextCodec *utf8Codec,
459 QString *out, QString *utf8Out,
460 bool *isSystem, bool *isUtf8, bool *needs8Bit)
461{
462 for (int i = 0; i < len; ++i)
463 if (str[i] & 0x80) {
464 if (utf8Codec) {
465 QTextCodec::ConverterState cvtState;
466 *utf8Out = utf8Codec->toUnicode(str, len, &cvtState);
467 *isUtf8 = !cvtState.invalidChars;
468 }
469 QTextCodec::ConverterState cvtState;
470 *out = codec->toUnicode(str, len, &cvtState);
471 *isSystem = !cvtState.invalidChars;
472 *needs8Bit = true;
473 return;
474 }
475 *out = QString::fromLatin1(str, len);
476 *isSystem = true;
477 if (utf8Codec) {
478 *utf8Out = *out;
479 *isUtf8 = true;
480 }
481 *needs8Bit = false;
482}
483
484bool loadQM(Translator &translator, QIODevice &dev, ConversionData &cd)
485{
486 QByteArray ba = dev.readAll();
487 const uchar *data = (uchar*)ba.data();
488 int len = ba.size();
489 if (len < MagicLength || memcmp(data, magic, MagicLength) != 0) {
490 cd.appendError(QLatin1String("QM-Format error: magic marker missing"));
491 return false;
492 }
493
494 enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88 };
495
496 // for squeezed but non-file data, this is what needs to be deleted
497 const uchar *messageArray = 0;
498 const uchar *offsetArray = 0;
499 const uchar *contextArray = 0;
500 const uchar *numerusRulesArray = 0;
501 uint messageLength = 0;
502 uint offsetLength = 0;
503 uint contextLength = 0;
504 uint numerusRulesLength = 0;
505
506 bool ok = true;
507 const uchar *end = data + len;
508
509 data += MagicLength;
510
511 while (data < end - 4) {
512 quint8 tag = read8(data++);
513 quint32 blockLen = read32(data);
514 //qDebug() << "TAG:" << tag << "BLOCKLEN:" << blockLen;
515 data += 4;
516 if (!tag || !blockLen)
517 break;
518 if (data + blockLen > end) {
519 ok = false;
520 break;
521 }
522
523 if (tag == Contexts) {
524 contextArray = data;
525 contextLength = blockLen;
526 //qDebug() << "CONTEXTS: " << contextLength << QByteArray((const char *)contextArray, contextLength).toHex();
527 } else if (tag == Hashes) {
528 offsetArray = data;
529 offsetLength = blockLen;
530 //qDebug() << "HASHES: " << offsetLength << QByteArray((const char *)offsetArray, offsetLength).toHex();
531 } else if (tag == Messages) {
532 messageArray = data;
533 messageLength = blockLen;
534 //qDebug() << "MESSAGES: " << messageLength << QByteArray((const char *)messageArray, messageLength).toHex();
535 } else if (tag == NumerusRules) {
536 numerusRulesArray = data;
537 numerusRulesLength = blockLen;
538 //qDebug() << "NUMERUSRULES: " << numerusRulesLength << QByteArray((const char *)numerusRulesArray, numerusRulesLength).toHex();
539 }
540
541 data += blockLen;
542 }
543
544
545 size_t numItems = offsetLength / (2 * sizeof(quint32));
546 //qDebug() << "NUMITEMS: " << numItems;
547
548 // FIXME: that's just a guess, the original locale data is lost...
549 QTextCodec *codec = QTextCodec::codecForLocale();
550 QTextCodec *utf8Codec = 0;
551 if (codec->name() != "UTF-8")
552 utf8Codec = QTextCodec::codecForName("UTF-8");
553
554 QString context, contextUtf8;
555 bool contextIsSystem, contextIsUtf8, contextNeeds8Bit;
556 QString sourcetext, sourcetextUtf8;
557 bool sourcetextIsSystem, sourcetextIsUtf8, sourcetextNeeds8Bit;
558 QString comment, commentUtf8;
559 bool commentIsSystem, commentIsUtf8, commentNeeds8Bit;
560 QStringList translations;
561
562 for (const uchar *start = offsetArray; start != offsetArray + (numItems << 3); start += 8) {
563 //quint32 hash = read32(start);
564 quint32 ro = read32(start + 4);
565 //qDebug() << "\nHASH:" << hash;
566 const uchar *m = messageArray + ro;
567
568 for (;;) {
569 uchar tag = read8(m++);
570 //qDebug() << "Tag:" << tag << " ADDR: " << m;
571 switch(tag) {
572 case Tag_End:
573 goto end;
574 case Tag_Translation: {
575 int len = read32(m);
576 if (len % 1) {
577 cd.appendError(QLatin1String("QM-Format error"));
578 return false;
579 }
580 m += 4;
581 QString str = QString::fromUtf16((const ushort *)m, len/2);
582 if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
583 for (int i = 0; i < str.length(); ++i)
584 str[i] = QChar((str.at(i).unicode() >> 8) +
585 ((str.at(i).unicode() << 8) & 0xff00));
586 }
587 str.replace(QChar(Translator::InternalVariantSeparator),
588 QChar(Translator::DefaultVariantSeparator));
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 msg.setTranslations(translations);
639 translations.clear();
640 if (contextNeeds8Bit || sourcetextNeeds8Bit || commentNeeds8Bit) {
641 if (utf8Codec && contextIsUtf8 && sourcetextIsUtf8 && commentIsUtf8) {
642 // The message is utf-8, but file is not.
643 msg.setUtf8(true);
644 msg.setContext(contextUtf8);
645 msg.setSourceText(sourcetextUtf8);
646 msg.setComment(commentUtf8);
647 translator.append(msg);
648 continue;
649 }
650 if (!(contextIsSystem && sourcetextIsSystem && commentIsSystem)) {
651 cd.appendError(QLatin1String(
652 "Cannot read file with current system character codec"));
653 return false;
654 }
655 // The message is 8-bit in the file's encoding (utf-8 or not).
656 }
657 msg.setContext(context);
658 msg.setSourceText(sourcetext);
659 msg.setComment(comment);
660 translator.append(msg);
661 }
662 return ok;
663}
664
665
666
667static bool saveQM(const Translator &translator, QIODevice &dev, ConversionData &cd)
668{
669 Releaser releaser;
670 QLocale::Language l;
671 QLocale::Country c;
672 Translator::languageAndCountry(translator.languageCode(), &l, &c);
673 QByteArray rules;
674 if (getNumerusInfo(l, c, &rules, 0))
675 releaser.setNumerusRules(rules);
676 releaser.setCodecName(translator.codecName());
677
678 int finished = 0;
679 int unfinished = 0;
680 int untranslated = 0;
681
682 for (int i = 0; i != translator.messageCount(); ++i) {
683 const TranslatorMessage &msg = translator.message(i);
684 TranslatorMessage::Type typ = msg.type();
685 if (typ != TranslatorMessage::Obsolete) {
686 if (typ == TranslatorMessage::Unfinished) {
687 if (msg.translation().isEmpty()) {
688 ++untranslated;
689 continue;
690 } else {
691 if (cd.ignoreUnfinished())
692 continue;
693 ++unfinished;
694 }
695 } else {
696 ++finished;
697 }
698 // Drop the comment in (context, sourceText, comment),
699 // unless the context is empty,
700 // unless (context, sourceText, "") already exists or
701 // unless we already dropped the comment of (context,
702 // sourceText, comment0).
703 bool forceComment =
704 msg.comment().isEmpty()
705 || msg.context().isEmpty()
706 || translator.contains(msg.context(), msg.sourceText(), QString());
707 releaser.insert(msg, forceComment);
708 }
709 }
710
711 releaser.squeeze(cd.m_saveMode);
712 bool saved = releaser.save(&dev);
713 if (saved && cd.isVerbose()) {
714 int generatedCount = finished + unfinished;
715 cd.appendError(QCoreApplication::translate("LRelease",
716 " Generated %n translation(s) (%1 finished and %2 unfinished)\n", 0,
717 QCoreApplication::CodecForTr, generatedCount).arg(finished).arg(unfinished));
718 if (untranslated)
719 cd.appendError(QCoreApplication::translate("LRelease",
720 " Ignored %n untranslated source text(s)\n", 0,
721 QCoreApplication::CodecForTr, untranslated));
722 }
723 return saved;
724}
725
726int initQM()
727{
728 Translator::FileFormat format;
729
730 format.extension = QLatin1String("qm");
731 format.description = QObject::tr("Compiled Qt translations");
732 format.fileType = Translator::FileFormat::TranslationBinary;
733 format.priority = 0;
734 format.loader = &loadQM;
735 format.saver = &saveQM;
736 Translator::registerFileFormat(format);
737
738 return 1;
739}
740
741Q_CONSTRUCTOR_FUNCTION(initQM)
742
743QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.