~aleteoryx/muditaos

3b591c1a92ac30b5d5ef99a3179f6c840627201a — Mateusz Piesta 4 years ago be1e6f7
[BH-1332] I18n update

i18n correctly loads languages
with special characters.
M module-apps/application-settings/windows/system/LanguagesWindow.cpp => module-apps/application-settings/windows/system/LanguagesWindow.cpp +1 -1
@@ 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"));
    }

M module-apps/application-settings/windows/system/LanguagesWindow.hpp => module-apps/application-settings/windows/system/LanguagesWindow.hpp +0 -1
@@ 18,7 18,6 @@ namespace gui
        void onBeforeShow(ShowMode mode, SwitchData *data) override;
        void setLanguageIndex();

        utils::LangLoader loader;
        const std::vector<Language> langList;
        Language selectedLanguage;
        unsigned int selectedLanguageIndex{0};

M module-utils/i18n/CMakeLists.txt => module-utils/i18n/CMakeLists.txt +3 -0
@@ 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
)

A module-utils/i18n/JSONLoader.hpp => module-utils/i18n/JSONLoader.hpp +35 -0
@@ 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 <optional>
#include <filesystem>
#include <fstream>
#include <log/log.hpp>
#include <json11.hpp>

namespace utils
{
    inline std::optional<json11::Json> 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

A module-utils/i18n/Metadata.hpp => module-utils/i18n/Metadata.hpp +31 -0
@@ 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 <optional>
#include <filesystem>
#include <json11.hpp>

namespace utils
{
    class LanguageMetadata
    {
      public:
        static std::optional<LanguageMetadata> 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

M module-utils/i18n/i18n.cpp => module-utils/i18n/i18n.cpp +62 -114
@@ 2,13 2,12 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "i18nImpl.hpp"
#include <log/log.hpp>
#include <algorithm>
#include <optional>
#include <cstdio>
#include "Metadata.hpp"

#include <purefs/filesystem_paths.hpp>
#include <i18n/i18n.hpp>
#include <utility>
#include <algorithm>
#include <optional>

namespace utils
{


@@ 19,74 18,11 @@ namespace utils
            return str.empty() ? ret : str;
        }

        std::optional<json11::Json> 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<char[]>(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<LanguageMetadata> 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<std::string> 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<Language> LangLoader::getAvailableDisplayLanguages() const
    {
        std::vector<std::string> 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<Language> LangLoader::getAvailableInputLanguages() const
    {
        std::vector<std::string> 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<LanguageMetadata> 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<LanguageMetadata> i18n::fetchMetadata(const std::filesystem::path &path) const
    {
        if (const auto jsonData = loader(path)) {
            return LanguageMetadata::get(path, jsonData.value());
        }
        return {};
    }
    std::vector<Language> i18n::getAvailableDisplayLanguages() const
    {
        std::vector<Language> 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<Language> i18n::getAvailableInputLanguages() const
    {
        std::vector<Language> 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<Language> getAvailableDisplayLanguages()
    {
        return localize.getAvailableDisplayLanguages();
    }

} // namespace utils

M module-utils/i18n/i18nImpl.hpp => module-utils/i18n/i18nImpl.hpp +18 -6
@@ 3,6 3,10 @@

#pragma once

#include "JSONLoader.hpp"
#include "Metadata.hpp"

#include <optional>
#include <mutex.hpp>
#include <json11.hpp>
#include <i18n/i18n.hpp>


@@ 12,6 16,9 @@ namespace utils
    class i18n
    {
      private:
        using Loader  = std::function<std::optional<json11::Json>(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<LanguageMetadata> metadata;

        void changeDisplayLanguage(const json11::Json &lang);
        void loadFallbackLanguage();
        void loadMetadata();
        std::optional<LanguageMetadata> getMetadata(const Language &lang) const;
        std::optional<LanguageMetadata> 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<Language> getAvailableDisplayLanguages() const;
        std::vector<Language> getAvailableInputLanguages() const;

        void resetDisplayLanguages();
        void resetAssetsPath(const std::filesystem::path &);
    };

M module-utils/i18n/include/i18n/i18n.hpp => module-utils/i18n/include/i18n/i18n.hpp +4 -7
@@ 6,6 6,7 @@
#include <string>
#include <filesystem>
#include <vector>
#include <functional>

using Language = std::string;



@@ 17,19 18,15 @@ namespace utils
        constexpr auto breakSign     = "_";
    } // namespace files

    class LangLoader
    {
      public:
        std::vector<Language> getAvailableDisplayLanguages() const;
        std::vector<Language> getAvailableInputLanguages() const;
    };

    const std::string &translate(const std::string &text);
    const std::vector<std::string> translate_array(const std::string &text);
    const std::string &getDisplayLanguage();
    const std::string &getInputLanguage();
    const std::string &getInputLanguageFilename(const std::string &inputMode);

    std::vector<Language> getAvailableDisplayLanguages();
    std::vector<Language> getAvailableInputLanguages();

    bool setInputLanguage(const Language &lang);
    bool setDisplayLanguage(const Language &lang);
    void resetDisplayLanguages();

M products/BellHybrid/apps/application-bell-onboarding/presenter/OnBoardingLanguageWindowPresenter.cpp => products/BellHybrid/apps/application-bell-onboarding/presenter/OnBoardingLanguageWindowPresenter.cpp +1 -1
@@ 11,7 11,7 @@ namespace app::OnBoarding

    std::vector<std::string> OnBoardingLanguageWindowPresenter::getLanguages()
    {
        return languages.getAvailableDisplayLanguages();
        return utils::getAvailableDisplayLanguages();
    }

    unsigned OnBoardingLanguageWindowPresenter::getSelectedLanguageIndex()

M products/BellHybrid/apps/application-bell-onboarding/presenter/OnBoardingLanguageWindowPresenter.hpp => products/BellHybrid/apps/application-bell-onboarding/presenter/OnBoardingLanguageWindowPresenter.hpp +0 -1
@@ 32,7 32,6 @@ namespace app::OnBoarding
    class OnBoardingLanguageWindowPresenter : public OnBoardingLanguageWindowContract::Presenter
    {
      private:
        utils::LangLoader languages;
        app::ApplicationCommon *app;

      public:

M products/BellHybrid/apps/application-bell-settings/presenter/LanguageWindowPresenter.cpp => products/BellHybrid/apps/application-bell-settings/presenter/LanguageWindowPresenter.cpp +1 -1
@@ 11,7 11,7 @@ namespace app::bell_settings

    std::vector<std::string> LanguageWindowPresenter::getLanguages() const
    {
        return languages.getAvailableDisplayLanguages();
        return utils::getAvailableDisplayLanguages();
    }

    std::string LanguageWindowPresenter::getSelectedLanguage() const

M products/BellHybrid/apps/application-bell-settings/presenter/LanguageWindowPresenter.hpp => products/BellHybrid/apps/application-bell-settings/presenter/LanguageWindowPresenter.hpp +0 -1
@@ 32,7 32,6 @@ namespace app::bell_settings
    class LanguageWindowPresenter : public LanguageWindowContract::Presenter
    {
      private:
        utils::LangLoader languages;
        app::ApplicationCommon *app{};

      public: