From eecd84ac096271b86dc393b4e80ac1ff45077aa6 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Thu, 29 Aug 2024 19:09:33 +0200 Subject: [PATCH] Added basic configuration support and menu entries for theme localization --- es-app/src/guis/GuiMenu.cpp | 60 +++++++++++++ es-app/src/guis/GuiSettings.cpp | 1 - es-core/src/Settings.cpp | 1 + es-core/src/ThemeData.cpp | 118 +++++++++++++++++++++++++ es-core/src/ThemeData.h | 4 + es-core/src/utils/LocalizationUtil.cpp | 25 +++--- es-core/src/utils/LocalizationUtil.h | 1 + 7 files changed, 199 insertions(+), 11 deletions(-) diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 1cb121434..21fa71755 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -479,6 +479,49 @@ void GuiMenu::openUIOptions() themeTransitionsFunc(Settings::getInstance()->getString("Theme"), Settings::getInstance()->getString("ThemeTransitions")); + // Theme language. + auto themeLanguage = std::make_shared>( + 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::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>( 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); diff --git a/es-app/src/guis/GuiSettings.cpp b/es-app/src/guis/GuiSettings.cpp index 3a735ff52..9aacf62f6 100644 --- a/es-app/src/guis/GuiSettings.cpp +++ b/es-app/src/guis/GuiSettings.cpp @@ -77,7 +77,6 @@ void GuiSettings::save() mCloseMenuFunction = nullptr; } ViewController::getInstance()->rescanROMDirectory(); - return; } if (mNeedsCollectionsUpdate) { diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index ad3975ec3..9fcf6e284 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -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"] = {"", ""}; diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index bb04c48c5..8ac12a2ba 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -102,6 +102,25 @@ std::map ThemeData::sAspectRatioMap { {"32:9_vertical", 0.2813f}, {"1:1", 1.0f}}; +std::vector> 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> ThemeData::sPropertyAttributeMap // The data type is defined by the parent property. { @@ -629,6 +648,7 @@ void ThemeData::loadFile(const std::map& 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& 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(capabilities.aspectRatios.size()) - 1; + if (capabilities.languages.size() > 0) + languages = static_cast(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& 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 aspectRatiosTemp; std::vector fontSizesTemp; + std::vector 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& 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 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()) { diff --git a/es-core/src/ThemeData.h b/es-core/src/ThemeData.h index 746cdc012..e033b96db 100644 --- a/es-core/src/ThemeData.h +++ b/es-core/src/ThemeData.h @@ -186,6 +186,7 @@ public: std::vector colorSchemes; std::vector fontSizes; std::vector aspectRatios; + std::vector languages; std::vector transitions; std::vector 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>> @@ -275,6 +277,7 @@ private: static std::vector> sSupportedFontSizes; static std::vector> sSupportedAspectRatios; + static std::vector> sSupportedLanguages; static std::map sAspectRatioMap; static std::map> 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; }; diff --git a/es-core/src/utils/LocalizationUtil.cpp b/es-core/src/utils/LocalizationUtil.cpp index b41712eb8..5724b7842 100644 --- a/es-core/src/utils/LocalizationUtil.cpp +++ b/es-core/src/utils/LocalizationUtil.cpp @@ -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> 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 localeVector; std::pair 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 diff --git a/es-core/src/utils/LocalizationUtil.h b/es-core/src/utils/LocalizationUtil.h index 05a131410..9f29e0b18 100644 --- a/es-core/src/utils/LocalizationUtil.h +++ b/es-core/src/utils/LocalizationUtil.h @@ -24,6 +24,7 @@ namespace Utils namespace Localization { extern const std::vector> sSupportedLocales; + extern std::string sCurrentLocale; extern float sMenuTitleScaleFactor; const char* pgettextBuiltin(const char* msgctxt, const char* msgid);