TextLocalizationContainer.cpp 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /*
  2. * TextLocalizationContainer.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "TextLocalizationContainer.h"
  12. #include "texts/CGeneralTextHandler.h"
  13. #include "Languages.h"
  14. #include "TextOperations.h"
  15. #include "../VCMI_Lib.h"
  16. #include "../json/JsonNode.h"
  17. #include "../modding/CModHandler.h"
  18. VCMI_LIB_NAMESPACE_BEGIN
  19. std::recursive_mutex TextLocalizationContainer::globalTextMutex;
  20. void TextLocalizationContainer::registerStringOverride(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language)
  21. {
  22. std::lock_guard globalLock(globalTextMutex);
  23. assert(!modContext.empty());
  24. // NOTE: implicitly creates entry, intended - strings added by maps, campaigns, vcmi and potentially - UI mods are not registered anywhere at the moment
  25. auto & entry = stringsLocalizations[UID.get()];
  26. // load string override only in following cases:
  27. // a) string was not modified in another mod (e.g. rebalance mod gave skill new description)
  28. // b) this string override is defined in the same mod as one that provided modified version of this string
  29. if (entry.identifierModContext == entry.baseStringModContext || modContext == entry.baseStringModContext)
  30. {
  31. entry.translatedText = localized;
  32. if (entry.identifierModContext.empty())
  33. {
  34. entry.identifierModContext = modContext;
  35. entry.baseStringModContext = modContext;
  36. }
  37. else
  38. {
  39. if (language == VLC->generaltexth->getPreferredLanguage())
  40. entry.overriden = true;
  41. }
  42. }
  43. else
  44. {
  45. logGlobal->debug("Skipping translation override for string %s: changed in a different mod", UID.get());
  46. }
  47. }
  48. void TextLocalizationContainer::addSubContainer(const TextLocalizationContainer & container)
  49. {
  50. std::lock_guard globalLock(globalTextMutex);
  51. assert(!vstd::contains(subContainers, &container));
  52. subContainers.push_back(&container);
  53. }
  54. void TextLocalizationContainer::removeSubContainer(const TextLocalizationContainer & container)
  55. {
  56. std::lock_guard globalLock(globalTextMutex);
  57. assert(vstd::contains(subContainers, &container));
  58. subContainers.erase(std::remove(subContainers.begin(), subContainers.end(), &container), subContainers.end());
  59. }
  60. const std::string & TextLocalizationContainer::translateString(const TextIdentifier & identifier) const
  61. {
  62. std::lock_guard globalLock(globalTextMutex);
  63. if(stringsLocalizations.count(identifier.get()) == 0)
  64. {
  65. for(auto containerIter = subContainers.rbegin(); containerIter != subContainers.rend(); ++containerIter)
  66. if((*containerIter)->identifierExists(identifier))
  67. return (*containerIter)->translateString(identifier);
  68. logGlobal->error("Unable to find localization for string '%s'", identifier.get());
  69. return identifier.get();
  70. }
  71. const auto & entry = stringsLocalizations.at(identifier.get());
  72. return entry.translatedText;
  73. }
  74. void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & inputUID, const JsonNode & localized)
  75. {
  76. assert(localized.isNull() || !localized.getModScope().empty());
  77. assert(localized.isNull() || !getModLanguage(localized.getModScope()).empty());
  78. if (localized.isNull())
  79. registerString(modContext, modContext, inputUID, localized.String());
  80. else
  81. registerString(modContext, localized.getModScope(), inputUID, localized.String());
  82. }
  83. void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized)
  84. {
  85. registerString(modContext, modContext, UID, localized);
  86. }
  87. void TextLocalizationContainer::registerString(const std::string & identifierModContext, const std::string & localizedStringModContext, const TextIdentifier & UID, const std::string & localized)
  88. {
  89. std::lock_guard globalLock(globalTextMutex);
  90. assert(!identifierModContext.empty());
  91. assert(!localizedStringModContext.empty());
  92. assert(UID.get().find("..") == std::string::npos); // invalid identifier - there is section that was evaluated to empty string
  93. assert(stringsLocalizations.count(UID.get()) == 0 || boost::algorithm::starts_with(UID.get(), "map") || boost::algorithm::starts_with(UID.get(), "header")); // registering already registered string? FIXME: "header" is a workaround. VMAP needs proper integration in translation system
  94. if(stringsLocalizations.count(UID.get()) > 0)
  95. {
  96. auto & value = stringsLocalizations[UID.get()];
  97. value.translatedText = localized;
  98. value.identifierModContext = identifierModContext;
  99. value.baseStringModContext = localizedStringModContext;
  100. }
  101. else
  102. {
  103. StringState value;
  104. value.translatedText = localized;
  105. value.identifierModContext = identifierModContext;
  106. value.baseStringModContext = localizedStringModContext;
  107. stringsLocalizations[UID.get()] = value;
  108. }
  109. }
  110. void TextLocalizationContainer::loadTranslationOverrides(const std::string & modContext, const std::string & language, const JsonNode & config)
  111. {
  112. for(const auto & node : config.Struct())
  113. registerStringOverride(modContext, node.first, node.second.String(), language);
  114. }
  115. bool TextLocalizationContainer::identifierExists(const TextIdentifier & UID) const
  116. {
  117. std::lock_guard globalLock(globalTextMutex);
  118. return stringsLocalizations.count(UID.get());
  119. }
  120. void TextLocalizationContainer::exportAllTexts(std::map<std::string, std::map<std::string, std::string>> & storage, bool onlyMissing) const
  121. {
  122. std::lock_guard globalLock(globalTextMutex);
  123. for (auto const & subContainer : subContainers)
  124. subContainer->exportAllTexts(storage, onlyMissing);
  125. for (auto const & entry : stringsLocalizations)
  126. {
  127. if (onlyMissing && entry.second.overriden)
  128. continue;
  129. std::string textToWrite;
  130. std::string modName = entry.second.baseStringModContext;
  131. if (entry.second.baseStringModContext == entry.second.identifierModContext && modName.find('.') != std::string::npos)
  132. modName = modName.substr(0, modName.find('.'));
  133. boost::range::replace(modName, '.', '_');
  134. textToWrite = entry.second.translatedText;
  135. if (!textToWrite.empty())
  136. storage[modName][entry.first] = textToWrite;
  137. }
  138. }
  139. std::string TextLocalizationContainer::getModLanguage(const std::string & modContext)
  140. {
  141. if (modContext == "core")
  142. return CGeneralTextHandler::getInstalledLanguage();
  143. return VLC->modh->getModLanguage(modContext);
  144. }
  145. void TextLocalizationContainer::jsonSerialize(JsonNode & dest) const
  146. {
  147. std::lock_guard globalLock(globalTextMutex);
  148. for(auto & s : stringsLocalizations)
  149. dest.Struct()[s.first].String() = s.second.translatedText;
  150. }
  151. TextContainerRegistrable::TextContainerRegistrable()
  152. {
  153. VLC->generaltexth->addSubContainer(*this);
  154. }
  155. TextContainerRegistrable::~TextContainerRegistrable()
  156. {
  157. VLC->generaltexth->removeSubContainer(*this);
  158. }
  159. TextContainerRegistrable::TextContainerRegistrable(const TextContainerRegistrable & other)
  160. : TextLocalizationContainer(other)
  161. {
  162. VLC->generaltexth->addSubContainer(*this);
  163. }
  164. TextContainerRegistrable::TextContainerRegistrable(TextContainerRegistrable && other) noexcept
  165. :TextLocalizationContainer(other)
  166. {
  167. VLC->generaltexth->addSubContainer(*this);
  168. }
  169. VCMI_LIB_NAMESPACE_END