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