428 lines
13 KiB
C
428 lines
13 KiB
C
/*
|
|
This file is part of Warzone 2100.
|
|
Copyright (C) 2005-2010 Warzone 2100 Project
|
|
|
|
Warzone 2100 is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Warzone 2100 is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Warzone 2100; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
#include "frame.h"
|
|
|
|
#include <locale.h>
|
|
#include <physfs.h>
|
|
|
|
#include "string_ext.h"
|
|
|
|
#ifdef WZ_OS_MAC
|
|
# include <CoreFoundation/CoreFoundation.h>
|
|
# include <CoreFoundation/CFURL.h>
|
|
#endif
|
|
|
|
/* Always use fallbacks on Windows */
|
|
#if defined(WZ_OS_WIN)
|
|
# undef LOCALEDIR
|
|
#endif
|
|
|
|
#if !defined(LOCALEDIR)
|
|
# define LOCALEDIR "locale"
|
|
#endif
|
|
|
|
// Language names (http://en.wikipedia.org/wiki/List_of_ISO_639-2_codes)
|
|
#define LANG_NAME_CZECH "česky"
|
|
#define LANG_NAME_DANISH "Dansk"
|
|
#define LANG_NAME_GERMAN "Deutsch"
|
|
#define LANG_NAME_ENGLISH "English"
|
|
#define LANG_NAME_ENGLISH_UK "English (United Kingdom)"
|
|
#define LANG_NAME_SPANISH "Español"
|
|
#define LANG_NAME_ESTONIAN "Eesti Keel"
|
|
#define LANG_NAME_BASQUE "euskara"
|
|
#define LANG_NAME_FINNISH "tanska"
|
|
#define LANG_NAME_FRENCH "Français"
|
|
#define LANG_NAME_FRISIAN_NETHERLANDS "frysk"
|
|
#define LANG_NAME_IRISH "Imruagadh"
|
|
#define LANG_NAME_CROATIAN "Hrvatski"
|
|
#define LANG_NAME_ITALIAN "Italiano"
|
|
#define LANG_NAME_LITHUANIAN "lietuvių kalba"
|
|
#define LANG_NAME_LATIN "latine"
|
|
#define LANG_NAME_LATVIAN "latviešu valoda"
|
|
#define LANG_NAME_KOREAN "한국어"
|
|
#define LANG_NAME_NORWEGIAN "Norsk"
|
|
#define LANG_NAME_NORWEGIAN_NYNORSK "nynorsk"
|
|
#define LANG_NAME_DUTCH "Nederlands"
|
|
#define LANG_NAME_POLISH "Polski"
|
|
#define LANG_NAME_PORTUGUESE_BRAZILIAN "Português Brasileiro"
|
|
#define LANG_NAME_PORTUGUESE "Português"
|
|
#define LANG_NAME_ROMANIAN "română"
|
|
#define LANG_NAME_RUSSIAN "Русский"
|
|
#define LANG_NAME_SLOVAK "Slovensky"
|
|
#define LANG_NAME_SLOVENIAN "Slovenski"
|
|
#define LANG_NAME_SWEDISH_SWEDEN "svenska (Sverige)"
|
|
#define LANG_NAME_SWEDISH "svenska"
|
|
#define LANG_NAME_TURKISH "Türkçe"
|
|
#define LANG_NAME_UZBEK_CYRILLIC "Ўзбек"
|
|
#define LANG_NAME_UKRAINIAN "Українська"
|
|
#define LANG_NAME_CHINESE_SIMPLIFIED "汉语"
|
|
#define LANG_NAME_CHINESE_TRADITIONAL "漢語"
|
|
|
|
#if defined(WZ_OS_WIN)
|
|
/*
|
|
* See msdn.microsoft.com for this stuff, esp.
|
|
* http://msdn.microsoft.com/en-us/library/ms693062%28VS.85,printer%29.aspx
|
|
* http://msdn.microsoft.com/en-us/library/dd318693%28VS.85,printer%29.aspx
|
|
*/
|
|
static const struct
|
|
{
|
|
const char * language;
|
|
const char * name;
|
|
USHORT usPrimaryLanguage;
|
|
USHORT usSubLanguage;
|
|
} map[] = {
|
|
{ "", N_("System locale"), LANG_NEUTRAL, SUBLANG_DEFAULT },
|
|
# if defined(ENABLE_NLS)
|
|
{ "cs", LANG_NAME_CZECH, LANG_CZECH, SUBLANG_DEFAULT },
|
|
{ "da", LANG_NAME_DANISH, LANG_DANISH, SUBLANG_DEFAULT },
|
|
{ "de", LANG_NAME_GERMAN, LANG_GERMAN, SUBLANG_GERMAN },
|
|
// { "en", LANG_NAME_ENGLISH, LANG_ENGLISH, SUBLANG_DEFAULT },
|
|
{ "en_GB", LANG_NAME_ENGLISH_UK, LANG_ENGLISH, SUBLANG_ENGLISH_UK },
|
|
{ "es", LANG_NAME_SPANISH, LANG_SPANISH, SUBLANG_SPANISH },
|
|
{ "et_EE", LANG_NAME_ESTONIAN, LANG_ESTONIAN, SUBLANG_DEFAULT },
|
|
// { "eu", LANG_NAME_BASQUE, LANG_BASQUE, SUBLANG_DEFAULT },
|
|
{ "fi", LANG_NAME_FINNISH, LANG_FINNISH, SUBLANG_DEFAULT },
|
|
{ "fr", LANG_NAME_FRENCH, LANG_FRENCH, SUBLANG_FRENCH },
|
|
/* Our Frisian translation is the "West Frisian" variation of it. This
|
|
* variation is mostly spoken in the Dutch province Friesland (Fryslân
|
|
* in Frisian) and has ISO 639-3 code "fry".
|
|
*
|
|
* FIXME: We should really use a sub-language code for this. E.g.
|
|
* fy_XX.
|
|
*/
|
|
{ "fy", LANG_NAME_FRISIAN_NETHERLANDS, LANG_FRISIAN, SUBLANG_FRISIAN_NETHERLANDS },
|
|
{ "ga", LANG_NAME_IRISH, LANG_IRISH, SUBLANG_IRISH_IRELAND },
|
|
{ "hr", LANG_NAME_CROATIAN, LANG_CROATIAN, SUBLANG_DEFAULT },
|
|
{ "it", LANG_NAME_ITALIAN, LANG_ITALIAN, SUBLANG_ITALIAN },
|
|
{ "ko_KR", LANG_NAME_KOREAN, LANG_KOREAN, SUBLANG_DEFAULT },
|
|
// { "la", LANG_NAME_LATIN, LANG_LATIN, SUBLANG_DEFAULT },
|
|
{ "lt", LANG_NAME_LITHUANIAN, LANG_LITHUANIAN, SUBLANG_DEFAULT },
|
|
// { "lv", LANG_NAME_LATVIAN, LANG_LATVIAN, SUBLANG_DEFAULT },
|
|
// MSDN uses "no"...
|
|
{ "nb", LANG_NAME_NORWEGIAN, LANG_NORWEGIAN, SUBLANG_DEFAULT },
|
|
// { "nn", LANG_NAME_NORWEGIAN_NYNORSK, LANG_NORWEGIAN, SUBLANG_NORWEGIAN_NYNORSK },
|
|
{ "nl", LANG_NAME_DUTCH, LANG_DUTCH, SUBLANG_DUTCH },
|
|
{ "pl", LANG_NAME_POLISH, LANG_POLISH, SUBLANG_DEFAULT },
|
|
{ "pt_BR", LANG_NAME_PORTUGUESE_BRAZILIAN, LANG_PORTUGUESE, SUBLANG_PORTUGUESE_BRAZILIAN },
|
|
{ "pt", LANG_NAME_PORTUGUESE, LANG_PORTUGUESE, SUBLANG_DEFAULT },
|
|
{ "ro", LANG_NAME_ROMANIAN, LANG_ROMANIAN, SUBLANG_DEFAULT },
|
|
{ "ru", LANG_NAME_RUSSIAN, LANG_RUSSIAN, SUBLANG_DEFAULT },
|
|
{ "sk", LANG_NAME_SLOVAK, LANG_SLOVAK, SUBLANG_DEFAULT },
|
|
{ "sl", LANG_NAME_SLOVENIAN, LANG_SLOVENIAN, SUBLANG_DEFAULT },
|
|
#if (WINVER >= 0x0600)
|
|
// { "sv_SE", LANG_NAME_SWEDISH_SWEDEN, LANG_SWEDISH, SUBLANG_SWEDISH_SWEDEN },
|
|
#else
|
|
// { "sv_SE", LANG_NAME_SWEDISH_SWEDEN, LANG_SWEDISH, SUBLANG_SWEDISH },
|
|
#endif
|
|
// { "sv", LANG_NAME_SWEDISH, LANG_SWEDISH, SUBLANG_DEFAULT },
|
|
{ "tr", LANG_NAME_TURKISH, LANG_TURKISH, SUBLANG_DEFAULT },
|
|
// { "uz", LANG_NAME_UZBEK_CYRILLIC, LANG_UZBEK, SUBLANG_UZBEK_CYRILLIC },
|
|
{ "uk_UA", LANG_NAME_UKRAINIAN, LANG_UKRAINIAN, SUBLANG_DEFAULT },
|
|
{ "zh_CN", LANG_NAME_CHINESE_SIMPLIFIED, LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED },
|
|
{ "zh_TW", LANG_NAME_CHINESE_TRADITIONAL, LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL },
|
|
# endif
|
|
};
|
|
#else
|
|
static const struct
|
|
{
|
|
const char *language;
|
|
const char *name;
|
|
const char *locale;
|
|
const char *localeFallback;
|
|
} map[] = {
|
|
{ "", N_("System locale"), "", "" },
|
|
# if defined(ENABLE_NLS)
|
|
{ "cs", LANG_NAME_CZECH, "cs.UTF-8", "cs" },
|
|
{ "da", LANG_NAME_DANISH, "da_DK.UTF-8", "da_DK" },
|
|
{ "de", LANG_NAME_GERMAN, "de_DE.UTF-8", "de_DE" },
|
|
// { "en", LANG_NAME_ENGLISH, "en_US.UTF-8", "en_US" },
|
|
{ "en_GB", LANG_NAME_ENGLISH_UK, "en_GB.UTF-8", "en_GB" },
|
|
{ "es", LANG_NAME_SPANISH, "es_ES.UTF-8", "es_ES" },
|
|
{ "et_EE", LANG_NAME_ESTONIAN, "et_EE.UTF-8", "et_EE" },
|
|
// { "eu", LANG_NAME_BASQUE, "eu.UTF-8", "eu" },
|
|
{ "fi", LANG_NAME_FINNISH, "fi.UTF-8", "fi" },
|
|
{ "fr", LANG_NAME_FRENCH, "fr_FR.UTF-8", "fr_FR" },
|
|
/* Our Frisian translation is the "West Frisian" variation of it. This
|
|
* variation is mostly spoken in the Dutch province Friesland (Fryslân
|
|
* in Frisian) and has ISO 639-3 code "fry".
|
|
*
|
|
* FIXME: We should really use a sub-language code for this. E.g.
|
|
* fy_XX.
|
|
*/
|
|
{ "fy", LANG_NAME_FRISIAN_NETHERLANDS, "fy.UTF-8", "fy" },
|
|
{ "ga", LANG_NAME_IRISH, "ga.UTF-8", "ga" },
|
|
{ "hr", LANG_NAME_CROATIAN, "hr_HR.UTF-8", "hr_HR" },
|
|
{ "it", LANG_NAME_ITALIAN, "it_IT.UTF-8", "it_IT" },
|
|
{ "ko_KR", LANG_NAME_KOREAN, "ko_KR.UTF-8", "ko_KR" },
|
|
{ "la", LANG_NAME_LATIN, "la.UTF-8", "la" },
|
|
{ "lt", LANG_NAME_LITHUANIAN, "lt.UTF-8", "lt" },
|
|
// { "lv", LANG_NAME_LATVIAN, "lv.UTF-8", "lv" },
|
|
{ "nb", LANG_NAME_NORWEGIAN, "nb_NO.UTF-8", "nb_NO" },
|
|
// { "nn", LANG_NAME_NORWEGIAN_NYNORSK, "nn.UTF-8", "nn" },
|
|
{ "nl", LANG_NAME_DUTCH, "nl_NL.UTF-8", "nl_NL" },
|
|
{ "pl", LANG_NAME_POLISH, "pl.UTF-8", "pl" },
|
|
{ "pt_BR", LANG_NAME_PORTUGUESE_BRAZILIAN, "pt_BR.UTF-8", "pt_BR" },
|
|
{ "pt", LANG_NAME_PORTUGUESE, "pt_PT.UTF-8", "pt_PT" },
|
|
{ "ro", LANG_NAME_ROMANIAN, "ro.UTF-8", "ro" },
|
|
{ "ru", LANG_NAME_RUSSIAN, "ru_RU.UTF-8", "ru_RU" },
|
|
{ "sk", LANG_NAME_SLOVAK, "sk_SK.UTF-8", "sk_SK" },
|
|
{ "sl", LANG_NAME_SLOVENIAN, "sl.UTF-8", "sl" },
|
|
// { "sv_SE", LANG_NAME_SWEDISH_SWEDEN, "sv_SE.UTF-8", "sv_SE" },
|
|
// { "sv", LANG_NAME_SWEDISH, "sv.UTF-8", "sv" },
|
|
{ "tr", LANG_NAME_TURKISH, "tr_TR.UTF-8", "tr_TR" },
|
|
// { "uz", LANG_NAME_UZBEK_CYRILLIC, "uz.UTF-8", "uz" },
|
|
{ "uk_UA", LANG_NAME_UKRAINIAN, "uk_UA.UTF-8", "uk_UA" },
|
|
{ "zh_CN", LANG_NAME_CHINESE_SIMPLIFIED, "zh_CN.UTF-8", "zh_CN" },
|
|
{ "zh_TW", LANG_NAME_CHINESE_TRADITIONAL, "zh_TW.UTF-8", "zh_TW" },
|
|
# endif
|
|
};
|
|
#endif
|
|
|
|
static unsigned int selectedLanguage = 0;
|
|
|
|
|
|
/*!
|
|
* Return the language part of the selected locale
|
|
*/
|
|
#if !defined(ENABLE_NLS)
|
|
const char* getLanguage(void)
|
|
{
|
|
return "";
|
|
}
|
|
#elif defined(WZ_OS_WIN)
|
|
const char *getLanguage(void)
|
|
{
|
|
USHORT usPrimaryLanguage = PRIMARYLANGID(LANGIDFROMLCID(GetThreadLocale()));
|
|
unsigned int i;
|
|
|
|
if (selectedLanguage == 0)
|
|
{
|
|
return ""; // Return empty string for system default
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(map); i++)
|
|
{
|
|
if (usPrimaryLanguage == map[i].usPrimaryLanguage)
|
|
{
|
|
return map[i].language;
|
|
}
|
|
}
|
|
|
|
return "";
|
|
}
|
|
#else
|
|
const char *getLanguage(void)
|
|
{
|
|
static char language[6] = { '\0' }; // large enough for xx_YY
|
|
const char *localeName = setlocale(LC_MESSAGES, NULL);
|
|
char *delim = NULL;
|
|
|
|
if (selectedLanguage == 0 || localeName == NULL)
|
|
{
|
|
return ""; // Return empty string for system default and errors
|
|
}
|
|
|
|
sstrcpy(language, localeName);
|
|
|
|
// cut anything after a '.' to get rid of the encoding part
|
|
delim = strchr(language, '.');
|
|
if (delim)
|
|
{
|
|
*delim = '\0';
|
|
}
|
|
|
|
// if language is xx_XX, cut the _XX part
|
|
delim = strchr(language, '_');
|
|
if (delim)
|
|
{
|
|
if (!strncasecmp(language, delim + 1, 2))
|
|
{
|
|
*delim = '\0';
|
|
}
|
|
}
|
|
|
|
return language;
|
|
}
|
|
#endif
|
|
|
|
|
|
const char* getLanguageName(void)
|
|
{
|
|
const char *language = getLanguage();
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(map); i++)
|
|
{
|
|
if (strcmp(language, map[i].language) == 0)
|
|
{
|
|
return gettext(map[i].name);
|
|
}
|
|
}
|
|
|
|
return language;
|
|
}
|
|
|
|
|
|
#if defined(ENABLE_NLS)
|
|
# if defined(WZ_OS_WIN)
|
|
static bool setLocaleWindows(USHORT usPrimaryLanguage, USHORT usSubLanguage)
|
|
{
|
|
bool success = SUCCEEDED( SetThreadLocale( MAKELCID( MAKELANGID(usPrimaryLanguage, usSubLanguage), SORT_DEFAULT ) ) );
|
|
|
|
if (!success)
|
|
{
|
|
info("Failed to set locale to \"%d\"", usPrimaryLanguage);
|
|
}
|
|
else
|
|
{
|
|
debug(LOG_WZ, "Requested locale \"%d\"", usPrimaryLanguage);
|
|
}
|
|
|
|
setlocale(LC_NUMERIC, "C"); // set radix character to the period (".")
|
|
|
|
return success;
|
|
}
|
|
# else
|
|
/*!
|
|
* Set the prefered locale
|
|
* \param locale The locale, NOT just the language part
|
|
* \note Use this instead of setlocale(), because we need the default radix character
|
|
*/
|
|
static bool setLocaleUnix(const char* locale)
|
|
{
|
|
const char *actualLocale = setlocale(LC_ALL, locale);
|
|
|
|
if (actualLocale == NULL)
|
|
{
|
|
info("Failed to set locale to \"%s\"", locale);
|
|
}
|
|
else
|
|
{
|
|
if (strcmp(locale, actualLocale))
|
|
{
|
|
debug(LOG_WZ, "Requested locale \"%s\", got \"%s\" instead", locale, actualLocale);
|
|
}
|
|
}
|
|
|
|
setlocale(LC_NUMERIC, "C"); // set radix character to the period (".")
|
|
|
|
return (actualLocale != NULL);
|
|
}
|
|
# endif
|
|
#endif
|
|
|
|
|
|
bool setLanguage(const char *language)
|
|
{
|
|
#if !defined(ENABLE_NLS)
|
|
return true;
|
|
#else
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(map); i++)
|
|
{
|
|
if (strcmp(language, map[i].language) == 0)
|
|
{
|
|
selectedLanguage = i;
|
|
debug(LOG_WZ, "Setting language to \"%s\" (%s)", map[i].name, map[i].language);
|
|
|
|
# if defined(WZ_OS_WIN)
|
|
return setLocaleWindows(map[i].usPrimaryLanguage, map[i].usSubLanguage);
|
|
# else
|
|
return setLocaleUnix(map[i].locale) || setLocaleUnix(map[i].localeFallback);
|
|
# endif
|
|
}
|
|
}
|
|
|
|
debug(LOG_ERROR, "Requested language \"%s\" not supported.", language);
|
|
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
|
|
void setNextLanguage(void)
|
|
{
|
|
selectedLanguage++;
|
|
if (selectedLanguage > ARRAY_SIZE(map) - 1)
|
|
{
|
|
selectedLanguage = 0;
|
|
}
|
|
|
|
if (!setLanguage(map[selectedLanguage].language) && selectedLanguage != 0)
|
|
{
|
|
setNextLanguage(); // try next
|
|
}
|
|
}
|
|
|
|
|
|
void initI18n(void)
|
|
{
|
|
const char *textdomainDirectory = NULL;
|
|
|
|
if (!setLanguage("")) // set to system default
|
|
{
|
|
// no system default?
|
|
debug(LOG_ERROR, "initI18n: No system language found");
|
|
}
|
|
#if defined(WZ_OS_WIN)
|
|
{
|
|
// Retrieve an absolute path to the locale directory
|
|
char localeDir[PATH_MAX];
|
|
sstrcpy(localeDir, PHYSFS_getBaseDir());
|
|
sstrcat(localeDir, "\\" LOCALEDIR);
|
|
|
|
// Set locale directory and translation domain name
|
|
textdomainDirectory = bindtextdomain(PACKAGE, localeDir);
|
|
}
|
|
#else
|
|
#ifdef WZ_OS_MAC
|
|
{
|
|
char resourcePath[PATH_MAX];
|
|
CFURLRef resourceURL = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle());
|
|
if( CFURLGetFileSystemRepresentation( resourceURL, true, (UInt8 *) resourcePath, PATH_MAX) )
|
|
{
|
|
sstrcat(resourcePath, "/locale");
|
|
textdomainDirectory = bindtextdomain(PACKAGE, resourcePath);
|
|
}
|
|
else
|
|
{
|
|
debug( LOG_ERROR, "Could not change to resources directory." );
|
|
}
|
|
|
|
if (resourceURL != NULL)
|
|
{
|
|
CFRelease(resourceURL);
|
|
}
|
|
|
|
debug(LOG_INFO, "resourcePath is %s", resourcePath);
|
|
}
|
|
#else
|
|
textdomainDirectory = bindtextdomain(PACKAGE, LOCALEDIR);
|
|
#endif
|
|
#endif
|
|
if (!textdomainDirectory)
|
|
{
|
|
debug(LOG_ERROR, "initI18n: bindtextdomain failed!");
|
|
}
|
|
|
|
(void)bind_textdomain_codeset(PACKAGE, "UTF-8");
|
|
(void)textdomain(PACKAGE);
|
|
}
|