2024-06-29 19:24:28 +00:00
|
|
|
// 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"
|
|
|
|
|
2024-06-30 21:10:04 +00:00
|
|
|
#include <SDL2/SDL_locale.h>
|
|
|
|
|
2024-06-29 19:24:28 +00:00
|
|
|
#include <algorithm>
|
|
|
|
#include <iostream>
|
|
|
|
|
2024-06-30 20:27:22 +00:00
|
|
|
#if defined(_WIN64)
|
|
|
|
#include <Windows.h>
|
|
|
|
#endif
|
|
|
|
|
2024-06-29 19:24:28 +00:00
|
|
|
namespace Utils
|
|
|
|
{
|
|
|
|
namespace Localization
|
|
|
|
{
|
|
|
|
std::string getLocale()
|
|
|
|
{
|
2024-06-30 20:27:22 +00:00
|
|
|
#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;
|
2024-06-30 21:10:04 +00:00
|
|
|
#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;
|
2024-06-30 20:27:22 +00:00
|
|
|
#else
|
2024-06-29 19:24:28 +00:00
|
|
|
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("."));
|
2024-06-30 20:27:22 +00:00
|
|
|
#endif
|
2024-06-29 19:24:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
2024-06-30 20:27:22 +00:00
|
|
|
#if defined(_WIN64)
|
|
|
|
_configthreadlocale(_DISABLE_PER_THREAD_LOCALE);
|
|
|
|
const LCID localeID {LocaleNameToLCID(Utils::String::stringToWideString(locale).c_str(),
|
|
|
|
LOCALE_ALLOW_NEUTRAL_NAMES)};
|
|
|
|
SetThreadLocale(localeID);
|
2024-06-30 21:10:04 +00:00
|
|
|
#elif defined(__APPLE__)
|
|
|
|
// This is seemingly needed specifically on macOS but not on Linux.
|
|
|
|
setenv("LANGUAGE", locale.c_str(), 1);
|
2024-06-30 20:27:22 +00:00
|
|
|
#else
|
|
|
|
setlocale(LC_MESSAGES, std::string {locale + ".UTF-8"}.c_str());
|
|
|
|
#endif
|
2024-06-29 19:24:28 +00:00
|
|
|
textdomain(locale.c_str());
|
|
|
|
bindtextdomain(locale.c_str(), objectPath.c_str());
|
|
|
|
bind_textdomain_codeset(locale.c_str(), "UTF-8");
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Localization
|
|
|
|
|
|
|
|
} // namespace Utils
|