Implement RMLUI string translation via LCE StringTable

Replace hardcoded English strings in RML UI files with {IDS_XXX}
placeholders that are translated at runtime via RmlUi's TranslateString
override, which looks up keys in the LCE StringTable.

- Override SystemInterface_Win64::TranslateString to scan for {KEY}
  patterns and resolve them via StringTable::getString(wstring)
- Add GetLCEStringTable() accessor to CConsoleMinecraftApp
- Update PauseMenu.rml, SettingsMenu.rml, HelpOptions.rml with IDS_XXX keys
- Update dynamic dialog SetInnerRML calls in UIScene_PauseMenu.cpp
- Fix CMakePresets.json: remove hardcoded VS2022 ml64.exe path
This commit is contained in:
Zero
2026-06-12 15:46:34 +02:00
parent 233aea0110
commit 5355394a5a
7 changed files with 104 additions and 36 deletions

View File

@@ -14,8 +14,7 @@
"cacheVariables": {
"PLATFORM_DEFINES": "_WINDOWS64",
"PLATFORM_NAME": "Windows64",
"IGGY_LIBS": "iggy_w64.lib;iggyperfmon_w64.lib;iggyexpruntime_w64.lib",
"CMAKE_ASM_MASM_COMPILER": "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.16.27023/bin/HostX64/x64/ml64.exe"
"IGGY_LIBS": "iggy_w64.lib;iggyperfmon_w64.lib;iggyexpruntime_w64.lib"
}
}
],

View File

@@ -6,11 +6,11 @@
<body>
<div id="help_options_menu">
<div id="buttons">
<button id="change_skin">Change Skin</button>
<button id="how_to_play">How To Play</button>
<button id="controls">Controls</button>
<button id="settings">Settings</button>
<button id="credits">Credits</button>
<button id="change_skin">{IDS_CHANGE_SKIN}</button>
<button id="how_to_play">{IDS_HOW_TO_PLAY}</button>
<button id="controls">{IDS_CONTROLS}</button>
<button id="settings">{IDS_SETTINGS}</button>
<button id="credits">{IDS_CREDITS}</button>
</div>
</div>
</body>

View File

@@ -6,25 +6,25 @@
<body>
<div id="pause_menu">
<div id="buttons">
<button id="resume">Resume Game</button>
<button id="help_options">Help &amp; Options</button>
<button id="achievements">Achievements</button>
<button id="save_game">Save Game</button>
<button id="exit_game">Exit Game</button>
<button id="resume">{IDS_RESUME_GAME}</button>
<button id="help_options">{IDS_HELP_AND_OPTIONS}</button>
<button id="achievements">{IDS_ACHIEVEMENTS}</button>
<button id="save_game">{IDS_SAVE_GAME}</button>
<button id="exit_game">{IDS_EXIT_GAME}</button>
</div>
<div id="exit_dialog">
<p id="exit_text">Exit Game?</p>
<p id="exit_text">{IDS_CONFIRM_EXIT_GAME}</p>
<div id="exit_buttons">
<button id="exit_save">Save &amp; Exit</button>
<button id="exit_nosave">Exit without Saving</button>
<button id="exit_cancel">Cancel</button>
<button id="exit_save">{IDS_EXIT_GAME_SAVE}</button>
<button id="exit_nosave">{IDS_EXIT_GAME_NO_SAVE}</button>
<button id="exit_cancel">{IDS_CANCEL}</button>
</div>
</div>
<div id="save_dialog">
<p id="save_text">Overwrite existing save?</p>
<p id="save_text">{IDS_CONFIRM_SAVE_GAME}</p>
<div id="save_buttons">
<button id="save_cancel">Cancel</button>
<button id="save_confirm">Save</button>
<button id="save_cancel">{IDS_CANCEL}</button>
<button id="save_confirm">{IDS_CONFIRM_SAVE_GAME}</button>
</div>
</div>
</div>

View File

@@ -6,18 +6,18 @@
<body>
<div id="settings_menu">
<div id="buttons">
<button id="options">Options</button>
<button id="audio">Audio</button>
<button id="controls">Controls</button>
<button id="graphics">Graphics</button>
<button id="ui">User Interface</button>
<button id="reset_defaults">Reset to Defaults</button>
<button id="options">{IDS_OPTIONS}</button>
<button id="audio">{IDS_AUDIO}</button>
<button id="controls">{IDS_CONTROLS}</button>
<button id="graphics">{IDS_GRAPHICS}</button>
<button id="ui">{IDS_USER_INTERFACE}</button>
<button id="reset_defaults">{IDS_RESET_TO_DEFAULTS}</button>
</div>
<div id="confirm_dialog">
<p id="confirm_text">Reset all settings to defaults?</p>
<p id="confirm_text">{IDS_RESET_TO_DEFAULTS}</p>
<div id="confirm_buttons">
<button id="confirm_no">No</button>
<button id="confirm_yes">Yes</button>
<button id="confirm_no">{IDS_NO}</button>
<button id="confirm_yes">{IDS_YES}</button>
</div>
</div>
</div>

View File

@@ -303,11 +303,11 @@ void UIScene_PauseMenu::ShowSaveDialog(bool show)
if (!ProfileManager.IsFullVersion())
{
if (saveText) saveText->SetInnerRML("You need to unlock the full game to save.");
if (saveText) saveText->SetInnerRML("{IDS_UNLOCK_TOSAVE_TEXT}");
}
else
{
if (saveText) saveText->SetInnerRML("Overwrite existing save?");
if (saveText) saveText->SetInnerRML("{IDS_CONFIRM_SAVE_GAME}");
}
m_document->GetElementById("save_cancel")->Focus();
@@ -395,11 +395,11 @@ void UIScene_PauseMenu::ShowExitDialog(bool show)
if (g_NetworkManager.GetPlayerCount() > 1)
{
if (exitText) exitText->SetInnerRML("Exit Game (disconnect other players)?");
if (exitText) exitText->SetInnerRML("{IDS_CONFIRM_EXIT_GAME_CONFIRM_DISCONNECT_SAVE}");
}
else
{
if (exitText) exitText->SetInnerRML("Exit Game?");
if (exitText) exitText->SetInnerRML("{IDS_CONFIRM_EXIT_GAME}");
}
if (exitCancel) exitCancel->Focus();
}
@@ -408,8 +408,8 @@ void UIScene_PauseMenu::ShowExitDialog(bool show)
// Trial version: simple OK/Cancel, "progress will be lost"
if (exitSave) exitSave->SetProperty("display", "none");
if (exitNosave) exitNosave->SetProperty("display", "inline-block");
if (exitNosave) exitNosave->SetInnerRML("Yes");
if (exitText) exitText->SetInnerRML("Exit Game (progress will be lost)?");
if (exitNosave) exitNosave->SetInnerRML("{IDS_YES}");
if (exitText) exitText->SetInnerRML("{IDS_CONFIRM_EXIT_GAME_PROGRESS_LOST}");
if (exitCancel) exitCancel->Focus();
}
@@ -424,7 +424,7 @@ void UIScene_PauseMenu::ShowExitDialog(bool show)
auto* exitSave = m_document->GetElementById("exit_save");
auto* exitNosave = m_document->GetElementById("exit_nosave");
if (exitSave) exitSave->SetProperty("display", "inline-block");
if (exitNosave) exitNosave->SetInnerRML("Exit without Saving");
if (exitNosave) exitNosave->SetInnerRML("{IDS_EXIT_GAME_NO_SAVE}");
if (exitNosave) exitNosave->SetProperty("display", "inline-block");
m_document->GetElementById("exit_game")->Focus();

View File

@@ -1,4 +1,7 @@
#include "RmlUi_SystemInterface_Win64.h"
#include "..\Common\Consoles_App.h"
#include "StringTable.h"
#include "Windows64_App.h"
SystemInterface_Win64::SystemInterface_Win64()
{
@@ -22,7 +25,72 @@ double SystemInterface_Win64::GetElapsedTime()
int SystemInterface_Win64::TranslateString(Rml::String& translated, const Rml::String& input)
{
translated = input;
return 0;
int count = 0;
StringTable* st = app.GetLCEStringTable();
if (!st)
return 0;
std::string result;
result.reserve(input.size());
for (size_t i = 0; i < input.size(); )
{
if (input[i] == '{')
{
size_t end = input.find('}', i + 1);
if (end != Rml::String::npos)
{
Rml::String key = input.substr(i + 1, end - i - 1);
// Convert UTF-8 key to wide string for StringTable lookup
int wlen = MultiByteToWideChar(CP_UTF8, 0, key.c_str(), (int)key.size(), nullptr, 0);
if (wlen > 0)
{
std::wstring wkey(wlen, L'\0');
MultiByteToWideChar(CP_UTF8, 0, key.c_str(), (int)key.size(), &wkey[0], wlen);
LPCWSTR wstr = st->getString(wkey);
if (wstr && wcslen(wstr) > 0)
{
// Convert wide result back to UTF-8
int utf8len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
if (utf8len > 0)
{
std::string utf8(utf8len - 1, '\0'); // -1 for null terminator
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &utf8[0], utf8len, nullptr, nullptr);
result += utf8;
count++;
}
else
{
result += input.substr(i, end - i + 1);
}
}
else
{
result += input.substr(i, end - i + 1);
}
}
else
{
result += input.substr(i, end - i + 1);
}
i = end + 1;
}
else
{
result += input[i];
i++;
}
}
else
{
result += input[i];
i++;
}
}
translated = result;
return count;
}
void SystemInterface_Win64::JoinPath(Rml::String& translated_path, const Rml::String& document_path, const Rml::String& path)

View File

@@ -29,6 +29,7 @@ public:
virtual void ReadBannedList(int iPad, eTMSAction action=static_cast<eTMSAction>(0), bool bCallback=false) {}
C4JStringTable *GetStringTable() { return nullptr;}
StringTable *GetLCEStringTable() { return m_stringTable; }
// original code
virtual void TemporaryCreateGameStart();