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