Working on translations for dialogs and cutscenes

This commit is contained in:
Vladislav Khorev committed 2026-07-05 21:52:37 +03:00
1 parent a98f71676d
commit 794a820be9
25 files changed
+3015 -123

No files matched your search

+18 -10
View File
@@ -15,6 +15,14 @@ void DialogueRuntime::setDatabase(const DialogueDatabase* value) {
database = value;
}
void DialogueRuntime::setTranslationDatabase(const TranslationDatabase* value) {
translations = value;
}
std::string DialogueRuntime::tr(const std::string& s) const {
return translations ? translations->translate(s, g_currentLanguage) : s;
}
bool DialogueRuntime::startDialogue(const std::string& dialogueId) {
if (!database) {
std::cerr << "[dialogue] No database assigned to runtime\n";
@@ -295,14 +303,14 @@ void DialogueRuntime::presentLine(const Node& node) {
mode = Mode::PresentingLine;
revealCharacters = 0.0f;
presentation.mode = PresentationMode::Dialogue;
presentation.speaker = node.speaker;
presentation.fullText = node.text;
presentation.speaker = tr(node.speaker);
presentation.fullText = tr(node.text);
presentation.visibleText.clear();
presentation.portraitPath = node.portrait;
presentation.choices.clear();
presentation.selectedChoice = -1;
presentation.revealCompleted = node.text.empty();
presentation.revealCompleted = presentation.fullText.empty();
presentation.showCutsceneSubtitle = false;
presentation.cutsceneSkippable = false;
@@ -311,8 +319,8 @@ void DialogueRuntime::presentLine(const Node& node) {
presentation.cutsceneBlackAlpha = 0.0f;
if (presentation.revealCompleted) {
presentation.visibleText = node.text;
revealCharacters = static_cast<float>(node.text.size());
presentation.visibleText = presentation.fullText;
revealCharacters = static_cast<float>(presentation.fullText.size());
}
applyQuestActions(node.questUnlock, node.questComplete, node.questFail,
@@ -321,7 +329,7 @@ void DialogueRuntime::presentLine(const Node& node) {
onDialogueLineStarted(node.luaCallback);
}
if (!node.chatBubble.empty() && onChatBubbleReady) {
onChatBubbleReady(node.text, node.chatBubble == "in");
onChatBubbleReady(presentation.fullText, node.chatBubble == "in");
}
}
@@ -333,7 +341,7 @@ void DialogueRuntime::presentChoices(const Node& node) {
if (!choice.id.empty() && consumedChoices.count(choice.id) > 0) continue;
if (!evaluateConditions(choice.conditions)) continue;
visibleChoices.push_back(choice);
presentation.choices.push_back({ choice.id, choice.text, choice.kind });
presentation.choices.push_back({ choice.id, tr(choice.text), choice.kind });
}
if (visibleChoices.empty()) {
@@ -349,9 +357,9 @@ void DialogueRuntime::presentChoices(const Node& node) {
mode = Mode::WaitingForChoice;
selectedChoice = -1;
presentation.mode = PresentationMode::Choice;
presentation.speaker = node.speaker;
presentation.fullText = node.text;
presentation.visibleText = node.text;
presentation.speaker = tr(node.speaker);
presentation.fullText = tr(node.text);
presentation.visibleText = presentation.fullText;
presentation.portraitPath = node.portrait;
presentation.selectedChoice = -1;
+4
View File
@@ -1,6 +1,7 @@
#pragma once
#include "dialogue/DialogueDatabase.h"
#include "dialogue/TranslationDatabase.h"
#include "quest/QuestJournal.h"
#include "external/nlohmann/json.hpp"
#include "ISaveable.h"
@@ -15,6 +16,7 @@ namespace ZL::Dialogue {
class DialogueRuntime : public ZL::ISaveable {
public:
void setDatabase(const DialogueDatabase* value);
void setTranslationDatabase(const TranslationDatabase* value);
bool startDialogue(const std::string& dialogueId);
void stop();
@@ -62,6 +64,7 @@ private:
std::function<void(const std::string&, bool)> onChatBubbleReady;
const DialogueDatabase* database = nullptr;
const TranslationDatabase* translations = nullptr;
Quest::QuestJournal* questJournal = nullptr;
const DialogueDefinition* activeDialogue = nullptr;
@@ -86,6 +89,7 @@ private:
bool enterNode(const std::string& nodeId);
void presentLine(const Node& node);
void presentChoices(const Node& node);
std::string tr(const std::string& s) const;
};
} // namespace ZL::Dialogue
+10
View File
@@ -4,7 +4,9 @@ namespace ZL::Dialogue {
bool DialogueSystem::init(Renderer& renderer, const std::string& zipFile) {
dialogueRuntime.setDatabase(&database);
dialogueRuntime.setTranslationDatabase(&translationDatabase);
cutsceneRuntime.setDatabase(&cutsceneDatabase);
cutsceneRuntime.setTranslationDatabase(&cutsceneTranslationDatabase);
// When dialogue hits a CutsceneStart node, hand off to cutsceneRuntime.
dialogueRuntime.setOnCutsceneStartNeeded([this](const std::string& cutsceneId, const std::string& nextNodeId) {
@@ -30,6 +32,14 @@ bool DialogueSystem::loadCutsceneDatabase(const std::string& path) {
return cutsceneDatabase.loadFromFile(path);
}
bool DialogueSystem::loadTranslations(const std::string& path) {
return translationDatabase.loadFromFile(path);
}
bool DialogueSystem::loadCutsceneTranslations(const std::string& path) {
return cutsceneTranslationDatabase.loadFromFile(path);
}
void DialogueSystem::update(int deltaMs) {
dialogueRuntime.update(deltaMs);
cutsceneRuntime.update(deltaMs);
+4
View File
@@ -20,6 +20,8 @@ public:
bool loadDatabase(const std::string& path);
bool loadCutsceneDatabase(const std::string& path);
bool loadTranslations(const std::string& path);
bool loadCutsceneTranslations(const std::string& path);
void update(int deltaMs);
void draw(Renderer& renderer);
@@ -64,10 +66,12 @@ public:
private:
DialogueDatabase database;
TranslationDatabase translationDatabase;
DialogueRuntime dialogueRuntime;
DialogueOverlay dialogueOverlay;
ZL::Cutscene::CutsceneDatabase cutsceneDatabase;
TranslationDatabase cutsceneTranslationDatabase;
ZL::Cutscene::CutsceneRuntime cutsceneRuntime;
ZL::Cutscene::CutsceneOverlay cutsceneOverlay;
+80
View File
@@ -0,0 +1,80 @@
#include "dialogue/TranslationDatabase.h"
#include "utils/Utils.h"
#include <iostream>
namespace ZL
{
extern const char* CONST_ZIP_FILE;
}
namespace ZL::Dialogue {
bool TranslationDatabase::loadFromFile(const std::string& path) {
entries.clear();
std::string raw;
try {
if (strlen(CONST_ZIP_FILE) == 0) {
raw = readTextFile(path);
}
else {
auto buf = readFileFromZIP(path, CONST_ZIP_FILE);
if (buf.empty()) {
std::cerr << "[translation] Failed to read " << path << " from zip\n";
return false;
}
raw.assign(buf.begin(), buf.end());
}
}
catch (const std::exception& e) {
std::cerr << "[translation] Failed to open " << path << ": " << e.what() << "\n";
return false;
}
json root;
try {
root = json::parse(raw);
}
catch (const std::exception& e) {
std::cerr << "[translation] JSON parse error in " << path << ": " << e.what() << "\n";
return false;
}
if (root.contains("dialogue_translations") && root["dialogue_translations"].is_array()) {
for (const auto& item : root["dialogue_translations"]) {
const std::string key = item.value("key", "");
if (key.empty()) continue;
std::unordered_map<std::string, std::string> langMap;
for (auto it = item.begin(); it != item.end(); ++it) {
if (it.key() == "key") continue;
if (it.value().is_string()) {
langMap[it.key()] = it.value().get<std::string>();
}
}
entries[key] = std::move(langMap);
}
}
return !entries.empty();
}
std::string TranslationDatabase::translate(const std::string& key, Language lang) const {
if (key.empty()) return key;
auto it = entries.find(key);
if (it == entries.end()) return key;
const auto& langMap = it->second;
auto langIt = langMap.find(languageToCode(lang));
if (langIt != langMap.end() && !langIt->second.empty()) return langIt->second;
auto ruIt = langMap.find("ru");
if (ruIt != langMap.end() && !ruIt->second.empty()) return ruIt->second;
return key;
}
} // namespace ZL::Dialogue
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include "Localization.h"
#include "external/nlohmann/json.hpp"
#include <string>
#include <unordered_map>
namespace ZL::Dialogue {
// Loads a shared translation file mapping dialogue "key" strings (the literal
// speaker/text/choice text found in dialogue config files) to per-language text.
class TranslationDatabase {
public:
using json = nlohmann::json;
bool loadFromFile(const std::string& path);
// Returns the translated text for `key` in `lang`, falling back to Russian,
// then to `key` itself if no entry or translation exists.
std::string translate(const std::string& key, Language lang) const;
private:
std::unordered_map<std::string, std::unordered_map<std::string, std::string>> entries;
};
} // namespace ZL::Dialogue