1
0
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:
Ivan Savenko
2024-09-30 17:07:54 +00:00
parent b85ccccb37
commit 1488629628
5 changed files with 97 additions and 70 deletions

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -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()

View File

@@ -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