Added basic configuration support and menu entries for theme localization

This commit is contained in:
Leon Styhre 2024-08-29 19:09:33 +02:00
parent 3f4ed60649
commit eecd84ac09
7 changed files with 199 additions and 11 deletions

View file

@ -479,6 +479,49 @@ void GuiMenu::openUIOptions()
themeTransitionsFunc(Settings::getInstance()->getString("Theme"),
Settings::getInstance()->getString("ThemeTransitions"));
// Theme language.
auto themeLanguage = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), _("THEME LANGUAGE"), false);
s->addWithLabel(_("THEME LANGUAGE"), themeLanguage);
s->addSaveFunc([themeLanguage, s] {
if (themeLanguage->getSelected() != Settings::getInstance()->getString("ThemeLanguage")) {
Settings::getInstance()->setString("ThemeLanguage", themeLanguage->getSelected());
s->setNeedsSaving();
s->setNeedsReloading();
s->setInvalidateCachedBackground();
}
});
auto themeLanguageFunc = [=](const std::string& selectedTheme,
const std::string& selectedLanguage) {
std::map<std::string, ThemeData::Theme, ThemeData::StringComparator>::const_iterator
currentSet {themes.find(selectedTheme)};
if (currentSet == themes.cend())
return;
// We need to recreate the OptionListComponent entries.
themeLanguage->clearEntries();
if (currentSet->second.capabilities.languages.size() > 0) {
for (auto& language : currentSet->second.capabilities.languages) {
themeLanguage->add(
Utils::String::toUpper(_(ThemeData::getLanguageLabel(language).c_str())),
language, language == selectedLanguage);
}
if (themeLanguage->getSelectedObjects().size() == 0)
themeLanguage->selectEntry(0);
}
else {
themeLanguage->add(_("NONE DEFINED"), "none", true);
themeLanguage->setEnabled(false);
themeLanguage->setOpacity(DISABLED_OPACITY);
themeLanguage->getParent()
->getChild(themeLanguage->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
}
};
themeLanguageFunc(Settings::getInstance()->getString("Theme"),
Settings::getInstance()->getString("ThemeLanguage"));
// Application language.
auto applicationLanguage = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), _("APPLICATION LANGUAGE"), false);
@ -519,6 +562,8 @@ void GuiMenu::openUIOptions()
s->setNeedsSaving();
s->setNeedsCloseMenu([this] { delete this; });
s->setNeedsRescanROMDirectory();
s->setNeedsReloading();
s->setNeedsCollectionsUpdate();
}
});
@ -1008,6 +1053,7 @@ void GuiMenu::openUIOptions()
themeColorSchemesFunc(themeName, themeColorScheme->getSelected());
themeFontSizeFunc(themeName, themeFontSize->getSelected());
themeAspectRatiosFunc(themeName, themeAspectRatio->getSelected());
themeLanguageFunc(themeName, themeLanguage->getSelected());
themeTransitionsFunc(themeName, themeTransitions->getSelected());
}
int selectableVariants {0};
@ -1057,6 +1103,20 @@ void GuiMenu::openUIOptions()
->getChild(themeFontSize->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
}
if (selectedTheme->second.capabilities.languages.size() > 0) {
themeLanguage->setEnabled(true);
themeLanguage->setOpacity(1.0f);
themeLanguage->getParent()
->getChild(themeLanguage->getChildIndex() - 1)
->setOpacity(1.0f);
}
else {
themeLanguage->setEnabled(false);
themeLanguage->setOpacity(DISABLED_OPACITY);
themeLanguage->getParent()
->getChild(themeLanguage->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
}
if (selectedTheme->second.capabilities.aspectRatios.size() > 0) {
themeAspectRatio->setEnabled(true);
themeAspectRatio->setOpacity(1.0f);

View file

@ -77,7 +77,6 @@ void GuiSettings::save()
mCloseMenuFunction = nullptr;
}
ViewController::getInstance()->rescanROMDirectory();
return;
}
if (mNeedsCollectionsUpdate) {

View file

@ -169,6 +169,7 @@ void Settings::setDefaults()
mStringMap["ThemeFontSize"] = {"", ""};
mStringMap["ThemeAspectRatio"] = {"", ""};
mStringMap["ThemeTransitions"] = {"automatic", "automatic"};
mStringMap["ThemeLanguage"] = {"automatic", "automatic"};
mStringMap["ApplicationLanguage"] = {"automatic", "automatic"};
mStringMap["QuickSystemSelect"] = {"leftrightshoulders", "leftrightshoulders"};
mStringMap["StartupSystem"] = {"", ""};

View file

@ -102,6 +102,25 @@ std::map<std::string, float> ThemeData::sAspectRatioMap {
{"32:9_vertical", 0.2813f},
{"1:1", 1.0f}};
std::vector<std::pair<std::string, std::string>> ThemeData::sSupportedLanguages {
{"automatic", "automatic"},
{"en_US", "ENGLISH (UNITED STATES)"},
{"en_GB", "ENGLISH (UNITED KINGDOM)"},
{"el_GR", "ΕΛΛΗΝΙΚΆ"},
{"de_DE", "DEUTSCH"},
{"es_ES", "ESPAÑOL (ESPAÑA)"},
{"fr_FR", "FRANÇAIS"},
{"it_IT", "ITALIANO"},
{"nl_NL", "NEDERLANDS"},
{"pl_PL", "POLSKI"},
{"pt_BR", "PORTUGUÊS (BRASIL)"},
{"ro_RO", "ROMÂNĂ"},
{"ru_RU", "РУССКИЙ"},
{"sv_SE", "SVENSKA"},
{"ja_JP", "日本語"},
{"zh_CN", "简体中文"},
{"ar_EG", "العربية"}};
std::map<std::string, std::map<std::string, std::string>> ThemeData::sPropertyAttributeMap
// The data type is defined by the parent property.
{
@ -629,6 +648,7 @@ void ThemeData::loadFile(const std::map<std::string, std::string>& sysDataMap,
}
sAspectRatioMatch = false;
sThemeLanguage = "";
if (sCurrentTheme->second.capabilities.aspectRatios.size() > 0) {
if (std::find(sCurrentTheme->second.capabilities.aspectRatios.cbegin(),
@ -663,6 +683,35 @@ void ThemeData::loadFile(const std::map<std::string, std::string>& sysDataMap,
}
}
if (sCurrentTheme->second.capabilities.languages.size() > 0) {
std::string langSetting {Settings::getInstance()->getString("ThemeLanguage")};
if (langSetting == "automatic")
langSetting = Utils::Localization::sCurrentLocale;
// Check if there is an exact match.
if (std::find(sCurrentTheme->second.capabilities.languages.cbegin(),
sCurrentTheme->second.capabilities.languages.cend(),
langSetting) != sCurrentTheme->second.capabilities.languages.cend()) {
sThemeLanguage = langSetting;
}
else {
// We assume all locales are in the correct format.
const std::string currLanguage {langSetting.substr(0, 2)};
// Select the closest matching locale (i.e. same language but possibly for a
// different country).
for (const auto& lang : sCurrentTheme->second.capabilities.languages) {
if (lang.substr(0, 2) == currLanguage) {
sThemeLanguage = lang;
break;
}
}
// If there is no match then fall back to the default language en_US, which is
// mandatory for all themes that provide language support.
if (sThemeLanguage == "")
sThemeLanguage = "en_US";
}
}
parseVariables(root);
parseColorSchemes(root);
parseFontSizes(root);
@ -795,8 +844,11 @@ void ThemeData::populateThemes()
LOG(LogInfo) << "Added theme \"" << *it << "\"" << themeName;
#endif
int aspectRatios {0};
int languages {0};
if (capabilities.aspectRatios.size() > 0)
aspectRatios = static_cast<int>(capabilities.aspectRatios.size()) - 1;
if (capabilities.languages.size() > 0)
languages = static_cast<int>(capabilities.languages.size()) - 1;
LOG(LogDebug) << "Theme includes support for " << capabilities.variants.size()
<< " variant" << (capabilities.variants.size() != 1 ? "s" : "")
<< ", " << capabilities.colorSchemes.size() << " color scheme"
@ -804,6 +856,7 @@ void ThemeData::populateThemes()
<< capabilities.fontSizes.size() << " font size"
<< (capabilities.fontSizes.size() != 1 ? "s" : "") << ", "
<< aspectRatios << " aspect ratio" << (aspectRatios != 1 ? "s" : "")
<< ", " << languages << " language" << (languages != 1 ? "s" : "")
<< " and " << capabilities.transitions.size() << " transition"
<< (capabilities.transitions.size() != 1 ? "s" : "");
@ -878,6 +931,18 @@ const std::string ThemeData::getAspectRatioLabel(const std::string& aspectRatio)
return "invalid ratio";
}
const std::string ThemeData::getLanguageLabel(const std::string& language)
{
auto it = std::find_if(sSupportedLanguages.cbegin(), sSupportedLanguages.cend(),
[&language](const std::pair<std::string, std::string>& entry) {
return entry.first == language;
});
if (it != sSupportedLanguages.cend())
return it->second;
else
return "invalid language";
}
void ThemeData::setThemeTransitions()
{
auto setTransitionsFunc = [](int transitionAnim) {
@ -971,6 +1036,7 @@ ThemeData::getCurrentThemeSelectedVariantOverrides()
const void ThemeData::themeLoadedLogOutput()
{
LOG(LogInfo) << "Finished loading theme \"" << sCurrentTheme->first << "\"";
if (sSelectedAspectRatio != "") {
const bool autoDetect {Settings::getInstance()->getString("ThemeAspectRatio") ==
"automatic"};
@ -980,6 +1046,13 @@ const void ThemeData::themeLoadedLogOutput()
<< "set to " << (autoDetect ? match : "") << "\""
<< Utils::String::replace(sSelectedAspectRatio, "_", " ") << "\"";
}
if (sThemeLanguage != "") {
LOG(LogInfo) << "Theme language set to \"" << sThemeLanguage << "\"";
}
else {
LOG(LogInfo) << "Theme does not have multilingual support";
}
}
unsigned int ThemeData::getHexColor(const std::string& str)
@ -1028,6 +1101,7 @@ ThemeData::ThemeCapability ThemeData::parseThemeCapabilities(const std::string&
ThemeCapability capabilities;
std::vector<std::string> aspectRatiosTemp;
std::vector<std::string> fontSizesTemp;
std::vector<std::string> languagesTemp;
bool hasTriggers {false};
const std::string capFile {path + "/capabilities.xml"};
@ -1305,6 +1379,36 @@ ThemeData::ThemeCapability ThemeData::parseThemeCapabilities(const std::string&
}
}
for (pugi::xml_node language {themeCapabilities.child("language")}; language;
language = language.next_sibling("language")) {
const std::string& value {language.text().get()};
if (std::find_if(sSupportedLanguages.cbegin(), sSupportedLanguages.cend(),
[&value](const std::pair<std::string, std::string>& entry) {
return entry.first == value;
}) == sSupportedLanguages.cend()) {
LOG(LogWarning) << "Declared language \"" << value
<< "\" is not supported, ignoring entry in \"" << capFile << "\"";
}
else {
if (std::find(languagesTemp.cbegin(), languagesTemp.cend(), value) !=
languagesTemp.cend()) {
LOG(LogWarning)
<< "Language \"" << value
<< "\" is declared multiple times, ignoring entry in \"" << capFile << "\"";
}
else {
languagesTemp.emplace_back(value);
}
}
}
if (languagesTemp.size() > 0 && std::find(languagesTemp.cbegin(), languagesTemp.cend(),
"en_US") == languagesTemp.cend()) {
LOG(LogError) << "Theme has declared language support but is missing mandatory "
<< "\"en_US\" entry in \"" << capFile << "\"";
languagesTemp.clear();
}
for (pugi::xml_node transitions {themeCapabilities.child("transitions")}; transitions;
transitions = transitions.next_sibling("transitions")) {
std::map<ViewTransition, ViewTransitionAnimation> readTransitions;
@ -1482,6 +1586,20 @@ ThemeData::ThemeCapability ThemeData::parseThemeCapabilities(const std::string&
}
}
// Add the languages in the order they are defined in sSupportedLanguages so they always
// show up in the same order in the UI Settings menu.
if (!languagesTemp.empty()) {
// Add the "automatic" language if there is at least one entry.
if (!languagesTemp.empty())
capabilities.languages.emplace_back(sSupportedLanguages.front().first);
for (auto& language : sSupportedLanguages) {
if (std::find(languagesTemp.cbegin(), languagesTemp.cend(), language.first) !=
languagesTemp.cend()) {
capabilities.languages.emplace_back(language.first);
}
}
}
// Add the font sizes in the order they are defined in sSupportedFontSizes so they always
// show up in the same order in the UI Settings menu.
if (!fontSizesTemp.empty()) {

View file

@ -186,6 +186,7 @@ public:
std::vector<ThemeColorScheme> colorSchemes;
std::vector<std::string> fontSizes;
std::vector<std::string> aspectRatios;
std::vector<std::string> languages;
std::vector<ThemeTransitions> transitions;
std::vector<std::string> suppressedTransitionProfiles;
bool validTheme;
@ -225,6 +226,7 @@ public:
const static std::string getSystemThemeFile(const std::string& system);
const static std::string getFontSizeLabel(const std::string& fontSize);
const static std::string getAspectRatioLabel(const std::string& aspectRatio);
const static std::string getLanguageLabel(const std::string& language);
static void setThemeTransitions();
const std::map<ThemeTriggers::TriggerType, std::pair<std::string, std::vector<std::string>>>
@ -275,6 +277,7 @@ private:
static std::vector<std::pair<std::string, std::string>> sSupportedFontSizes;
static std::vector<std::pair<std::string, std::string>> sSupportedAspectRatios;
static std::vector<std::pair<std::string, std::string>> sSupportedLanguages;
static std::map<std::string, float> sAspectRatioMap;
static std::map<std::string, std::map<std::string, std::string>> sPropertyAttributeMap;
@ -295,6 +298,7 @@ private:
std::string mSelectedFontSize;
static inline std::string sSelectedAspectRatio;
static inline bool sAspectRatioMatch {false};
static inline std::string sThemeLanguage;
bool mCustomCollection;
};

View file

@ -28,23 +28,26 @@ 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"}},
{{"ar"}, {"EG"}},
{{"de"}, {"DE"}},
{{"el"}, {"GR"}},
{{"de"}, {"DE"}},
{{"es"}, {"ES"}},
{{"fr"}, {"FR"}},
{{"it"}, {"IT"}},
{{"ja"}, {"JP"}},
{{"nl"}, {"NL"}},
{{"pl"}, {"PL"}},
{{"pt"}, {"BR"}},
{{"ro"}, {"RO"}},
{{"ru"}, {"RU"}},
{{"sv"}, {"SE"}},
{{"zh"}, {"CN"}}};
{{"ja"}, {"JP"}},
{{"zh"}, {"CN"}},
{{"ar"}, {"EG"}}};
// clang-format on
std::string sCurrentLocale {"en_US"};
float sMenuTitleScaleFactor {1.0f};
const char* pgettextBuiltin(const char* msgctxt, const char* msgid)
@ -136,6 +139,7 @@ namespace Utils
}
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;
@ -163,12 +167,12 @@ namespace Utils
if (std::find(sSupportedLocales.cbegin(), sSupportedLocales.cend(), localePair) !=
sSupportedLocales.cend()) {
locale = localePairCombined;
LOG(LogInfo) << "Setting application locale to \"" << locale << "\"";
LOG(LogInfo) << "Application language set to \"" << locale << "\"";
}
else {
for (auto& localeEntry : sSupportedLocales) {
if (localeEntry.first == localePair.first) {
LOG(LogInfo) << "No support for locale \"" << localePairCombined
LOG(LogInfo) << "No support for language \"" << localePairCombined
<< "\", falling back to closest match \""
<< localeEntry.first + "_" + localeEntry.second << "\"";
locale = localeEntry.first + "_" + localeEntry.second;
@ -178,16 +182,16 @@ namespace Utils
}
if (locale == "") {
LOG(LogInfo) << "No support for locale \"" << localePairCombined
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 == "de")
sMenuTitleScaleFactor = 0.92f;
else if (localePair.first == "el")
if (localePair.first == "el")
sMenuTitleScaleFactor = 0.94f;
else if (localePair.first == "de")
sMenuTitleScaleFactor = 0.92f;
else if (localePair.first == "es")
sMenuTitleScaleFactor = 0.90f;
else if (localePair.first == "fr")
@ -243,6 +247,7 @@ namespace Utils
textdomain(locale.c_str());
bindtextdomain(locale.c_str(), objectPath.c_str());
bind_textdomain_codeset(locale.c_str(), "UTF-8");
sCurrentLocale = locale;
}
} // namespace Localization

View file

@ -24,6 +24,7 @@ namespace Utils
namespace Localization
{
extern const std::vector<std::pair<std::string, std::string>> sSupportedLocales;
extern std::string sCurrentLocale;
extern float sMenuTitleScaleFactor;
const char* pgettextBuiltin(const char* msgctxt, const char* msgid);