source: branches/vendor/trolltech/qt/current/tools/linguist/shared/qm.cpp @ 2

Last change on this file since 2 was 2, checked in by Dmitry A. Kuminov, 13 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.