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

Last change on this file since 846 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 26.7 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the Qt Linguist of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this 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 have questions regarding the use of this file, please contact
37** Nokia at qt-info@nokia.com.
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
55QT_BEGIN_NAMESPACE
56
57// magic number for the file
58static const int MagicLength = 16;
59static 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
65namespace {
66
67enum 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
79enum Prefix {
80 NoPrefix,
81 Hash,
82 HashContext,
83 HashContextSourceText,
84 HashContextSourceTextComment
85};
86
87} // namespace anon
88
89static 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
108class ByteTranslatorMessage
109{
110public:
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 {}
121 const QByteArray &context() const { return m_context; }
122 const QByteArray &sourceText() const { return m_sourcetext; }
123 const QByteArray &comment() const { return m_comment; }
124 const QStringList &translations() const { return m_translations; }
125 bool operator<(const ByteTranslatorMessage& m) const;
126
127private:
128 QByteArray m_context;
129 QByteArray m_sourcetext;
130 QByteArray m_comment;
131 QStringList m_translations;
132};
133
134Q_DECLARE_TYPEINFO(ByteTranslatorMessage, Q_MOVABLE_TYPE);
135
136bool 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
145class Releaser
146{
147public:
148 struct Offset {
149 Offset()
150 : h(0), o(0)
151 {}
152 Offset(uint hash, uint offset)
153 : h(hash), o(offset)
154 {}
155
156 bool operator<(const Offset &other) const {
157 return (h != other.h) ? h < other.h : o < other.o;
158 }
159 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
168 Releaser() : m_codec(0) {}
169
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
184private:
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
212QByteArray 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
224uint Releaser::msgHash(const ByteTranslatorMessage &msg)
225{
226 return elfHash(msg.sourceText() + msg.comment());
227}
228
229Prefix 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
242void 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
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, 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
437void 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
444void 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
450void Releaser::setNumerusRules(const QByteArray &rules)
451{
452 m_numerusRules = rules;
453}
454
455static quint8 read8(const uchar *data)
456{
457 return *data;
458}
459
460static quint32 read32(const uchar *data)
461{
462 return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | (data[3]);
463}
464
465static 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
491bool 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 const uchar *contextArray = 0;
507 const uchar *numerusRulesArray = 0;
508 uint messageLength = 0;
509 uint offsetLength = 0;
510 uint contextLength = 0;
511 uint numerusRulesLength = 0;
512
513 bool ok = true;
514 const uchar *end = data + len;
515
516 data += MagicLength;
517
518 while (data < end - 4) {
519 quint8 tag = read8(data++);
520 quint32 blockLen = read32(data);
521 //qDebug() << "TAG:" << tag << "BLOCKLEN:" << blockLen;
522 data += 4;
523 if (!tag || !blockLen)
524 break;
525 if (data + blockLen > end) {
526 ok = false;
527 break;
528 }
529
530 if (tag == Contexts) {
531 contextArray = data;
532 contextLength = blockLen;
533 //qDebug() << "CONTEXTS: " << contextLength << QByteArray((const char *)contextArray, contextLength).toHex();
534 } else if (tag == Hashes) {
535 offsetArray = data;
536 offsetLength = blockLen;
537 //qDebug() << "HASHES: " << offsetLength << QByteArray((const char *)offsetArray, offsetLength).toHex();
538 } else if (tag == Messages) {
539 messageArray = data;
540 messageLength = blockLen;
541 //qDebug() << "MESSAGES: " << messageLength << QByteArray((const char *)messageArray, messageLength).toHex();
542 } else if (tag == NumerusRules) {
543 numerusRulesArray = data;
544 numerusRulesLength = blockLen;
545 //qDebug() << "NUMERUSRULES: " << numerusRulesLength << QByteArray((const char *)numerusRulesArray, numerusRulesLength).toHex();
546 }
547
548 data += blockLen;
549 }
550
551
552 size_t numItems = offsetLength / (2 * sizeof(quint32));
553 //qDebug() << "NUMITEMS: " << numItems;
554
555 QTextCodec *codec = QTextCodec::codecForName(
556 cd.m_codecForSource.isEmpty() ? QByteArray("Latin1") : cd.m_codecForSource);
557 QTextCodec *utf8Codec = 0;
558 if (codec->name() != "UTF-8")
559 utf8Codec = QTextCodec::codecForName("UTF-8");
560
561 QString strProN = QLatin1String("%n");
562 QLocale::Language l;
563 QLocale::Country c;
564 Translator::languageAndCountry(translator.languageCode(), &l, &c);
565 QStringList numerusForms;
566 bool guessPlurals = true;
567 if (getNumerusInfo(l, c, 0, &numerusForms, 0))
568 guessPlurals = (numerusForms.count() == 1);
569
570 QString context, contextUtf8;
571 bool contextIsSystem, contextIsUtf8, contextNeeds8Bit;
572 QString sourcetext, sourcetextUtf8;
573 bool sourcetextIsSystem, sourcetextIsUtf8, sourcetextNeeds8Bit;
574 QString comment, commentUtf8;
575 bool commentIsSystem, commentIsUtf8, commentNeeds8Bit;
576 QStringList translations;
577
578 for (const uchar *start = offsetArray; start != offsetArray + (numItems << 3); start += 8) {
579 //quint32 hash = read32(start);
580 quint32 ro = read32(start + 4);
581 //qDebug() << "\nHASH:" << hash;
582 const uchar *m = messageArray + ro;
583
584 for (;;) {
585 uchar tag = read8(m++);
586 //qDebug() << "Tag:" << tag << " ADDR: " << m;
587 switch(tag) {
588 case Tag_End:
589 goto end;
590 case Tag_Translation: {
591 int len = read32(m);
592 if (len % 1) {
593 cd.appendError(QLatin1String("QM-Format error"));
594 return false;
595 }
596 m += 4;
597 QString str = QString((const QChar *)m, len/2);
598 if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
599 for (int i = 0; i < str.length(); ++i)
600 str[i] = QChar((str.at(i).unicode() >> 8) +
601 ((str.at(i).unicode() << 8) & 0xff00));
602 }
603 translations << str;
604 m += len;
605 break;
606 }
607 case Tag_Obsolete1:
608 m += 4;
609 //qDebug() << "OBSOLETE";
610 break;
611 case Tag_SourceText: {
612 quint32 len = read32(m);
613 m += 4;
614 //qDebug() << "SOURCE LEN: " << len;
615 //qDebug() << "SOURCE: " << QByteArray((const char*)m, len);
616 fromBytes((const char*)m, len, codec, utf8Codec,
617 &sourcetext, &sourcetextUtf8,
618 &sourcetextIsSystem, &sourcetextIsUtf8, &sourcetextNeeds8Bit);
619 m += len;
620 break;
621 }
622 case Tag_Context: {
623 quint32 len = read32(m);
624 m += 4;
625 //qDebug() << "CONTEXT LEN: " << len;
626 //qDebug() << "CONTEXT: " << QByteArray((const char*)m, len);
627 fromBytes((const char*)m, len, codec, utf8Codec,
628 &context, &contextUtf8,
629 &contextIsSystem, &contextIsUtf8, &contextNeeds8Bit);
630 m += len;
631 break;
632 }
633 case Tag_Comment: {
634 quint32 len = read32(m);
635 m += 4;
636 //qDebug() << "COMMENT LEN: " << len;
637 //qDebug() << "COMMENT: " << QByteArray((const char*)m, len);
638 fromBytes((const char*)m, len, codec, utf8Codec,
639 &comment, &commentUtf8,
640 &commentIsSystem, &commentIsUtf8, &commentNeeds8Bit);
641 m += len;
642 break;
643 }
644 default:
645 //qDebug() << "UNKNOWN TAG" << tag;
646 break;
647 }
648 }
649 end:;
650 TranslatorMessage msg;
651 msg.setType(TranslatorMessage::Finished);
652 if (translations.count() > 1) {
653 // If guessPlurals is not false here, plural form discard messages
654 // will be spewn out later.
655 msg.setPlural(true);
656 } else if (guessPlurals) {
657 // This might cause false positives, so it is a fallback only.
658 if (sourcetext.contains(strProN))
659 msg.setPlural(true);
660 }
661 msg.setTranslations(translations);
662 translations.clear();
663 if (contextNeeds8Bit || sourcetextNeeds8Bit || commentNeeds8Bit) {
664 if (utf8Codec && contextIsUtf8 && sourcetextIsUtf8 && commentIsUtf8) {
665 // The message is utf-8, but file is not.
666 msg.setUtf8(true);
667 msg.setContext(contextUtf8);
668 msg.setSourceText(sourcetextUtf8);
669 msg.setComment(commentUtf8);
670 translator.append(msg);
671 continue;
672 }
673 if (!(contextIsSystem && sourcetextIsSystem && commentIsSystem)) {
674 cd.appendError(QLatin1String(
675 "Cannot read file with specified input codec"));
676 return false;
677 }
678 // The message is 8-bit in the file's encoding (utf-8 or not).
679 }
680 msg.setContext(context);
681 msg.setSourceText(sourcetext);
682 msg.setComment(comment);
683 translator.append(msg);
684 }
685 return ok;
686}
687
688
689
690static bool containsStripped(const Translator &translator, const TranslatorMessage &msg)
691{
692 foreach (const TranslatorMessage &tmsg, translator.messages())
693 if (tmsg.sourceText() == msg.sourceText()
694 && tmsg.context() == msg.context()
695 && tmsg.comment().isEmpty())
696 return true;
697 return false;
698}
699
700static bool saveQM(const Translator &translator, QIODevice &dev, ConversionData &cd)
701{
702 Releaser releaser;
703 QLocale::Language l;
704 QLocale::Country c;
705 Translator::languageAndCountry(translator.languageCode(), &l, &c);
706 QByteArray rules;
707 if (getNumerusInfo(l, c, &rules, 0, 0))
708 releaser.setNumerusRules(rules);
709 releaser.setCodecName(translator.codecName());
710
711 int finished = 0;
712 int unfinished = 0;
713 int untranslated = 0;
714 int missingIds = 0;
715 int droppedData = 0;
716
717 for (int i = 0; i != translator.messageCount(); ++i) {
718 const TranslatorMessage &msg = translator.message(i);
719 TranslatorMessage::Type typ = msg.type();
720 if (typ != TranslatorMessage::Obsolete) {
721 if (cd.m_idBased && msg.id().isEmpty()) {
722 ++missingIds;
723 continue;
724 }
725 if (typ == TranslatorMessage::Unfinished) {
726 if (!cd.m_idBased && msg.translation().isEmpty()) {
727 ++untranslated;
728 continue;
729 } else {
730 if (cd.ignoreUnfinished())
731 continue;
732 ++unfinished;
733 }
734 } else {
735 ++finished;
736 }
737 QStringList tlns = msg.translations();
738 if (msg.type() == TranslatorMessage::Unfinished
739 && (cd.m_idBased || !cd.m_unTrPrefix.isEmpty()))
740 for (int j = 0; j < tlns.size(); ++j)
741 if (tlns.at(j).isEmpty())
742 tlns[j] = cd.m_unTrPrefix + msg.sourceText();
743 if (cd.m_idBased) {
744 if (!msg.context().isEmpty() || !msg.comment().isEmpty())
745 ++droppedData;
746 releaser.insertIdBased(msg, tlns);
747 } else {
748 // Drop the comment in (context, sourceText, comment),
749 // unless the context is empty,
750 // unless (context, sourceText, "") already exists or
751 // unless we already dropped the comment of (context,
752 // sourceText, comment0).
753 bool forceComment =
754 msg.comment().isEmpty()
755 || msg.context().isEmpty()
756 || containsStripped(translator, msg);
757 releaser.insert(msg, tlns, forceComment);
758 }
759 }
760 }
761
762 if (missingIds)
763 cd.appendError(QCoreApplication::translate("LRelease",
764 "Dropped %n message(s) which had no ID.", 0,
765 QCoreApplication::CodecForTr, missingIds));
766 if (droppedData)
767 cd.appendError(QCoreApplication::translate("LRelease",
768 "Excess context/disambiguation dropped from %n message(s).", 0,
769 QCoreApplication::CodecForTr, droppedData));
770
771 releaser.squeeze(cd.m_saveMode);
772 bool saved = releaser.save(&dev);
773 if (saved && cd.isVerbose()) {
774 int generatedCount = finished + unfinished;
775 cd.appendError(QCoreApplication::translate("LRelease",
776 " Generated %n translation(s) (%1 finished and %2 unfinished)", 0,
777 QCoreApplication::CodecForTr, generatedCount).arg(finished).arg(unfinished));
778 if (untranslated)
779 cd.appendError(QCoreApplication::translate("LRelease",
780 " Ignored %n untranslated source text(s)", 0,
781 QCoreApplication::CodecForTr, untranslated));
782 }
783 return saved;
784}
785
786int initQM()
787{
788 Translator::FileFormat format;
789
790 format.extension = QLatin1String("qm");
791 format.description = QObject::tr("Compiled Qt translations");
792 format.fileType = Translator::FileFormat::TranslationBinary;
793 format.priority = 0;
794 format.loader = &loadQM;
795 format.saver = &saveQM;
796 Translator::registerFileFormat(format);
797
798 return 1;
799}
800
801Q_CONSTRUCTOR_FUNCTION(initQM)
802
803QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.