1 /*
2 	This file is part of Warzone 2100.
3 	Copyright (C) 2005-2020  Warzone 2100 Project
4 
5 	Warzone 2100 is free software; you can redistribute it and/or modify
6 	it under the terms of the GNU General Public License as published by
7 	the Free Software Foundation; either version 2 of the License, or
8 	(at your option) any later version.
9 
10 	Warzone 2100 is distributed in the hope that it will be useful,
11 	but WITHOUT ANY WARRANTY; without even the implied warranty of
12 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 	GNU General Public License for more details.
14 
15 	You should have received a copy of the GNU General Public License
16 	along with Warzone 2100; if not, write to the Free Software
17 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19 #include "frame.h"
20 
21 #include <sstream>
22 #include <vector>
23 
24 #include <locale.h>
25 #include <physfs.h>
26 #include "wzpaths.h"
27 
28 #ifdef WZ_OS_MAC
29 # include <CoreFoundation/CoreFoundation.h>
30 # include <CoreFoundation/CFURL.h>
31 #endif
32 
33 /* Always use fallbacks on Windows */
34 #if defined(WZ_OS_WIN)
35 #  undef LOCALEDIR
36 #endif
37 
38 #if !defined(LOCALEDIR)
39 #  define LOCALEDIR "locale"
40 #endif
41 
42 #ifdef PORTABLE
43 #  ifdef PACKAGE
44 #   undef PACKAGE
45 #   define PACKAGE "warzone2100_portable"
46 #  endif
47 #endif
48 // Language names (http://en.wikipedia.org/wiki/List_of_ISO_639-2_codes)
49 #define LANG_NAME_BASQUE "euskara"
50 #define LANG_NAME_BULGARIAN "български език"
51 #define LANG_NAME_CATALAN "català"
52 #define LANG_NAME_CHINESE_SIMPLIFIED "汉语"
53 #define LANG_NAME_CHINESE_TRADITIONAL "漢語"
54 #define LANG_NAME_CROATIAN "Hrvatski"
55 #define LANG_NAME_CZECH "česky"
56 #define LANG_NAME_DANISH "Dansk"
57 #define LANG_NAME_DUTCH "Nederlands"
58 #define LANG_NAME_ENGLISH "English"
59 #define LANG_NAME_ENGLISH_UK "British English"
60 #define LANG_NAME_ESTONIAN "Eesti Keel"
61 #define LANG_NAME_FINNISH "suomi"
62 #define LANG_NAME_FRENCH "Français"
63 #define LANG_NAME_FRISIAN_NETHERLANDS "frysk"
64 #define LANG_NAME_GERMAN "Deutsch"
65 #define LANG_NAME_GREEK "Ελληνικά"
66 #define LANG_NAME_HUNGARIAN "magyar"
67 #define LANG_NAME_INDONESIAN "Bahasa Indonesia"
68 #define LANG_NAME_IRISH "Imruagadh"
69 #define LANG_NAME_ITALIAN "Italiano"
70 #define LANG_NAME_KOREAN "한국어"
71 #define LANG_NAME_LATIN "latine"
72 #define LANG_NAME_LATVIAN "latviešu valoda"
73 #define LANG_NAME_LITHUANIAN "lietuvių kalba"
74 #define LANG_NAME_NORWEGIAN "Norsk"
75 #define LANG_NAME_NORWEGIAN_NYNORSK "nynorsk"
76 #define LANG_NAME_POLISH "Polski"
77 #define LANG_NAME_PORTUGUESE_BRAZILIAN "Português Brasileiro"
78 #define LANG_NAME_PORTUGUESE "Português"
79 #define LANG_NAME_ROMANIAN "română"
80 #define LANG_NAME_RUSSIAN "Русский"
81 #define LANG_NAME_SLOVAK "Slovensky"
82 #define LANG_NAME_SLOVENIAN "Slovenski"
83 #define LANG_NAME_SPANISH "Español"
84 #define LANG_NAME_SWEDISH "svenska"
85 #define LANG_NAME_SWEDISH_SWEDEN "svenska (Sverige)"
86 #define LANG_NAME_TURKISH "Türkçe"
87 #define LANG_NAME_UKRAINIAN "Українська"
88 #define LANG_NAME_UZBEK_CYRILLIC "Ўзбек"
89 
90 #if defined(WZ_OS_WIN)
91 /*
92  *  See msdn.microsoft.com for this stuff, esp.
93  *  http://msdn.microsoft.com/en-us/library/ms693062%28VS.85,printer%29.aspx
94  *  http://msdn.microsoft.com/en-us/library/dd318693%28VS.85,printer%29.aspx
95  */
96 static const struct
97 {
98 	const char *language;
99 	const char *name;
100 	USHORT usPrimaryLanguage;
101 	USHORT usSubLanguage;
102 } map[] =
103 {
104 	{ "", N_("System locale"), LANG_NEUTRAL, SUBLANG_DEFAULT },
105 #  if defined(ENABLE_NLS)
106 	{ "bg", LANG_NAME_BULGARIAN, LANG_BULGARIAN, SUBLANG_DEFAULT },
107 	{ "ca", LANG_NAME_CATALAN, LANG_CATALAN, SUBLANG_DEFAULT },
108 	{ "cs", LANG_NAME_CZECH, LANG_CZECH, SUBLANG_DEFAULT },
109 	{ "da", LANG_NAME_DANISH, LANG_DANISH, SUBLANG_DEFAULT },
110 	{ "de", LANG_NAME_GERMAN, LANG_GERMAN, SUBLANG_GERMAN },
111 	{ "el", LANG_NAME_GREEK, LANG_GREEK, SUBLANG_DEFAULT },
112 	{ "en", LANG_NAME_ENGLISH, LANG_ENGLISH, SUBLANG_DEFAULT },
113 	{ "en_GB", LANG_NAME_ENGLISH_UK, LANG_ENGLISH, SUBLANG_ENGLISH_UK },
114 	{ "es", LANG_NAME_SPANISH, LANG_SPANISH, SUBLANG_SPANISH },
115 	{ "et_EE", LANG_NAME_ESTONIAN, LANG_ESTONIAN, SUBLANG_DEFAULT },
116 //	{ "eu", LANG_NAME_BASQUE, LANG_BASQUE, SUBLANG_DEFAULT },
117 	{ "fi", LANG_NAME_FINNISH, LANG_FINNISH, SUBLANG_DEFAULT },
118 	{ "fr", LANG_NAME_FRENCH, LANG_FRENCH, SUBLANG_FRENCH },
119 	/* Our Frisian translation is the "West Frisian" variation of it. This
120 	 * variation is mostly spoken in the Dutch province Friesland (Fryslân
121 	 * in Frisian) and has ISO 639-3 code "fry".
122 	 *
123 	 * FIXME: We should really use a sub-language code for this. E.g.
124 	 *        fy_XX.
125 	 */
126 	{ "fy", LANG_NAME_FRISIAN_NETHERLANDS, LANG_FRISIAN, SUBLANG_FRISIAN_NETHERLANDS },
127 	{ "ga", LANG_NAME_IRISH, LANG_IRISH, SUBLANG_IRISH_IRELAND },
128 	{ "hr", LANG_NAME_CROATIAN, LANG_CROATIAN, SUBLANG_DEFAULT },
129 	{ "hu", LANG_NAME_HUNGARIAN, LANG_HUNGARIAN, SUBLANG_DEFAULT },
130 	{ "id", LANG_NAME_INDONESIAN, LANG_INDONESIAN, SUBLANG_DEFAULT },
131 	{ "it", LANG_NAME_ITALIAN, LANG_ITALIAN, SUBLANG_ITALIAN },
132 	{ "ko_KR", LANG_NAME_KOREAN, LANG_KOREAN, SUBLANG_DEFAULT },
133 //	{ "la", LANG_NAME_LATIN, LANG_LATIN, SUBLANG_DEFAULT },
134 	{ "lt", LANG_NAME_LITHUANIAN, LANG_LITHUANIAN, SUBLANG_DEFAULT },
135 //	{ "lv", LANG_NAME_LATVIAN, LANG_LATVIAN, SUBLANG_DEFAULT },
136 	// MSDN uses "no"...
137 	{ "nb", LANG_NAME_NORWEGIAN, LANG_NORWEGIAN, SUBLANG_DEFAULT },
138 //	{ "nn", LANG_NAME_NORWEGIAN_NYNORSK, LANG_NORWEGIAN, SUBLANG_NORWEGIAN_NYNORSK },
139 	{ "nl", LANG_NAME_DUTCH, LANG_DUTCH, SUBLANG_DUTCH },
140 	{ "pl", LANG_NAME_POLISH, LANG_POLISH, SUBLANG_DEFAULT },
141 	{ "pt_BR", LANG_NAME_PORTUGUESE_BRAZILIAN, LANG_PORTUGUESE, SUBLANG_PORTUGUESE_BRAZILIAN },
142 	{ "pt", LANG_NAME_PORTUGUESE, LANG_PORTUGUESE, SUBLANG_DEFAULT },
143 	{ "ro", LANG_NAME_ROMANIAN, LANG_ROMANIAN, SUBLANG_DEFAULT },
144 	{ "ru", LANG_NAME_RUSSIAN, LANG_RUSSIAN, SUBLANG_DEFAULT },
145 	{ "sk", LANG_NAME_SLOVAK, LANG_SLOVAK, SUBLANG_DEFAULT },
146 	{ "sl", LANG_NAME_SLOVENIAN, LANG_SLOVENIAN, SUBLANG_DEFAULT },
147 #if (WINVER >= 0x0600)
148 //	{ "sv_SE", LANG_NAME_SWEDISH_SWEDEN, LANG_SWEDISH, SUBLANG_SWEDISH_SWEDEN },
149 #else
150 //	{ "sv_SE", LANG_NAME_SWEDISH_SWEDEN, LANG_SWEDISH, SUBLANG_SWEDISH },
151 #endif
152 //	{ "sv", LANG_NAME_SWEDISH, LANG_SWEDISH, SUBLANG_DEFAULT },
153 	{ "tr", LANG_NAME_TURKISH, LANG_TURKISH, SUBLANG_DEFAULT },
154 //	{ "uz", LANG_NAME_UZBEK_CYRILLIC, LANG_UZBEK, SUBLANG_UZBEK_CYRILLIC },
155 	{ "uk_UA", LANG_NAME_UKRAINIAN, LANG_UKRAINIAN, SUBLANG_DEFAULT },
156 	{ "zh_CN", LANG_NAME_CHINESE_SIMPLIFIED, LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED },
157 	{ "zh_TW", LANG_NAME_CHINESE_TRADITIONAL, LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL },
158 #  endif
159 };
160 #else
161 static const struct
162 {
163 	const char *language;
164 	const char *name;
165 	const char *locale;
166 	const char *localeFallback;
167 } map[] =
168 {
169 	{ "",   N_("System locale"), "", "" },
170 #  if defined(ENABLE_NLS)
171 	{ "bg", LANG_NAME_BULGARIAN, "bg_BG.UTF-8", "bg" },
172 	{ "ca_ES", LANG_NAME_CATALAN, "ca_ES.UTF-8", "ca" },
173 	{ "cs_CZ", LANG_NAME_CZECH, "cs_CZ.UTF-8", "cs" },
174 	{ "da", LANG_NAME_DANISH, "da_DK.UTF-8", "da_DK" },
175 	{ "de", LANG_NAME_GERMAN, "de_DE.UTF-8", "de_DE" },
176 	{ "el_GR", LANG_NAME_GREEK, "el_GR.UTF-8", "el" },
177 	{ "en", LANG_NAME_ENGLISH, "en_US.UTF-8", "en_US" },
178 	{ "en_GB", LANG_NAME_ENGLISH_UK, "en_GB.UTF-8", "en_GB" },
179 	{ "es", LANG_NAME_SPANISH, "es_ES.UTF-8", "es_ES" },
180 	{ "et_EE", LANG_NAME_ESTONIAN, "et_EE.UTF-8", "et" },
181 //	{ "eu_ES", LANG_NAME_BASQUE, "eu_ES.UTF-8", "eu" },
182 	{ "fi", LANG_NAME_FINNISH, "fi_FI.UTF-8", "fi_FI" },
183 	{ "fr", LANG_NAME_FRENCH, "fr_FR.UTF-8", "fr_FR" },
184 	/* Our Frisian translation is the "West Frisian" variation of it. This
185 	 * variation is mostly spoken in the Dutch province Friesland (Fryslân
186 	 * in Frisian) and has ISO 639-3 code "fry".
187 	 */
188 	{ "fy_NL", LANG_NAME_FRISIAN_NETHERLANDS, "fy_NL.UTF-8", "fy" },
189 	{ "ga_IE", LANG_NAME_IRISH, "ga_IE.UTF-8", "ga" },
190 	{ "hr", LANG_NAME_CROATIAN, "hr_HR.UTF-8", "hr_HR" },
191 	{ "hu", LANG_NAME_HUNGARIAN, "hu_HU.UTF-8", "hu_HU" },
192 	{ "id", LANG_NAME_INDONESIAN, "id_ID.UTF-8", "id" },
193 	{ "it", LANG_NAME_ITALIAN, "it_IT.UTF-8", "it_IT" },
194 	{ "ko_KR", LANG_NAME_KOREAN, "ko_KR.UTF-8", "ko" },
195 	{ "la", LANG_NAME_LATIN, "la.UTF-8", "la" },
196 	{ "lt", LANG_NAME_LITHUANIAN, "lt_LT.UTF-8", "lt_LT" },
197 //	{ "lv", LANG_NAME_LATVIAN, "lv_LV.UTF-8", "lv_LV" },
198 	{ "nb_NO", LANG_NAME_NORWEGIAN, "nb_NO.UTF-8", "nb" },
199 //	{ "nn_NO", LANG_NAME_NORWEGIAN_NYNORSK, "nn_NO.UTF-8", "nn" },
200 	{ "nl", LANG_NAME_DUTCH, "nl_NL.UTF-8", "nl_NL" },
201 	{ "pl", LANG_NAME_POLISH, "pl_PL.UTF-8", "pl_PL" },
202 	{ "pt_BR", LANG_NAME_PORTUGUESE_BRAZILIAN, "pt_BR.UTF-8", "pt_BR" },
203 	{ "pt", LANG_NAME_PORTUGUESE, "pt_PT.UTF-8", "pt_PT" },
204 	{ "ro", LANG_NAME_ROMANIAN, "ro_RO.UTF-8", "ro_RO" },
205 	{ "ru", LANG_NAME_RUSSIAN, "ru_RU.UTF-8", "ru_RU" },
206 	{ "sk", LANG_NAME_SLOVAK, "sk_SK.UTF-8", "sk_SK" },
207 	{ "sl_SI", LANG_NAME_SLOVENIAN, "sl_SI.UTF-8", "sl" },
208 //	{ "sv_SE", LANG_NAME_SWEDISH_SWEDEN, "sv_SE.UTF-8", "sv" },
209 //	{ "sv", LANG_NAME_SWEDISH, "sv.UTF-8", "sv" },
210 	{ "tr", LANG_NAME_TURKISH, "tr_TR.UTF-8", "tr_TR" },
211 //	{ "uz", LANG_NAME_UZBEK_CYRILLIC, "uz_UZ.UTF-8", "uz_UZ" },
212 	{ "uk_UA", LANG_NAME_UKRAINIAN, "uk_UA.UTF-8", "uk" },
213 	{ "zh_CN", LANG_NAME_CHINESE_SIMPLIFIED, "zh_CN.UTF-8", "zh_CN" },
214 	{ "zh_TW", LANG_NAME_CHINESE_TRADITIONAL, "zh_TW.UTF-8", "zh_TW" },
215 #  endif
216 };
217 #endif
218 
219 static unsigned int selectedLanguage = 0;
220 
221 static char *compileDate = nullptr;
222 
223 /*!
224  * Return the language part of the selected locale
225  */
226 #if !defined(ENABLE_NLS)
getLanguage()227 const char *getLanguage()
228 {
229 	return "";
230 }
231 #elif defined(WZ_OS_WIN)
getLanguage()232 const char *getLanguage()
233 {
234 	USHORT usPrimaryLanguage = PRIMARYLANGID(LANGIDFROMLCID(GetThreadLocale()));
235 	unsigned int i;
236 
237 	if (selectedLanguage == 0)
238 	{
239 		return "";  // Return empty string for system default
240 	}
241 
242 	for (i = 0; i < ARRAY_SIZE(map); i++)
243 	{
244 		if (usPrimaryLanguage == map[i].usPrimaryLanguage)
245 		{
246 			return map[i].language;
247 		}
248 	}
249 
250 	return "";
251 }
252 #else
getLanguage()253 const char *getLanguage()
254 {
255 	static char language[6] = { '\0' }; // large enough for xx_YY
256 	const char *localeName = setlocale(LC_MESSAGES, NULL);
257 	char *delim = NULL;
258 
259 	if (selectedLanguage == 0 || localeName == NULL)
260 	{
261 		return "";  // Return empty string for system default and errors
262 	}
263 
264 	sstrcpy(language, localeName);
265 
266 	// cut anything after a '.' to get rid of the encoding part
267 	delim = strchr(language, '.');
268 	if (delim)
269 	{
270 		*delim = '\0';
271 	}
272 
273 	// if language is xx_XX, cut the _XX part
274 	delim = strchr(language, '_');
275 	if (delim)
276 	{
277 		if (!strncasecmp(language, delim + 1, 2))
278 		{
279 			*delim = '\0';
280 		}
281 	}
282 
283 	return language;
284 }
285 #endif
286 
287 
getLanguageName()288 const char *getLanguageName()
289 {
290 	const char *language = getLanguage();
291 	unsigned int i;
292 
293 	for (i = 0; i < ARRAY_SIZE(map); i++)
294 	{
295 		if (strcmp(language, map[i].language) == 0)
296 		{
297 			return gettext(map[i].name);
298 		}
299 	}
300 
301 	return language;
302 }
303 
304 
305 #if defined(ENABLE_NLS)
306 #  if defined(WZ_OS_WIN)
setLocaleWindows(USHORT usPrimaryLanguage,USHORT usSubLanguage)307 static bool setLocaleWindows(USHORT usPrimaryLanguage, USHORT usSubLanguage)
308 {
309 	bool success = SUCCEEDED(SetThreadLocale(MAKELCID(MAKELANGID(usPrimaryLanguage, usSubLanguage), SORT_DEFAULT)));
310 
311 	if (!success)
312 	{
313 		wz_info("Failed to set locale to \"%d\"", usPrimaryLanguage);
314 	}
315 	else
316 	{
317 		debug(LOG_WZ, "Requested locale \"%d\"", usPrimaryLanguage);
318 	}
319 
320 	setlocale(LC_NUMERIC, "C"); // set radix character to the period (".")
321 
322 	return success;
323 }
324 #  else
325 /*!
326  * Set the prefered locale
327  * \param locale The locale, NOT just the language part
328  * \note Use this instead of setlocale(), because we need the default radix character
329  */
setLocaleUnix(const char * locale)330 static bool setLocaleUnix(const char *locale)
331 {
332 #ifdef WZ_OS_MAC
333 	// Set the appropriate language environment variable *before* the call to libintl's `setlocale`.
334 
335 	// First, check if LC_ALL or LC_MESSAGES is set, as these may override LANG
336 	const char *pEnvLang = getenv("LC_ALL");
337 	if ((pEnvLang != nullptr) && (pEnvLang[0] != '\0'))
338 	{
339 		debug(LOG_WARNING, "Environment variable \"LC_ALL\" is set, and may override runtime language changes.");
340 	}
341 	pEnvLang = getenv("LC_MESSAGES");
342 	if ((pEnvLang != nullptr) && (pEnvLang[0] != '\0'))
343 	{
344 		debug(LOG_WARNING, "Environment variable \"LC_MESSAGES\" is set, and may override runtime language changes.");
345 	}
346 
347 	// Set the LANG env-var (temporarily saving the old value)
348 	pEnvLang = getenv("LANG");
349 	std::string prior_LANG((pEnvLang != nullptr) ? pEnvLang : "");
350 #  if defined HAVE_SETENV
351 	setenv("LANG", locale, 1);
352 #  else
353 	# warning "No supported method to set environment variables"
354 #  endif
355 #endif // (ifdef WZ_OS_MAC)
356 
357 	const char *actualLocale = setlocale(LC_ALL, locale);
358 
359 	if (actualLocale == NULL)
360 	{
361 		wz_info("Failed to set locale to \"%s\"", locale);
362 #ifdef WZ_OS_MAC
363 #  if defined HAVE_SETENV
364 		setenv("LANG", prior_LANG.c_str(), 1);
365 #  else
366 		# warning "No supported method to set environment variables"
367 #  endif
368 #endif
369 	}
370 	else
371 	{
372 		if (strcmp(locale, actualLocale))
373 		{
374 			debug(LOG_WZ, "Requested locale \"%s\", got \"%s\" instead", locale, actualLocale);
375 		}
376 	}
377 
378 	const char * numericLocale = setlocale(LC_NUMERIC, "C"); // set radix character to the period (".")
379 	debug(LOG_WZ, "LC_NUMERIC: \"%s\"", (numericLocale != nullptr) ? numericLocale : "<null>");
380 
381 	return (actualLocale != NULL);
382 }
383 #  endif
384 #endif
385 
386 
setLanguage(const char * language)387 bool setLanguage(const char *language)
388 {
389 #if !defined(ENABLE_NLS)
390 	return true;
391 #else
392 	unsigned int i;
393 
394 	for (i = 0; i < ARRAY_SIZE(map); i++)
395 	{
396 		if (strcmp(language, map[i].language) == 0)
397 		{
398 			selectedLanguage = i;
399 			debug(LOG_WZ, "Setting language to \"%s\" (%s)", map[i].name, map[i].language);
400 
401 #  if defined(WZ_OS_WIN)
402 			return setLocaleWindows(map[i].usPrimaryLanguage, map[i].usSubLanguage);
403 #  else
404 			return setLocaleUnix(map[i].locale) || setLocaleUnix(map[i].localeFallback);
405 #  endif
406 		}
407 	}
408 
409 	debug(LOG_ERROR, "Requested language \"%s\" not supported.", language);
410 
411 	return false;
412 #endif
413 }
414 
415 
setNextLanguage(bool prev)416 void setNextLanguage(bool prev)
417 {
418 	selectedLanguage = (selectedLanguage + ARRAY_SIZE(map) + (prev? -1 : 1)) % ARRAY_SIZE(map);
419 
420 	if (!setLanguage(map[selectedLanguage].language) && selectedLanguage != 0)
421 	{
422 		setNextLanguage(prev); // try next
423 	}
424 }
425 
wzBindTextDomain(const char * domainname,const char * dirname)426 std::string wzBindTextDomain(const char *domainname, const char *dirname)
427 {
428 #if (LIBINTL_VERSION >= 0x001500) && defined(_WIN32) && !defined(__CYGWIN__)
429 	// gettext 0.21+ provides a wbindtextdomain function on native Windows platforms
430 	// that properly supports Unicode paths
431 
432 	// convert the dirname from UTF-8 to UTF-16
433 	int wstr_len = MultiByteToWideChar(CP_UTF8, 0, dirname, -1, NULL, 0);
434 	if (wstr_len <= 0)
435 	{
436 		DWORD dwError = GetLastError();
437 		debug(LOG_ERROR, "Could not not convert string from UTF-8; MultiByteToWideChar failed with error %d: %s\n", dwError, dirname);
438 		return std::string();
439 	}
440 	auto wstr_dirname = std::vector<wchar_t>(wstr_len, L'\0');
441 	if (MultiByteToWideChar(CP_UTF8, 0, dirname, -1, &wstr_dirname[0], wstr_len) == 0)
442 	{
443 		DWORD dwError = GetLastError();
444 		debug(LOG_ERROR, "Could not not convert string from UTF-8; MultiByteToWideChar[2] failed with error %d: %s\n", dwError, dirname);
445 		return std::string();
446 	}
447 
448 	// and call wbindtextdomain
449 	const wchar_t *pResult = wbindtextdomain(domainname, wstr_dirname.data());
450 	if (!pResult)
451 	{
452 		debug(LOG_ERROR, "wbindtextdomain failed");
453 		return std::string();
454 	}
455 
456 	// convert the result back to UTF-8
457 	std::vector<char> utf8Buffer;
458 	int utf8Len = WideCharToMultiByte(CP_UTF8, 0, pResult, -1, NULL, 0, NULL, NULL);
459 	if ( utf8Len <= 0 )
460 	{
461 		// Encoding conversion error
462 		DWORD dwError = GetLastError();
463 		debug(LOG_ERROR, "Could not not convert string to UTF-8; WideCharToMultiByte failed with error %d\n", dwError);
464 		return std::string();
465 	}
466 	utf8Buffer.resize(utf8Len, 0);
467 	if ( (utf8Len = WideCharToMultiByte(CP_UTF8, 0, pResult, -1, &utf8Buffer[0], utf8Len, NULL, NULL)) <= 0 )
468 	{
469 		// Encoding conversion error
470 		DWORD dwError = GetLastError();
471 		debug(LOG_ERROR, "Could not not convert string to UTF-8; WideCharToMultiByte[2] failed with error %d\n", dwError);
472 		return std::string();
473 	}
474 	return std::string(utf8Buffer.data(), utf8Len - 1);
475 #else
476 	// call the normal bindtextdomain function
477 	const char * pResult = bindtextdomain(domainname, dirname);
478 	if (!pResult)
479 	{
480 		return std::string();
481 	}
482 	return std::string(pResult);
483 #endif
484 }
485 
initI18n()486 void initI18n()
487 {
488 	std::string textdomainDirectory;
489 
490 	if (!setLanguage("")) // set to system default
491 	{
492 		// no system default?
493 		debug(LOG_ERROR, "initI18n: No system language found");
494 	}
495 #ifdef WZ_OS_MAC
496 	{
497 		char resourcePath[PATH_MAX];
498 		CFURLRef resourceURL = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle());
499 		if (CFURLGetFileSystemRepresentation(resourceURL, true, (UInt8 *) resourcePath, PATH_MAX))
500 		{
501 			sstrcat(resourcePath, "/locale");
502 			textdomainDirectory = wzBindTextDomain(PACKAGE, resourcePath);
503 		}
504 		else
505 		{
506 			debug(LOG_ERROR, "Could not change to resources directory.");
507 		}
508 
509 		if (resourceURL != NULL)
510 		{
511 			CFRelease(resourceURL);
512 		}
513 
514 		debug(LOG_INFO, "resourcePath is %s", resourcePath);
515 	}
516 #else
517 # ifdef WZ_LOCALEDIR
518 	// New locale-dir setup (CMake)
519 	#ifndef WZ_LOCALEDIR_ISABSOLUTE
520 	// Treat WZ_LOCALEDIR as a relative path - append to the install PREFIX
521 	const std::string prefixDir = getWZInstallPrefix();
522 	const std::string dirSeparator(PHYSFS_getDirSeparator());
523 	std::string localeDir = prefixDir + dirSeparator + WZ_LOCALEDIR;
524 	textdomainDirectory = wzBindTextDomain(PACKAGE, localeDir.c_str());
525 	#else
526 	// Treat WZ_LOCALEDIR as an absolute path, and use directly
527 	textdomainDirectory = wzBindTextDomain(PACKAGE, WZ_LOCALEDIR);
528 	#endif
529 # else
530 	// Old locale-dir setup (autotools)
531 	textdomainDirectory = wzBindTextDomain(PACKAGE, LOCALEDIR);
532 # endif
533 #endif // ifdef WZ_OS_MAC
534 	if (textdomainDirectory.empty())
535 	{
536 		debug(LOG_ERROR, "initI18n: bindtextdomain failed!");
537 	}
538 
539 	(void)bind_textdomain_codeset(PACKAGE, "UTF-8");
540 	(void)textdomain(PACKAGE);
541 }
542 
543 // convert macro __DATE__ to ISO 8601 format
getCompileDate()544 const char *getCompileDate()
545 {
546 	if (compileDate == nullptr)
547 	{
548 		std::istringstream date(__DATE__);
549 		std::string monthName;
550 		int day = 0, month = 0, year = 0;
551 		date >> monthName >> day >> year;
552 
553 		std::string monthNames[] = {
554 						"Jan", "Feb", "Mar", "Apr",
555 						"May", "Jun", "Jul", "Aug",
556 						"Sep", "Oct", "Nov", "Dec"
557 		                           };
558 		for (int i = 0; i < 12; i++)
559 		{
560 			if (monthNames[i] == monthName)
561 			{
562 				month = i + 1;
563 				break;
564 			}
565 		}
566 		asprintfNull(&compileDate, "%04d-%02d-%02d", year, month, day);
567 	}
568 	return compileDate;
569 }
570