17#include <QtCore/QDebug>
19#include <QtCore/QFile>
20#include <QtCore/QFileInfo>
21#include <QtCore/QLocale>
22#include <QtCore/QTextStream>
24#include <private/qtranslator_p.h>
28Translator::Translator() :
29 m_locationsType(AbsoluteLocations),
37 QList<Translator::FileFormat> &formats = registeredFileFormats();
38 for (
int i = 0; i < formats.size(); ++i)
39 if (format.fileType == formats[i].fileType && format
.priority < formats[i].priority) {
40 formats.insert(i, format);
43 formats.append(format);
46QList<Translator::FileFormat> &
Translator::registeredFileFormats()
48 static QList<Translator::FileFormat> theFormats;
54 if (msg.sourceText().isEmpty() && msg.id().isEmpty()) {
55 m_ctxCmtIdx[msg.context()] = idx;
57 m_msgIdx[TMMKey(msg)] = idx;
58 if (!msg.id().isEmpty())
59 m_idMsgIdx[msg.id()] = idx;
66 if (msg.sourceText().isEmpty() && msg.id().isEmpty()) {
67 m_ctxCmtIdx.remove(msg.context());
69 m_msgIdx.remove(TMMKey(msg));
70 if (!msg.id().isEmpty())
71 m_idMsgIdx.remove(msg.id());
82 for (
int i = 0; i < m_messages.size(); i++)
83 addIndex(i, m_messages.at(i));
94 m_messages[index] = msg;
101 return id.size() <= len ? id : id.left(len - 5) + QLatin1String(
"[...]");
106 QString id = msg.context() + QLatin1String(
"//") + elidedId(msg.sourceText(), 100);
107 if (!msg.comment().isEmpty())
108 id += QLatin1String(
"//") + elidedId(msg.comment(), 30);
119 if (emsg.sourceText().isEmpty()) {
121 emsg.setSourceText(msg.sourceText());
122 addIndex(index, msg);
123 }
else if (!msg.sourceText().isEmpty() && emsg.sourceText() != msg.sourceText()) {
124 cd.appendError(QString::fromLatin1(
"Contradicting source strings for message with id '%1'.")
131 cd.appendError(QString::fromLatin1(
"Contradicting meta data for for %1.")
132 .arg(!emsg.id().isEmpty()
133 ? QString::fromLatin1(
"message with id '%1'").arg(emsg.id())
134 : QString::fromLatin1(
"message '%1'").arg(makeMsgId(msg))));
138 if (!msg.extraComment().isEmpty()) {
139 QString cmt = emsg.extraComment();
140 if (!cmt.isEmpty()) {
141 QStringList cmts = cmt.split(QLatin1String(
"\n----------\n"));
142 if (!cmts.contains(msg.extraComment())) {
143 cmts.append(msg.extraComment());
144 cmt = cmts.join(QLatin1String(
"\n----------\n"));
147 cmt = msg.extraComment();
149 emsg.setExtraComment(cmt);
157 if (idx == m_messages.size())
162 m_messages.insert(idx, msg);
167 insert(m_messages.size(), msg);
189 for (
const TranslatorMessage &mit : std::as_const(m_messages)) {
190 bool sameFile = mit.fileName() == msg.fileName() && mit.context() == msg.context();
192 if (sameFile && (curLine = mit.lineNumber()) >= prevLine) {
193 if (msgLine >= prevLine && msgLine < curLine) {
195 thisScore = thisSize ? 2 : 1;
205 if (thisScore > bestScore || (thisScore == bestScore && thisSize > bestSize)) {
207 bestScore = thisScore;
211 thisSize = sameFile ? 1 : 0;
217 if (thisSize && !thisScore) {
221 if (thisScore > bestScore || (thisScore == bestScore && thisSize > bestSize))
222 insert(thisIdx, msg);
224 insert(bestIdx, msg);
231 if (format != QLatin1String(
"auto"))
234 for (
const Translator::FileFormat &fmt : std::as_const(Translator::registeredFileFormats())) {
235 if (filename.endsWith(QLatin1Char(
'.') + fmt.extension, Qt::CaseInsensitive))
236 return fmt.extension;
241 return QLatin1String(
"ts");
246 cd.m_sourceDir = QFileInfo(filename).absoluteDir();
247 cd.m_sourceFileName = filename;
250 if (filename.isEmpty() || filename == QLatin1String(
"-")) {
253 ::_setmode(0, _O_BINARY);
255 if (!file.open(stdin, QIODevice::ReadOnly)) {
256 cd.appendError(QString::fromLatin1(
"Cannot open stdin!? (%1)")
257 .arg(file.errorString()));
261 file.setFileName(filename);
262 if (!file.open(QIODevice::ReadOnly)) {
263 cd.appendError(QString::fromLatin1(
"Cannot open %1: %2")
264 .arg(filename, file.errorString()));
269 QString fmt = guessFormat(filename, format);
271 for (
const FileFormat &format : std::as_const(registeredFileFormats())) {
272 if (fmt == format.extension) {
274 return (*format.loader)(*
this, file, cd);
275 cd.appendError(QString(QLatin1String(
"No loader for format %1 found"))
281 cd.appendError(QString(QLatin1String(
"Unknown format %1 for file %2"))
282 .arg(format, filename));
290 if (filename.isEmpty() || filename == QLatin1String(
"-")) {
293 ::_setmode(1, _O_BINARY);
295 if (!file.open(stdout, QIODevice::WriteOnly)) {
296 cd.appendError(QString::fromLatin1(
"Cannot open stdout!? (%1)")
297 .arg(file.errorString()));
301 file.setFileName(filename);
302 if (!file.open(QIODevice::WriteOnly)) {
303 cd.appendError(QString::fromLatin1(
"Cannot create %1: %2")
304 .arg(filename, file.errorString()));
309 QString fmt = guessFormat(filename, format);
310 cd.m_targetDir = QFileInfo(filename).absoluteDir();
312 for (
const FileFormat &format : std::as_const(registeredFileFormats())) {
313 if (fmt == format.extension) {
315 return (*format.saver)(*
this, file, cd);
316 cd.appendError(QString(QLatin1String(
"Cannot save %1 files")).arg(fmt));
321 cd.appendError(QString(QLatin1String(
"Unknown format %1 for file %2"))
322 .arg(format).arg(filename));
326QString
Translator::makeLanguageCode(QLocale::Language language, QLocale::Territory territory)
328 QString result = QLocale::languageToCode(language);
329 if (language != QLocale::C && territory != QLocale::AnyTerritory) {
330 result.append(QLatin1Char(
'_'));
331 result.append(QLocale::territoryToCode(territory));
336void Translator::languageAndTerritory(QStringView languageCode, QLocale::Language *langPtr,
337 QLocale::Territory *territoryPtr)
339 QLocale::Language language = QLocale::AnyLanguage;
340 QLocale::Territory territory = QLocale::AnyTerritory;
341 auto separator = languageCode.indexOf(u'_');
342 if (separator == -1) {
344 separator = languageCode.indexOf(u'-');
346 if (separator != -1) {
347 language = QLocale::codeToLanguage(languageCode.left(separator));
348 territory = QLocale::codeToTerritory(languageCode.mid(separator + 1));
350 language = QLocale::codeToLanguage(languageCode);
351 territory = QLocale(language).territory();
357 *territoryPtr = territory;
363 if (msg.id().isEmpty())
364 return m_msgIdx.value(TMMKey(msg), -1);
365 int i = m_idMsgIdx.value(msg.id(), -1);
368 i = m_msgIdx.value(TMMKey(msg), -1);
370 return i >= 0 && m_messages.at(i).id().isEmpty() ? i : -1;
376 if (!refs.isEmpty()) {
377 for (
auto it = m_messages.cbegin(), end = m_messages.cend(); it != end; ++it) {
378 if (it->context() == context && it->comment() == comment) {
379 for (
const auto &itref : it->allReferences()) {
380 for (
const auto &ref : refs) {
382 return it - m_messages.cbegin();
394 return m_ctxCmtIdx.value(context, -1);
399 for (
auto it = m_messages.begin(); it != m_messages.end(); )
401 it = m_messages.erase(it);
409 for (
auto it = m_messages.begin(); it != m_messages.end(); )
411 it = m_messages.erase(it);
419 for (
auto it = m_messages.begin(); it != m_messages.end(); )
420 if (!it->isTranslated())
421 it = m_messages.erase(it);
429 for (
const auto &message : m_messages) {
430 if (message.isTranslated())
438 for (
auto it = m_messages.begin(); it != m_messages.end(); )
440 it = m_messages.erase(it);
448 for (
auto it = m_messages.begin(); it != m_messages.end(); )
450 it = m_messages.erase(it);
458 for (
auto it = m_messages.begin(); it != m_messages.end(); ) {
460 if (it->translations().size() == 1 && it->translation() == it->sourceText())
461 it = m_messages.erase(it);
470 for (
auto &message : m_messages) {
471 if (message.type() == TranslatorMessage::Finished)
472 message.setType(TranslatorMessage::Unfinished);
473 message.setTranslation(QString());
479 const QString uiXt = QLatin1String(
".ui");
480 const QString juiXt = QLatin1String(
".jui");
481 for (
auto &message : m_messages) {
482 QHash<QString,
int> have;
483 QList<TranslatorMessage::Reference> refs;
484 for (
const auto &itref : message.allReferences()) {
485 const QString &fn = itref.fileName();
486 if (fn.endsWith(uiXt) || fn.endsWith(juiXt)) {
488 refs.append(TranslatorMessage::Reference(fn, -1));
493 message.setReferences(refs);
524 return qHash(tmp->id());
529 return tmp1->id() == tmp2->id();
542 size_t hash = qHash(tmp->context()) ^ qHash(tmp->sourceText());
543 if (!tmp->sourceText().isEmpty())
545 hash ^= qHash(tmp->comment());
551 if (tmp1->context() != tmp2->context() || tmp1->sourceText() != tmp2->sourceText())
554 if (tmp1->sourceText().isEmpty())
556 return tmp1->comment() == tmp2->comment();
562 QSet<TranslatorMessageIdPtr> idRefs;
563 QSet<TranslatorMessageContentPtr> contentRefs;
564 for (
int i = 0; i < m_messages.size();) {
568 DuplicateEntries *pDup;
569 if (!msg.id().isEmpty()) {
571 if (it != idRefs.constEnd()) {
572 oi = it->messageIndex;
573 omsg = &m_messages[oi];
580 if (it != contentRefs.constEnd()) {
581 oi = it->messageIndex;
582 omsg = &m_messages[oi];
583 if (msg.id().isEmpty() || omsg->id().isEmpty()) {
584 if (!msg.id().isEmpty() && omsg->id().isEmpty()) {
585 omsg->setId(msg.id());
588 pDup = &dups.byContents;
594 if (!msg.id().isEmpty())
602 omsg->setTranslations(msg.translations());
604 m_messages.removeAt(i);
610 const QString &fileName,
bool verbose)
612 if (!dupes.byId.isEmpty() || !dupes.byContents.isEmpty()) {
613 std::cerr <<
"Warning: dropping duplicate messages in '" << qPrintable(fileName);
615 std::cerr <<
"'\n(try -verbose for more info).\n";
618 for (
auto it = dupes.byId.begin(); it != dupes.byId.end(); ++it) {
620 std::cerr <<
"\n* ID: " << qPrintable(msg.id()) <<
std::endl;
621 reportDuplicatesLines(msg, it.value());
623 for (
auto it = dupes.byContents.begin(); it != dupes.byContents.end(); ++it) {
625 std::cerr <<
"\n* Context: " << qPrintable(msg.context())
626 <<
"\n* Source: " << qPrintable(msg.sourceText()) <<
std::endl;
627 if (!msg.comment().isEmpty())
628 std::cerr <<
"* Comment: " << qPrintable(msg.comment()) <<
std::endl;
629 reportDuplicatesLines(msg, it.value());
637 const DuplicateEntries::value_type &dups)
const
641 for (
int tsLineNumber : dups) {
642 if (tsLineNumber >= 0)
643 std::cerr <<
"* Duplicate at line: " << tsLineNumber << std::endl;
651 for (
auto &msg : m_messages) {
652 const TranslatorMessage::References refs = msg.allReferences();
653 msg.setReferences(TranslatorMessage::References());
654 for (
const TranslatorMessage::Reference &ref : refs) {
655 QString fileName = ref.fileName();
656 QFileInfo fi (fileName);
658 fileName = originalPath.absoluteFilePath(fileName);
659 msg.addReference(fileName, ref.lineNumber());
671 QStringList translations = msg.translations();
672 int numTranslations = msg
.isPlural() ? numPlurals : 1;
676 if (translations.size() > numTranslations) {
677 for (
int i = translations.size(); i > numTranslations; --i)
678 translations.removeLast();
679 }
else if (translations.size() < numTranslations) {
680 for (
int i = translations.size(); i < numTranslations; ++i)
681 translations.append(QString());
688 bool truncated =
false;
690 QLocale::Territory c;
691 languageAndTerritory(languageCode(), &l, &c);
693 if (l != QLocale::C) {
695 if (getNumerusInfo(l, c, 0, &forms, 0))
696 numPlurals = forms.size();
698 for (
int i = 0; i < m_messages.size(); ++i) {
700 QStringList tlns = msg.translations();
702 if (tlns.size() != ccnt) {
703 while (tlns.size() < ccnt)
704 tlns.append(QString());
705 while (tlns.size() > ccnt) {
709 m_messages[i].setTranslations(tlns);
713 cd.appendError(QLatin1String(
714 "Removed plural forms as the target language has less "
715 "forms.\nIf this sounds wrong, possibly the target language is "
716 "not set or recognized."));
719QString
Translator::guessLanguageCodeFromFileName(
const QString &filename)
721 QString str = filename;
722 for (
const FileFormat &format : std::as_const(registeredFileFormats())) {
723 if (str.endsWith(format.extension)) {
724 str = str.left(str.size() - format.extension.size() - 1);
728 static QRegularExpression re(QLatin1String(
"[\\._]"));
732 if (locale.language() != QLocale::C) {
734 return locale.name();
736 int pos = str.indexOf(re);
739 str = str.mid(pos + 1);
747 return m_extra.contains(key);
757 m_extra[key] = value;
const TranslatorMessage * operator->() const
TranslatorMessagePtrBase(const Translator *tor, int messageIndex)
QList< Reference > References
bool isTranslated() const
void setExtras(const ExtraData &extras)
const ExtraData & extras() const
void stripObsoleteMessages()
bool save(const QString &filename, ConversionData &err, const QString &format) const
bool load(const QString &filename, ConversionData &err, const QString &format)
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
int find(const QString &context) const
QString extra(const QString &ba) const
static void registerFileFormat(const FileFormat &format)
void append(const TranslatorMessage &msg)
void stripIdenticalSourceTranslations()
void makeFileNamesAbsolute(const QDir &originalPath)
bool translationsExist() const
Duplicates resolveDuplicates()
bool hasExtra(const QString &ba) const
int find(const QString &context, const QString &comment, const TranslatorMessage::References &refs) const
void normalizeTranslations(ConversionData &cd)
void appendSorted(const TranslatorMessage &msg)
void reportDuplicatesLines(const TranslatorMessage &msg, const DuplicateEntries::value_type &dups) const
int find(const TranslatorMessage &msg) const
void reportDuplicates(const Duplicates &dupes, const QString &fileName, bool verbose)
const QList< TranslatorMessage > & messages() const
void extend(const TranslatorMessage &msg, ConversionData &cd)
size_t qHash(TranslatorMessageIdPtr tmp)
Q_DECLARE_TYPEINFO(TranslatorMessageIdPtr, Q_RELOCATABLE_TYPE)
static QString guessFormat(const QString &filename, const QString &format)
size_t qHash(TranslatorMessageContentPtr tmp)
static QString elidedId(const QString &id, int len)
static QString makeMsgId(const TranslatorMessage &msg)
Q_DECLARE_TYPEINFO(TranslatorMessageContentPtr, Q_RELOCATABLE_TYPE)
bool operator==(TranslatorMessageContentPtr tmp1, TranslatorMessageContentPtr tmp2)
bool operator==(TranslatorMessageIdPtr tmp1, TranslatorMessageIdPtr tmp2)