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>
28using namespace Qt::Literals::StringLiterals;
32 QString f = str.toLower();
33 static QRegularExpression re(
"[.,:;!?()-]"_L1);
34 f.replace(re,
" "_L1);
36 return f.simplified();
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);
54 formats.append(format);
57QList<Translator::FileFormat> &
Translator::registeredFileFormats()
59 static QList<Translator::FileFormat> theFormats;
66 m_msgIdx[TMMKey(msg)] = idx;
67 if (!msg.id().isEmpty())
68 m_idMsgIdx[msg.id()] = idx;
76 m_msgIdx.remove(TMMKey(msg));
77 if (!msg.id().isEmpty())
78 m_idMsgIdx.remove(msg.id());
88 for (
int i = 0; i < m_messages.size(); i++)
89 addIndex(i, m_messages.at(i));
100 m_messages[index] = msg;
101 addIndex(index, msg);
107 return id.size() <= len ? id : id.left(len - 5) +
"[...]"_L1;
112 QString id = msg.context() +
"//"_L1 + elidedId(msg.sourceText(), 100);
113 if (!msg.comment().isEmpty())
114 id +=
"//"_L1 + elidedId(msg.comment(), 30);
125 if (emsg.sourceText().isEmpty()) {
127 emsg.setSourceText(msg.sourceText());
128 addIndex(index, msg);
129 }
else if (!msg.sourceText().isEmpty() && emsg.sourceText() != msg.sourceText()) {
131 "Contradicting source strings for message with id '%1'."_L1.arg(emsg.id()));
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))));
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);
152 cmt = msg.extraComment();
154 emsg.setExtraComment(cmt);
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()));
166 if (idx == m_messages.size())
171 m_messages.insert(idx, msg);
176 insert(m_messages.size(), msg);
198 for (
const TranslatorMessage &mit : std::as_const(m_messages)) {
199 bool sameFile = mit.fileName() == msg.fileName() && mit.context() == msg.context();
201 if (sameFile && (curLine = mit.lineNumber()) >= prevLine) {
202 if (msgLine >= prevLine && msgLine < curLine) {
204 thisScore = thisSize ? 2 : 1;
214 if (thisScore > bestScore || (thisScore == bestScore && thisSize > bestSize)) {
216 bestScore = thisScore;
220 thisSize = sameFile ? 1 : 0;
226 if (thisSize && !thisScore) {
230 if (thisScore > bestScore || (thisScore == bestScore && thisSize > bestSize))
231 insert(thisIdx, msg);
233 insert(bestIdx, msg);
240 if (format !=
"auto"_L1)
243 for (
const Translator::FileFormat &fmt : std::as_const(Translator::registeredFileFormats())) {
244 if (filename.endsWith(u'.' + fmt.extension, Qt::CaseInsensitive))
245 return fmt.extension;
255 const QString file = QFileInfo(filename).fileName();
256 const QString fmt = guessFormat(file, format);
258 if (file.endsWith(u'.' + fmt))
259 return file.chopped(fmt.size() + 1);
267 cd.m_sourceDir = QFileInfo(filename).absoluteDir();
268 cd.m_sourceFileName = filename;
271 if (filename.isEmpty() || filename ==
"-"_L1) {
274 ::_setmode(0, _O_BINARY);
276 if (!file.open(stdin, QIODevice::ReadOnly)) {
277 cd.appendError(QString::fromLatin1(
"Cannot open stdin!? (%1)")
278 .arg(file.errorString()));
282 file.setFileName(filename);
283 if (!file.open(QIODevice::ReadOnly)) {
284 cd.appendError(QString::fromLatin1(
"Cannot open %1: %2")
285 .arg(filename, file.errorString()));
290 QString fmt = guessFormat(filename, format);
292 for (
const FileFormat &format : std::as_const(registeredFileFormats())) {
293 if (fmt == format.extension) {
295 return (*format.loader)(*
this, file, cd);
296 cd.appendError(QString(
"No loader for format %1 found"_L1).arg(fmt));
301 cd.appendError(QString(
"Unknown format %1 for file %2"_L1).arg(format, filename));
309 if (filename.isEmpty() || filename ==
"-"_L1) {
312 ::_setmode(1, _O_BINARY);
314 if (!file.open(stdout, QIODevice::WriteOnly)) {
315 cd.appendError(QString::fromLatin1(
"Cannot open stdout!? (%1)")
316 .arg(file.errorString()));
320 file.setFileName(filename);
321 if (!file.open(QIODevice::WriteOnly)) {
322 cd.appendError(QString::fromLatin1(
"Cannot create %1: %2")
323 .arg(filename, file.errorString()));
328 QString fmt = guessFormat(filename, format);
329 cd.m_targetDir = QFileInfo(filename).absoluteDir();
331 for (
const FileFormat &format : std::as_const(registeredFileFormats())) {
332 if (fmt == format.extension) {
334 if (fmt != u"ts" && m_locationsType == RelativeLocations)
335 std::cerr <<
"Warning: relative locations are not supported for non TS files. "
337 << qPrintable(filename)
338 <<
" will be generated with the "
339 "default location type."
341 return (*format.saver)(*
this, file, cd);
343 cd.appendError(QString(
"Cannot save %1 files"_L1).arg(fmt));
348 cd.appendError(QString(
"Unknown format %1 for file %2"_L1).arg(format).arg(filename));
352QString
Translator::makeLanguageCode(QLocale::Language language, QLocale::Territory territory)
354 QString result = QLocale::languageToCode(language);
355 if (language != QLocale::C && territory != QLocale::AnyTerritory) {
357 result.append(QLocale::territoryToCode(territory));
362void Translator::languageAndTerritory(QStringView languageCode, QLocale::Language *langPtr,
363 QLocale::Territory *territoryPtr)
365 QLocale::Language language = QLocale::AnyLanguage;
366 QLocale::Territory territory = QLocale::AnyTerritory;
367 auto separator = languageCode.indexOf(u'_');
368 if (separator == -1) {
370 separator = languageCode.indexOf(u'-');
372 if (separator != -1) {
373 language = QLocale::codeToLanguage(languageCode.left(separator));
374 territory = QLocale::codeToTerritory(languageCode.mid(separator + 1));
376 language = QLocale::codeToLanguage(languageCode);
377 territory = QLocale(language).territory();
383 *territoryPtr = territory;
389 if (msg.id().isEmpty())
390 return m_msgIdx.value(TMMKey(msg), -1);
391 int i = m_idMsgIdx.value(msg.id(), -1);
394 i = m_msgIdx.value(TMMKey(msg), -1);
396 return i >= 0 && m_messages.at(i).id().isEmpty() ? i : -1;
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) {
408 return it - m_messages.cbegin();
419 for (
auto it = m_messages.begin(); it != m_messages.end(); )
421 it = m_messages.erase(it);
429 for (
auto it = m_messages.begin(); it != m_messages.end(); )
431 it = m_messages.erase(it);
439 for (
auto it = m_messages.begin(); it != m_messages.end(); )
440 if (!it->isTranslated())
441 it = m_messages.erase(it);
449 return std::any_of(m_messages.cbegin(), m_messages.cend(),
450 [](
const auto &m) {
return m.isTranslated(); });
455 return std::any_of(m_messages.cbegin(), m_messages.cend(),
456 [](
const auto &m) {
return m.type() == TranslatorMessage::Unfinished; });
461 for (
auto it = m_messages.begin(); it != m_messages.end(); )
463 it = m_messages.erase(it);
471 for (
auto it = m_messages.begin(); it != m_messages.end(); )
473 it = m_messages.erase(it);
481 for (
auto it = m_messages.begin(); it != m_messages.end(); ) {
483 if (it->translations().size() == 1 && it->translation() == it->sourceText())
484 it = m_messages.erase(it);
493 for (
auto &message : m_messages) {
494 if (message.type() == TranslatorMessage::Finished)
495 message.setType(TranslatorMessage::Unfinished);
496 message.setTranslation(QString());
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)) {
511 refs.append(TranslatorMessage::Reference(fn, -1));
516 message.setReferences(refs);
547 return qHash(tmp->id());
552 return tmp1->id() == tmp2->id();
565 size_t hash = qHash(tmp->context()) ^ qHash(tmp->sourceText());
566 if (!tmp->sourceText().isEmpty())
568 hash ^= qHash(tmp->comment());
574 if (tmp1->context() != tmp2->context() || tmp1->sourceText() != tmp2->sourceText())
577 if (tmp1->sourceText().isEmpty())
579 return tmp1->comment() == tmp2->comment();
585 QSet<TranslatorMessageIdPtr> idRefs;
586 QSet<TranslatorMessageContentPtr> contentRefs;
587 for (
int i = 0; i < m_messages.size();) {
591 DuplicateEntries *pDup;
592 if (!msg.id().isEmpty()) {
594 if (it != idRefs.constEnd()) {
595 oi = it->messageIndex;
596 omsg = &m_messages[oi];
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());
611 pDup = &dups.byContents;
617 if (!msg.id().isEmpty())
625 omsg->setTranslations(msg.translations());
627 m_messages.removeAt(i);
633 const QString &fileName,
bool verbose)
635 if (!dupes.byId.isEmpty() || !dupes.byContents.isEmpty()) {
636 std::cerr <<
"Warning: dropping duplicate messages in '" << qPrintable(fileName);
638 std::cerr <<
"'\n(try -verbose for more info).\n";
641 for (
auto it = dupes.byId.begin(); it != dupes.byId.end(); ++it) {
643 std::cerr <<
"\n* ID: " << qPrintable(msg.id()) <<
std::endl;
644 reportDuplicatesLines(msg, it.value());
646 for (
auto it = dupes.byContents.begin(); it != dupes.byContents.end(); ++it) {
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());
660 const DuplicateEntries::value_type &dups)
const
664 for (
int tsLineNumber : dups) {
665 if (tsLineNumber >= 0)
666 std::cerr <<
"* Duplicate at line: " << tsLineNumber << std::endl;
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);
681 fileName = originalPath.absoluteFilePath(fileName);
682 msg.addReference(fileName, ref.lineNumber());
694 QStringList translations = msg.translations();
695 int numTranslations = msg
.isPlural() ? numPlurals : 1;
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());
711 bool truncated =
false;
713 QLocale::Territory c;
714 languageAndTerritory(languageCode(), &l, &c);
716 if (l != QLocale::C) {
718 if (getNumerusInfo(l, c, 0, &forms, 0))
719 numPlurals = forms.size();
721 for (
int i = 0; i < m_messages.size(); ++i) {
723 QStringList tlns = msg.translations();
725 if (tlns.size() != ccnt) {
726 while (tlns.size() < ccnt)
727 tlns.append(QString());
728 while (tlns.size() > ccnt) {
732 m_messages[i].setTranslations(tlns);
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."));
742QString
Translator::guessLanguageCodeFromFileName(
const QString &filename)
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);
751 static QRegularExpression re(
"[\\._]"_L1);
755 if (locale.language() != QLocale::C) {
757 return locale.name();
759 int pos = str.indexOf(re);
762 str = str.mid(pos + 1);
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);
777 m_dependencies.append(mergeDeps);
780void Translator::satisfyDependency(
const QString &file,
const QString &format)
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);
791 return m_extra.contains(key);
799void Translator::setExtra(
const QString &key,
const QString &value)
801 m_extra[key] = value;
const TranslatorMessage * operator->() const
TranslatorMessagePtrBase(const Translator *tor, int messageIndex)
bool isTranslated() const
void setExtras(const ExtraData &extras)
QList< Reference > References
const ExtraData & extras() const
void stripObsoleteMessages()
bool unfinishedTranslationsExist() const
void stripEmptyContexts()
void stripUntranslatedMessages()
void stripFinishedMessages()
void replaceSorted(const TranslatorMessage &msg)
void stripNonPluralForms()
const TranslatorMessage & message(int i) const
static void registerFileFormat(const FileFormat &format)
void append(const TranslatorMessage &msg)
void stripIdenticalSourceTranslations()
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)