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