Selaa lähdekoodia

Add simple support for translation of strings that were changed by
another mod

Ivan Savenko 1 vuosi sitten
vanhempi
sitoutus
1488629628

+ 3 - 2
lib/CBonusTypeHandler.cpp

@@ -200,8 +200,9 @@ ImagePath CBonusTypeHandler::bonusToGraphics(const std::shared_ptr<Bonus> & bonu
 
 void CBonusTypeHandler::load()
 {
-	const JsonNode gameConf(JsonPath::builtin("config/gameConfig.json"));
-	const JsonNode config(JsonUtils::assembleFromFiles(gameConf["bonuses"].convertTo<std::vector<std::string>>()));
+	JsonNode gameConf(JsonPath::builtin("config/gameConfig.json"));
+	JsonNode config(JsonUtils::assembleFromFiles(gameConf["bonuses"].convertTo<std::vector<std::string>>()));
+	config.setModScope("vcmi");
 	load(config);
 }
 

+ 2 - 2
lib/mapping/CMapHeader.cpp

@@ -189,7 +189,7 @@ void CMapHeader::registerMapStrings()
 		JsonUtils::mergeCopy(data, translations[language]);
 	
 	for(auto & s : data.Struct())
-		texts.registerString("map", TextIdentifier(s.first), s.second.String(), language);
+		texts.registerString("map", TextIdentifier(s.first), s.second.String());
 }
 
 std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized)
@@ -199,7 +199,7 @@ std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeade
 
 std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized, const std::string & language)
 {
-	mapHeader.texts.registerString(modContext, UID, localized, language);
+	mapHeader.texts.registerString(modContext, UID, localized);
 	mapHeader.translations.Struct()[language].Struct()[UID.get()].String() = localized;
 	return UID.get();
 }

+ 17 - 5
lib/modding/CModHandler.cpp

@@ -401,6 +401,21 @@ CModVersion CModHandler::getModVersion(TModID modName) const
 	return {};
 }
 
+static JsonNode loadReferencesList(const JsonNode & source)
+{
+	if (source.isVector())
+	{
+		auto configList = source.convertTo<std::vector<std::string> >();
+		JsonNode result = JsonUtils::assembleFromFiles(configList);
+
+		return result;
+	}
+	else
+	{
+		return source;
+	}
+}
+
 void CModHandler::loadTranslation(const TModID & modName)
 {
 	const auto & mod = allMods[modName];
@@ -408,11 +423,8 @@ void CModHandler::loadTranslation(const TModID & 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);
+	JsonNode baseTranslation = loadReferencesList(mod.config["translations"]);
+	JsonNode extraTranslation = loadReferencesList(mod.config[preferredLanguage]["translations"]);
 
 	VLC->generaltexth->loadTranslationOverrides(modName, baseTranslation);
 	VLC->generaltexth->loadTranslationOverrides(modName, extraTranslation);

+ 51 - 39
lib/texts/TextLocalizationContainer.cpp

@@ -31,9 +31,22 @@ void TextLocalizationContainer::registerStringOverride(const std::string & modCo
 	// NOTE: implicitly creates entry, intended - strings added by maps, campaigns, vcmi and potentially - UI mods are not registered anywhere at the moment
 	auto & entry = stringsLocalizations[UID.get()];
 
-	entry.overrideValue = localized;
-	if (entry.modContext.empty())
-		entry.modContext = modContext;
+	// load string override only in following cases:
+	// a) string was not modified in another mod (e.g. rebalance mod gave skill new description)
+	// b) this string override is defined in the same mod as one that provided modified version of this string
+	if (entry.identifierModContext == entry.baseStringModContext || modContext == entry.baseStringModContext)
+	{
+		entry.translatedText = localized;
+		if (entry.identifierModContext.empty())
+		{
+			entry.identifierModContext = modContext;
+			entry.baseStringModContext = modContext;
+		}
+	}
+	else
+	{
+		logGlobal->debug("Skipping translation override for string %s: changed in a different mod", UID.get());
+	}
 }
 
 void TextLocalizationContainer::addSubContainer(const TextLocalizationContainer & container)
@@ -53,7 +66,7 @@ void TextLocalizationContainer::removeSubContainer(const TextLocalizationContain
 	subContainers.erase(std::remove(subContainers.begin(), subContainers.end(), &container), subContainers.end());
 }
 
-const std::string & TextLocalizationContainer::deserialize(const TextIdentifier & identifier) const
+const std::string & TextLocalizationContainer::translateString(const TextIdentifier & identifier) const
 {
 	std::lock_guard globalLock(globalTextMutex);
 
@@ -61,58 +74,59 @@ const std::string & TextLocalizationContainer::deserialize(const TextIdentifier
 	{
 		for(auto containerIter = subContainers.rbegin(); containerIter != subContainers.rend(); ++containerIter)
 			if((*containerIter)->identifierExists(identifier))
-				return (*containerIter)->deserialize(identifier);
+				return (*containerIter)->translateString(identifier);
 
 		logGlobal->error("Unable to find localization for string '%s'", identifier.get());
 		return identifier.get();
 	}
 
 	const auto & entry = stringsLocalizations.at(identifier.get());
-
-	if (!entry.overrideValue.empty())
-		return entry.overrideValue;
-	return entry.baseValue;
+	return entry.translatedText;
 }
 
 void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & inputUID, const JsonNode & localized)
 {
-	assert(!localized.getModScope().empty());
-	assert(!getModLanguage(localized.getModScope()).empty());
+	assert(localized.isNull() || !localized.getModScope().empty());
+	assert(localized.isNull() || !getModLanguage(localized.getModScope()).empty());
 
-	std::lock_guard globalLock(globalTextMutex);
-	registerString(modContext, inputUID, localized.String(), getModLanguage(modContext));
+	if (localized.isNull())
+		registerString(modContext, modContext, inputUID, localized.String());
+	else
+		registerString(modContext, localized.getModScope(), inputUID, localized.String());
 }
 
-void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language)
+void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized)
+{
+	registerString(modContext, modContext, UID, localized);
+}
+
+void TextLocalizationContainer::registerString(const std::string & identifierModContext, const std::string & localizedStringModContext, const TextIdentifier & UID, const std::string & localized)
 {
 	std::lock_guard globalLock(globalTextMutex);
 
-	assert(!modContext.empty());
-	assert(!Languages::getLanguageOptions(language).identifier.empty());
+	assert(!identifierModContext.empty());
+	assert(!localizedStringModContext.empty());
 	assert(UID.get().find("..") == std::string::npos); // invalid identifier - there is section that was evaluated to empty string
-	//assert(stringsLocalizations.count(UID.get()) == 0); // registering already registered string?
+	assert(stringsLocalizations.count(UID.get()) == 0 || identifierModContext == "map"); // registering already registered string?
 
 	if(stringsLocalizations.count(UID.get()) > 0)
 	{
 		auto & value = stringsLocalizations[UID.get()];
-		value.baseValue = localized;
+		value.translatedText = localized;
+		value.identifierModContext = identifierModContext;
+		value.baseStringModContext = localizedStringModContext;
 	}
 	else
 	{
 		StringState value;
-		value.baseValue = localized;
-		value.modContext = modContext;
+		value.translatedText = localized;
+		value.identifierModContext = identifierModContext;
+		value.baseStringModContext = localizedStringModContext;
 
 		stringsLocalizations[UID.get()] = value;
 	}
 }
 
-void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized)
-{
-	assert(!getModLanguage(modContext).empty());
-	registerString(modContext, UID, localized, getModLanguage(modContext));
-}
-
 void TextLocalizationContainer::loadTranslationOverrides(const std::string & modContext, const JsonNode & config)
 {
 	for(const auto & node : config.Struct())
@@ -136,17 +150,19 @@ void TextLocalizationContainer::exportAllTexts(std::map<std::string, std::map<st
 	for (auto const & entry : stringsLocalizations)
 	{
 		std::string textToWrite;
-		std::string modName = entry.second.modContext;
+		std::string modName = entry.second.baseStringModContext;
 
-		if (modName.find('.') != std::string::npos)
-			modName = modName.substr(0, modName.find('.'));
+		if (entry.second.baseStringModContext == entry.second.identifierModContext)
+		{
+			if (modName.find('.') != std::string::npos)
+				modName = modName.substr(0, modName.find('.'));
+		}
+		boost::range::replace(modName, '.', '_');
 
-		if (!entry.second.overrideValue.empty())
-			textToWrite = entry.second.overrideValue;
-		else
-			textToWrite = entry.second.baseValue;
+		textToWrite = entry.second.translatedText;
 
-		storage[modName][entry.first] = textToWrite;
+		if (!textToWrite.empty())
+			storage[modName][entry.first] = textToWrite;
 	}
 }
 
@@ -162,11 +178,7 @@ void TextLocalizationContainer::jsonSerialize(JsonNode & dest) const
 	std::lock_guard globalLock(globalTextMutex);
 
 	for(auto & s : stringsLocalizations)
-	{
-		dest.Struct()[s.first].String() = s.second.baseValue;
-		if(!s.second.overrideValue.empty())
-			dest.Struct()[s.first].String() = s.second.overrideValue;
-	}
+		dest.Struct()[s.first].String() = s.second.translatedText;
 }
 
 TextContainerRegistrable::TextContainerRegistrable()

+ 12 - 10
lib/texts/TextLocalizationContainer.h

@@ -23,20 +23,22 @@ protected:
 	struct StringState
 	{
 		/// Human-readable string that was added on registration
-		std::string baseValue;
-
-		/// Translated human-readable string
-		std::string overrideValue;
+		std::string translatedText;
 
 		/// ID of mod that created this string
-		std::string modContext;
+		std::string identifierModContext;
+
+		/// ID of mod that provides original, untranslated version of this string
+		/// Different from identifierModContext if mod has modified object from another mod (e.g. rebalance mods)
+		std::string baseStringModContext;
 
 		template <typename Handler>
 		void serialize(Handler & h)
 		{
-			h & baseValue;
+			h & translatedText;
 			//h & baseLanguage;
-			h & modContext;
+			h & identifierModContext;
+			h & baseStringModContext;
 		}
 	};
 
@@ -61,18 +63,18 @@ public:
 	/// add selected string to internal storage
 	void registerString(const std::string & modContext, const TextIdentifier & UID, const JsonNode & localized);
 	void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized);
-	void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language);
+	void registerString(const std::string & identifierModContext, const std::string & localizedStringModContext, const TextIdentifier & UID, const std::string & localized);
 
 	/// returns translated version of a string that can be displayed to user
 	template<typename  ... Args>
 	std::string translate(std::string arg1, Args ... args) const
 	{
 		TextIdentifier id(arg1, args ...);
-		return deserialize(id);
+		return translateString(id);
 	}
 
 	/// converts identifier into user-readable string
-	const std::string & deserialize(const TextIdentifier & identifier) const;
+	const std::string & translateString(const TextIdentifier & identifier) const;
 
 	/// Debug method, returns all currently stored texts
 	/// Format: [mod ID][string ID] -> human-readable text