diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index 849c2a6c5..a023bad68 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -410,10 +410,49 @@ void CGeneralTextHandler::registerStringOverride(const std::string & modContext, entry.modContext = modContext; } -void CGeneralTextHandler::loadTranslationOverrides(const std::string & language, const JsonNode & config) +bool CGeneralTextHandler::validateTranslation(const std::string & language, const std::string & modContext, const JsonNode & config) const +{ + bool allPresent = true; + + for (auto const & string : stringsLocalizations) + { + if (string.second.modContext != modContext) + continue; + + if (string.second.baseLanguage == language && !string.second.baseValue.empty()) + continue; + + if (config.Struct().count(string.first) > 0) + continue; + + if (allPresent) + logMod->warn("Translation into language '%s' in mod '%s' is incomplete! Missing lines:", language, modContext); + + logMod->warn(R"( "%s" : "",)", string.first); + allPresent = false; + } + + bool allFound = true; + + for (auto const & string : config.Struct()) + { + if (stringsLocalizations.count(string.first) > 0) + continue; + + if (allFound) + logMod->warn("Translation into language '%s' in mod '%s' has unused lines:", language, modContext); + + logMod->warn(R"( "%s" : "",)", string.first); + allFound = false; + } + + return allPresent && allFound; +} + +void CGeneralTextHandler::loadTranslationOverrides(const std::string & language, const std::string & modContext, const JsonNode & config) { for ( auto const & node : config.Struct()) - registerStringOverride(node.second.meta, language, node.first, node.second.String()); + registerStringOverride(modContext, language, node.first, node.second.String()); } CGeneralTextHandler::CGeneralTextHandler(): @@ -619,7 +658,7 @@ CGeneralTextHandler::CGeneralTextHandler(): } } -int32_t CGeneralTextHandler::pluralText(const int32_t textIndex, const int32_t count) const +int32_t CGeneralTextHandler::pluralText(int32_t textIndex, int32_t count) const { if(textIndex == 0) return 0; diff --git a/lib/CGeneralTextHandler.h b/lib/CGeneralTextHandler.h index 284abc7d4..2561bad38 100644 --- a/lib/CGeneralTextHandler.h +++ b/lib/CGeneralTextHandler.h @@ -41,7 +41,7 @@ namespace Unicode std::string DLL_LINKAGE fromUnicode(const std::string & text, const std::string & encoding); ///delete (amount) UTF characters from right - DLL_LINKAGE void trimRight(std::string & text, const size_t amount = 1); + DLL_LINKAGE void trimRight(std::string & text, size_t amount = 1); }; class CInputStream; @@ -87,8 +87,8 @@ public: /// end current line bool endLine(); - CLegacyConfigParser(std::string URI); - CLegacyConfigParser(const std::unique_ptr & input); + explicit CLegacyConfigParser(std::string URI); + explicit CLegacyConfigParser(const std::unique_ptr & input); }; class CGeneralTextHandler; @@ -175,9 +175,14 @@ class DLL_LINKAGE CGeneralTextHandler std::string getModLanguage(const std::string & modContext); public: + /// validates translation of specified language for specified mod + /// returns true if localization is valid and complete + /// any error messages will be written to log file + bool validateTranslation(const std::string & language, const std::string & modContext, JsonNode const & file) const; + /// Loads translation from provided json /// Any entries loaded by this will have priority over texts registered normally - void loadTranslationOverrides(const std::string & language, JsonNode const & file); + void loadTranslationOverrides(const std::string & language, const std::string & modContext, JsonNode const & file); /// add selected string to internal storage void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized); @@ -240,7 +245,7 @@ public: std::vector findStringsWithPrefix(std::string const & prefix); - int32_t pluralText(const int32_t textIndex, const int32_t count) const; + int32_t pluralText(int32_t textIndex, int32_t count) const; size_t getCampaignLength(size_t campaignID) const; diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index d6d4335e0..6c943f282 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -1118,32 +1118,55 @@ void CModHandler::initializeConfig() loadConfigFromFile("defaultMods.json"); } +bool CModHandler::validateTranslations(TModID modName) const +{ + bool result = true; + const auto & mod = allMods.at(modName); + + { + auto fileList = mod.config["translations"].convertTo >(); + JsonNode json = JsonUtils::assembleFromFiles(fileList); + result |= VLC->generaltexth->validateTranslation(mod.baseLanguage, modName, json); + } + + // TODO: unify language lists in mod handler, general text handler, launcher and json validation + static const std::vector languagesList = + { "english", "german", "polish", "russian", "ukrainian" }; + + for (auto const & language : languagesList) + { + if (mod.config[language].isNull()) + continue; + + auto fileList = mod.config[language]["translations"].convertTo >(); + JsonNode json = JsonUtils::assembleFromFiles(fileList); + result |= VLC->generaltexth->validateTranslation(language, modName, json); + } + + return result; +} + void CModHandler::loadTranslation(TModID modName) { - auto const & mod = allMods[modName]; + auto & mod = allMods[modName]; + std::string preferredLanguage = VLC->generaltexth->getPreferredLanguage(); std::string modBaseLanguage = allMods[modName].baseLanguage; - for (auto const & config : mod.config["translations"].Vector()) - { - JsonNode json(ResourceID(config.String(), EResType::TEXT)); - json.setMeta(modName); + auto baseTranslationList = mod.config["translations"].convertTo >(); + auto extraTranslationList = mod.config[preferredLanguage]["translations"].convertTo >(); - VLC->generaltexth->loadTranslationOverrides(modBaseLanguage, json); - } + JsonNode baseTranslation = JsonUtils::assembleFromFiles(baseTranslationList); + JsonNode extraTranslation = JsonUtils::assembleFromFiles(extraTranslationList); - for (auto const & config : mod.config[preferredLanguage]["translations"].Vector()) - { - JsonNode json(ResourceID(config.String(), EResType::TEXT)); - json.setMeta(modName); - - VLC->generaltexth->loadTranslationOverrides(preferredLanguage, json); - } + VLC->generaltexth->loadTranslationOverrides(modBaseLanguage, modName, baseTranslation); + VLC->generaltexth->loadTranslationOverrides(preferredLanguage, modName, extraTranslation); } void CModHandler::load() { - CStopWatch totalTime, timer; + CStopWatch totalTime; + CStopWatch timer; logMod->info("\tInitializing content handler: %d ms", timer.getDiff()); @@ -1166,15 +1189,19 @@ void CModHandler::load() for(const TModID & modName : activeMods) content->load(allMods[modName]); - for(const TModID & modName : activeMods) - loadTranslation(modName); - #if SCRIPTING_ENABLED VLC->scriptHandler->performRegistration(VLC);//todo: this should be done before any other handlers load #endif content->loadCustom(); + for(const TModID & modName : activeMods) + loadTranslation(modName); + + for(const TModID & modName : activeMods) + if (!validateTranslations(modName)) + allMods[modName].validation = CModInfo::FAILED; + logMod->info("\tLoading mod data: %d ms", timer.getDiff()); VLC->creh->loadCrExpBon(); diff --git a/lib/CModHandler.h b/lib/CModHandler.h index 2b62274c5..7af2c7b6b 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -286,6 +286,8 @@ class DLL_LINKAGE CModHandler void loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods); void loadOneMod(std::string modName, std::string parent, const JsonNode & modSettings, bool enableMods); void loadTranslation(TModID modName); + + bool validateTranslations(TModID modName) const; public: /// returns true if scope is reserved for internal use and can not be used by mods