From 3b591c1a92ac30b5d5ef99a3179f6c840627201a Mon Sep 17 00:00:00 2001 From: Mateusz Piesta Date: Tue, 21 Dec 2021 15:51:24 +0100 Subject: [PATCH] [BH-1332] I18n update i18n correctly loads languages with special characters. --- .../windows/system/LanguagesWindow.cpp | 2 +- .../windows/system/LanguagesWindow.hpp | 1 - module-utils/i18n/CMakeLists.txt | 3 + module-utils/i18n/JSONLoader.hpp | 35 ++++ module-utils/i18n/Metadata.hpp | 31 +++ module-utils/i18n/i18n.cpp | 176 ++++++------------ module-utils/i18n/i18nImpl.hpp | 24 ++- module-utils/i18n/include/i18n/i18n.hpp | 11 +- .../OnBoardingLanguageWindowPresenter.cpp | 2 +- .../OnBoardingLanguageWindowPresenter.hpp | 1 - .../presenter/LanguageWindowPresenter.cpp | 2 +- .../presenter/LanguageWindowPresenter.hpp | 1 - 12 files changed, 156 insertions(+), 133 deletions(-) create mode 100644 module-utils/i18n/JSONLoader.hpp create mode 100644 module-utils/i18n/Metadata.hpp diff --git a/module-apps/application-settings/windows/system/LanguagesWindow.cpp b/module-apps/application-settings/windows/system/LanguagesWindow.cpp index d3c941d7da85b44cc04c7a85f1008849540feea3..e64bd18561decec890c04674662c746ca0832661 100644 --- a/module-apps/application-settings/windows/system/LanguagesWindow.cpp +++ b/module-apps/application-settings/windows/system/LanguagesWindow.cpp @@ -9,7 +9,7 @@ namespace gui { LanguagesWindow::LanguagesWindow(app::ApplicationCommon *app, std::string name) - : BaseSettingsWindow(app, std::move(name)), langList(loader.getAvailableDisplayLanguages()) + : BaseSettingsWindow(app, std::move(name)), langList(utils::getAvailableDisplayLanguages()) { setTitle(utils::translate("app_settings_title_languages")); } diff --git a/module-apps/application-settings/windows/system/LanguagesWindow.hpp b/module-apps/application-settings/windows/system/LanguagesWindow.hpp index b6383567a9549d8b7877497c638610658d55be8b..c762470ada122214f74ea4ea5528a2d9b43bd2d2 100644 --- a/module-apps/application-settings/windows/system/LanguagesWindow.hpp +++ b/module-apps/application-settings/windows/system/LanguagesWindow.hpp @@ -18,7 +18,6 @@ namespace gui void onBeforeShow(ShowMode mode, SwitchData *data) override; void setLanguageIndex(); - utils::LangLoader loader; const std::vector langList; Language selectedLanguage; unsigned int selectedLanguageIndex{0}; diff --git a/module-utils/i18n/CMakeLists.txt b/module-utils/i18n/CMakeLists.txt index 65e426a11b5a3c8f727370ccd3642f27c24febc5..965aae045c3e8296792a4904b256eebecc1c9f62 100644 --- a/module-utils/i18n/CMakeLists.txt +++ b/module-utils/i18n/CMakeLists.txt @@ -1,9 +1,12 @@ add_library(i18n STATIC) +add_library(utils::i18n ALIAS i18n) target_sources(i18n PRIVATE i18n.cpp i18nImpl.hpp + Metadata.hpp + JSONLoader.hpp PUBLIC include/i18n/i18n.hpp ) diff --git a/module-utils/i18n/JSONLoader.hpp b/module-utils/i18n/JSONLoader.hpp new file mode 100644 index 0000000000000000000000000000000000000000..aa857acb9f7e6d3205af555edd77815eef3a0a44 --- /dev/null +++ b/module-utils/i18n/JSONLoader.hpp @@ -0,0 +1,35 @@ +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md + +#pragma once + +#include +#include +#include +#include +#include + +namespace utils +{ + inline std::optional JSONLoader(const std::filesystem::path &path) + { + auto file = std::ifstream{path}; + if (file.bad()) { + return {}; + } + std::stringstream buf{}; + buf << file.rdbuf(); + + std::string err; + json11::Json js = json11::Json::parse(buf.str(), err); + + if (err.length() != 0) { + LOG_ERROR("Failed to parse input data, err: %s", err.c_str()); + return {}; + } + else { + return js; + } + } + +} // namespace utils diff --git a/module-utils/i18n/Metadata.hpp b/module-utils/i18n/Metadata.hpp new file mode 100644 index 0000000000000000000000000000000000000000..46a32dca51e5e2d2762bf2dfa9f160385ac5165c --- /dev/null +++ b/module-utils/i18n/Metadata.hpp @@ -0,0 +1,31 @@ +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md + +#pragma once + +#include +#include +#include + +namespace utils +{ + class LanguageMetadata + { + public: + static std::optional get(const std::filesystem::path &path, const json11::Json &contents) + { + if (contents[metadataKey].is_null() || contents[metadataKey][metadataDisplayKey].is_null()) { + return {}; + } + + return LanguageMetadata{.displayName = contents[metadataKey][metadataDisplayKey].string_value(), + .path = path}; + } + const std::string displayName; + const std::filesystem::path path; + + private: + static constexpr auto metadataKey = "metadata"; + static constexpr auto metadataDisplayKey = "display_name"; + }; +} // namespace utils diff --git a/module-utils/i18n/i18n.cpp b/module-utils/i18n/i18n.cpp index ceb0c002de3fdaa80b1483b03dbfead23fd9c33e..3ee35e0dac1237250334a363e0981e95ec3b17dc 100644 --- a/module-utils/i18n/i18n.cpp +++ b/module-utils/i18n/i18n.cpp @@ -2,13 +2,12 @@ // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "i18nImpl.hpp" -#include -#include -#include -#include +#include "Metadata.hpp" + #include #include -#include +#include +#include namespace utils { @@ -19,74 +18,11 @@ namespace utils return str.empty() ? ret : str; } - std::optional loadJSONData(const std::filesystem::path &path) - { - auto fd = std::fopen(path.c_str(), "r"); - if (fd == nullptr) { - LOG_FATAL("Error during opening file %s", path.c_str()); - return {}; - } - - const auto fsize = std::filesystem::file_size(path); - - auto stream = std::make_unique(fsize + 1); // +1 for NULL terminator - - std::fread(stream.get(), 1, fsize, fd); - - std::string err; - json11::Json js = json11::Json::parse(stream.get(), err); - - std::fclose(fd); - - // Error - if (err.length() != 0) { - LOG_FATAL("%s", err.c_str()); - return {}; - } - else { - return js; - } - } - - struct LanguageMetadata - { - static std::optional get(const std::filesystem::path &path) - { - const auto contents = loadJSONData(path); - if (not contents) { - return {}; - } - const auto contentsValue = *contents; - if (contentsValue[metadataKey].is_null() || contentsValue[metadataKey][metadataDisplayKey].is_null()) { - return {}; - } - - return LanguageMetadata{.displayName = contentsValue[metadataKey][metadataDisplayKey].string_value()}; - } - const std::string displayName; - - private: - static constexpr auto metadataKey = "metadata"; - static constexpr auto metadataDisplayKey = "display_name"; - }; - } // namespace - - namespace - { class i18nPrivateInterface : public i18n { public: const std::string &get(const std::string &str); const std::vector get_array(const std::string &str); - using i18n::getDisplayLanguage; - using i18n::getDisplayLanguagePath; - using i18n::getInputLanguage; - using i18n::getInputLanguageFilename; - using i18n::getInputLanguagePath; - using i18n::resetAssetsPath; - using i18n::resetDisplayLanguages; - using i18n::setDisplayLanguage; - using i18n::setInputLanguage; }; const std::string &i18nPrivateInterface::get(const std::string &str) @@ -115,37 +51,9 @@ namespace utils return out; } - } // namespace - - i18nPrivateInterface localize; - - std::vector LangLoader::getAvailableDisplayLanguages() const - { - std::vector languageNames; - for (const auto &entry : std::filesystem::directory_iterator(getDisplayLanguagePath())) { - const auto metadata = LanguageMetadata::get(entry.path()); - if (metadata) { - languageNames.push_back(metadata->displayName); - } - else { - /// If metadata is not valid use file name string as a display language - languageNames.push_back(std::filesystem::path(entry).stem()); - } - } - - std::sort(languageNames.begin(), languageNames.end()); - - return languageNames; - } + i18nPrivateInterface localize; - std::vector LangLoader::getAvailableInputLanguages() const - { - std::vector languageNames; - for (const auto &entry : std::filesystem::directory_iterator(getInputLanguagePath())) { - languageNames.push_back(std::filesystem::path(entry.path()).stem()); - } - return languageNames; - } + } // namespace void i18n::resetAssetsPath(const std::filesystem::path &assets) { @@ -155,6 +63,7 @@ namespace utils bool i18n::setInputLanguage(const Language &lang) { + cpp_freertos::LockGuard lock(mutex); if (lang.empty() || lang == inputLanguage) { return false; } @@ -177,38 +86,37 @@ namespace utils bool i18n::setDisplayLanguage(const Language &lang) { - if (fallbackLanguage == json11::Json()) { + cpp_freertos::LockGuard lock(mutex); + if (fallbackLanguage.is_null()) { loadFallbackLanguage(); + loadMetadata(); } if ((lang.empty() || lang == currentDisplayLanguage) && !currentDisplayLanguage.empty()) { - return false; } - else if (const auto pack = - loadJSONData(localize.getDisplayLanguagePath() / (lang + utils::files::jsonExtension))) { - + else if (const auto result = getMetadata(lang)) { currentDisplayLanguage = lang; - changeDisplayLanguage(pack.value()); - + displayLanguage = loader(result->path).value(); return true; } return false; } - void i18n::changeDisplayLanguage(const json11::Json &lang) - { - cpp_freertos::LockGuard lock(mutex); - displayLanguage = lang; - } - void i18n::loadFallbackLanguage() { - cpp_freertos::LockGuard lock(mutex); currentDisplayLanguage = fallbackLanguageName; fallbackLanguage = - loadJSONData(localize.getDisplayLanguagePath() / (fallbackLanguageName + utils::files::jsonExtension)) - .value(); + loader(localize.getDisplayLanguagePath() / (fallbackLanguageName + utils::files::jsonExtension)).value(); + } + + void i18n::loadMetadata() + { + for (const auto &entry : std::filesystem::directory_iterator(getDisplayLanguagePath())) { + if (const auto data = fetchMetadata(entry.path())) { + metadata.push_back(*data); + } + } } const std::string &translate(const std::string &text) @@ -263,6 +171,42 @@ namespace utils fallbackLanguage = json11::Json(); } + std::optional i18n::getMetadata(const Language &lang) const + { + const auto result = + std::find_if(metadata.begin(), metadata.end(), [&lang](const auto &e) { return e.displayName == lang; }); + if (result != metadata.end()) { + return *result; + } + else { + return {}; + } + } + std::optional i18n::fetchMetadata(const std::filesystem::path &path) const + { + if (const auto jsonData = loader(path)) { + return LanguageMetadata::get(path, jsonData.value()); + } + return {}; + } + std::vector i18n::getAvailableDisplayLanguages() const + { + std::vector languages{metadata.size()}; + std::transform( + metadata.cbegin(), metadata.cend(), languages.begin(), [](const auto &meta) { return meta.displayName; }); + + std::sort(languages.begin(), languages.end()); + return languages; + } + std::vector i18n::getAvailableInputLanguages() const + { + std::vector languageNames; + for (const auto &entry : std::filesystem::directory_iterator(getInputLanguagePath())) { + languageNames.push_back(std::filesystem::path(entry.path()).stem()); + } + return languageNames; + } + void resetDisplayLanguages() { return localize.resetDisplayLanguages(); @@ -272,5 +216,9 @@ namespace utils { return localize.resetAssetsPath(p); } + std::vector getAvailableDisplayLanguages() + { + return localize.getAvailableDisplayLanguages(); + } } // namespace utils diff --git a/module-utils/i18n/i18nImpl.hpp b/module-utils/i18n/i18nImpl.hpp index 21acf2ac2cbabcc0e7a5874db9671b851f253010..704cb560c9f56c637548f361b0687a70c3dc4d44 100644 --- a/module-utils/i18n/i18nImpl.hpp +++ b/module-utils/i18n/i18nImpl.hpp @@ -3,6 +3,10 @@ #pragma once +#include "JSONLoader.hpp" +#include "Metadata.hpp" + +#include #include #include #include @@ -12,6 +16,9 @@ namespace utils class i18n { private: + using Loader = std::function(const std::filesystem::path &path)>; + Loader loader = JSONLoader; + json11::Json displayLanguage; json11::Json fallbackLanguage; // backup language if item not found Language fallbackLanguageName = getDefaultLanguage(); @@ -20,13 +27,15 @@ namespace utils Language currentDisplayLanguage; std::filesystem::path InputLanguageDirPath = "assets/profiles"; std::filesystem::path DisplayLanguageDirPath = "assets/lang"; - mutable cpp_freertos::MutexStandard mutex; + cpp_freertos::MutexStandard mutex; + std::vector metadata; - void changeDisplayLanguage(const json11::Json &lang); void loadFallbackLanguage(); + void loadMetadata(); + std::optional getMetadata(const Language &lang) const; + std::optional fetchMetadata(const std::filesystem::path &path) const; - protected: - const std::string &get(const std::string &str); + public: json11::Json &getDisplayLanguageJSON() { return displayLanguage; @@ -46,15 +55,18 @@ namespace utils const std::string &getInputLanguageFilename(const std::string &inputMode); bool setInputLanguage(const Language &lang); bool setDisplayLanguage(const Language &lang); - const std::filesystem::path getInputLanguagePath() + const std::filesystem::path getInputLanguagePath() const { return InputLanguageDirPath; } - const std::filesystem::path getDisplayLanguagePath() + const std::filesystem::path getDisplayLanguagePath() const { return DisplayLanguageDirPath; } + std::vector getAvailableDisplayLanguages() const; + std::vector getAvailableInputLanguages() const; + void resetDisplayLanguages(); void resetAssetsPath(const std::filesystem::path &); }; diff --git a/module-utils/i18n/include/i18n/i18n.hpp b/module-utils/i18n/include/i18n/i18n.hpp index 2f3db6e2e07c45c202b1a219880989c87789cbf9..2f9c8200833bb84ade521af2c58d4d1ca02a0d06 100644 --- a/module-utils/i18n/include/i18n/i18n.hpp +++ b/module-utils/i18n/include/i18n/i18n.hpp @@ -6,6 +6,7 @@ #include #include #include +#include using Language = std::string; @@ -17,19 +18,15 @@ namespace utils constexpr auto breakSign = "_"; } // namespace files - class LangLoader - { - public: - std::vector getAvailableDisplayLanguages() const; - std::vector getAvailableInputLanguages() const; - }; - const std::string &translate(const std::string &text); const std::vector translate_array(const std::string &text); const std::string &getDisplayLanguage(); const std::string &getInputLanguage(); const std::string &getInputLanguageFilename(const std::string &inputMode); + std::vector getAvailableDisplayLanguages(); + std::vector getAvailableInputLanguages(); + bool setInputLanguage(const Language &lang); bool setDisplayLanguage(const Language &lang); void resetDisplayLanguages(); diff --git a/products/BellHybrid/apps/application-bell-onboarding/presenter/OnBoardingLanguageWindowPresenter.cpp b/products/BellHybrid/apps/application-bell-onboarding/presenter/OnBoardingLanguageWindowPresenter.cpp index 26516a4395017e9b12e1f3cb8d156485b8a64b45..6efeee6387ce5df0307a931998310575a16b1d0a 100644 --- a/products/BellHybrid/apps/application-bell-onboarding/presenter/OnBoardingLanguageWindowPresenter.cpp +++ b/products/BellHybrid/apps/application-bell-onboarding/presenter/OnBoardingLanguageWindowPresenter.cpp @@ -11,7 +11,7 @@ namespace app::OnBoarding std::vector OnBoardingLanguageWindowPresenter::getLanguages() { - return languages.getAvailableDisplayLanguages(); + return utils::getAvailableDisplayLanguages(); } unsigned OnBoardingLanguageWindowPresenter::getSelectedLanguageIndex() diff --git a/products/BellHybrid/apps/application-bell-onboarding/presenter/OnBoardingLanguageWindowPresenter.hpp b/products/BellHybrid/apps/application-bell-onboarding/presenter/OnBoardingLanguageWindowPresenter.hpp index 15b3cd1c5e7eecb0431c05f0678a4ec59ff43585..915772af475c35e5d41a490285325f5aee4caa5c 100644 --- a/products/BellHybrid/apps/application-bell-onboarding/presenter/OnBoardingLanguageWindowPresenter.hpp +++ b/products/BellHybrid/apps/application-bell-onboarding/presenter/OnBoardingLanguageWindowPresenter.hpp @@ -32,7 +32,6 @@ namespace app::OnBoarding class OnBoardingLanguageWindowPresenter : public OnBoardingLanguageWindowContract::Presenter { private: - utils::LangLoader languages; app::ApplicationCommon *app; public: diff --git a/products/BellHybrid/apps/application-bell-settings/presenter/LanguageWindowPresenter.cpp b/products/BellHybrid/apps/application-bell-settings/presenter/LanguageWindowPresenter.cpp index 3bae78f7197d37b859f444cf2a7380b10289a79e..e3368dbc68f10b3e277247b3e22ffa0703e92e58 100644 --- a/products/BellHybrid/apps/application-bell-settings/presenter/LanguageWindowPresenter.cpp +++ b/products/BellHybrid/apps/application-bell-settings/presenter/LanguageWindowPresenter.cpp @@ -11,7 +11,7 @@ namespace app::bell_settings std::vector LanguageWindowPresenter::getLanguages() const { - return languages.getAvailableDisplayLanguages(); + return utils::getAvailableDisplayLanguages(); } std::string LanguageWindowPresenter::getSelectedLanguage() const diff --git a/products/BellHybrid/apps/application-bell-settings/presenter/LanguageWindowPresenter.hpp b/products/BellHybrid/apps/application-bell-settings/presenter/LanguageWindowPresenter.hpp index 30531846abdc0a0380e6b5245c3298a14f5946b4..60d91d55f60b10679837be2e2a39fa344c41335e 100644 --- a/products/BellHybrid/apps/application-bell-settings/presenter/LanguageWindowPresenter.hpp +++ b/products/BellHybrid/apps/application-bell-settings/presenter/LanguageWindowPresenter.hpp @@ -32,7 +32,6 @@ namespace app::bell_settings class LanguageWindowPresenter : public LanguageWindowContract::Presenter { private: - utils::LangLoader languages; app::ApplicationCommon *app{}; public: