mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2025-03-06 14:27:43 +00:00
262 lines
12 KiB
C++
262 lines
12 KiB
C++
// 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 "Settings.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
|
|
{
|
|
// clang-format off
|
|
// When adding a new locale, then make sure to also update ThemeData::sSupportedLanguages.
|
|
const std::vector<std::pair<std::string, std::string>> sSupportedLocales {{{"en"}, {"US"}},
|
|
{{"en"}, {"GB"}},
|
|
{{"ca"}, {"ES"}},
|
|
{{"de"}, {"DE"}},
|
|
{{"es"}, {"ES"}},
|
|
{{"fr"}, {"FR"}},
|
|
{{"it"}, {"IT"}},
|
|
{{"nl"}, {"NL"}},
|
|
{{"pl"}, {"PL"}},
|
|
{{"pt"}, {"BR"}},
|
|
{{"ro"}, {"RO"}},
|
|
{{"ru"}, {"RU"}},
|
|
{{"sv"}, {"SE"}},
|
|
{{"ja"}, {"JP"}},
|
|
{{"ko"}, {"KR"}},
|
|
{{"zh"}, {"CN"}}};
|
|
// clang-format on
|
|
|
|
std::string sCurrentLocale {"en_US"};
|
|
float sMenuTitleScaleFactor {1.0f};
|
|
|
|
const char* pgettextBuiltin(const char* msgctxt, const char* msgid)
|
|
{
|
|
// This is an unbelievable hack but it's actually done pretty much the same way in
|
|
// the gettext.h header where a macro is used to wrap around the libintl functionality.
|
|
// Why this function is simply not part of libintl itself is anyone's guess, as that
|
|
// would be the logical thing to do.
|
|
std::string lookup;
|
|
lookup.append(msgctxt).append("\004").append(msgid);
|
|
const char* translation {gettext(lookup.c_str())};
|
|
if (translation == lookup.c_str())
|
|
return msgid;
|
|
else
|
|
return translation;
|
|
}
|
|
|
|
const char* npgettextBuiltin(const char* msgctxt,
|
|
const char* msgid1,
|
|
const char* msgid2,
|
|
unsigned long int n)
|
|
{
|
|
std::string lookup;
|
|
lookup.append(msgctxt).append("\004").append(msgid1);
|
|
const char* translation {ngettext(lookup.c_str(), msgid2, n)};
|
|
if (translation == lookup.c_str())
|
|
return msgid1;
|
|
else
|
|
return translation;
|
|
}
|
|
|
|
std::pair<std::string, std::string> getLocale()
|
|
{
|
|
#if defined(_WIN64)
|
|
std::wstring localeNameWide(LOCALE_NAME_MAX_LENGTH, '\0');
|
|
if (GetUserDefaultLocaleName(&localeNameWide[0], LOCALE_NAME_MAX_LENGTH) == 0)
|
|
return std::make_pair("en", "US");
|
|
|
|
std::string localeName {Utils::String::wideStringToString(localeNameWide)};
|
|
localeName.erase(localeName.find('\0'));
|
|
|
|
// This should never happen, but who knows with Windows.
|
|
if (localeName.empty())
|
|
return std::make_pair("en", "US");
|
|
|
|
std::vector<std::string> localeVector;
|
|
|
|
// Of course Windows doesn't follow standards and names locales with dashes
|
|
// instead of underscores, such as "sv-SE" instead of "sv_SE". But who knows
|
|
// if this is consistent, so we check for underscores as an extra precaution.
|
|
if (localeName.find("_") != std::string::npos)
|
|
localeVector = Utils::String::delimitedStringToVector(localeName, "_");
|
|
else
|
|
localeVector = Utils::String::delimitedStringToVector(localeName, "-");
|
|
|
|
if (localeVector.size() == 1)
|
|
return std::make_pair(localeVector[0], "");
|
|
else
|
|
return std::make_pair(localeVector[0], localeVector[1]);
|
|
#else
|
|
// SDL_GetPreferredLocales() does not seem to always return accurate results
|
|
// on Windows but for all other operating systems we use it.
|
|
SDL_Locale* preferredLocales {SDL_GetPreferredLocales()};
|
|
|
|
if (preferredLocales == nullptr)
|
|
return std::make_pair("en", "US");
|
|
|
|
std::string language {preferredLocales->language};
|
|
std::string country;
|
|
if (preferredLocales->country != nullptr)
|
|
country = preferredLocales->country;
|
|
|
|
SDL_free(preferredLocales);
|
|
return std::make_pair(language, country);
|
|
#endif
|
|
}
|
|
|
|
void setLocale()
|
|
{
|
|
// Only detect locale once (on application startup).
|
|
if (Settings::getInstance()->getString("DetectedLocale") == "") {
|
|
const std::pair<std::string, std::string> detectedLocale {getLocale()};
|
|
if (detectedLocale.second == "")
|
|
Settings::getInstance()->setString("DetectedLocale", detectedLocale.first);
|
|
else {
|
|
Settings::getInstance()->setString(
|
|
"DetectedLocale", detectedLocale.first + "_" + detectedLocale.second);
|
|
}
|
|
}
|
|
|
|
sMenuTitleScaleFactor = 1.0f;
|
|
sCurrentLocale = "en_US";
|
|
std::string languageSetting {Settings::getInstance()->getString("ApplicationLanguage")};
|
|
std::vector<std::string> localeVector;
|
|
std::pair<std::string, std::string> localePair;
|
|
|
|
if (languageSetting == "automatic") {
|
|
localeVector = Utils::String::delimitedStringToVector(
|
|
Settings::getInstance()->getString("DetectedLocale"), "_");
|
|
}
|
|
else {
|
|
localeVector = Utils::String::delimitedStringToVector(languageSetting, "_");
|
|
}
|
|
if (localeVector.size() == 1)
|
|
localePair = std::make_pair(localeVector[0], "");
|
|
else
|
|
localePair = std::make_pair(localeVector[0], localeVector[1]);
|
|
|
|
std::string locale;
|
|
std::string localePairCombined;
|
|
|
|
if (localePair.second == "")
|
|
localePairCombined = localePair.first;
|
|
else
|
|
localePairCombined = localePair.first + "_" + localePair.second;
|
|
|
|
if (std::find(sSupportedLocales.cbegin(), sSupportedLocales.cend(), localePair) !=
|
|
sSupportedLocales.cend()) {
|
|
locale = localePairCombined;
|
|
LOG(LogInfo) << "Application language set to \"" << locale << "\"";
|
|
}
|
|
else {
|
|
for (auto& localeEntry : sSupportedLocales) {
|
|
if (localeEntry.first == localePair.first) {
|
|
LOG(LogInfo) << "No support for language \"" << localePairCombined
|
|
<< "\", falling back to closest match \""
|
|
<< localeEntry.first + "_" + localeEntry.second << "\"";
|
|
locale = localeEntry.first + "_" + localeEntry.second;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (locale == "") {
|
|
LOG(LogInfo) << "No support for language \"" << localePairCombined
|
|
<< "\", falling back to default \"en_US\"";
|
|
locale = "en_US";
|
|
}
|
|
|
|
// Language-specific menu title scale factor.
|
|
if (localePair.first == "ca")
|
|
sMenuTitleScaleFactor = 0.92f;
|
|
else if (localePair.first == "de")
|
|
sMenuTitleScaleFactor = 0.92f;
|
|
else if (localePair.first == "es")
|
|
sMenuTitleScaleFactor = 0.90f;
|
|
else if (localePair.first == "fr")
|
|
sMenuTitleScaleFactor = 0.90f;
|
|
else if (localePair.first == "it")
|
|
sMenuTitleScaleFactor = 0.94f;
|
|
else if (localePair.first == "nl")
|
|
sMenuTitleScaleFactor = 0.94f;
|
|
else if (localePair.first == "pl")
|
|
sMenuTitleScaleFactor = 0.94f;
|
|
else if (localePair.first == "pt")
|
|
sMenuTitleScaleFactor = 0.90f;
|
|
else if (localePair.first == "ro")
|
|
sMenuTitleScaleFactor = 0.94f;
|
|
else if (localePair.first == "ru")
|
|
sMenuTitleScaleFactor = 0.94f;
|
|
else if (localePair.first == "sv")
|
|
sMenuTitleScaleFactor = 0.87f;
|
|
else if (localePair.first == "ja")
|
|
sMenuTitleScaleFactor = 0.94f;
|
|
else if (localePair.first == "ko")
|
|
sMenuTitleScaleFactor = 0.96f;
|
|
else if (localePair.first == "zh")
|
|
sMenuTitleScaleFactor = 0.94f;
|
|
|
|
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);
|
|
#else
|
|
setenv("LANGUAGE", locale.c_str(), 1);
|
|
setenv("LANG", locale.c_str(), 1);
|
|
// For some bizarre reason we need to first set the locale to en_US.UTF-8 before
|
|
// we set it to the requested locale as some specific locales like pt_BR and zh_CN
|
|
// otherwise won't work consistently. This must be some kind of library or OS bug as
|
|
// it only happens on regular Linux, and not on macOS, Windows, Android or FreeBSD.
|
|
setlocale(LC_MESSAGES, std::string {"en_US.UTF-8"}.c_str());
|
|
|
|
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");
|
|
sCurrentLocale = locale;
|
|
}
|
|
|
|
} // namespace Localization
|
|
|
|
} // namespace Utils
|