1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Linguist of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include "translator.h"
30
31#include "simtexth.h"
32
33#include <iostream>
34
35#include <stdio.h>
36#ifdef Q_OS_WIN
37// required for _setmode, to avoid _O_TEXT streams...
38# include <io.h> // for _setmode
39# include <fcntl.h> // for _O_BINARY
40#endif
41
42#include <QtCore/QDebug>
43#include <QtCore/QDir>
44#include <QtCore/QFile>
45#include <QtCore/QFileInfo>
46#include <QtCore/QRegExp>
47#include <QtCore/QTextStream>
48
49#include <private/qlocale_p.h>
50#include <private/qtranslator_p.h>
51
52QT_BEGIN_NAMESPACE
53
54Translator::Translator() :
55 m_locationsType(AbsoluteLocations),
56 m_indexOk(true)
57{
58}
59
60void Translator::registerFileFormat(const FileFormat &format)
61{
62 //qDebug() << "Translator: Registering format " << format.extension;
63 QList<Translator::FileFormat> &formats = registeredFileFormats();
64 for (int i = 0; i < formats.size(); ++i)
65 if (format.fileType == formats[i].fileType && format.priority < formats[i].priority) {
66 formats.insert(i, t: format);
67 return;
68 }
69 formats.append(t: format);
70}
71
72QList<Translator::FileFormat> &Translator::registeredFileFormats()
73{
74 static QList<Translator::FileFormat> theFormats;
75 return theFormats;
76}
77
78void Translator::addIndex(int idx, const TranslatorMessage &msg) const
79{
80 if (msg.sourceText().isEmpty() && msg.id().isEmpty()) {
81 m_ctxCmtIdx[msg.context()] = idx;
82 } else {
83 m_msgIdx[TMMKey(msg)] = idx;
84 if (!msg.id().isEmpty())
85 m_idMsgIdx[msg.id()] = idx;
86 }
87}
88
89void Translator::delIndex(int idx) const
90{
91 const TranslatorMessage &msg = m_messages.at(i: idx);
92 if (msg.sourceText().isEmpty() && msg.id().isEmpty()) {
93 m_ctxCmtIdx.remove(akey: msg.context());
94 } else {
95 m_msgIdx.remove(akey: TMMKey(msg));
96 if (!msg.id().isEmpty())
97 m_idMsgIdx.remove(akey: msg.id());
98 }
99}
100
101void Translator::ensureIndexed() const
102{
103 if (!m_indexOk) {
104 m_indexOk = true;
105 m_ctxCmtIdx.clear();
106 m_idMsgIdx.clear();
107 m_msgIdx.clear();
108 for (int i = 0; i < m_messages.count(); i++)
109 addIndex(idx: i, msg: m_messages.at(i));
110 }
111}
112
113void Translator::replaceSorted(const TranslatorMessage &msg)
114{
115 int index = find(msg);
116 if (index == -1) {
117 appendSorted(msg);
118 } else {
119 delIndex(idx: index);
120 m_messages[index] = msg;
121 addIndex(idx: index, msg);
122 }
123}
124
125static QString elidedId(const QString &id, int len)
126{
127 return id.length() <= len ? id : id.left(n: len - 5) + QLatin1String("[...]");
128}
129
130static QString makeMsgId(const TranslatorMessage &msg)
131{
132 QString id = msg.context() + QLatin1String("//") + elidedId(id: msg.sourceText(), len: 100);
133 if (!msg.comment().isEmpty())
134 id += QLatin1String("//") + elidedId(id: msg.comment(), len: 30);
135 return id;
136}
137
138void Translator::extend(const TranslatorMessage &msg, ConversionData &cd)
139{
140 int index = find(msg);
141 if (index == -1) {
142 append(msg);
143 } else {
144 TranslatorMessage &emsg = m_messages[index];
145 if (emsg.sourceText().isEmpty()) {
146 delIndex(idx: index);
147 emsg.setSourceText(msg.sourceText());
148 addIndex(idx: index, msg);
149 } else if (!msg.sourceText().isEmpty() && emsg.sourceText() != msg.sourceText()) {
150 cd.appendError(error: QString::fromLatin1(str: "Contradicting source strings for message with id '%1'.")
151 .arg(a: emsg.id()));
152 return;
153 }
154 if (emsg.extras().isEmpty()) {
155 emsg.setExtras(msg.extras());
156 } else if (!msg.extras().isEmpty() && emsg.extras() != msg.extras()) {
157 cd.appendError(error: QString::fromLatin1(str: "Contradicting meta data for for %1.")
158 .arg(a: !emsg.id().isEmpty()
159 ? QString::fromLatin1(str: "message with id '%1'").arg(a: emsg.id())
160 : QString::fromLatin1(str: "message '%1'").arg(a: makeMsgId(msg))));
161 return;
162 }
163 emsg.addReferenceUniq(fileName: msg.fileName(), lineNumber: msg.lineNumber());
164 if (!msg.extraComment().isEmpty()) {
165 QString cmt = emsg.extraComment();
166 if (!cmt.isEmpty()) {
167 QStringList cmts = cmt.split(sep: QLatin1String("\n----------\n"));
168 if (!cmts.contains(str: msg.extraComment())) {
169 cmts.append(t: msg.extraComment());
170 cmt = cmts.join(sep: QLatin1String("\n----------\n"));
171 }
172 } else {
173 cmt = msg.extraComment();
174 }
175 emsg.setExtraComment(cmt);
176 }
177 }
178}
179
180void Translator::insert(int idx, const TranslatorMessage &msg)
181{
182 if (m_indexOk) {
183 if (idx == m_messages.count())
184 addIndex(idx, msg);
185 else
186 m_indexOk = false;
187 }
188 m_messages.insert(i: idx, t: msg);
189}
190
191void Translator::append(const TranslatorMessage &msg)
192{
193 insert(idx: m_messages.count(), msg);
194}
195
196void Translator::appendSorted(const TranslatorMessage &msg)
197{
198 int msgLine = msg.lineNumber();
199 if (msgLine < 0) {
200 append(msg);
201 return;
202 }
203
204 int bestIdx = 0; // Best insertion point found so far
205 int bestScore = 0; // Its category: 0 = no hit, 1 = pre or post, 2 = middle
206 int bestSize = 0; // The length of the region. Longer is better within one category.
207
208 // The insertion point to use should this region turn out to be the best one so far
209 int thisIdx = 0;
210 int thisScore = 0;
211 int thisSize = 0;
212 // Working vars
213 int prevLine = 0;
214 int curIdx = 0;
215 foreach (const TranslatorMessage &mit, m_messages) {
216 bool sameFile = mit.fileName() == msg.fileName() && mit.context() == msg.context();
217 int curLine;
218 if (sameFile && (curLine = mit.lineNumber()) >= prevLine) {
219 if (msgLine >= prevLine && msgLine < curLine) {
220 thisIdx = curIdx;
221 thisScore = thisSize ? 2 : 1;
222 }
223 ++thisSize;
224 prevLine = curLine;
225 } else {
226 if (thisSize) {
227 if (!thisScore) {
228 thisIdx = curIdx;
229 thisScore = 1;
230 }
231 if (thisScore > bestScore || (thisScore == bestScore && thisSize > bestSize)) {
232 bestIdx = thisIdx;
233 bestScore = thisScore;
234 bestSize = thisSize;
235 }
236 thisScore = 0;
237 thisSize = sameFile ? 1 : 0;
238 prevLine = 0;
239 }
240 }
241 ++curIdx;
242 }
243 if (thisSize && !thisScore) {
244 thisIdx = curIdx;
245 thisScore = 1;
246 }
247 if (thisScore > bestScore || (thisScore == bestScore && thisSize > bestSize))
248 insert(idx: thisIdx, msg);
249 else if (bestScore)
250 insert(idx: bestIdx, msg);
251 else
252 append(msg);
253}
254
255static QString guessFormat(const QString &filename, const QString &format)
256{
257 if (format != QLatin1String("auto"))
258 return format;
259
260 foreach (const Translator::FileFormat &fmt, Translator::registeredFileFormats()) {
261 if (filename.endsWith(s: QLatin1Char('.') + fmt.extension, cs: Qt::CaseInsensitive))
262 return fmt.extension;
263 }
264
265 // the default format.
266 // FIXME: change to something more widely distributed later.
267 return QLatin1String("ts");
268}
269
270bool Translator::load(const QString &filename, ConversionData &cd, const QString &format)
271{
272 cd.m_sourceDir = QFileInfo(filename).absoluteDir();
273 cd.m_sourceFileName = filename;
274
275 QFile file;
276 if (filename.isEmpty() || filename == QLatin1String("-")) {
277#ifdef Q_OS_WIN
278 // QFile is broken for text files
279 ::_setmode(0, _O_BINARY);
280#endif
281 if (!file.open(stdin, ioFlags: QIODevice::ReadOnly)) {
282 cd.appendError(error: QString::fromLatin1(str: "Cannot open stdin!? (%1)")
283 .arg(a: file.errorString()));
284 return false;
285 }
286 } else {
287 file.setFileName(filename);
288 if (!file.open(flags: QIODevice::ReadOnly)) {
289 cd.appendError(error: QString::fromLatin1(str: "Cannot open %1: %2")
290 .arg(args: filename, args: file.errorString()));
291 return false;
292 }
293 }
294
295 QString fmt = guessFormat(filename, format);
296
297 foreach (const FileFormat &format, registeredFileFormats()) {
298 if (fmt == format.extension) {
299 if (format.loader)
300 return (*format.loader)(*this, file, cd);
301 cd.appendError(error: QString(QLatin1String("No loader for format %1 found"))
302 .arg(a: fmt));
303 return false;
304 }
305 }
306
307 cd.appendError(error: QString(QLatin1String("Unknown format %1 for file %2"))
308 .arg(a1: format, a2: filename));
309 return false;
310}
311
312
313bool Translator::save(const QString &filename, ConversionData &cd, const QString &format) const
314{
315 QFile file;
316 if (filename.isEmpty() || filename == QLatin1String("-")) {
317#ifdef Q_OS_WIN
318 // QFile is broken for text files
319 ::_setmode(1, _O_BINARY);
320#endif
321 if (!file.open(stdout, ioFlags: QIODevice::WriteOnly)) {
322 cd.appendError(error: QString::fromLatin1(str: "Cannot open stdout!? (%1)")
323 .arg(a: file.errorString()));
324 return false;
325 }
326 } else {
327 file.setFileName(filename);
328 if (!file.open(flags: QIODevice::WriteOnly)) {
329 cd.appendError(error: QString::fromLatin1(str: "Cannot create %1: %2")
330 .arg(args: filename, args: file.errorString()));
331 return false;
332 }
333 }
334
335 QString fmt = guessFormat(filename, format);
336 cd.m_targetDir = QFileInfo(filename).absoluteDir();
337
338 foreach (const FileFormat &format, registeredFileFormats()) {
339 if (fmt == format.extension) {
340 if (format.saver)
341 return (*format.saver)(*this, file, cd);
342 cd.appendError(error: QString(QLatin1String("Cannot save %1 files")).arg(a: fmt));
343 return false;
344 }
345 }
346
347 cd.appendError(error: QString(QLatin1String("Unknown format %1 for file %2"))
348 .arg(a: format).arg(a: filename));
349 return false;
350}
351
352QString Translator::makeLanguageCode(QLocale::Language language, QLocale::Country country)
353{
354 QString result = QLocalePrivate::languageToCode(language);
355 if (language != QLocale::C && country != QLocale::AnyCountry) {
356 result.append(c: QLatin1Char('_'));
357 result.append(s: QLocalePrivate::countryToCode(country));
358 }
359 return result;
360}
361
362void Translator::languageAndCountry(const QString &languageCode,
363 QLocale::Language *lang, QLocale::Country *country)
364{
365 QLocale::Script script;
366 QLocalePrivate::getLangAndCountry(name: languageCode, lang&: *lang, script, cntry&: *country);
367}
368
369int Translator::find(const TranslatorMessage &msg) const
370{
371 ensureIndexed();
372 if (msg.id().isEmpty())
373 return m_msgIdx.value(akey: TMMKey(msg), adefaultValue: -1);
374 int i = m_idMsgIdx.value(akey: msg.id(), adefaultValue: -1);
375 if (i >= 0)
376 return i;
377 i = m_msgIdx.value(akey: TMMKey(msg), adefaultValue: -1);
378 // If both have an id, then find only by id.
379 return i >= 0 && m_messages.at(i).id().isEmpty() ? i : -1;
380}
381
382int Translator::find(const QString &context,
383 const QString &comment, const TranslatorMessage::References &refs) const
384{
385 if (!refs.isEmpty()) {
386 for (TMM::ConstIterator it = m_messages.constBegin(); it != m_messages.constEnd(); ++it) {
387 if (it->context() == context && it->comment() == comment) {
388 foreach (const TranslatorMessage::Reference &itref, it->allReferences()) {
389 foreach (const TranslatorMessage::Reference &ref, refs) {
390 if (itref == ref)
391 return it - m_messages.constBegin();
392 }
393 }
394 }
395 }
396 }
397 return -1;
398}
399
400int Translator::find(const QString &context) const
401{
402 ensureIndexed();
403 return m_ctxCmtIdx.value(akey: context, adefaultValue: -1);
404}
405
406void Translator::stripObsoleteMessages()
407{
408 for (TMM::Iterator it = m_messages.begin(); it != m_messages.end(); )
409 if (it->type() == TranslatorMessage::Obsolete || it->type() == TranslatorMessage::Vanished)
410 it = m_messages.erase(it);
411 else
412 ++it;
413 m_indexOk = false;
414}
415
416void Translator::stripFinishedMessages()
417{
418 for (TMM::Iterator it = m_messages.begin(); it != m_messages.end(); )
419 if (it->type() == TranslatorMessage::Finished)
420 it = m_messages.erase(it);
421 else
422 ++it;
423 m_indexOk = false;
424}
425
426void Translator::stripUntranslatedMessages()
427{
428 for (TMM::Iterator it = m_messages.begin(); it != m_messages.end(); )
429 if (!it->isTranslated())
430 it = m_messages.erase(it);
431 else
432 ++it;
433 m_indexOk = false;
434}
435
436bool Translator::translationsExist()
437{
438 for (TMM::Iterator it = m_messages.begin(); it != m_messages.end(); ) {
439 if (it->isTranslated())
440 return true;
441 else
442 ++it;
443 }
444 return false;
445}
446
447void Translator::stripEmptyContexts()
448{
449 for (TMM::Iterator it = m_messages.begin(); it != m_messages.end();)
450 if (it->sourceText() == QLatin1String(ContextComment))
451 it = m_messages.erase(it);
452 else
453 ++it;
454 m_indexOk = false;
455}
456
457void Translator::stripNonPluralForms()
458{
459 for (TMM::Iterator it = m_messages.begin(); it != m_messages.end(); )
460 if (!it->isPlural())
461 it = m_messages.erase(it);
462 else
463 ++it;
464 m_indexOk = false;
465}
466
467void Translator::stripIdenticalSourceTranslations()
468{
469 for (TMM::Iterator it = m_messages.begin(); it != m_messages.end(); ) {
470 // we need to have just one translation, and it be equal to the source
471 if (it->translations().count() == 1 && it->translation() == it->sourceText())
472 it = m_messages.erase(it);
473 else
474 ++it;
475 }
476 m_indexOk = false;
477}
478
479void Translator::dropTranslations()
480{
481 for (TMM::Iterator it = m_messages.begin(); it != m_messages.end(); ++it) {
482 if (it->type() == TranslatorMessage::Finished)
483 it->setType(TranslatorMessage::Unfinished);
484 it->setTranslation(QString());
485 }
486}
487
488void Translator::dropUiLines()
489{
490 QString uiXt = QLatin1String(".ui");
491 QString juiXt = QLatin1String(".jui");
492 for (TMM::Iterator it = m_messages.begin(); it != m_messages.end(); ++it) {
493 QHash<QString, int> have;
494 QList<TranslatorMessage::Reference> refs;
495 foreach (const TranslatorMessage::Reference &itref, it->allReferences()) {
496 const QString &fn = itref.fileName();
497 if (fn.endsWith(s: uiXt) || fn.endsWith(s: juiXt)) {
498 if (++have[fn] == 1)
499 refs.append(t: TranslatorMessage::Reference(fn, -1));
500 } else {
501 refs.append(t: itref);
502 }
503 }
504 it->setReferences(refs);
505 }
506}
507
508struct TranslatorMessageIdPtr {
509 explicit TranslatorMessageIdPtr(const TranslatorMessage &tm)
510 {
511 ptr = &tm;
512 }
513
514 inline const TranslatorMessage *operator->() const
515 {
516 return ptr;
517 }
518
519 const TranslatorMessage *ptr;
520};
521
522Q_DECLARE_TYPEINFO(TranslatorMessageIdPtr, Q_MOVABLE_TYPE);
523
524inline int qHash(TranslatorMessageIdPtr tmp)
525{
526 return qHash(key: tmp->id());
527}
528
529inline bool operator==(TranslatorMessageIdPtr tmp1, TranslatorMessageIdPtr tmp2)
530{
531 return tmp1->id() == tmp2->id();
532}
533
534struct TranslatorMessageContentPtr {
535 explicit TranslatorMessageContentPtr(const TranslatorMessage &tm)
536 {
537 ptr = &tm;
538 }
539
540 inline const TranslatorMessage *operator->() const
541 {
542 return ptr;
543 }
544
545 const TranslatorMessage *ptr;
546};
547
548Q_DECLARE_TYPEINFO(TranslatorMessageContentPtr, Q_MOVABLE_TYPE);
549
550inline int qHash(TranslatorMessageContentPtr tmp)
551{
552 int hash = qHash(key: tmp->context()) ^ qHash(key: tmp->sourceText());
553 if (!tmp->sourceText().isEmpty())
554 // Special treatment for context comments (empty source).
555 hash ^= qHash(key: tmp->comment());
556 return hash;
557}
558
559inline bool operator==(TranslatorMessageContentPtr tmp1, TranslatorMessageContentPtr tmp2)
560{
561 if (tmp1->context() != tmp2->context() || tmp1->sourceText() != tmp2->sourceText())
562 return false;
563 // Special treatment for context comments (empty source).
564 if (tmp1->sourceText().isEmpty())
565 return true;
566 return tmp1->comment() == tmp2->comment();
567}
568
569Translator::Duplicates Translator::resolveDuplicates()
570{
571 Duplicates dups;
572 QHash<TranslatorMessageIdPtr, int> idRefs;
573 QHash<TranslatorMessageContentPtr, int> contentRefs;
574 for (int i = 0; i < m_messages.count();) {
575 const TranslatorMessage &msg = m_messages.at(i);
576 TranslatorMessage *omsg;
577 int oi;
578 QSet<int> *pDup;
579 if (!msg.id().isEmpty()) {
580 QHash<TranslatorMessageIdPtr, int>::ConstIterator it =
581 idRefs.constFind(akey: TranslatorMessageIdPtr(msg));
582 if (it != idRefs.constEnd()) {
583 oi = *it;
584 omsg = &m_messages[oi];
585 pDup = &dups.byId;
586 goto gotDupe;
587 }
588 }
589 {
590 QHash<TranslatorMessageContentPtr, int>::ConstIterator it =
591 contentRefs.constFind(akey: TranslatorMessageContentPtr(msg));
592 if (it != contentRefs.constEnd()) {
593 oi = *it;
594 omsg = &m_messages[oi];
595 if (msg.id().isEmpty() || omsg->id().isEmpty()) {
596 if (!msg.id().isEmpty() && omsg->id().isEmpty()) {
597 omsg->setId(msg.id());
598 idRefs[TranslatorMessageIdPtr(*omsg)] = oi;
599 }
600 pDup = &dups.byContents;
601 goto gotDupe;
602 }
603 // This is really a content dupe, but with two distinct IDs.
604 }
605 }
606 if (!msg.id().isEmpty())
607 idRefs[TranslatorMessageIdPtr(msg)] = i;
608 contentRefs[TranslatorMessageContentPtr(msg)] = i;
609 ++i;
610 continue;
611 gotDupe:
612 pDup->insert(value: oi);
613 if (!omsg->isTranslated() && msg.isTranslated())
614 omsg->setTranslations(msg.translations());
615 m_indexOk = false;
616 m_messages.removeAt(i);
617 }
618 return dups;
619}
620
621void Translator::reportDuplicates(const Duplicates &dupes,
622 const QString &fileName, bool verbose)
623{
624 if (!dupes.byId.isEmpty() || !dupes.byContents.isEmpty()) {
625 std::cerr << "Warning: dropping duplicate messages in '" << qPrintable(fileName);
626 if (!verbose) {
627 std::cerr << "'\n(try -verbose for more info).\n";
628 } else {
629 std::cerr << "':\n";
630 foreach (int i, dupes.byId)
631 std::cerr << "\n* ID: " << qPrintable(message(i).id()) << std::endl;
632 foreach (int j, dupes.byContents) {
633 const TranslatorMessage &msg = message(i: j);
634 std::cerr << "\n* Context: " << qPrintable(msg.context())
635 << "\n* Source: " << qPrintable(msg.sourceText()) << std::endl;
636 if (!msg.comment().isEmpty())
637 std::cerr << "* Comment: " << qPrintable(msg.comment()) << std::endl;
638 }
639 std::cerr << std::endl;
640 }
641 }
642}
643
644// Used by lupdate to be able to search using absolute paths during merging
645void Translator::makeFileNamesAbsolute(const QDir &originalPath)
646{
647 for (TMM::iterator it = m_messages.begin(); it != m_messages.end(); ++it) {
648 TranslatorMessage &msg = *it;
649 TranslatorMessage::References refs = msg.allReferences();
650 msg.setReferences(TranslatorMessage::References());
651 foreach (const TranslatorMessage::Reference &ref, refs) {
652 QString fileName = ref.fileName();
653 QFileInfo fi (fileName);
654 if (fi.isRelative())
655 fileName = originalPath.absoluteFilePath(fileName);
656 msg.addReference(fileName, lineNumber: ref.lineNumber());
657 }
658 }
659}
660
661QList<TranslatorMessage> Translator::messages() const
662{
663 return m_messages;
664}
665
666QStringList Translator::normalizedTranslations(const TranslatorMessage &msg, int numPlurals)
667{
668 QStringList translations = msg.translations();
669 int numTranslations = msg.isPlural() ? numPlurals : 1;
670
671 // make sure that the stringlist always have the size of the
672 // language's current numerus, or 1 if its not plural
673 if (translations.count() > numTranslations) {
674 for (int i = translations.count(); i > numTranslations; --i)
675 translations.removeLast();
676 } else if (translations.count() < numTranslations) {
677 for (int i = translations.count(); i < numTranslations; ++i)
678 translations.append(t: QString());
679 }
680 return translations;
681}
682
683void Translator::normalizeTranslations(ConversionData &cd)
684{
685 bool truncated = false;
686 QLocale::Language l;
687 QLocale::Country c;
688 languageAndCountry(languageCode: languageCode(), lang: &l, country: &c);
689 int numPlurals = 1;
690 if (l != QLocale::C) {
691 QStringList forms;
692 if (getNumerusInfo(language: l, country: c, rules: 0, forms: &forms, gettextRules: 0))
693 numPlurals = forms.count(); // includes singular
694 }
695 for (int i = 0; i < m_messages.count(); ++i) {
696 const TranslatorMessage &msg = m_messages.at(i);
697 QStringList tlns = msg.translations();
698 int ccnt = msg.isPlural() ? numPlurals : 1;
699 if (tlns.count() != ccnt) {
700 while (tlns.count() < ccnt)
701 tlns.append(t: QString());
702 while (tlns.count() > ccnt) {
703 tlns.removeLast();
704 truncated = true;
705 }
706 m_messages[i].setTranslations(tlns);
707 }
708 }
709 if (truncated)
710 cd.appendError(error: QLatin1String(
711 "Removed plural forms as the target language has less "
712 "forms.\nIf this sounds wrong, possibly the target language is "
713 "not set or recognized."));
714}
715
716QString Translator::guessLanguageCodeFromFileName(const QString &filename)
717{
718 QString str = filename;
719 foreach (const FileFormat &format, registeredFileFormats()) {
720 if (str.endsWith(s: format.extension)) {
721 str = str.left(n: str.size() - format.extension.size() - 1);
722 break;
723 }
724 }
725 static QRegExp re(QLatin1String("[\\._]"));
726 while (true) {
727 QLocale locale(str);
728 //qDebug() << "LANGUAGE FROM " << str << "LANG: " << locale.language();
729 if (locale.language() != QLocale::C) {
730 //qDebug() << "FOUND " << locale.name();
731 return locale.name();
732 }
733 int pos = str.indexOf(re);
734 if (pos == -1)
735 break;
736 str = str.mid(position: pos + 1);
737 }
738 //qDebug() << "LANGUAGE GUESSING UNSUCCESSFUL";
739 return QString();
740}
741
742bool Translator::hasExtra(const QString &key) const
743{
744 return m_extra.contains(akey: key);
745}
746
747QString Translator::extra(const QString &key) const
748{
749 return m_extra[key];
750}
751
752void Translator::setExtra(const QString &key, const QString &value)
753{
754 m_extra[key] = value;
755}
756
757void Translator::dump() const
758{
759 for (int i = 0; i != messageCount(); ++i)
760 message(i).dump();
761}
762
763QT_END_NAMESPACE
764

source code of qttools/src/linguist/shared/translator.cpp