~aleteoryx/muditaos

1ae0f91a223c591abd7c3a791876e9e5f13b4d39 — Mateusz Piesta 4 years ago 5e0be62
[BH-1288] Special characters support

Added support for special chars to i18n.
M image/assets/lang/Deutsch.json => image/assets/lang/Deutsch.json +4 -1
@@ 1,4 1,7 @@
{
  "metadata": {
    "display_name": "Deutsch"
  },
  "common_add": "HINZUFÜGEN",
  "common_open": "ÖFFNEN",
  "common_call": "ANRUFEN",


@@ 675,4 678,4 @@
  "app_bell_meditation_interval_every_x_minutes": "jede %0 Minuten",
  "app_bell_meditation_put_down_and_wait": "<text>Legen Sie Mudita Harmony<br>ab und warten Sie auf den Gong</text>",
  "app_bell_meditation_thank_you_for_session": "<text>Danke für<br>die Sitzung</text>"
}
\ No newline at end of file
}

M image/assets/lang/English.json => image/assets/lang/English.json +3 -0
@@ 1,4 1,7 @@
{
  "metadata": {
    "display_name": "English"
  },
  "common_add": "ADD",
  "common_open": "OPEN",
  "common_call": "CALL",

R image/assets/lang/Español.json => image/assets/lang/Espanol.json +4 -1
@@ 1,4 1,7 @@
{
  "metadata": {
    "display_name": "Español"
  },
  "common_add": "AÑADIR",
  "common_open": "ABRIR",
  "common_call": "LLAMAR",


@@ 675,4 678,4 @@
  "app_bell_meditation_interval_every_x_minutes": "cada %0 minutos",
  "app_bell_meditation_put_down_and_wait": "<text>Posicione Mudita Harmony<br>y espere el gong</text>",
  "app_bell_meditation_thank_you_for_session": "<text>Gracias<br>por la sesión</text>"
}
\ No newline at end of file
}

R image/assets/lang/Français.json => image/assets/lang/Francais.json +4 -1
@@ 1,4 1,7 @@
{
  "metadata": {
    "display_name": "Français"
  },
  "common_add": "AJOUTER",
  "common_open": "OUVRIR",
  "common_call": "APPELER",


@@ 645,4 648,4 @@
  "app_bell_meditation_interval_every_x_minutes": "toutes les %0 minutes",
  "app_bell_meditation_put_down_and_wait": "<text>Posez Mudita Harmony<br>et attendez le gong</text>",
  "app_bell_meditation_thank_you_for_session": "<text>Merci<br>pour cette session</text>"
}
\ No newline at end of file
}

M image/assets/lang/Polski.json => image/assets/lang/Polski.json +4 -1
@@ 1,4 1,7 @@
{
  "metadata": {
    "display_name": "Polski"
  },
  "common_add": "DODAJ",
  "common_open": "OTWÓRZ",
  "common_call": "ZADZWOŃ",


@@ 688,4 691,4 @@
  "app_bell_meditation_interval_every_x_minutes": "co %0 minuty",
  "app_bell_meditation_put_down_and_wait": "<text>Odłóż Mudita Harmony<br>i czekaj na gong</text>",
  "app_bell_meditation_thank_you_for_session": "<text>Dziękujemy<br>za sesję</text>"
}
\ No newline at end of file
}

M image/assets/lang/Svenska.json => image/assets/lang/Svenska.json +6 -2
@@ 1,4 1,7 @@
{
  "metadata": {
    "display_name": "Svenska"
  },
  "common_add": "LÄGG TILL",
  "common_open": "ÖPPNA",
  "common_call": "RING",


@@ 276,8 279,9 @@
  "app_onboarding_skip_confirm": "<text> SIM-installation krävs för nätverksanslutning. Hoppar du ändå över installationen? </text>",
  "app_onboarding_configuration_successful": "<text> Din Mudita Pure <br> </br> är klar att använda. </text>",
  "app_onboarding_no_configuration": "<text> Din Mudita Pure har inte konfigurerats. </br> Du kan gå till <br> </br> Inställningar för att konfigurera den. </text>",
  "app_onboarding_update_info": "<text> Den nuvarande versionen av MuditaOS är <br> </br> <token> $ VERSION </token>. Uppdateringar med nya <br> </br> funktioner och korrigeringar visas ofta. <br> </br> För att uppdatera din telefon vänligen <br> </br> besök: </text> <text font = 'gt_pressura' weight = 'bold' size = '27 '> www.mudita.com/updateos </text > <br> </br> <text> och följ instruktionerna. </text> ",  "app_settings_title_main": "Inställningar",
  "app_settings_advanced" : "Avancerad",
  "app_onboarding_update_info": "<text> Den nuvarande versionen av MuditaOS är <br> </br> <token> $ VERSION </token>. Uppdateringar med nya <br> </br> funktioner och korrigeringar visas ofta. <br> </br> För att uppdatera din telefon vänligen <br> </br> besök: </text> <text font = 'gt_pressura' weight = 'bold' size = '27 '> www.mudita.com/updateos </text > <br> </br> <text> och följ instruktionerna. </text> ",
  "app_settings_title_main": "Inställningar",
  "app_settings_advanced": "Avancerad",
  "app_settings_bt": "Bluetooth",
  "app_settings_bluetooth_add_device": "Lägg till enhet",
  "app_settings_bluetooth_all_devices": "Alla enheter",

M module-utils/i18n/i18n.cpp => module-utils/i18n/i18n.cpp +71 -34
@@ 4,8 4,11 @@
#include "i18nImpl.hpp"
#include <log/log.hpp>
#include <algorithm>
#include <optional>
#include <cstdio>
#include <purefs/filesystem_paths.hpp>
#include <i18n/i18n.hpp>
#include <utility>

namespace utils
{


@@ 15,6 18,57 @@ 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


@@ 65,42 119,22 @@ namespace utils

    i18nPrivateInterface localize;

    json11::Json LangLoaderImpl::createJson(const std::string &filename)
    {
        const auto path = localize.getDisplayLanguagePath() / (filename + utils::files::jsonExtension);
        auto fd         = std::fopen(path.c_str(), "r");
        if (fd == nullptr) {
            LOG_FATAL("Error during opening file %s", path.c_str());
            return json11::Json();
        }

        uint32_t 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 json11::Json();
        }
        else {
            return js;
        }
    }

    std::vector<Language> LangLoader::getAvailableDisplayLanguages() const
    {
        std::vector<std::string> languageNames;
        for (const auto &entry : std::filesystem::directory_iterator(getDisplayLanguagePath())) {
            languageNames.push_back(std::filesystem::path(entry.path()).stem());
            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;
    }



@@ 151,10 185,11 @@ namespace utils

            return false;
        }
        else if (json11::Json pack = loader.createJson(lang); pack != json11::Json()) {
        else if (const auto pack =
                     loadJSONData(localize.getDisplayLanguagePath() / (lang + utils::files::jsonExtension))) {

            currentDisplayLanguage = lang;
            changeDisplayLanguage(pack);
            changeDisplayLanguage(pack.value());

            return true;
        }


@@ 171,7 206,9 @@ namespace utils
    {
        cpp_freertos::LockGuard lock(mutex);
        currentDisplayLanguage = fallbackLanguageName;
        fallbackLanguage       = loader.createJson(fallbackLanguageName);
        fallbackLanguage =
            loadJSONData(localize.getDisplayLanguagePath() / (fallbackLanguageName + utils::files::jsonExtension))
                .value();
    }

    const std::string &translate(const std::string &text)

M module-utils/i18n/i18nImpl.hpp => module-utils/i18n/i18nImpl.hpp +0 -8
@@ 9,19 9,11 @@

namespace utils
{

    class LangLoaderImpl : public LangLoader
    {
      public:
        json11::Json createJson(const std::string &filename);
    };

    class i18n
    {
      private:
        json11::Json displayLanguage;
        json11::Json fallbackLanguage; // backup language if item not found
        LangLoaderImpl loader;
        Language fallbackLanguageName = getDefaultLanguage();
        Language inputLanguage        = fallbackLanguageName;
        Language inputLanguageFilename;

M module-utils/i18n/include/i18n/i18n.hpp => module-utils/i18n/include/i18n/i18n.hpp +0 -1
@@ 20,7 20,6 @@ namespace utils
    class LangLoader
    {
      public:
        virtual ~LangLoader() = default;
        std::vector<Language> getAvailableDisplayLanguages() const;
        std::vector<Language> getAvailableInputLanguages() const;
    };

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.getSupportedLanguages();
        return languages.getAvailableDisplayLanguages();
    }

    unsigned OnBoardingLanguageWindowPresenter::getSelectedLanguageIndex()

M products/BellHybrid/apps/application-bell-onboarding/presenter/OnBoardingLanguageWindowPresenter.hpp => products/BellHybrid/apps/application-bell-onboarding/presenter/OnBoardingLanguageWindowPresenter.hpp +1 -2
@@ 5,7 5,6 @@

#include <apps-common/BasePresenter.hpp>
#include <apps-common/ApplicationCommon.hpp>
#include <common/Languages.hpp>

#include <vector>
#include <string>


@@ 33,7 32,7 @@ namespace app::OnBoarding
    class OnBoardingLanguageWindowPresenter : public OnBoardingLanguageWindowContract::Presenter
    {
      private:
        common::Languages languages;
        utils::LangLoader languages;
        app::ApplicationCommon *app;

      public:

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

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

    std::string LanguageWindowPresenter::getSelectedLanguage() const

M products/BellHybrid/apps/application-bell-settings/presenter/advanced/LanguageWindowPresenter.hpp => products/BellHybrid/apps/application-bell-settings/presenter/advanced/LanguageWindowPresenter.hpp +1 -2
@@ 5,7 5,6 @@

#include <apps-common/BasePresenter.hpp>
#include <apps-common/ApplicationCommon.hpp>
#include <common/Languages.hpp>

#include <vector>
#include <string>


@@ 33,7 32,7 @@ namespace app::bell_settings
    class LanguageWindowPresenter : public LanguageWindowContract::Presenter
    {
      private:
        common::Languages languages;
        utils::LangLoader languages;
        app::ApplicationCommon *app{};

      public:

M products/BellHybrid/apps/common/CMakeLists.txt => products/BellHybrid/apps/common/CMakeLists.txt +0 -1
@@ 42,7 42,6 @@ target_sources(application-bell-common
        src/options/OptionBellMenu.cpp
        src/options/BellOptionsNavigation.cpp
    PUBLIC
        include/common/Languages.hpp
        include/common/BellListItemProvider.hpp
        include/common/LanguageUtils.hpp
        include/common/SoundsRepository.hpp

D products/BellHybrid/apps/common/include/common/Languages.hpp => products/BellHybrid/apps/common/include/common/Languages.hpp +0 -39
@@ 1,39 0,0 @@
// 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 <i18n/i18n.hpp>

#include <algorithm>
#include <vector>
#include <array>
#include <string_view>

namespace common
{
    class Languages
    {
      public:
        std::vector<Language> getSupportedLanguages() const
        {
            auto languages = loader.getAvailableDisplayLanguages();
            languages.erase(
                std::remove_if(languages.begin(), languages.end(), [this](const auto &lang) { return matcher(lang); }),
                languages.end());
            std::sort(languages.begin(), languages.end());
            return languages;
        }

      private:
        bool matcher(const std::string_view language) const
        {
            const auto result = std::find(excludedLanguages.begin(), excludedLanguages.end(), language);
            return result != excludedLanguages.end();
        }

        static constexpr std::array<std::string_view, 1> excludedLanguages = {{"Svenska"}};
        utils::LangLoader loader;
    };

} // namespace common