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

source code of qtbase/src/corelib/kernel/qtranslator.cpp