#include "platform/preferred_languages.hpp" #include "platform/settings.hpp" #include "coding/string_utf8_multilang.hpp" #include "base/buffer_vector.hpp" #include "base/macros.hpp" #include "base/string_utils.hpp" #include "std/target_os.hpp" #include // getenv #include // strlen #include #if defined(OMIM_OS_MAC) || defined(OMIM_OS_IPHONE) #include #include #elif defined(OMIM_OS_WINDOWS) #include "std/windows.hpp" // for XP it's not defined #define MUI_LANGUAGE_NAME 0x8 #elif defined(OMIM_OS_LINUX) #include #elif defined(OMIM_OS_ANDROID) /// Body for this function is inside android/sdk/src/main/cpp sources std::string GetAndroidSystemLanguage(); #else #error "Define language preferences for your platform" #endif #ifdef OMIM_OS_WINDOWS struct MSLocale { uint16_t m_code; char const * m_name; }; /// Used on Windows XP which lacks LCIDToLocaleName function /// Taken from MSDN: http://msdn.microsoft.com/en-us/library/cc233968(v=PROT.10).aspx static MSLocale const gLocales[] = { {0x1, "ar"}, {0x2, "bg"}, {0x3, "ca"}, {0x4, "zh-Hans"}, {0x5, "cs"}, {0x6, "da"}, {0x7, "de"}, {0x8, "el"}, {0x9, "en"}, {0xa, "es"}, {0xb, "fi"}, {0xc, "fr"}, {0xd, "he"}, {0xe, "hu"}, {0xf, "is"}, {0x10, "it"}, {0x11, "ja"}, {0x12, "ko"}, {0x13, "nl"}, {0x14, "no"}, {0x15, "pl"}, {0x16, "pt"}, {0x17, "rm"}, {0x18, "ro"}, {0x19, "ru"}, {0x1a, "hr"}, {0x1b, "sk"}, {0x1c, "sq"}, {0x1d, "sv"}, {0x1e, "th"}, {0x1f, "tr"}, {0x20, "ur"}, {0x21, "id"}, {0x22, "uk"}, {0x23, "be"}, {0x24, "sl"}, {0x25, "et"}, {0x26, "lv"}, {0x27, "lt"}, {0x28, "tg"}, {0x29, "fa"}, {0x2a, "vi"}, {0x2b, "hy"}, {0x2c, "az"}, {0x2d, "eu"}, {0x2e, "hsb"}, {0x2f, "mk"}, {0x32, "tn"}, {0x34, "xh"}, {0x35, "zu"}, {0x36, "af"}, {0x37, "ka"}, {0x38, "fo"}, {0x39, "hi"}, {0x3a, "mt"}, {0x3b, "se"}, {0x3c, "ga"}, {0x3e, "ms"}, {0x3f, "kk"}, {0x40, "ky"}, {0x41, "sw"}, {0x42, "tk"}, {0x43, "uz"}, {0x44, "tt"}, {0x45, "bn"}, {0x46, "pa"}, {0x47, "gu"}, {0x48, "or"}, {0x49, "ta"}, {0x4a, "te"}, {0x4b, "kn"}, {0x4c, "ml"}, {0x4d, "as"}, {0x4e, "mr"}, {0x4f, "sa"}, {0x50, "mn"}, {0x51, "bo"}, {0x52, "cy"}, {0x53, "km"}, {0x54, "lo"}, {0x56, "gl"}, {0x57, "kok"}, {0x5a, "syr"}, {0x5b, "si"}, {0x5d, "iu"}, {0x5e, "am"}, {0x5f, "tzm"}, {0x61, "ne"}, {0x62, "fy"}, {0x63, "ps"}, {0x64, "fil"}, {0x65, "dv"}, {0x68, "ha"}, {0x6a, "yo"}, {0x6b, "quz"}, {0x6c, "nso"}, {0x6d, "ba"}, {0x6e, "lb"}, {0x6f, "kl"}, {0x70, "ig"}, {0x78, "ii"}, {0x7a, "arn"}, {0x7c, "moh"}, {0x7e, "br"}, {0x80, "ug"}, {0x81, "mi"}, {0x82, "oc"}, {0x83, "co"}, {0x84, "gsw"}, {0x85, "sah"}, {0x86, "qut"}, {0x87, "rw"}, {0x88, "wo"}, {0x8c, "prs"}, {0x91, "gd"}, {0x401, "ar-SA"}, {0x402, "bg-BG"}, {0x403, "ca-ES"}, {0x404, "zh-TW"}, {0x405, "cs-CZ"}, {0x406, "da-DK"}, {0x407, "de-DE"}, {0x408, "el-GR"}, {0x409, "en-US"}, {0x40a, "es-ES_tradnl"}, {0x40b, "fi-FI"}, {0x40c, "fr-FR"}, {0x40d, "he-IL"}, {0x40e, "hu-HU"}, {0x40f, "is-IS"}, {0x410, "it-IT"}, {0x411, "ja-JP"}, {0x412, "ko-KR"}, {0x413, "nl-NL"}, {0x414, "nb-NO"}, {0x415, "pl-PL"}, {0x416, "pt-BR"}, {0x417, "rm-CH"}, {0x418, "ro-RO"}, {0x419, "ru-RU"}, {0x41a, "hr-HR"}, {0x41b, "sk-SK"}, {0x41c, "sq-AL"}, {0x41d, "sv-SE"}, {0x41e, "th-TH"}, {0x41f, "tr-TR"}, {0x420, "ur-PK"}, {0x421, "id-ID"}, {0x422, "uk-UA"}, {0x423, "be-BY"}, {0x424, "sl-SI"}, {0x425, "et-EE"}, {0x426, "lv-LV"}, {0x427, "lt-LT"}, {0x428, "tg-Cyrl-TJ"}, {0x429, "fa-IR"}, {0x42a, "vi-VN"}, {0x42b, "hy-AM"}, {0x42c, "az-Latn-AZ"}, {0x42d, "eu-ES"}, {0x42e, "wen-DE"}, {0x42f, "mk-MK"}, {0x430, "st-ZA"}, {0x431, "ts-ZA"}, {0x432, "tn-ZA"}, {0x433, "ven-ZA"}, {0x434, "xh-ZA"}, {0x435, "zu-ZA"}, {0x436, "af-ZA"}, {0x437, "ka-GE"}, {0x438, "fo-FO"}, {0x439, "hi-IN"}, {0x43a, "mt-MT"}, {0x43b, "se-NO"}, {0x43e, "ms-MY"}, {0x43f, "kk-KZ"}, {0x440, "ky-KG"}, {0x441, "sw-KE"}, {0x442, "tk-TM"}, {0x443, "uz-Latn-UZ"}, {0x444, "tt-RU"}, {0x445, "bn-IN"}, {0x446, "pa-IN"}, {0x447, "gu-IN"}, {0x448, "or-IN"}, {0x449, "ta-IN"}, {0x44a, "te-IN"}, {0x44b, "kn-IN"}, {0x44c, "ml-IN"}, {0x44d, "as-IN"}, {0x44e, "mr-IN"}, {0x44f, "sa-IN"}, {0x450, "mn-MN"}, {0x451, "bo-CN"}, {0x452, "cy-GB"}, {0x453, "km-KH"}, {0x454, "lo-LA"}, {0x455, "my-MM"}, {0x456, "gl-ES"}, {0x457, "kok-IN"}, {0x458, "mni"}, {0x459, "sd-IN"}, {0x45a, "syr-SY"}, {0x45b, "si-LK"}, {0x45c, "chr-US"}, {0x45d, "iu-Cans-CA"}, {0x45e, "am-ET"}, {0x45f, "tmz"}, {0x461, "ne-NP"}, {0x462, "fy-NL"}, {0x463, "ps-AF"}, {0x464, "fil-PH"}, {0x465, "dv-MV"}, {0x466, "bin-NG"}, {0x467, "fuv-NG"}, {0x468, "ha-Latn-NG"}, {0x469, "ibb-NG"}, {0x46a, "yo-NG"}, {0x46b, "quz-BO"}, {0x46c, "nso-ZA"}, {0x46d, "ba-RU"}, {0x46e, "lb-LU"}, {0x46f, "kl-GL"}, {0x470, "ig-NG"}, {0x471, "kr-NG"}, {0x472, "gaz-ET"}, {0x473, "ti-ER"}, {0x474, "gn-PY"}, {0x475, "haw-US"}, {0x477, "so-SO"}, {0x478, "ii-CN"}, {0x479, "pap-AN"}, {0x47a, "arn-CL"}, {0x47c, "moh-CA"}, {0x47e, "br-FR"}, {0x480, "ug-CN"}, {0x481, "mi-NZ"}, {0x482, "oc-FR"}, {0x483, "co-FR"}, {0x484, "gsw-FR"}, {0x485, "sah-RU"}, {0x486, "qut-GT"}, {0x487, "rw-RW"}, {0x488, "wo-SN"}, {0x48c, "prs-AF"}, {0x48d, "plt-MG"}, {0x491, "gd-GB"}, {0x801, "ar-IQ"}, {0x804, "zh-CN"}, {0x807, "de-CH"}, {0x809, "en-GB"}, {0x80a, "es-MX"}, {0x80c, "fr-BE"}, {0x810, "it-CH"}, {0x813, "nl-BE"}, {0x814, "nn-NO"}, {0x816, "pt-PT"}, {0x818, "ro-MO"}, {0x819, "ru-MO"}, {0x81a, "sr-Latn-CS"}, {0x81d, "sv-FI"}, {0x820, "ur-IN"}, {0x82c, "az-Cyrl-AZ"}, {0x82e, "dsb-DE"}, {0x83b, "se-SE"}, {0x83c, "ga-IE"}, {0x83e, "ms-BN"}, {0x843, "uz-Cyrl-UZ"}, {0x845, "bn-BD"}, {0x846, "pa-PK"}, {0x850, "mn-Mong-CN"}, {0x851, "bo-BT"}, {0x859, "sd-PK"}, {0x85d, "iu-Latn-CA"}, {0x85f, "tzm-Latn-DZ"}, {0x861, "ne-IN"}, {0x86b, "quz-EC"}, {0x873, "ti-ET"}, {0xc01, "ar-EG"}, {0xc04, "zh-HK"}, {0xc07, "de-AT"}, {0xc09, "en-AU"}, {0xc0a, "es-ES"}, {0xc0c, "fr-CA"}, {0xc1a, "sr-Cyrl-CS"}, {0xc3b, "se-FI"}, {0xc5f, "tmz-MA"}, {0xc6b, "quz-PE"}, {0x1001, "ar-LY"}, {0x1004, "zh-SG"}, {0x1007, "de-LU"}, {0x1009, "en-CA"}, {0x100a, "es-GT"}, {0x100c, "fr-CH"}, {0x101a, "hr-BA"}, {0x103b, "smj-NO"}, {0x1401, "ar-DZ"}, {0x1404, "zh-MO"}, {0x1407, "de-LI"}, {0x1409, "en-NZ"}, {0x140a, "es-CR"}, {0x140c, "fr-LU"}, {0x141a, "bs-Latn-BA"}, {0x143b, "smj-SE"}, {0x1801, "ar-MA"}, {0x1809, "en-IE"}, {0x180a, "es-PA"}, {0x180c, "fr-MC"}, {0x181a, "sr-Latn-BA"}, {0x183b, "sma-NO"}, {0x1c01, "ar-TN"}, {0x1c09, "en-ZA"}, {0x1c0a, "es-DO"}, {0x1c0c, "fr-WestIndies"}, {0x1c1a, "sr-Cyrl-BA"}, {0x1c3b, "sma-SE"}, {0x2001, "ar-OM"}, {0x2009, "en-JM"}, {0x200a, "es-VE"}, {0x200c, "fr-RE"}, {0x201a, "bs-Cyrl-BA"}, {0x203b, "sms-FI"}, {0x2401, "ar-YE"}, {0x2409, "en-CB"}, {0x240a, "es-CO"}, {0x240c, "fr-CG"}, {0x241a, "sr-Latn-RS"}, {0x243b, "smn-FI"}, {0x2801, "ar-SY"}, {0x2809, "en-BZ"}, {0x280a, "es-PE"}, {0x280c, "fr-SN"}, {0x281a, "sr-Cyrl-RS"}, {0x2c01, "ar-JO"}, {0x2c09, "en-TT"}, {0x2c0a, "es-AR"}, {0x2c0c, "fr-CM"}, {0x2c1a, "sr-Latn-ME"}, {0x3001, "ar-LB"}, {0x3009, "en-ZW"}, {0x300a, "es-EC"}, {0x300c, "fr-CI"}, {0x301a, "sr-Cyrl-ME"}, {0x3401, "ar-KW"}, {0x3409, "en-PH"}, {0x340a, "es-CL"}, {0x340c, "fr-ML"}, {0x3801, "ar-AE"}, {0x3809, "en-ID"}, {0x380a, "es-UY"}, {0x380c, "fr-MA"}, {0x3c01, "ar-BH"}, {0x3c09, "en-HK"}, {0x3c0a, "es-PY"}, {0x3c0c, "fr-HT"}, {0x4001, "ar-QA"}, {0x4009, "en-IN"}, {0x400a, "es-BO"}, {0x4409, "en-MY"}, {0x440a, "es-SV"}, {0x4809, "en-SG"}, {0x480a, "es-HN"}, {0x4c0a, "es-NI"}, {0x500a, "es-PR"}, {0x540a, "es-US"}, {0x641a, "bs-Cyrl"}, {0x681a, "bs-Latn"}, {0x6c1a, "sr-Cyrl"}, {0x701a, "sr-Latn"}, {0x703b, "smn"}, {0x742c, "az-Cyrl"}, {0x743b, "sms"}, {0x7804, "zh"}, {0x7814, "nn"}, {0x781a, "bs"}, {0x782c, "az-Latn"}, {0x783b, "sma"}, {0x7843, "uz-Cyrl"}, {0x7850, "mn-Cyrl"}, {0x785d, "iu-Cans"}, {0x7c04, "zh-Hant"}, {0x7c14, "nb"}, {0x7c1a, "sr"}, {0x7c28, "tg-Cyrl"}, {0x7c2e, "dsb"}, {0x7c3b, "smj"}, {0x7c43, "uz-Latn"}, {0x7c50, "mn-Mong"}, {0x7c5d, "iu-Latn"}, {0x7c5f, "tzm-Latn"}, {0x7c68, "ha-Latn"}, }; #endif namespace languages { struct SystemLanguages { buffer_vector m_langs; SystemLanguages() { /// @DebugNote // Hardcode draw text language. // m_langs.push_back("hi"); // return; #if defined(OMIM_OS_MAC) || defined(OMIM_OS_IPHONE) || defined(OMIM_OS_LINUX) // check environment variables char const * p = std::getenv("LANGUAGE"); if (p && strlen(p)) // LANGUAGE can contain several values divided by ':' strings::Tokenize(p, ":", [this](std::string_view s) { m_langs.push_back(std::string(s)); }); else if ((p = getenv("LC_ALL"))) m_langs.push_back(p); else if ((p = getenv("LC_MESSAGES"))) m_langs.push_back(p); else if ((p = getenv("LANG"))) m_langs.push_back(p); #if defined(OMIM_OS_MAC) || defined(OMIM_OS_IPHONE) else { // Mac and iOS implementation CFArrayRef langs = CFLocaleCopyPreferredLanguages(); char buf[30]; for (CFIndex i = 0; i < CFArrayGetCount(langs); ++i) { CFStringRef strRef = (CFStringRef)CFArrayGetValueAtIndex(langs, i); CFStringGetCString(strRef, buf, 30, kCFStringEncodingUTF8); m_langs.push_back(buf); } CFRelease(langs); } #endif #elif defined(OMIM_OS_WINDOWS) // if we're on Vista or above, take list of preferred languages typedef BOOL(WINAPI * PGETUSERPREFERREDUILANGUAGES)(DWORD, PULONG, PWCHAR, PULONG); PGETUSERPREFERREDUILANGUAGES p = reinterpret_cast( GetProcAddress(GetModuleHandleA("Kernel32.dll"), "GetUserPreferredUILanguages")); if (p) { // Vista or above, get buffer size first ULONG numLangs; WCHAR * buf = NULL; ULONG bufSize = 0; CHECK_EQUAL(TRUE, p(MUI_LANGUAGE_NAME, &numLangs, buf, &bufSize), ()); CHECK_GREATER(bufSize, 0U, ("GetUserPreferredUILanguages failed")); buf = new WCHAR[++bufSize]; p(MUI_LANGUAGE_NAME, &numLangs, buf, &bufSize); size_t len; WCHAR * pCurr = buf; while ((len = wcslen(pCurr))) { char * utf8Buf = new char[len * 2]; CHECK_NOT_EQUAL(WideCharToMultiByte(CP_UTF8, 0, pCurr, -1, utf8Buf, len * 2, NULL, NULL), 0, ()); m_langs.push_back(utf8Buf); delete[] utf8Buf; pCurr += len + 1; } delete[] buf; } if (m_langs.empty()) { // used mostly on WinXP LANGID langId = GetUserDefaultLangID(); for (size_t i = 0; i < ARRAY_SIZE(gLocales); ++i) if (gLocales[i].m_code == langId) { m_langs.push_back(gLocales[i].m_name); break; } } #elif defined(OMIM_OS_ANDROID) m_langs.push_back(GetAndroidSystemLanguage()); #else #error "Define language preferences for your platform" #endif } }; buffer_vector const & GetSystemPreferred() { static SystemLanguages const langs; return langs.m_langs; } std::string GetPreferred() { // generate output string std::string result; for (auto const & e : GetSystemPreferred()) { result.append(e); result.push_back('|'); } if (result.empty()) result = "default"; else result.pop_back(); return result; } std::string GetCurrentOrig() { auto const & arr = GetSystemPreferred(); if (arr.empty()) return "en"; else return arr[0]; } std::string Normalize(std::string_view lang) { return std::string{lang.substr(0, lang.find_first_of("-_ "))}; } std::string GetCurrentNorm() { return Normalize(GetCurrentOrig()); } std::string GetCurrentMapLanguage() { std::string languageCode; if (!settings::Get(settings::kMapLanguageCode, languageCode) || languageCode.empty()) { for (auto const & systemLanguage : GetSystemPreferred()) { auto normalizedLang = Normalize(systemLanguage); if (StringUtf8Multilang::GetLangIndex(normalizedLang) != StringUtf8Multilang::kUnsupportedLanguageCode) return normalizedLang; } return std::string(StringUtf8Multilang::GetLangByCode(StringUtf8Multilang::kDefaultCode)); } return languageCode; } std::vector GetPreferredLangIndexes() { std::vector langs = {}; auto const mapLang = StringUtf8Multilang::GetLangIndex(languages::GetCurrentMapLanguage()); if (mapLang != StringUtf8Multilang::kUnsupportedLanguageCode) langs.push_back(mapLang); for (auto const & systemLanguage : GetSystemPreferred()) { auto normalizedLang = Normalize(systemLanguage); int8_t lang = StringUtf8Multilang::GetLangIndex(normalizedLang); if (lang != StringUtf8Multilang::kUnsupportedLanguageCode && find(langs.begin(), langs.end(), lang) == langs.end()) langs.push_back(lang); } return langs; } std::string GetTwine(std::string const & lang) { // Special cases for different Chinese variations. if (lang.find("zh") == 0) { std::string lower = lang; strings::AsciiToLower(lower); // Traditional Chinese. for (char const * s : {"hant", "tw", "hk", "mo"}) if (lower.find(s) != std::string::npos) return "zh-Hant"; // Simplified Chinese by default for all other cases. return "zh-Hans"; } // Use short (2 or 3 chars) versions for all other languages. return Normalize(lang); } std::string GetCurrentTwine() { return GetTwine(GetCurrentOrig()); } std::string GetCurrentMapTwine() { return GetTwine(GetCurrentMapLanguage()); } } // namespace languages