/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: Locale.cpp Date: 2021-6-11 Author: Reece ***/ #include #include "Locale.hpp" #include #include #include namespace Aurora::Locale { // Note: [0] out of touch boomers deprecated this before going for a nappy. we do not have a replacement yet // [1] the native win32 implementation appears to be more well optimized than MSVC/stl static std::wstring_convert> gUtf8Conv; AUKN_SYM AuString ConvertFromWChar(const wchar_t *in) { return ConvertFromWChar(in, wcslen(in)); } AUKN_SYM AuString ConvertFromWChar(const wchar_t *in, AuMach length) { #if defined (VENDOR_GENERIC_MICROSOFT) || defined(VENDOR_CONSOLE_MICROSOFT) AuString ret; auto chars = WideCharToMultiByte(CP_UTF8, 0, in, length, NULL, 0, NULL, NULL); if (!chars) { return {}; } ret.resize(chars); WideCharToMultiByte(CP_UTF8, 0, in, length, ret.data(), ret.size(), NULL, NULL); return ret; #else return gUtf8Conv.to_bytes(std::wstring(in, wcslen(in))); #endif } AUKN_SYM std::wstring ConvertFromUTF8(const AuString &in) { #if defined (VENDOR_GENERIC_MICROSOFT) || defined(VENDOR_CONSOLE_MICROSOFT) std::wstring ret; auto chars = MultiByteToWideChar(CP_UTF8, 0, in.c_str(), in.length(), NULL, 0); if (!chars) { return {}; } ret.resize(chars); MultiByteToWideChar(CP_UTF8, 0, in.c_str(), in.length(), ret.data(), ret.size()); return ret; #else return gUtf8Conv.from_bytes(in); #endif } static AuString gCountryCode; static AuString gLanguageCode; static AuString gCodeset; #if defined(AURORA_PLATFORM_WIN32) static void SetLanguageWin32() { int ret; wchar_t name[LOCALE_NAME_MAX_LENGTH] = { 0 }; ret = LCIDToLocaleName(LOCALE_USER_DEFAULT, name, LOCALE_NAME_MAX_LENGTH, LOCALE_ALLOW_NEUTRAL_NAMES); SysAssert(ret, "Couldn't acquire win32 locale information"); wchar_t language[LOCALE_NAME_MAX_LENGTH] = { 0 }; ret = GetLocaleInfoEx(name, LOCALE_SISO639LANGNAME, language, LOCALE_NAME_MAX_LENGTH); SysAssert(ret, "Couldn't acquire win32 provided ISO 639 map of {}", ConvertFromWChar(name)); wchar_t country[LOCALE_NAME_MAX_LENGTH] = { 0 }; ret = GetLocaleInfoEx(name, LOCALE_SISO3166CTRYNAME, country, LOCALE_NAME_MAX_LENGTH); SysAssert(ret, "Couldn't acquire win32 provided ISO 3166 map of {}", ConvertFromWChar(name)); gCountryCode = ConvertFromWChar(country); gLanguageCode = ConvertFromWChar(language); gCodeset = "UTF-8"; } #elif defined(AURORA_PLATFORM_LINUX) || defined(AURORA_PLATFORM_BSD) static AuHashMap ParseLocaleString(const AuString &locale) { static auto isCharacterSplitter = [&](unsigned char ch) -> bool { static AuList characterSplitters = { '.', '_', '@' }; for (auto const splitter : characterSplitters) { if (splitter == ch) { return true; } } return false; }; AuHashMap parseTable; AuMach startingIndex = 0; unsigned char startingCharacter = '!'; for (AuMach i = 0; i < locale.size(); i++) { unsigned char curCh = locale[i]; if (!(isCharacterSplitter(curCh))) { continue; } parseTable.insert(std::make_pair(startingCharacter, locale.substr(startingIndex, i - startingIndex))); startingIndex = i + 1; startingCharacter = curCh; } parseTable.insert(std::make_pair(startingCharacter, locale.substr(startingIndex, locale.size() - startingIndex))); return parseTable; } static void SetLanguageUnix() { #if 0 // this doesn't seem to work with libc++ lol? auto locale = std::locale("").name(); #else setlocale(LC_ALL, ""); AuString locale = setlocale(LC_ALL, NULL); #endif if (locale == "C") { LogWarn("Improperly configured UNIX environment."); LogWarn("This localization detection code was written in 2020, please follow the `language[_territory][.codeset][@modifier]` convention for user/sys locales."); LogWarn("'C' is not a language, country, or anything with which we can discern anything meaningful from. Fix your scuffed unix operating system and try again later..."); SysPanic("You fools"); } auto parseTable = ParseLocaleString(locale); AuString *lc; if ((TryFind(parseTable, '!', lc)) && (lc->size())) { gLanguageCode = *lc; } else { LogWarn("Improperly configured UNIX environment."); LogWarn("Couldn't discern language from localization string: {}", locale); SysPanic("You fools"); } AuString *cc; if ((TryFind(parseTable, '_', cc)) && (cc->size())) { gCountryCode = *cc; } AuString *cs; if ((TryFind(parseTable, '.', cs)) && (cs->size())) { gCodeset = *cs; } else { gCodeset = "UTF-8"; //also technically not true, but most UNIX/Linux applications expect UTF8 byte stirngs or UTF-32 wchar_t strings. this assumption shouldn't break anything } } #define AURORA_HAS_UNIXLOCALE #endif #if defined(AURORA_PLATFORM_WIN32) || defined(AURORA_PLATFORM_LINUX) static void SetLanguageEnvBlock() { const char *language; if (language = getenv("AURORA_ENV_LANGUAGE")) { gLanguageCode = language; } const char *countryCode; if (countryCode = getenv("AURORA_ENV_COUNTRY")) { gCountryCode = countryCode; } const char *codeSet; if (codeSet = getenv("AURORA_ENV_CODESET")) { gCodeset = codeSet; } } #define AURORA_HAS_ENVBLOCK #endif void Init() { #if defined(AURORA_PLATFORM_WIN32) SetLanguageWin32(); #elif defined(AURORA_HAS_UNIXLOCALE) SetLanguageUnix(); #endif #if defined(AURORA_HAS_ENVBLOCK) SetLanguageEnvBlock(); #endif LogDbg("Initialized localization information (language: {}, country: {}, codeset: {})", gLanguageCode, gCountryCode, gCodeset); } AUKN_SYM LocalizationInfo GetLocale() { return LocalizationInfo(gLanguageCode, gCountryCode, gCodeset); } }