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