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
translator.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 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#include "simtexth.h"
7
8#include <iostream>
9
10#include <stdio.h>
11#ifdef Q_OS_WIN
12// required for _setmode, to avoid _O_TEXT streams...
13# include <io.h> // for _setmode
14# include <fcntl.h> // for _O_BINARY
15#endif
16
17#include <QtCore/QDebug>
18#include <QtCore/QDir>
19#include <QtCore/QFile>
20#include <QtCore/QFileInfo>
21#include <QtCore/QLocale>
22#include <QtCore/QTextStream>
23
24#include <private/qtranslator_p.h>
25
26QT_BEGIN_NAMESPACE
27
28Translator::Translator() :
29 m_locationsType(AbsoluteLocations),
30 m_indexOk(true)
31{
32}
33
35{
36 //qDebug() << "Translator: Registering format " << format.extension;
37 QList<Translator::FileFormat> &formats = registeredFileFormats();
38 for (int i = 0; i < formats.size(); ++i)
39 if (format.fileType == formats[i].fileType && format.priority < formats[i].priority) {
40 formats.insert(i, format);
41 return;
42 }
43 formats.append(format);
44}
45
46QList<Translator::FileFormat> &Translator::registeredFileFormats()
47{
48 static QList<Translator::FileFormat> theFormats;
49 return theFormats;
50}
51
52void Translator::addIndex(int idx, const TranslatorMessage &msg) const
53{
54 if (msg.sourceText().isEmpty() && msg.id().isEmpty()) {
55 m_ctxCmtIdx[msg.context()] = idx;
56 } else {
57 m_msgIdx[TMMKey(msg)] = idx;
58 if (!msg.id().isEmpty())
59 m_idMsgIdx[msg.id()] = idx;
60 }
61}
62
63void Translator::delIndex(int idx) const
64{
65 const TranslatorMessage &msg = m_messages.at(idx);
66 if (msg.sourceText().isEmpty() && msg.id().isEmpty()) {
67 m_ctxCmtIdx.remove(msg.context());
68 } else {
69 m_msgIdx.remove(TMMKey(msg));
70 if (!msg.id().isEmpty())
71 m_idMsgIdx.remove(msg.id());
72 }
73}
74
75void Translator::ensureIndexed() const
76{
77 if (!m_indexOk) {
78 m_indexOk = true;
79 m_ctxCmtIdx.clear();
80 m_idMsgIdx.clear();
81 m_msgIdx.clear();
82 for (int i = 0; i < m_messages.size(); i++)
83 addIndex(i, m_messages.at(i));
84 }
85}
86
88{
89 int index = find(msg);
90 if (index == -1) {
92 } else {
93 delIndex(index);
94 m_messages[index] = msg;
95 addIndex(index, msg);
96 }
97}
98
99static QString elidedId(const QString &id, int len)
100{
101 return id.size() <= len ? id : id.left(len - 5) + QLatin1String("[...]");
102}
103
105{
106 QString id = msg.context() + QLatin1String("//") + elidedId(msg.sourceText(), 100);
107 if (!msg.comment().isEmpty())
108 id += QLatin1String("//") + elidedId(msg.comment(), 30);
109 return id;
110}
111
113{
114 int index = find(msg);
115 if (index == -1) {
116 append(msg);
117 } else {
118 TranslatorMessage &emsg = m_messages[index];
119 if (emsg.sourceText().isEmpty()) {
120 delIndex(index);
121 emsg.setSourceText(msg.sourceText());
122 addIndex(index, msg);
123 } else if (!msg.sourceText().isEmpty() && emsg.sourceText() != msg.sourceText()) {
124 cd.appendError(QString::fromLatin1("Contradicting source strings for message with id '%1'.")
125 .arg(emsg.id()));
126 return;
127 }
128 if (emsg.extras().isEmpty()) {
130 } else if (!msg.extras().isEmpty() && emsg.extras() != msg.extras()) {
131 cd.appendError(QString::fromLatin1("Contradicting meta data for for %1.")
132 .arg(!emsg.id().isEmpty()
133 ? QString::fromLatin1("message with id '%1'").arg(emsg.id())
134 : QString::fromLatin1("message '%1'").arg(makeMsgId(msg))));
135 return;
136 }
137 emsg.addReferenceUniq(msg.fileName(), msg.lineNumber());
138 if (!msg.extraComment().isEmpty()) {
139 QString cmt = emsg.extraComment();
140 if (!cmt.isEmpty()) {
141 QStringList cmts = cmt.split(QLatin1String("\n----------\n"));
142 if (!cmts.contains(msg.extraComment())) {
143 cmts.append(msg.extraComment());
144 cmt = cmts.join(QLatin1String("\n----------\n"));
145 }
146 } else {
147 cmt = msg.extraComment();
148 }
149 emsg.setExtraComment(cmt);
150 }
151 }
152}
153
154void Translator::insert(int idx, const TranslatorMessage &msg)
155{
156 if (m_indexOk) {
157 if (idx == m_messages.size())
158 addIndex(idx, msg);
159 else
160 m_indexOk = false;
161 }
162 m_messages.insert(idx, msg);
163}
164
166{
167 insert(m_messages.size(), msg);
168}
169
171{
172 int msgLine = msg.lineNumber();
173 if (msgLine < 0) {
174 append(msg);
175 return;
176 }
177
178 int bestIdx = 0; // Best insertion point found so far
179 int bestScore = 0; // Its category: 0 = no hit, 1 = pre or post, 2 = middle
180 int bestSize = 0; // The length of the region. Longer is better within one category.
181
182 // The insertion point to use should this region turn out to be the best one so far
183 int thisIdx = 0;
184 int thisScore = 0;
185 int thisSize = 0;
186 // Working vars
187 int prevLine = 0;
188 int curIdx = 0;
189 for (const TranslatorMessage &mit : std::as_const(m_messages)) {
190 bool sameFile = mit.fileName() == msg.fileName() && mit.context() == msg.context();
191 int curLine;
192 if (sameFile && (curLine = mit.lineNumber()) >= prevLine) {
193 if (msgLine >= prevLine && msgLine < curLine) {
194 thisIdx = curIdx;
195 thisScore = thisSize ? 2 : 1;
196 }
197 ++thisSize;
198 prevLine = curLine;
199 } else {
200 if (thisSize) {
201 if (!thisScore) {
202 thisIdx = curIdx;
203 thisScore = 1;
204 }
205 if (thisScore > bestScore || (thisScore == bestScore && thisSize > bestSize)) {
206 bestIdx = thisIdx;
207 bestScore = thisScore;
208 bestSize = thisSize;
209 }
210 thisScore = 0;
211 thisSize = sameFile ? 1 : 0;
212 prevLine = 0;
213 }
214 }
215 ++curIdx;
216 }
217 if (thisSize && !thisScore) {
218 thisIdx = curIdx;
219 thisScore = 1;
220 }
221 if (thisScore > bestScore || (thisScore == bestScore && thisSize > bestSize))
222 insert(thisIdx, msg);
223 else if (bestScore)
224 insert(bestIdx, msg);
225 else
226 append(msg);
227}
228
229static QString guessFormat(const QString &filename, const QString &format)
230{
231 if (format != QLatin1String("auto"))
232 return format;
233
234 for (const Translator::FileFormat &fmt : std::as_const(Translator::registeredFileFormats())) {
235 if (filename.endsWith(QLatin1Char('.') + fmt.extension, Qt::CaseInsensitive))
236 return fmt.extension;
237 }
238
239 // the default format.
240 // FIXME: change to something more widely distributed later.
241 return QLatin1String("ts");
242}
243
244bool Translator::load(const QString &filename, ConversionData &cd, const QString &format)
245{
246 cd.m_sourceDir = QFileInfo(filename).absoluteDir();
247 cd.m_sourceFileName = filename;
248
249 QFile file;
250 if (filename.isEmpty() || filename == QLatin1String("-")) {
251#ifdef Q_OS_WIN
252 // QFile is broken for text files
253 ::_setmode(0, _O_BINARY);
254#endif
255 if (!file.open(stdin, QIODevice::ReadOnly)) {
256 cd.appendError(QString::fromLatin1("Cannot open stdin!? (%1)")
257 .arg(file.errorString()));
258 return false;
259 }
260 } else {
261 file.setFileName(filename);
262 if (!file.open(QIODevice::ReadOnly)) {
263 cd.appendError(QString::fromLatin1("Cannot open %1: %2")
264 .arg(filename, file.errorString()));
265 return false;
266 }
267 }
268
269 QString fmt = guessFormat(filename, format);
270
271 for (const FileFormat &format : std::as_const(registeredFileFormats())) {
272 if (fmt == format.extension) {
273 if (format.loader)
274 return (*format.loader)(*this, file, cd);
275 cd.appendError(QString(QLatin1String("No loader for format %1 found"))
276 .arg(fmt));
277 return false;
278 }
279 }
280
281 cd.appendError(QString(QLatin1String("Unknown format %1 for file %2"))
282 .arg(format, filename));
283 return false;
284}
285
286
287bool Translator::save(const QString &filename, ConversionData &cd, const QString &format) const
288{
289 QFile file;
290 if (filename.isEmpty() || filename == QLatin1String("-")) {
291#ifdef Q_OS_WIN
292 // QFile is broken for text files
293 ::_setmode(1, _O_BINARY);
294#endif
295 if (!file.open(stdout, QIODevice::WriteOnly)) {
296 cd.appendError(QString::fromLatin1("Cannot open stdout!? (%1)")
297 .arg(file.errorString()));
298 return false;
299 }
300 } else {
301 file.setFileName(filename);
302 if (!file.open(QIODevice::WriteOnly)) {
303 cd.appendError(QString::fromLatin1("Cannot create %1: %2")
304 .arg(filename, file.errorString()));
305 return false;
306 }
307 }
308
309 QString fmt = guessFormat(filename, format);
310 cd.m_targetDir = QFileInfo(filename).absoluteDir();
311
312 for (const FileFormat &format : std::as_const(registeredFileFormats())) {
313 if (fmt == format.extension) {
314 if (format.saver)
315 return (*format.saver)(*this, file, cd);
316 cd.appendError(QString(QLatin1String("Cannot save %1 files")).arg(fmt));
317 return false;
318 }
319 }
320
321 cd.appendError(QString(QLatin1String("Unknown format %1 for file %2"))
322 .arg(format).arg(filename));
323 return false;
324}
325
326QString Translator::makeLanguageCode(QLocale::Language language, QLocale::Territory territory)
327{
328 QString result = QLocale::languageToCode(language);
329 if (language != QLocale::C && territory != QLocale::AnyTerritory) {
330 result.append(QLatin1Char('_'));
331 result.append(QLocale::territoryToCode(territory));
332 }
333 return result;
334}
335
336void Translator::languageAndTerritory(QStringView languageCode, QLocale::Language *langPtr,
337 QLocale::Territory *territoryPtr)
338{
339 QLocale::Language language = QLocale::AnyLanguage;
340 QLocale::Territory territory = QLocale::AnyTerritory;
341 auto separator = languageCode.indexOf(u'_'); // "de_DE"
342 if (separator == -1) {
343 // compatibility with older .ts files
344 separator = languageCode.indexOf(u'-'); // "de-DE"
345 }
346 if (separator != -1) {
347 language = QLocale::codeToLanguage(languageCode.left(separator));
348 territory = QLocale::codeToTerritory(languageCode.mid(separator + 1));
349 } else {
350 language = QLocale::codeToLanguage(languageCode);
351 territory = QLocale(language).territory();
352 }
353
354 if (langPtr)
355 *langPtr = language;
356 if (territoryPtr)
357 *territoryPtr = territory;
358}
359
360int Translator::find(const TranslatorMessage &msg) const
361{
362 ensureIndexed();
363 if (msg.id().isEmpty())
364 return m_msgIdx.value(TMMKey(msg), -1);
365 int i = m_idMsgIdx.value(msg.id(), -1);
366 if (i >= 0)
367 return i;
368 i = m_msgIdx.value(TMMKey(msg), -1);
369 // If both have an id, then find only by id.
370 return i >= 0 && m_messages.at(i).id().isEmpty() ? i : -1;
371}
372
373int Translator::find(const QString &context,
374 const QString &comment, const TranslatorMessage::References &refs) const
375{
376 if (!refs.isEmpty()) {
377 for (auto it = m_messages.cbegin(), end = m_messages.cend(); it != end; ++it) {
378 if (it->context() == context && it->comment() == comment) {
379 for (const auto &itref : it->allReferences()) {
380 for (const auto &ref : refs) {
381 if (itref == ref)
382 return it - m_messages.cbegin();
383 }
384 }
385 }
386 }
387 }
388 return -1;
389}
390
391int Translator::find(const QString &context) const
392{
393 ensureIndexed();
394 return m_ctxCmtIdx.value(context, -1);
395}
396
398{
399 for (auto it = m_messages.begin(); it != m_messages.end(); )
400 if (it->type() == TranslatorMessage::Obsolete || it->type() == TranslatorMessage::Vanished)
401 it = m_messages.erase(it);
402 else
403 ++it;
404 m_indexOk = false;
405}
406
408{
409 for (auto it = m_messages.begin(); it != m_messages.end(); )
410 if (it->type() == TranslatorMessage::Finished)
411 it = m_messages.erase(it);
412 else
413 ++it;
414 m_indexOk = false;
415}
416
418{
419 for (auto it = m_messages.begin(); it != m_messages.end(); )
420 if (!it->isTranslated())
421 it = m_messages.erase(it);
422 else
423 ++it;
424 m_indexOk = false;
425}
426
428{
429 for (const auto &message : m_messages) {
430 if (message.isTranslated())
431 return true;
432 }
433 return false;
434}
435
437{
438 for (auto it = m_messages.begin(); it != m_messages.end(); )
439 if (it->sourceText() == QLatin1String(ContextComment))
440 it = m_messages.erase(it);
441 else
442 ++it;
443 m_indexOk = false;
444}
445
447{
448 for (auto it = m_messages.begin(); it != m_messages.end(); )
449 if (!it->isPlural())
450 it = m_messages.erase(it);
451 else
452 ++it;
453 m_indexOk = false;
454}
455
457{
458 for (auto it = m_messages.begin(); it != m_messages.end(); ) {
459 // we need to have just one translation, and it be equal to the source
460 if (it->translations().size() == 1 && it->translation() == it->sourceText())
461 it = m_messages.erase(it);
462 else
463 ++it;
464 }
465 m_indexOk = false;
466}
467
469{
470 for (auto &message : m_messages) {
471 if (message.type() == TranslatorMessage::Finished)
472 message.setType(TranslatorMessage::Unfinished);
473 message.setTranslation(QString());
474 }
475}
476
478{
479 const QString uiXt = QLatin1String(".ui");
480 const QString juiXt = QLatin1String(".jui");
481 for (auto &message : m_messages) {
482 QHash<QString, int> have;
483 QList<TranslatorMessage::Reference> refs;
484 for (const auto &itref : message.allReferences()) {
485 const QString &fn = itref.fileName();
486 if (fn.endsWith(uiXt) || fn.endsWith(juiXt)) {
487 if (++have[fn] == 1)
488 refs.append(TranslatorMessage::Reference(fn, -1));
489 } else {
490 refs.append(itref);
491 }
492 }
493 message.setReferences(refs);
494 }
495}
496
498{
499public:
500 explicit TranslatorMessagePtrBase(const Translator *tor, int messageIndex)
501 : tor(tor), messageIndex(messageIndex)
502 {
503 }
504
505 inline const TranslatorMessage *operator->() const
506 {
508 }
509
511 const int messageIndex;
512};
513
515{
516public:
517 using TranslatorMessagePtrBase::TranslatorMessagePtrBase;
518};
519
521
523{
524 return qHash(tmp->id());
525}
526
528{
529 return tmp1->id() == tmp2->id();
530}
531
533{
534public:
535 using TranslatorMessagePtrBase::TranslatorMessagePtrBase;
536};
537
539
541{
542 size_t hash = qHash(tmp->context()) ^ qHash(tmp->sourceText());
543 if (!tmp->sourceText().isEmpty())
544 // Special treatment for context comments (empty source).
545 hash ^= qHash(tmp->comment());
546 return hash;
547}
548
550{
551 if (tmp1->context() != tmp2->context() || tmp1->sourceText() != tmp2->sourceText())
552 return false;
553 // Special treatment for context comments (empty source).
554 if (tmp1->sourceText().isEmpty())
555 return true;
556 return tmp1->comment() == tmp2->comment();
557}
558
560{
561 Duplicates dups;
562 QSet<TranslatorMessageIdPtr> idRefs;
563 QSet<TranslatorMessageContentPtr> contentRefs;
564 for (int i = 0; i < m_messages.size();) {
565 const TranslatorMessage &msg = m_messages.at(i);
566 TranslatorMessage *omsg;
567 int oi;
568 DuplicateEntries *pDup;
569 if (!msg.id().isEmpty()) {
570 const auto it = idRefs.constFind(TranslatorMessageIdPtr(this, i));
571 if (it != idRefs.constEnd()) {
572 oi = it->messageIndex;
573 omsg = &m_messages[oi];
574 pDup = &dups.byId;
575 goto gotDupe;
576 }
577 }
578 {
579 const auto it = contentRefs.constFind(TranslatorMessageContentPtr(this, i));
580 if (it != contentRefs.constEnd()) {
581 oi = it->messageIndex;
582 omsg = &m_messages[oi];
583 if (msg.id().isEmpty() || omsg->id().isEmpty()) {
584 if (!msg.id().isEmpty() && omsg->id().isEmpty()) {
585 omsg->setId(msg.id());
586 idRefs.insert(TranslatorMessageIdPtr(this, oi));
587 }
588 pDup = &dups.byContents;
589 goto gotDupe;
590 }
591 // This is really a content dupe, but with two distinct IDs.
592 }
593 }
594 if (!msg.id().isEmpty())
595 idRefs.insert(TranslatorMessageIdPtr(this, i));
596 contentRefs.insert(TranslatorMessageContentPtr(this, i));
597 ++i;
598 continue;
599 gotDupe:
600 (*pDup)[oi].append(msg.tsLineNumber());
601 if (!omsg->isTranslated() && msg.isTranslated())
602 omsg->setTranslations(msg.translations());
603 m_indexOk = false;
604 m_messages.removeAt(i);
605 }
606 return dups;
607}
608
610 const QString &fileName, bool verbose)
611{
612 if (!dupes.byId.isEmpty() || !dupes.byContents.isEmpty()) {
613 std::cerr << "Warning: dropping duplicate messages in '" << qPrintable(fileName);
614 if (!verbose) {
615 std::cerr << "'\n(try -verbose for more info).\n";
616 } else {
617 std::cerr << "':\n";
618 for (auto it = dupes.byId.begin(); it != dupes.byId.end(); ++it) {
619 const TranslatorMessage &msg = message(it.key());
620 std::cerr << "\n* ID: " << qPrintable(msg.id()) << std::endl;
621 reportDuplicatesLines(msg, it.value());
622 }
623 for (auto it = dupes.byContents.begin(); it != dupes.byContents.end(); ++it) {
624 const TranslatorMessage &msg = message(it.key());
625 std::cerr << "\n* Context: " << qPrintable(msg.context())
626 << "\n* Source: " << qPrintable(msg.sourceText()) << std::endl;
627 if (!msg.comment().isEmpty())
628 std::cerr << "* Comment: " << qPrintable(msg.comment()) << std::endl;
629 reportDuplicatesLines(msg, it.value());
630 }
631 std::cerr << std::endl;
632 }
633 }
634}
635
637 const DuplicateEntries::value_type &dups) const
638{
639 if (msg.tsLineNumber() >= 0) {
640 std::cerr << "* Line in .ts file: " << msg.tsLineNumber() << std::endl;
641 for (int tsLineNumber : dups) {
642 if (tsLineNumber >= 0)
643 std::cerr << "* Duplicate at line: " << tsLineNumber << std::endl;
644 }
645 }
646}
647
648// Used by lupdate to be able to search using absolute paths during merging
649void Translator::makeFileNamesAbsolute(const QDir &originalPath)
650{
651 for (auto &msg : m_messages) {
652 const TranslatorMessage::References refs = msg.allReferences();
653 msg.setReferences(TranslatorMessage::References());
654 for (const TranslatorMessage::Reference &ref : refs) {
655 QString fileName = ref.fileName();
656 QFileInfo fi (fileName);
657 if (fi.isRelative())
658 fileName = originalPath.absoluteFilePath(fileName);
659 msg.addReference(fileName, ref.lineNumber());
660 }
661 }
662}
663
665{
666 return m_messages;
667}
668
669QStringList Translator::normalizedTranslations(const TranslatorMessage &msg, int numPlurals)
670{
671 QStringList translations = msg.translations();
672 int numTranslations = msg.isPlural() ? numPlurals : 1;
673
674 // make sure that the stringlist always have the size of the
675 // language's current numerus, or 1 if its not plural
676 if (translations.size() > numTranslations) {
677 for (int i = translations.size(); i > numTranslations; --i)
678 translations.removeLast();
679 } else if (translations.size() < numTranslations) {
680 for (int i = translations.size(); i < numTranslations; ++i)
681 translations.append(QString());
682 }
683 return translations;
684}
685
687{
688 bool truncated = false;
689 QLocale::Language l;
690 QLocale::Territory c;
691 languageAndTerritory(languageCode(), &l, &c);
692 int numPlurals = 1;
693 if (l != QLocale::C) {
694 QStringList forms;
695 if (getNumerusInfo(l, c, 0, &forms, 0))
696 numPlurals = forms.size(); // includes singular
697 }
698 for (int i = 0; i < m_messages.size(); ++i) {
699 const TranslatorMessage &msg = m_messages.at(i);
700 QStringList tlns = msg.translations();
701 int ccnt = msg.isPlural() ? numPlurals : 1;
702 if (tlns.size() != ccnt) {
703 while (tlns.size() < ccnt)
704 tlns.append(QString());
705 while (tlns.size() > ccnt) {
706 tlns.removeLast();
707 truncated = true;
708 }
709 m_messages[i].setTranslations(tlns);
710 }
711 }
712 if (truncated)
713 cd.appendError(QLatin1String(
714 "Removed plural forms as the target language has less "
715 "forms.\nIf this sounds wrong, possibly the target language is "
716 "not set or recognized."));
717}
718
719QString Translator::guessLanguageCodeFromFileName(const QString &filename)
720{
721 QString str = filename;
722 for (const FileFormat &format : std::as_const(registeredFileFormats())) {
723 if (str.endsWith(format.extension)) {
724 str = str.left(str.size() - format.extension.size() - 1);
725 break;
726 }
727 }
728 static QRegularExpression re(QLatin1String("[\\._]"));
729 while (true) {
730 QLocale locale(str);
731 //qDebug() << "LANGUAGE FROM " << str << "LANG: " << locale.language();
732 if (locale.language() != QLocale::C) {
733 //qDebug() << "FOUND " << locale.name();
734 return locale.name();
735 }
736 int pos = str.indexOf(re);
737 if (pos == -1)
738 break;
739 str = str.mid(pos + 1);
740 }
741 //qDebug() << "LANGUAGE GUESSING UNSUCCESSFUL";
742 return QString();
743}
744
745bool Translator::hasExtra(const QString &key) const
746{
747 return m_extra.contains(key);
748}
749
750QString Translator::extra(const QString &key) const
751{
752 return m_extra[key];
753}
754
755void Translator::setExtra(const QString &key, const QString &value)
756{
757 m_extra[key] = value;
758}
759
760void Translator::dump() const
761{
762 for (int i = 0; i != messageCount(); ++i)
764}
765
766QT_END_NAMESPACE
const TranslatorMessage * operator->() const
TranslatorMessagePtrBase(const Translator *tor, int messageIndex)
const Translator * tor
QList< Reference > References
bool isTranslated() const
void setExtras(const ExtraData &extras)
const ExtraData & extras() const
void stripObsoleteMessages()
void dropTranslations()
bool save(const QString &filename, ConversionData &err, const QString &format) const
bool load(const QString &filename, ConversionData &err, const QString &format)
void dropUiLines()
void dump() const
void stripEmptyContexts()
void stripUntranslatedMessages()
void stripFinishedMessages()
void replaceSorted(const TranslatorMessage &msg)
void stripNonPluralForms()
void setExtra(const QString &ba, const QString &var)
const TranslatorMessage & message(int i) const
Definition translator.h:148
int find(const QString &context) const
QString extra(const QString &ba) const
static void registerFileFormat(const FileFormat &format)
void append(const TranslatorMessage &msg)
void stripIdenticalSourceTranslations()
int messageCount() const
Definition translator.h:146
void makeFileNamesAbsolute(const QDir &originalPath)
bool translationsExist() const
Duplicates resolveDuplicates()
bool hasExtra(const QString &ba) const
int find(const QString &context, const QString &comment, const TranslatorMessage::References &refs) const
void normalizeTranslations(ConversionData &cd)
void appendSorted(const TranslatorMessage &msg)
void reportDuplicatesLines(const TranslatorMessage &msg, const DuplicateEntries::value_type &dups) const
int find(const TranslatorMessage &msg) const
void reportDuplicates(const Duplicates &dupes, const QString &fileName, bool verbose)
const QList< TranslatorMessage > & messages() const
void extend(const TranslatorMessage &msg, ConversionData &cd)
size_t qHash(TranslatorMessageIdPtr tmp)
Q_DECLARE_TYPEINFO(TranslatorMessageIdPtr, Q_RELOCATABLE_TYPE)
static QString guessFormat(const QString &filename, const QString &format)
size_t qHash(TranslatorMessageContentPtr tmp)
static QString elidedId(const QString &id, int len)
static QString makeMsgId(const TranslatorMessage &msg)
Q_DECLARE_TYPEINFO(TranslatorMessageContentPtr, Q_RELOCATABLE_TYPE)
bool operator==(TranslatorMessageContentPtr tmp1, TranslatorMessageContentPtr tmp2)
bool operator==(TranslatorMessageIdPtr tmp1, TranslatorMessageIdPtr tmp2)
#define ContextComment
Definition translator.h:229