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 const QString fmt = guessFormat(filename, format);
319
320 const FileFormat *handler = nullptr;
321 for (const FileFormat &f : std::as_const(registeredFileFormats())) {
322 if (fmt == f.extension) {
323 handler = &f;
324 break;
325 }
326 }
327 if (!handler) {
328 cd.appendError(QString("Unknown format %1 for file %2"_L1).arg(format, filename));
329 return false;
330 }
331 if (!handler->saver) {
332 cd.appendError(QString("Saving '%1' files is not supported"_L1).arg(fmt));
333 return false;
334 }
335
336 QFile file;
337 if (filename.isEmpty() || filename == "-"_L1) {
338#ifdef Q_OS_WIN
339 // QFile is broken for text files
340 ::_setmode(1, _O_BINARY);
341#endif
342 if (!file.open(stdout, QIODevice::WriteOnly)) {
343 cd.appendError(QString::fromLatin1("Cannot open stdout!? (%1)")
344 .arg(file.errorString()));
345 return false;
346 }
347 } else {
348 file.setFileName(filename);
349 QIODevice::OpenMode mode = QIODevice::WriteOnly;
350#ifdef Q_OS_WIN
351 // Preserve CRLF line endings on Windows for text formats
352 if (handler->fileType != FileFormat::TranslationBinary && usesCRLF(filename))
353 mode |= QIODevice::Text;
354#endif
355 if (!file.open(mode)) {
356 cd.appendError(QString::fromLatin1("Cannot create %1: %2")
357 .arg(filename, file.errorString()));
358 return false;
359 }
360 }
361
362 cd.m_targetDir = QFileInfo(filename).absoluteDir();
363
364 if (fmt != u"ts" && m_locationsType == RelativeLocations) {
365 std::cerr << "Warning: relative locations are not supported for non TS files. "
366 "File "
367 << qPrintable(filename)
368 << " will be generated with the "
369 "default location type."
370 << std::endl;
371 }
372
373 return (*handler->saver)(*this, file, cd);
374}
375
376QString Translator::makeLanguageCode(QLocale::Language language, QLocale::Territory territory)
377{
378 QString result = QLocale::languageToCode(language);
379 if (language != QLocale::C && territory != QLocale::AnyTerritory) {
380 result.append(u'_');
381 result.append(QLocale::territoryToCode(territory));
382 }
383 return result;
384}
385
386void Translator::languageAndTerritory(QStringView languageCode, QLocale::Language *langPtr,
387 QLocale::Territory *territoryPtr)
388{
389 QLocale::Language language = QLocale::AnyLanguage;
390 QLocale::Territory territory = QLocale::AnyTerritory;
391 auto separator = languageCode.indexOf(u'_'); // "de_DE"
392 if (separator == -1) {
393 // compatibility with older .ts files
394 separator = languageCode.indexOf(u'-'); // "de-DE"
395 }
396 if (separator != -1) {
397 language = QLocale::codeToLanguage(languageCode.left(separator));
398 territory = QLocale::codeToTerritory(languageCode.mid(separator + 1));
399 } else {
400 language = QLocale::codeToLanguage(languageCode);
401 territory = QLocale(language).territory();
402 }
403
404 if (langPtr)
405 *langPtr = language;
406 if (territoryPtr)
407 *territoryPtr = territory;
408}
409
410int Translator::find(const TranslatorMessage &msg) const
411{
412 ensureIndexed();
413 if (msg.id().isEmpty())
414 return m_msgIdx.value(TMMKey(msg), -1);
415 int i = m_idMsgIdx.value(msg.id(), -1);
416 if (i >= 0)
417 return i;
418 i = m_msgIdx.value(TMMKey(msg), -1);
419 // If both have an id, then find only by id.
420 return i >= 0 && m_messages.at(i).id().isEmpty() ? i : -1;
421}
422
423int Translator::find(const QString &context,
424 const QString &comment, const TranslatorMessage::References &refs) const
425{
426 if (!refs.isEmpty()) {
427 for (auto it = m_messages.cbegin(), end = m_messages.cend(); it != end; ++it) {
428 if (it->context() == context && it->comment() == comment) {
429 for (const auto &itref : it->allReferences()) {
430 for (const auto &ref : refs) {
431 if (itref == ref)
432 return it - m_messages.cbegin();
433 }
434 }
435 }
436 }
437 }
438 return -1;
439}
440
442{
443 for (auto it = m_messages.begin(); it != m_messages.end(); )
444 if (it->type() == TranslatorMessage::Obsolete || it->type() == TranslatorMessage::Vanished)
445 it = m_messages.erase(it);
446 else
447 ++it;
448 m_indexOk = false;
449}
450
452{
453 for (auto it = m_messages.begin(); it != m_messages.end(); )
454 if (it->type() == TranslatorMessage::Finished)
455 it = m_messages.erase(it);
456 else
457 ++it;
458 m_indexOk = false;
459}
460
462{
463 for (auto it = m_messages.begin(); it != m_messages.end(); )
464 if (!it->isTranslated())
465 it = m_messages.erase(it);
466 else
467 ++it;
468 m_indexOk = false;
469}
470
472{
473 return std::any_of(m_messages.cbegin(), m_messages.cend(),
474 [](const auto &m) { return m.isTranslated(); });
475}
476
478{
479 return std::any_of(m_messages.cbegin(), m_messages.cend(),
480 [](const auto &m) { return m.type() == TranslatorMessage::Unfinished; });
481}
482
484{
485 for (auto it = m_messages.begin(); it != m_messages.end(); )
486 if (it->sourceText() == QLatin1String(ContextComment))
487 it = m_messages.erase(it);
488 else
489 ++it;
490 m_indexOk = false;
491}
492
494{
495 for (auto it = m_messages.begin(); it != m_messages.end(); )
496 if (!it->isPlural())
497 it = m_messages.erase(it);
498 else
499 ++it;
500 m_indexOk = false;
501}
502
504{
505 for (auto it = m_messages.begin(); it != m_messages.end(); ) {
506 // we need to have just one translation, and it be equal to the source
507 if (it->translations().size() == 1 && it->translation() == it->sourceText())
508 it = m_messages.erase(it);
509 else
510 ++it;
511 }
512 m_indexOk = false;
513}
514
516{
517 for (auto &message : m_messages) {
518 if (message.type() == TranslatorMessage::Finished)
519 message.setType(TranslatorMessage::Unfinished);
520 message.setTranslation(QString());
521 }
522}
523
525{
526 const QString uiXt = ".ui"_L1;
527 const QString juiXt = ".jui"_L1;
528 for (auto &message : m_messages) {
529 QHash<QString, int> have;
530 QList<TranslatorMessage::Reference> refs;
531 for (const auto &itref : message.allReferences()) {
532 const QString &fn = itref.fileName();
533 if (fn.endsWith(uiXt) || fn.endsWith(juiXt)) {
534 if (++have[fn] == 1)
535 refs.append(TranslatorMessage::Reference(fn, -1, -1, -1));
536 } else {
537 refs.append(itref);
538 }
539 }
540 message.setReferences(refs);
541 }
542}
543
545{
546public:
547 explicit TranslatorMessagePtrBase(const Translator *tor, int messageIndex)
548 : tor(tor), messageIndex(messageIndex)
549 {
550 }
551
552 inline const TranslatorMessage *operator->() const
553 {
555 }
556
558 const int messageIndex;
559};
560
562{
563public:
564 using TranslatorMessagePtrBase::TranslatorMessagePtrBase;
565};
566
568
569inline size_t qHash(TranslatorMessageIdPtr tmp, size_t seed = 0)
570{
571 return qHash(tmp->id(), seed);
572}
573
575{
576 return tmp1->id() == tmp2->id();
577}
578
580{
581public:
582 using TranslatorMessagePtrBase::TranslatorMessagePtrBase;
583};
584
586
587inline size_t qHash(TranslatorMessageContentPtr tmp, size_t seed = 0)
588{
589 size_t hash = qHash(tmp->context(), seed) ^ qHash(tmp->sourceText(), seed);
590 if (!tmp->sourceText().isEmpty())
591 // Special treatment for context comments (empty source).
592 hash ^= qHash(tmp->comment(), seed);
593 return hash;
594}
595
597{
598 if (tmp1->context() != tmp2->context() || tmp1->sourceText() != tmp2->sourceText())
599 return false;
600 // Special treatment for context comments (empty source).
601 if (tmp1->sourceText().isEmpty())
602 return true;
603 return tmp1->comment() == tmp2->comment();
604}
605
607{
608 Duplicates dups;
609 QSet<TranslatorMessageIdPtr> idRefs;
610 QSet<TranslatorMessageContentPtr> contentRefs;
611 for (int i = 0; i < m_messages.size();) {
612 const TranslatorMessage &msg = m_messages.at(i);
613 TranslatorMessage *omsg;
614 int oi;
615 DuplicateEntries *pDup;
616 if (!msg.id().isEmpty()) {
617 const auto it = idRefs.constFind(TranslatorMessageIdPtr(this, i));
618 if (it != idRefs.constEnd()) {
619 oi = it->messageIndex;
620 omsg = &m_messages[oi];
621 pDup = &dups.byId;
622 goto gotDupe;
623 }
624 }
625 {
626 const auto it = contentRefs.constFind(TranslatorMessageContentPtr(this, i));
627 if (it != contentRefs.constEnd()) {
628 oi = it->messageIndex;
629 omsg = &m_messages[oi];
630 if (msg.id().isEmpty() || omsg->id().isEmpty()) {
631 if (!msg.id().isEmpty() && omsg->id().isEmpty()) {
632 omsg->setId(msg.id());
633 idRefs.insert(TranslatorMessageIdPtr(this, oi));
634 }
635 pDup = &dups.byContents;
636 goto gotDupe;
637 }
638 // This is really a content dupe, but with two distinct IDs.
639 }
640 }
641 if (!msg.id().isEmpty())
642 idRefs.insert(TranslatorMessageIdPtr(this, i));
643 contentRefs.insert(TranslatorMessageContentPtr(this, i));
644 ++i;
645 continue;
646 gotDupe:
647 (*pDup)[oi].append(msg.tsLineNumber());
648 if (!omsg->isTranslated() && msg.isTranslated())
649 omsg->setTranslations(msg.translations());
650 m_indexOk = false;
651 m_messages.removeAt(i);
652 }
653 return dups;
654}
655
657 const QString &fileName, bool verbose)
658{
659 if (!dupes.byId.isEmpty() || !dupes.byContents.isEmpty()) {
660 std::cerr << "Warning: dropping duplicate messages in '" << qPrintable(fileName);
661 if (!verbose) {
662 std::cerr << "'\n(try -verbose for more info).\n";
663 } else {
664 std::cerr << "':\n";
665 for (auto it = dupes.byId.begin(); it != dupes.byId.end(); ++it) {
666 const TranslatorMessage &msg = message(it.key());
667 std::cerr << "\n* ID: " << qPrintable(msg.id()) << std::endl;
668 reportDuplicatesLines(msg, it.value());
669 }
670 for (auto it = dupes.byContents.begin(); it != dupes.byContents.end(); ++it) {
671 const TranslatorMessage &msg = message(it.key());
672 std::cerr << "\n* Context: " << qPrintable(msg.context())
673 << "\n* Source: " << qPrintable(msg.sourceText()) << std::endl;
674 if (!msg.comment().isEmpty())
675 std::cerr << "* Comment: " << qPrintable(msg.comment()) << std::endl;
676 reportDuplicatesLines(msg, it.value());
677 }
678 std::cerr << std::endl;
679 }
680 }
681}
682
684 const DuplicateEntries::value_type &dups) const
685{
686 if (msg.tsLineNumber() >= 0) {
687 std::cerr << "* Line in .ts file: " << msg.tsLineNumber() << std::endl;
688 for (int tsLineNumber : dups) {
689 if (tsLineNumber >= 0)
690 std::cerr << "* Duplicate at line: " << tsLineNumber << std::endl;
691 }
692 }
693}
694
695// Used by lupdate to be able to search using absolute paths during merging
696void Translator::makeFileNamesAbsolute(const QDir &originalPath)
697{
698 for (auto &msg : m_messages) {
699 const TranslatorMessage::References refs = msg.allReferences();
700 msg.setReferences(TranslatorMessage::References());
701 for (const TranslatorMessage::Reference &ref : refs) {
702 QString fileName = ref.fileName();
703 QFileInfo fi (fileName);
704 if (fi.isRelative())
705 fileName = originalPath.absoluteFilePath(fileName);
706 msg.addReference(fileName, ref.lineNumber(), ref.startOffset(), ref.endOffset());
707 }
708 }
709}
710
712{
713 return m_messages;
714}
715
716QStringList Translator::normalizedTranslations(const TranslatorMessage &msg, int numPlurals)
717{
718 QStringList translations = msg.translations();
719 int numTranslations = msg.isPlural() ? numPlurals : 1;
720
721 // make sure that the stringlist always have the size of the
722 // language's current numerus, or 1 if its not plural
723 if (translations.size() > numTranslations) {
724 for (int i = translations.size(); i > numTranslations; --i)
725 translations.removeLast();
726 } else if (translations.size() < numTranslations) {
727 for (int i = translations.size(); i < numTranslations; ++i)
728 translations.append(QString());
729 }
730 return translations;
731}
732
734{
735 bool truncated = false;
736 QLocale::Language l;
737 QLocale::Territory c;
738 languageAndTerritory(languageCode(), &l, &c);
739 int numPlurals = 1;
740 if (l != QLocale::C) {
741 QStringList forms;
742 if (getNumerusInfo(l, c, 0, &forms, 0))
743 numPlurals = forms.size(); // includes singular
744 }
745 for (int i = 0; i < m_messages.size(); ++i) {
746 const TranslatorMessage &msg = m_messages.at(i);
747 QStringList tlns = msg.translations();
748 int ccnt = msg.isPlural() ? numPlurals : 1;
749 if (tlns.size() != ccnt) {
750 while (tlns.size() < ccnt)
751 tlns.append(QString());
752 while (tlns.size() > ccnt) {
753 tlns.removeLast();
754 truncated = true;
755 }
756 m_messages[i].setTranslations(tlns);
757 }
758 }
759 if (truncated)
760 cd.appendError(QLatin1String(
761 "Removed plural forms as the target language has less "
762 "forms.\nIf this sounds wrong, possibly the target language is "
763 "not set or recognized."));
764}
765
766QString Translator::guessLanguageCodeFromFileName(const QString &filename)
767{
768 QString str = filename;
769 for (const FileFormat &format : std::as_const(registeredFileFormats())) {
770 if (str.endsWith(format.extension)) {
771 str = str.left(str.size() - format.extension.size() - 1);
772 break;
773 }
774 }
775 static QRegularExpression re("[\\._]"_L1);
776 while (true) {
777 QLocale locale(str);
778 //qDebug() << "LANGUAGE FROM " << str << "LANG: " << locale.language();
779 if (locale.language() != QLocale::C) {
780 //qDebug() << "FOUND " << locale.name();
781 return locale.name();
782 }
783 int pos = str.indexOf(re);
784 if (pos == -1)
785 break;
786 str = str.mid(pos + 1);
787 }
788 //qDebug() << "LANGUAGE GUESSING UNSUCCESSFUL";
789 return QString();
790}
791
792void Translator::appendDependencies(const QStringList &dependencies)
793{
794 QStringList mergeDeps;
795 for (const QString &dep : dependencies) {
796 if (const auto it = std::find(m_dependencies.cbegin(), m_dependencies.cend(), dep);
797 it == m_dependencies.cend()) {
798 mergeDeps.append(dep);
799 }
800 }
801 m_dependencies.append(mergeDeps);
802}
803
804void Translator::satisfyDependency(const QString &file, const QString &format)
805{
806 const auto dep = getDependencyName(file, format);
807 if (const auto it = std::find(m_dependencies.cbegin(), m_dependencies.cend(), dep);
808 it != m_dependencies.cend()) {
809 m_dependencies.erase(it);
810 }
811}
812
813bool Translator::hasExtra(const QString &key) const
814{
815 return m_extra.contains(key);
816}
817
818QString Translator::extra(const QString &key) const
819{
820 return m_extra[key];
821}
822
823void Translator::setExtra(const QString &key, const QString &value)
824{
825 m_extra[key] = value;
826}
827
828void Translator::dump() const
829{
830 for (int i = 0; i != messageCount(); ++i)
832}
833
834QT_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:139
QString extra(const QString &ba) const
static void registerFileFormat(const FileFormat &format)
void append(const TranslatorMessage &msg)
@ RelativeLocations
Definition translator.h:122
@ AbsoluteLocations
Definition translator.h:122
void stripIdenticalSourceTranslations()
int messageCount() const
Definition translator.h:137
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)
Q_DECLARE_TYPEINFO(TranslatorMessageIdPtr, Q_RELOCATABLE_TYPE)
static QString guessFormat(const QString &filename, const QString &format)
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)
QString friendlyString(const QString &str)
#define ContextComment
Definition translator.h:224