mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Add simple support for translation of strings that were changed by
another mod
This commit is contained in:
		| @@ -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); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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(); | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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,56 +74,57 @@ 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)); | ||||
| } | ||||
|  | ||||
| void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language) | ||||
| { | ||||
| 	std::lock_guard globalLock(globalTextMutex); | ||||
|  | ||||
| 	assert(!modContext.empty()); | ||||
| 	assert(!Languages::getLanguageOptions(language).identifier.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? | ||||
|  | ||||
| 	if(stringsLocalizations.count(UID.get()) > 0) | ||||
| 	{ | ||||
| 		auto & value = stringsLocalizations[UID.get()]; | ||||
| 		value.baseValue = localized; | ||||
| 	} | ||||
| 	if (localized.isNull()) | ||||
| 		registerString(modContext, modContext, inputUID, localized.String()); | ||||
| 	else | ||||
| 	{ | ||||
| 		StringState value; | ||||
| 		value.baseValue = localized; | ||||
| 		value.modContext = modContext; | ||||
|  | ||||
| 		stringsLocalizations[UID.get()] = value; | ||||
| 	} | ||||
| 		registerString(modContext, localized.getModScope(), inputUID, localized.String()); | ||||
| } | ||||
|  | ||||
| void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized) | ||||
| { | ||||
| 	assert(!getModLanguage(modContext).empty()); | ||||
| 	registerString(modContext, UID, localized, getModLanguage(modContext)); | ||||
| 	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(!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 || identifierModContext == "map"); // registering already registered string? | ||||
|  | ||||
| 	if(stringsLocalizations.count(UID.get()) > 0) | ||||
| 	{ | ||||
| 		auto & value = stringsLocalizations[UID.get()]; | ||||
| 		value.translatedText = localized; | ||||
| 		value.identifierModContext = identifierModContext; | ||||
| 		value.baseStringModContext = localizedStringModContext; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		StringState value; | ||||
| 		value.translatedText = localized; | ||||
| 		value.identifierModContext = identifierModContext; | ||||
| 		value.baseStringModContext = localizedStringModContext; | ||||
|  | ||||
| 		stringsLocalizations[UID.get()] = value; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void TextLocalizationContainer::loadTranslationOverrides(const std::string & modContext, const JsonNode & config) | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user