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