/** * @file llfindlocale.cpp * @brief Detect system language setting * * $LicenseInfo:firstyear=2008&license=viewergpl$ * * Copyright (c) 2008-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ // NOTE: this file used to be in llcommon/, but is only used in newview/ (i.e. // it is not used by SLPlugin or other llcommon.a consummers), and is best // placed together with the keyboard and display related sources. HB #if LL_WINDOWS # include "llwin32headers.h" #endif #include "linden_common.h" #include #include #include #include "llfindlocale.h" static void lang_country_variant_from_envstring(const char* str, char** lang, char** country, char** variant) { int end = 0; int start; // Get lang, if any start = end; while (isalnum(str[end])) { ++end; } if (start != end) { int i; int len = end - start; char* s = (char*)malloc(len + 1); for (i = 0; i < len; ++i) { s[i] = tolower(str[start + i]); } s[i] = '\0'; *lang = s; } else { *lang = NULL; } if (str[end] && str[end]!=':') // Not at end of st { ++end; } // Get country, if any start = end; while (isalnum(str[end])) { ++end; } if (start != end) { int i; int len = end - start; char* s = (char*)malloc(len + 1); for (i = 0; i < len; ++i) { s[i] = toupper(str[start + i]); } s[i] = '\0'; *country = s; } else { *country = NULL; } if (str[end] && str[end]!=':') // Not at end of str { ++end; } // Get variant, if any start = end; while (str[end] && str[end]!=':') { ++end; } if (start != end) { int i; int len = end - start; char* s = (char*)malloc(len + 1); for (i = 0; i < len; ++i) { s[i] = str[start + i]; } s[i] = '\0'; *variant = s; } else { *variant = NULL; } } static int accumulate_locstring(const char* str, FL_Locale* l) { char* lang = NULL; char* country = NULL; char* variant = NULL; if (str) { lang_country_variant_from_envstring(str, &lang, &country, &variant); if (lang) { l->lang = lang; l->country = country; l->variant = variant; return 1; } } free(lang); free(country); free(variant); return 0; } static int accumulate_env(const char* name, FL_Locale* l) { char* lang = NULL; char* country = NULL; char* variant = NULL; char* env = getenv(name); if (env) { return accumulate_locstring(env, l); } free(lang); free(country); free(variant); return 0; } // This function fixes some common locale-specifying mistakes static void canonise_fl(FL_Locale* l) { // en_UK -> en_GB if (l->lang && 0 == strcmp(l->lang, "en")) { if (l->country && 0 == strcmp(l->country, "UK")) { free((void*)l->country); l->country = strdup("GB"); } } // ja_JA -> ja_JP */ if (l->lang && 0 == strcmp(l->lang, "ja")) { if (l->country && 0 == strcmp(l->country, "JA")) { free((void*)l->country); l->country = strdup("JP"); } } } #ifdef WIN32 # include # define ML(pn,sn) MAKELANGID(LANG_##pn, SUBLANG_##pn##_##sn) # define MLN(pn) MAKELANGID(LANG_##pn, SUBLANG_DEFAULT) # define RML(pn,sn) MAKELANGID(LANG_##pn, SUBLANG_##sn) struct IDToCode { LANGID id; char* code; }; static const IDToCode both_to_code[] = { { ML(ENGLISH,US), "en_US.ISO_8859-1" }, { ML(ENGLISH,CAN), "en_CA" }, // english / canadian { ML(ENGLISH,UK), "en_GB" }, { ML(ENGLISH,EIRE), "en_IE" }, { ML(ENGLISH,AUS), "en_AU" }, { MLN(GERMAN), "de_DE" }, { MLN(SPANISH), "es_ES" }, { ML(SPANISH,MEXICAN), "es_MX" }, { MLN(FRENCH), "fr_FR" }, { ML(FRENCH,CANADIAN), "fr_CA" }, { ML(FRENCH,BELGIAN), "fr_BE" }, { ML(DUTCH,BELGIAN), "nl_BE" }, { ML(PORTUGUESE,BRAZILIAN), "pt_BR" }, { MLN(PORTUGUESE), "pt_PT" }, { MLN(SWEDISH), "sv_SE" }, { ML(CHINESE,HONGKONG), "zh_HK" }, // These are machine-generated and not yet verified { RML(AFRIKAANS,DEFAULT), "af_ZA" }, { RML(ALBANIAN,DEFAULT), "sq_AL" }, { RML(ARABIC,ARABIC_ALGERIA), "ar_DZ" }, { RML(ARABIC,ARABIC_BAHRAIN), "ar_BH" }, { RML(ARABIC,ARABIC_EGYPT), "ar_EG" }, { RML(ARABIC,ARABIC_IRAQ), "ar_IQ" }, { RML(ARABIC,ARABIC_JORDAN), "ar_JO" }, { RML(ARABIC,ARABIC_KUWAIT), "ar_KW" }, { RML(ARABIC,ARABIC_LEBANON), "ar_LB" }, { RML(ARABIC,ARABIC_LIBYA), "ar_LY" }, { RML(ARABIC,ARABIC_MOROCCO), "ar_MA" }, { RML(ARABIC,ARABIC_OMAN), "ar_OM" }, { RML(ARABIC,ARABIC_QATAR), "ar_QA" }, { RML(ARABIC,ARABIC_SAUDI_ARABIA), "ar_SA" }, { RML(ARABIC,ARABIC_SYRIA), "ar_SY" }, { RML(ARABIC,ARABIC_TUNISIA), "ar_TN" }, { RML(ARABIC,ARABIC_UAE), "ar_AE" }, { RML(ARABIC,ARABIC_YEMEN), "ar_YE" }, { RML(ARMENIAN,DEFAULT), "hy_AM" }, { RML(AZERI,AZERI_CYRILLIC), "az_AZ" }, { RML(AZERI,AZERI_LATIN), "az_AZ" }, { RML(BASQUE,DEFAULT), "eu_ES" }, { RML(BELARUSIAN,DEFAULT), "be_BY" }, //{RML(BRETON,DEFAULT), "br_FR" }, { RML(BULGARIAN,DEFAULT), "bg_BG" }, { RML(CATALAN,DEFAULT), "ca_ES" }, { RML(CHINESE,CHINESE_HONGKONG), "zh_HK" }, { RML(CHINESE,CHINESE_MACAU), "zh_MO" }, { RML(CHINESE,CHINESE_SIMPLIFIED), "zh_CN" }, { RML(CHINESE,CHINESE_SINGAPORE), "zh_SG" }, { RML(CHINESE,CHINESE_TRADITIONAL), "zh_TW" }, //{RML(CORNISH,DEFAULT), "kw_GB" }, { RML(CZECH,DEFAULT), "cs_CZ" }, { RML(DANISH,DEFAULT), "da_DK" }, { RML(DUTCH,DUTCH), "nl_NL" }, { RML(DUTCH,DUTCH_BELGIAN), "nl_BE" }, //{RML(DUTCH,DUTCH_SURINAM), "nl_SR" }, { RML(ENGLISH,ENGLISH_AUS), "en_AU" }, { RML(ENGLISH,ENGLISH_BELIZE), "en_BZ" }, { RML(ENGLISH,ENGLISH_CAN), "en_CA" }, { RML(ENGLISH,ENGLISH_CARIBBEAN), "en_CB" }, { RML(ENGLISH,ENGLISH_EIRE), "en_IE" }, { RML(ENGLISH,ENGLISH_JAMAICA), "en_JM" }, { RML(ENGLISH,ENGLISH_NZ), "en_NZ" }, { RML(ENGLISH,ENGLISH_PHILIPPINES), "en_PH" }, { RML(ENGLISH,ENGLISH_SOUTH_AFRICA), "en_ZA" }, { RML(ENGLISH,ENGLISH_TRINIDAD), "en_TT" }, { RML(ENGLISH,ENGLISH_UK), "en_GB" }, { RML(ENGLISH,ENGLISH_US), "en_US" }, { RML(ENGLISH,ENGLISH_ZIMBABWE), "en_ZW" }, //{RML(ESPERANTO,DEFAULT), "eo_" }, { RML(ESTONIAN,DEFAULT), "et_EE" }, { RML(FAEROESE,DEFAULT), "fo_FO" }, { RML(FARSI,DEFAULT), "fa_IR" }, { RML(FINNISH,DEFAULT), "fi_FI" }, { RML(FRENCH,FRENCH), "fr_FR" }, { RML(FRENCH,FRENCH_BELGIAN), "fr_BE" }, { RML(FRENCH,FRENCH_CANADIAN), "fr_CA" }, { RML(FRENCH,FRENCH_LUXEMBOURG), "fr_LU" }, { RML(FRENCH,FRENCH_MONACO), "fr_MC" }, { RML(FRENCH,FRENCH_SWISS), "fr_CH" }, //{RML(GAELIC,GAELIC), "ga_IE" }, //{RML(GAELIC,GAELIC_MANX), "gv_GB" }, //{RML(GAELIC,GAELIC_SCOTTISH), "gd_GB" }, //{RML(GALICIAN,DEFAULT), "gl_ES" }, { RML(GEORGIAN,DEFAULT), "ka_GE" }, { RML(GERMAN,GERMAN), "de_DE" }, { RML(GERMAN,GERMAN_AUSTRIAN), "de_AT" }, { RML(GERMAN,GERMAN_LIECHTENSTEIN), "de_LI" }, { RML(GERMAN,GERMAN_LUXEMBOURG), "de_LU" }, { RML(GERMAN,GERMAN_SWISS), "de_CH" }, { RML(GREEK,DEFAULT), "el_GR" }, { RML(GUJARATI,DEFAULT), "gu_IN" }, { RML(HEBREW,DEFAULT), "he_IL" }, { RML(HINDI,DEFAULT), "hi_IN" }, { RML(HUNGARIAN,DEFAULT), "hu_HU" }, { RML(ICELANDIC,DEFAULT), "is_IS" }, { RML(INDONESIAN,DEFAULT), "id_ID" }, { RML(ITALIAN,ITALIAN), "it_IT" }, { RML(ITALIAN,ITALIAN_SWISS), "it_CH" }, { RML(JAPANESE,DEFAULT), "ja_JP" }, { RML(KANNADA,DEFAULT), "kn_IN" }, { RML(KAZAK,DEFAULT), "kk_KZ" }, { RML(KONKANI,DEFAULT), "kok_IN" }, { RML(KOREAN,KOREAN), "ko_KR" }, //{RML(KYRGYZ,DEFAULT), "ky_KG" }, { RML(LATVIAN,DEFAULT), "lv_LV" }, { RML(LITHUANIAN,LITHUANIAN), "lt_LT" }, { RML(MACEDONIAN,DEFAULT), "mk_MK" }, { RML(MALAY,MALAY_BRUNEI_DARUSSALAM), "ms_BN" }, { RML(MALAY,MALAY_MALAYSIA), "ms_MY" }, { RML(MARATHI,DEFAULT), "mr_IN" }, //{RML(MONGOLIAN,DEFAULT), "mn_MN" }, { RML(NORWEGIAN,NORWEGIAN_BOKMAL), "nb_NO" }, { RML(NORWEGIAN,NORWEGIAN_NYNORSK), "nn_NO" }, { RML(POLISH,DEFAULT), "pl_PL" }, { RML(PORTUGUESE,PORTUGUESE), "pt_PT" }, { RML(PORTUGUESE,PORTUGUESE_BRAZILIAN), "pt_BR" }, { RML(PUNJABI,DEFAULT), "pa_IN" }, { RML(ROMANIAN,DEFAULT), "ro_RO" }, { RML(RUSSIAN,DEFAULT), "ru_RU" }, { RML(SANSKRIT,DEFAULT), "sa_IN" }, { RML(SERBIAN,DEFAULT), "hr_HR" }, { RML(SERBIAN,SERBIAN_CYRILLIC), "sr_SP" }, { RML(SERBIAN,SERBIAN_LATIN), "sr_SP" }, { RML(SLOVAK,DEFAULT), "sk_SK" }, { RML(SLOVENIAN,DEFAULT), "sl_SI" }, { RML(SPANISH,SPANISH), "es_ES" }, { RML(SPANISH,SPANISH_ARGENTINA), "es_AR" }, { RML(SPANISH,SPANISH_BOLIVIA), "es_BO" }, { RML(SPANISH,SPANISH_CHILE), "es_CL" }, { RML(SPANISH,SPANISH_COLOMBIA), "es_CO" }, { RML(SPANISH,SPANISH_COSTA_RICA), "es_CR" }, { RML(SPANISH,SPANISH_DOMINICAN_REPUBLIC), "es_DO" }, { RML(SPANISH,SPANISH_ECUADOR), "es_EC" }, { RML(SPANISH,SPANISH_EL_SALVADOR), "es_SV" }, { RML(SPANISH,SPANISH_GUATEMALA), "es_GT" }, { RML(SPANISH,SPANISH_HONDURAS), "es_HN" }, { RML(SPANISH,SPANISH_MEXICAN), "es_MX" }, { RML(SPANISH,SPANISH_MODERN), "es_ES" }, { RML(SPANISH,SPANISH_NICARAGUA), "es_NI" }, { RML(SPANISH,SPANISH_PANAMA), "es_PA" }, { RML(SPANISH,SPANISH_PARAGUAY), "es_PY" }, { RML(SPANISH,SPANISH_PERU), "es_PE" }, { RML(SPANISH,SPANISH_PUERTO_RICO), "es_PR" }, { RML(SPANISH,SPANISH_URUGUAY), "es_UY" }, { RML(SPANISH,SPANISH_VENEZUELA), "es_VE" }, { RML(SWAHILI,DEFAULT), "sw_KE" }, { RML(SWEDISH,SWEDISH), "sv_SE" }, { RML(SWEDISH,SWEDISH_FINLAND), "sv_FI" }, //{ RML(SYRIAC,DEFAULT), "syr_SY" }, { RML(TAMIL,DEFAULT), "ta_IN" }, { RML(TATAR,DEFAULT), "tt_TA" }, { RML(TELUGU,DEFAULT), "te_IN" }, { RML(THAI,DEFAULT), "th_TH" }, { RML(TURKISH,DEFAULT), "tr_TR" }, { RML(UKRAINIAN,DEFAULT), "uk_UA" }, { RML(URDU,URDU_PAKISTAN), "ur_PK" }, { RML(UZBEK,UZBEK_CYRILLIC), "uz_UZ" }, { RML(UZBEK,UZBEK_LATIN), "uz_UZ" }, { RML(VIETNAMESE,DEFAULT), "vi_VN" }, //{RML(WALON,DEFAULT), "wa_BE" }, //{RML(WELSH,DEFAULT), "cy_GB" }, }; static const IDToCode primary_to_code[] = { { LANG_AFRIKAANS, "af" }, { LANG_ARABIC, "ar" }, { LANG_AZERI, "az" }, { LANG_BULGARIAN, "bg" }, //{ LANG_BRETON, "br" }, { LANG_BELARUSIAN, "by" }, { LANG_CATALAN, "ca" }, { LANG_CZECH, "cs" }, //{ LANG_WELSH, "cy" }, { LANG_DANISH, "da" }, { LANG_GERMAN, "de" }, { LANG_GREEK, "el" }, { LANG_ENGLISH, "en" }, //{ LANG_ESPERANTO, "eo" }, { LANG_SPANISH, "es" }, { LANG_ESTONIAN, "et" }, { LANG_BASQUE, "eu" }, { LANG_FARSI, "fa" }, { LANG_FINNISH, "fi" }, { LANG_FAEROESE, "fo" }, { LANG_FRENCH, "fr" }, //{ LANG_GAELIC, "ga" }, //{ LANG_GALICIAN, "gl" }, { LANG_GUJARATI, "gu" }, { LANG_HEBREW, "he" }, { LANG_HINDI, "hi" }, { LANG_SERBIAN, "hr" }, { LANG_HUNGARIAN, "hu" }, { LANG_ARMENIAN, "hy" }, { LANG_INDONESIAN, "id" }, { LANG_ITALIAN, "it" }, { LANG_JAPANESE, "ja" }, { LANG_GEORGIAN, "ka" }, { LANG_KAZAK, "kk" }, { LANG_KANNADA, "kn" }, { LANG_KOREAN, "ko" }, //{ LANG_KYRGYZ, "ky" }, { LANG_LITHUANIAN, "lt" }, { LANG_LATVIAN, "lv" }, { LANG_MACEDONIAN, "mk" }, //{ LANG_MONGOLIAN, "mn" }, { LANG_MARATHI, "mr" }, { LANG_MALAY, "ms" }, { LANG_NORWEGIAN, "nb" }, { LANG_DUTCH, "nl" }, { LANG_NORWEGIAN, "nn" }, { LANG_NORWEGIAN, "no" }, // Unofficial ? { LANG_PUNJABI, "pa" }, { LANG_POLISH, "pl" }, { LANG_PORTUGUESE, "pt" }, { LANG_ROMANIAN, "ro" }, { LANG_RUSSIAN, "ru" }, { LANG_SLOVAK, "sk" }, { LANG_SLOVENIAN, "sl" }, { LANG_ALBANIAN, "sq" }, { LANG_SERBIAN, "sr" }, { LANG_SWEDISH, "sv" }, { LANG_SWAHILI, "sw" }, { LANG_TAMIL, "ta" }, { LANG_THAI, "th" }, { LANG_TURKISH, "tr" }, { LANG_TATAR, "tt" }, { LANG_UKRAINIAN, "uk" }, { LANG_URDU, "ur" }, { LANG_UZBEK, "uz" }, { LANG_VIETNAMESE, "vi" }, //{ LANG_WALON, "wa" }, { LANG_CHINESE, "zh" }, }; constexpr S32 num_primary_to_code = LL_ARRAY_SIZE(primary_to_code); constexpr S32 num_both_to_code = LL_ARRAY_SIZE(both_to_code); static S32 lcid_to_fl(LCID lcid, FL_Locale* rtn) { LANGID langid = LANGIDFROMLCID(lcid); LANGID primary_lang = PRIMARYLANGID(langid); // Try to find an exact primary/sublanguage combo that we know about for (S32 i = 0; i < num_both_to_code; ++i) { if (both_to_code[i].id == langid) { accumulate_locstring(both_to_code[i].code, rtn); return 1; } } // Fallback to just checking the primary language id for (S32 i = 0; i < num_primary_to_code; ++i) { if (primary_to_code[i].id == primary_lang) { accumulate_locstring(primary_to_code[i].code, rtn); return 1; } } return 0; } #endif FL_Success FL_FindLocale(FL_Locale** locale, FL_Domain domain) { FL_Success success = FL_FAILED; FL_Locale *rtn = (FL_Locale*)malloc(sizeof(FL_Locale)); rtn->lang = NULL; rtn->country = NULL; rtn->variant = NULL; #ifdef WIN32 // win32 >= mswindows95 LCID lcid = GetThreadLocale(); if (lcid_to_fl(lcid, rtn)) { success = FL_CONFIDENT; } else if (success == FL_FAILED) { // Assume US English on mswindows systems unless we know otherwise if (accumulate_locstring("en_US.ISO_8859-1", rtn)) { success = FL_DEFAULT_GUESS; } } #else // POSIX // Examples: sv_SE.ISO_8859-1, fr_FR.ISO8859-1, no_NO_NB, de_DE // Try the various vars in decreasing order of authority if (accumulate_env("LC_ALL", rtn) || accumulate_env("LC_MESSAGES", rtn) || accumulate_env("LANG", rtn) || accumulate_env("LANGUAGE", rtn)) { success = FL_CONFIDENT; } else if (success == FL_FAILED) { // Assume US English on unixoid systems unless we know otherwise if (accumulate_locstring("en_US.ISO_8859-1", rtn)) { success = FL_DEFAULT_GUESS; } } #endif if (success != FL_FAILED) { canonise_fl(rtn); } *locale = rtn; return success; } void FL_FreeLocale(FL_Locale** locale) { if (locale) { FL_Locale *l = *locale; if (l) { if (l->lang) { free((void*)l->lang); } if (l->country) { free((void*)l->country); } if (l->variant) { free((void*)l->variant); } free(l); *locale = NULL; } } }