From 0d72183c08758c9601d6fc221648fb08d34e1f2e Mon Sep 17 00:00:00 2001 From: Rix Date: Sat, 3 Oct 2026 11:23:44 -0700 Subject: [PATCH 1/3] i18n: msgctxt support, and apply fuzzy flags to the right entry C_() and lC_() look up "ctx\004str", which is how gettext keys a msgctxt entry, and loadLocale() now reads msgctxt instead of rejecting the file. The fuzzy flag was applied to the entry after the one it belongs to, and stayed set until the next comment line. --- Makefile | 2 +- src/core/system.cc | 75 +++++++++++++++++++++++++---------- src/core/system.h | 5 +++ tests/core/i18n.cc | 97 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 22 deletions(-) create mode 100644 tests/core/i18n.cc diff --git a/Makefile b/Makefile index d8efe16d..fc519f72 100644 --- a/Makefile +++ b/Makefile @@ -383,7 +383,7 @@ endef regen-i18n: find src -name *.cc -or -name *.c -or -name *.h | sort -u > pcsx-src-list.txt - xgettext --from-code=utf-8 --keyword=_ --keyword=f_ --keyword=l_ --language=C++ --add-comments --sort-by-file -o i18n/pcsx-redux.pot -f pcsx-src-list.txt + xgettext --from-code=utf-8 --keyword=_ --keyword=f_ --keyword=l_ --keyword=C_:1c,2 --keyword=lC_:1c,2 --language=C++ --add-comments --sort-by-file -o i18n/pcsx-redux.pot -f pcsx-src-list.txt find src -name *.lua | sort -u > pcsx-src-list.txt xgettext --from-code=utf-8 --keyword=t_ --language=Lua --join-existing --sort-by-file -o i18n/pcsx-redux.pot -f pcsx-src-list.txt sed '/POT-Creation-Date/d' -i i18n/pcsx-redux.pot diff --git a/src/core/system.cc b/src/core/system.cc index c508b2fb..61b06fd0 100644 --- a/src/core/system.cc +++ b/src/core/system.cc @@ -119,25 +119,31 @@ bool PCSX::System::loadLocale(const std::string& name, const std::filesystem::pa unsigned inlineIndex = 0; unsigned shift = 0; uint8_t inlined = 0; - enum : int { + enum { WAITING_MSGIDTOKEN, + WAITING_MSGCTXT, + WAITING_MSGIDTOKEN_AFTER_CTXT, WAITING_MSGID, WAITING_MSGSTRTOKEN, WAITING_MSGSTR, - STATE_MAX } state = WAITING_MSGIDTOKEN; uint64_t hashValue; + std::string context; + bool hasContext = false; std::map locale; if (in->failed()) return false; std::string comment; + // The flags comment comes before the entry it applies to, which is only stored + // once the next entry starts, so it is kept per entry. bool fuzzy = false; + bool entryFuzzy = false; while ((c = in->getc()) >= 0) { if (c == '\n' || c == '\r') { if (inString) return false; - if (inComment) fuzzy = comment.find(", fuzzy") != std::string::npos; + if (inComment && comment.find(", fuzzy") != std::string::npos) fuzzy = true; inComment = false; newLine = true; comment.clear(); @@ -154,7 +160,10 @@ bool PCSX::System::loadLocale(const std::string& name, const std::filesystem::pa continue; } if (c == '\"') { - if (state != WAITING_MSGIDTOKEN && state != WAITING_MSGSTRTOKEN) return false; + if (state != WAITING_MSGIDTOKEN && state != WAITING_MSGIDTOKEN_AFTER_CTXT && + state != WAITING_MSGSTRTOKEN) { + return false; + } inString = true; continue; } @@ -229,25 +238,42 @@ bool PCSX::System::loadLocale(const std::string& name, const std::filesystem::pa } else { if (c == ' ' || c == '\t') continue; switch (state) { + case WAITING_MSGCTXT: case WAITING_MSGID: case WAITING_MSGSTR: - if (c == '\"') { - inString = true; - (*reinterpret_cast(&state))++; - if (state == STATE_MAX) state = WAITING_MSGIDTOKEN; - } else { - return false; + if (c != '\"') return false; + inString = true; + switch (state) { + case WAITING_MSGCTXT: + state = WAITING_MSGIDTOKEN_AFTER_CTXT; + break; + case WAITING_MSGID: + state = WAITING_MSGSTRTOKEN; + break; + default: + state = WAITING_MSGIDTOKEN; + break; } break; case WAITING_MSGIDTOKEN: + case WAITING_MSGIDTOKEN_AFTER_CTXT: case WAITING_MSGSTRTOKEN: if (token.empty()) { switch (state) { case WAITING_MSGIDTOKEN: - if (!currentString.empty() && !fuzzy) locale[hashValue] = currentString; + if (!currentString.empty() && !entryFuzzy) locale[hashValue] = currentString; + entryFuzzy = fuzzy; + fuzzy = false; break; - case WAITING_MSGSTRTOKEN: - hashValue = djb::hash(currentString); + case WAITING_MSGIDTOKEN_AFTER_CTXT: + context = currentString; + hasContext = true; + break; + default: + // A string with a context is looked up as "context\004string", + // the same key gettext uses for msgctxt entries. + hashValue = djb::hash(hasContext ? context + '\004' + currentString : currentString); + hasContext = false; break; } currentString = ""; @@ -255,19 +281,26 @@ bool PCSX::System::loadLocale(const std::string& name, const std::filesystem::pa token += c; switch (state) { case WAITING_MSGIDTOKEN: + if (token == "msgid") { + token = ""; + state = WAITING_MSGID; + } else if (token == "msgctxt") { + token = ""; + state = WAITING_MSGCTXT; + } else if (token.length() >= 7) { + return false; + } + break; + case WAITING_MSGIDTOKEN_AFTER_CTXT: if (token.length() == 5) { - if (token != "msgid") { - return false; - } + if (token != "msgid") return false; token = ""; state = WAITING_MSGID; } break; - case WAITING_MSGSTRTOKEN: + default: if (token.length() == 6) { - if (token != "msgstr") { - return false; - } + if (token != "msgstr") return false; token = ""; state = WAITING_MSGSTR; } @@ -280,7 +313,7 @@ bool PCSX::System::loadLocale(const std::string& name, const std::filesystem::pa if (inString || (state != WAITING_MSGIDTOKEN)) return false; - if (!currentString.empty() && !fuzzy) locale[hashValue] = currentString; + if (!currentString.empty() && !entryFuzzy) locale[hashValue] = currentString; m_locales[name] = locale; return true; } diff --git a/src/core/system.h b/src/core/system.h index d72774a9..258afd68 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -308,3 +308,8 @@ extern System *g_system; #define f_(str) fmt::runtime(PCSX::g_system->getStr(PCSX::djb::ctHash(str), str)) // Lambda string lookup to use with static arrays of strings #define l_(str) []() { return PCSX::g_system->getStr(PCSX::djb::ctHash(str), str); } +// Same as _() and l_(), with a translation context for strings whose meaning +// depends on where they're used, e.g. C_("Menu", "Update"). The context shows up +// as msgctxt in the .pot file, and both arguments need to be string literals. +#define C_(ctx, str) PCSX::g_system->getStr(PCSX::djb::ctHash(ctx "\004" str), str) +#define lC_(ctx, str) []() { return PCSX::g_system->getStr(PCSX::djb::ctHash(ctx "\004" str), str); } diff --git a/tests/core/i18n.cc b/tests/core/i18n.cc new file mode 100644 index 00000000..541fb74c --- /dev/null +++ b/tests/core/i18n.cc @@ -0,0 +1,97 @@ +/*************************************************************************** + * Copyright (C) 2026 PCSX-Redux authors * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#include +#include +#include + +#include "core/system.h" +#include "gtest/gtest.h" + +namespace { + +class TestSystem final : public PCSX::System { + public: + void softReset() override {} + void hardReset() override {} + void biosPutc(int) override {} + const PCSX::Arguments& getArgs() const override { throw std::logic_error("unused"); } + void printf(std::string&&) override {} + void log(PCSX::LogClass, std::string&&) override {} + void message(std::string&&) override {} + void luaMessage(const std::string&, bool) override {} + void update(bool) override {} + void close() override {} + void purgeAllEvents() override {} + void testQuit(int) override {} +}; + +const char* const c_poFile = R"(msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" + +#: src/a.cc:1 +msgid "Mono" +msgstr "Mono-fr" + +#: src/b.cc:1 +msgctxt "Audio channels" +msgid "Mono" +msgstr "Mono-audio" + +msgctxt "" +"Multi" +"line" +msgid "" +"Split " +"string" +msgstr "Split-ctx" + +#, fuzzy +msgctxt "Playback" +msgid "Pause" +msgstr "Pause-fuzzy" + +msgid "Last" +msgstr "Last-fr" +)"; + +} // namespace + +TEST(I18n, Context) { + auto path = std::filesystem::temp_directory_path() / "pcsx-redux-i18n-test.po"; + { + std::ofstream out(path, std::ios::binary); + out << c_poFile; + } + TestSystem system; + auto* oldSystem = PCSX::g_system; + PCSX::g_system = &system; + ASSERT_TRUE(system.loadLocale("Test", path)); + system.activateLocale("Test"); + EXPECT_STREQ(_("Mono"), "Mono-fr"); + EXPECT_STREQ(C_("Audio channels", "Mono"), "Mono-audio"); + EXPECT_STREQ(C_("Multiline", "Split string"), "Split-ctx"); + EXPECT_STREQ(C_("Playback", "Pause"), "Pause"); + EXPECT_STREQ(C_("Other", "Mono"), "Mono"); + EXPECT_STREQ(lC_("Audio channels", "Mono")(), "Mono-audio"); + EXPECT_STREQ(_("Last"), "Last-fr"); + PCSX::g_system = oldSystem; + std::filesystem::remove(path); +} From 3c3918fd7c08ddd0674b0cafe3329de5c482bcb9 Mon Sep 17 00:00:00 2001 From: Rix Date: Sat, 3 Oct 2026 11:23:44 -0700 Subject: [PATCH 2/3] i18n: drop untranslatable strings, strip edge whitespace, add contexts Button glyphs, sector modes, ImGui ids, format strings and acronyms no locale translates no longer go through _(). Translated strings carry no leading or trailing whitespace, which Transifex doesn't show. Ambiguous strings get a context. --- src/core/OpenGL_GPU/gpu_opengl.cc | 2 +- src/core/debug.h | 3 +- src/core/gpulogger.cc | 3 +- src/core/pad.cc | 23 +++++++--- src/core/psxmem.cc | 11 +++-- src/gui/gui.cc | 12 +++--- src/gui/widgets/assembly.cc | 8 ++-- src/gui/widgets/breakpoints.cc | 66 +++++++++++++++++------------ src/gui/widgets/events.cc | 4 +- src/gui/widgets/gpudump.cc | 4 +- src/gui/widgets/gpulogger.cc | 7 ++- src/gui/widgets/handlers.cc | 6 +-- src/gui/widgets/isobrowser.cc | 12 +++--- src/gui/widgets/memcard_manager.cc | 4 +- src/gui/widgets/memory_observer.cc | 4 +- src/gui/widgets/named_savestates.cc | 2 +- src/gui/widgets/patches.cc | 3 +- src/gui/widgets/pio-cart.cc | 2 +- src/gui/widgets/typed_debugger.cc | 30 ++++++------- src/spu/cfg.cc | 2 +- 20 files changed, 115 insertions(+), 93 deletions(-) diff --git a/src/core/OpenGL_GPU/gpu_opengl.cc b/src/core/OpenGL_GPU/gpu_opengl.cc index 98906829..7e08a274 100644 --- a/src/core/OpenGL_GPU/gpu_opengl.cc +++ b/src/core/OpenGL_GPU/gpu_opengl.cc @@ -478,7 +478,7 @@ bool PCSX::OpenGL_GPU::configure() { int msaaSampleCount = g_emulator->settings.get(); const auto msaaString = msaaSampleCount == 1 ? _("No MSAA") : fmt::format(f_("{}x MSAA"), msaaSampleCount); - if (ImGui::BeginCombo(_("MSAA"), msaaString.c_str())) { + if (ImGui::BeginCombo("MSAA", msaaString.c_str())) { const int maxMSAA = OpenGL::maxSamples(); if (ImGui::Selectable(_("No MSAA"))) { diff --git a/src/core/debug.h b/src/core/debug.h index 1a64dcb7..8155f147 100644 --- a/src/core/debug.h +++ b/src/core/debug.h @@ -35,7 +35,8 @@ class Debug { Debug(); static uint32_t normalizeAddress(uint32_t address); static bool isInKernel(uint32_t address, bool biosIsKernel = true); - static inline std::function s_breakpoint_type_names[] = {l_("Exec"), l_("Read"), l_("Write")}; + static inline std::function s_breakpoint_type_names[] = { + lC_("Breakpoint type", "Exec"), lC_("Breakpoint type", "Read"), lC_("Breakpoint type", "Write")}; enum class BreakpointType { Exec, Read, Write }; enum class BreakpointCondition { Always, Change, Greater, Less, Equal }; diff --git a/src/core/gpulogger.cc b/src/core/gpulogger.cc index 3e5ca72c..7992bea0 100644 --- a/src/core/gpulogger.cc +++ b/src/core/gpulogger.cc @@ -434,7 +434,8 @@ void PCSX::GPU::DrawingAreaEnd::drawLogNode(unsigned, const DrawLogSettings&) { void PCSX::GPU::DrawingOffset::drawLogNode(unsigned, const DrawLogSettings&) { ImGui::Text(" X: %i, Y: %i", x, y); } void PCSX::GPU::MaskBit::drawLogNode(unsigned, const DrawLogSettings&) { - ImGui::Text(_(" Set: %s, Check: %s"), set ? _("Yes") : _("No"), check ? _("Yes") : _("No")); + ImGui::Text(" %s", + fmt::format(f_("Set: {}, Check: {}"), set ? _("Yes") : _("No"), check ? _("Yes") : _("No")).c_str()); } void PCSX::GPU::CtrlReset::drawLogNode(unsigned, const DrawLogSettings&) {} diff --git a/src/core/pad.cc b/src/core/pad.cc index 32a92d4f..aed6e3f8 100644 --- a/src/core/pad.cc +++ b/src/core/pad.cc @@ -1029,14 +1029,25 @@ bool PadsImpl::Pad::configure() { l_("Keyboard"), }; static std::function const c_buttonNames[] = { - l_("╳"), l_("□"), l_("△"), l_("◯"), l_("Select"), l_("Start"), l_("L1"), - l_("R1"), l_("L2"), l_("R2"), l_("L3"), l_("R3"), l_("Analog Mode"), + []() { return "╳"; }, + []() { return "□"; }, + []() { return "△"; }, + []() { return "◯"; }, + lC_("Controller button", "Select"), + lC_("Controller button", "Start"), + []() { return "L1"; }, + []() { return "R1"; }, + []() { return "L2"; }, + []() { return "R2"; }, + []() { return "L3"; }, + []() { return "R3"; }, + l_("Analog Mode"), }; static std::function const c_dpadDirections[] = { - l_("↑"), - l_("→"), - l_("↓"), - l_("←"), + []() { return "↑"; }, + []() { return "→"; }, + []() { return "↓"; }, + []() { return "←"; }, }; static std::function const c_controllerTypes[] = { l_("Digital"), diff --git a/src/core/psxmem.cc b/src/core/psxmem.cc index ae0c8961..3bf57be0 100644 --- a/src/core/psxmem.cc +++ b/src/core/psxmem.cc @@ -215,12 +215,11 @@ void PCSX::Memory::reset() { m_bios[index++] = w & 0xff; w >>= 8; } - strcpy((char *)m_bios + index, _(R"( - No BIOS loaded, emulation halted. - -Set a BIOS file into the configuration, and do a hard reset of the emulator. -The distributed OpenBIOS.bin file can be an appropriate BIOS replacement. -)")); + std::string nobiosMessage = fmt::format("\n{:19}{}\n\n{}\n", "", _("No BIOS loaded, emulation halted."), + _("Set a BIOS file into the configuration, and do a hard reset of the " + "emulator.\nThe distributed OpenBIOS.bin file can be an appropriate " + "BIOS replacement.")); + strcpy((char *)m_bios + index, nobiosMessage.c_str()); uint32_t nobioscrc = crc32(0L, Z_NULL, 0); nobioscrc = crc32(nobioscrc, m_bios, bios_size); diff --git a/src/gui/gui.cc b/src/gui/gui.cc index 030c2861..1e36522b 100644 --- a/src/gui/gui.cc +++ b/src/gui/gui.cc @@ -803,7 +803,7 @@ void PCSX::GUI::init(std::function applyArguments) { m_parallelPortEditor.title = l_("Parallel Port"); m_scratchPadEditor.title = l_("Scratch Pad"); m_hwrEditor.title = l_("Hardware Registers"); - m_biosEditor.title = l_("BIOS"); + m_biosEditor.title = []() { return "BIOS"; }; m_vramEditor.title = l_("VRAM"); auto makeExportFn = [this](MemoryEditorWrapper& wrapper, std::string postfixName) { return [this, &wrapper, postfixName](size_t len, size_t base_addr) { @@ -1489,7 +1489,7 @@ void PCSX::GUI::endFrame() { ImGui::Separator(); if (ImGui::BeginMenu(_("Debug"))) { ImGui::MenuItem(_("Show Logs"), nullptr, &m_log.m_show); - if (ImGui::BeginMenu(_("Lua"))) { + if (ImGui::BeginMenu("Lua")) { ImGui::MenuItem(_("Show Lua Console"), nullptr, &m_luaConsole.m_show); ImGui::MenuItem(_("Show Lua Inspector"), nullptr, &m_luaInspector.m_show); ImGui::MenuItem(_("Show Lua editor"), nullptr, &m_luaEditor.m_show); @@ -1619,7 +1619,7 @@ in Configuration->Emulation, restart PCSX-Redux, then try again.)")); uint32_t frameCount = g_emulator->m_spu->getFrameCount(); ImGui::Text(_("%.2f ms audio buffer (%i frames)"), 1000.0f * frameCount / 44100.0f, frameCount); } else { - ImGui::TextUnformatted(_("Idle")); + ImGui::TextUnformatted(C_("Audio status", "Idle")); } ImGui::EndMainMenuBar(); @@ -2009,7 +2009,7 @@ the update and manually apply it.)"))); } ImGui::ProgressBar(m_update.progress()); if (!m_updateDownloading) { - if (ImGui::Button(_("Update"))) { + if (ImGui::Button(C_("Update dialog button", "Update"))) { m_updateDownloading = true; bool started = m_update.downloadAndApplyUpdate( g_system->getVersion(), @@ -2032,7 +2032,7 @@ the update and manually apply it.)"))); } } ImGui::SameLine(); - if (ImGui::Button(_("Download"))) { + if (ImGui::Button(C_("Update dialog button", "Download"))) { m_updateDownloading = true; bool started = m_update.getDownloadUrl( g_system->getVersion(), @@ -2562,7 +2562,7 @@ bool PCSX::GUI::about() { if (version.buildId.has_value()) { ImGui::Text(_("Build: %i"), version.buildId.value()); } - ImGui::TextUnformatted(_("Changeset: ")); + ImGui::TextUnformatted(_("Changeset:")); ImGui::SameLine(); if (ImGui::SmallButton(version.changeset.c_str())) { openUrl(fmt::format("https://github.com/grumpycoders/pcsx-redux/commit/{}", version.changeset)); diff --git a/src/gui/widgets/assembly.cc b/src/gui/widgets/assembly.cc index 3244c7ad..686f66c3 100644 --- a/src/gui/widgets/assembly.cc +++ b/src/gui/widgets/assembly.cc @@ -498,8 +498,8 @@ bool PCSX::Widgets::Assembly::draw(GUI* gui, psxRegisters* registers, Memory* me ImGui::EndMenu(); } if (ImGui::BeginMenu(_("Debug"))) { - if (ImGui::MenuItem(_("Pause"), "F6", nullptr, g_system->running())) g_system->pause(); - if (ImGui::MenuItem(_("Resume"), "F5", nullptr, !g_system->running())) g_system->resume(); + if (ImGui::MenuItem(C_("Emulation", "Pause"), "F6", nullptr, g_system->running())) g_system->pause(); + if (ImGui::MenuItem(C_("Emulation", "Resume"), "F5", nullptr, !g_system->running())) g_system->resume(); ImGui::Separator(); if (ImGui::MenuItem(_("Step In"), "F11", nullptr, !g_system->running())) g_emulator->m_debug->stepIn(); if (ImGui::MenuItem(_("Step Over"), "F10", nullptr, !g_system->running())) g_emulator->m_debug->stepOver(); @@ -574,9 +574,9 @@ settings, otherwise debugging features may not work.)"); if (ImGui::Button(_("Jump to PC"))) { m_jumpToPC = m_registers->pc; } - DButton(_("Pause"), g_system->running(), [&]() mutable { g_system->pause(); }); + DButton(C_("Emulation", "Pause"), g_system->running(), [&]() mutable { g_system->pause(); }); ImGui::SameLine(); - DButton(_("Resume"), !g_system->running(), [&]() mutable { g_system->resume(); }); + DButton(C_("Emulation", "Resume"), !g_system->running(), [&]() mutable { g_system->resume(); }); ImGui::SameLine(); DButton(_("Step In"), !g_system->running(), [&]() mutable { g_emulator->m_debug->stepIn(); }); ImGui::SameLine(); diff --git a/src/gui/widgets/breakpoints.cc b/src/gui/widgets/breakpoints.cc index ec6d0113..699ba946 100644 --- a/src/gui/widgets/breakpoints.cc +++ b/src/gui/widgets/breakpoints.cc @@ -37,15 +37,15 @@ static uint32_t getValueAboutToWrite(PCSX::R3000Acpu& cpu) { static const char* getBreakpointConditionName(PCSX::Debug::BreakpointCondition condition) { switch (condition) { case PCSX::Debug::BreakpointCondition::Always: - return _("Always"); + return C_("Breakpoint condition", "Always"); case PCSX::Debug::BreakpointCondition::Greater: - return _("Greater"); + return C_("Breakpoint condition", "Greater"); case PCSX::Debug::BreakpointCondition::Less: - return _("Less"); + return C_("Breakpoint condition", "Less"); case PCSX::Debug::BreakpointCondition::Change: - return _("Change"); + return C_("Breakpoint condition", "Change"); case PCSX::Debug::BreakpointCondition::Equal: - return _("Equal"); + return C_("Breakpoint condition", "Equal"); } return _("Unknown"); } @@ -192,9 +192,9 @@ void PCSX::Widgets::Breakpoints::draw(const char* title) { static int range = 8; static int width = 1; if (m_breakpointType != (int)Debug::BreakpointType::Exec) { - ImGui::RadioButton(_("Byte"), &width, 1); - ImGui::RadioButton(_("Half"), &width, 2); - ImGui::RadioButton(_("Word"), &width, 4); + ImGui::RadioButton(C_("Memory access width", "Byte"), &width, 1); + ImGui::RadioButton(C_("Memory access width", "Half"), &width, 2); + ImGui::RadioButton(C_("Memory access width", "Word"), &width, 4); ImGui::RadioButton(_("Range"), &width, 0); if (width == 0) { @@ -388,28 +388,38 @@ void PCSX::Widgets::Breakpoints::draw(const char* title) { _("The mapping feature is a simple concept, but requires some amount of explanation. See the documentation " "website for more details, in the Misc Features subsection of the Debugging section.")); ImGui::Checkbox(_("Map execution"), &debugger->m_mapping_e); - ImGui::Checkbox(_("Map byte reads "), &debugger->m_mapping_r8); - ImGui::SameLine(); - ImGui::Checkbox(_("Map half reads "), &debugger->m_mapping_r16); - ImGui::SameLine(); - ImGui::Checkbox(_("Map word reads "), &debugger->m_mapping_r32); - ImGui::Checkbox(_("Map byte writes "), &debugger->m_mapping_w8); - ImGui::SameLine(); - ImGui::Checkbox(_("Map half writes "), &debugger->m_mapping_w16); - ImGui::SameLine(); - ImGui::Checkbox(_("Map word writes "), &debugger->m_mapping_w32); + if (ImGui::BeginTable("MapFlags", 3, ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableNextColumn(); + ImGui::Checkbox(_("Map byte reads"), &debugger->m_mapping_r8); + ImGui::TableNextColumn(); + ImGui::Checkbox(_("Map half reads"), &debugger->m_mapping_r16); + ImGui::TableNextColumn(); + ImGui::Checkbox(_("Map word reads"), &debugger->m_mapping_r32); + ImGui::TableNextColumn(); + ImGui::Checkbox(_("Map byte writes"), &debugger->m_mapping_w8); + ImGui::TableNextColumn(); + ImGui::Checkbox(_("Map half writes"), &debugger->m_mapping_w16); + ImGui::TableNextColumn(); + ImGui::Checkbox(_("Map word writes"), &debugger->m_mapping_w32); + ImGui::EndTable(); + } ImGui::Separator(); ImGui::Checkbox(_("Break on execution map"), &debugger->m_breakmp_e); - ImGui::Checkbox(_("Break on byte read map "), &debugger->m_breakmp_r8); - ImGui::SameLine(); - ImGui::Checkbox(_("Break on half read map "), &debugger->m_breakmp_r16); - ImGui::SameLine(); - ImGui::Checkbox(_("Break on word read map "), &debugger->m_breakmp_r32); - ImGui::Checkbox(_("Break on byte write map"), &debugger->m_breakmp_w8); - ImGui::SameLine(); - ImGui::Checkbox(_("Break on half write map"), &debugger->m_breakmp_w16); - ImGui::SameLine(); - ImGui::Checkbox(_("Break on word write map"), &debugger->m_breakmp_w32); + if (ImGui::BeginTable("BreakFlags", 3, ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableNextColumn(); + ImGui::Checkbox(_("Break on byte read map"), &debugger->m_breakmp_r8); + ImGui::TableNextColumn(); + ImGui::Checkbox(_("Break on half read map"), &debugger->m_breakmp_r16); + ImGui::TableNextColumn(); + ImGui::Checkbox(_("Break on word read map"), &debugger->m_breakmp_r32); + ImGui::TableNextColumn(); + ImGui::Checkbox(_("Break on byte write map"), &debugger->m_breakmp_w8); + ImGui::TableNextColumn(); + ImGui::Checkbox(_("Break on half write map"), &debugger->m_breakmp_w16); + ImGui::TableNextColumn(); + ImGui::Checkbox(_("Break on word write map"), &debugger->m_breakmp_w32); + ImGui::EndTable(); + } ImGui::TreePop(); } diff --git a/src/gui/widgets/events.cc b/src/gui/widgets/events.cc index 3611621b..670928ec 100644 --- a/src/gui/widgets/events.cc +++ b/src/gui/widgets/events.cc @@ -35,12 +35,12 @@ void PCSX::Widgets::Events::draw(IO memFile, const char* title) { ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV; auto events = Kernel::Events::getAllEvents(memFile); if (ImGui::BeginTable("Events", 6, flags)) { - ImGui::TableSetupColumn(_("ID")); + ImGui::TableSetupColumn("ID"); ImGui::TableSetupColumn(_("Core")); ImGui::TableSetupColumn(_("Spec")); ImGui::TableSetupColumn(_("Mode")); ImGui::TableSetupColumn(_("Flag")); - ImGui::TableSetupColumn(_("CB")); + ImGui::TableSetupColumn("CB"); ImGui::TableHeadersRow(); for (auto& ev : events) { ImGui::TableNextRow(); diff --git a/src/gui/widgets/gpudump.cc b/src/gui/widgets/gpudump.cc index 696ffaf0..d32c68a1 100644 --- a/src/gui/widgets/gpudump.cc +++ b/src/gui/widgets/gpudump.cc @@ -107,10 +107,10 @@ void PCSX::Widgets::GPUDump::drawPlayer() { if (!player->comment().empty()) ImGui::Text(_("Comment: %s"), player->comment().c_str()); ImGui::Text(_("Frame %llu"), static_cast(player->frame())); - if (ImGui::Button(m_playing ? _("Pause") : _("Play"))) m_playing = !m_playing; + if (ImGui::Button(m_playing ? C_("Playback", "Pause") : C_("Playback", "Play"))) m_playing = !m_playing; ImGui::SameLine(); ImGui::BeginDisabled(m_playing); - if (ImGui::Button(_("Step"))) player->step(); + if (ImGui::Button(C_("Playback", "Step"))) player->step(); ImGui::EndDisabled(); ImGui::SameLine(); if (ImGui::Button(_("Rewind"))) player->rewind(); diff --git a/src/gui/widgets/gpulogger.cc b/src/gui/widgets/gpulogger.cc index 0e16b0ff..3b09675a 100644 --- a/src/gui/widgets/gpulogger.cc +++ b/src/gui/widgets/gpulogger.cc @@ -57,8 +57,7 @@ void PCSX::Widgets::GPULogger::draw(PCSX::GPULogger* logger, const char* title) colorFormat == GPU::Logged::DrawLogSettings::ColorFormat::Expanded)) { colorFormat = GPU::Logged::DrawLogSettings::ColorFormat::Expanded; } - if (ImGui::MenuItem(_("HTML"), nullptr, - colorFormat == GPU::Logged::DrawLogSettings::ColorFormat::HTML)) { + if (ImGui::MenuItem("HTML", nullptr, colorFormat == GPU::Logged::DrawLogSettings::ColorFormat::HTML)) { colorFormat = GPU::Logged::DrawLogSettings::ColorFormat::HTML; } ImGui::EndMenu(); @@ -81,7 +80,7 @@ void PCSX::Widgets::GPULogger::draw(PCSX::GPULogger* logger, const char* title) "and memory.")); ImGui::Checkbox(_("Breakpoint on vsync"), &logger->m_breakOnVSync); ImGui::SameLine(); - if (ImGui::Button(_("Resume"))) { + if (ImGui::Button(C_("Emulation", "Resume"))) { g_system->resume(); } ImGui::Checkbox(_("Replay frame"), &m_replay); @@ -245,7 +244,7 @@ void PCSX::Widgets::GPULogger::draw(PCSX::GPULogger* logger, const char* title) break; } ImGui::SameLine(); - ImGui::TextUnformatted(_("at PC = ")); + ImGui::TextUnformatted(_("at PC =")); ImGui::SameLine(); label = fmt::format("{:08x}", logged.pc); if (ImGui::Button(label.c_str())) { diff --git a/src/gui/widgets/handlers.cc b/src/gui/widgets/handlers.cc index 2fb853b2..d582bb69 100644 --- a/src/gui/widgets/handlers.cc +++ b/src/gui/widgets/handlers.cc @@ -58,20 +58,20 @@ void PCSX::Widgets::Handlers::draw(const uint32_t* psxMemory, const char* title) } while (infoAddr) { std::string buttonStr; - ImGui::TextUnformatted(_("Handler data at ")); + ImGui::TextUnformatted(_("Handler data at")); ImGui::SameLine(); buttonStr = fmt::format("{:08x}##{}", infoAddr, counter++); if (ImGui::Button(buttonStr.c_str())) { g_system->m_eventBus->signal(Events::GUI::JumpToMemory{infoAddr, 16}); } - ImGui::TextUnformatted(_(" verifier: ")); + ImGui::Text(" %s", _("verifier:")); ImGui::SameLine(); uint32_t verifierAddr = memFile->readAt(infoAddr + 8); buttonStr = fmt::format("{:08x}##{}", verifierAddr, counter++); if (ImGui::Button(buttonStr.c_str())) { g_system->m_eventBus->signal(Events::GUI::JumpToPC{verifierAddr}); } - ImGui::TextUnformatted(_(" handler: ")); + ImGui::Text(" %s", _("handler:")); ImGui::SameLine(); uint32_t handlerAddr = memFile->readAt(infoAddr + 4); buttonStr = fmt::format("{:08x}##{}", handlerAddr, counter++); diff --git a/src/gui/widgets/isobrowser.cc b/src/gui/widgets/isobrowser.cc index 2ca649c4..9c17ee9b 100644 --- a/src/gui/widgets/isobrowser.cc +++ b/src/gui/widgets/isobrowser.cc @@ -401,7 +401,7 @@ headers and subheader file boundary markers.)")); ImVec2(0, ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.y))) { ImGui::TableSetupColumn(_("Path"), ImGuiTableColumnFlags_NoHide); - ImGui::TableSetupColumn(_("LBA"), ImGuiTableColumnFlags_WidthFixed, 80.0f); + ImGui::TableSetupColumn("LBA", ImGuiTableColumnFlags_WidthFixed, 80.0f); ImGui::TableSetupColumn(_("Size"), ImGuiTableColumnFlags_WidthFixed, 100.0f); ImGui::TableSetupColumn(_("Type"), ImGuiTableColumnFlags_WidthFixed, 60.0f); ImGui::TableHeadersRow(); @@ -508,13 +508,13 @@ headers and subheader file boundary markers.)")); typeStr = _("Gap"); break; case FlatEntry::HiddenM1: - typeStr = _("M1"); + typeStr = "M1"; break; case FlatEntry::HiddenM2F1: - typeStr = _("M2F1"); + typeStr = "M2F1"; break; case FlatEntry::HiddenM2F2: - typeStr = _("M2F2"); + typeStr = "M2F2"; break; case FlatEntry::System: typeStr = _("System"); @@ -618,7 +618,7 @@ significantly by caching the files beforehand.)")); ImGui::TextUnformatted(str.c_str()); if (ImGui::BeginTable("Tracks", 5, ImGuiTableFlags_Resizable)) { ImGui::TableSetupColumn(_("Track")); - ImGui::TableSetupColumn(_("Start")); + ImGui::TableSetupColumn(C_("Track position", "Start")); ImGui::TableSetupColumn(_("Length")); ImGui::TableSetupColumn(_("Pregap")); ImGui::TableSetupColumn("CRC32"); @@ -809,7 +809,7 @@ significantly by caching the files beforehand.)")); ImVec2(0, ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.y))) { ImGui::TableSetupColumn(_("Name"), ImGuiTableColumnFlags_NoHide); - ImGui::TableSetupColumn(_("LBA"), ImGuiTableColumnFlags_WidthFixed, 80.0f); + ImGui::TableSetupColumn("LBA", ImGuiTableColumnFlags_WidthFixed, 80.0f); ImGui::TableSetupColumn(_("Size"), ImGuiTableColumnFlags_WidthFixed, 100.0f); ImGui::TableHeadersRow(); std::unordered_set visitedDirs; diff --git a/src/gui/widgets/memcard_manager.cc b/src/gui/widgets/memcard_manager.cc index 1cac21a9..0b829ce4 100644 --- a/src/gui/widgets/memcard_manager.cc +++ b/src/gui/widgets/memcard_manager.cc @@ -138,7 +138,7 @@ bool PCSX::Widgets::MemcardManager::draw(GUI* gui, const char* title) { g_emulator->m_sio->saveMcd(1); g_emulator->m_sio->saveMcd(2); } - ImGui::TextUnformatted(_("Undo version: ")); + ImGui::TextUnformatted(_("Undo version:")); ImGui::SameLine(); if (isLatest) { ImGui::TextUnformatted(_("Latest")); @@ -197,7 +197,7 @@ bool PCSX::Widgets::MemcardManager::draw(GUI* gui, const char* title) { ImGui::TableSetupColumn(_("Block number")); ImGui::TableSetupColumn(_("Icon")); ImGui::TableSetupColumn(_("Title")); - ImGui::TableSetupColumn(_("ID")); + ImGui::TableSetupColumn("ID"); ImGui::TableSetupColumn(_("Filename")); ImGui::TableSetupColumn(_("Action")); ImGui::TableHeadersRow(); diff --git a/src/gui/widgets/memory_observer.cc b/src/gui/widgets/memory_observer.cc index 736ea41b..8ee9c1ab 100644 --- a/src/gui/widgets/memory_observer.cc +++ b/src/gui/widgets/memory_observer.cc @@ -397,7 +397,7 @@ void PCSX::Widgets::MemoryObserver::draw(const char* title) { if (ImGui::BeginTabItem(_("Pattern search"))) { if (m_useSIMD) { - ImGui::TextUnformatted(_("Sequence size: ")); + ImGui::TextUnformatted(_("Sequence size:")); ImGui::SameLine(); ImGui::RadioButton(_("8 bytes (fast)"), &m_sequenceSize, 8); ImGui::SameLine(); @@ -408,7 +408,7 @@ void PCSX::Widgets::MemoryObserver::draw(const char* title) { ImGui::InputText(_("Sequence"), m_sequence, m_sequenceSize + 1); - ImGui::InputInt(_("Step"), &m_step); + ImGui::InputInt(C_("Search increment", "Step"), &m_step); if (m_step >= 1 && ImGui::Button(_("Search"))) { if (m_useSIMD && m_sequenceSize == 8) { diff --git a/src/gui/widgets/named_savestates.cc b/src/gui/widgets/named_savestates.cc index 744fec82..bdaceaaf 100644 --- a/src/gui/widgets/named_savestates.cc +++ b/src/gui/widgets/named_savestates.cc @@ -78,7 +78,7 @@ void PCSX::Widgets::NamedSaveStates::draw(GUI* gui, const char* title) { float posY = ImGui::GetCursorPosY(); ImGui::SetCursorPosY(posY + verticalAlignAdjust); - ImGui::TextUnformatted(_("Filename: ")); + ImGui::TextUnformatted(_("Filename:")); ImGui::SameLine(); ImGui::TextUnformatted(gui->getSaveStatePrefix(true).c_str()); ImGui::SameLine(0.0f, 0.0f); diff --git a/src/gui/widgets/patches.cc b/src/gui/widgets/patches.cc index f4945f8b..3ee40bc7 100644 --- a/src/gui/widgets/patches.cc +++ b/src/gui/widgets/patches.cc @@ -85,7 +85,8 @@ void PCSX::Widgets::Patches::draw(const char* title) { ImGui::PopID(); ImGui::TableSetColumnIndex(3); - ImGui::TextUnformatted(patch.type == PCSX::PatchManager::Patch::Type::Return ? _("Return") : _("NOP")); + ImGui::TextUnformatted(patch.type == PCSX::PatchManager::Patch::Type::Return ? C_("Patch type", "Return") + : "NOP"); } ImGui::EndTable(); diff --git a/src/gui/widgets/pio-cart.cc b/src/gui/widgets/pio-cart.cc index df414984..b4435bb2 100644 --- a/src/gui/widgets/pio-cart.cc +++ b/src/gui/widgets/pio-cart.cc @@ -33,7 +33,7 @@ bool PCSX::Widgets::PIOCart::draw(const char* title) { return false; } else { auto pio_rom = settings.get().string(); - ImGui::InputText(_("ROM"), const_cast(reinterpret_cast(pio_rom.c_str())), pio_rom.length(), + ImGui::InputText("ROM", const_cast(reinterpret_cast(pio_rom.c_str())), pio_rom.length(), ImGuiInputTextFlags_ReadOnly); ImGui::SameLine(); selectEXP1Dialog = ImGui::Button("..."); diff --git a/src/gui/widgets/typed_debugger.cc b/src/gui/widgets/typed_debugger.cc index 79336126..9b952739 100644 --- a/src/gui/widgets/typed_debugger.cc +++ b/src/gui/widgets/typed_debugger.cc @@ -357,7 +357,7 @@ void PCSX::Widgets::TypedDebugger::displayNode(WatchTreeNode* node, const uint32 bool addressOfPointer, uint32_t extraImGuiId) { ImGui::TableNextRow(); ImGui::TableNextColumn(); // Name. - std::string nameColumnString = fmt::format(f_("{}\t@ {:#x}##{}"), node->name, currentAddress, extraImGuiId); + std::string nameColumnString = fmt::format("{}\t@ {:#x}##{}", node->name, currentAddress, extraImGuiId); IO memFile = g_emulator->m_mem->getMemoryAsFile(); const char* nodeType = node->type.c_str(); @@ -560,16 +560,16 @@ void PCSX::Widgets::TypedDebugger::displayNewValueInput(const char* type, size_t case 1: if (equals(type, "char")) { m_newValues[address] = *value.data(); - if (ImGui::InputScalar(fmt::format(f_("##{}"), address).c_str(), ImGuiDataType_S8, - &m_newValues[address], &step, &stepFast, signedFormat, inputFlags)) { + if (ImGui::InputScalar(fmt::format("##{}", address).c_str(), ImGuiDataType_S8, &m_newValues[address], + &step, &stepFast, signedFormat, inputFlags)) { memFile->write(m_newValues[address]); } } else { // We have a uchar or something of size 1. m_newValues[address] = *value.data(); - if (ImGui::InputScalar(fmt::format(f_("##{}"), address).c_str(), ImGuiDataType_U8, - &m_newValues[address], &step, &stepFast, unsignedFormat, inputFlags)) { + if (ImGui::InputScalar(fmt::format("##{}", address).c_str(), ImGuiDataType_U8, &m_newValues[address], + &step, &stepFast, unsignedFormat, inputFlags)) { memFile->write(m_newValues[address]); } } @@ -577,15 +577,15 @@ void PCSX::Widgets::TypedDebugger::displayNewValueInput(const char* type, size_t case 2: if (equals(type, "short")) { m_newValues[address] = *value.data(); - if (ImGui::InputScalar(fmt::format(f_("##{}"), address).c_str(), ImGuiDataType_S16, - &m_newValues[address], &step, &stepFast, signedFormat, inputFlags)) { + if (ImGui::InputScalar(fmt::format("##{}", address).c_str(), ImGuiDataType_S16, &m_newValues[address], + &step, &stepFast, signedFormat, inputFlags)) { memFile->write(m_newValues[address]); } } else { // We have a ushort or something of size 2. m_newValues[address] = *value.data(); - if (ImGui::InputScalar(fmt::format(f_("##{}"), address).c_str(), ImGuiDataType_U16, - &m_newValues[address], &step, &stepFast, unsignedFormat, inputFlags)) { + if (ImGui::InputScalar(fmt::format("##{}", address).c_str(), ImGuiDataType_U16, &m_newValues[address], + &step, &stepFast, unsignedFormat, inputFlags)) { memFile->write(m_newValues[address]); } } @@ -593,15 +593,15 @@ void PCSX::Widgets::TypedDebugger::displayNewValueInput(const char* type, size_t case 4: if (equals(type, "int") || equals(type, "long")) { m_newValues[address] = *value.data(); - if (ImGui::InputScalar(fmt::format(f_("##{}"), address).c_str(), ImGuiDataType_S32, - &m_newValues[address], &step, &stepFast, signedFormat, inputFlags)) { + if (ImGui::InputScalar(fmt::format("##{}", address).c_str(), ImGuiDataType_S32, &m_newValues[address], + &step, &stepFast, signedFormat, inputFlags)) { memFile->write(m_newValues[address]); } } else { // We have uint or something of size 4. m_newValues[address] = *value.data(); - if (ImGui::InputScalar(fmt::format(f_("##{}"), address).c_str(), ImGuiDataType_U32, - &m_newValues[address], &step, &stepFast, unsignedFormat, inputFlags)) { + if (ImGui::InputScalar(fmt::format("##{}", address).c_str(), ImGuiDataType_U32, &m_newValues[address], + &step, &stepFast, unsignedFormat, inputFlags)) { memFile->write(m_newValues[address]); } } @@ -781,7 +781,7 @@ Arrays and pointers are specified as for data types)")); ImGui::SameLine(); if (numberToCreate > 0 && ImGui::Button("Add") && m_structs.contains(type)) { WatchTreeNode rootNode; - const auto inputType = createArray ? fmt::format(f_("{}[{}]"), type, numberToCreate) : type; + const auto inputType = createArray ? fmt::format("{}[{}]", type, numberToCreate) : type; rootNode.type = inputType; rootNode.name = inputType; for (const auto& field : m_structs[type]) { @@ -823,7 +823,7 @@ Arrays and pointers are specified as for data types)")); ImGui::Checkbox("Input in hexadecimal", &m_hex); gui->useMonoFont(); - if (ImGui::BeginTable(_("WatchTable"), 6, treeTableFlags)) { + if (ImGui::BeginTable("WatchTable", 6, treeTableFlags)) { ImGui::TableSetupColumn(_("Name")); ImGui::TableSetupColumn(_("Type")); ImGui::TableSetupColumn(_("Size")); diff --git a/src/spu/cfg.cc b/src/spu/cfg.cc index 68011695..73b4af92 100644 --- a/src/spu/cfg.cc +++ b/src/spu/cfg.cc @@ -77,7 +77,7 @@ XA audio and audio tracks.)")); _("Cubic interpolation - better treble")}; changed |= ImGui::Combo(_("Interpolation"), &settings.get().value, interpolationValues, IM_ARRAYSIZE(interpolationValues)); - changed |= ImGui::Checkbox(_("Mono"), &settings.get().value); + changed |= ImGui::Checkbox(C_("Audio channels", "Mono"), &settings.get().value); ImGuiHelpers::ShowHelpMarker(_("Downmixes stereo to mono.")); changed |= ImGui::InputInt(_("Speed multiplier"), &settings.get().value); ImGuiHelpers::ShowHelpMarker(_(R"(Emulation speed, applied at the audio sink (the master clock). From 97e31a3561944839be04045758b76f872d72facd Mon Sep 17 00:00:00 2001 From: Rix Date: Sun, 4 Oct 2026 11:55:07 -0700 Subject: [PATCH 3/3] i18n test: load the locale before swapping g_system --- tests/core/i18n.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/i18n.cc b/tests/core/i18n.cc index 541fb74c..95ee7886 100644 --- a/tests/core/i18n.cc +++ b/tests/core/i18n.cc @@ -81,10 +81,10 @@ TEST(I18n, Context) { out << c_poFile; } TestSystem system; - auto* oldSystem = PCSX::g_system; - PCSX::g_system = &system; ASSERT_TRUE(system.loadLocale("Test", path)); system.activateLocale("Test"); + auto* oldSystem = PCSX::g_system; + PCSX::g_system = &system; EXPECT_STREQ(_("Mono"), "Mono-fr"); EXPECT_STREQ(C_("Audio channels", "Mono"), "Mono-audio"); EXPECT_STREQ(C_("Multiline", "Split string"), "Split-ctx");