diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index 3e54d7cf7..6188a7941 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -247,22 +247,38 @@ bool CLegacyConfigParser::endLine() return curr < end; } -void CGeneralTextHandler::readToVector(const std::string & sourceID, const std::string & sourceName) +void TextLocalizationContainer::registerStringOverride(const std::string & modContext, const std::string & language, const TextIdentifier & UID, const std::string & localized) { - CLegacyConfigParser parser(TextPath::builtin(sourceName)); - size_t index = 0; - do - { - registerString( "core", {sourceID, index}, parser.readString()); - index += 1; - } - while (parser.endLine()); + assert(!modContext.empty()); + assert(!language.empty()); + + // 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.overrideLanguage = language; + entry.overrideValue = localized; + if (entry.modContext.empty()) + entry.modContext = modContext; } -const std::string & CGeneralTextHandler::deserialize(const TextIdentifier & identifier) const +void TextLocalizationContainer::addSubContainer(const TextLocalizationContainer & container) +{ + subContainers.insert(&container); +} + +void TextLocalizationContainer::removeSubContainer(const TextLocalizationContainer & container) +{ + subContainers.erase(&container); +} + +const std::string & TextLocalizationContainer::deserialize(const TextIdentifier & identifier) const { if(stringsLocalizations.count(identifier.get()) == 0) { + for(const auto * container : subContainers) + if(container->identifierExists(identifier)) + return container->deserialize(identifier); + logGlobal->error("Unable to find localization for string '%s'", identifier.get()); return identifier.get(); } @@ -274,7 +290,7 @@ const std::string & CGeneralTextHandler::deserialize(const TextIdentifier & iden return entry.baseValue; } -void CGeneralTextHandler::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized) +void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized) { assert(!modContext.empty()); assert(!getModLanguage(modContext).empty()); @@ -307,21 +323,7 @@ void CGeneralTextHandler::registerString(const std::string & modContext, const T } } -void CGeneralTextHandler::registerStringOverride(const std::string & modContext, const std::string & language, const TextIdentifier & UID, const std::string & localized) -{ - assert(!modContext.empty()); - assert(!language.empty()); - - // 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.overrideLanguage = language; - entry.overrideValue = localized; - if (entry.modContext.empty()) - entry.modContext = modContext; -} - -bool CGeneralTextHandler::validateTranslation(const std::string & language, const std::string & modContext, const JsonNode & config) const +bool TextLocalizationContainer::validateTranslation(const std::string & language, const std::string & modContext, const JsonNode & config) const { bool allPresent = true; @@ -372,12 +374,50 @@ bool CGeneralTextHandler::validateTranslation(const std::string & language, cons return allPresent && allFound; } -void CGeneralTextHandler::loadTranslationOverrides(const std::string & language, const std::string & modContext, const JsonNode & config) +void TextLocalizationContainer::loadTranslationOverrides(const std::string & language, const std::string & modContext, const JsonNode & config) { for(const auto & node : config.Struct()) registerStringOverride(modContext, language, node.first, node.second.String()); } +bool TextLocalizationContainer::identifierExists(const TextIdentifier & UID) const +{ + return stringsLocalizations.count(UID.get()); +} + +void TextLocalizationContainer::dumpAllTexts() +{ + logGlobal->info("BEGIN TEXT EXPORT"); + for(const auto & entry : stringsLocalizations) + { + if (!entry.second.overrideValue.empty()) + logGlobal->info(R"("%s" : "%s",)", entry.first, TextOperations::escapeString(entry.second.overrideValue)); + else + logGlobal->info(R"("%s" : "%s",)", entry.first, TextOperations::escapeString(entry.second.baseValue)); + } + + logGlobal->info("END TEXT EXPORT"); +} + +std::string TextLocalizationContainer::getModLanguage(const std::string & modContext) +{ + if (modContext == "core") + return CGeneralTextHandler::getInstalledLanguage(); + return VLC->modh->getModLanguage(modContext); +} + +void CGeneralTextHandler::readToVector(const std::string & sourceID, const std::string & sourceName) +{ + CLegacyConfigParser parser(TextPath::builtin(sourceName)); + size_t index = 0; + do + { + registerString( "core", {sourceID, index}, parser.readString()); + index += 1; + } + while (parser.endLine()); +} + CGeneralTextHandler::CGeneralTextHandler(): victoryConditions(*this, "core.vcdesc" ), lossCondtions (*this, "core.lcdesc" ), @@ -591,20 +631,6 @@ int32_t CGeneralTextHandler::pluralText(const int32_t textIndex, const int32_t c return textIndex + 1; } -void CGeneralTextHandler::dumpAllTexts() -{ - logGlobal->info("BEGIN TEXT EXPORT"); - for(const auto & entry : stringsLocalizations) - { - if (!entry.second.overrideValue.empty()) - logGlobal->info(R"("%s" : "%s",)", entry.first, TextOperations::escapeString(entry.second.overrideValue)); - else - logGlobal->info(R"("%s" : "%s",)", entry.first, TextOperations::escapeString(entry.second.baseValue)); - } - - logGlobal->info("END TEXT EXPORT"); -} - size_t CGeneralTextHandler::getCampaignLength(size_t campaignID) const { assert(campaignID < scenariosCountPerCampaign.size()); @@ -614,13 +640,6 @@ size_t CGeneralTextHandler::getCampaignLength(size_t campaignID) const return 0; } -std::string CGeneralTextHandler::getModLanguage(const std::string & modContext) -{ - if (modContext == "core") - return getInstalledLanguage(); - return VLC->modh->getModLanguage(modContext); -} - std::string CGeneralTextHandler::getPreferredLanguage() { assert(!settings["general"]["language"].String().empty()); diff --git a/lib/CGeneralTextHandler.h b/lib/CGeneralTextHandler.h index 6bbbc98c1..82b68e66b 100644 --- a/lib/CGeneralTextHandler.h +++ b/lib/CGeneralTextHandler.h @@ -113,9 +113,9 @@ public: {} }; -/// Handles all text-related data in game -class DLL_LINKAGE CGeneralTextHandler +class DLL_LINKAGE TextLocalizationContainer { +protected: struct StringState { /// Human-readable string that was added on registration @@ -132,21 +132,26 @@ class DLL_LINKAGE CGeneralTextHandler /// ID of mod that created this string std::string modContext; + + template + void serialize(Handler & h, const int Version) + { + h & baseValue; + h & baseLanguage; + h & modContext; + } }; - + /// map identifier -> localization std::unordered_map stringsLocalizations; - - void readToVector(const std::string & sourceID, const std::string & sourceName); - - /// number of scenarios in specific campaign. TODO: move to a better location - std::vector scenariosCountPerCampaign; - - std::string getModLanguage(const std::string & modContext); - + + std::set subContainers; + /// add selected string to internal storage as high-priority strings void registerStringOverride(const std::string & modContext, const std::string & language, const TextIdentifier & UID, const std::string & localized); - + + std::string getModLanguage(const std::string & modContext); + public: /// validates translation of specified language for specified mod /// returns true if localization is valid and complete @@ -157,13 +162,12 @@ public: /// Any entries loaded by this will have priority over texts registered normally void loadTranslationOverrides(const std::string & language, const std::string & modContext, JsonNode const & file); + // returns true if identifier with such name was registered, even if not translated to current language + bool identifierExists(const TextIdentifier & UID) const; + /// add selected string to internal storage void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized); - - // returns true if identifier with such name was registered, even if not translated to current language - // not required right now, can be added if necessary - // bool identifierExists( const std::string identifier) const; - + /// returns translated version of a string that can be displayed to user template std::string translate(std::string arg1, Args ... args) const @@ -174,10 +178,51 @@ public: /// converts identifier into user-readable string const std::string & deserialize(const TextIdentifier & identifier) const; - + /// Debug method, dumps all currently known texts into console using Json-like format void dumpAllTexts(); + + /// Add or override subcontainer which can store identifiers + void addSubContainer(const TextLocalizationContainer & container); + + /// Remove subcontainer with give name + void removeSubContainer(const TextLocalizationContainer & container); + + template + void serialize(Handler & h, const int Version) + { + std::string key; + auto sz = stringsLocalizations.size(); + h & sz; + if(h.saving) + { + for(auto s : stringsLocalizations) + { + key = s.first; + h & key; + h & s.second; + } + } + else + { + for(size_t i = 0; i < sz; ++i) + { + h & key; + h & stringsLocalizations[key]; + } + } + } +}; +/// Handles all text-related data in game +class DLL_LINKAGE CGeneralTextHandler: public TextLocalizationContainer +{ + void readToVector(const std::string & sourceID, const std::string & sourceName); + + /// number of scenarios in specific campaign. TODO: move to a better location + std::vector scenariosCountPerCampaign; + +public: LegacyTextContainer allTexts; LegacyTextContainer arraytxt; diff --git a/lib/mapping/CMapHeader.cpp b/lib/mapping/CMapHeader.cpp index 9d7cd718a..345974bc2 100644 --- a/lib/mapping/CMapHeader.cpp +++ b/lib/mapping/CMapHeader.cpp @@ -127,6 +127,12 @@ CMapHeader::CMapHeader() : version(EMapFormat::VCMI), height(72), width(72), setupEvents(); allowedHeroes = VLC->heroh->getDefaultAllowed(); players.resize(PlayerColor::PLAYER_LIMIT_I); + VLC->generaltexth->addSubContainer(*this); +} + +CMapHeader::~CMapHeader() +{ + VLC->generaltexth->removeSubContainer(*this); } ui8 CMapHeader::levels() const diff --git a/lib/mapping/CMapHeader.h b/lib/mapping/CMapHeader.h index 9866e095d..5250d575b 100644 --- a/lib/mapping/CMapHeader.h +++ b/lib/mapping/CMapHeader.h @@ -14,6 +14,7 @@ #include "../LogicalExpression.h" #include "../int3.h" #include "../MetaString.h" +#include "../CGeneralTextHandler.h" VCMI_LIB_NAMESPACE_BEGIN @@ -199,7 +200,7 @@ struct DLL_LINKAGE TriggeredEvent }; /// The map header holds information about loss/victory condition,map format, version, players, height, width,... -class DLL_LINKAGE CMapHeader +class DLL_LINKAGE CMapHeader: public TextLocalizationContainer { void setupEvents(); public: @@ -213,7 +214,7 @@ public: static const int MAP_SIZE_GIANT = 252; CMapHeader(); - virtual ~CMapHeader() = default; + virtual ~CMapHeader(); ui8 levels() const; @@ -248,6 +249,7 @@ public: template void serialize(Handler & h, const int Version) { + h & static_cast(*this); h & version; h & mods; h & name; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index a9e42b9db..5b44974ef 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -2274,7 +2274,7 @@ std::string CMapLoaderH3M::readLocalizedString(const TextIdentifier & stringIden if(mapString.empty()) return ""; - VLC->generaltexth->registerString(modName, fullIdentifier, mapString); + mapHeader->registerString(modName, fullIdentifier, mapString); return fullIdentifier.get(); }