diff --git a/src/plugin/jamipluginmanager.cpp b/src/plugin/jamipluginmanager.cpp index f25c5e9bbc907982fe8987bd7df00b70177dcbea..73d3ac0e4c48cb8f718ea9545922d0cdc66d560b 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 237d0567d5887034f3b68e7beb45274a93e0bdcc..a60a9cef8b3a4c24df9aaf327888c128c7169323 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 559a3f0c9b2d49c60096225cf2d12d78ffb386a2..7980a1d7f104ffcbf69b5a591fe0bffddb766d51 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 b5eb85bc7711d9232a2d5b4d5669e2ac665a34d2..a40d21df44514e411be06433305de759e69cb6e3 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 adccd201bd43fa3407fee4bba252adc40f446e03..f5df57757b9e49ad06fee98b3c2d1227151ca1da 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 Binary files a/test/unitTest/plugins/TestSuite.jpl and b/test/unitTest/plugins/TestSuite.jpl differ diff --git a/test/unitTest/plugins/plugins.cpp b/test/unitTest/plugins/plugins.cpp index ece8f596dbb2496fc039c9d16f4d0ca5dc3065ce..8ccf47b448b53b7a1d9bca16c0a62e0e2249f69e 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_)); }