7#include <QtCore/QCoreApplication>
9#include <QtCore/QDataStream>
10#include <QtCore/QDebug>
12#include <QtCore/QFile>
13#include <QtCore/QFileInfo>
15#include <QtCore/QString>
16#include <QtCore/QStringDecoder>
20using namespace Qt::Literals::StringLiterals;
25 0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95,
26 0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd
48 HashContextSourceText,
49 HashContextSourceTextComment
56 const uchar *k = (
const uchar *)ba.data();
63 if ((g = (h & 0xf0000000)) != 0)
77 const QByteArray &context,
78 const QByteArray &sourceText,
79 const QByteArray &comment,
80 const QStringList &translations) :
94 QByteArray m_sourcetext;
96 QStringList m_translations;
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;
122 return (h != other.h) ? h < other.h : o < other.o;
125 return h == other.h && o == other.o;
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;
170QByteArray
Releaser::originalBytes(
const QString &str)
const
175 return QByteArray(
"");
182 return elfHash(msg.sourceText() + msg.comment());
187 if (msgHash(m1) != msgHash(m2))
189 if (m1.context() != m2.context())
191 if (m1.sourceText() != m2.sourceText())
193 if (m1.comment() != m2.comment())
194 return HashContextSourceText;
195 return HashContextSourceTextComment;
201 for (
int i = 0; i < msg.translations().size(); ++i)
205 prefix = HashContextSourceTextComment;
210 case HashContextSourceTextComment:
213 case HashContextSourceText:
230 if (!m_language.isEmpty()) {
231 QByteArray lang = originalBytes(m_language);
232 quint32 las = quint32(lang.size());
234 s.writeRawData(lang, las);
236 if (!m_dependencyArray.isEmpty()) {
237 quint32 das = quint32(m_dependencyArray.size());
239 s.writeRawData(m_dependencyArray.constData(), das);
241 if (!m_offsetArray.isEmpty()) {
242 quint32 oas = quint32(m_offsetArray.size());
243 s << quint8(
Hashes) << oas;
244 s.writeRawData(m_offsetArray.constData(), oas);
246 if (!m_messageArray.isEmpty()) {
247 quint32 mas = quint32(m_messageArray.size());
249 s.writeRawData(m_messageArray.constData(), mas);
251 if (!m_contextArray.isEmpty()) {
252 quint32 cas = quint32(m_contextArray.size());
254 s.writeRawData(m_contextArray.constData(), cas);
256 if (!m_numerusRules.isEmpty()) {
257 quint32 nrs = m_numerusRules.size();
259 s.writeRawData(m_numerusRules.constData(), nrs);
266 m_dependencyArray.clear();
267 QDataStream depstream(&m_dependencyArray, QIODevice::WriteOnly);
268 for (
const QString &dep : std::as_const(m_dependencies))
271 if (m_messages.isEmpty() && mode == SaveEverything)
274 const auto messages = m_messages;
277 m_messageArray.clear();
278 m_offsetArray.clear();
279 m_contextArray.clear();
282 QMap<Offset,
void *> offsets;
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) {
288 const auto next = std::next(it);
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)));
297 auto offset = offsets.cbegin();
298 QDataStream ds(&m_offsetArray, QIODevice::WriteOnly);
299 while (offset != offsets.cend()) {
302 ds << quint32(k.h) << quint32(k.o);
306 QMap<QByteArray,
int> contextSet;
307 for (
auto it = messages.cbegin(), end = messages.cend(); it != end; ++it)
308 ++contextSet[it.key().context()];
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;
316 hTableSize = (contextSet.size() < 10000) ? 15013 : 3 * contextSet.size() / 2;
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());
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344 m_contextArray.resize(2 + (hTableSize << 1));
345 QDataStream t(&m_contextArray, QIODevice::WriteOnly);
347 quint16 *hTable =
new quint16[hTableSize];
348 memset(hTable, 0, hTableSize *
sizeof(quint16));
351 t.device()->seek(2 + (hTableSize << 1));
355 auto entry = hashMap.constBegin();
356 while (entry != hashMap.constEnd()) {
358 hTable[i] = quint16(upto >> 1);
361 const char *con = entry.value().constData();
362 uint len = uint(entry.value().size());
363 len = qMin(len, 255u);
365 t.writeRawData(con, len);
368 }
while (entry != hashMap.constEnd() && entry.key() == i);
376 for (
int j = 0; j < hTableSize; j++)
381 qWarning(
"Releaser::squeeze: Too many contexts");
382 m_contextArray.clear();
390 originalBytes(message.sourceText()),
391 originalBytes(message.comment()),
395 bmsg.context(), bmsg.sourceText(), QByteArray(
""), bmsg.translations());
396 if (!m_messages.contains(bmsg2)) {
397 m_messages.insert(bmsg2, 0);
401 m_messages.insert(bmsg, 0);
407 m_messages.insert(bmsg, 0);
412 m_numerusRules = rules;
417 m_dependencies = dependencies;
427 return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | (data[3]);
430static void fromBytes(
const char *str,
int len, QString *out,
bool *utf8Fail)
432 QStringDecoder toUnicode(QStringDecoder::Utf8, QStringDecoder::Flag::Stateless);
433 *out = toUnicode(QByteArrayView(str, len));
434 *utf8Fail = toUnicode.hasError();
439 QByteArray ba = dev.readAll();
440 const uchar *data = (uchar*)ba.data();
443 cd.appendError(
"QM-Format error: magic marker missing"_L1);
447 enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88, Dependencies = 0x96, Language = 0xa7 };
450 const uchar *messageArray =
nullptr;
451 const uchar *offsetArray =
nullptr;
452 uint offsetLength = 0;
455 bool utf8Fail =
false;
456 const uchar *end = data + len;
460 while (data < end - 4) {
461 quint8 tag = read8(data++);
462 quint32 blockLen = read32(data);
465 if (!tag || !blockLen)
467 if (data + blockLen > end) {
474 offsetLength = blockLen;
476 }
else if (tag == Messages) {
479 }
else if (tag == Dependencies) {
480 QStringList dependencies;
481 QDataStream stream(QByteArray::fromRawData((
const char*)data, blockLen));
483 while (!stream.atEnd()) {
485 dependencies.append(dep);
487 translator.setDependencies(dependencies);
488 }
else if (tag == Language) {
490 fromBytes((
const char *)data, blockLen, &language, &utf8Fail);
491 translator.setLanguageCode(language);
498 size_t numItems = offsetLength / (2 *
sizeof(quint32));
501 QString strProN =
"%n"_L1;
503 QLocale::Territory c;
504 Translator::languageAndTerritory(translator.languageCode(), &l, &c);
505 QStringList numerusForms;
506 bool guessPlurals =
true;
507 if (getNumerusInfo(l, c, 0, &numerusForms, 0))
508 guessPlurals = (numerusForms.size() == 1);
510 QString context, sourcetext, comment;
511 QStringList translations;
513 for (
const uchar *start = offsetArray; start != offsetArray + (numItems << 3); start += 8) {
515 quint32 ro = read32(start + 4);
517 const uchar *m = messageArray + ro;
520 uchar tag = read8(m++);
531 if ((len != -1) && (len & 1)) {
532 cd.appendError(
"QM-Format error"_L1);
537 str = QString((
const QChar *)m, len / 2);
538 if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
539 for (
int i = 0; i < str.size(); ++i)
540 str[i] = QChar((str.at(i).unicode() >> 8) +
541 ((str.at(i).unicode() << 8) & 0xff00));
552 quint32 len = read32(m);
556 fromBytes((
const char*)m, len, &sourcetext, &utf8Fail);
561 quint32 len = read32(m);
565 fromBytes((
const char*)m, len, &context, &utf8Fail);
570 quint32 len = read32(m);
574 fromBytes((
const char*)m, len, &comment, &utf8Fail);
586 if (translations.size() > 1) {
590 }
else if (guessPlurals) {
592 if (sourcetext.contains(strProN))
595 msg.setTranslations(translations);
596 translations.clear();
597 msg.setContext(context);
598 msg.setSourceText(sourcetext);
599 msg.setComment(comment);
603 cd.appendError(
"Error: File contains invalid UTF-8 sequences."_L1);
613 for (
const TranslatorMessage &tmsg : translator.messages())
614 if (tmsg.sourceText() == msg.sourceText()
615 && tmsg.context() == msg.context()
616 && tmsg.comment().isEmpty())
623 Releaser releaser(translator.languageCode());
625 QLocale::Territory c;
626 Translator::languageAndTerritory(translator.languageCode(), &l, &c);
628 if (getNumerusInfo(l, c, &rules, 0, 0))
629 releaser.setNumerusRules(rules);
633 int untranslated = 0;
641 if (msg.translation().isEmpty() && msg.id().isEmpty()
642 && cd.m_unTrPrefix.isEmpty()) {
653 QStringList tlns = msg.translations();
655 && (!msg.id().isEmpty() || !cd.m_unTrPrefix.isEmpty()))
656 for (
int j = 0; j < tlns.size(); ++j)
657 if (tlns.at(j).isEmpty())
658 tlns[j] = cd.m_unTrPrefix + msg.sourceText();
659 if (!msg.id().isEmpty()) {
660 if (!msg.context().isEmpty() || !msg.comment().isEmpty())
662 releaser.insertIdBased(msg, tlns);
670 msg.comment().isEmpty()
671 || msg.context().isEmpty()
673 releaser.insert(msg, tlns, forceComment);
679 cd.appendError(QCoreApplication::translate(
"LRelease",
680 "Excess context/disambiguation dropped from %n message(s).", 0,
683 releaser.setDependencies(translator.dependencies());
685 bool saved = releaser
.save(&dev
);
687 int generatedCount = finished + unfinished;
688 cd.appendError(QCoreApplication::translate(
"LRelease",
689 " Generated %n translation(s) (%1 finished and %2 unfinished)", 0,
690 generatedCount).arg(finished).arg(unfinished));
692 cd.appendError(QCoreApplication::translate(
"LRelease",
693 " Ignored %n untranslated source text(s)", 0,
703 format.extension =
"qm"_L1;
714Q_CONSTRUCTOR_FUNCTION(initQM)
bool operator<(const ByteTranslatorMessage &m) const
const QByteArray & sourceText() const
ByteTranslatorMessage(const QByteArray &context, const QByteArray &sourceText, const QByteArray &comment, const QStringList &translations)
const QByteArray & context() const
const QStringList & translations() const
const QByteArray & comment() const
TranslatorSaveMode m_saveMode
bool ignoreUnfinished() const
void insertIdBased(const TranslatorMessage &message, const QStringList &tlns)
Releaser(const QString &language)
bool save(QIODevice *iod)
void setDependencies(const QStringList &dependencies)
void squeeze(TranslatorSaveMode mode)
void setNumerusRules(const QByteArray &rules)
void insert(const TranslatorMessage &msg, const QStringList &tlns, bool forceComment)
void setPlural(bool isplural)
const TranslatorMessage & message(int i) const
static void registerFileFormat(const FileFormat &format)
void append(const TranslatorMessage &msg)
Combined button and popup list for selecting options.
static const int MagicLength
static quint32 read32(const uchar *data)
static bool containsStripped(const Translator &translator, const TranslatorMessage &msg)
bool loadQM(Translator &translator, QIODevice &dev, ConversionData &cd)
static void fromBytes(const char *str, int len, QString *out, bool *utf8Fail)
Q_DECLARE_TYPEINFO(ByteTranslatorMessage, Q_RELOCATABLE_TYPE)
static uint elfHash(const QByteArray &ba)
static quint8 read8(const uchar *data)
static const uchar magic[MagicLength]
Offset(uint hash, uint offset)
bool operator==(const Offset &other) const
bool operator<(const Offset &other) const
bool saveQM(const Translator &translator, QIODevice &dev, ConversionData &cd)