From 61239ae8222561a296ff01a4d9d98b61944c47ee Mon Sep 17 00:00:00 2001
From: Xavier Jouslin de Noray <xavier.jouslindenoray@savoirfairelinux.com>
Date: Thu, 17 Aug 2023 17:32:37 -0400
Subject: [PATCH] Manifest: add parser to get the good translation for plugin
 description

Change-Id: Ice250eba3a638f8eae52a14edc7a68d7e08597cf
---
 src/plugin/jamipluginmanager.cpp      |   4 +-
 src/plugin/pluginpreferencesutils.cpp |  90 +------------------
 src/plugin/pluginpreferencesutils.h   |  18 ----
 src/plugin/pluginsutils.cpp           | 125 +++++++++++++++++++++++++-
 src/plugin/pluginsutils.h             |  34 ++++++-
 test/unitTest/plugins/TestSuite.jpl   | Bin 423270 -> 422979 bytes
 test/unitTest/plugins/plugins.cpp     |   4 +-
 7 files changed, 163 insertions(+), 112 deletions(-)

diff --git a/src/plugin/jamipluginmanager.cpp b/src/plugin/jamipluginmanager.cpp
index f25c5e9bbc..73d3ac0e4c 100644
--- a/src/plugin/jamipluginmanager.cpp
+++ b/src/plugin/jamipluginmanager.cpp
@@ -76,7 +76,7 @@ JamiPluginManager::getPluginDetails(const std::string& rootPath)
     }
 
     std::map<std::string, std::string> details = PluginUtils::parseManifestFile(
-        PluginUtils::manifestPath(rootPath));
+        PluginUtils::manifestPath(rootPath), rootPath);
     if (!details.empty()) {
         auto itIcon = details.find("iconPath");
         itIcon->second.insert(0, rootPath + DIR_SEPARATOR_CH + "data" + DIR_SEPARATOR_CH);
@@ -231,7 +231,7 @@ JamiPluginManager::installPlugin(const std::string& jplPath, bool force)
                                         + DIR_SEPARATOR_CH + name};
             // Find if there is an existing version of this plugin
             const auto alreadyInstalledManifestMap = PluginUtils::parseManifestFile(
-                PluginUtils::manifestPath(destinationDir));
+                PluginUtils::manifestPath(destinationDir), destinationDir);
 
             if (!alreadyInstalledManifestMap.empty()) {
                 if (force) {
diff --git a/src/plugin/pluginpreferencesutils.cpp b/src/plugin/pluginpreferencesutils.cpp
index 237d0567d5..a60a9cef8b 100644
--- a/src/plugin/pluginpreferencesutils.cpp
+++ b/src/plugin/pluginpreferencesutils.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "pluginpreferencesutils.h"
+#include "pluginsutils.h"
 
 #include <msgpack.hpp>
 #include <sstream>
@@ -60,51 +61,6 @@ PluginPreferencesUtils::getAllowDenyListsPath()
            + "allowdeny.msgpack";
 }
 
-std::map<std::string, std::string>
-PluginPreferencesUtils::processLocaleFile(const std::string& preferenceLocaleFilePath)
-{
-    if (!fileutils::isFile(preferenceLocaleFilePath)) {
-        return {};
-    }
-    std::ifstream file(preferenceLocaleFilePath);
-    Json::Value root;
-    Json::CharReaderBuilder rbuilder;
-    rbuilder["collectComments"] = false;
-    std::string errs;
-    std::map<std::string, std::string> locales {};
-    if (file) {
-        // Read the file to a json format
-        if (Json::parseFromStream(rbuilder, file, &root, &errs)) {
-            auto keys = root.getMemberNames();
-            for (const auto& key : keys) {
-                locales[key] = root.get(key, "").asString();
-            }
-        }
-    }
-    return locales;
-}
-
-std::map<std::string, std::string>
-PluginPreferencesUtils::getLocales(const std::string& rootPath, const std::string& lang)
-{
-    auto pluginName = rootPath.substr(rootPath.find_last_of(DIR_SEPARATOR_CH) + 1);
-    auto basePath = fmt::format("{}/data/locale/{}", rootPath, pluginName + "_");
-
-    std::map<std::string, std::string> locales = {};
-
-    // Get language translations
-    if (!lang.empty()) {
-        locales = processLocaleFile(basePath + lang + ".json");
-    }
-
-    // Get default english values if no translations were found
-    if (locales.empty()) {
-        locales = processLocaleFile(basePath + "en.json");
-    }
-
-    return locales;
-}
-
 std::string
 PluginPreferencesUtils::convertArrayToString(const Json::Value& jsonArray)
 {
@@ -159,48 +115,8 @@ PluginPreferencesUtils::getPreferences(const std::string& rootPath, const std::s
     std::vector<std::map<std::string, std::string>> preferences;
     if (file) {
         // Get preferences locale
-        std::string lang;
-        if (auto envLang = std::getenv("JAMI_LANG"))
-            lang = envLang;
-        else
-            JAMI_INFO() << "Error getting JAMI_LANG env, trying to get system language";
-        // If language preference is empty, try to get from the system.
-        if (lang.empty()) {
-#ifdef WIN32
-            WCHAR localeBuffer[LOCALE_NAME_MAX_LENGTH];
-            if (GetUserDefaultLocaleName(localeBuffer, LOCALE_NAME_MAX_LENGTH) != 0) {
-                char utf8Buffer[LOCALE_NAME_MAX_LENGTH] {};
-                WideCharToMultiByte(CP_UTF8,
-                                    0,
-                                    localeBuffer,
-                                    LOCALE_NAME_MAX_LENGTH,
-                                    utf8Buffer,
-                                    LOCALE_NAME_MAX_LENGTH,
-                                    nullptr,
-                                    nullptr);
-
-                lang.append(utf8Buffer);
-                string_replace(lang, "-", "_");
-            }
-            // Even though we default to the system variable in windows, technically this
-            // part of the code should not be reached because the client-qt must define that
-            // variable and we cannot run the client and the daemon in diferent processes in Windows.
-#else
-            // The same way described in the comment just above, the android should not reach this
-            // part of the code given the client-android must define "JAMI_LANG" system variable.
-            // And even if this part is reached, it should not work since std::locale is not
-            // supported by the NDK.
-
-            // LC_COLLATE is used to grab the locale for the case when the system user has set different
-            // values for the preferred Language and Format.
-            lang = setlocale(LC_COLLATE, "");
-            // We set the environment to avoid checking from system everytime.
-            // This is the case when running daemon and client in different processes
-            // like with dbus.
-            setenv("JAMI_LANG", lang.c_str(), 1);
-#endif // WIN32
-        }
-        auto locales = getLocales(rootPath, std::string(string_remove_suffix(lang, '.')));
+        const auto& lang = PluginUtils::getLanguage();
+        auto locales = PluginUtils::getLocales(rootPath, std::string(string_remove_suffix(lang, '.')));
 
         // Read the file to a json format
         bool ok = Json::parseFromStream(rbuilder, file, &root, &errs);
diff --git a/src/plugin/pluginpreferencesutils.h b/src/plugin/pluginpreferencesutils.h
index 559a3f0c9b..7980a1d7f1 100644
--- a/src/plugin/pluginpreferencesutils.h
+++ b/src/plugin/pluginpreferencesutils.h
@@ -67,24 +67,6 @@ public:
      */
     static std::string getAllowDenyListsPath();
 
-    /**
-     * @brief Returns the available keys and translations for a given file.
-     * If the locale is not available, return empty map.
-     * @param localeFilePath
-     * @return locales map
-     */
-    static std::map<std::string, std::string> processLocaleFile(const std::string& localeFilePath);
-
-    /**
-     * @brief Returns the available keys and translations for a given plugin.
-     * If the locale is not available, return the english default.
-     * @param rootPath
-     * @param lang
-     * @return locales map
-     */
-    static std::map<std::string, std::string> getLocales(const std::string& rootPath,
-                                                         const std::string& lang);
-
     /**
      * @brief Returns a colon separated string with values from a json::Value containing an array.
      * @param jsonArray
diff --git a/src/plugin/pluginsutils.cpp b/src/plugin/pluginsutils.cpp
index b5eb85bc77..a40d21df44 100644
--- a/src/plugin/pluginsutils.cpp
+++ b/src/plugin/pluginsutils.cpp
@@ -142,13 +142,14 @@ checkManifestValidity(const std::vector<uint8_t>& vec)
 }
 
 std::map<std::string, std::string>
-parseManifestFile(const std::string& manifestFilePath)
+parseManifestFile(const std::string& manifestFilePath, const std::string& rootPath)
 {
     std::lock_guard<std::mutex> guard(fileutils::getFileLock(manifestFilePath));
     std::ifstream file(manifestFilePath);
     if (file) {
         try {
-            return checkManifestValidity(file);
+            const auto& traduction = parseManifestTranslation(rootPath, file);
+            return checkManifestValidity(std::vector<uint8_t>(traduction.begin(), traduction.end()));
         } catch (const std::exception& e) {
             JAMI_ERR() << e.what();
         }
@@ -156,10 +157,36 @@ parseManifestFile(const std::string& manifestFilePath)
     return {};
 }
 
+std::string
+parseManifestTranslation(const std::string& rootPath, std::ifstream& manifestFile)
+{
+    if (manifestFile) {
+        std::stringstream buffer;
+        buffer << manifestFile.rdbuf();
+        std::string manifest = buffer.str();
+        const auto& translation = getLocales(rootPath, getLanguage());
+        std::regex pattern(R"(\{\{([^}]+)\}\})");
+        std::smatch matches;
+        // replace the pattern to the correct translation
+        while (std::regex_search(manifest, matches, pattern)) {
+            if (matches.size() == 2) {
+                auto it = translation.find(matches[1].str());
+                if (it == translation.end()) {
+                    manifest = std::regex_replace(manifest, pattern, "");
+                    continue;
+                }
+                manifest = std::regex_replace(manifest, pattern, it->second, std::regex_constants::format_first_only);
+            }
+        }
+        return manifest;
+    }
+    return {};
+}
+
 bool
 checkPluginValidity(const std::string& rootPath)
 {
-    return !parseManifestFile(manifestPath(rootPath)).empty();
+    return !parseManifestFile(manifestPath(rootPath), rootPath).empty();
 }
 
 std::map<std::string, std::string>
@@ -240,5 +267,97 @@ uncompressJplFunction(std::string_view relativeFileName)
     }
     return std::make_pair(true, relativeFileName);
 }
+
+std::string
+getLanguage()
+{
+    std::string lang;
+        if (auto envLang = std::getenv("JAMI_LANG"))
+            lang = envLang;
+        else
+            JAMI_INFO() << "Error getting JAMI_LANG env, trying to get system language";
+        // If language preference is empty, try to get from the system.
+        if (lang.empty()) {
+#ifdef WIN32
+            WCHAR localeBuffer[LOCALE_NAME_MAX_LENGTH];
+            if (GetUserDefaultLocaleName(localeBuffer, LOCALE_NAME_MAX_LENGTH) != 0) {
+                char utf8Buffer[LOCALE_NAME_MAX_LENGTH] {};
+                WideCharToMultiByte(CP_UTF8,
+                                    0,
+                                    localeBuffer,
+                                    LOCALE_NAME_MAX_LENGTH,
+                                    utf8Buffer,
+                                    LOCALE_NAME_MAX_LENGTH,
+                                    nullptr,
+                                    nullptr);
+
+                lang.append(utf8Buffer);
+                string_replace(lang, "-", "_");
+            }
+            // Even though we default to the system variable in windows, technically this
+            // part of the code should not be reached because the client-qt must define that
+            // variable and we cannot run the client and the daemon in diferent processes in Windows.
+#else
+            // The same way described in the comment just above, the android should not reach this
+            // part of the code given the client-android must define "JAMI_LANG" system variable.
+            // And even if this part is reached, it should not work since std::locale is not
+            // supported by the NDK.
+
+            // LC_COLLATE is used to grab the locale for the case when the system user has set different
+            // values for the preferred Language and Format.
+            lang = setlocale(LC_COLLATE, "");
+            // We set the environment to avoid checking from system everytime.
+            // This is the case when running daemon and client in different processes
+            // like with dbus.
+            setenv("JAMI_LANG", lang.c_str(), 1);
+#endif // WIN32
+    }
+    return lang;
+}
+
+std::map<std::string, std::string>
+getLocales(const std::string& rootPath, const std::string& lang)
+{
+    auto pluginName = rootPath.substr(rootPath.find_last_of(DIR_SEPARATOR_CH) + 1);
+    auto basePath = fmt::format("{}/data/locale/{}", rootPath, pluginName + "_");
+
+    std::map<std::string, std::string> locales = {};
+
+    // Get language translations
+    if (!lang.empty()) {
+        locales = processLocaleFile(basePath + lang + ".json");
+    }
+
+    // Get default english values if no translations were found
+    if (locales.empty()) {
+        locales = processLocaleFile(basePath + "en.json");
+    }
+
+    return locales;
+}
+
+std::map<std::string, std::string>
+processLocaleFile(const std::string& preferenceLocaleFilePath)
+{
+    if (!fileutils::isFile(preferenceLocaleFilePath)) {
+        return {};
+    }
+    std::ifstream file(preferenceLocaleFilePath);
+    Json::Value root;
+    Json::CharReaderBuilder rbuilder;
+    rbuilder["collectComments"] = false;
+    std::string errs;
+    std::map<std::string, std::string> locales {};
+    if (file) {
+        // Read the file to a json format
+        if (Json::parseFromStream(rbuilder, file, &root, &errs)) {
+            auto keys = root.getMemberNames();
+            for (const auto& key : keys) {
+                locales[key] = root.get(key, "").asString();
+            }
+        }
+    }
+    return locales;
+}
 } // namespace PluginUtils
 } // namespace jami
diff --git a/src/plugin/pluginsutils.h b/src/plugin/pluginsutils.h
index adccd201bd..f5df57757b 100644
--- a/src/plugin/pluginsutils.h
+++ b/src/plugin/pluginsutils.h
@@ -81,7 +81,15 @@ std::map<std::string, std::string> checkManifestValidity(const std::vector<uint8
  * @param manifestFilePath
  * @return Map with manifest contents
  */
-std::map<std::string, std::string> parseManifestFile(const std::string& manifestFilePath);
+std::map<std::string, std::string> parseManifestFile(const std::string& manifestFilePath, const std::string& rootPath);
+
+/**
+ * @brief Parses the manifest file of an installed plugin if it's valid.
+ * @param rootPath
+ * @param manifestFile
+ * @return Map with manifest contents
+ */
+std::string parseManifestTranslation(const std::string& rootPath, std::ifstream& manifestFile);
 
 /**
  * @brief Validates a plugin based on its manifest.json file.
@@ -134,5 +142,29 @@ std::vector<uint8_t> readSignatureFileFromArchive(const std::string& jplPath);
  * @return Pair <bool, string> meaning if file should be extracted and where to.
  */
 std::pair<bool, std::string_view> uncompressJplFunction(std::string_view relativeFileName);
+
+/**
+ * @brief Returns the language of the current locale.
+ * @return language
+ */
+std::string getLanguage();
+
+/**
+ * @brief Returns the available keys and translations for a given plugin.
+ * If the locale is not available, return the english default.
+ * @param rootPath
+ * @param lang
+ * @return locales map
+ */
+std::map<std::string, std::string> getLocales(const std::string& rootPath,
+                                                     const std::string& lang);
+
+/**
+ * @brief Returns the available keys and translations for a given file.
+ * If the locale is not available, return empty map.
+ * @param localeFilePath
+ * @return locales map
+ */
+std::map<std::string, std::string> processLocaleFile(const std::string& localeFilePath);
 } // namespace PluginUtils
 } // namespace jami
diff --git a/test/unitTest/plugins/TestSuite.jpl b/test/unitTest/plugins/TestSuite.jpl
index 18efdf05ed865b418e7ac3b76b23b619b0dbe697..477a6d683f9dd2bd14b3bf038a4493e887bcbc7f 100644
GIT binary patch
delta 3271
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
b1nk>+6CO`@C}or3GG%69_));dz`y_iIN*xY

delta 3557
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
Xo0gvGvL$RXTyL2e7?O+F7#J7;Z~4R6

diff --git a/test/unitTest/plugins/plugins.cpp b/test/unitTest/plugins/plugins.cpp
index ece8f596db..8ccf47b448 100644
--- a/test/unitTest/plugins/plugins.cpp
+++ b/test/unitTest/plugins/plugins.cpp
@@ -507,18 +507,20 @@ PluginsTest::testTranslations()
     Manager::instance().pluginPreferences.setPluginsEnabled(true);
     setenv("JAMI_LANG", "en", true);
     Manager::instance().getJamiPluginManager().installPlugin(jplPath_, true);
-
     auto preferences = Manager::instance().getJamiPluginManager().getPluginPreferences(installationPath_, "");
     CPPUNIT_ASSERT(!preferences.empty());
     auto preferencesValuesEN = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, "");
+    auto detailsValuesEN = Manager::instance().getJamiPluginManager().getPluginDetails(installationPath_);
 
     setenv("JAMI_LANG", "fr", true);
 
     CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, "") != preferencesValuesEN);
+    CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().getPluginDetails(installationPath_) != detailsValuesEN);
 
     setenv("JAMI_LANG", "en", true);
 
     CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, "") == preferencesValuesEN);
+    CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().getPluginDetails(installationPath_) == detailsValuesEN);
 
     CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().uninstallPlugin(installationPath_));
 }
-- 
GitLab