mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-06-04 02:16:03 -04:00
implement framework for custom text in other languages
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
#include "d/d_msg_class.h"
|
||||
#include "randomizer_context.hpp"
|
||||
|
||||
#include "dusk/version.hpp"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
// Format certain messages that need to have dynamic info in them
|
||||
@@ -40,6 +42,17 @@ char* GetFormatedTextOverride(u32 key, std::string& text) {
|
||||
return buf.data();
|
||||
}
|
||||
|
||||
u8 getLanguageForOverride() {
|
||||
u8 language = randomizer::Text::ENGLISH;
|
||||
if (dusk::version::isRegionPal()) {
|
||||
language = dComIfGs_getPalLanguage();
|
||||
}/* else if (dusk::version::isRegionJpn()) {
|
||||
language = randomizer::Text::JAPANESE;
|
||||
}*/
|
||||
|
||||
return language;
|
||||
}
|
||||
|
||||
void HandleTextOverrides(JMessage::TControl* control, JMessage::TProcessor const* pProcessor, int groupID, int index) {
|
||||
if (randomizer_IsActive()) {
|
||||
// Get the entry for this message
|
||||
@@ -58,8 +71,9 @@ void HandleTextOverrides(JMessage::TControl* control, JMessage::TProcessor const
|
||||
|
||||
u32 key = (group << 16) | msgId;
|
||||
auto& textOverrides = randomizer_GetContext().mTextOverrides;
|
||||
if (textOverrides.contains(key)) {
|
||||
control->pMessageText_begin_ = GetFormatedTextOverride(key, textOverrides[key]);
|
||||
u8 language = getLanguageForOverride();
|
||||
if (textOverrides.at(language).contains(key)) {
|
||||
control->pMessageText_begin_ = GetFormatedTextOverride(key, textOverrides[language][key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,8 +82,9 @@ void HandleTextOverrides(JMessage::TControl* control, JMessage::TProcessor const
|
||||
char* GetTextOverride(s16 groupID, u32 messageId) {
|
||||
u32 key = (groupID << 16) | messageId;
|
||||
auto& textOverrides = randomizer_GetContext().mTextOverrides;
|
||||
if (textOverrides.contains(key)) {
|
||||
return GetFormatedTextOverride(key, textOverrides[key]);
|
||||
u8 language = getLanguageForOverride();
|
||||
if (textOverrides.at(language).contains(key)) {
|
||||
return GetFormatedTextOverride(key, textOverrides[language][key]);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,9 +107,15 @@ std::optional<std::string> RandomizerContext::WriteToFile() {
|
||||
textData << YAML::BeginMap;
|
||||
textData << YAML::Key << "mTextOverrides";
|
||||
textData << YAML::BeginMap;
|
||||
for (const auto& [key, text] : this->mTextOverrides) {
|
||||
textData << YAML::Key << key;
|
||||
textData << YAML::Value << YAML::Binary(reinterpret_cast<const unsigned char*>(text.data()), text.size());
|
||||
for (auto language : randomizer::supportedLanguages) {
|
||||
auto languageStr = randomizer::languageToString(language);
|
||||
textData << YAML::Key << languageStr;
|
||||
textData << YAML::BeginMap;
|
||||
for (const auto& [key, text] : this->mTextOverrides[language]) {
|
||||
textData << YAML::Key << key;
|
||||
textData << YAML::Value << YAML::Binary(reinterpret_cast<const unsigned char*>(text.data()), text.size());
|
||||
}
|
||||
textData << YAML::EndMap;
|
||||
}
|
||||
textData << YAML::EndMap;
|
||||
textData << YAML::EndMap;
|
||||
@@ -254,11 +260,15 @@ std::optional<std::string> RandomizerContext::LoadFromHash(const std::string& ha
|
||||
}
|
||||
|
||||
// Text Overrides
|
||||
for (const auto& textNode: in["mTextOverrides"]) {
|
||||
auto key = textNode.first.as<u32>();
|
||||
auto binary = textNode.second.as<YAML::Binary>();
|
||||
std::string text(reinterpret_cast<const char*>(binary.data()), binary.size());
|
||||
this->mTextOverrides[key] = std::move(text);
|
||||
for (const auto& languageNode: in["mTextOverrides"]) {
|
||||
const auto& languageStr = languageNode.first.as<std::string>();
|
||||
auto language = randomizer::stringToLanguage(languageStr);
|
||||
for (const auto& textNode : languageNode.second) {
|
||||
auto key = textNode.first.as<u32>();
|
||||
auto binary = textNode.second.as<YAML::Binary>();
|
||||
std::string text(reinterpret_cast<const char*>(binary.data()), binary.size());
|
||||
this->mTextOverrides[language][key] = std::move(text);
|
||||
}
|
||||
}
|
||||
|
||||
DuskLog.debug("Loaded Randomizer Seed {}", this->mHash);
|
||||
@@ -1268,19 +1278,19 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) {
|
||||
auto textOverrides = LOAD_EMBED_YAML(RANDO_DATA_PATH "text/text_overrides.yaml");
|
||||
for (const auto& overrideNode : textOverrides) {
|
||||
const auto& name = overrideNode["Name"].as<std::string>();
|
||||
// TODO: Handle multiple languages
|
||||
auto language = randomizer::Text::ENGLISH;
|
||||
std::string text;
|
||||
if (world->GetTextDatabase().contains(name)) {
|
||||
text = world->GetText(name);
|
||||
} else {
|
||||
text = randomizer::getTextStr(name);
|
||||
for (auto language : randomizer::supportedLanguages) {
|
||||
std::string text;
|
||||
if (world->GetTextDatabase().contains(name)) {
|
||||
text = world->GetText(name, randomizer::Text::STANDARD, language);
|
||||
} else {
|
||||
text = randomizer::getTextStr(name, randomizer::Text::STANDARD, language);
|
||||
}
|
||||
u8 group = overrideNode["Group"].as<u8>();
|
||||
u16 messageId = overrideNode["Message Id"].as<u16>();
|
||||
u32 key = (group << 16) | messageId;
|
||||
randomizer::applyMessageCodes(text);
|
||||
randoData.mTextOverrides[language][key] = text;
|
||||
}
|
||||
u8 group = overrideNode["Group"].as<u8>();
|
||||
u16 messageId = overrideNode["Message Id"].as<u16>();
|
||||
u32 key = (group << 16) | messageId;
|
||||
randomizer::applyMessageCodes(text);
|
||||
randoData.mTextOverrides[key] = text;
|
||||
}
|
||||
|
||||
return std::move(randoData);
|
||||
|
||||
@@ -66,7 +66,8 @@ public:
|
||||
// std::array<u8, 16> mAttributes{};
|
||||
// std::string mText{};
|
||||
// };
|
||||
std::unordered_map<u32, std::string> mTextOverrides{};
|
||||
// Map of language -> map of key -> string
|
||||
std::unordered_map<int, std::unordered_map<u32, std::string>> mTextOverrides{};
|
||||
|
||||
// TODO: hook this up to generator data
|
||||
struct {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -151,8 +151,12 @@ namespace randomizer::logic::world
|
||||
seedgen::settings::Setting& Setting(const std::string& settingName);
|
||||
|
||||
TextDatabase& GetTextDatabase() { return this->_textDatabase; }
|
||||
const std::string& GetText(const std::string& name) {
|
||||
return this->_textDatabase.at(name).at(Text::Type::STANDARD).mText.at(Text::Language::ENGLISH);
|
||||
const std::string& GetText(const std::string& name, Text::Type type = Text::STANDARD, Text::Language language = Text::ENGLISH) {
|
||||
if (!this->_textDatabase.at(name).at(type).mText.at(language).empty()) {
|
||||
return this->_textDatabase.at(name).at(type).mText.at(language);
|
||||
}
|
||||
|
||||
return this->_textDatabase.at(name).at(type).mText.at(Text::ENGLISH);
|
||||
}
|
||||
// Make a new custom text entry for this world specifically and return a reference to it
|
||||
Text& AddNewText(const std::string& name, Text::Type type = Text::STANDARD) {
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
#include "yaml.hpp"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
namespace randomizer {
|
||||
|
||||
// std::array<std::string, 3> supported_languages = {"English", "Spanish", "French"};
|
||||
@@ -212,7 +215,7 @@ namespace randomizer {
|
||||
return Text(lhs) + rhs;
|
||||
}
|
||||
|
||||
Text::Type string_to_type(const std::string& str) {
|
||||
Text::Type stringToType(const std::string& str) {
|
||||
std::unordered_map<std::string, Text::Type> strToType = {
|
||||
{"Standard", Text::Type::STANDARD},
|
||||
{"Pretty", Text::Type::PRETTY},
|
||||
@@ -227,9 +230,14 @@ namespace randomizer {
|
||||
throw std::runtime_error("Text type \"" + str + "\" is not recognized.");
|
||||
}
|
||||
|
||||
Text::Language string_to_language(const std::string& str) {
|
||||
Text::Language stringToLanguage(const std::string& str) {
|
||||
std::unordered_map<std::string, Text::Language> strToLanguage = {
|
||||
{"english", Text::Language::ENGLISH},
|
||||
{"english", Text::ENGLISH},
|
||||
{"spanish", Text::SPANISH},
|
||||
{"french", Text::FRENCH},
|
||||
{"german", Text::GERMAN},
|
||||
{"italian", Text::ITALIAN},
|
||||
{"japanese", Text::JAPANESE}
|
||||
};
|
||||
|
||||
if (strToLanguage.contains(str))
|
||||
@@ -240,7 +248,27 @@ namespace randomizer {
|
||||
throw std::runtime_error("Language \"" + str + "\" is not recognized.");
|
||||
}
|
||||
|
||||
Text::Gender string_to_gender(const std::string& str)
|
||||
|
||||
std::string languageToString(Text::Language language) {
|
||||
switch (language) {
|
||||
case Text::ENGLISH:
|
||||
return "english";
|
||||
case Text::SPANISH:
|
||||
return "spanish";
|
||||
case Text::FRENCH:
|
||||
return "french";
|
||||
case Text::GERMAN:
|
||||
return "german";
|
||||
case Text::ITALIAN:
|
||||
return "italian";
|
||||
case Text::JAPANESE:
|
||||
return "japanese";
|
||||
default:
|
||||
return "unknown language enum";
|
||||
}
|
||||
}
|
||||
|
||||
Text::Gender stringToGender(const std::string& str)
|
||||
{
|
||||
std::unordered_map<std::string, Text::Gender> strToGender = {
|
||||
{"Masculine", Text::Gender::MASCULINE},
|
||||
@@ -255,12 +283,47 @@ namespace randomizer {
|
||||
return Text::Gender::NUETRAL;
|
||||
}
|
||||
|
||||
Text::Plurality string_to_plurality(const std::string& str)
|
||||
Text::Plurality stringToPlurality(const std::string& str)
|
||||
{
|
||||
if (str == "Plural") return Text::Plurality::PLURAL;
|
||||
return Text::Plurality::SINGULAR;
|
||||
}
|
||||
|
||||
std::string UTF8ToLatin1(const std::string& utf8Str) {
|
||||
std::string latin1Str;
|
||||
// The output string will be equal to or shorter than the UTF-8 string
|
||||
latin1Str.reserve(utf8Str.length());
|
||||
|
||||
size_t read_pos = 0;
|
||||
size_t len = utf8Str.length();
|
||||
|
||||
while (read_pos < len) {
|
||||
unsigned char c = utf8Str[read_pos];
|
||||
|
||||
if (c < 0x80) {
|
||||
// Standard ASCII (0x00 - 0x7F)
|
||||
latin1Str.push_back(c);
|
||||
++read_pos;
|
||||
}
|
||||
else if ((c & 0xE0) == 0xC0 && (read_pos + 1 < len)) {
|
||||
// Two-byte UTF-8 sequence (0xC0 - 0xDF)
|
||||
unsigned char next_byte = utf8Str[read_pos + 1];
|
||||
|
||||
// Reconstruct the Latin-1 character value
|
||||
unsigned char latin1_char = ((c & 0x1F) << 6) | (next_byte & 0x3F);
|
||||
|
||||
latin1Str.push_back(latin1_char);
|
||||
read_pos += 2;
|
||||
}
|
||||
else {
|
||||
// Multi-byte sequences out of Latin-1 range (or malformed bytes)
|
||||
throw std::runtime_error(fmt::format("Invalid bytes when converting to Latin1 with \"{}\"", utf8Str));
|
||||
}
|
||||
}
|
||||
|
||||
return latin1Str;
|
||||
}
|
||||
|
||||
static void LoadTextData(TextDatabase& tb) {
|
||||
struct LanguageEntry {
|
||||
std::string language;
|
||||
@@ -268,23 +331,32 @@ namespace randomizer {
|
||||
};
|
||||
auto files = std::to_array<LanguageEntry>({
|
||||
{"english", GET_EMBED_DATA(RANDO_DATA_PATH "text/languages/english.yaml")},
|
||||
{"spanish", GET_EMBED_DATA(RANDO_DATA_PATH "text/languages/spanish.yaml")},
|
||||
{"french", GET_EMBED_DATA(RANDO_DATA_PATH "text/languages/french.yaml")},
|
||||
{"german", GET_EMBED_DATA(RANDO_DATA_PATH "text/languages/german.yaml")},
|
||||
{"italian", GET_EMBED_DATA(RANDO_DATA_PATH "text/languages/italian.yaml")},
|
||||
});
|
||||
|
||||
for (const auto& file : files) {
|
||||
auto language = string_to_language(file.language);
|
||||
auto language = stringToLanguage(file.language);
|
||||
auto textData = LOAD_EMBED_DATA(file.languageData);
|
||||
for (const auto& textNode : textData) {
|
||||
const auto& name = textNode.first.as<std::string>();
|
||||
for (const auto& typeNode : textNode.second) {
|
||||
auto type = string_to_type(typeNode.first.as<std::string>());
|
||||
auto type = stringToType(typeNode.first.as<std::string>());
|
||||
auto typeData = typeNode.second;
|
||||
const auto& text = typeData["Text"].as<std::string>();
|
||||
tb[name][type].mText[language] = text;
|
||||
if (language != Text::JAPANESE) {
|
||||
tb[name][type].mText[language] = UTF8ToLatin1(text);
|
||||
} else {
|
||||
// Probably have to handle Japanese another way at some point
|
||||
tb[name][type].mText[language] = text;
|
||||
}
|
||||
if (typeData["Gender"]) {
|
||||
tb[name][type].mGender[language] = string_to_gender(typeData["Gender"].as<std::string>());
|
||||
tb[name][type].mGender[language] = stringToGender(typeData["Gender"].as<std::string>());
|
||||
}
|
||||
if (typeData["Plurality"]) {
|
||||
tb[name][type].mPlurality[language] = string_to_plurality(typeData["Plurality"].as<std::string>());
|
||||
tb[name][type].mPlurality[language] = stringToPlurality(typeData["Plurality"].as<std::string>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,6 +391,12 @@ namespace randomizer {
|
||||
if (!tb.contains(name)) {
|
||||
throw std::runtime_error("Text name \"" + name + "\" is not recognized.");
|
||||
}
|
||||
|
||||
if (!tb.at(name).at(type).mText.at(language).empty()) {
|
||||
return tb.at(name).at(type).mText.at(language);
|
||||
}
|
||||
|
||||
// Return english if the other language's string is empty
|
||||
return tb.at(name).at(type).mText.at(language);
|
||||
}
|
||||
|
||||
@@ -359,6 +437,8 @@ namespace randomizer {
|
||||
{"<fast>", "\x1A\x05\x00\x00\x01"s},
|
||||
{"<slow>", "\x1A\x05\x00\x00\x02"s},
|
||||
{"<begin choice>", "\x1A\x05\x00\x00\x20"s},
|
||||
{"<male>", "\x1A\x05\x06\x00\x02"s},
|
||||
{"<female>", "\x1A\x05\x06\x00\x03"s},
|
||||
{"<choice 1>", "\x1A\x06\x00\x00\x09\x01"s},
|
||||
{"<choice 2>", "\x1A\x06\x00\x00\x09\x02"s},
|
||||
{"<choice 3>", "\x1A\x06\x00\x00\x09\x03"s},
|
||||
@@ -373,8 +453,6 @@ namespace randomizer {
|
||||
{"<dark green>", "\x1A\x06\xFF\x00\x00\x09"s},
|
||||
{"<blue>", "\x1A\x06\xFF\x00\x00\x0A"s},
|
||||
{"<silver>", "\x1A\x06\xFF\x00\x00\x0B"s},
|
||||
{"<male>", "\x1A\x05\x06\x00\x02"s},
|
||||
{"<female>", "\x1A\x05\x06\x00\x03"s},
|
||||
};
|
||||
|
||||
for (const auto& [code, replacement] : messageCodes) {
|
||||
|
||||
@@ -8,7 +8,14 @@ namespace randomizer {
|
||||
class Text {
|
||||
public:
|
||||
enum Language {
|
||||
ENGLISH = 0,
|
||||
// First 5 match ordering of dSv_config_language in d_save.h
|
||||
ENGLISH,
|
||||
GERMAN,
|
||||
FRENCH,
|
||||
SPANISH,
|
||||
ITALIAN,
|
||||
// End of ordering for dSv_config_language
|
||||
JAPANESE, // Not supported yet
|
||||
LANGUAGE_MAX
|
||||
};
|
||||
|
||||
@@ -72,8 +79,12 @@ namespace randomizer {
|
||||
friend Text operator+(const std::string& lhs, const Text& rhs);
|
||||
};
|
||||
|
||||
inline constexpr std::array supported_languages = {
|
||||
inline constexpr std::array supportedLanguages = {
|
||||
Text::ENGLISH,
|
||||
Text::SPANISH,
|
||||
Text::FRENCH,
|
||||
Text::GERMAN,
|
||||
Text::ITALIAN
|
||||
};
|
||||
|
||||
// std::u16string apply_name_color(std::u16string str, const Color& color);
|
||||
@@ -81,8 +92,10 @@ namespace randomizer {
|
||||
// std::string pad_str_4_lines(const std::string& string);
|
||||
// std::u16string pad_str_4_lines(const std::u16string& string);
|
||||
|
||||
Text::Gender string_to_gender(const std::string& str);
|
||||
Text::Plurality string_to_plurality(const std::string& str);
|
||||
Text::Language stringToLanguage(const std::string& str);
|
||||
std::string languageToString(Text::Language language);
|
||||
Text::Gender stringToGender(const std::string& str);
|
||||
Text::Plurality stringToPlurality(const std::string& str);
|
||||
|
||||
// Retrieval of Text objects keyed by name and type (standard, pretty, cryptic)
|
||||
using TextDatabase = std::unordered_map<std::string, std::array<Text, Text::TYPE_MAX>>;
|
||||
|
||||
Reference in New Issue
Block a user