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" }, { 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", "Installer_Button_Next",
{ {
@ -130,6 +202,30 @@ inline static std::unordered_map<std::string, std::unordered_map<ELanguage, std:
{ ELanguage::German, "ORDNER HINZUFÜGEN" }, { 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) 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; 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) static std::vector<std::string> Split(const char* str, char delimiter)
{ {
std::vector<std::string> result; 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 paragraphSize = MeasureCentredParagraph(font, fontSize, lineMargin, lines);
auto offsetY = 0.0f; 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()); 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); offsetY += textSize.y + Scale(lineMargin);
} }

View file

@ -117,19 +117,24 @@ static bool g_currentMessagePromptConfirmation = false;
static int g_currentMessageResult = -1; static int g_currentMessageResult = -1;
static bool g_currentMessageUpdateRemaining = false; 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", switch (index)
"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).", case 0: return Localise("Installer_Page_Language");
"Select the files for the DLC Packs. These can be digital dumps (PIRS) or a folder with their contents.", case 1: return Localise("Installer_Page_Welcome");
"The content will be installed to the program's folder. Please confirm you have enough space available.\n\n", case 2: return Localise("Installer_Page_AddGame");
"Please wait while the content is being installed...", case 3: return Localise("Installer_Page_AddDLC");
"Installation is complete.\n\nThis project's been brought to you by:\n\n", case 4: return Localise("Installer_Page_FreeSpace");
"Installation has failed.\n\nError:\n\n" 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[] = static const int WIZARD_INSTALL_TEXTURE_INDEX[] =
{ {
@ -164,8 +169,6 @@ const ELanguage LANGUAGE_ENUM[] =
ELanguage::Japanese, ELanguage::Japanese,
}; };
const char GAME_SOURCE_TEXT[] = "FULL GAME";
const char UPDATE_SOURCE_TEXT[] = "UPDATE";
const char *DLC_SOURCE_TEXT[] = const char *DLC_SOURCE_TEXT[] =
{ {
"SPAGONIA", "SPAGONIA",
@ -176,9 +179,6 @@ const char *DLC_SOURCE_TEXT[] =
"EMPIRE CITY & ADABAT", "EMPIRE CITY & ADABAT",
}; };
const char REQUIRED_SPACE_TEXT[] = "Required space";
const char AVAILABLE_SPACE_TEXT[] = "Available space";
static int DLCIndex(DLC dlc) static int DLCIndex(DLC dlc)
{ {
assert(dlc != DLC::Unknown); assert(dlc != DLC::Unknown);
@ -243,7 +243,7 @@ static void DrawHeaderIcons()
ImVec2 arrowCircleMin = { Scale(iconsPosX - iconsScale / 2), Scale(iconsPosY - iconsScale / 2) }; ImVec2 arrowCircleMin = { Scale(iconsPosX - iconsScale / 2), Scale(iconsPosY - iconsScale / 2) };
ImVec2 arrowCircleMax = { 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 currentAngle = g_arrowCircleCurrentRotation * (3.14159f / 180.0f); // Rotation angle in radians
float cos_a = cosf(currentAngle); float cos_a = cosf(currentAngle);
float sin_a = sinf(currentAngle); float sin_a = sinf(currentAngle);
@ -385,14 +385,24 @@ static void DrawDescriptionContainer()
DrawContainer(descriptionMin, descriptionMax, true); DrawContainer(descriptionMin, descriptionMax, true);
char descriptionText[512]; 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) if (g_currentPage == WizardPage::CheckSpace)
{ {
constexpr double DivisorGiB = (1024.0 * 1024.0 * 1024.0); constexpr double DivisorGiB = (1024.0 * 1024.0 * 1024.0);
double requiredGiB = double(g_installerSources.totalSize) / DivisorGiB; double requiredGiB = double(g_installerSources.totalSize) / DivisorGiB;
double availableGiB = double(g_installerAvailableSize) / 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) 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)); assert((g_currentPage == WizardPage::SelectGameAndUpdate) || (g_currentPage == WizardPage::SelectDLC));
constexpr size_t failedPathLimit = 5; constexpr size_t failedPathLimit = 5;
bool isFailedPathsOverLimit = false;
std::list<std::filesystem::path> failedPaths; std::list<std::filesystem::path> failedPaths;
if (g_currentPage == WizardPage::SelectGameAndUpdate) if (g_currentPage == WizardPage::SelectGameAndUpdate)
{ {
@ -677,6 +688,10 @@ static void ParseSourcePaths(std::list<std::filesystem::path> &paths)
{ {
failedPaths.push_back(path); failedPaths.push_back(path);
} }
else
{
isFailedPathsOverLimit = true;
}
} }
} }
else if(g_currentPage == WizardPage::SelectDLC) else if(g_currentPage == WizardPage::SelectDLC)
@ -698,12 +713,15 @@ static void ParseSourcePaths(std::list<std::filesystem::path> &paths)
if (!failedPaths.empty()) if (!failedPaths.empty())
{ {
std::stringstream stringStream; 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) 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_currentMessagePrompt = stringStream.str();
g_currentMessagePromptConfirmation = false; g_currentMessagePromptConfirmation = false;
} }
@ -774,8 +792,8 @@ static void DrawSources()
{ {
if (g_currentPage == WizardPage::SelectGameAndUpdate) if (g_currentPage == WizardPage::SelectGameAndUpdate)
{ {
DrawSourceButton(ButtonColumnMiddle, 1.5f, GAME_SOURCE_TEXT, !g_gameSourcePath.empty()); DrawSourceButton(ButtonColumnMiddle, 1.5f, Localise("Installer_Step_Game").c_str(), !g_gameSourcePath.empty());
DrawSourceButton(ButtonColumnMiddle, 0.5f, UPDATE_SOURCE_TEXT, !g_updateSourcePath.empty()); DrawSourceButton(ButtonColumnMiddle, 0.5f, Localise("Installer_Step_Update").c_str(), !g_updateSourcePath.empty());
} }
if (g_currentPage == WizardPage::SelectDLC) if (g_currentPage == WizardPage::SelectDLC)
@ -883,8 +901,7 @@ static void DrawNextButton()
XexPatcher::Result patcherResult; XexPatcher::Result patcherResult;
if (g_currentPage == WizardPage::SelectGameAndUpdate && (patcherResult = Installer::checkGameUpdateCompatibility(g_gameSourcePath, g_updateSourcePath), patcherResult != XexPatcher::Result::Success)) if (g_currentPage == WizardPage::SelectGameAndUpdate && (patcherResult = Installer::checkGameUpdateCompatibility(g_gameSourcePath, g_updateSourcePath), patcherResult != XexPatcher::Result::Success))
{ {
// TODO: localise this. g_currentMessagePrompt = Localise("Installer_Message_IncompatibleGameData");
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_currentMessagePromptConfirmation = false; g_currentMessagePromptConfirmation = false;
} }
else if (g_currentPage == WizardPage::SelectDLC) else if (g_currentPage == WizardPage::SelectDLC)
@ -902,17 +919,15 @@ static void DrawNextButton()
bool dlcInstallerMode = g_gameSourcePath.empty(); bool dlcInstallerMode = g_gameSourcePath.empty();
if (!InstallerParseSources()) if (!InstallerParseSources())
{ {
// TODO: localise this.
// Some of the sources that were provided to the installer are not valid. Restart the file selection process. // 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_currentMessagePromptConfirmation = false;
g_currentPage = dlcInstallerMode ? WizardPage::SelectDLC : WizardPage::SelectGameAndUpdate; g_currentPage = dlcInstallerMode ? WizardPage::SelectDLC : WizardPage::SelectGameAndUpdate;
} }
else if (dlcIncomplete && !dlcInstallerMode) 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. // 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; g_currentMessagePromptConfirmation = true;
} }
else if (skipButton && dlcInstallerMode) else if (skipButton && dlcInstallerMode)