diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index 1583e9703..12636ed35 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -183,44 +183,108 @@ void ClientCommandManager::handleNotDialogCommand() LOCPLINT->showingDialog->setn(false); } -void ClientCommandManager::handleConvertTextCommand() +void ClientCommandManager::handleTranslateGameCommand() { - logGlobal->info("Searching for available maps"); + std::map> textsByMod; + VLC->generaltexth->exportAllTexts(textsByMod); + + const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / "translation"; + boost::filesystem::create_directories(outPath); + + for(const auto & modEntry : textsByMod) + { + JsonNode output; + + for(const auto & stringEntry : modEntry.second) + { + if(boost::algorithm::starts_with(stringEntry.first, "map.")) + continue; + if(boost::algorithm::starts_with(stringEntry.first, "campaign.")) + continue; + + output[stringEntry.first].String() = stringEntry.second; + } + + if (!output.isNull()) + { + const boost::filesystem::path filePath = outPath / (modEntry.first + ".json"); + std::ofstream file(filePath.c_str()); + file << output.toString(); + } + } + + printCommandMessage("Translation export complete"); +} + +void ClientCommandManager::handleTranslateMapsCommand() +{ + CMapService mapService; + + printCommandMessage("Searching for available maps"); std::unordered_set mapList = CResourceHandler::get()->getFilteredFiles([&](const ResourcePath & ident) { return ident.getType() == EResType::MAP; }); - std::unordered_set campaignList = CResourceHandler::get()->getFilteredFiles([&](const ResourcePath & ident) - { - return ident.getType() == EResType::CAMPAIGN; - }); + std::vector> loadedMaps; + std::vector> loadedCampaigns; - CMapService mapService; - - logGlobal->info("Loading maps for export"); + printCommandMessage("Loading maps for export"); for (auto const & mapName : mapList) { try { // load and drop loaded map - we only need loader to run over all maps - mapService.loadMap(mapName, nullptr); + loadedMaps.push_back(mapService.loadMap(mapName, nullptr)); } catch(std::exception & e) { - logGlobal->error("Map %s is invalid. Message: %s", mapName.getName(), e.what()); + logGlobal->warn("Map %s is invalid. Message: %s", mapName.getName(), e.what()); } } + printCommandMessage("Searching for available campaigns"); + std::unordered_set campaignList = CResourceHandler::get()->getFilteredFiles([&](const ResourcePath & ident) + { + return ident.getType() == EResType::CAMPAIGN; + }); + logGlobal->info("Loading campaigns for export"); for (auto const & campaignName : campaignList) { - auto state = CampaignHandler::getCampaign(campaignName.getName()); - for (auto const & part : state->allScenarios()) - state->getMap(part, nullptr); + loadedCampaigns.push_back(CampaignHandler::getCampaign(campaignName.getName())); + for (auto const & part : loadedCampaigns.back()->allScenarios()) + loadedCampaigns.back()->getMap(part, nullptr); } - VLC->generaltexth->dumpAllTexts(); + std::map> textsByMod; + VLC->generaltexth->exportAllTexts(textsByMod); + + const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / "translation"; + boost::filesystem::create_directories(outPath); + + for(const auto & modEntry : textsByMod) + { + JsonNode output; + + for(const auto & stringEntry : modEntry.second) + { + if(boost::algorithm::starts_with(stringEntry.first, "map.")) + output[stringEntry.first].String() = stringEntry.second; + + if(boost::algorithm::starts_with(stringEntry.first, "campaign.")) + output[stringEntry.first].String() = stringEntry.second; + } + + if (!output.isNull()) + { + const boost::filesystem::path filePath = outPath / (modEntry.first + ".json"); + std::ofstream file(filePath.c_str()); + file << output.toString(); + } + } + + printCommandMessage("Translation export complete"); } void ClientCommandManager::handleGetConfigCommand() @@ -522,8 +586,11 @@ void ClientCommandManager::processCommand(const std::string & message, bool call else if(commandName == "not dialog") handleNotDialogCommand(); - else if(message=="convert txt") - handleConvertTextCommand(); + else if(message=="translate" || message=="translate game") + handleTranslateGameCommand(); + + else if(message=="translate maps") + handleTranslateMapsCommand(); else if(message=="get config") handleGetConfigCommand(); diff --git a/client/ClientCommandManager.h b/client/ClientCommandManager.h index 8f4100197..b146f56fc 100644 --- a/client/ClientCommandManager.h +++ b/client/ClientCommandManager.h @@ -48,8 +48,11 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a // Set the state indicating if dialog box is active to "no" void handleNotDialogCommand(); - // Dumps all game text, maps text and campaign maps text into Client log between BEGIN TEXT EXPORT and END TEXT EXPORT - void handleConvertTextCommand(); + // Extracts all translateable game texts into Translation directory, separating files on per-mod basis + void handleTranslateGameCommand(); + + // Extracts all translateable texts from maps and campaigns into Translation directory, separating files on per-mod basis + void handleTranslateMapsCommand(); // Saves current game configuration into extracted/configuration folder void handleGetConfigCommand(); diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index ba8888e1a..e7049c2c7 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -10,15 +10,16 @@ #include "StdInc.h" #include "CGeneralTextHandler.h" -#include "filesystem/Filesystem.h" -#include "serializer/JsonSerializeFormat.h" #include "CConfigHandler.h" #include "GameSettings.h" -#include "mapObjects/CQuest.h" -#include "modding/CModHandler.h" -#include "VCMI_Lib.h" #include "Languages.h" #include "TextOperations.h" +#include "VCMIDirs.h" +#include "VCMI_Lib.h" +#include "filesystem/Filesystem.h" +#include "mapObjects/CQuest.h" +#include "modding/CModHandler.h" +#include "serializer/JsonSerializeFormat.h" VCMI_LIB_NAMESPACE_BEGIN @@ -386,18 +387,26 @@ bool TextLocalizationContainer::identifierExists(const TextIdentifier & UID) con return stringsLocalizations.count(UID.get()); } -void TextLocalizationContainer::dumpAllTexts() +void TextLocalizationContainer::exportAllTexts(std::map> & storage) const { - 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)); - } + for (auto const & subContainer : subContainers) + subContainer->exportAllTexts(storage); - logGlobal->info("END TEXT EXPORT"); + for (auto const & entry : stringsLocalizations) + { + std::string textToWrite; + std::string modName = entry.second.modContext; + + if (modName.find('.') != std::string::npos) + modName = modName.substr(0, modName.find('.')); + + if (!entry.second.overrideValue.empty()) + textToWrite = entry.second.overrideValue; + else + textToWrite = entry.second.baseValue; + + storage[modName][entry.first] = textToWrite; + } } std::string TextLocalizationContainer::getModLanguage(const std::string & modContext) diff --git a/lib/CGeneralTextHandler.h b/lib/CGeneralTextHandler.h index f4fa874bf..3608b5661 100644 --- a/lib/CGeneralTextHandler.h +++ b/lib/CGeneralTextHandler.h @@ -181,8 +181,9 @@ 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(); + /// Debug method, returns all currently stored texts + /// Format: [mod ID][string ID] -> human-readable text + void exportAllTexts(std::map> & storage) const; /// Add or override subcontainer which can store identifiers void addSubContainer(const TextLocalizationContainer & container);