installer_wizard: implement localisation

This commit is contained in:
Hyper 2024-12-04 03:00:33 +00:00
parent ba541c0fca
commit 7b0e6dc635
3 changed files with 186 additions and 31 deletions

View file

@ -98,6 +98,78 @@ inline static std::unordered_map<std::string, std::unordered_map<ELanguage, std:
{ ELanguage::Spanish, "INSTALANDO" },
}
},
{
"Installer_Page_Language",
{
{ ELanguage::English, "Please select a language." }
}
},
{
"Installer_Page_Welcome",
{
{ ELanguage::English, "Welcome to Unleashed Recompiled!\n\nYou'll need an Xbox 360 copy\nof Sonic Unleashed in order to proceed with the installation." }
}
},
{
"Installer_Page_AddGame",
{
{ ELanguage::English, "Add the files for the game and its title update. You can use digital dumps or pre-extracted folders containing the necessary files." }
}
},
{
"Installer_Page_AddDLC",
{
{ ELanguage::English, "Add the files for the DLC. You can use digital dumps or pre-extracted folders containing the necessary files." }
}
},
{
"Installer_Page_FreeSpace",
{
{ ELanguage::English, "The content will be installed to the program's folder. Please confirm you have enough free space.\n\n" }
}
},
{
"Installer_Page_Installing",
{
{ ELanguage::English, "Please wait while the content is being installed..." }
}
},
{
"Installer_Page_InstallComplete",
{
{ ELanguage::English, "Installation complete.\n\nThis project is brought to you by:\n\n" }
}
},
{
"Installer_Page_InstallFailed",
{
{ ELanguage::English, "Installation failed.\n\nError:\n\n" }
}
},
{
"Installer_Step_Game",
{
{ ELanguage::English, "GAME" }
}
},
{
"Installer_Step_Update",
{
{ ELanguage::English, "UPDATE" }
}
},
{
"Installer_Step_RequiredSpace",
{
{ ELanguage::English, "Required space:" }
}
},
{
"Installer_Step_AvailableSpace",
{
{ ELanguage::English, "Available space:" }
}
},
{
"Installer_Button_Next",
{
@ -130,6 +202,30 @@ inline static std::unordered_map<std::string, std::unordered_map<ELanguage, std:
{ ELanguage::German, "ORDNER HINZUFÜGEN" },
}
},
{
"Installer_Message_InvalidFilesList",
{
{ ELanguage::English, "The following selected files are invalid:" }
}
},
{
"Installer_Message_InvalidFiles",
{
{ ELanguage::English, "Some of the files that have\nbeen provided are not valid.\n\nPlease make sure all the\nspecified files are correct\nand try again." }
}
},
{
"Installer_Message_IncompatibleGameData",
{
{ ELanguage::English, "The specified game and\nupdate file are incompatible.\n\nPlease ensure the files are\nfor the same version and\nregion and try again." }
}
},
{
"Installer_Message_DLCWarning",
{
{ ELanguage::English, "It is highly recommended\nthat you install all of the\nDLC, as it includes high\nquality lighting textures\nfor the base game.\n\nAre you sure you want to\nskip this step?" }
}
}
};
static std::string& Localise(const char* key)

View file

@ -232,6 +232,32 @@ static float CalcWidestTextSize(const ImFont* font, float fontSize, std::span<st
return result;
}
static std::string Truncate(const std::string& input, size_t maxLength, bool useEllipsis = true, bool usePrefixEllipsis = false)
{
const std::string ellipsis = "...";
if (input.length() > maxLength)
{
if (useEllipsis && maxLength > ellipsis.length())
{
if (usePrefixEllipsis)
{
return ellipsis + input.substr(0, maxLength - ellipsis.length());
}
else
{
return input.substr(0, maxLength - ellipsis.length()) + ellipsis;
}
}
else
{
return input.substr(0, maxLength);
}
}
return input;
}
static std::vector<std::string> Split(const char* str, char delimiter)
{
std::vector<std::string> result;
@ -285,11 +311,29 @@ static void DrawCentredParagraph(const ImFont* font, float fontSize, const ImVec
auto paragraphSize = MeasureCentredParagraph(font, fontSize, lineMargin, lines);
auto offsetY = 0.0f;
for (auto& str : lines)
auto hasList = std::strstr(text, "- ");
auto isList = false;
auto listOffsetX = 0.0f;
for (int i = 0; i < lines.size(); i++)
{
auto& str = lines[i];
auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, str.c_str());
drawMethod(str.c_str(), ImVec2(/* X */ centre.x - textSize.x / 2, /* Y */ centre.y - paragraphSize.y / 2 + offsetY));
if (hasList)
{
if (!isList && str.starts_with("- ") && lines.size() > i + 1 && lines[i + 1].starts_with("- "))
{
isList = true;
listOffsetX = centre.x - textSize.x / 2;
}
else if (isList && !str.starts_with("- "))
{
isList = false;
}
}
drawMethod(str.c_str(), ImVec2(/* X */ isList ? listOffsetX : centre.x - textSize.x / 2, /* Y */ centre.y - paragraphSize.y / 2 + offsetY));
offsetY += textSize.y + Scale(lineMargin);
}

View file

@ -117,19 +117,24 @@ static bool g_currentMessagePromptConfirmation = false;
static int g_currentMessageResult = -1;
static bool g_currentMessageUpdateRemaining = false;
const char CREDITS_TEXT[] = "Sajid (RIP)";
const char CREDITS_TEXT[] = "- Sajid (RIP)\n- imgui sega balls!";
const char *WIZARD_TEXT[] =
static std::string& GetWizardText(int index)
{
"Please select a language.\n",
"Welcome to Unleashed Recompiled!\n\nMake sure you have a copy of Sonic Unleashed's files for Xbox 360 before proceeding with the installation.",
"Select the files for the Game and the Update. You can use digital dumps (PIRS), a folder with the game's contents or a disc image (ISO).",
"Select the files for the DLC Packs. These can be digital dumps (PIRS) or a folder with their contents.",
"The content will be installed to the program's folder. Please confirm you have enough space available.\n\n",
"Please wait while the content is being installed...",
"Installation is complete.\n\nThis project's been brought to you by:\n\n",
"Installation has failed.\n\nError:\n\n"
};
switch (index)
{
case 0: return Localise("Installer_Page_Language");
case 1: return Localise("Installer_Page_Welcome");
case 2: return Localise("Installer_Page_AddGame");
case 3: return Localise("Installer_Page_AddDLC");
case 4: return Localise("Installer_Page_FreeSpace");
case 5: return Localise("Installer_Page_Installing");
case 6: return Localise("Installer_Page_InstallComplete");
case 7: return Localise("Installer_Page_InstallFailed");
}
return g_localeMissing;
}
static const int WIZARD_INSTALL_TEXTURE_INDEX[] =
{
@ -164,8 +169,6 @@ const ELanguage LANGUAGE_ENUM[] =
ELanguage::Japanese,
};
const char GAME_SOURCE_TEXT[] = "FULL GAME";
const char UPDATE_SOURCE_TEXT[] = "UPDATE";
const char *DLC_SOURCE_TEXT[] =
{
"SPAGONIA",
@ -176,9 +179,6 @@ const char *DLC_SOURCE_TEXT[] =
"EMPIRE CITY & ADABAT",
};
const char REQUIRED_SPACE_TEXT[] = "Required space";
const char AVAILABLE_SPACE_TEXT[] = "Available space";
static int DLCIndex(DLC dlc)
{
assert(dlc != DLC::Unknown);
@ -243,7 +243,7 @@ static void DrawHeaderIcons()
ImVec2 arrowCircleMin = { Scale(iconsPosX - iconsScale / 2), Scale(iconsPosY - iconsScale / 2) };
ImVec2 arrowCircleMax = { Scale(iconsPosX + iconsScale / 2), Scale(iconsPosY + iconsScale / 2) };
ImVec2 center = { Scale(iconsPosX) + 0.5f, Scale(iconsPosY) };
ImVec2 center = { Scale(iconsPosX) + 0.5f, Scale(iconsPosY) - 0.5f };
float currentAngle = g_arrowCircleCurrentRotation * (3.14159f / 180.0f); // Rotation angle in radians
float cos_a = cosf(currentAngle);
float sin_a = sinf(currentAngle);
@ -385,14 +385,24 @@ static void DrawDescriptionContainer()
DrawContainer(descriptionMin, descriptionMax, true);
char descriptionText[512];
strncpy(descriptionText, WIZARD_TEXT[int(g_currentPage)], sizeof(descriptionText) - 1);
strncpy(descriptionText, GetWizardText(int(g_currentPage)).c_str(), sizeof(descriptionText) - 1);
if (g_currentPage == WizardPage::CheckSpace)
{
constexpr double DivisorGiB = (1024.0 * 1024.0 * 1024.0);
double requiredGiB = double(g_installerSources.totalSize) / DivisorGiB;
double availableGiB = double(g_installerAvailableSize) / DivisorGiB;
snprintf(descriptionText, sizeof(descriptionText), "%s%s: %2.2f GiB\n\n%s: %2.2f GiB", WIZARD_TEXT[int(g_currentPage)], REQUIRED_SPACE_TEXT, requiredGiB, AVAILABLE_SPACE_TEXT, availableGiB);
// TODO: the format for where the numbers are (%2.2f GiB) should be moved to the localised string.
snprintf
(
descriptionText,
sizeof(descriptionText),
"%s%s %2.2f GiB\n%s %2.2f GiB",
GetWizardText(int(g_currentPage)).c_str(),
Localise("Installer_Step_RequiredSpace").c_str(), requiredGiB,
Localise("Installer_Step_AvailableSpace").c_str(), availableGiB
);
}
else if (g_currentPage == WizardPage::InstallSucceeded)
{
@ -660,6 +670,7 @@ static void ParseSourcePaths(std::list<std::filesystem::path> &paths)
assert((g_currentPage == WizardPage::SelectGameAndUpdate) || (g_currentPage == WizardPage::SelectDLC));
constexpr size_t failedPathLimit = 5;
bool isFailedPathsOverLimit = false;
std::list<std::filesystem::path> failedPaths;
if (g_currentPage == WizardPage::SelectGameAndUpdate)
{
@ -677,6 +688,10 @@ static void ParseSourcePaths(std::list<std::filesystem::path> &paths)
{
failedPaths.push_back(path);
}
else
{
isFailedPathsOverLimit = true;
}
}
}
else if(g_currentPage == WizardPage::SelectDLC)
@ -698,12 +713,15 @@ static void ParseSourcePaths(std::list<std::filesystem::path> &paths)
if (!failedPaths.empty())
{
std::stringstream stringStream;
stringStream << "Some of the files that were selected are not valid." << std::endl;
stringStream << Localise("Installer_Message_InvalidFilesList") << std::endl;
for (const std::filesystem::path &path : failedPaths)
{
stringStream << std::endl << path.filename().string();
stringStream << std::endl << "- " << Truncate(path.filename().string(), 32, true, true);
}
if (isFailedPathsOverLimit)
stringStream << std::endl << "- [...]";
g_currentMessagePrompt = stringStream.str();
g_currentMessagePromptConfirmation = false;
}
@ -774,8 +792,8 @@ static void DrawSources()
{
if (g_currentPage == WizardPage::SelectGameAndUpdate)
{
DrawSourceButton(ButtonColumnMiddle, 1.5f, GAME_SOURCE_TEXT, !g_gameSourcePath.empty());
DrawSourceButton(ButtonColumnMiddle, 0.5f, UPDATE_SOURCE_TEXT, !g_updateSourcePath.empty());
DrawSourceButton(ButtonColumnMiddle, 1.5f, Localise("Installer_Step_Game").c_str(), !g_gameSourcePath.empty());
DrawSourceButton(ButtonColumnMiddle, 0.5f, Localise("Installer_Step_Update").c_str(), !g_updateSourcePath.empty());
}
if (g_currentPage == WizardPage::SelectDLC)
@ -883,8 +901,7 @@ static void DrawNextButton()
XexPatcher::Result patcherResult;
if (g_currentPage == WizardPage::SelectGameAndUpdate && (patcherResult = Installer::checkGameUpdateCompatibility(g_gameSourcePath, g_updateSourcePath), patcherResult != XexPatcher::Result::Success))
{
// TODO: localise this.
g_currentMessagePrompt = "The specified game and\nupdate file are incompatible.\n\nPlease ensure the files are\nfor the same version and\nregion and try again.";
g_currentMessagePrompt = Localise("Installer_Message_IncompatibleGameData");
g_currentMessagePromptConfirmation = false;
}
else if (g_currentPage == WizardPage::SelectDLC)
@ -902,17 +919,15 @@ static void DrawNextButton()
bool dlcInstallerMode = g_gameSourcePath.empty();
if (!InstallerParseSources())
{
// TODO: localise this.
// Some of the sources that were provided to the installer are not valid. Restart the file selection process.
g_currentMessagePrompt = "Some of the files that have\nbeen provided are not valid.\n\nPlease make sure all the\nspecified files are correct\nand try again.";
g_currentMessagePrompt = Localise("Installer_Message_InvalidFiles");
g_currentMessagePromptConfirmation = false;
g_currentPage = dlcInstallerMode ? WizardPage::SelectDLC : WizardPage::SelectGameAndUpdate;
}
else if (dlcIncomplete && !dlcInstallerMode)
{
// TODO: localise this.
// Not all the DLC was specified, we show a prompt and await a confirmation before starting the installer.
g_currentMessagePrompt = "It is highly recommended\nthat you install all of the DLC,\nas it includes high quality\nlighting textures for the\nstages in each pack.\n\nAre you sure you want to\nskip this step?";
g_currentMessagePrompt = Localise("Installer_Message_DLCWarning");
g_currentMessagePromptConfirmation = true;
}
else if (skipButton && dlcInstallerMode)