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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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