From fd46a0db272b8695489da57a1235e77b6fb9a3ae Mon Sep 17 00:00:00 2001 From: Mosch0512 Date: Thu, 22 Oct 2026 22:41:12 +0200 Subject: [PATCH 01/10] feat(items): item names can stay English while the rest of the UI is translated [UI] TranslateItemNames (default 1) picks the language of the item names: the UI language, or English when it is 0. The item data handler applies it at load, on a language change and when RefreshItemNames() is called after the setting changed. The item help window builds its content again when only the language of the item names changed. --- src/bin/config.ini.template | 1 + .../DataHandler/ItemData/ItemDataHandler.cpp | 18 ++++++++++++++---- .../DataHandler/ItemData/ItemDataHandler.h | 7 ++++++- src/source/Data/GameConfig/GameConfig.cpp | 7 +++++++ src/source/Data/GameConfig/GameConfig.h | 9 +++++++++ .../Data/GameConfig/GameConfigConstants.h | 3 +++ .../Data/GameData/ItemData/ItemNames.cpp | 5 +++++ src/source/Data/GameData/ItemData/ItemNames.h | 4 ++++ src/source/UI/Inventory/ItemHelpContentKey.cpp | 6 +++++- src/source/UI/Inventory/ItemHelpContentKey.h | 6 ++++-- tests/data/test_item_names.cpp | 7 +++++++ tests/data/test_item_stats.cpp | 6 ++++++ 12 files changed, 71 insertions(+), 8 deletions(-) diff --git a/src/bin/config.ini.template b/src/bin/config.ini.template index 4e3025542c..e24c6414fb 100644 --- a/src/bin/config.ini.template +++ b/src/bin/config.ini.template @@ -16,6 +16,7 @@ ServerIP=localhost ServerPort=44406 [UI] Locale=en +TranslateItemNames=1 Font= RmlTheme=legacy UIScalePercent=100 diff --git a/src/source/Data/DataHandler/ItemData/ItemDataHandler.cpp b/src/source/Data/DataHandler/ItemData/ItemDataHandler.cpp index 6513cb5762..58d99f07fb 100644 --- a/src/source/Data/DataHandler/ItemData/ItemDataHandler.cpp +++ b/src/source/Data/DataHandler/ItemData/ItemDataHandler.cpp @@ -7,6 +7,7 @@ #include "Core/Text/Utf8.h" #include "Core/Utilities/Log/MuLogger.h" #include "Data/DataHandler/DataIssueReport.h" +#include "Data/GameConfig/GameConfig.h" #include "Data/GameData/ItemData/ItemAttributeConversion.h" #include "Data/GameData/EffectData/GlowColorList.h" #include "Data/GameData/ItemData/ItemDataValidation.h" @@ -63,6 +64,11 @@ std::unique_ptr BuildItemAttributes(std::string_view locale) return attributes; } +std::string_view ItemNameLocale() +{ + return GetItemNameLocale(I18N::GetCurrentLocale(), GameConfig::GetInstance().GetTranslateItemNames()); +} + #ifdef _EDITOR // The tables and profiles in the writer's order (by name), so a file read as it // was written and one the editor stored compare equal (the undo history). @@ -134,7 +140,7 @@ bool CItemDataHandler::Load(std::string& errorMessage) #endif const auto buildStart = std::chrono::steady_clock::now(); - g_ItemDatabase.SetDisplayLocale(I18N::GetCurrentLocale()); + g_ItemDatabase.SetDisplayLocale(ItemNameLocale()); g_ItemDatabase.SetStatProfiles(std::move(statProfiles)); g_ItemDatabase.Build(result.items); FillItemAttributes(); @@ -208,9 +214,13 @@ void CItemDataHandler::RegisterLocaleObserver() void CItemDataHandler::OnLocaleChanged(void* context) noexcept { - auto* handler = static_cast(context); - g_ItemDatabase.SetDisplayLocale(I18N::GetCurrentLocale()); - handler->FillItemAttributes(); + static_cast(context)->RefreshItemNames(); +} + +void CItemDataHandler::RefreshItemNames() +{ + g_ItemDatabase.SetDisplayLocale(ItemNameLocale()); + FillItemAttributes(); } #ifdef _EDITOR diff --git a/src/source/Data/DataHandler/ItemData/ItemDataHandler.h b/src/source/Data/DataHandler/ItemData/ItemDataHandler.h index c0267c554a..467b942b7b 100644 --- a/src/source/Data/DataHandler/ItemData/ItemDataHandler.h +++ b/src/source/Data/DataHandler/ItemData/ItemDataHandler.h @@ -21,12 +21,17 @@ // Owns loading the item data (Data/Items/*.json) into the item database and // the item models (Data/Items/Models/*.json) into the item model database, and // keeps ItemAttribute[], the table most game code still reads, filled from -// the database. Item names follow the UI locale. +// the database. Item names follow the UI locale, or stay English when the +// player turned translated item names off ([UI] TranslateItemNames). class CItemDataHandler { public: static CItemDataHandler& GetInstance(); + // Shows the item names in the language the UI locale and the + // TranslateItemNames setting give, after either changed. + void RefreshItemNames(); + // Data/Local//Item_.bmd (legacy item files). static std::wstring GetItemFilePath(const std::wstring& language); diff --git a/src/source/Data/GameConfig/GameConfig.cpp b/src/source/Data/GameConfig/GameConfig.cpp index 7ef8bf0f64..56cb09deb8 100644 --- a/src/source/Data/GameConfig/GameConfig.cpp +++ b/src/source/Data/GameConfig/GameConfig.cpp @@ -81,6 +81,7 @@ void GameConfig::Load() m_chatCommandTemplates = ReadStringList(L"ChatCommands", L"Template"); m_uiLocale = ReadString(CfgSectionUI, CfgKeyUILocale, CfgDefaultUILocale); + m_translateItemNames = ReadBool(CfgSectionUI, CfgKeyTranslateItemNames, CfgDefaultTranslateItemNames); m_fontSelection = ReadString(CfgSectionUI, CfgKeyFont, CfgDefaultFont); m_rmlTheme = ReadString(CfgSectionUI, CfgKeyRmlTheme, CfgDefaultRmlTheme); m_uiScalePercent = std::clamp(ReadInt(CfgSectionUI, CfgKeyUIScalePercent, CfgDefaultUIScalePercent), @@ -153,6 +154,7 @@ void GameConfig::Save() WriteStringList(L"ChatCommands", L"Template", m_chatCommandTemplates); WriteString(CfgSectionUI, CfgKeyUILocale, m_uiLocale); + WriteBool(CfgSectionUI, CfgKeyTranslateItemNames, m_translateItemNames); WriteString(CfgSectionUI, CfgKeyFont, m_fontSelection); WriteString(CfgSectionUI, CfgKeyRmlTheme, m_rmlTheme); WriteInt(CfgSectionUI, CfgKeyUIScalePercent, m_uiScalePercent); @@ -331,6 +333,11 @@ void GameConfig::SetUILocale(const std::wstring& locale) m_uiLocale = locale; } +void GameConfig::SetTranslateItemNames(bool translate) +{ + m_translateItemNames = translate; +} + void GameConfig::SetFontSelection(const std::wstring& font) { m_fontSelection = font; diff --git a/src/source/Data/GameConfig/GameConfig.h b/src/source/Data/GameConfig/GameConfig.h index 8144949ecb..814634f1c8 100644 --- a/src/source/Data/GameConfig/GameConfig.h +++ b/src/source/Data/GameConfig/GameConfig.h @@ -72,6 +72,14 @@ class GameConfig std::wstring GetUILocale() const { return m_uiLocale; } void SetUILocale(const std::wstring& locale); + // Whether item names are shown in the UI language (GetUILocale); off keeps them English + // while the rest of the UI stays translated. + bool GetTranslateItemNames() const + { + return m_translateItemNames; + } + void SetTranslateItemNames(bool translate); + // UI font family name (GDI face name). Empty = platform default. std::wstring GetFontSelection() const { return m_fontSelection; } void SetFontSelection(const std::wstring& font); @@ -208,6 +216,7 @@ class GameConfig int m_serverPort; std::wstring m_uiLocale; + bool m_translateItemNames; std::wstring m_fontSelection; std::wstring m_rmlTheme; int m_uiScalePercent; diff --git a/src/source/Data/GameConfig/GameConfigConstants.h b/src/source/Data/GameConfig/GameConfigConstants.h index dbf0a6a56a..225ad1fd5e 100644 --- a/src/source/Data/GameConfig/GameConfigConstants.h +++ b/src/source/Data/GameConfig/GameConfigConstants.h @@ -37,6 +37,8 @@ namespace CfgKeys // UI inline constexpr wchar_t CfgKeyUILocale[] = L"Locale"; inline constexpr wchar_t CfgKeyFont[] = L"Font"; + // Whether item names follow the UI language; off keeps them English. + inline constexpr wchar_t CfgKeyTranslateItemNames[] = L"TranslateItemNames"; // Active RmlUi theme name ("legacy"/"modern"), Data/Interface/RmlUi/themes//. Not yet // a full runtime hot-swap -- read once at startup. See UI::RmlBridge::RmlTheme. inline constexpr wchar_t CfgKeyRmlTheme[] = L"RmlTheme"; @@ -111,6 +113,7 @@ namespace CfgDefaults // I18N locale code; "en" is the default the resx generator falls back to. inline constexpr wchar_t CfgDefaultUILocale[] = L"en"; + inline constexpr bool CfgDefaultTranslateItemNames = true; // UI font family name. Empty = each platform's built-in default (Tahoma on // Windows, fontconfig "sans-serif" on Linux), so the look is unchanged until diff --git a/src/source/Data/GameData/ItemData/ItemNames.cpp b/src/source/Data/GameData/ItemData/ItemNames.cpp index f577b83a88..180cbb6f35 100644 --- a/src/source/Data/GameData/ItemData/ItemNames.cpp +++ b/src/source/Data/GameData/ItemData/ItemNames.cpp @@ -22,6 +22,11 @@ bool HasNameIn(const ItemDefinition& definition, std::string_view locale) return !GetOwnName(definition, locale).empty(); } +std::string_view GetItemNameLocale(std::string_view uiLocale, bool translateItemNames) +{ + return translateItemNames ? uiLocale : LocalizedString::NeutralLocale; +} + std::vector CountItemNames(std::span items, std::span locales) { std::vector counts; diff --git a/src/source/Data/GameData/ItemData/ItemNames.h b/src/source/Data/GameData/ItemData/ItemNames.h index f583701108..b25416740f 100644 --- a/src/source/Data/GameData/ItemData/ItemNames.h +++ b/src/source/Data/GameData/ItemData/ItemNames.h @@ -19,6 +19,10 @@ const std::string& GetOwnName(const ItemDefinition& definition, std::string_view // Whether the item has a name of its own in the language (GetOwnName). bool HasNameIn(const ItemDefinition& definition, std::string_view locale); +// The language the game shows item names in: the UI language, or English when +// the player turned translated item names off in the options window. +std::string_view GetItemNameLocale(std::string_view uiLocale, bool translateItemNames); + struct ItemNameCount { std::string locale; diff --git a/src/source/UI/Inventory/ItemHelpContentKey.cpp b/src/source/UI/Inventory/ItemHelpContentKey.cpp index 6515e60d94..e4f2ddf0a3 100644 --- a/src/source/UI/Inventory/ItemHelpContentKey.cpp +++ b/src/source/UI/Inventory/ItemHelpContentKey.cpp @@ -8,6 +8,10 @@ mu::ui::window::ItemHelpContentKey mu::ui::window::GetItemHelpContentKey(int itemType) { - return {itemType, g_ItemDatabase.GetVersion(), I18N::LocaleIndex(I18N::GetCurrentLocale()), Hero->Class, + return {itemType, + g_ItemDatabase.GetVersion(), + I18N::LocaleIndex(I18N::GetCurrentLocale()), + I18N::LocaleIndex(g_ItemDatabase.GetDisplayLocale().c_str()), + Hero->Class, GetItemInfoHeroStats()}; } diff --git a/src/source/UI/Inventory/ItemHelpContentKey.h b/src/source/UI/Inventory/ItemHelpContentKey.h index ca7bffb43b..fce7f4fd12 100644 --- a/src/source/UI/Inventory/ItemHelpContentKey.h +++ b/src/source/UI/Inventory/ItemHelpContentKey.h @@ -5,13 +5,15 @@ namespace mu::ui::window { // What the item help window's lines and table are built from: the item and its data (the item -// editor and a reload raise the item database's version), the language, and the hero's class, -// level and stats. The window builds them again only when this changes. +// editor and a reload raise the item database's version), the language, the language of the item +// names (English while translated item names are off), and the hero's class, level and stats. The +// window builds them again only when this changes. struct ItemHelpContentKey { int itemType = -1; int itemDataVersion = 0; int localeIndex = 0; + int itemNameLocaleIndex = 0; CLASS_TYPE heroClass{}; ItemInfoHeroStats heroStats{}; diff --git a/tests/data/test_item_names.cpp b/tests/data/test_item_names.cpp index 9b49c5c653..7583765947 100644 --- a/tests/data/test_item_names.cpp +++ b/tests/data/test_item_names.cpp @@ -36,6 +36,13 @@ TEST_CASE("An item has a name in a language when it has a translation, also one CHECK(GetOwnName(kris, "de").empty()); } +TEST_CASE("Item names are shown in the UI language, or in English while translated item names are off [data][items]") +{ + CHECK(GetItemNameLocale("de", true) == "de"); + CHECK(GetItemNameLocale("de", false) == "en"); + CHECK(GetItemNameLocale("en", false) == "en"); +} + TEST_CASE("Item names are counted by language over the items that exist [data][items]") { const std::vector items = {MakeNamedItem(0, "Kris||pt=Kris"), diff --git a/tests/data/test_item_stats.cpp b/tests/data/test_item_stats.cpp index 859c35c109..3f6112e55d 100644 --- a/tests/data/test_item_stats.cpp +++ b/tests/data/test_item_stats.cpp @@ -924,6 +924,12 @@ TEST_CASE("The item help window builds its content again only when what it shows I18N::SetLocale("en"); CHECK(GetItemHelpContentKey(ITEM_BONE_BLADE) == shown); + // Translated item names turned on or off: only the item names change language. + g_ItemDatabase.SetDisplayLocale("pt"); + CHECK(GetItemHelpContentKey(ITEM_BONE_BLADE) != shown); + g_ItemDatabase.SetDisplayLocale("en"); + CHECK(GetItemHelpContentKey(ITEM_BONE_BLADE) == shown); + // An item editor change, which stores the item in the item database. g_ItemDatabase.Set(*g_ItemDatabase.Find(ITEM_BONE_BLADE)); CHECK(GetItemHelpContentKey(ITEM_BONE_BLADE) != shown); From 4d006e7165116decfc078208ec17a51f12dc6ade Mon Sep 17 00:00:00 2001 From: Mosch0512 Date: Thu, 22 Oct 2026 22:41:12 +0200 Subject: [PATCH 02/10] feat(options): a "Translate item names" checkbox under the language The Interface tab shows it as a part of the language row, in both themes. Unticked, item names switch to English at once while every other text keeps the chosen language; the setting is saved in config.ini. The label is in all eleven game languages. --- src/Localization/Game.de.resx | 3 ++ src/Localization/Game.en.resx | 3 ++ src/Localization/Game.es.resx | 3 ++ src/Localization/Game.id.resx | 3 ++ src/Localization/Game.ja.resx | 3 ++ src/Localization/Game.pl.resx | 3 ++ src/Localization/Game.pt.resx | 3 ++ src/Localization/Game.ru.resx | 3 ++ src/Localization/Game.tl.resx | 3 ++ src/Localization/Game.uk.resx | 3 ++ src/Localization/Game.zh-TW.resx | 3 ++ .../Data/Interface/RmlUi/option_window.rml | 6 ++++ .../RmlUi/themes/modern/option_window.rml | 4 +++ src/source/UI/Options/OptionWindow.cpp | 32 +++++++++++++++++++ src/source/UI/Options/OptionWindow.h | 6 ++++ 15 files changed, 81 insertions(+) diff --git a/src/Localization/Game.de.resx b/src/Localization/Game.de.resx index 78f4c389ab..2146af929d 100644 --- a/src/Localization/Game.de.resx +++ b/src/Localization/Game.de.resx @@ -1430,6 +1430,9 @@ Sprache + + Gegenstandsnamen übersetzen + Fenstermodus diff --git a/src/Localization/Game.en.resx b/src/Localization/Game.en.resx index d4be915557..bb0d6da592 100644 --- a/src/Localization/Game.en.resx +++ b/src/Localization/Game.en.resx @@ -1430,6 +1430,9 @@ Language + + Translate item names + Windowed mode diff --git a/src/Localization/Game.es.resx b/src/Localization/Game.es.resx index adb23d861a..5b36c9cc9d 100644 --- a/src/Localization/Game.es.resx +++ b/src/Localization/Game.es.resx @@ -1430,6 +1430,9 @@ Idioma + + Traducir nombres de objetos + Modo ventana diff --git a/src/Localization/Game.id.resx b/src/Localization/Game.id.resx index 161158eb79..05ef4a34ab 100644 --- a/src/Localization/Game.id.resx +++ b/src/Localization/Game.id.resx @@ -1430,6 +1430,9 @@ Bahasa + + Terjemahkan nama barang + Mode jendela diff --git a/src/Localization/Game.ja.resx b/src/Localization/Game.ja.resx index 432e99c191..f9092237ef 100644 --- a/src/Localization/Game.ja.resx +++ b/src/Localization/Game.ja.resx @@ -11112,4 +11112,7 @@ Bonus: %ls + + アイテム名を翻訳する + diff --git a/src/Localization/Game.pl.resx b/src/Localization/Game.pl.resx index edb04ab13f..b55f029f68 100644 --- a/src/Localization/Game.pl.resx +++ b/src/Localization/Game.pl.resx @@ -1430,6 +1430,9 @@ Język + + Tłumacz nazwy przedmiotów + Tryb okienkowy diff --git a/src/Localization/Game.pt.resx b/src/Localization/Game.pt.resx index 6167a57bd6..c9236efbbe 100644 --- a/src/Localization/Game.pt.resx +++ b/src/Localization/Game.pt.resx @@ -1430,6 +1430,9 @@ Idioma + + Traduzir nomes dos itens + Modo janela diff --git a/src/Localization/Game.ru.resx b/src/Localization/Game.ru.resx index 7ab88d9cb2..130c0833da 100644 --- a/src/Localization/Game.ru.resx +++ b/src/Localization/Game.ru.resx @@ -1430,6 +1430,9 @@ Язык + + Переводить названия предметов + Оконный режим diff --git a/src/Localization/Game.tl.resx b/src/Localization/Game.tl.resx index 861d50af84..43262ed2f7 100644 --- a/src/Localization/Game.tl.resx +++ b/src/Localization/Game.tl.resx @@ -1430,6 +1430,9 @@ Wika + + Isalin ang mga pangalan ng item + Windowed mode diff --git a/src/Localization/Game.uk.resx b/src/Localization/Game.uk.resx index e564c89401..45e830f1eb 100644 --- a/src/Localization/Game.uk.resx +++ b/src/Localization/Game.uk.resx @@ -1430,6 +1430,9 @@ Мова + + Перекладати назви предметів + Віконний режим diff --git a/src/Localization/Game.zh-TW.resx b/src/Localization/Game.zh-TW.resx index 83a1a96fd8..589721da31 100644 --- a/src/Localization/Game.zh-TW.resx +++ b/src/Localization/Game.zh-TW.resx @@ -1430,6 +1430,9 @@ 語言 + + 翻譯物品名稱 + 視窗模式 diff --git a/src/bin/Data/Interface/RmlUi/option_window.rml b/src/bin/Data/Interface/RmlUi/option_window.rml index a2fb7d83b5..f47d050e5a 100644 --- a/src/bin/Data/Interface/RmlUi/option_window.rml +++ b/src/bin/Data/Interface/RmlUi/option_window.rml @@ -438,6 +438,12 @@ +
+ {{translate_item_names_label}} +
+
+
+
{{font_row_label}}
diff --git a/src/bin/Data/Interface/RmlUi/themes/modern/option_window.rml b/src/bin/Data/Interface/RmlUi/themes/modern/option_window.rml index 86db825c89..3a594d6fa6 100644 --- a/src/bin/Data/Interface/RmlUi/themes/modern/option_window.rml +++ b/src/bin/Data/Interface/RmlUi/themes/modern/option_window.rml @@ -413,6 +413,10 @@
+
+ {{translate_item_names_label}} +
+
{{font_row_label}}
diff --git a/src/source/UI/Options/OptionWindow.cpp b/src/source/UI/Options/OptionWindow.cpp index 6e1eace52b..dcd600a742 100644 --- a/src/source/UI/Options/OptionWindow.cpp +++ b/src/source/UI/Options/OptionWindow.cpp @@ -6,6 +6,7 @@ #include "Render/Textures/TexturePack.h" #include "Render/Textures/ZzzTexture.h" #include "Audio/DSPlaySound.h" +#include "Data/DataHandler/ItemData/ItemDataHandler.h" #include "Data/GameConfig/GameConfig.h" #include "Data/GameConfig/GameConfigConstants.h" #include "Render/Look/LookPresetSetting.h" @@ -307,6 +308,7 @@ mu::ui::window::COptionWindow::COptionWindow() m_iResolutionIndex = 0; m_bWindowedMode = (g_bUseWindowMode == TRUE); m_iLanguageIndex = FindCurrentLanguageIndex(); + m_bTranslateItemNames = GameConfig::GetInstance().GetTranslateItemNames(); m_iFontIndex = FindCurrentFontIndex(); m_iActiveTab = 0; @@ -398,6 +400,8 @@ void mu::ui::window::COptionWindow::BindRmlModel(Rml::DataModelConstructor& c, O c.Bind("language_index", &model.languageIndex); c.Bind("language_row_label", &model.languageRowLabel); c.Bind("language_value_label", &model.languageValueLabel); + c.Bind("translate_item_names", &model.translateItemNames); + c.Bind("translate_item_names_label", &model.translateItemNamesLabel); c.Bind("font_labels", &model.fontLabels); c.Bind("font_index", &model.fontIndex); c.Bind("font_row_label", &model.fontRowLabel); @@ -517,6 +521,9 @@ void mu::ui::window::COptionWindow::BindRmlModel(Rml::DataModelConstructor& c, O [this](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) { RmlToggleRenderAllEffects(); }); c.BindEventCallback("option_toggle_windowed_mode", [this](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) { RmlToggleWindowedMode(); }); + c.BindEventCallback("option_toggle_translate_item_names", + [this](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) + { RmlToggleTranslateItemNames(); }); c.BindEventCallback("option_gauge", [this](Rml::DataModelHandle, Rml::Event& ev, const Rml::VariantList& args) @@ -771,6 +778,7 @@ void mu::ui::window::COptionWindow::OpenningProcess() // exposed through, so opening this window always reflects the current live truth. InitResolutionCombo(); m_iLanguageIndex = FindCurrentLanguageIndex(); + m_bTranslateItemNames = GameConfig::GetInstance().GetTranslateItemNames(); m_iFontIndex = FindCurrentFontIndex(); m_bWindowedMode = (g_bUseWindowMode == TRUE); @@ -974,6 +982,29 @@ void mu::ui::window::COptionWindow::RmlLanguageChanged(int index) ApplyLanguage(); } +void mu::ui::window::COptionWindow::RmlToggleTranslateItemNames() +{ + m_bTranslateItemNames = !m_bTranslateItemNames; + GameConfig::GetInstance().SetTranslateItemNames(m_bTranslateItemNames); + GameConfig::GetInstance().Save(); + CItemDataHandler::GetInstance().RefreshItemNames(); +} + +void mu::ui::window::COptionWindow::SyncTranslateItemNamesRow(OptionRmlModel& model) +{ + const Rml::String label = StringUtils::WideToNarrow(I18N::Game::TranslateItemNames); + if (model.translateItemNamesLabel != label) + { + model.translateItemNamesLabel = label; + m_RmlView.MarkDirty("translate_item_names_label"); + } + if (model.translateItemNames != m_bTranslateItemNames) + { + model.translateItemNames = m_bTranslateItemNames; + m_RmlView.MarkDirty("translate_item_names"); + } +} + void mu::ui::window::COptionWindow::RmlFontChanged(int index) { // See RmlResolutionChanged's own comment -- same settle-frame guard applies here. @@ -1769,6 +1800,7 @@ void mu::ui::window::COptionWindow::SyncRmlModel() } if (model.languageIndex != m_iLanguageIndex) { model.languageIndex = m_iLanguageIndex; m_RmlView.MarkDirty("language_index"); } syncDropdownValue(model.languageValueLabel, "language_value_label", model.languageLabels, model.languageIndex); + SyncTranslateItemNamesRow(model); // Rebuilt every sync (not just once) so the localized "Default" entry follows a live language // switch, same as native's own GetFontLabels(). diff --git a/src/source/UI/Options/OptionWindow.h b/src/source/UI/Options/OptionWindow.h index 5975032d38..2dc6350e9d 100644 --- a/src/source/UI/Options/OptionWindow.h +++ b/src/source/UI/Options/OptionWindow.h @@ -164,6 +164,8 @@ namespace mu::ui::window void RmlGaugeEvent(Rml::Event& event, int gauge); void RmlResolutionChanged(int index); void RmlLanguageChanged(int index); + // The checkbox under the language: item names in the UI language, or English. + void RmlToggleTranslateItemNames(); void RmlFontChanged(int index); // Graphics tab -- DXP-23's per-system effect-cost toggles (MainScene.h), promoted from // console-only ($effects ...) diagnostics to a persisted, in-game-options setting. @@ -299,6 +301,8 @@ namespace mu::ui::window int languageIndex = 0; Rml::String languageRowLabel; Rml::String languageValueLabel; + bool translateItemNames = true; + Rml::String translateItemNamesLabel; std::vector fontLabels; int fontIndex = 0; Rml::String fontRowLabel; @@ -401,6 +405,7 @@ namespace mu::ui::window }; void BindRmlModel(Rml::DataModelConstructor& c, OptionRmlModel& model); void SyncImpliedEffectRows(OptionRmlModel& model); + void SyncTranslateItemNamesRow(OptionRmlModel& model); void SyncTuningModel(OptionRmlModel& model); void OnRmlBuilt(); UI::RmlBridge::ThemedView m_RmlView{"option_window", @@ -428,6 +433,7 @@ namespace mu::ui::window int m_iResolutionIndex; bool m_bWindowedMode; int m_iLanguageIndex; + bool m_bTranslateItemNames; int m_iFontIndex; std::vector m_resolutions; From 023e0be87b14b0dbdbd51e996bf6f72af9b7e6cf Mon Sep 17 00:00:00 2001 From: Mosch0512 Date: Thu, 22 Oct 2026 22:41:12 +0200 Subject: [PATCH 03/10] docs: the Translate item names option --- docs/options-window.md | 7 +++++-- docs/translation-system.md | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/options-window.md b/docs/options-window.md index b5dc7a9ce1..61ac98452e 100644 --- a/docs/options-window.md +++ b/docs/options-window.md @@ -50,7 +50,7 @@ The window has five tabs: | Graphics | Quality: Enhanced graphics, anti-aliasing, anisotropic filtering, render level. Effects: the effect toggles. Shadows: the shadow toggles | | Audio | Sound and music volume, whisper sound | | Gameplay | Auto attack, slide help | -| Interface | Language, font, UI theme, FPS counter, debug info | +| Interface | Language, translate item names, font, UI theme, FPS counter, debug info | Each setting is a row with its name on the left and its control on the right, aligned in two columns. A tab's settings scroll when they don't fit, @@ -113,7 +113,10 @@ window writes to these sections: own, through a temporary file, when a slider is let go; see "Tuning it yourself" in [`enhanced-look.md`](enhanced-look.md). - `[UI]` - `Locale`, `Font`, `RmlTheme` and `UIScalePercent` from the Display and - Interface tabs. `EnableAnimationTaskPool` (`0`/`1`, set by hand only, like the + Interface tabs. `TranslateItemNames` (default `1`): the "Translate item names" + checkbox under the language, applied at once; unticked, item names stay + English while the rest of the UI is in the chosen language. + `EnableAnimationTaskPool` (`0`/`1`, set by hand only, like the `--enable-taskpool` command-line flag) animates characters on worker threads. - `[Audio]` - volumes. diff --git a/docs/translation-system.md b/docs/translation-system.md index fa10c2b441..ac8d1215db 100644 --- a/docs/translation-system.md +++ b/docs/translation-system.md @@ -326,7 +326,10 @@ Item names are not in the `.resx` files. They are part of the item data `src/bin/Data/Items/*.json` holds the names by language code, using the same codes as the UI texts. The game shows the name for the active locale and falls back to English, like the UI texts; switching the locale at runtime -updates item names too. +updates item names too. Players who know the items by their English names can +untick **Translate item names** under the language in the options window +(Interface tab): item names then stay English, also in the item editor, while +every other text follows the language. ### In the JSON files From 87f8bfcd49b6553292abf76c0e5ed2e0eac0215d Mon Sep 17 00:00:00 2001 From: Mosch0512 Date: Thu, 22 Oct 2026 23:10:31 +0200 Subject: [PATCH 04/10] refactor(items): the item data handler keeps the Translate item names flag itself The handler no longer reads GameConfig: start-up sets the flag from [UI] TranslateItemNames and the options window sets it when the checkbox changes. Tests that load item data no longer depend on the config.ini next to the test program, and a test can turn the option off. --- src/source/App/Platform/Windows/Winmain.cpp | 2 ++ .../DataHandler/ItemData/ItemDataHandler.cpp | 21 ++++++++++++----- .../DataHandler/ItemData/ItemDataHandler.h | 23 +++++++++++++++---- src/source/UI/Options/OptionWindow.cpp | 2 +- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/source/App/Platform/Windows/Winmain.cpp b/src/source/App/Platform/Windows/Winmain.cpp index 615575dbfd..ecced7b133 100644 --- a/src/source/App/Platform/Windows/Winmain.cpp +++ b/src/source/App/Platform/Windows/Winmain.cpp @@ -26,6 +26,7 @@ #include #include #include "Core/Platform/WinIni.h" // private-profile (.ini) API +#include "Data/DataHandler/ItemData/ItemDataHandler.h" #include "Data/GameConfig/GameConfig.h" #include "UI/Core/UIManager.h" #include "Render/Textures/ZzzOpenglUtil.h" @@ -2300,6 +2301,7 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nC std::string uiLocale(uiLocaleW.begin(), uiLocaleW.end()); I18N::SetLocale(uiLocale.c_str()); } + g_ItemDataHandler.SetTranslateItemNames(GameConfig::GetInstance().GetTranslateItemNames()); #ifdef _EDITOR // Initialize MU Editor with the live SDL GPU window. diff --git a/src/source/Data/DataHandler/ItemData/ItemDataHandler.cpp b/src/source/Data/DataHandler/ItemData/ItemDataHandler.cpp index 58d99f07fb..ba4ad4fa24 100644 --- a/src/source/Data/DataHandler/ItemData/ItemDataHandler.cpp +++ b/src/source/Data/DataHandler/ItemData/ItemDataHandler.cpp @@ -7,7 +7,6 @@ #include "Core/Text/Utf8.h" #include "Core/Utilities/Log/MuLogger.h" #include "Data/DataHandler/DataIssueReport.h" -#include "Data/GameConfig/GameConfig.h" #include "Data/GameData/ItemData/ItemAttributeConversion.h" #include "Data/GameData/EffectData/GlowColorList.h" #include "Data/GameData/ItemData/ItemDataValidation.h" @@ -64,11 +63,6 @@ std::unique_ptr BuildItemAttributes(std::string_view locale) return attributes; } -std::string_view ItemNameLocale() -{ - return GetItemNameLocale(I18N::GetCurrentLocale(), GameConfig::GetInstance().GetTranslateItemNames()); -} - #ifdef _EDITOR // The tables and profiles in the writer's order (by name), so a file read as it // was written and one the editor stored compare equal (the undo history). @@ -144,6 +138,7 @@ bool CItemDataHandler::Load(std::string& errorMessage) g_ItemDatabase.SetStatProfiles(std::move(statProfiles)); g_ItemDatabase.Build(result.items); FillItemAttributes(); + m_loaded = true; RegisterLocaleObserver(); MU_LOG_INFO(mu::log::Get("data"), "Loaded {} items from {} in {:.1f} ms (item database build {:.2f} ms)", @@ -217,6 +212,20 @@ void CItemDataHandler::OnLocaleChanged(void* context) noexcept static_cast(context)->RefreshItemNames(); } +void CItemDataHandler::SetTranslateItemNames(bool translate) +{ + m_translateItemNames = translate; + if (m_loaded) + { + RefreshItemNames(); + } +} + +std::string_view CItemDataHandler::ItemNameLocale() const +{ + return Data::Items::GetItemNameLocale(I18N::GetCurrentLocale(), m_translateItemNames); +} + void CItemDataHandler::RefreshItemNames() { g_ItemDatabase.SetDisplayLocale(ItemNameLocale()); diff --git a/src/source/Data/DataHandler/ItemData/ItemDataHandler.h b/src/source/Data/DataHandler/ItemData/ItemDataHandler.h index 467b942b7b..8c2196902f 100644 --- a/src/source/Data/DataHandler/ItemData/ItemDataHandler.h +++ b/src/source/Data/DataHandler/ItemData/ItemDataHandler.h @@ -21,16 +21,21 @@ // Owns loading the item data (Data/Items/*.json) into the item database and // the item models (Data/Items/Models/*.json) into the item model database, and // keeps ItemAttribute[], the table most game code still reads, filled from -// the database. Item names follow the UI locale, or stay English when the -// player turned translated item names off ([UI] TranslateItemNames). +// the database. Item names follow the UI locale, or stay English while +// translated item names are off (SetTranslateItemNames). class CItemDataHandler { public: static CItemDataHandler& GetInstance(); - // Shows the item names in the language the UI locale and the - // TranslateItemNames setting give, after either changed. - void RefreshItemNames(); + // Whether item names follow the UI locale; off shows them in English. The + // game sets it from [UI] TranslateItemNames at start and when the option + // changes. Once items are loaded, the names change at once. + void SetTranslateItemNames(bool translate); + bool GetTranslateItemNames() const + { + return m_translateItemNames; + } // Data/Local//Item_.bmd (legacy item files). static std::wstring GetItemFilePath(const std::wstring& language); @@ -190,6 +195,11 @@ class CItemDataHandler ~CItemDataHandler() = default; void FillItemAttributes(); + // The language of the item names: the UI locale, or English while + // translated item names are off. + std::string_view ItemNameLocale() const; + // Shows the item names in that language. + void RefreshItemNames(); void RegisterLocaleObserver(); static void OnLocaleChanged(void* context) noexcept; @@ -256,6 +266,9 @@ class CItemDataHandler #endif bool m_localeObserverRegistered = false; + bool m_translateItemNames = true; + // Load succeeded once: there are item names to show. + bool m_loaded = false; // Prevent copying CItemDataHandler(const CItemDataHandler&) = delete; diff --git a/src/source/UI/Options/OptionWindow.cpp b/src/source/UI/Options/OptionWindow.cpp index dcd600a742..903f2371c9 100644 --- a/src/source/UI/Options/OptionWindow.cpp +++ b/src/source/UI/Options/OptionWindow.cpp @@ -987,7 +987,7 @@ void mu::ui::window::COptionWindow::RmlToggleTranslateItemNames() m_bTranslateItemNames = !m_bTranslateItemNames; GameConfig::GetInstance().SetTranslateItemNames(m_bTranslateItemNames); GameConfig::GetInstance().Save(); - CItemDataHandler::GetInstance().RefreshItemNames(); + g_ItemDataHandler.SetTranslateItemNames(m_bTranslateItemNames); } void mu::ui::window::COptionWindow::SyncTranslateItemNamesRow(OptionRmlModel& model) From ab09c6d112b40c37c51be6d3e705621cb4107966 Mon Sep 17 00:00:00 2001 From: Mosch0512 Date: Thu, 22 Oct 2026 23:11:51 +0200 Subject: [PATCH 05/10] test(items): translated item names off reach the item names and are saved Loading with the option off, a language change while it is off and turning it on and off again each leave the item names in the expected language. The display settings contract checks that GameConfig reads and writes [UI] TranslateItemNames and that start-up gives it to the item data handler. --- tests/data/test_item_database_files.cpp | 33 +++++++++++++++++++ tests/ui/test_display_settings_contract.cmake | 13 ++++++++ 2 files changed, 46 insertions(+) diff --git a/tests/data/test_item_database_files.cpp b/tests/data/test_item_database_files.cpp index 70a4a0d0ff..27fc534449 100644 --- a/tests/data/test_item_database_files.cpp +++ b/tests/data/test_item_database_files.cpp @@ -252,6 +252,39 @@ TEST_CASE("Loading item data fills ItemAttribute in the UI locale [data][items]" CHECK(client.Name(KrisType) == L"Kris"); } +// Turns translated item names back on when the test ends, as the other tests +// expect them. +class TranslatedItemNamesScope +{ +public: + ~TranslatedItemNamesScope() + { + g_ItemDataHandler.SetTranslateItemNames(true); + } +}; + +TEST_CASE("Item names stay English while translated item names are off, also after a language change [data][items]") +{ + ClientDataScope client(ShippedClientDirectory); + TranslatedItemNamesScope translatedNames; + I18N::SetLocale("pt"); + g_ItemDataHandler.SetTranslateItemNames(false); + + std::string errorMessage; + REQUIRE(g_ItemDataHandler.Load(errorMessage)); + CHECK(client.Name(BladeType) == L"Blade"); + + I18N::SetLocale("de"); + CHECK(client.Name(BladeType) == L"Blade"); + + g_ItemDataHandler.SetTranslateItemNames(true); + CHECK(client.Name(BladeType) == L"Klinge"); + + g_ItemDataHandler.SetTranslateItemNames(false); + CHECK(client.Name(BladeType) == L"Blade"); + CHECK(g_ItemDatabase.Find(BladeType)->name == L"Blade"); +} + TEST_CASE("Item data errors stop loading [data][items]") { TemporaryClientFolder folder; diff --git a/tests/ui/test_display_settings_contract.cmake b/tests/ui/test_display_settings_contract.cmake index c6e77f3560..76f2785ee1 100644 --- a/tests/ui/test_display_settings_contract.cmake +++ b/tests/ui/test_display_settings_contract.cmake @@ -145,6 +145,19 @@ foreach(required_text IN ITEMS require_text(config_source "${required_text}" "GameConfig.cpp must persist VSync through") endforeach() +# Translated item names ([UI] TranslateItemNames, on by default): saved with the other [UI] keys +# and given to the item data handler at start-up. +require_text(config_constants "CfgDefaultTranslateItemNames = true;" + "GameConfigConstants.h must turn translated item names on by default") +foreach(required_text IN ITEMS + "m_translateItemNames = ReadBool(CfgSectionUI, CfgKeyTranslateItemNames, CfgDefaultTranslateItemNames);" + "WriteBool(CfgSectionUI, CfgKeyTranslateItemNames, m_translateItemNames);") + require_text(config_source "${required_text}" "GameConfig.cpp must persist translated item names through") +endforeach() +require_text(winmain_source + "g_ItemDataHandler.SetTranslateItemNames(GameConfig::GetInstance().GetTranslateItemNames());" + "Winmain.cpp must apply translated item names at startup") + # A missing or invalid Look counts as CfgDefaultLook, which is Classic: Enhanced is opt-in. require_text(config_constants "CfgDefaultLook[] = L\"Classic\"" "GameConfigConstants.h must default the look preset to Classic") From 4dbe07344efc3b96a825c2f7762f1c422ad0fb56 Mon Sep 17 00:00:00 2001 From: Mosch0512 Date: Thu, 22 Oct 2026 23:13:55 +0200 Subject: [PATCH 06/10] refactor(items): one item name version for the views that keep item names ItemDatabase::GetNameVersion counts the changes of the shown item names: a stored item or another language of the names. The item help window, the namesake labels and the Item Categories rows compare it instead of each choosing a language to compare. A language change does not raise the item version, so the item tools' open edits stay. --- src/MuEditor/UI/ItemTools/ItemNamesakes.cpp | 4 ++-- src/MuEditor/UI/ItemTools/ItemNamesakes.h | 4 ++-- .../UI/ItemTools/MuItemCategoriesUI.cpp | 6 +++--- .../UI/ItemTools/MuItemCategoriesUI.h | 2 +- .../Data/GameData/ItemData/ItemDatabase.cpp | 6 ++++++ .../Data/GameData/ItemData/ItemDatabase.h | 10 ++++++++++ .../UI/Inventory/ItemHelpContentKey.cpp | 2 +- src/source/UI/Inventory/ItemHelpContentKey.h | 8 ++++---- tests/data/test_item_database.cpp | 19 +++++++++++++++++++ tests/data/test_item_stats.cpp | 17 ++++++++++------- 10 files changed, 58 insertions(+), 20 deletions(-) diff --git a/src/MuEditor/UI/ItemTools/ItemNamesakes.cpp b/src/MuEditor/UI/ItemTools/ItemNamesakes.cpp index a37f56a3d4..d49166ecae 100644 --- a/src/MuEditor/UI/ItemTools/ItemNamesakes.cpp +++ b/src/MuEditor/UI/ItemTools/ItemNamesakes.cpp @@ -61,12 +61,12 @@ const std::string& ItemNamesakes::GetQualifier(int itemType) void ItemNamesakes::Update() { - if (m_version == g_ItemDatabase.GetVersion() && m_locale == g_ItemDatabase.GetDisplayLocale()) + if (m_version == g_ItemDatabase.GetVersion() && m_nameVersion == g_ItemDatabase.GetNameVersion()) { return; } m_version = g_ItemDatabase.GetVersion(); - m_locale = g_ItemDatabase.GetDisplayLocale(); + m_nameVersion = g_ItemDatabase.GetNameVersion(); const auto slots = g_ItemDatabase.GetAllSlots(); m_qualifiers.assign(slots.size(), std::string()); diff --git a/src/MuEditor/UI/ItemTools/ItemNamesakes.h b/src/MuEditor/UI/ItemTools/ItemNamesakes.h index 4f8aa2ac5c..c4dae6069d 100644 --- a/src/MuEditor/UI/ItemTools/ItemNamesakes.h +++ b/src/MuEditor/UI/ItemTools/ItemNamesakes.h @@ -24,12 +24,12 @@ class ItemNamesakes private: ItemNamesakes() = default; - // Again when the items or the UI language changed. + // Again when the items or their names changed. void Update(); std::vector m_qualifiers; int m_version = -1; - std::string m_locale; + int m_nameVersion = -1; }; // Draws the qualifier after the item's name on the same line, greyed out; diff --git a/src/MuEditor/UI/ItemTools/MuItemCategoriesUI.cpp b/src/MuEditor/UI/ItemTools/MuItemCategoriesUI.cpp index e2fdca7704..6b216dc717 100644 --- a/src/MuEditor/UI/ItemTools/MuItemCategoriesUI.cpp +++ b/src/MuEditor/UI/ItemTools/MuItemCategoriesUI.cpp @@ -282,14 +282,14 @@ void CMuItemCategoriesUI::UpdateTagLabels() void CMuItemCategoriesUI::UpdateRows() { - const std::string& locale = g_ItemDatabase.GetDisplayLocale(); - if (m_rowsVersion == g_ItemDatabase.GetVersion() && m_rowsTag == m_tag && m_rowsLocale == locale) + if (m_rowsVersion == g_ItemDatabase.GetVersion() && m_rowsNameVersion == g_ItemDatabase.GetNameVersion() && + m_rowsTag == m_tag) { return; } m_rowsVersion = g_ItemDatabase.GetVersion(); + m_rowsNameVersion = g_ItemDatabase.GetNameVersion(); m_rowsTag = m_tag; - m_rowsLocale = locale; m_rows.clear(); for (const int itemType : g_ItemDatabase.GetItemTypesWithTag(m_tag)) { diff --git a/src/MuEditor/UI/ItemTools/MuItemCategoriesUI.h b/src/MuEditor/UI/ItemTools/MuItemCategoriesUI.h index d418a9a984..a2ef54b28f 100644 --- a/src/MuEditor/UI/ItemTools/MuItemCategoriesUI.h +++ b/src/MuEditor/UI/ItemTools/MuItemCategoriesUI.h @@ -54,8 +54,8 @@ class CMuItemCategoriesUI int m_labelsVersion = -1; std::vector m_rows; Data::Items::ItemTag m_rowsTag{}; - std::string m_rowsLocale; int m_rowsVersion = -1; + int m_rowsNameVersion = -1; int m_shownSerial = -1; }; diff --git a/src/source/Data/GameData/ItemData/ItemDatabase.cpp b/src/source/Data/GameData/ItemData/ItemDatabase.cpp index 913b898f70..5eb137410a 100644 --- a/src/source/Data/GameData/ItemData/ItemDatabase.cpp +++ b/src/source/Data/GameData/ItemData/ItemDatabase.cpp @@ -46,6 +46,11 @@ void ItemDatabase::Build(std::span definitions) void ItemDatabase::SetDisplayLocale(std::string_view locale) { + if (m_displayLocale == locale) + { + return; + } + ++m_nameVersion; m_displayLocale = std::string(locale); for (ItemDefinition& definition : m_definitions) { @@ -178,6 +183,7 @@ void ItemDatabase::UpdateDisplayName(ItemDefinition& definition) const void ItemDatabase::Store(int itemType, ItemDefinition definition) { ++m_version; + ++m_nameVersion; definition.group = GetItemGroup(itemType); definition.number = GetItemNumber(itemType); UpdateDisplayName(definition); diff --git a/src/source/Data/GameData/ItemData/ItemDatabase.h b/src/source/Data/GameData/ItemData/ItemDatabase.h index 93c545748d..e6b610e41f 100644 --- a/src/source/Data/GameData/ItemData/ItemDatabase.h +++ b/src/source/Data/GameData/ItemData/ItemDatabase.h @@ -142,6 +142,15 @@ class ItemDatabase return m_version; } + // Counts the changes of the item names shown: a stored item or another + // language of the names (SetDisplayLocale). Views that keep item names + // take them again when it changes. Unlike GetVersion, a language change + // leaves the item tools' open edits alone. + int GetNameVersion() const + { + return m_nameVersion; + } + // Item editor changes. Set replaces the item at the definition's // (group, number); an item without names counts as not existing. void Set(const ItemDefinition& definition); @@ -193,6 +202,7 @@ class ItemDatabase std::string m_displayLocale{LocalizedString::NeutralLocale}; int m_existingItemCount = 0; int m_version = 0; + int m_nameVersion = 0; }; } // namespace Data::Items diff --git a/src/source/UI/Inventory/ItemHelpContentKey.cpp b/src/source/UI/Inventory/ItemHelpContentKey.cpp index e4f2ddf0a3..e680e90772 100644 --- a/src/source/UI/Inventory/ItemHelpContentKey.cpp +++ b/src/source/UI/Inventory/ItemHelpContentKey.cpp @@ -11,7 +11,7 @@ mu::ui::window::ItemHelpContentKey mu::ui::window::GetItemHelpContentKey(int ite return {itemType, g_ItemDatabase.GetVersion(), I18N::LocaleIndex(I18N::GetCurrentLocale()), - I18N::LocaleIndex(g_ItemDatabase.GetDisplayLocale().c_str()), + g_ItemDatabase.GetNameVersion(), Hero->Class, GetItemInfoHeroStats()}; } diff --git a/src/source/UI/Inventory/ItemHelpContentKey.h b/src/source/UI/Inventory/ItemHelpContentKey.h index fce7f4fd12..5c07819552 100644 --- a/src/source/UI/Inventory/ItemHelpContentKey.h +++ b/src/source/UI/Inventory/ItemHelpContentKey.h @@ -5,15 +5,15 @@ namespace mu::ui::window { // What the item help window's lines and table are built from: the item and its data (the item -// editor and a reload raise the item database's version), the language, the language of the item -// names (English while translated item names are off), and the hero's class, level and stats. The -// window builds them again only when this changes. +// editor and a reload raise the item database's version), the language, the item names (their +// version also changes when translated item names are turned on or off), and the hero's class, +// level and stats. The window builds them again only when this changes. struct ItemHelpContentKey { int itemType = -1; int itemDataVersion = 0; int localeIndex = 0; - int itemNameLocaleIndex = 0; + int itemNameVersion = 0; CLASS_TYPE heroClass{}; ItemInfoHeroStats heroStats{}; diff --git a/tests/data/test_item_database.cpp b/tests/data/test_item_database.cpp index 1b64615aef..dcfd6b8713 100644 --- a/tests/data/test_item_database.cpp +++ b/tests/data/test_item_database.cpp @@ -236,6 +236,25 @@ TEST_CASE("Item names follow the display locale with English fallback [data][ite CHECK(potion->name == L"Large Healing Potion"); } +TEST_CASE("Another language of the item names raises the name version but not the item version [data][items]") +{ + ItemDatabase database; + database.Build(std::vector{MakeItem(SwordGroup, 0, "Kris||pt=Cris")}); + const int version = database.GetVersion(); + const int nameVersion = database.GetNameVersion(); + + database.SetDisplayLocale("en"); + CHECK(database.GetNameVersion() == nameVersion); + + database.SetDisplayLocale("pt"); + CHECK(database.GetNameVersion() != nameVersion); + CHECK(database.GetVersion() == version); + + const int ptNameVersion = database.GetNameVersion(); + database.Set(*database.Find(SwordGroup, 0)); + CHECK(database.GetNameVersion() != ptNameVersion); +} + TEST_CASE("Item log names use English names and the item id [data][items]") { const std::vector items = { diff --git a/tests/data/test_item_stats.cpp b/tests/data/test_item_stats.cpp index 3f6112e55d..3c215e97a3 100644 --- a/tests/data/test_item_stats.cpp +++ b/tests/data/test_item_stats.cpp @@ -919,18 +919,21 @@ TEST_CASE("The item help window builds its content again only when what it shows CHECK(GetItemHelpContentKey(ITEM_BONE_BLADE) != shown); help.HeroCharacter().Class = CLASS_WIZARD; + // Another language changes the texts and the item names. Back in English + // the names were taken again; the content then stays until something changes. I18N::SetLocale("pt"); CHECK(GetItemHelpContentKey(ITEM_BONE_BLADE) != shown); I18N::SetLocale("en"); - CHECK(GetItemHelpContentKey(ITEM_BONE_BLADE) == shown); + const auto backInEnglish = GetItemHelpContentKey(ITEM_BONE_BLADE); + CHECK(GetItemHelpContentKey(ITEM_BONE_BLADE) == backInEnglish); + + // An item editor change, which stores the item in the item database. + g_ItemDatabase.Set(*g_ItemDatabase.Find(ITEM_BONE_BLADE)); + CHECK(GetItemHelpContentKey(ITEM_BONE_BLADE) != backInEnglish); // Translated item names turned on or off: only the item names change language. + const auto namesInEnglish = GetItemHelpContentKey(ITEM_BONE_BLADE); g_ItemDatabase.SetDisplayLocale("pt"); - CHECK(GetItemHelpContentKey(ITEM_BONE_BLADE) != shown); + CHECK(GetItemHelpContentKey(ITEM_BONE_BLADE) != namesInEnglish); g_ItemDatabase.SetDisplayLocale("en"); - CHECK(GetItemHelpContentKey(ITEM_BONE_BLADE) == shown); - - // An item editor change, which stores the item in the item database. - g_ItemDatabase.Set(*g_ItemDatabase.Find(ITEM_BONE_BLADE)); - CHECK(GetItemHelpContentKey(ITEM_BONE_BLADE) != shown); } From 8766c1d0ad67765baab525db73ab42c88688a481 Mon Sep 17 00:00:00 2001 From: Mosch0512 Date: Thu, 22 Oct 2026 23:14:44 +0200 Subject: [PATCH 07/10] fix(editor): the effect browser's item list follows translated item names The item list of the effect browser's item preview was built again only when the items, their models or the UI language changed, so turning translated item names on or off kept the old names in the list and its search. It compares the item name version now. --- src/MuEditor/UI/EffectBrowser/EffectPreviewView.cpp | 10 +++++----- src/MuEditor/UI/EffectBrowser/EffectPreviewView.h | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/MuEditor/UI/EffectBrowser/EffectPreviewView.cpp b/src/MuEditor/UI/EffectBrowser/EffectPreviewView.cpp index 65ed5ad9df..5e5e7d2e04 100644 --- a/src/MuEditor/UI/EffectBrowser/EffectPreviewView.cpp +++ b/src/MuEditor/UI/EffectBrowser/EffectPreviewView.cpp @@ -346,16 +346,16 @@ void CEffectPreviewView::RenderItemList() } // The names come from the items as the game has them now; the list is made -// again when the item tools change the items or their models, or the language -// changes. The item models are all opened on the loading screen and stay, so +// again when the item tools change the items or their models, or the item +// names change (another UI language, translated item names turned on or off). The item models are all opened on the loading screen and stay, so // the list does not change with the map; the models of the chosen level are // checked when drawn. void CEffectPreviewView::RefreshItems() { const int version = g_ItemDatabase.GetVersion(); const int modelVersion = g_ItemModelDatabase.GetVersion(); - const char* locale = I18N::GetCurrentLocale(); - if (m_itemsVersion == version && m_itemModelsVersion == modelVersion && m_itemsLocale == locale) + const int namesVersion = g_ItemDatabase.GetNameVersion(); + if (m_itemsVersion == version && m_itemModelsVersion == modelVersion && m_itemNamesVersion == namesVersion) return; // Whether an item can be drawn is known once its changed model is opened // again, before the next frame: until then the list is built again each @@ -363,7 +363,7 @@ void CEffectPreviewView::RefreshItems() const bool modelsOpened = !g_ItemDataHandler.HasModelsToReopen(); m_itemsVersion = modelsOpened ? version : -1; m_itemModelsVersion = modelVersion; - m_itemsLocale = locale; + m_itemNamesVersion = namesVersion; m_items.Build( MAX_ITEM, [](int itemType) { return Core::Text::ToUtf8(ItemAttribute[itemType].Name); }, IsItemDrawable); m_itemMatchesValid = false; diff --git a/src/MuEditor/UI/EffectBrowser/EffectPreviewView.h b/src/MuEditor/UI/EffectBrowser/EffectPreviewView.h index 97098d0371..b819d5d11a 100644 --- a/src/MuEditor/UI/EffectBrowser/EffectPreviewView.h +++ b/src/MuEditor/UI/EffectBrowser/EffectPreviewView.h @@ -81,8 +81,7 @@ class CEffectPreviewView MuEditor::Effects::EffectPreviewItems m_items; int m_itemsVersion = -1; int m_itemModelsVersion = -1; - // The language the item names are in (a pointer into I18N's locales). - const char* m_itemsLocale = nullptr; + int m_itemNamesVersion = -1; char m_itemSearch[64] = {}; std::vector m_itemMatches; bool m_itemMatchesValid = false; From 9a04edb75f7d7723e75d93c0e10b3cb960a342c4 Mon Sep 17 00:00:00 2001 From: Mosch0512 Date: Thu, 22 Oct 2026 23:15:50 +0200 Subject: [PATCH 08/10] fix(editor): the CSV and S6E3 item exports keep the names of the UI language Both exports wrote the names ItemAttribute shows, which are English while translated item names are off. They take the item names of the UI locale from the item database now, as the bmd export takes each language's. --- .../Data/DataHandler/ItemData/ItemDataExportAsCSV.cpp | 11 ++++------- .../Data/DataHandler/ItemData/ItemDataExportAsCSV.h | 5 ++++- .../Data/DataHandler/ItemData/ItemDataExportS6E3.cpp | 9 +++------ .../Data/DataHandler/ItemData/ItemDataExportS6E3.h | 5 ++++- .../Data/DataHandler/ItemData/ItemDataHandler.cpp | 4 ++-- .../Data/DataHandler/ItemData/ItemDataHandler.h | 2 ++ 6 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/source/Data/DataHandler/ItemData/ItemDataExportAsCSV.cpp b/src/source/Data/DataHandler/ItemData/ItemDataExportAsCSV.cpp index 0d1049f5cd..9142a659ae 100644 --- a/src/source/Data/DataHandler/ItemData/ItemDataExportAsCSV.cpp +++ b/src/source/Data/DataHandler/ItemData/ItemDataExportAsCSV.cpp @@ -10,9 +10,6 @@ #include "Core/Utilities/StringUtils.h" #include -// External references -extern ITEM_ATTRIBUTE* ItemAttribute; - // X-Macro helpers for CSV generation #define CSV_HEADER_SIMPLE(name, type, arraySize, width, i18nName) "," #name #define CSV_HEADER_ARRAY(nameWithIndex, baseName, index, type, width, i18nName) "," #nameWithIndex @@ -29,7 +26,7 @@ extern ITEM_ATTRIBUTE* ItemAttribute; #define CSV_PRINT_SIMPLE(name, type, arraySize, width, i18nName) PRINT_FIELD_##type(item, name); #define CSV_PRINT_ARRAY(nameWithIndex, baseName, index, type, width, i18nName) fprintf(csvFp, ",%d", item.baseName[index]); -bool ItemDataExportAsCSV::ExportToCsv(wchar_t* fileName) +bool ItemDataExportAsCSV::ExportToCsv(wchar_t* fileName, const ITEM_ATTRIBUTE* attributes) { FILE* csvFp = _wfopen(fileName, L"w"); if (!csvFp) @@ -46,10 +43,10 @@ bool ItemDataExportAsCSV::ExportToCsv(wchar_t* fileName) for (int i = 0; i < MAX_ITEM; i++) { // Only export items with names - if (ItemAttribute[i].Name[0] != 0) + if (attributes[i].Name[0] != 0) { // Convert wide char name to UTF-8 - std::string utf8Name = StringUtils::WideToNarrow(ItemAttribute[i].Name); + std::string utf8Name = StringUtils::WideToNarrow(attributes[i].Name); // Escape quotes in name std::string escapedName; @@ -69,7 +66,7 @@ bool ItemDataExportAsCSV::ExportToCsv(wchar_t* fileName) fprintf(csvFp, "%d,\"%s\"", i, escapedName.c_str()); // Print all fields using X-macros - ITEM_ATTRIBUTE& item = ItemAttribute[i]; + const ITEM_ATTRIBUTE& item = attributes[i]; ITEM_FIELDS_SIMPLE(CSV_PRINT_SIMPLE) ITEM_FIELDS_ARRAYS(CSV_PRINT_ARRAY) diff --git a/src/source/Data/DataHandler/ItemData/ItemDataExportAsCSV.h b/src/source/Data/DataHandler/ItemData/ItemDataExportAsCSV.h index db433058a4..8d1d833945 100644 --- a/src/source/Data/DataHandler/ItemData/ItemDataExportAsCSV.h +++ b/src/source/Data/DataHandler/ItemData/ItemDataExportAsCSV.h @@ -2,11 +2,14 @@ #ifdef _EDITOR +#include "Data/GameData/ItemData/ItemStructs.h" + // Item Data Export/Import Operations class ItemDataExportAsCSV { public: - static bool ExportToCsv(wchar_t* fileName); + // Writes the MAX_ITEM records of attributes that have a name. + static bool ExportToCsv(wchar_t* fileName, const ITEM_ATTRIBUTE* attributes); // Future: static bool ImportFromCsv(wchar_t* fileName); }; diff --git a/src/source/Data/DataHandler/ItemData/ItemDataExportS6E3.cpp b/src/source/Data/DataHandler/ItemData/ItemDataExportS6E3.cpp index 170ffdb158..f376388327 100644 --- a/src/source/Data/DataHandler/ItemData/ItemDataExportS6E3.cpp +++ b/src/source/Data/DataHandler/ItemData/ItemDataExportS6E3.cpp @@ -11,10 +11,7 @@ #include "Data/Translation/MultiLanguage.h" #include -// External references -extern ITEM_ATTRIBUTE* ItemAttribute; - -bool ItemDataExportS6E3::SaveLegacy(wchar_t* fileName) +bool ItemDataExportS6E3::SaveLegacy(wchar_t* fileName, const ITEM_ATTRIBUTE* attributes) { const int Size = sizeof(ITEM_ATTRIBUTE_FILE_LEGACY); @@ -27,13 +24,13 @@ bool ItemDataExportS6E3::SaveLegacy(wchar_t* fileName) auto Buffer = std::make_unique(Size * MAX_ITEM); BYTE* pSeek = Buffer.get(); - // Convert ItemAttribute to ITEM_ATTRIBUTE_FILE_LEGACY format + // Convert the attributes to ITEM_ATTRIBUTE_FILE_LEGACY format for (int i = 0; i < MAX_ITEM; i++) { ITEM_ATTRIBUTE_FILE_LEGACY dest; memset(&dest, 0, Size); - CopyItemAttributeToDestination(dest, ItemAttribute[i]); + CopyItemAttributeToDestination(dest, attributes[i]); memcpy(pSeek, &dest, Size); pSeek += Size; diff --git a/src/source/Data/DataHandler/ItemData/ItemDataExportS6E3.h b/src/source/Data/DataHandler/ItemData/ItemDataExportS6E3.h index e31b9ce4a1..5532064f43 100644 --- a/src/source/Data/DataHandler/ItemData/ItemDataExportS6E3.h +++ b/src/source/Data/DataHandler/ItemData/ItemDataExportS6E3.h @@ -4,11 +4,14 @@ #include +#include "Data/GameData/ItemData/ItemStructs.h" + // Item Data Legacy Format Saving Operations (S6E3 format) class ItemDataExportS6E3 { public: - static bool SaveLegacy(wchar_t* fileName); + // Writes the MAX_ITEM records of attributes. + static bool SaveLegacy(wchar_t* fileName, const ITEM_ATTRIBUTE* attributes); }; #endif // _EDITOR diff --git a/src/source/Data/DataHandler/ItemData/ItemDataHandler.cpp b/src/source/Data/DataHandler/ItemData/ItemDataHandler.cpp index ba4ad4fa24..748db05a69 100644 --- a/src/source/Data/DataHandler/ItemData/ItemDataHandler.cpp +++ b/src/source/Data/DataHandler/ItemData/ItemDataHandler.cpp @@ -744,11 +744,11 @@ bool CItemDataHandler::ExportAsBmd(std::string& changeLog) bool CItemDataHandler::ExportAsS6E3(wchar_t* fileName) { - return ItemDataExportS6E3::SaveLegacy(fileName); + return ItemDataExportS6E3::SaveLegacy(fileName, BuildItemAttributes(I18N::GetCurrentLocale()).get()); } bool CItemDataHandler::ExportToCsv(wchar_t* fileName) { - return ItemDataExportAsCSV::ExportToCsv(fileName); + return ItemDataExportAsCSV::ExportToCsv(fileName, BuildItemAttributes(I18N::GetCurrentLocale()).get()); } #endif diff --git a/src/source/Data/DataHandler/ItemData/ItemDataHandler.h b/src/source/Data/DataHandler/ItemData/ItemDataHandler.h index 8c2196902f..9148becd6b 100644 --- a/src/source/Data/DataHandler/ItemData/ItemDataHandler.h +++ b/src/source/Data/DataHandler/ItemData/ItemDataHandler.h @@ -181,6 +181,8 @@ class CItemDataHandler // are and do not count as a failure. bool ExportAsBmd(std::string& changeLog); + // The S6E3 item.bmd and the CSV table have the item names of the UI + // locale, also while translated item names are off. bool ExportAsS6E3(wchar_t* fileName); bool ExportToCsv(wchar_t* fileName); #endif From a1193464362b9af9f4ce53000e728d578019c09e Mon Sep 17 00:00:00 2001 From: Mosch0512 Date: Thu, 22 Oct 2026 23:17:05 +0200 Subject: [PATCH 09/10] refactor(options): one label sync for the options window's rows SyncRmlModel's syncLabel lambda becomes the SyncLabel member, and the Translate item names row uses it instead of a copy of its body; its checkbox is synced like every other row's. --- src/source/UI/Options/OptionWindow.cpp | 133 ++++++++++++------------- src/source/UI/Options/OptionWindow.h | 3 +- 2 files changed, 64 insertions(+), 72 deletions(-) diff --git a/src/source/UI/Options/OptionWindow.cpp b/src/source/UI/Options/OptionWindow.cpp index 903f2371c9..bd8be893d4 100644 --- a/src/source/UI/Options/OptionWindow.cpp +++ b/src/source/UI/Options/OptionWindow.cpp @@ -990,21 +990,6 @@ void mu::ui::window::COptionWindow::RmlToggleTranslateItemNames() g_ItemDataHandler.SetTranslateItemNames(m_bTranslateItemNames); } -void mu::ui::window::COptionWindow::SyncTranslateItemNamesRow(OptionRmlModel& model) -{ - const Rml::String label = StringUtils::WideToNarrow(I18N::Game::TranslateItemNames); - if (model.translateItemNamesLabel != label) - { - model.translateItemNamesLabel = label; - m_RmlView.MarkDirty("translate_item_names_label"); - } - if (model.translateItemNames != m_bTranslateItemNames) - { - model.translateItemNames = m_bTranslateItemNames; - m_RmlView.MarkDirty("translate_item_names"); - } -} - void mu::ui::window::COptionWindow::RmlFontChanged(int index) { // See RmlResolutionChanged's own comment -- same settle-frame guard applies here. @@ -1679,6 +1664,16 @@ void mu::ui::window::COptionWindow::ApplyWindowModeToggle() GameConfig::GetInstance().Save(); } +void mu::ui::window::COptionWindow::SyncLabel(Rml::String& field, const char* fieldName, const wchar_t* text) +{ + const Rml::String narrow = StringUtils::WideToNarrow(text); + if (field != narrow) + { + field = narrow; + m_RmlView.MarkDirty(fieldName); + } +} + void mu::ui::window::COptionWindow::SyncRmlModel() { if (!m_RmlView.Document()) @@ -1699,15 +1694,6 @@ void mu::ui::window::COptionWindow::SyncRmlModel() // Re-fetched every sync, not just once at BuildRmlUi() time -- native re-rendered every one of // these from the live I18N::Game::* pointer every frame, so a language switch made from this // very window relabels it immediately rather than only on next reopen. - const auto syncLabel = [this](Rml::String& field, const char* fieldName, const wchar_t* text) - { - const Rml::String narrow = StringUtils::WideToNarrow(text); - if (field != narrow) - { - field = narrow; - m_RmlView.MarkDirty(fieldName); - } - }; // .option-dropdown's own closed-state box shows this instead of a {{}} array-index expression // -- see model.resolutionValueLabel's own comment (OptionWindow.h) for why. const auto syncDropdownValue = [this](Rml::String& field, const char* fieldName, @@ -1721,50 +1707,51 @@ void mu::ui::window::COptionWindow::SyncRmlModel() m_RmlView.MarkDirty(fieldName); } }; - syncLabel(model.title, "title", I18N::Game::Option385); - syncLabel(model.closeLabel, "close_label", I18N::Game::Close); - syncLabel(model.tabGameplayLabel, "tab_gameplay_label", I18N::Game::Gameplay); - syncLabel(model.tabAudioLabel, "tab_audio_label", I18N::Game::Audio); - syncLabel(model.tabDisplayLabel, "tab_display_label", I18N::Game::Display); - syncLabel(model.tabGraphicsLabel, "tab_graphics_label", I18N::Game::Graphics); - syncLabel(model.tabInterfaceLabel, "tab_interface_label", I18N::Game::Interface); - syncLabel(model.sectionQualityLabel, "section_quality_label", I18N::Game::Quality); - syncLabel(model.sectionEffectsLabel, "section_effects_label", I18N::Game::Effects); - syncLabel(model.sectionShadowsLabel, "section_shadows_label", I18N::Game::Shadows); - syncLabel(model.autoAttackLabel, "auto_attack_label", I18N::Game::AutomaticAttack); - syncLabel(model.whisperSoundLabel, "whisper_sound_label", I18N::Game::BeepSoundForWhispering); - syncLabel(model.slideHelpLabel, "slide_help_label", I18N::Game::SlideHelp); - syncLabel(model.renderAllEffectsLabel, "render_all_effects_label", I18N::Game::RenderFullEffects); - syncLabel(model.windowedModeLabel, "windowed_mode_label", I18N::Game::WindowedMode); - syncLabel(model.soundVolumeLabel, "sound_volume_label", I18N::Game::SoundVolume); - syncLabel(model.musicVolumeLabel, "music_volume_label", I18N::Game::MusicVolume); - syncLabel(model.renderLevelLabel, "render_level_label", I18N::Game::EffectLimitation); - syncLabel(model.resolutionRowLabel, "resolution_row_label", I18N::Game::Resolution); - syncLabel(model.languageRowLabel, "language_row_label", I18N::Game::Language); - syncLabel(model.fontRowLabel, "font_row_label", I18N::Game::Font); - syncLabel(model.vsyncLabel, "vsync_label", I18N::Game::VSync); - syncLabel(model.fpsCapRowLabel, "fps_cap_row_label", I18N::Game::FPSLimit); - syncLabel(model.disableEffectsLabel, "disable_effects_label", I18N::Game::DisableEffects); - syncLabel(model.disableParticlesLabel, "disable_particles_label", I18N::Game::DisableParticles); - syncLabel(model.disableSkillEffectModelsLabel, "disable_skill_effect_models_label", + SyncLabel(model.title, "title", I18N::Game::Option385); + SyncLabel(model.closeLabel, "close_label", I18N::Game::Close); + SyncLabel(model.tabGameplayLabel, "tab_gameplay_label", I18N::Game::Gameplay); + SyncLabel(model.tabAudioLabel, "tab_audio_label", I18N::Game::Audio); + SyncLabel(model.tabDisplayLabel, "tab_display_label", I18N::Game::Display); + SyncLabel(model.tabGraphicsLabel, "tab_graphics_label", I18N::Game::Graphics); + SyncLabel(model.tabInterfaceLabel, "tab_interface_label", I18N::Game::Interface); + SyncLabel(model.sectionQualityLabel, "section_quality_label", I18N::Game::Quality); + SyncLabel(model.sectionEffectsLabel, "section_effects_label", I18N::Game::Effects); + SyncLabel(model.sectionShadowsLabel, "section_shadows_label", I18N::Game::Shadows); + SyncLabel(model.autoAttackLabel, "auto_attack_label", I18N::Game::AutomaticAttack); + SyncLabel(model.whisperSoundLabel, "whisper_sound_label", I18N::Game::BeepSoundForWhispering); + SyncLabel(model.slideHelpLabel, "slide_help_label", I18N::Game::SlideHelp); + SyncLabel(model.renderAllEffectsLabel, "render_all_effects_label", I18N::Game::RenderFullEffects); + SyncLabel(model.windowedModeLabel, "windowed_mode_label", I18N::Game::WindowedMode); + SyncLabel(model.soundVolumeLabel, "sound_volume_label", I18N::Game::SoundVolume); + SyncLabel(model.musicVolumeLabel, "music_volume_label", I18N::Game::MusicVolume); + SyncLabel(model.renderLevelLabel, "render_level_label", I18N::Game::EffectLimitation); + SyncLabel(model.resolutionRowLabel, "resolution_row_label", I18N::Game::Resolution); + SyncLabel(model.languageRowLabel, "language_row_label", I18N::Game::Language); + SyncLabel(model.translateItemNamesLabel, "translate_item_names_label", I18N::Game::TranslateItemNames); + SyncLabel(model.fontRowLabel, "font_row_label", I18N::Game::Font); + SyncLabel(model.vsyncLabel, "vsync_label", I18N::Game::VSync); + SyncLabel(model.fpsCapRowLabel, "fps_cap_row_label", I18N::Game::FPSLimit); + SyncLabel(model.disableEffectsLabel, "disable_effects_label", I18N::Game::DisableEffects); + SyncLabel(model.disableParticlesLabel, "disable_particles_label", I18N::Game::DisableParticles); + SyncLabel(model.disableSkillEffectModelsLabel, "disable_skill_effect_models_label", I18N::Game::DisableSkillEffectModels); - syncLabel(model.disableBoidsLabel, "disable_boids_label", I18N::Game::DisableAmbientWildlife); - syncLabel(model.disableWingShadowLabel, "disable_wing_shadow_label", I18N::Game::DisableWingShadow); - syncLabel(model.disableBodyShadowsLabel, "disable_body_shadows_label", I18N::Game::DisableShadows); - syncLabel(model.enhancedGraphicsLabel, "enhanced_graphics_label", I18N::Game::EnhancedGraphics); - syncLabel(model.lookLightingLabel, "look_lighting_label", I18N::Game::EnhancedLighting); - syncLabel(model.lookDynamicLightsLabel, "look_dynamic_lights_label", I18N::Game::DynamicLights); - syncLabel(model.lookLightMoodLabel, "look_light_mood_label", I18N::Game::LightMood); - syncLabel(model.lookPostProcessingLabel, "look_post_processing_label", I18N::Game::BloomAndColourGrading); - syncLabel(model.antialiasingRowLabel, "antialiasing_row_label", I18N::Game::AntiAliasing); - syncLabel(model.antialiasingTooltip, "antialiasing_tooltip", I18N::Game::AntiAliasingTooltip); - syncLabel(model.anisotropyRowLabel, "anisotropy_row_label", I18N::Game::AnisotropicFiltering); - syncLabel(model.anisotropyTooltip, "anisotropy_tooltip", I18N::Game::AnisotropicFilteringTooltip); - syncLabel(model.showFpsCounterLabel, "show_fps_counter_label", I18N::Game::ShowFPSCounter); - syncLabel(model.showDebugInfoLabel, "show_debug_info_label", I18N::Game::ShowDebugInfo); - syncLabel(model.themeRowLabel, "theme_row_label", I18N::Game::UITheme); - syncLabel(model.uiScaleRowLabel, "ui_scale_row_label", I18N::Game::UIScale); - syncLabel(model.uiScaleTooltip, "ui_scale_tooltip", I18N::Game::UIScaleTooltip); + SyncLabel(model.disableBoidsLabel, "disable_boids_label", I18N::Game::DisableAmbientWildlife); + SyncLabel(model.disableWingShadowLabel, "disable_wing_shadow_label", I18N::Game::DisableWingShadow); + SyncLabel(model.disableBodyShadowsLabel, "disable_body_shadows_label", I18N::Game::DisableShadows); + SyncLabel(model.enhancedGraphicsLabel, "enhanced_graphics_label", I18N::Game::EnhancedGraphics); + SyncLabel(model.lookLightingLabel, "look_lighting_label", I18N::Game::EnhancedLighting); + SyncLabel(model.lookDynamicLightsLabel, "look_dynamic_lights_label", I18N::Game::DynamicLights); + SyncLabel(model.lookLightMoodLabel, "look_light_mood_label", I18N::Game::LightMood); + SyncLabel(model.lookPostProcessingLabel, "look_post_processing_label", I18N::Game::BloomAndColourGrading); + SyncLabel(model.antialiasingRowLabel, "antialiasing_row_label", I18N::Game::AntiAliasing); + SyncLabel(model.antialiasingTooltip, "antialiasing_tooltip", I18N::Game::AntiAliasingTooltip); + SyncLabel(model.anisotropyRowLabel, "anisotropy_row_label", I18N::Game::AnisotropicFiltering); + SyncLabel(model.anisotropyTooltip, "anisotropy_tooltip", I18N::Game::AnisotropicFilteringTooltip); + SyncLabel(model.showFpsCounterLabel, "show_fps_counter_label", I18N::Game::ShowFPSCounter); + SyncLabel(model.showDebugInfoLabel, "show_debug_info_label", I18N::Game::ShowDebugInfo); + SyncLabel(model.themeRowLabel, "theme_row_label", I18N::Game::UITheme); + SyncLabel(model.uiScaleRowLabel, "ui_scale_row_label", I18N::Game::UIScale); + SyncLabel(model.uiScaleTooltip, "ui_scale_tooltip", I18N::Game::UIScaleTooltip); // positioned/root_x/root_y stay at their model defaults (false/0/0, see OptionRmlModel's own // comment): window_shell's `.center-both` CSS places the window, a drag moves it. @@ -1800,7 +1787,11 @@ void mu::ui::window::COptionWindow::SyncRmlModel() } if (model.languageIndex != m_iLanguageIndex) { model.languageIndex = m_iLanguageIndex; m_RmlView.MarkDirty("language_index"); } syncDropdownValue(model.languageValueLabel, "language_value_label", model.languageLabels, model.languageIndex); - SyncTranslateItemNamesRow(model); + if (model.translateItemNames != m_bTranslateItemNames) + { + model.translateItemNames = m_bTranslateItemNames; + m_RmlView.MarkDirty("translate_item_names"); + } // Rebuilt every sync (not just once) so the localized "Default" entry follows a live language // switch, same as native's own GetFontLabels(). @@ -1924,10 +1915,10 @@ void mu::ui::window::COptionWindow::SyncRmlModel() // The pack labels are built in RefreshTexturePackChoices(); only the localized "Original" // entry can change here. - syncLabel(model.texturePackRowLabel, "texture_pack_row_label", I18N::Game::TexturePack); - syncLabel(model.texturePackTooltip, "texture_pack_tooltip", I18N::Game::TexturePackTooltip); + SyncLabel(model.texturePackRowLabel, "texture_pack_row_label", I18N::Game::TexturePack); + SyncLabel(model.texturePackTooltip, "texture_pack_tooltip", I18N::Game::TexturePackTooltip); if (!model.texturePackLabels.empty()) - syncLabel(model.texturePackLabels.front(), "texture_pack_labels", I18N::Game::OriginalTextures); + SyncLabel(model.texturePackLabels.front(), "texture_pack_labels", I18N::Game::OriginalTextures); if (model.texturePackIndex != m_iTexturePackIndex) { model.texturePackIndex = m_iTexturePackIndex; diff --git a/src/source/UI/Options/OptionWindow.h b/src/source/UI/Options/OptionWindow.h index 2dc6350e9d..233198c450 100644 --- a/src/source/UI/Options/OptionWindow.h +++ b/src/source/UI/Options/OptionWindow.h @@ -405,7 +405,8 @@ namespace mu::ui::window }; void BindRmlModel(Rml::DataModelConstructor& c, OptionRmlModel& model); void SyncImpliedEffectRows(OptionRmlModel& model); - void SyncTranslateItemNamesRow(OptionRmlModel& model); + // The model's copy of a UI text, marked dirty when the text changed. + void SyncLabel(Rml::String& field, const char* fieldName, const wchar_t* text); void SyncTuningModel(OptionRmlModel& model); void OnRmlBuilt(); UI::RmlBridge::ThemedView m_RmlView{"option_window", From fe3b6c22a0fa40d73ad92444435ad97c44cf859a Mon Sep 17 00:00:00 2001 From: Mosch0512 Date: Thu, 22 Oct 2026 23:17:56 +0200 Subject: [PATCH 10/10] docs(items): comments name the item name language The comments that said item names are in the UI language say the item name language now: the UI language, or English while translated item names are off. A cache keyed on the UI language alone would miss the option. --- src/MuEditor/UI/EffectBrowser/EffectPreviewView.cpp | 7 ++++--- src/MuEditor/UI/ItemTools/ItemBrowserList.h | 2 +- src/MuEditor/UI/ItemTools/ItemNamesakes.h | 2 +- src/MuEditor/UI/ItemTools/ItemToolWindow.cpp | 4 ++-- src/MuEditor/UI/ItemTools/ItemToolWindow.h | 2 +- src/source/Data/DataHandler/ItemData/ItemDataHandler.h | 4 ++-- src/source/Data/GameData/ItemData/ItemDatabase.h | 5 +++-- src/source/Data/GameData/ItemData/ItemDefinition.h | 3 ++- 8 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/MuEditor/UI/EffectBrowser/EffectPreviewView.cpp b/src/MuEditor/UI/EffectBrowser/EffectPreviewView.cpp index 5e5e7d2e04..64932bfaf6 100644 --- a/src/MuEditor/UI/EffectBrowser/EffectPreviewView.cpp +++ b/src/MuEditor/UI/EffectBrowser/EffectPreviewView.cpp @@ -347,9 +347,10 @@ void CEffectPreviewView::RenderItemList() // The names come from the items as the game has them now; the list is made // again when the item tools change the items or their models, or the item -// names change (another UI language, translated item names turned on or off). The item models are all opened on the loading screen and stay, so -// the list does not change with the map; the models of the chosen level are -// checked when drawn. +// names change (another UI language, translated item names turned on or off). +// The item models are all opened on the loading screen and stay, so the list +// does not change with the map; the models of the chosen level are checked +// when drawn. void CEffectPreviewView::RefreshItems() { const int version = g_ItemDatabase.GetVersion(); diff --git a/src/MuEditor/UI/ItemTools/ItemBrowserList.h b/src/MuEditor/UI/ItemTools/ItemBrowserList.h index 6d25534f13..bdd4f1e400 100644 --- a/src/MuEditor/UI/ItemTools/ItemBrowserList.h +++ b/src/MuEditor/UI/ItemTools/ItemBrowserList.h @@ -6,7 +6,7 @@ namespace MuEditor::Items { -// The rows of the item browser: number, name in the UI language and model +// The rows of the item browser: number, name as the game shows it and model // file of each listed item, the selected one highlighted. class CItemBrowserList { diff --git a/src/MuEditor/UI/ItemTools/ItemNamesakes.h b/src/MuEditor/UI/ItemTools/ItemNamesakes.h index c4dae6069d..f782c38482 100644 --- a/src/MuEditor/UI/ItemTools/ItemNamesakes.h +++ b/src/MuEditor/UI/ItemTools/ItemNamesakes.h @@ -7,7 +7,7 @@ namespace MuEditor::Items { -// Items whose name (in the UI language) another item has too, e.g. the lucky +// Items whose name (as the game shows it) another item has too, e.g. the lucky // sets and the tiers of the seed spheres: a short text that tells them apart // in the item lists. It names the tags an item has that not all its // namesakes have ("luckyItem"); namesakes that no tag tells apart count up diff --git a/src/MuEditor/UI/ItemTools/ItemToolWindow.cpp b/src/MuEditor/UI/ItemTools/ItemToolWindow.cpp index 288a1e99dc..b810a228b2 100644 --- a/src/MuEditor/UI/ItemTools/ItemToolWindow.cpp +++ b/src/MuEditor/UI/ItemTools/ItemToolWindow.cpp @@ -152,8 +152,8 @@ bool RenderSelectedItem(int itemType) I18N::Editor::NoItemAtThisNumber); return false; } - // In the UI language, as the item browser shows it and its namesakes are - // found. + // In the item name language, as the item browser shows it and its + // namesakes are found. ImGui::Text("%s (%d,%d)", Core::Text::ToUtf8(item->name.c_str()).c_str(), Data::Items::GetItemGroup(itemType), Data::Items::GetItemNumber(itemType)); RenderNamesakeQualifier(itemType); diff --git a/src/MuEditor/UI/ItemTools/ItemToolWindow.h b/src/MuEditor/UI/ItemTools/ItemToolWindow.h index 25ec31d60a..1c8355555d 100644 --- a/src/MuEditor/UI/ItemTools/ItemToolWindow.h +++ b/src/MuEditor/UI/ItemTools/ItemToolWindow.h @@ -16,7 +16,7 @@ void End(); // editor's bar; a click does not take the focus from the item tool. void RenderUndoRedo(); -// "Kris (0,0)" in the UI language, with what tells it apart from items of the +// "Kris (0,0)" in the item name language, with what tells it apart from items of the // same name (ItemNamesakes), or "Select an item"; false when there is no item // to edit. bool RenderSelectedItem(int itemType); diff --git a/src/source/Data/DataHandler/ItemData/ItemDataHandler.h b/src/source/Data/DataHandler/ItemData/ItemDataHandler.h index 9148becd6b..9d49374c10 100644 --- a/src/source/Data/DataHandler/ItemData/ItemDataHandler.h +++ b/src/source/Data/DataHandler/ItemData/ItemDataHandler.h @@ -52,7 +52,7 @@ class CItemDataHandler #ifdef _EDITOR // Puts the item into the item database at its (group, number) and refills - // its row of ItemAttribute[]: the item in the UI locale, or an empty row + // its row of ItemAttribute[]: the item in the item name language, or an empty row // when it no longer exists (no names). The weapon kind and the stat // profile follow the data (FollowGroupWeaponKind, FollowStatProfile) // unless the definition changes them itself: then they are the user's pick. @@ -129,7 +129,7 @@ class CItemDataHandler // Sets the item's name in `locale` (an empty text removes the translation; // the English name cannot be emptied) in the item database, whole also when // it is longer than ITEM_ATTRIBUTE's name; ItemAttribute shows the name in - // the UI locale. False when the item does not exist, when the English name + // the item name language. False when the item does not exist, when the English name // would be empty (also when the item has none yet) or when the text has // LocalizedString::Separator, which the item checks refuse. bool SetItemName(int itemType, std::string_view locale, std::string text); diff --git a/src/source/Data/GameData/ItemData/ItemDatabase.h b/src/source/Data/GameData/ItemData/ItemDatabase.h index e6b610e41f..28be3c1581 100644 --- a/src/source/Data/GameData/ItemData/ItemDatabase.h +++ b/src/source/Data/GameData/ItemData/ItemDatabase.h @@ -28,8 +28,9 @@ class ItemDatabase // definitions with invalid ids are skipped (validation reports them). void Build(std::span definitions); - // Fills ItemDefinition::name from the names for this UI locale, falling - // back to English. + // Fills ItemDefinition::name from the names in the item name language: + // the UI language, or English while translated item names are off. A + // missing translation falls back to English. void SetDisplayLocale(std::string_view locale); const std::string& GetDisplayLocale() const { diff --git a/src/source/Data/GameData/ItemData/ItemDefinition.h b/src/source/Data/GameData/ItemData/ItemDefinition.h index eac093bd86..bf38614a5a 100644 --- a/src/source/Data/GameData/ItemData/ItemDefinition.h +++ b/src/source/Data/GameData/ItemData/ItemDefinition.h @@ -164,7 +164,8 @@ struct ItemDefinition // All names of the item, English first. Logs always use the English // name (names.GetNeutral()), never a translation. LocalizedString names; - // The name in the current UI locale, shown to the player. Filled by + // The name in the item name language (the UI language, or English while + // translated item names are off), shown to the player. Filled by // ItemDatabase from names. std::wstring name;