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

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