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