From 97d905e8bfe9fa090d381335fcaf9fe2d29e126c Mon Sep 17 00:00:00 2001 From: Dav999-v Date: Thu, 29 Dec 2022 05:23:41 +0100 Subject: [PATCH] Add LocalizationMaint.cpp/h (not compiled yet) This commit is part of rewritten history of the localization branch. The original (unsquashed) commit history can be found here: https://github.com/Dav999-v/VVVVVV/tree/localization-orig --- desktop_version/src/LocalizationMaint.cpp | 398 ++++++++++++++++++++++ desktop_version/src/LocalizationMaint.h | 17 + 2 files changed, 415 insertions(+) create mode 100644 desktop_version/src/LocalizationMaint.cpp create mode 100644 desktop_version/src/LocalizationMaint.h diff --git a/desktop_version/src/LocalizationMaint.cpp b/desktop_version/src/LocalizationMaint.cpp new file mode 100644 index 00000000..4048dde8 --- /dev/null +++ b/desktop_version/src/LocalizationMaint.cpp @@ -0,0 +1,398 @@ +#define LOCALIZATIONMAINT_CPP +#include "Localization.h" +#include "LocalizationStorage.h" + +#include + +#include "FileSystemUtils.h" +#include "Graphics.h" +#include "Vlogging.h" +#include "XMLUtils.h" + + +namespace loc +{ + +static void sync_lang_file(const std::string& langcode) +{ + /* Update translation files for the given language with new strings from templates. + * This basically takes the (English) templates, fills in existing translations, and saves. + * Any FILESYSTEM_saveTiXml2Document() writes to main lang dir */ + vlog_info("Syncing %s with templates...", langcode.c_str()); + + lang = langcode; + loadtext(false); + + tinyxml2::XMLDocument doc; + tinyxml2::XMLHandle hDoc(&doc); + tinyxml2::XMLElement* pElem; + tinyxml2::XMLElement* subElem; + + if (load_lang_doc("meta", doc, "en")) + { + FOR_EACH_XML_ELEMENT(hDoc, pElem) + { + const char* pKey = pElem->Value(); + + if (SDL_strcmp(pKey, "active") == 0) + pElem->SetText((int) langmeta.active); + else if (SDL_strcmp(pKey, "nativename") == 0) + pElem->SetText(langmeta.nativename.c_str()); + else if (SDL_strcmp(pKey, "credit") == 0) + pElem->SetText(langmeta.credit.c_str()); + else if (SDL_strcmp(pKey, "action_hint") == 0) + pElem->SetText(langmeta.action_hint.c_str()); + else if (SDL_strcmp(pKey, "autowordwrap") == 0) + pElem->SetText((int) langmeta.autowordwrap); + else if (SDL_strcmp(pKey, "toupper") == 0) + pElem->SetText((int) langmeta.toupper); + else if (SDL_strcmp(pKey, "toupper_i_dot") == 0) + pElem->SetText((int) langmeta.toupper_i_dot); + else if (SDL_strcmp(pKey, "toupper_lower_escape_char") == 0) + pElem->SetText((int) langmeta.toupper_lower_escape_char); + else if (SDL_strcmp(pKey, "menu_select") == 0) + pElem->SetText(langmeta.menu_select.c_str()); + else if (SDL_strcmp(pKey, "menu_select_tight") == 0) + pElem->SetText(langmeta.menu_select_tight.c_str()); + } + + /* This part exists because we want to preserve blank lines between the commented + * options for clarity, so we have to take matters into our own hands. */ + for ( + tinyxml2::XMLNode* pNode = hDoc.FirstChildElement().FirstChild().ToNode(); + pNode != NULL; + pNode = pNode->NextSibling() + ) + { + tinyxml2::XMLComment* pCom = pNode->ToComment(); + if (pCom != NULL) + { + tinyxml2::XMLNode* pPrevNode = pCom->PreviousSibling(); + if (pPrevNode != NULL) + { + doc.FirstChildElement()->InsertAfterChild(pPrevNode, doc.NewText("\n\n ")); + } + doc.FirstChildElement()->InsertAfterChild(pCom, doc.NewText("\n ")); + } + } + + FILESYSTEM_saveTiXml2Document((langcode + "/meta.xml").c_str(), doc); + } + + if (load_lang_doc("strings", doc, "en")) + { + FOR_EACH_XML_ELEMENT(hDoc, pElem) + { + EXPECT_ELEM(pElem, "string"); + + const char* eng = pElem->Attribute("english"); + if (eng != NULL) + { + pElem->SetAttribute("translation", map_lookup_text(map_translation, eng, "")); + } + } + + FILESYSTEM_saveTiXml2Document((langcode + "/strings.xml").c_str(), doc); + } + + if (load_lang_doc("strings_plural", doc, "en")) + { + /* Form 255 is technically invalid, but we have to account for it */ + bool form_id_used[256]; + SDL_zeroa(form_id_used); + for (int num = 0; num < 200; num++) + { + form_id_used[number_plural_form[num]] = true; + } + + FOR_EACH_XML_ELEMENT(hDoc, pElem) + { + EXPECT_ELEM(pElem, "string"); + + pElem->DeleteChildren(); + + const char* eng_plural = pElem->Attribute("english_plural"); + + for (int form_id = 0; form_id < 255; form_id++) + { + if (form_id_used[form_id] && eng_plural != NULL) + { + subElem = doc.NewElement("translation"); + pElem->LinkEndChild(subElem); + + subElem->SetAttribute("form", form_id); + + char* key = add_disambiguator(form_id+1, eng_plural, NULL); + if (key == NULL) + { + /* Are we out of memory? Stop, don't blank our language files... */ + return; + } + + subElem->SetAttribute("translation", map_lookup_text(map_translation_plural, key, "")); + + SDL_free(key); + } + } + } + + FILESYSTEM_saveTiXml2Document((langcode + "/strings_plural.xml").c_str(), doc); + } + + if (load_lang_doc("cutscenes", doc, "en")) + { + FOR_EACH_XML_ELEMENT(hDoc, pElem) + { + EXPECT_ELEM(pElem, "cutscene"); + + const char* cutscene_id = pElem->Attribute("id"); + if (cutscene_id == NULL) + { + continue; + } + + hashmap* map = map_translation_cutscene; + + uintptr_t ptr_cutscene_map; + bool found = hashmap_get(map, (void*) cutscene_id, SDL_strlen(cutscene_id), &ptr_cutscene_map); + hashmap* cutscene_map = (hashmap*) ptr_cutscene_map; + if (!found || cutscene_map == NULL) + { + continue; + } + + FOR_EACH_XML_SUB_ELEMENT(pElem, subElem) + { + EXPECT_ELEM(subElem, "dialogue"); + + const char* eng = subElem->Attribute("english"); + if (eng == NULL) + { + continue; + } + + size_t alloc_len; + const std::string eng_unwrapped = graphics.string_unwordwrap(eng); + char* eng_prefixed = add_disambiguator(subElem->UnsignedAttribute("case", 1), eng_unwrapped.c_str(), &alloc_len); + if (eng_prefixed == NULL) + { + /* Out of memory or something, stop */ + return; + } + + uintptr_t ptr_format; + found = hashmap_get(cutscene_map, (void*) eng_prefixed, alloc_len-1, &ptr_format); + const TextboxFormat* format = (TextboxFormat*) ptr_format; + + SDL_free(eng_prefixed); + + if (!found || format == NULL) + { + continue; + } + + subElem->DeleteAttribute("tt"); + subElem->DeleteAttribute("wraplimit"); + subElem->DeleteAttribute("centertext"); + subElem->DeleteAttribute("pad"); + subElem->DeleteAttribute("pad_left"); + subElem->DeleteAttribute("pad_right"); + subElem->DeleteAttribute("padtowidth"); + + if (format->text != NULL) + subElem->SetAttribute("translation", format->text); + if (format->tt) + subElem->SetAttribute("tt", 1); + if (format->wraplimit_raw != 0) + subElem->SetAttribute("wraplimit", format->wraplimit_raw); + if (format->centertext) + subElem->SetAttribute("centertext", 1); + if (format->pad_left == format->pad_right && format->pad_left != 0) + { + subElem->SetAttribute("pad", format->pad_left); + } + else + { + if (format->pad_left != 0) + subElem->SetAttribute("pad_left", format->pad_left); + if (format->pad_right != 0) + subElem->SetAttribute("pad_right", format->pad_right); + } + if (format->padtowidth != 0) + subElem->SetAttribute("padtowidth", format->padtowidth); + } + } + + FILESYSTEM_saveTiXml2Document((langcode + "/cutscenes.xml").c_str(), doc); + } + + if (load_lang_doc("roomnames", doc, "en")) + { + FOR_EACH_XML_ELEMENT(hDoc, pElem) + { + EXPECT_ELEM(pElem, "roomname"); + + pElem->SetAttribute("translation", + get_roomname_translation(false, pElem->UnsignedAttribute("x"), pElem->UnsignedAttribute("y")) + ); + } + + FILESYSTEM_saveTiXml2Document((langcode + "/roomnames.xml").c_str(), doc); + } + + if (load_lang_doc("roomnames_special", doc, "en")) + { + FOR_EACH_XML_ELEMENT(hDoc, pElem) + { + EXPECT_ELEM(pElem, "roomname"); + + const char* eng = pElem->Attribute("english"); + if (eng != NULL) + { + pElem->SetAttribute("translation", map_lookup_text(map_translation_roomnames_special, eng, "")); + } + } + + FILESYSTEM_saveTiXml2Document((langcode + "/roomnames_special.xml").c_str(), doc); + } +} + +bool sync_lang_files(void) +{ + /* Returns false if we can't set the lang write dir, true otherwise. + * This could maybe be extended with better error reporting, + * problem is getting across which files failed in which languages. */ + std::string oldlang = lang; + if (!FILESYSTEM_setLangWriteDir()) + { + vlog_error("Cannot set write dir to lang dir, not syncing language files"); + return false; + } + + for (size_t i = 0; i < languagelist.size(); i++) + { + if (languagelist[i].code != "en") + sync_lang_file(languagelist[i].code); + } + + FILESYSTEM_restoreWriteDir(); + lang = oldlang; + loadtext(false); + + return true; +} + +bool save_roomname_to_file(const std::string& langcode, bool custom_level, int roomx, int roomy, const char* tra, const char* explanation) +{ + if (custom_level) + { + vlog_error("Saving custom level room names not implemented"); + return false; + } + + if (!fix_room_coords(custom_level, &roomx, &roomy)) + { + return false; + } + + tinyxml2::XMLDocument doc; + tinyxml2::XMLHandle hDoc(&doc); + tinyxml2::XMLElement* pElem; + + if (!load_lang_doc("roomnames", doc, langcode)) + { + return false; + } + + bool found = false; + FOR_EACH_XML_ELEMENT(hDoc, pElem) + { + EXPECT_ELEM(pElem, "roomname"); + + int x = pElem->IntAttribute("x", -1); + int y = pElem->IntAttribute("y", -1); + + if (x == roomx && y == roomy) + { + if (explanation != NULL) + { + pElem->SetAttribute("explanation", explanation); + } + if (tra != NULL) + { + pElem->SetAttribute("translation", tra); + } + found = true; + } + } + + if (!found) + { + vlog_error("Could not find room %d,%d in language file to replace!", roomx, roomy); + return false; + } + + if (!FILESYSTEM_setLangWriteDir()) + { + vlog_error("Cannot set write dir to lang dir, so room name can't be saved"); + return false; + } + bool save_success = FILESYSTEM_saveTiXml2Document((langcode + "/roomnames.xml").c_str(), doc); + FILESYSTEM_restoreWriteDir(); + if (!save_success) + { + vlog_error("Could not write roomnames document!"); + return false; + } + return store_roomname_translation(custom_level, roomx, roomy, tra, explanation); +} + +bool save_roomname_explanation_to_files(bool custom_level, int roomx, int roomy, const char* explanation) +{ + bool success = true; + for (size_t i = 0; i < languagelist.size(); i++) + { + if (!save_roomname_to_file(languagelist[i].code, custom_level, roomx, roomy, NULL, explanation)) + { + success = false; + vlog_warn("Could not save room name explanation to language %s", languagelist[i].code.c_str()); + } + } + + return !languagelist.empty() && success; +} + +void local_limits_check(void) +{ + text_overflows.clear(); + loadtext(true); + limitscheck_current_overflow = 0; +} + +void global_limits_check(void) +{ + text_overflows.clear(); + + std::string oldlang = lang; + + textbook_clear(&textbook_main); + textbook_set_protected(&textbook_main, true); + + for (size_t i = 0; i < languagelist.size(); i++) + { + if (languagelist[i].code != "en") + { + lang = languagelist[i].code; + loadtext(true); + } + } + + lang = oldlang; + loadtext(false); + + textbook_set_protected(&textbook_main, false); + + limitscheck_current_overflow = 0; +} + +} /* namespace loc */ diff --git a/desktop_version/src/LocalizationMaint.h b/desktop_version/src/LocalizationMaint.h new file mode 100644 index 00000000..f9038105 --- /dev/null +++ b/desktop_version/src/LocalizationMaint.h @@ -0,0 +1,17 @@ +#ifndef LOCALIZATIONMAINT_H +#define LOCALIZATIONMAINT_H + +namespace loc +{ + +bool sync_lang_files(void); + +bool save_roomname_to_file(const std::string& langcode, bool custom_level, int roomx, int roomy, const char* tra, const char* explanation); +bool save_roomname_explanation_to_files(bool custom_level, int roomx, int roomy, const char* explanation); + +void local_limits_check(void); +void global_limits_check(void); + +} /* namespace loc */ + +#endif /* LOCALIZATIONMAINT_H */