1
0
mirror of https://github.com/TerryCavanagh/VVVVVV.git synced 2024-06-30 16:38:29 +02:00
VVVVVV/desktop_version/src/LocalizationMaint.cpp
Dav999-v 159c70dade Move wordwrapping functions and len to Font.cpp/font:: namespace
The following functions were moved directly:
- next_wrap
- next_wrap_s
- string_wordwrap
- string_wordwrap_balanced
- string_unwordwrap

These ones will probably still need get a flags argument, except for
string_unwordwrap (since they need to know what font we're talking
about.

The implementation of graphics.len has also been moved to Font.cpp,
but graphics.len still exists for now and is deprecated.
2023-02-13 23:27:00 -08:00

498 lines
15 KiB
C++

#define LOCALIZATIONMAINT_CPP
#include "Localization.h"
#include "LocalizationStorage.h"
#include <tinyxml2.h>
#include "Alloc.h"
#include "FileSystemUtils.h"
#include "Font.h"
#include "Graphics.h"
#include "Script.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)
{
char textcase = pElem->UnsignedAttribute("case", 0);
const char* tra;
if (textcase == 0)
{
tra = map_lookup_text(map_translation, eng, "");
}
else
{
char* eng_prefixed = add_disambiguator(textcase, eng, NULL);
if (eng_prefixed == NULL)
{
/* Are we out of memory? Stop, don't blank our language files... */
return;
}
/* Note the fallback: if this string used to not be cased and now it is,
* simply fill in the old single variant we already had. */
tra = map_lookup_text(
map_translation,
eng_prefixed,
map_lookup_text(map_translation, eng, "")
);
VVV_free(eng_prefixed);
}
pElem->SetAttribute("translation", tra);
}
}
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)
{
/* Out of memory or something, stop */
return;
}
subElem->SetAttribute("translation", map_lookup_text(map_translation_plural, key, ""));
VVV_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 = font::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;
VVV_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;
}
void populate_testable_script_ids(void)
{
testable_script_ids.clear();
tinyxml2::XMLDocument doc;
tinyxml2::XMLHandle hDoc(&doc);
tinyxml2::XMLElement* pElem;
if (!load_lang_doc("cutscenes", doc))
{
return;
}
FOR_EACH_XML_ELEMENT(hDoc, pElem)
{
EXPECT_ELEM(pElem, "cutscene");
const char* id = pElem->Attribute("id");
if (id != NULL)
{
testable_script_ids.push_back(id);
}
}
}
bool populate_cutscene_test(const char* script_id)
{
tinyxml2::XMLDocument doc;
tinyxml2::XMLHandle hDoc(&doc);
tinyxml2::XMLElement* pElem;
if (!load_lang_doc("cutscenes", doc))
{
return false;
}
const char* original = get_level_original_lang(hDoc);
FOR_EACH_XML_ELEMENT(hDoc, pElem)
{
EXPECT_ELEM(pElem, "cutscene");
if (SDL_strcmp(pElem->Attribute("id"), script_id) != 0)
{
/* Not the correct cutscene */
continue;
}
tinyxml2::XMLElement* subElem;
FOR_EACH_XML_SUB_ELEMENT(pElem, subElem)
{
EXPECT_ELEM(subElem, "dialogue");
const char* tra = subElem->Attribute("translation");
const char* speaker = subElem->Attribute("speaker");
const char* eng = subElem->Attribute(original);
if (tra != NULL && tra[0] != '\0' && speaker != NULL && eng != NULL)
{
script.add_test_line(
speaker,
eng,
subElem->UnsignedAttribute("case", 1)
);
}
}
return true;
}
return false;
}
} /* namespace loc */