1 //=============================================================================
2 //
3 // File : KviLocale.cpp
4 // Creation date : Fri Mar 19 1999 19:08:41 by Szymon Stefanek
5 //
6 // This file is part of the KVIrc IRC client distribution
7 // Copyright (C) 1999-2010 Szymon Stefanek (pragma at kvirc dot net)
8 // Copyright (C) 2011 Elvio Basello (hellvis69 at gmail dot com)
9 //
10 // This program is FREE software. You can redistribute it and/or
11 // modify it under the terms of the GNU General Public License
12 // as published by the Free Software Foundation; either version 2
13 // of the License, or (at your option) any later version.
14 //
15 // This program is distributed in the HOPE that it will be USEFUL,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 // See the GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with this program. If not, write to the Free Software Foundation,
22 // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 //
24 //=============================================================================
25
26 #include "kvi_debug.h"
27 #include "kvi_defaults.h"
28 #include "KviMemory.h"
29 #include "KviByteOrder.h"
30
31 #define _KVI_LOCALE_CPP_
32 #include "KviLocale.h"
33 #include "KviCString.h"
34 #include "KviQString.h"
35 #include "KviEnvironment.h"
36 #include "KviFileUtils.h"
37 #include "KviFile.h"
38 #include "KviPointerHashTable.h"
39 #include "KviTranslator.h"
40
41 #include <QApplication>
42 #include <QByteArray>
43 #include <QDir>
44 #include <QLocale>
45 #include <QString>
46 #include <QTextCodec>
47 #include <QtGlobal>
48
49 KVILIB_API KviMessageCatalogue * g_pMainCatalogue = nullptr;
50
51 static KviTranslator * g_pTranslator = nullptr;
52 static KviPointerHashTable<const char *, KviMessageCatalogue> * g_pCatalogueDict = nullptr;
53 static QTextCodec * g_pUtf8TextCodec = nullptr;
54 static QString g_szDefaultLocalePath; // FIXME: Convert this to a search path list
55
56 //
57 // The following code was extracted and adapted from gutf8.c
58 // from the GNU GLIB2 package.
59 //
60 // gutf8.c - Operations on UTF-8 strings.
61 //
62 // Copyright (C) 1999 Tom Tromey
63 // Copyright (C) 2000 Red Hat, Inc.
64 //
65 // This library is free software; you can redistribute it and/or
66 // modify it under the terms of the GNU Lesser General Public
67 // License as published by the Free Software Foundation; either
68 // version 2 of the License, or (at your option) any later version.
69 //
70 // This library is distributed in the hope that it will be useful,
71 // but WITHOUT ANY WARRANTY; without even the implied warranty of
72 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
73 // Lesser General Public License for more details.
74 //
75 // You should have received a copy of the GNU Lesser General Public
76 // License along with this library; if not, write to the
77 // Free Software Foundation, Inc., 59 Temple Place - Suite 330,
78 // Boston, MA 02111-1307, USA.
79 //
80
81 #define UNICODE_VALID(Char) \
82 ((Char) < 0x110000 && (((Char)&0xFFFFF800) != 0xD800) && ((Char) < 0xFDD0 || (Char) > 0xFDEF) && ((Char)&0xFFFE) != 0xFFFE)
83
84 #define CONTINUATION_CHAR \
85 if((*(unsigned char *)p & 0xc0) != 0x80) /* 10xxxxxx */ \
86 goto error; \
87 val <<= 6; \
88 val |= (*(unsigned char *)p) & 0x3f;
89
fast_validate(const char * str)90 static const char * fast_validate(const char * str)
91 {
92 unsigned int val = 0;
93 unsigned int min = 0;
94 const char * p;
95
96 for(p = str; *p; p++)
97 {
98 if(*(unsigned char *)p < 128)
99 /* done */;
100 else
101 {
102 const char * last;
103
104 last = p;
105 if((*(unsigned char *)p & 0xe0) == 0xc0) /* 110xxxxx */
106 {
107 if((*(unsigned char *)p & 0x1e) == 0)
108 goto error;
109 p++;
110 if((*(unsigned char *)p & 0xc0) != 0x80) /* 10xxxxxx */
111 goto error;
112 }
113 else
114 {
115 if((*(unsigned char *)p & 0xf0) == 0xe0) /* 1110xxxx */
116 {
117 min = (1 << 11);
118 val = *(unsigned char *)p & 0x0f;
119 goto TWO_REMAINING;
120 }
121 else if((*(unsigned char *)p & 0xf8) == 0xf0) /* 11110xxx */
122 {
123 min = (1 << 16);
124 val = *(unsigned char *)p & 0x07;
125 }
126 else
127 goto error;
128
129 p++;
130 CONTINUATION_CHAR;
131 TWO_REMAINING:
132 p++;
133 CONTINUATION_CHAR;
134 p++;
135 CONTINUATION_CHAR;
136
137 if(val < min)
138 goto error;
139
140 if(!UNICODE_VALID(val))
141 goto error;
142 }
143
144 continue;
145
146 error:
147 return last;
148 }
149 }
150
151 return p;
152 }
153
fast_validate_len(const char * str,int max_len)154 static const char * fast_validate_len(const char * str, int max_len)
155 {
156 unsigned int val = 0;
157 unsigned int min = 0;
158 const char * p;
159
160 for(p = str; (max_len < 0 || (p - str) < max_len) && *p; p++)
161 {
162 if(*(unsigned char *)p < 128)
163 /* done */;
164 else
165 {
166 const char * last;
167
168 last = p;
169 if((*(unsigned char *)p & 0xe0) == 0xc0) /* 110xxxxx */
170 {
171 if(max_len >= 0 && max_len - (p - str) < 2)
172 goto error;
173
174 if((*(unsigned char *)p & 0x1e) == 0)
175 goto error;
176 p++;
177 if((*(unsigned char *)p & 0xc0) != 0x80) /* 10xxxxxx */
178 goto error;
179 }
180 else
181 {
182 if((*(unsigned char *)p & 0xf0) == 0xe0) /* 1110xxxx */
183 {
184 if(max_len >= 0 && max_len - (p - str) < 3)
185 goto error;
186
187 min = (1 << 11);
188 val = *(unsigned char *)p & 0x0f;
189 goto TWO_REMAINING;
190 }
191 else if((*(unsigned char *)p & 0xf8) == 0xf0) /* 11110xxx */
192 {
193 if(max_len >= 0 && max_len - (p - str) < 4)
194 goto error;
195
196 min = (1 << 16);
197 val = *(unsigned char *)p & 0x07;
198 }
199 else
200 goto error;
201
202 p++;
203 CONTINUATION_CHAR;
204 TWO_REMAINING:
205 p++;
206 CONTINUATION_CHAR;
207 p++;
208 CONTINUATION_CHAR;
209
210 if(val < min)
211 goto error;
212 if(!UNICODE_VALID(val))
213 goto error;
214 }
215
216 continue;
217
218 error:
219 return last;
220 }
221 }
222
223 return p;
224 }
225
g_utf8_validate(const char * str,int max_len,const char ** end)226 static bool g_utf8_validate(const char * str, int max_len, const char ** end)
227 {
228 const char * p;
229
230 if(max_len < 0)
231 p = fast_validate(str);
232 else
233 p = fast_validate_len(str, max_len);
234
235 if(end)
236 *end = p;
237
238 if((max_len >= 0 && p != str + max_len) || (max_len < 0 && *p != '\0'))
239 return false;
240 else
241 return true;
242 }
243
244 //
245 // End of gutf8.c
246 //
247
248 class KviSmartTextCodec : public QTextCodec
249 {
250 private:
251 QByteArray m_szName;
252 QTextCodec * m_pRecvCodec;
253 QTextCodec * m_pSendCodec;
254
255 public:
KviSmartTextCodec(const char * szName,QTextCodec * pChildCodec,bool bSendInUtf8)256 KviSmartTextCodec(const char * szName, QTextCodec * pChildCodec, bool bSendInUtf8)
257 : QTextCodec()
258 {
259 Q_ASSERT(pChildCodec);
260
261 m_pRecvCodec = pChildCodec;
262 m_szName = szName;
263
264 if(!g_pUtf8TextCodec)
265 {
266 g_pUtf8TextCodec = QTextCodec::codecForName("UTF-8");
267 if(!g_pUtf8TextCodec)
268 {
269 qDebug("Can't find the global UTF-8 text codec!");
270 g_pUtf8TextCodec = QTextCodec::codecForLocale(); // try anything else...
271 if(!g_pUtf8TextCodec)
272 qDebug("Argh! Have no UTF-8 text codec: we're in trouble.");
273 }
274 }
275
276 m_pSendCodec = bSendInUtf8 ? g_pUtf8TextCodec : pChildCodec;
277 }
278
279 public:
ok()280 bool ok() { return m_pRecvCodec && g_pUtf8TextCodec; };
281
mibEnum() const282 int mibEnum() const override { return 0; };
283
name() const284 QByteArray name() const override { return m_szName; };
285 protected:
convertFromUnicode(const QChar * input,int number,ConverterState * state) const286 QByteArray convertFromUnicode(const QChar * input, int number, ConverterState * state) const override
287 {
288 return m_pSendCodec->fromUnicode(input, number, state);
289 }
convertToUnicode(const char * chars,int len,ConverterState * state) const290 QString convertToUnicode(const char * chars, int len, ConverterState * state) const override
291 {
292 if(g_utf8_validate(chars, len, nullptr))
293 return g_pUtf8TextCodec->toUnicode(chars, len, state);
294
295 return m_pRecvCodec->toUnicode(chars, len, state);
296 }
297 };
298
299 static KviPointerHashTable<const char *, KviSmartTextCodec> * g_pSmartCodecDict = nullptr;
300
301 static const char * encoding_groups[] = {
302 "Unicode",
303 "West European",
304 "East European",
305 "Cyrillic",
306 "Middle Eastern",
307 #ifndef QT_NO_BIG_CODECS
308 "Chinese",
309 "Japanese",
310 "Other Asiatic",
311 #endif
312 nullptr
313 };
314
315 static KviLocale::EncodingDescription supported_encodings[] = {
316 // clang-format off
317 // Unicode
318 { "UTF-8" , 0 , 0 , 0, "8-bit Unicode" },
319 // West European
320 { "ISO-8859-1" , 0 , 0 , 1, "Western, Latin-1" },
321 { "ISO-8859-15" , 0 , 0 , 1, "Western, Latin-1 + Euro" },
322 { "IBM-850" , 0 , 0 , 1, "IBM-850" },
323 { "CP-1252" , 0 , 0 , 1, "Western Codepage" },
324 { "ISO-8859-14" , 0 , 0 , 1, "Celtic" },
325 { "ISO-8859-7" , 0 , 0 , 1, "Greek" },
326 { "CP-1253" , 0 , 0 , 1, "Greek Codepage" },
327 { "ISO-8859-10" , 0 , 0 , 1, "Nordic" },
328 { "ISO-8859-3" , 0 , 0 , 1, "South European" },
329 // East European
330 { "ISO-8859-4" , 0 , 0 , 2, "Baltic, Standard" },
331 { "ISO-8859-13" , 0 , 0 , 2, "Baltic" },
332 { "CP-1257" , 0 , 0 , 2, "Baltic Codepage" },
333 { "ISO-8859-2" , 0 , 0 , 2, "Central European" },
334 { "CP-1250" , 0 , 0 , 2, "Central European Codepage" },
335 { "ISO-8859-9" , 0 , 0 , 2, "Turkish, Latin-5" },
336 { "CP-1254" , 0 , 0 , 2, "Turkish Codepage" },
337 // Cyrillic
338 { "ISO-8859-5" , 0 , 0 , 3, "Cyrillic, ISO" },
339 { "CP-1251" , 0 , 0 , 3, "Cyrillic Codepage" },
340 { "KOI8-R" , 0 , 0 , 3, "Cyrillic, KOI" },
341 { "KOI8-U" , 0 , 0 , 3, "Cyrillic/Ukrainian" },
342 { "IBM-866" , 0 , 0 , 3, "IBM-866" },
343 // Middle Eastern
344 { "ISO-8859-6" , 0 , 0 , 4, "Arabic, Standard" },
345 { "CP-1256" , 0 , 0 , 4, "Arabic Codepage" },
346 { "ISO-8859-8" , 0 , 0 , 4, "Hebrew, visually ordered" },
347 { "ISO-8859-8-i" , 0 , 0 , 4, "Hebrew, logically ordered" },
348 { "CP-1255" , 0 , 0 , 4, "Hebrew Codepage" },
349 // Other Asiatic
350 { "TIS-620" , 0 , 0 , 7, "Thai" },
351 { "CP874" , 0 , 0 , 7, "Thai Codepage" },
352 #ifndef QT_NO_BIG_CODECS
353 // Chinese
354 { "Big5" , 0 , 0 , 5, "Chinese Traditional" },
355 { "Big5-HKSCS" , 0 , 0 , 5, "Chinese Traditional, Hong Kong" },
356 { "GB18030" , 0 , 0 , 5, "Chinese Simplified" },
357 // Japanese
358 { "ISO-2022-JP" , 0 , 0 , 6, "Japanese (ISO-2022-JP/JIS7)" },
359 { "Shift-JIS" , 0 , 0 , 6, "Japanese (Shift-JIS)" },
360 { "EUC-JP" , 0 , 0 , 6, "Japanese (EUC-JP)" },
361 // Other Asiatic
362 { "EUC-KR" , 0 , 0 , 7, "Korean" },
363 { "TSCII" , 0 , 0 , 7, "Tamil" },
364 { "CP-949" , 0 , 0 , 7, "Korean Codepage" },
365 #endif
366
367 // smart codecs that send in the local charset
368 // West European
369 { "ISO-8859-1 [UTF-8]" , 1 , 0 , 1, "Western, Latin-1 - Unicode" },
370 { "ISO-8859-15 [UTF-8]" , 1 , 0 , 1, "Western, Latin-1 + Euro - Unicode" },
371 { "IBM-850 [UTF-8]" , 1 , 0 , 1, "IBM-850 - Unicode" },
372 { "CP-1252 [UTF-8]" , 1 , 0 , 1, "Western Codepage - Unicode" },
373 { "ISO-8859-14 [UTF-8]" , 1 , 0 , 1, "Celtic - Unicode" },
374 { "ISO-8859-7 [UTF-8]" , 1 , 0 , 1, "Greek - Unicode" },
375 { "CP-1253 [UTF-8]" , 1 , 0 , 1, "Greek Codepage - Unicode" },
376 { "ISO-8859-10 [UTF-8]" , 1 , 0 , 1, "Nordic - Unicode" },
377 { "ISO-8859-3 [UTF-8]" , 1 , 0 , 1, "South European - Unicode" },
378 // East European
379 { "ISO-8859-4 [UTF-8]" , 1 , 0 , 2, "Baltic, Standard - Unicode" },
380 { "ISO-8859-13 [UTF-8]" , 1 , 0 , 2, "Baltic - Unicode" },
381 { "CP-1257 [UTF-8]" , 1 , 0 , 2, "Baltic Codepage - Unicode" },
382 { "ISO-8859-2 [UTF-8]" , 1 , 0 , 2, "Central European - Unicode" },
383 { "CP-1250 [UTF-8]" , 1 , 0 , 2, "Central European Codepage - Unicode" },
384 { "ISO-8859-9 [UTF-8]" , 1 , 0 , 2, "Turkish, Latin-5 - Unicode" },
385 { "CP-1254 [UTF-8]" , 1 , 0 , 2, "Turkish Codepage - Unicode" },
386 // Cyrillic
387 { "ISO-8859-5 [UTF-8]" , 1 , 0 , 3, "Cyrillic, ISO - Unicode" },
388 { "CP-1251 [UTF-8]" , 1 , 0 , 3, "Cyrillic Codepage - Unicode" },
389 { "KOI8-R [UTF-8]" , 1 , 0 , 3, "Cyrillic, KOI - Unicode" },
390 { "KOI8-U [UTF-8]" , 1 , 0 , 3, "Cyrillic/Ukrainian - Unicode" },
391 { "IBM-866 [UTF-8]" , 1 , 0 , 3, "IBM-866 - Unicode" },
392 // Middle Eastern
393 { "ISO-8859-6 [UTF-8]" , 1 , 0 , 4, "Arabic, Standard - Unicode" },
394 { "CP-1256 [UTF-8]" , 1 , 0 , 4, "Arabic Codepage - Unicode" },
395 { "ISO-8859-8 [UTF-8]" , 1 , 0 , 4, "Hebrew, visually ordered - Unicode" },
396 { "ISO-8859-8-i [UTF-8]" , 1 , 0 , 4, "Hebrew, logically ordered - Unicode" },
397 { "CP-1255 [UTF-8]" , 1 , 0 , 4, "Hebrew Codepage - Unicode" },
398 // Other Asiatic
399 { "TIS-620 [UTF-8]" , 1 , 0 , 7, "Thai - Unicode" },
400 { "CP874 [UTF-8]" , 1 , 0 , 7, "Thai Codepage - Unicode" },
401 #ifndef QT_NO_BIG_CODECS
402 // Chinese
403 { "Big5 [UTF-8]" , 1 , 0 , 5, "Chinese Traditional - Unicode" },
404 { "Big5-HKSCS [UTF-8]" , 1 , 0 , 5, "Chinese Traditional, Hong Kong - Unicode" },
405 { "GB18030 [UTF-8]" , 1 , 0 , 5, "Chinese Simplified - Unicode" },
406 // Japanese
407 { "ISO-2022-JP [UTF-8]" , 1 , 0 , 6, "Japanese (ISO-2022-JP/JIS7) - Unicode" },
408 { "Shift-JIS [UTF-8]" , 1 , 0 , 6, "Japanese (Shift-JIS) - Unicode" },
409 { "EUC-JP [UTF-8]" , 1 , 0 , 6, "Japanese (EUC-JP) - Unicode" },
410 // Other Asiatic
411 { "EUC-KR [UTF-8]" , 1 , 0 , 7, "Korean - Unicode" },
412 { "TSCII [UTF-8]" , 1 , 0 , 7, "Tamil - Unicode" },
413 { "CP-949 [UTF-8]" , 1 , 0 , 7, "Korean Codepage - Unicode" },
414 #endif
415
416 // smart codecs that send in utf8
417 // West European
418 { "UTF-8 [ISO-8859-1]" , 1 , 1 , 1, "Unicode - Western, Latin-1" },
419 { "UTF-8 [ISO-8859-15]" , 1 , 1 , 1, "Unicode - Western, Latin-1 + Euro" },
420 { "UTF-8 [IBM-850]" , 1 , 1 , 1, "Unicode - IBM-850" },
421 { "UTF-8 [CP-1252]" , 1 , 1 , 1, "Unicode - Western Codepage" },
422 { "UTF-8 [ISO-8859-14]" , 1 , 1 , 1, "Unicode - Celtic" },
423 { "UTF-8 [ISO-8859-7]" , 1 , 1 , 1, "Unicode - Greek" },
424 { "UTF-8 [CP-1253]" , 1 , 1 , 1, "Unicode - Greek Codepage" },
425 { "UTF-8 [ISO-8859-10]" , 1 , 1 , 1, "Unicode - Nordic" },
426 { "UTF-8 [ISO-8859-3]" , 1 , 1 , 1, "Unicode - South European" },
427 // East European
428 { "UTF-8 [ISO-8859-4]" , 1 , 1 , 2, "Unicode - Baltic, Standard" },
429 { "UTF-8 [ISO-8859-13]" , 1 , 1 , 2, "Unicode - Baltic" },
430 { "UTF-8 [CP-1257]" , 1 , 1 , 2, "Unicode - Baltic Codepage" },
431 { "UTF-8 [ISO-8859-2]" , 1 , 1 , 2, "Unicode - Central European" },
432 { "UTF-8 [CP-1250]" , 1 , 1 , 2, "Unicode - Central European Codepage" },
433 { "UTF-8 [ISO-8859-9]" , 1 , 1 , 2, "Unicode - Turkish, Latin-5" },
434 { "UTF-8 [CP-1254]" , 1 , 1 , 2, "Unicode - Turkish Codepage" },
435 // Cyrillic
436 { "UTF-8 [ISO-8859-5]" , 1 , 1 , 3, "Unicode - Cyrillic, ISO" },
437 { "UTF-8 [CP-1251]" , 1 , 1 , 3, "Unicode - Cyrillic Codepage" },
438 { "UTF-8 [KOI8-R]" , 1 , 1 , 3, "Unicode - Cyrillic, KOI" },
439 { "UTF-8 [KOI8-U]" , 1 , 1 , 3, "Unicode - Cyrillic/Ukrainian" },
440 { "UTF-8 [IBM-866]" , 1 , 1 , 3, "Unicode - IBM-866" },
441 // Middle Eastern
442 { "UTF-8 [ISO-8859-6]" , 1 , 1 , 4, "Unicode - Arabic, Standard" },
443 { "UTF-8 [CP-1256]" , 1 , 1 , 4, "Unicode - Arabic Codepage" },
444 { "UTF-8 [ISO-8859-8]" , 1 , 1 , 4, "Unicode - Hebrew, visually ordered" },
445 { "UTF-8 [ISO-8859-8-i]" , 1 , 1 , 4, "Unicode - Hebrew, logically ordered" },
446 { "UTF-8 [CP-1255]" , 1 , 1 , 4, "Unicode - Hebrew Codepage" },
447 // Other Asiatic
448 { "UTF-8 [TIS-620]" , 1 , 1 , 7, "Unicode - Thai" },
449 { "UTF-8 [CP874]" , 1 , 1 , 7, "Unicode - Thai Codepage" },
450 #ifndef QT_NO_BIG_CODECS
451 // Chinese
452 { "UTF-8 [Big5]" , 1 , 1 , 5, "Unicode - Chinese Traditional" },
453 { "UTF-8 [Big5-HKSCS]" , 1 , 1 , 5, "Unicode - Chinese Traditional, Hong Kong" },
454 { "UTF-8 [GB18030]" , 1 , 1 , 5, "Unicode - Chinese Simplified" },
455 // Japanese
456 { "UTF-8 [ISO-2022-JP]" , 1 , 1 , 6, "Unicode - Japanese (ISO-2022-JP/JIS7)" },
457 { "UTF-8 [Shift-JIS]" , 1 , 1 , 6, "Unicode - Japanese (Shift-JIS)" },
458 { "UTF-8 [EUC-JP]" , 1 , 1 , 6, "Unicode - Japanese (EUC-JP)" },
459 // Other Asiatic
460 { "UTF-8 [EUC-KR]" , 1 , 1 , 7, "Unicode - Korean" },
461 { "UTF-8 [TSCII]" , 1 , 1 , 7, "Unicode - Tamil" },
462 { "UTF-8 [CP-949]" , 1 , 1 , 7, "Unicode - Korean Codepage" },
463 #endif
464 { nullptr , 0 , 0 , 0 , nullptr }
465 // clang-format on
466 };
467
468 KviLocale * KviLocale::m_pSelf = nullptr;
469 unsigned int KviLocale::m_uCount = 0;
470 QString KviLocale::g_szLang = "";
471
KviLocale(QApplication * pApp,const QString & szLocaleDir,const QString & szForceLocaleDir)472 KviLocale::KviLocale(QApplication * pApp, const QString & szLocaleDir, const QString & szForceLocaleDir)
473 {
474 m_pApp = pApp;
475
476 // first of all try to find out the current locale
477 QString szLangFile = QString("%1/%2").arg(szForceLocaleDir, KVI_FORCE_LOCALE_FILE_NAME);
478
479 if(KviFileUtils::fileExists(szLangFile))
480 KviFileUtils::readFile(szLangFile, g_szLang);
481 if(g_szLang.isEmpty())
482 g_szLang = KviEnvironment::getVariable("KVIRC_LANG");
483 #ifdef COMPILE_KDE_SUPPORT
484 if(g_szLang.isEmpty())
485 g_szLang = KviEnvironment::getVariable("KDE_LANG");
486 #endif //COMPILE_KDE_SUPPORT
487 if(g_szLang.isEmpty())
488 g_szLang = KviEnvironment::getVariable("LC_MESSAGES");
489 if(g_szLang.isEmpty())
490 g_szLang = KviEnvironment::getVariable("LANG");
491 if(g_szLang.isEmpty())
492 g_szLang = QLocale::system().name();
493 if(g_szLang.isEmpty())
494 g_szLang = "en";
495 g_szLang = g_szLang.trimmed();
496
497 g_szDefaultLocalePath = szLocaleDir;
498
499 // the main catalogue is supposed to be kvirc_<language>.mo
500 g_pMainCatalogue = new KviMessageCatalogue();
501 // the catalogue dict
502 g_pCatalogueDict = new KviPointerHashTable<const char *, KviMessageCatalogue>;
503 g_pCatalogueDict->setAutoDelete(true);
504
505 // the smart codec dict
506 g_pSmartCodecDict = new KviPointerHashTable<const char *, KviSmartTextCodec>;
507 // the Qt docs explicitly state that we shouldn't delete
508 // the codecs by ourselves...
509 g_pSmartCodecDict->setAutoDelete(false);
510
511 if(!g_szLang.isEmpty())
512 {
513 // ensure Qt will use the right locale when formatting dates, numbers, ..
514 // Note: Qt will use the system() locale anyway, we need to construct an empty QLocale()
515 // to get it use our specified locale.
516 QLocale::setDefault(QLocale(g_szLang));
517
518 QString szBuffer;
519 if(findCatalogue(szBuffer, "kvirc", szLocaleDir))
520 {
521 g_pMainCatalogue->load(szBuffer);
522 g_pTranslator = new KviTranslator(m_pApp);
523 m_pApp->installTranslator(g_pTranslator);
524 }
525 else
526 {
527 KviCString szTmp = g_szLang;
528 szTmp.cutFromFirst('.');
529 szTmp.cutFromFirst('_');
530 szTmp.cutFromFirst('@');
531 szTmp.toLower();
532 if(!(kvi_strEqualCI(szTmp.ptr(), "en") || kvi_strEqualCI(szTmp.ptr(), "c") || kvi_strEqualCI(szTmp.ptr(), "us") || kvi_strEqualCI(szTmp.ptr(), "gb") || kvi_strEqualCI(szTmp.ptr(), "posix")))
533 {
534 // FIXME: THIS IS NO LONGER VALID!!!
535 qDebug("Can't find the catalogue for locale \"%s\" (%s)", g_szLang.toUtf8().data(), szTmp.ptr());
536 qDebug("There is no such translation or the $LANG variable was incorrectly set");
537 qDebug("You can use $KVIRC_LANG to override the catalogue name");
538 qDebug("For example you can set KVIRC_LANG to it_IT to force usage of the it.mo catalogue");
539 }
540 }
541 }
542 }
543
~KviLocale()544 KviLocale::~KviLocale()
545 {
546 delete g_pMainCatalogue;
547 delete g_pCatalogueDict;
548 delete g_pSmartCodecDict;
549 g_pMainCatalogue = nullptr;
550 g_pCatalogueDict = nullptr;
551 g_pSmartCodecDict = nullptr;
552 if(g_pTranslator)
553 {
554 m_pApp->removeTranslator(g_pTranslator);
555 delete g_pTranslator;
556 g_pTranslator = nullptr;
557 }
558 }
559
init(QApplication * pApp,const QString & szLocaleDir,const QString & szForceLocaleDir)560 void KviLocale::init(QApplication * pApp, const QString & szLocaleDir, const QString & szForceLocaleDir)
561 {
562 if((!m_pSelf) && (m_pSelf->count() == 0))
563 {
564 m_pSelf = new KviLocale(pApp, szLocaleDir, szForceLocaleDir);
565 m_uCount++;
566 }
567 }
568
done()569 void KviLocale::done()
570 {
571 m_uCount--;
572 if(m_pSelf->count() == 0)
573 delete m_pSelf;
574 }
575
codecForName(const char * pcName)576 QTextCodec * KviLocale::codecForName(const char * pcName)
577 {
578 KviCString szTmp = pcName;
579 bool bSendUtf8;
580
581 int iIdx = szTmp.findFirstIdx('[');
582 if(iIdx != -1)
583 {
584 // Might be a composite codec: either UTF-8 [child codec] or child codec [UTF-8]
585 KviSmartTextCodec * pCodec = g_pSmartCodecDict->find(pcName);
586 if(pCodec)
587 return pCodec; // got cached copy
588
589 if(kvi_strEqualCIN("UTF-8 [", pcName, 7))
590 {
591 // Likely a smart codec that sends UTF-8
592 szTmp.replaceAll("UTF-8 [", "");
593 szTmp.replaceAll("]", "");
594 bSendUtf8 = true;
595 }
596 else
597 {
598 // Likely a smart codec that sends child encoding ?
599 szTmp.cutFromFirst(' ');
600 bSendUtf8 = false;
601 }
602
603 QTextCodec * pChildCodec = QTextCodec::codecForName(szTmp.ptr());
604 if(pChildCodec)
605 {
606 pCodec = new KviSmartTextCodec(pcName, pChildCodec, bSendUtf8);
607
608 if(pCodec->ok())
609 {
610 g_pSmartCodecDict->replace(pcName, pCodec);
611 return pCodec;
612 }
613
614 delete pCodec;
615 }
616 else
617 {
618 // The name of the child codec was invalid: can't create such a smart codec.
619 // We probably screwed up the guess above related to the [ char.
620 // This code path is also triggered by the yircfuzzer by specifying completely invalid codec names.
621 }
622 }
623
624 return QTextCodec::codecForName(pcName);
625 }
626
findCatalogue(QString & szBuffer,const QString & szName,const QString & szLocaleDir)627 bool KviLocale::findCatalogue(QString & szBuffer, const QString & szName, const QString & szLocaleDir)
628 {
629 KviCString szLocale = g_szLang;
630
631 QString szLocDir = szLocaleDir;
632 KviQString::ensureLastCharIs(szLocDir, KVI_PATH_SEPARATOR_CHAR);
633
634 szBuffer = QString("%1%2_%3.mo").arg(szLocDir, szName).arg(szLocale.ptr());
635
636 if(KviFileUtils::fileExists(szBuffer))
637 return true;
638
639 if(szLocale.findFirstIdx('.') != -1)
640 {
641 // things like en_GB.utf8
642 // kill them
643 szLocale.cutFromFirst('.');
644
645 szBuffer = QString("%1%2_%3.mo").arg(szLocDir, szName).arg(szLocale.ptr());
646 if(KviFileUtils::fileExists(szBuffer))
647 return true;
648 }
649
650 if(szLocale.findFirstIdx('@') != -1)
651 {
652 // things like @euro ?
653 // kill them
654 szLocale.cutFromFirst('@');
655 szBuffer = QString("%1%2_%3.mo").arg(szLocDir, szName).arg(szLocale.ptr());
656 if(KviFileUtils::fileExists(szBuffer))
657 return true;
658 }
659
660 if(szLocale.findFirstIdx('_') != -1)
661 {
662 // things like en_GB
663 // kill them
664 szLocale.cutFromFirst('_');
665 szBuffer = QString("%1%2_%3.mo").arg(szLocDir, szName).arg(szLocale.ptr());
666 if(KviFileUtils::fileExists(szBuffer))
667 return true;
668 }
669
670 // try the lower case version too
671 szLocale.toLower();
672 szBuffer = QString("%1%2_%3.mo").arg(szLocDir, szName).arg(szLocale.ptr());
673 if(KviFileUtils::fileExists(szBuffer))
674 return true;
675
676 return false;
677 }
678
loadCatalogue(const QString & szName,const QString & szLocaleDir)679 KviMessageCatalogue * KviLocale::loadCatalogue(const QString & szName, const QString & szLocaleDir)
680 {
681 //qDebug("Looking up catalogue %s",szName.toUtf8().data());
682 QString szBuffer;
683
684 KviMessageCatalogue * pCatalogue = g_pCatalogueDict->find(szName.toUtf8().data());
685 if(pCatalogue)
686 return pCatalogue; // already loaded
687
688 if(!findCatalogue(szBuffer, szName, szLocaleDir))
689 return nullptr;
690
691 pCatalogue = new KviMessageCatalogue();
692 if(pCatalogue->load(szBuffer))
693 {
694 g_pCatalogueDict->insert(szName.toUtf8().data(), pCatalogue);
695 return pCatalogue;
696 }
697 delete pCatalogue;
698 return nullptr;
699 }
700
unloadCatalogue(const QString & szName)701 bool KviLocale::unloadCatalogue(const QString & szName)
702 {
703 //qDebug("Unloading catalogue: %s",szName.toUtf8().data());
704 return g_pCatalogueDict->remove(szName.toUtf8().data());
705 }
706
getLoadedCatalogue(const QString & szName)707 KviMessageCatalogue * KviLocale::getLoadedCatalogue(const QString & szName)
708 {
709 return g_pCatalogueDict->find(szName.toUtf8().data());
710 }
711
encodingGroup(int iIdx)712 const char * KviLocale::encodingGroup(int iIdx)
713 {
714 if(iIdx > KVI_NUM_ENCODING_GROUPS)
715 return encoding_groups[KVI_NUM_ENCODING_GROUPS];
716 return encoding_groups[iIdx];
717 }
718
encodingDescription(int iIdx)719 KviLocale::EncodingDescription * KviLocale::encodingDescription(int iIdx)
720 {
721 if(iIdx > KVI_NUM_ENCODINGS)
722 return &(supported_encodings[KVI_NUM_ENCODINGS]);
723 return &(supported_encodings[iIdx]);
724 }
725
translate(const char * pcText,const char * pcContext)726 const char * KviLocale::translate(const char * pcText, const char * pcContext)
727 {
728 if(!pcContext)
729 return g_pMainCatalogue->translate(pcText);
730
731 KviMessageCatalogue * pCatalogue = g_pCatalogueDict->find(pcContext);
732 if(!pCatalogue)
733 {
734 pCatalogue = loadCatalogue(QString::fromUtf8(pcContext), g_szDefaultLocalePath);
735 if(!pCatalogue)
736 {
737 // Fake it....
738 pCatalogue = new KviMessageCatalogue();
739 g_pCatalogueDict->insert(pcContext, pCatalogue);
740 }
741 }
742 return pCatalogue->translate(pcText);
743 }
744
translateToQString(const char * pcText,const char * pcContext)745 const QString & KviLocale::translateToQString(const char * pcText, const char * pcContext)
746 {
747 if(!pcContext)
748 return g_pMainCatalogue->translateToQString(pcText);
749
750 KviMessageCatalogue * pCatalogue = g_pCatalogueDict->find(pcContext);
751 if(!pCatalogue)
752 {
753 pCatalogue = loadCatalogue(QString::fromUtf8(pcContext), g_szDefaultLocalePath);
754 if(!pCatalogue)
755 {
756 // Fake it....
757 pCatalogue = new KviMessageCatalogue();
758 g_pCatalogueDict->insert(pcContext, pCatalogue);
759 }
760 }
761 return pCatalogue->translateToQString(pcText);
762 }
763