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
qtranslator.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
5#include "qplatformdefs.h"
6
7#include "qtranslator.h"
8
9#ifndef QT_NO_TRANSLATION
10
11#include "qfileinfo.h"
12#include "qstring.h"
13#include "qstringlist.h"
16#include "qdatastream.h"
17#include "qendian.h"
18#include "qfile.h"
19#include "qalgorithms.h"
20#include "qtranslator_p.h"
21#include "qlocale.h"
22#include "qlogging.h"
24#include "qdebug.h"
25#include "qendian.h"
26#include "qresource.h"
27
28#if defined(Q_OS_UNIX) && !defined(Q_OS_INTEGRITY)
29# define QT_USE_MMAP
30# include "private/qcore_unix_p.h"
31// for mmap
32# include <sys/mman.h>
33#endif
34
35#include <stdlib.h>
36#include <new>
37
38#include "qobject_p.h"
39
40#include <vector>
41#include <memory>
42
43#include <QtCore/qmutex.h>
44
46
47Q_STATIC_LOGGING_CATEGORY(lcTranslator, "qt.core.qtranslator")
48
49namespace {
52}
53
54/*
55$ mcookie
563cb86418caef9c95cd211cbf60a1bddd
57$
58*/
59
60// magic number for the file
61static const int MagicLength = 16;
62static const uchar magic[MagicLength] = {
63 0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95,
64 0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd
65};
66
67static inline QString dotQmLiteral() { return QStringLiteral(".qm"); }
68
69static bool match(const uchar *found, uint foundLen, const char *target, uint targetLen)
70{
71 // catch the case if \a found has a zero-terminating symbol and \a len includes it.
72 // (normalize it to be without the zero-terminating symbol)
73 if (foundLen > 0 && found[foundLen-1] == '\0')
74 --foundLen;
75 return ((targetLen == foundLen) && memcmp(found, target, foundLen) == 0);
76}
77
78static void elfHash_continue(const char *name, uint &h)
79{
80 const uchar *k;
81 uint g;
82
83 k = (const uchar *) name;
84 while (*k) {
85 h = (h << 4) + *k++;
86 if ((g = (h & 0xf0000000)) != 0)
87 h ^= g >> 24;
88 h &= ~g;
89 }
90}
91
92static void elfHash_finish(uint &h)
93{
94 if (!h)
95 h = 1;
96}
97
98static uint elfHash(const char *name)
99{
100 uint hash = 0;
101 elfHash_continue(name, hash);
103 return hash;
104}
105
106/*
107 \internal
108
109 Determines whether \a rules are valid "numerus rules". Test input with this
110 function before calling numerusHelper, below.
111 */
112static bool isValidNumerusRules(const uchar *rules, uint rulesSize)
113{
114 // Disabled computation of maximum numerus return value
115 // quint32 numerus = 0;
116
117 if (rulesSize == 0)
118 return true;
119
120 quint32 offset = 0;
121 do {
122 uchar opcode = rules[offset];
123 uchar op = opcode & Q_OP_MASK;
124
125 if (opcode & 0x80)
126 return false; // Bad op
127
128 if (++offset == rulesSize)
129 return false; // Missing operand
130
131 // right operand
132 ++offset;
133
134 switch (op)
135 {
136 case Q_EQ:
137 case Q_LT:
138 case Q_LEQ:
139 break;
140
141 case Q_BETWEEN:
142 if (offset != rulesSize) {
143 // third operand
144 ++offset;
145 break;
146 }
147 return false; // Missing operand
148
149 default:
150 return false; // Bad op (0)
151 }
152
153 // ++numerus;
154 if (offset == rulesSize)
155 return true;
156
157 } while (((rules[offset] == Q_AND)
158 || (rules[offset] == Q_OR)
159 || (rules[offset] == Q_NEWRULE))
160 && ++offset != rulesSize);
161
162 // Bad op
163 return false;
164}
165
166/*
167 \internal
168
169 This function does no validation of input and assumes it is well-behaved,
170 these assumptions can be checked with isValidNumerusRules, above.
171
172 Determines which translation to use based on the value of \a n. The return
173 value is an index identifying the translation to be used.
174
175 \a rules is a character array of size \a rulesSize containing bytecode that
176 operates on the value of \a n and ultimately determines the result.
177
178 This function has O(1) space and O(rulesSize) time complexity.
179 */
180static uint numerusHelper(int n, const uchar *rules, uint rulesSize)
181{
182 uint result = 0;
183 uint i = 0;
184
185 if (rulesSize == 0)
186 return 0;
187
188 for (;;) {
189 bool orExprTruthValue = false;
190
191 for (;;) {
192 bool andExprTruthValue = true;
193
194 for (;;) {
195 bool truthValue = true;
196 int opcode = rules[i++];
197
198 int leftOperand = n;
199 if (opcode & Q_MOD_10) {
200 leftOperand %= 10;
201 } else if (opcode & Q_MOD_100) {
202 leftOperand %= 100;
203 } else if (opcode & Q_LEAD_1000) {
204 while (leftOperand >= 1000)
205 leftOperand /= 1000;
206 }
207
208 int op = opcode & Q_OP_MASK;
209 int rightOperand = rules[i++];
210
211 switch (op) {
212 case Q_EQ:
213 truthValue = (leftOperand == rightOperand);
214 break;
215 case Q_LT:
216 truthValue = (leftOperand < rightOperand);
217 break;
218 case Q_LEQ:
219 truthValue = (leftOperand <= rightOperand);
220 break;
221 case Q_BETWEEN:
222 int bottom = rightOperand;
223 int top = rules[i++];
224 truthValue = (leftOperand >= bottom && leftOperand <= top);
225 }
226
227 if (opcode & Q_NOT)
228 truthValue = !truthValue;
229
230 andExprTruthValue = andExprTruthValue && truthValue;
231
232 if (i == rulesSize || rules[i] != Q_AND)
233 break;
234 ++i;
235 }
236
237 orExprTruthValue = orExprTruthValue || andExprTruthValue;
238
239 if (i == rulesSize || rules[i] != Q_OR)
240 break;
241 ++i;
242 }
243
244 if (orExprTruthValue)
245 return result;
246
247 ++result;
248
249 if (i == rulesSize)
250 return result;
251
252 i++; // Q_NEWRULE
253 }
254
255 Q_ASSERT(false);
256 return 0;
257}
258
260{
261 Q_DECLARE_PUBLIC(QTranslator)
262public:
263 enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88, Dependencies = 0x96, Language = 0xa7 };
264
265 mutable QMutex lock;
266
268#if defined(QT_USE_MMAP)
269 used_mmap(0),
270#endif
271 unmapPointer(nullptr), unmapLength(0), resource(nullptr),
272 messageArray(nullptr), offsetArray(nullptr), contextArray(nullptr), numerusRulesArray(nullptr),
274
275#if defined(QT_USE_MMAP)
276 bool used_mmap : 1;
277#endif
278 char *unmapPointer; // used memory (mmap, new or resource file)
280
281 // The resource object in case we loaded the translations from a resource
283
284 // used if the translator has dependencies
286
287 // Pointers and offsets into unmapPointer[unmapLength] array, or user
288 // provided data array
297
298 QString language;
299 QString filePath;
300
301 bool load_translation(const QStringList &languages, const QString &filename, const QString &prefix,
302 const QString &directory, const QString &suffix);
303 bool do_load(const QString &filename, const QString &directory);
304 bool do_load(const uchar *data, qsizetype len, const QString &directory);
305 QString do_translate(const char *context, const char *sourceText, const char *comment,
306 int n) const;
307 void clear();
308};
309
310/*!
311 \class QTranslator
312 \inmodule QtCore
313
314 \brief The QTranslator class provides internationalization support for text
315 output.
316
317 \ingroup i18n
318
319 An object of this class contains a set of translations from a
320 source language to a target language. QTranslator provides
321 functions to look up translations in a translation file.
322 Translation files are created using \l{Qt Linguist}.
323
324 The most common use of QTranslator is to: load a translation
325 file, and install it using QCoreApplication::installTranslator().
326
327 Here's an example \c main() function using the
328 QTranslator:
329
330 \snippet hellotrmain.cpp 0
331
332 Note that the translator must be created \e before the
333 application's widgets.
334
335 Most applications will never need to do anything else with this
336 class. The other functions provided by this class are useful for
337 applications that work on translator files.
338
339 \section1 Looking up Translations
340
341 It is possible to look up a translation using translate() (as tr()
342 and QCoreApplication::translate() do). The translate() function takes
343 up to three parameters:
344
345 \list
346 \li The \e context - usually the class name for the tr() caller.
347 \li The \e {source text} - usually the argument to tr().
348 \li The \e disambiguation - an optional string that helps disambiguate
349 different uses of the same text in the same context.
350 \endlist
351
352 For example, the "Cancel" in a dialog might have "Anuluj" when the
353 program runs in Polish (in this case the source text would be
354 "Cancel"). The context would (normally) be the dialog's class
355 name; there would normally be no comment, and the translated text
356 would be "Anuluj".
357
358 But it's not always so simple. The Spanish version of a printer
359 dialog with settings for two-sided printing and binding would
360 probably require both "Activado" and "Activada" as translations
361 for "Enabled". In this case the source text would be "Enabled" in
362 both cases, and the context would be the dialog's class name, but
363 the two items would have disambiguations such as "two-sided printing"
364 for one and "binding" for the other. The disambiguation enables the
365 translator to choose the appropriate gender for the Spanish version,
366 and enables Qt to distinguish between translations.
367
368 \section1 Using Multiple Translations
369
370 Multiple translation files can be installed in an application.
371 Translations are searched for in the reverse order in which they were
372 installed, so the most recently installed translation file is searched
373 for translations first and the earliest translation file is searched
374 last. The search stops as soon as a translation containing a matching
375 string is found.
376
377 This mechanism makes it possible for a specific translation to be
378 "selected" or given priority over the others; simply uninstall the
379 translator from the application by passing it to the
380 QCoreApplication::removeTranslator() function and reinstall it with
381 QCoreApplication::installTranslator(). It will then be the first
382 translation to be searched for matching strings.
383
384 \section1 Security Considerations
385
386 Only install translation files from trusted sources.
387
388 Translation files are binary files that are generated from text-based
389 translation source files. The format of these binary files is strictly
390 defined by Qt and any manipulation of the data in the binary file may
391 crash the application when the file is loaded. Furthermore, even well-formed
392 translation files may contain misleading or malicious translations.
393
394 \sa QCoreApplication::installTranslator(), QCoreApplication::removeTranslator(),
395 QObject::tr(), QCoreApplication::translate(), {I18N Example},
396 {Hello tr() Example}, {Arrow Pad Example}, {Troll Print Example}
397*/
398
399/*!
400 Constructs an empty message file object with parent \a parent that
401 is not connected to any file.
402*/
403
404QTranslator::QTranslator(QObject * parent)
405 : QObject(*new QTranslatorPrivate, parent)
406{
407}
408
409/*!
410 Destroys the object and frees any allocated resources.
411*/
412
413QTranslator::~QTranslator()
414{
415 if (QCoreApplication::instanceExists())
416 QCoreApplication::removeTranslator(this);
417 Q_D(QTranslator);
418 d->clear();
419}
420
421/*!
422
423 Loads \a filename + \a suffix (".qm" if the \a suffix is not
424 specified), which may be an absolute file name or relative to \a
425 directory. Returns \c true if the translation is successfully loaded;
426 otherwise returns \c false.
427
428 If \a directory is not specified, the current directory is used
429 (i.e., as \l{QDir::}{currentPath()}).
430
431 The previous contents of this translator object are discarded.
432
433 If the file name does not exist, other file names are tried
434 in the following order:
435
436 \list 1
437 \li File name without \a suffix appended.
438 \li File name with text after a character in \a search_delimiters
439 stripped ("_." is the default for \a search_delimiters if it is
440 an empty string) and \a suffix.
441 \li File name stripped without \a suffix appended.
442 \li File name stripped further, etc.
443 \endlist
444
445 For example, an application running in the fr_CA locale
446 (French-speaking Canada) might call load("foo.fr_ca",
447 "/opt/foolib"). load() would then try to open the first existing
448 readable file from this list:
449
450 \list 1
451 \li \c /opt/foolib/foo.fr_ca.qm
452 \li \c /opt/foolib/foo.fr_ca
453 \li \c /opt/foolib/foo.fr.qm
454 \li \c /opt/foolib/foo.fr
455 \li \c /opt/foolib/foo.qm
456 \li \c /opt/foolib/foo
457 \endlist
458
459 Usually, it is better to use the QTranslator::load(const QLocale &,
460 const QString &, const QString &, const QString &, const QString &)
461 function instead, because it uses \l{QLocale::uiLanguages()} and not simply
462 the locale name, which refers to the formatting of dates and numbers and not
463 necessarily the UI language.
464*/
465
466bool QTranslator::load(const QString & filename, const QString & directory,
467 const QString & search_delimiters,
468 const QString & suffix)
469{
470 Q_D(QTranslator);
471 QMutexLocker locker(&d->lock);
472 d->clear();
473
474 QString prefix;
475 if (QFileInfo(filename).isRelative()) {
476 prefix = directory;
477 if (prefix.size() && !prefix.endsWith(u'/'))
478 prefix += u'/';
479 }
480
481 const QString suffixOrDotQM = suffix.isNull() ? dotQmLiteral() : suffix;
482 QStringView fname(filename);
483 QString realname;
484 const QString delims = search_delimiters.isNull() ? QStringLiteral("_.") : search_delimiters;
485
486 for (;;) {
487 QFileInfo fi;
488
489 realname = prefix + fname + suffixOrDotQM;
490 fi.setFile(realname);
491 if (fi.isReadable() && fi.isFile())
492 break;
493
494 realname = prefix + fname;
495 fi.setFile(realname);
496 if (fi.isReadable() && fi.isFile())
497 break;
498
499 qsizetype rightmost = 0;
500 for (auto ch : delims)
501 rightmost = std::max(rightmost, fname.lastIndexOf(ch));
502
503 // no truncations? fail
504 if (rightmost == 0)
505 return false;
506
507 fname.truncate(rightmost);
508 }
509
510 // realname is now the fully qualified name of a readable file.
511 return d->do_load(realname, directory);
512}
513
514bool QTranslatorPrivate::do_load(const QString &realname, const QString &directory)
515{
516 QTranslatorPrivate *d = this;
517 bool ok = false;
518
519 if (realname.startsWith(u':')) {
520 // If the translation is in a non-compressed resource file, the data is already in
521 // memory, so no need to use QFile to copy it again.
522 Q_ASSERT(!d->resource);
523 d->resource = std::make_unique<QResource>(realname);
524 if (resource->isValid() && resource->compressionAlgorithm() == QResource::NoCompression
525 && resource->size() >= MagicLength
526 && !memcmp(resource->data(), magic, MagicLength)) {
527 d->unmapLength = resource->size();
528 d->unmapPointer = reinterpret_cast<char *>(const_cast<uchar *>(resource->data()));
529#if defined(QT_USE_MMAP)
530 d->used_mmap = false;
531#endif
532 ok = true;
533 } else {
534 resource = nullptr;
535 }
536 }
537
538 if (!ok) {
539 QFile file(realname);
540 if (!file.open(QIODevice::ReadOnly | QIODevice::Unbuffered))
541 return false;
542
543 qint64 fileSize = file.size();
544 if (fileSize < MagicLength || fileSize > std::numeric_limits<qsizetype>::max())
545 return false;
546
547 {
548 char magicBuffer[MagicLength];
549 if (MagicLength != file.read(magicBuffer, MagicLength)
550 || memcmp(magicBuffer, magic, MagicLength))
551 return false;
552 }
553
554 d->unmapLength = qsizetype(fileSize);
555
556#ifdef QT_USE_MMAP
557
558#ifndef MAP_FILE
559#define MAP_FILE 0
560#endif
561#ifndef MAP_FAILED
562#define MAP_FAILED reinterpret_cast<void *>(-1)
563#endif
564
565 int fd = file.handle();
566 if (fd >= 0) {
567 int protection = PROT_READ; // read-only memory
568 int flags = MAP_FILE | MAP_PRIVATE; // swap-backed map from file
569 void *ptr = QT_MMAP(nullptr, d->unmapLength,// any address, whole file
570 protection, flags,
571 fd, 0); // from offset 0 of fd
572 if (ptr != MAP_FAILED) {
573 file.close();
574 d->used_mmap = true;
575 d->unmapPointer = static_cast<char *>(ptr);
576 ok = true;
577 }
578 }
579#endif // QT_USE_MMAP
580
581 if (!ok) {
582 d->unmapPointer = new (std::nothrow) char[d->unmapLength];
583 if (d->unmapPointer) {
584 file.seek(0);
585 qint64 readResult = file.read(d->unmapPointer, d->unmapLength);
586 if (readResult == qint64(unmapLength))
587 ok = true;
588 }
589 }
590 }
591
592 if (ok) {
593 const QString base_dir =
594 !directory.isEmpty() ? directory : QFileInfo(realname).absolutePath();
595 if (d->do_load(reinterpret_cast<const uchar *>(d->unmapPointer), d->unmapLength,
596 base_dir)) {
597 d->filePath = realname;
598 return true;
599 }
600 }
601
602#if defined(QT_USE_MMAP)
603 if (used_mmap) {
604 used_mmap = false;
605 munmap(unmapPointer, unmapLength);
606 } else
607#endif
608 if (!d->resource)
609 delete [] unmapPointer;
610
611 d->resource = nullptr;
612 d->unmapPointer = nullptr;
613 d->unmapLength = 0;
614
615 return false;
616}
617
618bool QTranslatorPrivate::load_translation(const QStringList &languages,
619 const QString &filename,
620 const QString &prefix,
621 const QString &directory,
622 const QString &suffix)
623{
624 qCDebug(lcTranslator).noquote().nospace() << "Searching translation for "
625 << filename << prefix << languages << suffix
626 << " in " << directory;
627 QString path;
628 if (QFileInfo(filename).isRelative()) {
629 path = directory;
630 if (!path.isEmpty() && !path.endsWith(u'/'))
631 path += u'/';
632 }
633 const QString suffixOrDotQM = suffix.isNull() ? dotQmLiteral() : suffix;
634
635 QString realname;
636 realname += path + filename + prefix; // using += in the hope for some reserve capacity
637 const qsizetype realNameBaseSize = realname.size();
638
639 // see http://www.unicode.org/reports/tr35/#LanguageMatching for inspiration
640
641 // For each name returned by locale.uiLanguages(), also try a lowercase
642 // version. Since these languages are used to create file names, this is
643 // important on case-sensitive file systems, where otherwise a file called
644 // something like "prefix_en_us.qm" won't be found under the "en_US"
645 // locale. Note that the Qt resource system is always case-sensitive, even
646 // on Windows (in other words: this codepath is *not* UNIX-only).
647
648 auto loadFile = [this, &realname, &directory] { return do_load(realname, directory); };
649
650 for (const QString &localeName : languages) {
651 QString loc = localeName;
652 // First try this given name, then in lower-case form (if different):
653 while (true) {
654 // First, try with suffix:
655 realname += loc + suffixOrDotQM;
656 if (loadFile())
657 return true;
658
659 // Next, try without:
660 realname.truncate(realNameBaseSize + loc.size());
661 if (loadFile())
662 return true;
663 // Reset realname:
664 realname.truncate(realNameBaseSize);
665
666 // Non-trivial while-loop condition:
667 if (loc != localeName) // loc was the lower-case form, we're done.
668 break;
669 loc = std::move(loc).toLower(); // Try lower-case next,
670 if (loc == localeName) // but only if different.
671 break;
672 }
673 }
674
675 const qsizetype realNameBaseSizeFallbacks = path.size() + filename.size();
676
677 // realname == path + filename + prefix;
678 if (!suffix.isNull()) {
679 realname.replace(realNameBaseSizeFallbacks, prefix.size(), suffix);
680 // realname == path + filename;
681 if (loadFile())
682 return true;
683 realname.replace(realNameBaseSizeFallbacks, suffix.size(), prefix);
684 }
685
686 // realname == path + filename + prefix;
687 if (loadFile())
688 return true;
689
690 realname.truncate(realNameBaseSizeFallbacks);
691 // realname == path + filename;
692 return loadFile();
693}
694
695/*!
696 \since 4.8
697
698 Loads \a filename + \a prefix + \l{QLocale::uiLanguages()}{ui language
699 name} + \a suffix (".qm" if the \a suffix is not specified), which may be
700 an absolute file name or relative to \a directory. Returns \c true if the
701 translation is successfully loaded; otherwise returns \c false.
702
703 The previous contents of this translator object are discarded.
704
705 If the file name does not exist, other file names are tried
706 in the following order:
707
708 \list 1
709 \li File name without \a suffix appended.
710 \li File name with ui language part after a "_" character stripped and \a suffix.
711 \li File name with ui language part stripped without \a suffix appended.
712 \li File name with ui language part stripped further, etc.
713 \endlist
714
715 For example, an application running in the \a locale with the following
716 \l{QLocale::uiLanguages()}{ui languages} - "es", "fr-CA", "de" might call
717 load(QLocale(), "foo", ".", "/opt/foolib", ".qm"). load() would
718 replace '-' (dash) with '_' (underscore) in the ui language and then try to
719 open the first existing readable file from this list:
720
721 \list 1
722 \li \c /opt/foolib/foo.es.qm
723 \li \c /opt/foolib/foo.es
724 \li \c /opt/foolib/foo.fr_CA.qm
725 \li \c /opt/foolib/foo.fr_CA
726 \li \c /opt/foolib/foo.fr.qm
727 \li \c /opt/foolib/foo.fr
728 \li \c /opt/foolib/foo.de.qm
729 \li \c /opt/foolib/foo.de
730 \li \c /opt/foolib/foo.qm
731 \li \c /opt/foolib/foo.
732 \li \c /opt/foolib/foo
733 \endlist
734
735 On operating systems where file system is case sensitive, QTranslator also
736 tries to load a lower-cased version of the locale name.
737*/
738bool QTranslator::load(const QLocale & locale,
739 const QString & filename,
740 const QString & prefix,
741 const QString & directory,
742 const QString & suffix)
743{
744 Q_D(QTranslator);
745 const QStringList languages = locale.uiLanguages(QLocale::TagSeparator::Underscore);
746 qCDebug(lcTranslator) << "Requested UI languages" << languages;
747 QMutexLocker locker(&d->lock);
748 d->clear();
749 return d->load_translation(languages, filename, prefix, directory, suffix);
750}
751
752/*!
753 \overload load()
754
755 Loads the QM file data \a data of length \a len into the
756 translator.
757
758 The data is not copied. The caller must be able to guarantee that \a data
759 will not be deleted or modified.
760
761 \a directory is only used to specify the base directory when loading the dependencies
762 of a QM file. If the file does not have dependencies, this argument is ignored.
763*/
764bool QTranslator::load(const uchar *data, int len, const QString &directory)
765{
766 Q_D(QTranslator);
767 QMutexLocker locker(&d->lock);
768 d->clear();
769
770 if (!data || len < MagicLength || memcmp(data, magic, MagicLength))
771 return false;
772
773 return d->do_load(data, len, directory);
774}
775
776static quint8 read8(const uchar *data)
777{
778 return qFromBigEndian<quint8>(data);
779}
780
781static quint16 read16(const uchar *data)
782{
783 return qFromBigEndian<quint16>(data);
784}
785
786static quint32 read32(const uchar *data)
787{
788 return qFromBigEndian<quint32>(data);
789}
790
791bool QTranslatorPrivate::do_load(const uchar *data, qsizetype len, const QString &directory)
792{
793 bool ok = true;
794 const uchar *end = data + len;
795
796 data += MagicLength;
797
798 QStringList dependencies;
799 while (data < end - 5) {
800 quint8 tag = read8(data++);
801 quint32 blockLen = read32(data);
802 data += 4;
803 if (!tag || !blockLen)
804 break;
805 if (quint32(end - data) < blockLen) {
806 ok = false;
807 break;
808 }
809
810 if (tag == QTranslatorPrivate::Language) {
811 language = QString::fromUtf8((const char *)data, blockLen);
812 } else if (tag == QTranslatorPrivate::Contexts) {
813 contextArray = data;
814 contextLength = blockLen;
815 } else if (tag == QTranslatorPrivate::Hashes) {
816 offsetArray = data;
817 offsetLength = blockLen;
818 } else if (tag == QTranslatorPrivate::Messages) {
819 messageArray = data;
820 messageLength = blockLen;
821 } else if (tag == QTranslatorPrivate::NumerusRules) {
822 numerusRulesArray = data;
823 numerusRulesLength = blockLen;
824 } else if (tag == QTranslatorPrivate::Dependencies) {
825 QDataStream stream(QByteArray::fromRawData((const char*)data, blockLen));
826 QString dep;
827 while (!stream.atEnd()) {
828 stream >> dep;
829 dependencies.append(dep);
830 }
831 }
832
833 data += blockLen;
834 }
835
836 if (ok && !isValidNumerusRules(numerusRulesArray, numerusRulesLength))
837 ok = false;
838 if (ok) {
839 subTranslators.reserve(std::size_t(dependencies.size()));
840 for (const QString &dependency : std::as_const(dependencies)) {
841 auto translator = std::make_unique<QTranslator>();
842 ok = translator->load(dependency, directory);
843 if (!ok)
844 break;
845 subTranslators.push_back(std::move(translator));
846 }
847
848 // In case some dependencies fail to load, unload all the other ones too.
849 if (!ok)
850 subTranslators.clear();
851 }
852
853 if (!ok) {
854 messageArray = nullptr;
855 contextArray = nullptr;
856 offsetArray = nullptr;
857 numerusRulesArray = nullptr;
858 messageLength = 0;
859 contextLength = 0;
860 offsetLength = 0;
862 }
863
864 return ok;
865}
866
867static QString getMessage(const uchar *m, const uchar *end, const char *context,
868 const char *sourceText, const char *comment, uint numerus)
869{
870 const uchar *tn = nullptr;
871 uint tn_length = 0;
872 const uint sourceTextLen = uint(strlen(sourceText));
873 const uint contextLen = uint(strlen(context));
874 const uint commentLen = uint(strlen(comment));
875
876 for (;;) {
877 uchar tag = 0;
878 if (m < end)
879 tag = read8(m++);
880 switch ((Tag)tag) {
881 case Tag_End:
882 goto end;
883 case Tag_Translation: {
884 int len = read32(m);
885 if (len & 1)
886 return QString();
887 m += 4;
888 if (!numerus--) {
889 tn_length = len;
890 tn = m;
891 }
892 m += len;
893 break;
894 }
895 case Tag_Obsolete1:
896 m += 4;
897 break;
898 case Tag_SourceText: {
899 quint32 len = read32(m);
900 m += 4;
901 if (!match(m, len, sourceText, sourceTextLen))
902 return QString();
903 m += len;
904 }
905 break;
906 case Tag_Context: {
907 quint32 len = read32(m);
908 m += 4;
909 if (!match(m, len, context, contextLen))
910 return QString();
911 m += len;
912 }
913 break;
914 case Tag_Comment: {
915 quint32 len = read32(m);
916 m += 4;
917 if (*m && !match(m, len, comment, commentLen))
918 return QString();
919 m += len;
920 }
921 break;
922 default:
923 return QString();
924 }
925 }
926end:
927 if (!tn)
928 return QString();
929 QString str(tn_length / 2, Qt::Uninitialized);
930 qFromBigEndian<char16_t>(tn, str.size(), str.data());
931 return str;
932}
933
934QString QTranslatorPrivate::do_translate(const char *context, const char *sourceText,
935 const char *comment, int n) const
936{
937 if (context == nullptr)
938 context = "";
939 if (sourceText == nullptr)
940 sourceText = "";
941 if (comment == nullptr)
942 comment = "";
943
944 uint numerus = 0;
945 size_t numItems = 0;
946
947 if (!offsetLength)
948 goto searchDependencies;
949
950 /*
951 Check if the context belongs to this QTranslator. If many
952 translators are installed, this step is necessary.
953 */
954 if (contextLength) {
955 quint16 hTableSize = read16(contextArray);
956 uint g = elfHash(context) % hTableSize;
957 const uchar *c = contextArray + 2 + (g << 1);
958 quint16 off = read16(c);
959 c += 2;
960 if (off == 0)
961 return QString();
962 c = contextArray + (2 + (hTableSize << 1) + (off << 1));
963
964 const uint contextLen = uint(strlen(context));
965 for (;;) {
966 quint8 len = read8(c++);
967 if (len == 0)
968 return QString();
969 if (match(c, len, context, contextLen))
970 break;
971 c += len;
972 }
973 }
974
975 numItems = offsetLength / (2 * sizeof(quint32));
976 if (!numItems)
977 goto searchDependencies;
978
979 if (n >= 0)
980 numerus = numerusHelper(n, numerusRulesArray, numerusRulesLength);
981
982 for (;;) {
983 quint32 h = 0;
984 elfHash_continue(sourceText, h);
985 elfHash_continue(comment, h);
986 elfHash_finish(h);
987
988 const uchar *start = offsetArray;
989 const uchar *end = start + ((numItems - 1) << 3);
990 while (start <= end) {
991 const uchar *middle = start + (((end - start) >> 4) << 3);
992 uint hash = read32(middle);
993 if (h == hash) {
994 start = middle;
995 break;
996 } else if (hash < h) {
997 start = middle + 8;
998 } else {
999 end = middle - 8;
1000 }
1001 }
1002
1003 if (start <= end) {
1004 // go back on equal key
1005 while (start != offsetArray && read32(start) == read32(start - 8))
1006 start -= 8;
1007
1008 while (start < offsetArray + offsetLength) {
1009 quint32 rh = read32(start);
1010 start += 4;
1011 if (rh != h)
1012 break;
1013 quint32 ro = read32(start);
1014 start += 4;
1015 QString tn = getMessage(messageArray + ro, messageArray + messageLength, context,
1016 sourceText, comment, numerus);
1017 if (!tn.isNull())
1018 return tn;
1019 }
1020 }
1021 if (!comment[0])
1022 break;
1023 comment = "";
1024 }
1025
1026searchDependencies:
1027 for (const auto &translator : subTranslators) {
1028 QString tn = translator->translate(context, sourceText, comment, n);
1029 if (!tn.isNull())
1030 return tn;
1031 }
1032 return QString();
1033}
1034
1035/*
1036 Empties this translator of all contents.
1037*/
1038
1040{
1041 Q_Q(QTranslator);
1042 if (unmapPointer && unmapLength) {
1043#if defined(QT_USE_MMAP)
1044 if (used_mmap) {
1045 used_mmap = false;
1046 munmap(unmapPointer, unmapLength);
1047 } else
1048#endif
1049 if (!resource)
1050 delete [] unmapPointer;
1051 }
1052
1053 resource = nullptr;
1054 unmapPointer = nullptr;
1055 unmapLength = 0;
1056 messageArray = nullptr;
1057 contextArray = nullptr;
1058 offsetArray = nullptr;
1059 numerusRulesArray = nullptr;
1060 messageLength = 0;
1061 contextLength = 0;
1062 offsetLength = 0;
1064
1065 subTranslators.clear();
1066
1067 language.clear();
1068 filePath.clear();
1069
1070 if (QCoreApplicationPrivate::isTranslatorInstalled(q))
1071 QCoreApplication::postEvent(QCoreApplication::instance(),
1072 new QEvent(QEvent::LanguageChange));
1073}
1074
1075/*!
1076 \threadsafe
1077
1078 Returns the translation for the key (\a context, \a sourceText,
1079 \a disambiguation). If none is found, also tries (\a context, \a
1080 sourceText, ""). If that still fails, returns a null string.
1081
1082 \note Incomplete translations may result in unexpected behavior:
1083 If no translation for (\a context, \a sourceText, "")
1084 is provided, the method might in this case actually return a
1085 translation for a different \a disambiguation.
1086
1087 If \a n is not -1, it is used to choose an appropriate form for
1088 the translation (e.g. "%n file found" vs. "%n files found").
1089
1090 If you need to programmatically insert translations into a
1091 QTranslator, this function can be reimplemented.
1092
1093 \sa load()
1094*/
1095QString QTranslator::translate(const char *context, const char *sourceText, const char *disambiguation,
1096 int n) const
1097{
1098 Q_D(const QTranslator);
1099
1100 // Return early to avoid a deadlock in case translate() is called
1101 // from a code path triggered by QTranslator::load()
1102 if (!d->lock.tryLock())
1103 return QString();
1104 QString result = d->do_translate(context, sourceText, disambiguation, n);
1105 d->lock.unlock();
1106 return result;
1107}
1108
1109/*!
1110 Returns \c true if this translator is empty, otherwise returns \c false.
1111*/
1112bool QTranslator::isEmpty() const
1113{
1114 Q_D(const QTranslator);
1115 return !d->messageArray && !d->offsetArray && !d->contextArray
1116 && d->subTranslators.empty();
1117}
1118
1119/*!
1120 \since 5.15
1121
1122 Returns the target language as stored in the translation file.
1123 */
1124QString QTranslator::language() const
1125{
1126 Q_D(const QTranslator);
1127 return d->language;
1128}
1129
1130/*!
1131 \since 5.15
1132
1133 Returns the path of the loaded translation file.
1134
1135 The file path is empty if no translation was loaded yet,
1136 the loading failed, or if the translation was not loaded
1137 from a file.
1138 */
1139QString QTranslator::filePath() const
1140{
1141 Q_D(const QTranslator);
1142 return d->filePath;
1143}
1144
1145QT_END_NAMESPACE
1146
1147#include "moc_qtranslator.cpp"
1148
1149#endif // QT_NO_TRANSLATION
\inmodule QtCore
Definition qfile.h:96
\inmodule QtCore
Definition qresource.h:21
bool do_load(const QString &filename, const QString &directory)
const uchar * contextArray
bool do_load(const uchar *data, qsizetype len, const QString &directory)
const uchar * messageArray
QString do_translate(const char *context, const char *sourceText, const char *comment, int n) const
const uchar * offsetArray
bool load_translation(const QStringList &languages, const QString &filename, const QString &prefix, const QString &directory, const QString &suffix)
std::unique_ptr< QResource > resource
std::vector< std::unique_ptr< QTranslator > > subTranslators
const uchar * numerusRulesArray
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)
#define QStringLiteral(str)
Definition qstring.h:1826
static const int MagicLength
static QString getMessage(const uchar *m, const uchar *end, const char *context, const char *sourceText, const char *comment, uint numerus)
static quint32 read32(const uchar *data)
static void elfHash_continue(const char *name, uint &h)
static bool isValidNumerusRules(const uchar *rules, uint rulesSize)
static QString dotQmLiteral()
static uint elfHash(const char *name)
static bool match(const uchar *found, uint foundLen, const char *target, uint targetLen)
static uint numerusHelper(int n, const uchar *rules, uint rulesSize)
static quint8 read8(const uchar *data)
static quint16 read16(const uchar *data)
static const uchar magic[MagicLength]
static void elfHash_finish(uint &h)