1 /**********************************************************************
2 ** Copyright (C) 2002-2007 Detlev Offenbach <detlev@die-offenbachs.de>
3 **
4 ** This is a modified version of lupdate. The original is part of Qt-Linguist.
5 ** The copyright of the original file can be found below.
6 **
7 ** This version is modified to handle python sources.
8 **
9 ** The file is provided AS IS with NO WARRANTY OF ANY KIND,
10 ** INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR
11 ** A PARTICULAR PURPOSE.
12 ****************************************************************************/
13
14 /****************************************************************************
15 **
16 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
17 ** Contact: http://www.qt-project.org/legal
18 **
19 ** This file is part of the tools applications of the Qt Toolkit.
20 **
21 ** $QT_BEGIN_LICENSE:LGPL$
22 ** Commercial License Usage
23 ** Licensees holding valid commercial Qt licenses may use this file in
24 ** accordance with the commercial license agreement provided with the
25 ** Software or, alternatively, in accordance with the terms contained in
26 ** a written agreement between you and Digia. For licensing terms and
27 ** conditions see http://qt.digia.com/licensing. For further information
28 ** use the contact form at http://qt.digia.com/contact-us.
29 **
30 ** GNU Lesser General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU Lesser
32 ** General Public License version 2.1 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.LGPL included in the
34 ** packaging of this file. Please review the following information to
35 ** ensure the GNU Lesser General Public License version 2.1 requirements
36 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
37 **
38 ** In addition, as a special exception, Digia gives you certain additional
39 ** rights. These rights are described in the Digia Qt LGPL Exception
40 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
41 **
42 ** GNU General Public License Usage
43 ** Alternatively, this file may be used under the terms of the GNU
44 ** General Public License version 3.0 as published by the Free Software
45 ** Foundation and appearing in the file LICENSE.GPL included in the
46 ** packaging of this file. Please review the following information to
47 ** ensure the GNU General Public License version 3.0 requirements will be
48 ** met: http://www.gnu.org/copyleft/gpl.html.
49 **
50 **
51 ** $QT_END_LICENSE$
52 **
53 ****************************************************************************/
54
55 #include "translator.h"
56
57 #ifndef QT_NO_TRANSLATION
58
59 #include "qfileinfo.h"
60 #include "qstring.h"
61 #include "qcoreapplication.h"
62 #include "qdatastream.h"
63 #include "qfile.h"
64 #include "qmap.h"
65 #include "qalgorithms.h"
66 #include "qhash.h"
67 #include "qglobal.h"
68
69 // most of the headers below are already included in qplatformdefs.h
70 // also this lacks Large File support but that's probably irrelevant
71 #if defined(QT_USE_MMAP)
72 // for mmap
73 #include <sys/mman.h>
74 #include <errno.h>
75 #endif
76
77 #include <stdlib.h>
78
79 /*
80 $ mcookie
81 3cb86418caef9c95cd211cbf60a1bddd
82 $
83 */
84
85 // magic number for the file
86 static const int MagicLength = 16;
87 static const uchar magic[MagicLength] = {
88 0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95,
89 0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd
90 };
91
elfHash(const char * name)92 static uint elfHash(const char * name)
93 {
94 const uchar *k;
95 uint h = 0;
96 uint g;
97
98 if (name) {
99 k = (const uchar *) name;
100 while (*k) {
101 h = (h << 4) + *k++;
102 if ((g = (h & 0xf0000000)) != 0)
103 h ^= g >> 24;
104 h &= ~g;
105 }
106 }
107 if (!h)
108 h = 1;
109 return h;
110 }
111
112 extern bool qt_detectRTLLanguage();
113
114 class TranslatorPrivate
115 {
116 public:
117 struct Offset {
OffsetTranslatorPrivate::Offset118 Offset()
119 : h(0), o(0) { }
OffsetTranslatorPrivate::Offset120 Offset(const TranslatorMessage& m, int offset)
121 : h(m.hash()), o(offset) { }
122
operator <TranslatorPrivate::Offset123 bool operator<(const Offset &other) const {
124 return (h != other.h) ? h < other.h : o < other.o;
125 }
operator ==TranslatorPrivate::Offset126 bool operator==(const Offset &other) const {
127 return h == other.h && o == other.o;
128 }
129 uint h;
130 uint o;
131 };
132
133 enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69 };
134
TranslatorPrivate(Translator * qq)135 TranslatorPrivate(Translator *qq) : q(qq), unmapPointer(0), unmapLength(0) {}
136 // Translator must finalize this before deallocating it
137
138 Translator *q;
139 // for mmap'ed files, this is what needs to be unmapped.
140 char *unmapPointer;
141 unsigned int unmapLength;
142
143 // for squeezed but non-file data, this is what needs to be deleted
144 QByteArray messageArray;
145 QByteArray offsetArray;
146 QByteArray contextArray;
147
148 #ifndef QT_NO_TRANSLATION_BUILDER
149 QMap<TranslatorMessage, void *> messages;
150 #endif
151
152 bool do_load(const uchar *data, int len);
153
154 };
155
156
157 /*!
158 \class Translator
159
160 \brief The Translator class provides internationalization support for text
161 output.
162
163 \ingroup i18n
164 \ingroup environment
165 \mainclass
166
167 An object of this class contains a set of TranslatorMessage
168 objects, each of which specifies a translation from a source
169 language to a target language. Translator provides functions to
170 look up translations, add new ones, remove them, load and save
171 them, etc.
172
173 The most common use of Translator is to: load a translator file
174 created with \l{Qt Linguist Manual}, install it using
175 QCoreApplication::installTranslator(), and use it via QObject::tr().
176 For example:
177
178 \code
179 int main(int argc, char ** argv)
180 {
181 QCoreApplication app(argc, argv);
182
183 Translator translator(0);
184 translator.load("french.qm", ".");
185 app.installTranslator(&translator);
186
187 MyWidget m;
188 app.setMainWidget(&m);
189 m.show();
190
191 return app.exec();
192 }
193 \endcode
194 Note that the translator must be created \e before the
195 application's main window.
196
197 Most applications will never need to do anything else with this
198 class. The other functions provided by this class are useful for
199 applications that work on translator files.
200
201 We call a translation a "messsage". For this reason, translation
202 files are sometimes referred to as "message files".
203
204 It is possible to lookup a translation using findMessage() (as
205 tr() and QCoreApplication::translate() do) and contains(), to insert a
206 new translation messsage using insert(), and to remove one using
207 remove().
208
209 Translation tools often need more information than the bare source
210 text and translation, for example, context information to help
211 the translator. But end-user programs that are using translations
212 usually only need lookup. To cater for these different needs,
213 Translator can use stripped translator files that use the minimum
214 of memory and which support little more functionality than
215 findMessage().
216
217 Thus, load() may not load enough information to make anything more
218 than findMessage() work. save() has an argument indicating
219 whether to save just this minimum of information or to save
220 everything.
221
222 "Everything" means that for each translation item the following
223 information is kept:
224
225 \list
226 \i The \e {translated text} - the return value from tr().
227 \i The input key:
228 \list
229 \i The \e {source text} - usually the argument to tr().
230 \i The \e context - usually the class name for the tr() caller.
231 \i The \e comment - a comment that helps disambiguate different uses
232 of the same text in the same context.
233 \endlist
234 \endlist
235
236 The minimum for each item is just the information necessary for
237 findMessage() to return the right text. This may include the
238 source, context and comment, but usually it is just a hash value
239 and the translated text.
240
241 For example, the "Cancel" in a dialog might have "Anuluj" when the
242 program runs in Polish (in this case the source text would be
243 "Cancel"). The context would (normally) be the dialog's class
244 name; there would normally be no comment, and the translated text
245 would be "Anuluj".
246
247 But it's not always so simple. The Spanish version of a printer
248 dialog with settings for two-sided printing and binding would
249 probably require both "Activado" and "Activada" as translations
250 for "Enabled". In this case the source text would be "Enabled" in
251 both cases, and the context would be the dialog's class name, but
252 the two items would have disambiguating comments such as
253 "two-sided printing" for one and "binding" for the other. The
254 comment enables the translator to choose the appropriate gender
255 for the Spanish version, and enables Qt to distinguish between
256 translations.
257
258 Note that when Translator loads a stripped file, most functions
259 do not work. The functions that do work with stripped files are
260 explicitly documented as such.
261
262 \sa TranslatorMessage QCoreApplication::installTranslator()
263 QCoreApplication::removeTranslator() QObject::tr()
264 QCoreApplication::translate()
265 */
266
267 /*!
268 \enum Translator::SaveMode
269
270 This enum type defines how Translator writes translation
271 files. There are two modes:
272
273 \value Everything files are saved with all available information
274 \value Stripped files are saved with just enough information for
275 end-user applications
276
277 Note that when Translator loads a stripped file, most functions do
278 not work. The functions that do work with stripped files are
279 explicitly documented as such.
280 */
281
282 /*!
283 Constructs an empty message file object with parent \a parent that
284 is not connected to any file.
285 */
286
Translator(QObject * parent)287 Translator::Translator(QObject * parent)
288 : QTranslator(parent)
289 {
290 d = new TranslatorPrivate(this);
291 }
292
293 /*!
294 Destroys the object and frees any allocated resources.
295 */
296
~Translator()297 Translator::~Translator()
298 {
299 if (QCoreApplication::instance())
300 QCoreApplication::instance()->removeTranslator(this);
301 clear();
302 delete d;
303 }
304
305 /*!
306 Loads \a filename + \a suffix (".qm" if the \a suffix is
307 not specified), which may be an absolute file name or relative
308 to \a directory. The previous contents of this translator object
309 is discarded.
310
311 If the file name does not exist, other file names are tried
312 in the following order:
313
314 \list 1
315 \i File name without \a suffix appended.
316 \i File name with text after a character in \a search_delimiters
317 stripped ("_." is the default for \a search_delimiters if it is
318 an empty string) and \a suffix.
319 \i File name stripped without \a suffix appended.
320 \i File name stripped further, etc.
321 \endlist
322
323 For example, an application running in the fr_CA locale
324 (French-speaking Canada) might call load("foo.fr_ca",
325 "/opt/foolib"). load() would then try to open the first existing
326 readable file from this list:
327
328 \list 1
329 \i /opt/foolib/foo.fr_ca.qm
330 \i /opt/foolib/foo.fr_ca
331 \i /opt/foolib/foo.fr.qm
332 \i /opt/foolib/foo.fr
333 \i /opt/foolib/foo.qm
334 \i /opt/foolib/foo
335 \endlist
336
337 \sa save()
338 */
339
load(const QString & filename,const QString & directory,const QString & search_delimiters,const QString & suffix)340 bool Translator::load(const QString & filename, const QString & directory,
341 const QString & search_delimiters,
342 const QString & suffix)
343 {
344 clear();
345
346 QString prefix;
347
348 if (filename[0] == QLatin1Char('/')
349 #ifdef Q_WS_WIN
350 || (filename[0].isLetter() && filename[1] == QLatin1Char(':')) || filename[0] == QLatin1Char('\\')
351 #endif
352 )
353 prefix = QLatin1String("");
354 else
355 prefix = directory;
356
357 if (prefix.length()) {
358 if (prefix[int(prefix.length()-1)] != QLatin1Char('/'))
359 prefix += QLatin1Char('/');
360 }
361
362 QString fname = filename;
363 QString realname;
364 QString delims;
365 delims = search_delimiters.isNull() ? QString::fromLatin1("_.") : search_delimiters;
366
367 for (;;) {
368 QFileInfo fi;
369
370 realname = prefix + fname + (suffix.isNull() ? QString::fromLatin1(".qm") : suffix);
371 fi.setFile(realname);
372 if (fi.isReadable())
373 break;
374
375 realname = prefix + fname;
376 fi.setFile(realname);
377 if (fi.isReadable())
378 break;
379
380 int rightmost = 0;
381 for (int i = 0; i < (int)delims.length(); i++) {
382 int k = fname.lastIndexOf(delims[i]);
383 if (k > rightmost)
384 rightmost = k;
385 }
386
387 // no truncations? fail
388 if (rightmost == 0)
389 return false;
390
391 fname.truncate(rightmost);
392 }
393
394 // realname is now the fully qualified name of a readable file.
395
396 bool ok = false;
397
398 #ifdef QT_USE_MMAP
399
400 #ifndef MAP_FILE
401 #define MAP_FILE 0
402 #endif
403 #ifndef MAP_FAILED
404 #define MAP_FAILED -1
405 #endif
406
407 int fd = -1;
408 if (!realname.startsWith(QLatin1String(":")))
409 fd = QT_OPEN(QFile::encodeName(realname), O_RDONLY,
410 #if defined(Q_OS_WIN)
411 _S_IREAD | _S_IWRITE
412 #else
413 0666
414 #endif
415 );
416 if (fd >= 0) {
417 struct stat st;
418 if (!fstat(fd, &st)) {
419 char *ptr;
420 ptr = reinterpret_cast<char *>(
421 mmap(0, st.st_size, // any address, whole file
422 PROT_READ, // read-only memory
423 MAP_FILE | MAP_PRIVATE, // swap-backed map from file
424 fd, 0)); // from offset 0 of fd
425 if (ptr && ptr != reinterpret_cast<char *>(MAP_FAILED)) {
426 d->unmapPointer = ptr;
427 d->unmapLength = st.st_size;
428 ok = true;
429 }
430 }
431 ::close(fd);
432 }
433 #endif // QT_USE_MMAP
434
435 if (!ok) {
436 QFile file(realname);
437 if (!file.exists())
438 return false;
439 d->unmapLength = file.size();
440 d->unmapPointer = new char[d->unmapLength];
441
442 if (file.open(QIODevice::ReadOnly))
443 ok = (d->unmapLength == (uint)file.read(d->unmapPointer, d->unmapLength));
444
445 if (!ok) {
446 delete [] d->unmapPointer;
447 d->unmapPointer = 0;
448 d->unmapLength = 0;
449 return false;
450 }
451 }
452
453 return d->do_load(reinterpret_cast<const uchar *>(d->unmapPointer), d->unmapLength);
454 }
455
456 /*!
457 \overload
458 \fn bool Translator::load(const uchar *data, int len)
459
460 Loads the .qm file data \a data of length \a len into the
461 translator.
462
463 The data is not copied. The caller must be able to guarantee that \a data
464 will not be deleted or modified.
465 */
load(const uchar * data,int len)466 bool Translator::load(const uchar *data, int len)
467 {
468 clear();
469 return d->do_load(data, len);
470 }
471
do_load(const uchar * data,int len)472 bool TranslatorPrivate::do_load(const uchar *data, int len)
473 {
474 if (len < MagicLength || memcmp(data, magic, MagicLength) != 0) {
475 q->clear();
476 return false;
477 }
478
479 QByteArray array = QByteArray::fromRawData((const char *) data, len);
480 QDataStream s(&array, QIODevice::ReadOnly);
481 bool ok = true;
482
483 s.device()->seek(MagicLength);
484
485 quint8 tag = 0;
486 quint32 blockLen = 0;
487 s >> tag >> blockLen;
488 while (tag && blockLen) {
489 if ((quint32) s.device()->pos() + blockLen > (quint32) len) {
490 ok = false;
491 break;
492 }
493
494 if (tag == TranslatorPrivate::Contexts) {
495 contextArray = QByteArray(array.constData() + s.device()->pos(), blockLen);
496 } else if (tag == TranslatorPrivate::Hashes) {
497 offsetArray = QByteArray(array.constData() + s.device()->pos(), blockLen);
498 } else if (tag == TranslatorPrivate::Messages) {
499 messageArray = QByteArray(array.constData() + s.device()->pos(), blockLen);
500 }
501
502 if (!s.device()->seek(s.device()->pos() + blockLen)) {
503 ok = false;
504 break;
505 }
506 tag = 0;
507 blockLen = 0;
508 if (!s.atEnd())
509 s >> tag >> blockLen;
510 }
511
512 return ok;
513 }
514
515 #ifndef QT_NO_TRANSLATION_BUILDER
516
517 /*!
518 Saves this message file to \a filename, overwriting the previous
519 contents of \a filename. If \a mode is \c Everything (the
520 default), all the information is preserved. If \a mode is \c
521 Stripped, any information that is not necessary for findMessage()
522 is stripped away.
523
524 \sa load()
525 */
526
save(const QString & filename,SaveMode mode)527 bool Translator::save(const QString & filename, SaveMode mode)
528 {
529 QFile file(filename);
530 if (file.open(QIODevice::WriteOnly)) {
531 squeeze(mode);
532
533 QDataStream s(&file);
534 s.writeRawData((const char *)magic, MagicLength);
535 quint8 tag;
536
537 if (!d->offsetArray.isEmpty()) {
538 tag = (quint8)TranslatorPrivate::Hashes;
539 quint32 oas = (quint32)d->offsetArray.size();
540 s << tag << oas;
541 s.writeRawData(d->offsetArray, oas);
542 }
543 if (!d->messageArray.isEmpty()) {
544 tag = (quint8)TranslatorPrivate::Messages;
545 quint32 mas = (quint32)d->messageArray.size();
546 s << tag << mas;
547 s.writeRawData(d->messageArray, mas);
548 }
549 if (!d->contextArray.isEmpty()) {
550 tag = (quint8)TranslatorPrivate::Contexts;
551 quint32 cas = (quint32)d->contextArray.size();
552 s << tag << cas;
553 s.writeRawData(d->contextArray, cas);
554 }
555 return true;
556 }
557 return false;
558 }
559
560 #endif
561
562 /*!
563 Empties this translator of all contents.
564
565 This function works with stripped translator files.
566 */
567
clear()568 void Translator::clear()
569 {
570 if (d->unmapPointer && d->unmapLength) {
571 #if defined(QT_USE_MMAP)
572 munmap(d->unmapPointer, d->unmapLength);
573 #else
574 delete [] d->unmapPointer;
575 #endif
576 d->unmapPointer = 0;
577 d->unmapLength = 0;
578 }
579
580 d->messageArray.clear();
581 d->offsetArray.clear();
582 d->contextArray.clear();
583 #ifndef QT_NO_TRANSLATION_BUILDER
584 d->messages.clear();
585 #endif
586
587 QEvent ev(QEvent::LanguageChange);
588 QCoreApplication::sendEvent(QCoreApplication::instance(), &ev);
589 }
590
591 #ifndef QT_NO_TRANSLATION_BUILDER
592
593 /*!
594 Converts this message file to the compact format used to store
595 message files on disk.
596
597 You should never need to call this directly; save() and other
598 functions call it as necessary. \a mode is for internal use.
599
600 \sa save() unsqueeze()
601 */
602
squeeze(SaveMode mode)603 void Translator::squeeze(SaveMode mode)
604 {
605 if (d->messages.isEmpty()) {
606 if (mode == Stripped)
607 unsqueeze();
608 else
609 return;
610 }
611
612 QMap<TranslatorMessage, void *> messages = d->messages;
613 clear();
614
615 QMap<TranslatorPrivate::Offset, void *> offsets;
616
617 QDataStream ms(&d->messageArray, QIODevice::WriteOnly);
618 QMap<TranslatorMessage, void *>::const_iterator it, next;
619 int cpPrev = 0, cpNext = 0;
620 for (it = messages.constBegin(); it != messages.constEnd(); ++it) {
621 cpPrev = cpNext;
622 next = it;
623 ++next;
624 if (next == messages.constEnd())
625 cpNext = 0;
626 else
627 cpNext = (int) it.key().commonPrefix(next.key());
628 offsets.insert(TranslatorPrivate::Offset(it.key(), ms.device()->pos()), (void *)0);
629 it.key().write(ms, mode == Stripped, (TranslatorMessage::Prefix)qMax(cpPrev, cpNext + 1));
630 }
631
632 QMap<TranslatorPrivate::Offset, void *>::Iterator offset;
633 offset = offsets.begin();
634 QDataStream ds(&d->offsetArray, QIODevice::WriteOnly);
635 while (offset != offsets.end()) {
636 TranslatorPrivate::Offset k = offset.key();
637 ++offset;
638 ds << (quint32)k.h << (quint32)k.o;
639 }
640
641 if (mode == Stripped) {
642 QMap<QByteArray, int> contextSet;
643 for (it = messages.constBegin(); it != messages.constEnd(); ++it)
644 ++contextSet[it.key().context()];
645
646 quint16 hTableSize;
647 if (contextSet.size() < 200)
648 hTableSize = (contextSet.size() < 60) ? 151 : 503;
649 else if (contextSet.size() < 2500)
650 hTableSize = (contextSet.size() < 750) ? 1511 : 5003;
651 else
652 hTableSize = (contextSet.size() < 10000) ? 15013 : 3 * contextSet.size() / 2;
653
654 QMultiMap<int, const char *> hashMap;
655 QMap<QByteArray, int>::const_iterator c;
656 for (c = contextSet.constBegin(); c != contextSet.constEnd(); ++c)
657 hashMap.insert(elfHash(c.key()) % hTableSize, c.key());
658
659 /*
660 The contexts found in this translator are stored in a hash
661 table to provide fast lookup. The context array has the
662 following format:
663
664 quint16 hTableSize;
665 quint16 hTable[hTableSize];
666 quint8 contextPool[...];
667
668 The context pool stores the contexts as Pascal strings:
669
670 quint8 len;
671 quint8 data[len];
672
673 Let's consider the look-up of context "FunnyDialog". A
674 hash value between 0 and hTableSize - 1 is computed, say h.
675 If hTable[h] is 0, "FunnyDialog" is not covered by this
676 translator. Else, we check in the contextPool at offset
677 2 * hTable[h] to see if "FunnyDialog" is one of the
678 contexts stored there, until we find it or we meet the
679 empty string.
680 */
681 d->contextArray.resize(2 + (hTableSize << 1));
682 QDataStream t(&d->contextArray, QIODevice::WriteOnly);
683
684 quint16 *hTable = new quint16[hTableSize];
685 memset(hTable, 0, hTableSize * sizeof(quint16));
686
687 t << hTableSize;
688 t.device()->seek(2 + (hTableSize << 1));
689 t << (quint16)0; // the entry at offset 0 cannot be used
690 uint upto = 2;
691
692 QMap<int, const char *>::const_iterator entry = hashMap.constBegin();
693 while (entry != hashMap.constEnd()) {
694 int i = entry.key();
695 hTable[i] = (quint16)(upto >> 1);
696
697 do {
698 const char *con = entry.value();
699 uint len = (uint)qstrlen(con);
700 len = qMin(len, 255u);
701 t << (quint8)len;
702 t.writeRawData(con, len);
703 upto += 1 + len;
704 ++entry;
705 } while (entry != hashMap.constEnd() && entry.key() == i);
706 do {
707 t << (quint8) 0; // empty string
708 ++upto;
709 } while ((upto & 0x1) != 0); // offsets have to be even
710 }
711 t.device()->seek(2);
712 for (int j = 0; j < hTableSize; j++)
713 t << hTable[j];
714 delete [] hTable;
715
716 if (upto > 131072) {
717 qWarning("Translator::squeeze: Too many contexts");
718 d->contextArray.clear();
719 }
720 }
721 }
722
723
724 /*!
725 Converts this message file into an easily modifiable data
726 structure, less compact than the format used in the files.
727
728 You should never need to call this function; it is called by
729 insert() and friends as necessary.
730
731 \sa squeeze()
732 */
733
unsqueeze()734 void Translator::unsqueeze()
735 {
736 if (!d->messages.isEmpty() || d->messageArray.isEmpty())
737 return;
738
739 qFatal("Cannot unsqueeze (bug in Linguist?)");
740 }
741
742
743 /*!
744 Returns true if this message file contains a message with the key
745 (\a context, \a sourceText, \a comment); otherwise returns false.
746
747 This function works with stripped translator files.
748
749 (This is is a one-liner that calls findMessage().)
750 */
751
contains(const char * context,const char * sourceText,const char * comment) const752 bool Translator::contains(const char* context, const char* sourceText,
753 const char* comment) const
754 {
755 return !findMessage(context, sourceText, comment).translation().isNull();
756 }
757
758
contains(const char * context,const char * comment,const QString & fileName,int lineNumber) const759 bool Translator::contains(const char *context,
760 const char *comment, const QString &fileName, int lineNumber) const
761 {
762 return !findMessage(context, 0, comment, fileName, lineNumber).isNull();
763 }
764
765 /*!
766 Inserts \a message into this message file.
767
768 This function does \e not work with stripped translator files. It
769 may appear to, but that is not dependable.
770
771 \sa remove()
772 */
773
insert(const TranslatorMessage & message)774 void Translator::insert(const TranslatorMessage& message)
775 {
776 unsqueeze();
777 d->messages.remove(message); // safer
778 d->messages.insert(message, (void *) 0);
779 }
780
781 /*!
782 \fn void Translator::insert(const char *context, const char
783 *sourceText, const QString &translation)
784 \overload
785 \obsolete
786
787 Inserts the \a sourceText and \a translation into the translator
788 with the given \a context.
789 */
790
791 /*!
792 Removes \a message from this translator.
793
794 This function works with stripped translator files.
795
796 \sa insert()
797 */
798
remove(const TranslatorMessage & message)799 void Translator::remove(const TranslatorMessage& message)
800 {
801 unsqueeze();
802 d->messages.remove(message);
803 }
804
805
806 /*!
807 \fn void Translator::remove(const char *, const char *)
808 \overload
809 \obsolete
810
811 Removes the translation associated to the key (\a context, \a sourceText,
812 "") from this translator.
813 */
814 #endif
815
816 /*! Returns the TranslatorMessage for the key
817 (\a context, \a sourceText, \a comment). If none is found,
818 also tries (\a context, \a sourceText, "").
819 */
820
findMessage(const char * context,const char * sourceText,const char * comment,const QString & fileName,int lineNumber) const821 TranslatorMessage Translator::findMessage(const char *context, const char *sourceText,
822 const char *comment,
823 const QString &fileName, int lineNumber) const
824 {
825 if (context == 0)
826 context = "";
827 if (sourceText == 0)
828 sourceText = "";
829 if (comment == 0)
830 comment = "";
831
832 QString myFilename = fileName;
833 int myLineNumber = lineNumber;
834
835 if (!d->messages.isEmpty()) {
836 QMap<TranslatorMessage, void *>::const_iterator it;
837
838 // Either we want to find an item that matches context, sourcetext (and optionally comment)
839 // Or we want to find an item that matches context, filename, linenumber (and optionally comment)
840 it = d->messages.find(TranslatorMessage(context, sourceText, comment, myFilename, myLineNumber));
841 if (it != d->messages.constEnd())
842 return it.key();
843
844 if (comment[0]) {
845 it = d->messages.find(TranslatorMessage(context, sourceText, "", myFilename, myLineNumber));
846 if (it != d->messages.constEnd())
847 return it.key();
848 }
849 it = d->messages.find(TranslatorMessage(context, "", comment, myFilename, myLineNumber));
850 if (it != d->messages.constEnd())
851 return it.key();
852 if (comment[0]) {
853 it = d->messages.find(TranslatorMessage(context, "", "", myFilename, myLineNumber));
854 if (it != d->messages.constEnd())
855 return it.key();
856 }
857 return TranslatorMessage();
858 }
859
860 return TranslatorMessage();
861 }
862
863 /*!
864 Returns true if this translator is empty, otherwise returns false.
865 This function works with stripped and unstripped translation files.
866 */
isEmpty() const867 bool Translator::isEmpty() const
868 {
869 return !d->unmapPointer && !d->unmapLength && d->messageArray.isEmpty() &&
870 d->offsetArray.isEmpty() && d->contextArray.isEmpty() && d->messages.isEmpty();
871 }
872
873
874 #ifndef QT_NO_TRANSLATION_BUILDER
875
876 /*!
877 Returns a list of the messages in the translator. This function is
878 rather slow. Because it is seldom called, it's optimized for
879 simplicity and small size, rather than speed.
880
881 If you want to iterate over the list, you should iterate over a
882 copy, e.g.
883 \code
884 QList<TranslatorMessage> list = myTranslator.messages();
885 QList<TranslatorMessage>::Iterator it = list.begin();
886 while (it != list.end()) {
887 process_message(*it);
888 ++it;
889 }
890 \endcode
891 */
892
messages() const893 QList<TranslatorMessage> Translator::messages() const
894 {
895 ((Translator *) this)->unsqueeze();
896 return d->messages.keys();
897 }
898
899 #endif
900
901 /*!
902 \class TranslatorMessage
903
904 \brief The TranslatorMessage class contains a translator message and its
905 properties.
906
907 \ingroup i18n
908 \ingroup environment
909
910 This class is of no interest to most applications. It is useful
911 for translation tools such as \l{Qt Linguist Manual}{Qt Linguist}.
912 It is provided simply to make the API complete and regular.
913
914 For a Translator object, a lookup key is a triple (\e context, \e
915 {source text}, \e comment) that uniquely identifies a message. An
916 extended key is a quadruple (\e hash, \e context, \e {source
917 text}, \e comment), where \e hash is computed from the source text
918 and the comment. Unless you plan to read and write messages
919 yourself, you need not worry about the hash value.
920
921 TranslatorMessage stores this triple or quadruple and the relevant
922 translation if there is any.
923
924 \sa Translator
925 */
926
927 /*!
928 Constructs a translator message with the extended key (0, 0, 0, 0)
929 and an empty string as translation.
930 */
931
TranslatorMessage()932 TranslatorMessage::TranslatorMessage()
933 : h(0), m_fileName(), m_lineNumber(-1)
934 {
935 }
936
937
938 /*!
939 Constructs an translator message with the extended key (\e h, \a
940 context, \a sourceText, \a comment), where \e h is computed from
941 \a sourceText and \a comment, and possibly with a \a translation.
942 */
943
TranslatorMessage(const char * context,const char * sourceText,const char * comment,const QString & fileName,int lineNumber,const QStringList & translations)944 TranslatorMessage::TranslatorMessage(const char * context,
945 const char * sourceText,
946 const char * comment,
947 const QString &fileName,
948 int lineNumber,
949 const QStringList& translations)
950 : cx(context), st(sourceText), cm(comment), m_translations(translations),
951 m_fileName(fileName), m_lineNumber(lineNumber)
952 {
953 // 0 means we don't know, "" means empty
954 if (cx == (const char*)0)
955 cx = "";
956 if (st == (const char*)0)
957 st = "";
958 if (cm == (const char*)0)
959 cm = "";
960 h = elfHash(st + cm);
961 }
962
963
964 /*!
965 Constructs a copy of translator message \a m.
966 */
967
TranslatorMessage(const TranslatorMessage & m)968 TranslatorMessage::TranslatorMessage(const TranslatorMessage & m)
969 : cx(m.cx), st(m.st), cm(m.cm), m_translations(m.m_translations),
970 m_fileName(m.m_fileName), m_lineNumber(m.m_lineNumber)
971 {
972 h = m.h;
973 }
974
975
976 /*!
977 Assigns message \a m to this translator message and returns a
978 reference to this translator message.
979 */
980
operator =(const TranslatorMessage & m)981 TranslatorMessage & TranslatorMessage::operator=(
982 const TranslatorMessage & m)
983 {
984 h = m.h;
985 cx = m.cx;
986 st = m.st;
987 cm = m.cm;
988 m_translations = m.m_translations;
989 m_fileName = m.m_fileName;
990 m_lineNumber = m.m_lineNumber;
991 return *this;
992 }
993
994
995 /*!
996 \fn uint TranslatorMessage::hash() const
997
998 Returns the hash value used internally to represent the lookup
999 key. This value is zero only if this translator message was
1000 constructed from a stream containing invalid data.
1001
1002 The hashing function is unspecified, but it will remain unchanged
1003 in future versions of Qt.
1004 */
1005
1006 /*!
1007 \fn const char *TranslatorMessage::context() const
1008
1009 Returns the context for this message (e.g. "MyDialog").
1010 */
1011
1012 /*!
1013 \fn const char *TranslatorMessage::sourceText() const
1014
1015 Returns the source text of this message (e.g. "&Save").
1016 */
1017
1018 /*!
1019 \fn const char *TranslatorMessage::comment() const
1020
1021 Returns the comment for this message (e.g. "File|Save").
1022 */
1023
1024 /*!
1025 \fn void TranslatorMessage::setTranslation(const QString & translation)
1026
1027 Sets the translation of the source text to \a translation.
1028
1029 \sa translation()
1030 */
1031
1032 /*!
1033 \fn QString TranslatorMessage::translation() const
1034
1035 Returns the translation of the source text (e.g., "&Sauvegarder").
1036
1037 \sa setTranslation()
1038 */
1039
1040 /*!
1041 \enum TranslatorMessage::Prefix
1042
1043 Let (\e h, \e c, \e s, \e m) be the extended key. The possible
1044 prefixes are
1045
1046 \value NoPrefix no prefix
1047 \value Hash only (\e h)
1048 \value HashContext only (\e h, \e c)
1049 \value HashContextSourceText only (\e h, \e c, \e s)
1050 \value HashContextSourceTextComment the whole extended key, (\e
1051 h, \e c, \e s, \e m)
1052
1053 \sa write() commonPrefix()
1054 */
1055
1056 /*!
1057 Writes this translator message to the \a stream. If \a strip is
1058 false (the default), all the information in the message is
1059 written. If \a strip is true, only the part of the extended key
1060 specified by \a prefix is written with the translation (\c
1061 HashContextSourceTextComment by default).
1062
1063 \sa commonPrefix()
1064 */
1065
write(QDataStream & stream,bool strip,Prefix prefix) const1066 void TranslatorMessage::write(QDataStream & stream, bool strip, Prefix prefix) const
1067 {
1068 for (int i = 0; i < m_translations.count(); ++i)
1069 stream << quint8(Tag_Translation) << m_translations.at(i);
1070
1071 if (!strip)
1072 prefix = HashContextSourceTextComment;
1073
1074 switch (prefix) {
1075 case HashContextSourceTextComment:
1076 stream << quint8(Tag_Comment) << cm;
1077 // fall through
1078 case HashContextSourceText:
1079 stream << quint8(Tag_SourceText) << st;
1080 // fall through
1081 case HashContext:
1082 stream << quint8(Tag_Context) << cx;
1083 default:
1084 ;
1085 }
1086
1087 stream << quint8(Tag_End);
1088 }
1089
1090
1091 /*!
1092 Returns the widest lookup prefix that is common to this translator
1093 message and to message \a m.
1094
1095 For example, if the extended key is for this message is (71,
1096 "PrintDialog", "Yes", "Print?") and that for \a m is (71,
1097 "PrintDialog", "No", "Print?"), this function returns \c
1098 HashContext.
1099
1100 \sa write()
1101 */
1102
commonPrefix(const TranslatorMessage & m) const1103 TranslatorMessage::Prefix TranslatorMessage::commonPrefix(
1104 const TranslatorMessage& m) const
1105 {
1106 if (h != m.h)
1107 return NoPrefix;
1108 if (cx != m.cx)
1109 return Hash;
1110 if (st != m.st)
1111 return HashContext;
1112 if (cm != m.cm)
1113 return HashContextSourceText;
1114 return HashContextSourceTextComment;
1115 }
1116
1117
1118 /*!
1119 Returns true if the extended key of this object is equal to that of
1120 \a m; otherwise returns false.
1121 */
1122
operator ==(const TranslatorMessage & m) const1123 bool TranslatorMessage::operator==(const TranslatorMessage& m) const
1124 {
1125 bool isHashEq = (h == m.h ? true : false);
1126 bool isContextEq = (cx == m.cx ? true : false);
1127 bool isSourceEq = (st == m.st ? true : false);
1128 bool isCommentEq = (cm == m.cm ? true : false);
1129 bool isLocationEq = m_lineNumber == m.m_lineNumber && m_fileName == m.m_fileName;
1130
1131 return (isHashEq && isContextEq && isSourceEq && isCommentEq) || // translation can be different, but treat the equal
1132 (st.isEmpty() && isContextEq && isCommentEq && isLocationEq);
1133 }
1134
1135
1136 /*!
1137 \fn bool TranslatorMessage::operator!=(const TranslatorMessage& m) const
1138
1139 Returns true if the extended key of this object is different from
1140 that of \a m; otherwise returns false.
1141 */
1142
1143
1144 /*!
1145 Returns true if the extended key of this object is
1146 lexicographically before than that of \a m; otherwise returns
1147 false.
1148 */
1149
operator <(const TranslatorMessage & m) const1150 bool TranslatorMessage::operator<(const TranslatorMessage& m) const
1151 {
1152 return h != m.h ? h < m.h
1153 : (cx != m.cx ? cx < m.cx
1154 : (st != m.st ? st < m.st : cm < m.cm));
1155 }
1156
1157
1158 /*!
1159 \fn bool TranslatorMessage::operator<=(const TranslatorMessage& m) const
1160
1161 Returns true if the extended key of this object is
1162 lexicographically before that of \a m or if they are equal;
1163 otherwise returns false.
1164 */
1165
1166 /*!
1167 \fn bool TranslatorMessage::operator>(const TranslatorMessage& m) const
1168
1169 Returns true if the extended key of this object is
1170 lexicographically after that of \a m; otherwise returns false.
1171 */
1172
1173 /*!
1174 \fn bool TranslatorMessage::operator>=(const TranslatorMessage& m) const
1175
1176 Returns true if the extended key of this object is
1177 lexicographically after that of \a m or if they are equal;
1178 otherwise returns false.
1179 */
1180
1181 /*!
1182 \fn QString Translator::find(const char *context, const char *sourceText, const char * comment) const
1183
1184 Use findMessage() instead.
1185 */
1186
getNumerusInfo(QLocale::Language language,QLocale::Country country,QStringList * forms)1187 bool getNumerusInfo(QLocale::Language language, QLocale::Country country,
1188 QStringList *forms)
1189 {
1190 forever {
1191 for (int i = 0; i < NumerusTableSize; ++i) {
1192 const NumerusTableEntry &entry = numerusTable[i];
1193 for (int j = 0; entry.languages[j] != EOL; ++j) {
1194 if (entry.languages[j] == language
1195 && ((!entry.countries && country == QLocale::AnyCountry)
1196 || (entry.countries && entry.countries[j] == country))) {
1197 if (forms) {
1198 forms->clear();
1199 for (int k = 0; entry.forms[k]; ++k)
1200 forms->append(QLatin1String(entry.forms[k]));
1201 }
1202 return true;
1203 }
1204 }
1205 }
1206
1207 if (country == QLocale::AnyCountry)
1208 break;
1209 country = QLocale::AnyCountry;
1210 }
1211 return false;
1212 }
1213
1214 #endif // QT_NO_TRANSLATION
1215