Răsfoiți Sursa

VCMI will now check whether translations are complete

Ivan Savenko 2 ani în urmă
părinte
comite
f57a77c2fe
4 a modificat fișierele cu 98 adăugiri și 25 ștergeri
  1. 42 3
      lib/CGeneralTextHandler.cpp
  2. 10 5
      lib/CGeneralTextHandler.h
  3. 44 17
      lib/CModHandler.cpp
  4. 2 0
      lib/CModHandler.h

+ 42 - 3
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;

+ 10 - 5
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<CInputStream> & input);
+	explicit CLegacyConfigParser(std::string URI);
+	explicit CLegacyConfigParser(const std::unique_ptr<CInputStream> & 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<std::string> 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;
 

+ 44 - 17
lib/CModHandler.cpp

@@ -1118,32 +1118,55 @@ void CModHandler::initializeConfig()
 	loadConfigFromFile("defaultMods.json");
 }
 
-void CModHandler::loadTranslation(TModID modName)
+bool CModHandler::validateTranslations(TModID modName) const
 {
-	auto const & mod = allMods[modName];
-	std::string preferredLanguage = VLC->generaltexth->getPreferredLanguage();
-	std::string modBaseLanguage = allMods[modName].baseLanguage;
+	bool result = true;
+	const auto & mod = allMods.at(modName);
 
-	for (auto const & config : mod.config["translations"].Vector())
 	{
-		JsonNode json(ResourceID(config.String(), EResType::TEXT));
-		json.setMeta(modName);
-
-		VLC->generaltexth->loadTranslationOverrides(modBaseLanguage, json);
+		auto fileList = mod.config["translations"].convertTo<std::vector<std::string> >();
+		JsonNode json = JsonUtils::assembleFromFiles(fileList);
+		result |= VLC->generaltexth->validateTranslation(mod.baseLanguage, modName, json);
 	}
 
-	for (auto const & config : mod.config[preferredLanguage]["translations"].Vector())
+	// TODO: unify language lists in mod handler, general text handler, launcher and json validation
+	static const std::vector<std::string> languagesList =
+	{ "english", "german", "polish", "russian", "ukrainian" };
+
+	for (auto const & language : languagesList)
 	{
-		JsonNode json(ResourceID(config.String(), EResType::TEXT));
-		json.setMeta(modName);
+		if (mod.config[language].isNull())
+			continue;
 
-		VLC->generaltexth->loadTranslationOverrides(preferredLanguage, json);
+		auto fileList = mod.config[language]["translations"].convertTo<std::vector<std::string> >();
+		JsonNode json = JsonUtils::assembleFromFiles(fileList);
+		result |= VLC->generaltexth->validateTranslation(language, modName, json);
 	}
+
+	return result;
+}
+
+void CModHandler::loadTranslation(TModID modName)
+{
+	auto & mod = allMods[modName];
+
+	std::string preferredLanguage = VLC->generaltexth->getPreferredLanguage();
+	std::string modBaseLanguage = allMods[modName].baseLanguage;
+
+	auto baseTranslationList = mod.config["translations"].convertTo<std::vector<std::string> >();
+	auto extraTranslationList = mod.config[preferredLanguage]["translations"].convertTo<std::vector<std::string> >();
+
+	JsonNode baseTranslation = JsonUtils::assembleFromFiles(baseTranslationList);
+	JsonNode extraTranslation = JsonUtils::assembleFromFiles(extraTranslationList);
+
+	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();

+ 2 - 0
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