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