2022-12-29 05:23:41 +01:00
|
|
|
#define LOCALIZATIONMAINT_CPP
|
|
|
|
#include "Localization.h"
|
|
|
|
#include "LocalizationStorage.h"
|
|
|
|
|
|
|
|
#include <tinyxml2.h>
|
|
|
|
|
2022-12-31 04:37:45 +01:00
|
|
|
#include "Alloc.h"
|
2022-12-29 05:23:41 +01:00
|
|
|
#include "FileSystemUtils.h"
|
2023-01-06 19:17:50 +01:00
|
|
|
#include "Font.h"
|
2022-12-29 05:23:41 +01:00
|
|
|
#include "Graphics.h"
|
2022-12-24 04:16:56 +01:00
|
|
|
#include "Script.h"
|
2022-12-29 05:23:41 +01:00
|
|
|
#include "Vlogging.h"
|
|
|
|
#include "XMLUtils.h"
|
|
|
|
|
|
|
|
|
|
|
|
namespace loc
|
|
|
|
{
|
|
|
|
|
2023-01-23 03:15:17 +01:00
|
|
|
static void write_max_local(tinyxml2::XMLElement* pElem, uint8_t glyph_w, uint8_t glyph_h)
|
|
|
|
{
|
|
|
|
const char* max;
|
|
|
|
if ((max = pElem->Attribute("max")) != NULL)
|
|
|
|
{
|
|
|
|
unsigned short max_w, max_h, max_local_w, max_local_h;
|
|
|
|
if (parse_max(max, &max_w, &max_h))
|
|
|
|
{
|
|
|
|
max_local_w = (max_w*8) / glyph_w;
|
|
|
|
max_local_h = (max_h*10) / SDL_max(10, glyph_h);
|
|
|
|
|
|
|
|
if (max_local_h == 0)
|
|
|
|
{
|
|
|
|
max_local_h = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
char buf[16];
|
|
|
|
if (max_h == 1)
|
|
|
|
{
|
|
|
|
SDL_snprintf(buf, sizeof(buf), "%d", max_local_w);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
SDL_snprintf(buf, sizeof(buf), "%d*%d", max_local_w, max_local_h);
|
|
|
|
}
|
|
|
|
pElem->SetAttribute("max_local", buf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void write_max_local_decl(tinyxml2::XMLDocument* doc, uint8_t glyph_w, uint8_t glyph_h)
|
|
|
|
{
|
|
|
|
char buf[16];
|
|
|
|
SDL_snprintf(buf, sizeof(buf), "%dx%d", glyph_w, glyph_h);
|
|
|
|
doc->FirstChildElement()->SetAttribute("max_local_for", buf);
|
|
|
|
}
|
|
|
|
|
2022-12-29 05:23:41 +01:00
|
|
|
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);
|
|
|
|
|
2023-01-23 03:15:17 +01:00
|
|
|
uint8_t glyph_w = 8, glyph_h = 8;
|
|
|
|
font::glyph_dimensions(PR_FONT_IDX(langmeta.font_idx), &glyph_w, &glyph_h);
|
|
|
|
bool max_local_needed = glyph_w != 8 || glyph_h != 8;
|
|
|
|
|
2022-12-29 05:23:41 +01:00
|
|
|
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());
|
2023-03-26 20:15:45 +02:00
|
|
|
else if (SDL_strcmp(pKey, "gamepad_hint") == 0)
|
|
|
|
pElem->SetText(langmeta.gamepad_hint.c_str());
|
2022-12-29 05:23:41 +01:00
|
|
|
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());
|
2023-01-15 01:31:02 +01:00
|
|
|
else if (SDL_strcmp(pKey, "font") == 0)
|
|
|
|
pElem->SetText(font::get_main_font_name(langmeta.font_idx));
|
2022-12-29 05:23:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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"))
|
|
|
|
{
|
2023-01-23 03:15:17 +01:00
|
|
|
if (max_local_needed)
|
|
|
|
{
|
|
|
|
write_max_local_decl(&doc, glyph_w, glyph_h);
|
|
|
|
}
|
|
|
|
|
2022-12-29 05:23:41 +01:00
|
|
|
FOR_EACH_XML_ELEMENT(hDoc, pElem)
|
|
|
|
{
|
|
|
|
EXPECT_ELEM(pElem, "string");
|
|
|
|
|
|
|
|
const char* eng = pElem->Attribute("english");
|
|
|
|
if (eng != NULL)
|
|
|
|
{
|
Add support for string cases in strings.xml (gendered Rescued/Missing)
I wanted to not complicate the system with different string cases (like
cgettext) if possible, and I have been able to keep the main strings a
simple English=Translation mapping thus far, but apparently strings
like "Rescued!" (which are one string in English), have to be
translated for the correct gender in some languages. So this was a good
time to add support for string cases anyway.
It's a number that can be given to a string to specify the specific
case it's used, to disambiguate identical English keys. In the case of
"Rescued!" and "Missing...", male versions of the string are case 1,
female versions are case 2, and Viridian being missing is case 3. Of
course, if a language doesn't need to use different variants, it can
simply fill in the same string for the different cases.
If any other string needs to switch to different cases: distinguish
them in the English strings.xml with the case="N" attribute (N=1 and
higher), sync language files from the translator menu (existing
translations for the uncased string will simply be copied to all cases)
and change loc::gettext("...") to loc::gettext_case("...", 1),
loc::gettext_case("...", 2), etc.
2022-12-01 01:27:30 +01:00
|
|
|
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, "")
|
|
|
|
);
|
2022-12-31 04:37:45 +01:00
|
|
|
VVV_free(eng_prefixed);
|
Add support for string cases in strings.xml (gendered Rescued/Missing)
I wanted to not complicate the system with different string cases (like
cgettext) if possible, and I have been able to keep the main strings a
simple English=Translation mapping thus far, but apparently strings
like "Rescued!" (which are one string in English), have to be
translated for the correct gender in some languages. So this was a good
time to add support for string cases anyway.
It's a number that can be given to a string to specify the specific
case it's used, to disambiguate identical English keys. In the case of
"Rescued!" and "Missing...", male versions of the string are case 1,
female versions are case 2, and Viridian being missing is case 3. Of
course, if a language doesn't need to use different variants, it can
simply fill in the same string for the different cases.
If any other string needs to switch to different cases: distinguish
them in the English strings.xml with the case="N" attribute (N=1 and
higher), sync language files from the translator menu (existing
translations for the uncased string will simply be copied to all cases)
and change loc::gettext("...") to loc::gettext_case("...", 1),
loc::gettext_case("...", 2), etc.
2022-12-01 01:27:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pElem->SetAttribute("translation", tra);
|
2022-12-29 05:23:41 +01:00
|
|
|
}
|
2023-01-23 03:15:17 +01:00
|
|
|
|
|
|
|
if (max_local_needed)
|
|
|
|
{
|
|
|
|
write_max_local(pElem, glyph_w, glyph_h);
|
|
|
|
}
|
2022-12-29 05:23:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-01-23 03:15:17 +01:00
|
|
|
if (max_local_needed)
|
|
|
|
{
|
|
|
|
write_max_local_decl(&doc, glyph_w, glyph_h);
|
|
|
|
}
|
|
|
|
|
2022-12-29 05:23:41 +01:00
|
|
|
FOR_EACH_XML_ELEMENT(hDoc, pElem)
|
|
|
|
{
|
|
|
|
EXPECT_ELEM(pElem, "string");
|
|
|
|
|
2023-01-23 03:15:17 +01:00
|
|
|
if (max_local_needed)
|
|
|
|
{
|
|
|
|
write_max_local(pElem, glyph_w, glyph_h);
|
|
|
|
}
|
|
|
|
|
2022-12-29 05:23:41 +01:00
|
|
|
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)
|
|
|
|
{
|
Add support for string cases in strings.xml (gendered Rescued/Missing)
I wanted to not complicate the system with different string cases (like
cgettext) if possible, and I have been able to keep the main strings a
simple English=Translation mapping thus far, but apparently strings
like "Rescued!" (which are one string in English), have to be
translated for the correct gender in some languages. So this was a good
time to add support for string cases anyway.
It's a number that can be given to a string to specify the specific
case it's used, to disambiguate identical English keys. In the case of
"Rescued!" and "Missing...", male versions of the string are case 1,
female versions are case 2, and Viridian being missing is case 3. Of
course, if a language doesn't need to use different variants, it can
simply fill in the same string for the different cases.
If any other string needs to switch to different cases: distinguish
them in the English strings.xml with the case="N" attribute (N=1 and
higher), sync language files from the translator menu (existing
translations for the uncased string will simply be copied to all cases)
and change loc::gettext("...") to loc::gettext_case("...", 1),
loc::gettext_case("...", 2), etc.
2022-12-01 01:27:30 +01:00
|
|
|
/* Out of memory or something, stop */
|
2022-12-29 05:23:41 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
subElem->SetAttribute("translation", map_lookup_text(map_translation_plural, key, ""));
|
|
|
|
|
2022-12-31 04:37:45 +01:00
|
|
|
VVV_free(key);
|
2022-12-29 05:23:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2023-03-05 21:42:21 +01:00
|
|
|
bool found = hashmap_get(map, cutscene_id, SDL_strlen(cutscene_id), &ptr_cutscene_map);
|
2022-12-29 05:23:41 +01:00
|
|
|
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;
|
2023-01-06 19:17:50 +01:00
|
|
|
const std::string eng_unwrapped = font::string_unwordwrap(eng);
|
2022-12-29 05:23:41 +01:00
|
|
|
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;
|
2023-03-05 21:42:21 +01:00
|
|
|
found = hashmap_get(cutscene_map, eng_prefixed, alloc_len-1, &ptr_format);
|
2022-12-29 05:23:41 +01:00
|
|
|
const TextboxFormat* format = (TextboxFormat*) ptr_format;
|
|
|
|
|
2022-12-31 04:37:45 +01:00
|
|
|
VVV_free(eng_prefixed);
|
2022-12-29 05:23:41 +01:00
|
|
|
|
|
|
|
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");
|
|
|
|
|
2023-03-22 21:30:42 +01:00
|
|
|
bool buttons = subElem->BoolAttribute("buttons", false);
|
|
|
|
subElem->DeleteAttribute("buttons"); // we want this at the end...
|
|
|
|
|
2022-12-29 05:23:41 +01:00
|
|
|
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);
|
2023-03-22 21:30:42 +01:00
|
|
|
if (buttons)
|
|
|
|
subElem->SetAttribute("buttons", 1);
|
2022-12-29 05:23:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-12-24 04:16:56 +01:00
|
|
|
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,
|
2023-03-22 21:30:42 +01:00
|
|
|
subElem->UnsignedAttribute("case", 1),
|
|
|
|
subElem->BoolAttribute("buttons", false)
|
2022-12-24 04:16:56 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-12-29 05:23:41 +01:00
|
|
|
} /* namespace loc */
|