// SPDX-License-Identifier: MIT // // ES-DE Frontend // LocalizationUtil.cpp // // Localization functions. // Provides support for translations using gettext/libintl. // #include "utils/LocalizationUtil.h" #include "Log.h" #include "resources/ResourceManager.h" #include "utils/StringUtil.h" #include <SDL2/SDL_locale.h> #include <algorithm> #include <iostream> #if defined(_WIN64) #include <Windows.h> #endif namespace Utils { namespace Localization { std::string getLocale() { #if defined(_WIN64) std::wstring localeName(LOCALE_NAME_MAX_LENGTH, '\0'); if (GetUserDefaultLocaleName(&localeName[0], LOCALE_NAME_MAX_LENGTH) == 0) return "en_US"; // Of course Windows doesn't follow standards and names locales with dashes instead // of underscores, such as "sv-SE" instead of "sv_SE". std::string locale { Utils::String::replace(Utils::String::wideStringToString(localeName), "-", "_")}; locale.erase(locale.find('\0')); return locale; #elif defined(__APPLE__) // The SDL locale function does not seem to always return the correct result // (at least not on Windows) but for macOS it's very annoying to use the OS-supplied // locale facilities, so here we still use the SDL method. SDL_Locale* preferredLocales {SDL_GetPreferredLocales()}; if (preferredLocales == nullptr) return "en_US"; std::string primaryLocale {preferredLocales->language}; if (preferredLocales->country != nullptr) primaryLocale.append("_").append(preferredLocales->country); SDL_free(preferredLocales); return primaryLocale; #else std::string language; // The LANGUAGE environment variable takes precedence over LANG. if (getenv("LANGUAGE") != nullptr) language = getenv("LANGUAGE"); const std::vector<std::string> languageValues { Utils::String::delimitedStringToVector(language, ":")}; for (auto value : languageValues) { if (std::find(sSupportedLanguages.cbegin(), sSupportedLanguages.cend(), value) != sSupportedLanguages.cend()) { return value; } } if (getenv("LANG") != nullptr) language = getenv("LANG"); if (language.empty()) return "en_US"; return language.substr(0, language.find(".")); #endif } void setLanguage(const std::string& locale) { if (std::find(sSupportedLanguages.cbegin(), sSupportedLanguages.cend(), locale) == sSupportedLanguages.cend()) { LOG(LogInfo) << "No support for language \"" << locale << "\", reverting to default language \"en_US\""; return; } else { LOG(LogInfo) << "Setting application language to \"" << locale << "\""; } // No need to perform translations if we're using the default language. if (locale == "en_US") return; std::string localePath; localePath.append("/") .append(locale) .append("/LC_MESSAGES/") .append(locale) .append(".mo"); // If the message catalog file is not found then an emergency shutdown will be // initiated by ResourceManager. std::string objectPath { ResourceManager::getInstance().getResourcePath(":/locale" + localePath)}; // This makes it possible to override the message catalog with a file in the // application data directory. if (objectPath.length() > localePath.length()) objectPath = objectPath.substr(0, objectPath.length() - localePath.length()); #if defined(_WIN64) _configthreadlocale(_DISABLE_PER_THREAD_LOCALE); const LCID localeID {LocaleNameToLCID(Utils::String::stringToWideString(locale).c_str(), LOCALE_ALLOW_NEUTRAL_NAMES)}; SetThreadLocale(localeID); #elif defined(__APPLE__) // This is seemingly needed specifically on macOS but not on Linux. setenv("LANGUAGE", locale.c_str(), 1); #else setlocale(LC_MESSAGES, std::string {locale + ".UTF-8"}.c_str()); #endif textdomain(locale.c_str()); bindtextdomain(locale.c_str(), objectPath.c_str()); bind_textdomain_codeset(locale.c_str(), "UTF-8"); } } // namespace Localization } // namespace Utils