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;
39 QList<Translator::FileFormat> &formats = registeredFileFormats();
40 for (
int i = 0; i < formats.size(); ++i)
41 if (format.fileType == formats[i].fileType && format
.priority < formats[i].priority) {
42 formats.insert(i, format);
45 formats.append(format);
48QList<Translator::FileFormat> &
Translator::registeredFileFormats()
50 static QList<Translator::FileFormat> theFormats;
57 m_msgIdx[TMMKey(msg)] = idx;
58 if (!msg.id().isEmpty())
59 m_idMsgIdx[msg.id()] = idx;
67 m_msgIdx.remove(TMMKey(msg));
68 if (!msg.id().isEmpty())
69 m_idMsgIdx.remove(msg.id());
79 for (
int i = 0; i < m_messages.size(); i++)
80 addIndex(i, m_messages.at(i));
91 m_messages[index] = msg;
98 return id.size() <= len ? id : id.left(len - 5) +
"[...]"_L1;
103 QString id = msg.context() +
"//"_L1 + elidedId(msg.sourceText(), 100);
104 if (!msg.comment().isEmpty())
105 id +=
"//"_L1 + elidedId(msg.comment(), 30);
116 if (emsg.sourceText().isEmpty()) {
118 emsg.setSourceText(msg.sourceText());
119 addIndex(index, msg);
120 }
else if (!msg.sourceText().isEmpty() && emsg.sourceText() != msg.sourceText()) {
121 cd.appendError(QString::fromLatin1(
"Contradicting source strings for message with id '%1'.")
128 cd.appendError(QString::fromLatin1(
"Contradicting meta data for for %1.")
129 .arg(!emsg.id().isEmpty()
130 ? QString::fromLatin1(
"message with id '%1'").arg(emsg.id())
131 : QString::fromLatin1(
"message '%1'").arg(makeMsgId(msg))));
135 if (!msg.extraComment().isEmpty()) {
136 QString cmt = emsg.extraComment();
137 if (!cmt.isEmpty()) {
138 QStringList cmts = cmt.split(
"\n----------\n"_L1);
139 if (!cmts.contains(msg.extraComment())) {
140 cmts.append(msg.extraComment());
141 cmt = cmts.join(
"\n----------\n"_L1);
144 cmt = msg.extraComment();
146 emsg.setExtraComment(cmt);
154 if (idx == m_messages.size())
159 m_messages.insert(idx, msg);
164 insert(m_messages.size(), msg);
186 for (
const TranslatorMessage &mit : std::as_const(m_messages)) {
187 bool sameFile = mit.fileName() == msg.fileName() && mit.context() == msg.context();
189 if (sameFile && (curLine = mit.lineNumber()) >= prevLine) {
190 if (msgLine >= prevLine && msgLine < curLine) {
192 thisScore = thisSize ? 2 : 1;
202 if (thisScore > bestScore || (thisScore == bestScore && thisSize > bestSize)) {
204 bestScore = thisScore;
208 thisSize = sameFile ? 1 : 0;
214 if (thisSize && !thisScore) {
218 if (thisScore > bestScore || (thisScore == bestScore && thisSize > bestSize))
219 insert(thisIdx, msg);
221 insert(bestIdx, msg);
228 if (format !=
"auto"_L1)
231 for (
const Translator::FileFormat &fmt : std::as_const(Translator::registeredFileFormats())) {
232 if (filename.endsWith(u'.' + fmt.extension, Qt::CaseInsensitive))
233 return fmt.extension;
243 const QString file = QFileInfo(filename).fileName();
244 const QString fmt = guessFormat(file, format);
246 if (file.endsWith(u'.' + fmt))
247 return file.chopped(fmt.size() + 1);
255 cd.m_sourceDir = QFileInfo(filename).absoluteDir();
256 cd.m_sourceFileName = filename;
259 if (filename.isEmpty() || filename ==
"-"_L1) {
262 ::_setmode(0, _O_BINARY);
264 if (!file.open(stdin, QIODevice::ReadOnly)) {
265 cd.appendError(QString::fromLatin1(
"Cannot open stdin!? (%1)")
266 .arg(file.errorString()));
270 file.setFileName(filename);
271 if (!file.open(QIODevice::ReadOnly)) {
272 cd.appendError(QString::fromLatin1(
"Cannot open %1: %2")
273 .arg(filename, file.errorString()));
278 QString fmt = guessFormat(filename, format);
280 for (
const FileFormat &format : std::as_const(registeredFileFormats())) {
281 if (fmt == format.extension) {
283 return (*format.loader)(*
this, file, cd);
284 cd.appendError(QString(
"No loader for format %1 found"_L1).arg(fmt));
289 cd.appendError(QString(
"Unknown format %1 for file %2"_L1).arg(format, filename));
297 if (filename.isEmpty() || filename ==
"-"_L1) {
300 ::_setmode(1, _O_BINARY);
302 if (!file.open(stdout, QIODevice::WriteOnly)) {
303 cd.appendError(QString::fromLatin1(
"Cannot open stdout!? (%1)")
304 .arg(file.errorString()));
308 file.setFileName(filename);
309 if (!file.open(QIODevice::WriteOnly)) {
310 cd.appendError(QString::fromLatin1(
"Cannot create %1: %2")
311 .arg(filename, file.errorString()));
316 QString fmt = guessFormat(filename, format);
317 cd.m_targetDir = QFileInfo(filename).absoluteDir();
319 for (
const FileFormat &format : std::as_const(registeredFileFormats())) {
320 if (fmt == format.extension) {
322 if (fmt != u"ts" && m_locationsType == RelativeLocations)
323 std::cerr <<
"Warning: relative locations are not supported for non TS files. "
325 << qPrintable(filename)
326 <<
" will be generated with the "
327 "default location type."
329 return (*format.saver)(*
this, file, cd);
331 cd.appendError(QString(
"Cannot save %1 files"_L1).arg(fmt));
336 cd.appendError(QString(
"Unknown format %1 for file %2"_L1).arg(format).arg(filename));
340QString
Translator::makeLanguageCode(QLocale::Language language, QLocale::Territory territory)
342 QString result = QLocale::languageToCode(language);
343 if (language != QLocale::C && territory != QLocale::AnyTerritory) {
345 result.append(QLocale::territoryToCode(territory));
350void Translator::languageAndTerritory(QStringView languageCode, QLocale::Language *langPtr,
351 QLocale::Territory *territoryPtr)
353 QLocale::Language language = QLocale::AnyLanguage;
354 QLocale::Territory territory = QLocale::AnyTerritory;
355 auto separator = languageCode.indexOf(u'_');
356 if (separator == -1) {
358 separator = languageCode.indexOf(u'-');
360 if (separator != -1) {
361 language = QLocale::codeToLanguage(languageCode.left(separator));
362 territory = QLocale::codeToTerritory(languageCode.mid(separator + 1));
364 language = QLocale::codeToLanguage(languageCode);
365 territory = QLocale(language).territory();
371 *territoryPtr = territory;
377 if (msg.id().isEmpty())
378 return m_msgIdx.value(TMMKey(msg), -1);
379 int i = m_idMsgIdx.value(msg.id(), -1);
382 i = m_msgIdx.value(TMMKey(msg), -1);
384 return i >= 0 && m_messages.at(i).id().isEmpty() ? i : -1;
390 if (!refs.isEmpty()) {
391 for (
auto it = m_messages.cbegin(), end = m_messages.cend(); it != end; ++it) {
392 if (it->context() == context && it->comment() == comment) {
393 for (
const auto &itref : it->allReferences()) {
394 for (
const auto &ref : refs) {
396 return it - m_messages.cbegin();
407 for (
auto it = m_messages.begin(); it != m_messages.end(); )
409 it = m_messages.erase(it);
417 for (
auto it = m_messages.begin(); it != m_messages.end(); )
419 it = m_messages.erase(it);
427 for (
auto it = m_messages.begin(); it != m_messages.end(); )
428 if (!it->isTranslated())
429 it = m_messages.erase(it);
437 return std::any_of(m_messages.cbegin(), m_messages.cend(),
438 [](
const auto &m) {
return m.isTranslated(); });
443 return std::any_of(m_messages.cbegin(), m_messages.cend(),
444 [](
const auto &m) {
return m.type() == TranslatorMessage::Unfinished; });
449 for (
auto it = m_messages.begin(); it != m_messages.end(); )
451 it = m_messages.erase(it);
459 for (
auto it = m_messages.begin(); it != m_messages.end(); )
461 it = m_messages.erase(it);
469 for (
auto it = m_messages.begin(); it != m_messages.end(); ) {
471 if (it->translations().size() == 1 && it->translation() == it->sourceText())
472 it = m_messages.erase(it);
481 for (
auto &message : m_messages) {
482 if (message.type() == TranslatorMessage::Finished)
483 message.setType(TranslatorMessage::Unfinished);
484 message.setTranslation(QString());
490 const QString uiXt =
".ui"_L1;
491 const QString juiXt =
".jui"_L1;
492 for (
auto &message : m_messages) {
493 QHash<QString,
int> have;
494 QList<TranslatorMessage::Reference> refs;
495 for (
const auto &itref : message.allReferences()) {
496 const QString &fn = itref.fileName();
497 if (fn.endsWith(uiXt) || fn.endsWith(juiXt)) {
499 refs.append(TranslatorMessage::Reference(fn, -1));
504 message.setReferences(refs);
535 return qHash(tmp->id());
540 return tmp1->id() == tmp2->id();
553 size_t hash = qHash(tmp->context()) ^ qHash(tmp->sourceText());
554 if (!tmp->sourceText().isEmpty())
556 hash ^= qHash(tmp->comment());
562 if (tmp1->context() != tmp2->context() || tmp1->sourceText() != tmp2->sourceText())
565 if (tmp1->sourceText().isEmpty())
567 return tmp1->comment() == tmp2->comment();
573 QSet<TranslatorMessageIdPtr> idRefs;
574 QSet<TranslatorMessageContentPtr> contentRefs;
575 for (
int i = 0; i < m_messages.size();) {
579 DuplicateEntries *pDup;
580 if (!msg.id().isEmpty()) {
582 if (it != idRefs.constEnd()) {
583 oi = it->messageIndex;
584 omsg = &m_messages[oi];
591 if (it != contentRefs.constEnd()) {
592 oi = it->messageIndex;
593 omsg = &m_messages[oi];
594 if (msg.id().isEmpty() || omsg->id().isEmpty()) {
595 if (!msg.id().isEmpty() && omsg->id().isEmpty()) {
596 omsg->setId(msg.id());
599 pDup = &dups.byContents;
605 if (!msg.id().isEmpty())
613 omsg->setTranslations(msg.translations());
615 m_messages.removeAt(i);
621 const QString &fileName,
bool verbose)
623 if (!dupes.byId.isEmpty() || !dupes.byContents.isEmpty()) {
624 std::cerr <<
"Warning: dropping duplicate messages in '" << qPrintable(fileName);
626 std::cerr <<
"'\n(try -verbose for more info).\n";
629 for (
auto it = dupes.byId.begin(); it != dupes.byId.end(); ++it) {
631 std::cerr <<
"\n* ID: " << qPrintable(msg.id()) <<
std::endl;
632 reportDuplicatesLines(msg, it.value());
634 for (
auto it = dupes.byContents.begin(); it != dupes.byContents.end(); ++it) {
636 std::cerr <<
"\n* Context: " << qPrintable(msg.context())
637 <<
"\n* Source: " << qPrintable(msg.sourceText()) <<
std::endl;
638 if (!msg.comment().isEmpty())
639 std::cerr <<
"* Comment: " << qPrintable(msg.comment()) <<
std::endl;
640 reportDuplicatesLines(msg, it.value());
648 const DuplicateEntries::value_type &dups)
const
652 for (
int tsLineNumber : dups) {
653 if (tsLineNumber >= 0)
654 std::cerr <<
"* Duplicate at line: " << tsLineNumber << std::endl;
662 for (
auto &msg : m_messages) {
663 const TranslatorMessage::References refs = msg.allReferences();
664 msg.setReferences(TranslatorMessage::References());
665 for (
const TranslatorMessage::Reference &ref : refs) {
666 QString fileName = ref.fileName();
667 QFileInfo fi (fileName);
669 fileName = originalPath.absoluteFilePath(fileName);
670 msg.addReference(fileName, ref.lineNumber());
682 QStringList translations = msg.translations();
683 int numTranslations = msg
.isPlural() ? numPlurals : 1;
687 if (translations.size() > numTranslations) {
688 for (
int i = translations.size(); i > numTranslations; --i)
689 translations.removeLast();
690 }
else if (translations.size() < numTranslations) {
691 for (
int i = translations.size(); i < numTranslations; ++i)
692 translations.append(QString());
699 bool truncated =
false;
701 QLocale::Territory c;
702 languageAndTerritory(languageCode(), &l, &c);
704 if (l != QLocale::C) {
706 if (getNumerusInfo(l, c, 0, &forms, 0))
707 numPlurals = forms.size();
709 for (
int i = 0; i < m_messages.size(); ++i) {
711 QStringList tlns = msg.translations();
713 if (tlns.size() != ccnt) {
714 while (tlns.size() < ccnt)
715 tlns.append(QString());
716 while (tlns.size() > ccnt) {
720 m_messages[i].setTranslations(tlns);
724 cd.appendError(QLatin1String(
725 "Removed plural forms as the target language has less "
726 "forms.\nIf this sounds wrong, possibly the target language is "
727 "not set or recognized."));
730QString
Translator::guessLanguageCodeFromFileName(
const QString &filename)
732 QString str = filename;
733 for (
const FileFormat &format : std::as_const(registeredFileFormats())) {
734 if (str.endsWith(format.extension)) {
735 str = str.left(str.size() - format.extension.size() - 1);
739 static QRegularExpression re(
"[\\._]"_L1);
743 if (locale.language() != QLocale::C) {
745 return locale.name();
747 int pos = str.indexOf(re);
750 str = str.mid(pos + 1);
758 QStringList mergeDeps;
759 for (
const QString &dep : dependencies) {
760 if (
const auto it = std::find(m_dependencies.cbegin(), m_dependencies.cend(), dep);
761 it == m_dependencies.cend()) {
762 mergeDeps.append(dep);
765 m_dependencies.append(mergeDeps);
770 const auto dep = getDependencyName(file, format);
771 if (
const auto it = std::find(m_dependencies.cbegin(), m_dependencies.cend(), dep);
772 it != m_dependencies.cend()) {
773 m_dependencies.erase(it);
779 return m_extra.contains(key);
789 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 save(const QString &filename, ConversionData &err, const QString &format) const
bool load(const QString &filename, ConversionData &err, const QString &format)
bool unfinishedTranslationsExist() 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
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)
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)
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)
static QString getDependencyName(const QString &filename, const QString &format)
bool operator==(TranslatorMessageIdPtr tmp1, TranslatorMessageIdPtr tmp2)