From f3aec0c965df248b316709e2aa9742f8d554b42f Mon Sep 17 00:00:00 2001 From: Stenzek Date: Mon, 13 May 2024 01:46:45 +1000 Subject: [PATCH] Host: Re-introduce plural translation support --- src/core/achievements.cpp | 17 +- src/core/game_list.cpp | 2 +- src/core/gpu.cpp | 2 +- src/core/hotkeys.cpp | 196 +++++++++--------- src/core/memory_card.cpp | 5 +- src/core/system.cpp | 39 ++-- src/duckstation-qt/qttranslations.cpp | 5 + .../create-update-and-edit-language.bat | 2 +- .../translations/duckstation-qt_en.ts | 84 +++++--- .../translations/update-and-edit-english.bat | 2 +- .../translations/update-and-edit-language.bat | 2 +- src/duckstation-regtest/regtest_host.cpp | 17 ++ src/util/host.h | 7 + 13 files changed, 230 insertions(+), 150 deletions(-) diff --git a/src/core/achievements.cpp b/src/core/achievements.cpp index 161c6168d..014de7050 100644 --- a/src/core/achievements.cpp +++ b/src/core/achievements.cpp @@ -1018,9 +1018,13 @@ void Achievements::DisplayAchievementSummary() if (s_game_summary.num_core_achievements > 0) { summary = fmt::format( - TRANSLATE_FS("Achievements", "You have unlocked {0} of {1} achievements, and earned {2} of {3} points."), - s_game_summary.num_unlocked_achievements, s_game_summary.num_core_achievements, s_game_summary.points_unlocked, - s_game_summary.points_core); + TRANSLATE_FS("Achievements", "{0}, {1}."), + SmallString::from_format(TRANSLATE_PLURAL_FS("Achievements", "You have unlocked {} of %n achievements", + "Achievement popup", s_game_summary.num_core_achievements), + s_game_summary.num_unlocked_achievements), + SmallString::from_format(TRANSLATE_PLURAL_FS("Achievements", "and earned {} of %n points", "Achievement popup", + s_game_summary.points_core), + s_game_summary.points_unlocked)); } else { @@ -1091,8 +1095,11 @@ void Achievements::HandleGameCompleteEvent(const rc_client_event_t* event) if (g_settings.achievements_notifications && FullscreenUI::Initialize()) { std::string title = fmt::format(TRANSLATE_FS("Achievements", "Mastered {}"), s_game_title); - std::string message = fmt::format(TRANSLATE_FS("Achievements", "{} achievements, {} points"), - s_game_summary.num_unlocked_achievements, s_game_summary.points_unlocked); + std::string message = fmt::format( + TRANSLATE_FS("Achievements", "{0}, {1}"), + TRANSLATE_PLURAL_STR("Achievements", "%n achievements", "Mastery popup", + s_game_summary.num_unlocked_achievements), + TRANSLATE_PLURAL_STR("Achievements", "%n points", "Mastery popup", s_game_summary.num_unlocked_achievements)); ImGuiFullscreen::AddNotification("achievement_mastery", GAME_COMPLETE_NOTIFICATION_TIME, std::move(title), std::move(message), s_game_icon); diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index 064744321..addf6f336 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -1067,7 +1067,7 @@ TinyString GameList::FormatTimespan(std::time_t timespan, bool long_format) if (hours > 0) ret.format(TRANSLATE_FS("GameList", "{} hours"), hours); else - ret.format(TRANSLATE_FS("GameList", "{} minutes"), minutes); + ret.assign(TRANSLATE_PLURAL_STR("GameList", "%n minutes", "", minutes)); } return ret; diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index be09e322f..14a3eebaf 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -2476,7 +2476,7 @@ bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, if (!osd_key.empty()) { Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_CAMERA, - fmt::format(result ? TRANSLATE_FS("GS", "Saved screenshot to '{}'.") : + fmt::format(result ? TRANSLATE_FS("GPU", "Saved screenshot to '{}'.") : TRANSLATE_FS("GPU", "Failed to save screenshot to '{}'."), Path::GetFileName(filename), result ? Host::OSD_INFO_DURATION : Host::OSD_ERROR_DURATION)); diff --git a/src/core/hotkeys.cpp b/src/core/hotkeys.cpp index e0c99fa6b..db49aba22 100644 --- a/src/core/hotkeys.cpp +++ b/src/core/hotkeys.cpp @@ -146,11 +146,10 @@ static bool CanPause() const float delta = static_cast(Common::Timer::ConvertValueToSeconds(time - s_last_pause_time)); if (delta < PAUSE_INTERVAL) { - Host::AddIconOSDMessage( - "PauseCooldown", ICON_FA_CLOCK, - fmt::format(TRANSLATE_FS("Hotkeys", "You cannot pause until another {:.1f} seconds have passed."), - PAUSE_INTERVAL - delta), - Host::OSD_QUICK_DURATION); + Host::AddIconOSDMessage("PauseCooldown", ICON_FA_CLOCK, + TRANSLATE_PLURAL_STR("Hotkeys", "You cannot pause until another %n second(s) have passed.", + "", static_cast(std::ceil(PAUSE_INTERVAL - delta))), + Host::OSD_QUICK_DURATION); return false; } @@ -543,36 +542,35 @@ DEFINE_HOTKEY("AudioVolumeDown", TRANSLATE_NOOP("Hotkeys", "Audio"), TRANSLATE_N // NOTE: All save/load state hotkeys are deferred, because it can trigger setting reapply, which reloads bindings. DEFINE_HOTKEY("LoadSelectedSaveState", TRANSLATE_NOOP("Hotkeys", "Save States"), - TRANSLATE_NOOP("Hotkeys", "Load From Selected Slot"), - [](s32 pressed) { + TRANSLATE_NOOP("Hotkeys", "Load From Selected Slot"), [](s32 pressed) { if (!pressed) Host::RunOnCPUThread(SaveStateSelectorUI::LoadCurrentSlot); }) -DEFINE_HOTKEY( - "SaveSelectedSaveState", TRANSLATE_NOOP("Hotkeys", "Save States"), TRANSLATE_NOOP("Hotkeys", "Save To Selected Slot"), - [](s32 pressed) { - if (!pressed) - Host::RunOnCPUThread(SaveStateSelectorUI::SaveCurrentSlot); - }) DEFINE_HOTKEY("SelectPreviousSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"), - TRANSLATE_NOOP("Hotkeys", "Select Previous Save Slot"), - [](s32 pressed) { - if (!pressed) - Host::RunOnCPUThread([]() { SaveStateSelectorUI::SelectPreviousSlot(true); }); - }) DEFINE_HOTKEY("SelectNextSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"), - TRANSLATE_NOOP("Hotkeys", "Select Next Save Slot"), - [](s32 pressed) { - if (!pressed) - Host::RunOnCPUThread([]() { SaveStateSelectorUI::SelectNextSlot(true); }); - }) DEFINE_HOTKEY("SaveStateAndSelectNextSlot", - TRANSLATE_NOOP("Hotkeys", "Save States"), - TRANSLATE_NOOP("Hotkeys", "Save State and Select Next Slot"), - [](s32 pressed) { - if (!pressed && System::IsValid()) - { - SaveStateSelectorUI::SaveCurrentSlot(); - SaveStateSelectorUI::SelectNextSlot(false); - } - }) +DEFINE_HOTKEY("SaveSelectedSaveState", TRANSLATE_NOOP("Hotkeys", "Save States"), + TRANSLATE_NOOP("Hotkeys", "Save To Selected Slot"), + [](s32 pressed) { + if (!pressed) + Host::RunOnCPUThread(SaveStateSelectorUI::SaveCurrentSlot); + }) +DEFINE_HOTKEY("SelectPreviousSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"), + TRANSLATE_NOOP("Hotkeys", "Select Previous Save Slot"), + [](s32 pressed) { + if (!pressed) + Host::RunOnCPUThread([]() { SaveStateSelectorUI::SelectPreviousSlot(true); }); + }) DEFINE_HOTKEY("SelectNextSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"), + TRANSLATE_NOOP("Hotkeys", "Select Next Save Slot"), + [](s32 pressed) { + if (!pressed) + Host::RunOnCPUThread([]() { SaveStateSelectorUI::SelectNextSlot(true); }); + }) DEFINE_HOTKEY("SaveStateAndSelectNextSlot", TRANSLATE_NOOP("Hotkeys", "Save States"), + TRANSLATE_NOOP("Hotkeys", "Save State and Select Next Slot"), + [](s32 pressed) { + if (!pressed && System::IsValid()) + { + SaveStateSelectorUI::SaveCurrentSlot(); + SaveStateSelectorUI::SelectNextSlot(false); + } + }) DEFINE_HOTKEY("UndoLoadState", TRANSLATE_NOOP("Hotkeys", "Save States"), TRANSLATE_NOOP("Hotkeys", "Undo Load State"), [](s32 pressed) { @@ -593,71 +591,79 @@ DEFINE_HOTKEY( Host::RunOnCPUThread([]() { HotkeySaveStateSlot(global, slot); }); \ }) - MAKE_LOAD_STATE_HOTKEY(false, 1, TRANSLATE_NOOP("Hotkeys", "Load Game State 1")) MAKE_SAVE_STATE_HOTKEY( - false, 1, TRANSLATE_NOOP("Hotkeys", "Save Game State 1")) - MAKE_LOAD_STATE_HOTKEY(false, 2, TRANSLATE_NOOP("Hotkeys", "Load Game State 2")) MAKE_SAVE_STATE_HOTKEY( - false, 2, TRANSLATE_NOOP("Hotkeys", "Save Game State 2")) - MAKE_LOAD_STATE_HOTKEY(false, 3, TRANSLATE_NOOP("Hotkeys", "Load Game State 3")) MAKE_SAVE_STATE_HOTKEY( - false, 3, TRANSLATE_NOOP("Hotkeys", "Save Game State 3")) - MAKE_LOAD_STATE_HOTKEY(false, 4, TRANSLATE_NOOP("Hotkeys", "Load Game State 4")) MAKE_SAVE_STATE_HOTKEY( - false, 4, - TRANSLATE_NOOP("Hotkeys", "Save Game State 4")) MAKE_LOAD_STATE_HOTKEY(false, 5, - TRANSLATE_NOOP("Hotkeys", - "Load Game State 5")) - MAKE_SAVE_STATE_HOTKEY(false, 5, TRANSLATE_NOOP("Hotkeys", "Save Game State 5")) MAKE_LOAD_STATE_HOTKEY( - false, 6, - TRANSLATE_NOOP("Hotkeys", "Load Game State 6")) - MAKE_SAVE_STATE_HOTKEY(false, 6, TRANSLATE_NOOP("Hotkeys", "Save Game State 6")) MAKE_LOAD_STATE_HOTKEY( - false, 7, - TRANSLATE_NOOP("Hotkeys", "Load Game State 7")) - MAKE_SAVE_STATE_HOTKEY(false, 7, TRANSLATE_NOOP("Hotkeys", "Save Game State 7")) MAKE_LOAD_STATE_HOTKEY( - false, 8, - TRANSLATE_NOOP("Hotkeys", "Load Game State 8")) - MAKE_SAVE_STATE_HOTKEY(false, 8, TRANSLATE_NOOP("Hotkeys", "Save Game State 8")) - MAKE_LOAD_STATE_HOTKEY(false, 9, TRANSLATE_NOOP("Hotkeys", "Load Game State 9")) - MAKE_SAVE_STATE_HOTKEY(false, 9, TRANSLATE_NOOP("Hotkeys", "Save Game State 9")) - MAKE_LOAD_STATE_HOTKEY(false, 10, TRANSLATE_NOOP("Hotkeys", "Load Game State 10")) - MAKE_SAVE_STATE_HOTKEY(false, 10, TRANSLATE_NOOP("Hotkeys", "Save Game State 10")) + MAKE_LOAD_STATE_HOTKEY(false, 1, TRANSLATE_NOOP("Hotkeys", "Load Game State 1")) + MAKE_SAVE_STATE_HOTKEY(false, 1, TRANSLATE_NOOP("Hotkeys", "Save Game State 1")) + MAKE_LOAD_STATE_HOTKEY(false, 2, TRANSLATE_NOOP("Hotkeys", "Load Game State 2")) MAKE_SAVE_STATE_HOTKEY( + false, 2, TRANSLATE_NOOP("Hotkeys", "Save Game State 2")) + MAKE_LOAD_STATE_HOTKEY(false, 3, TRANSLATE_NOOP("Hotkeys", "Load Game State 3")) MAKE_SAVE_STATE_HOTKEY( + false, 3, TRANSLATE_NOOP("Hotkeys", "Save Game State 3")) + MAKE_LOAD_STATE_HOTKEY(false, 4, TRANSLATE_NOOP("Hotkeys", "Load Game State 4")) MAKE_SAVE_STATE_HOTKEY( + false, 4, TRANSLATE_NOOP("Hotkeys", "Save Game State 4")) + MAKE_LOAD_STATE_HOTKEY(false, 5, TRANSLATE_NOOP("Hotkeys", "Load Game State 5")) MAKE_SAVE_STATE_HOTKEY( + false, 5, TRANSLATE_NOOP("Hotkeys", "Save Game State 5")) + MAKE_LOAD_STATE_HOTKEY(false, 6, TRANSLATE_NOOP("Hotkeys", "Load Game State 6")) MAKE_SAVE_STATE_HOTKEY( + false, 6, TRANSLATE_NOOP("Hotkeys", "Save Game State 6")) + MAKE_LOAD_STATE_HOTKEY(false, 7, TRANSLATE_NOOP("Hotkeys", "Load Game State 7")) + MAKE_SAVE_STATE_HOTKEY(false, 7, TRANSLATE_NOOP("Hotkeys", "Save Game State 7")) + MAKE_LOAD_STATE_HOTKEY(false, 8, TRANSLATE_NOOP("Hotkeys", "Load Game State 8")) + MAKE_SAVE_STATE_HOTKEY(false, 8, TRANSLATE_NOOP("Hotkeys", "Save Game State 8")) + MAKE_LOAD_STATE_HOTKEY(false, 9, TRANSLATE_NOOP("Hotkeys", "Load Game State 9")) + MAKE_SAVE_STATE_HOTKEY(false, 9, TRANSLATE_NOOP("Hotkeys", "Save Game State 9")) + MAKE_LOAD_STATE_HOTKEY(false, 10, TRANSLATE_NOOP("Hotkeys", "Load Game State 10")) + MAKE_SAVE_STATE_HOTKEY(false, 10, TRANSLATE_NOOP("Hotkeys", "Save Game State 10")) - MAKE_LOAD_STATE_HOTKEY(true, 1, TRANSLATE_NOOP("Hotkeys", "Load Global State 1")) - MAKE_SAVE_STATE_HOTKEY(true, 1, TRANSLATE_NOOP("Hotkeys", "Save Global State 1")) - MAKE_LOAD_STATE_HOTKEY(true, 2, TRANSLATE_NOOP("Hotkeys", "Load Global State 2")) - MAKE_SAVE_STATE_HOTKEY(true, 2, TRANSLATE_NOOP("Hotkeys", "Save Global State 2")) - MAKE_LOAD_STATE_HOTKEY(true, 3, TRANSLATE_NOOP("Hotkeys", "Load Global State 3")) - MAKE_SAVE_STATE_HOTKEY(true, 3, TRANSLATE_NOOP("Hotkeys", "Save Global State 3")) - MAKE_LOAD_STATE_HOTKEY(true, - 4, TRANSLATE_NOOP("Hotkeys", "Load Global State 4")) - MAKE_SAVE_STATE_HOTKEY(true, - 4, TRANSLATE_NOOP("Hotkeys", "Save Global State 4")) - MAKE_LOAD_STATE_HOTKEY(true, - 5, TRANSLATE_NOOP("Hotkeys", "Load Global State 5")) - MAKE_SAVE_STATE_HOTKEY( - true, 5, TRANSLATE_NOOP("Hotkeys", "Save Global State 5")) - MAKE_LOAD_STATE_HOTKEY( - true, 6, TRANSLATE_NOOP("Hotkeys", "Load Global State 6")) - MAKE_SAVE_STATE_HOTKEY( - true, 6, TRANSLATE_NOOP("Hotkeys", "Save Global State 6")) - MAKE_LOAD_STATE_HOTKEY( - true, 7, TRANSLATE_NOOP("Hotkeys", "Load Global State 7")) - MAKE_SAVE_STATE_HOTKEY( - true, 7, TRANSLATE_NOOP("Hotkeys", "Save Global State 7")) - MAKE_LOAD_STATE_HOTKEY( - true, 8, TRANSLATE_NOOP("Hotkeys", "Load Global State 8")) - MAKE_SAVE_STATE_HOTKEY( - true, 8, TRANSLATE_NOOP("Hotkeys", "Save Global State 8")) - MAKE_LOAD_STATE_HOTKEY( - true, 9, TRANSLATE_NOOP("Hotkeys", "Load Global State 9")) - MAKE_SAVE_STATE_HOTKEY( - true, 9, - TRANSLATE_NOOP("Hotkeys", "Save Global State 9")) - MAKE_LOAD_STATE_HOTKEY( - true, 10, - TRANSLATE_NOOP("Hotkeys", "Load Global State 10")) - MAKE_SAVE_STATE_HOTKEY( - true, 10, - TRANSLATE_NOOP("Hotkeys", "Save Global State 10")) + MAKE_LOAD_STATE_HOTKEY(true, 1, TRANSLATE_NOOP("Hotkeys", "Load Global State 1")) + MAKE_SAVE_STATE_HOTKEY(true, 1, TRANSLATE_NOOP("Hotkeys", "Save Global State 1")) + MAKE_LOAD_STATE_HOTKEY(true, 2, TRANSLATE_NOOP("Hotkeys", "Load Global State 2")) + MAKE_SAVE_STATE_HOTKEY(true, 2, + TRANSLATE_NOOP("Hotkeys", "Save Global State 2")) + MAKE_LOAD_STATE_HOTKEY(true, + 3, TRANSLATE_NOOP("Hotkeys", "Load Global State 3")) + MAKE_SAVE_STATE_HOTKEY(true, 3, + TRANSLATE_NOOP("Hotkeys", "Save Global State 3")) + MAKE_LOAD_STATE_HOTKEY(true, 4, + TRANSLATE_NOOP("Hotkeys", "Load Global State 4")) + MAKE_SAVE_STATE_HOTKEY(true, 4, + TRANSLATE_NOOP("Hotkeys", "Save Global State 4")) + MAKE_LOAD_STATE_HOTKEY(true, 5, + TRANSLATE_NOOP("Hotkeys", + "Load Global State 5")) + MAKE_SAVE_STATE_HOTKEY(true, 5, + TRANSLATE_NOOP("Hotkeys", + "Save Global State 5")) + MAKE_LOAD_STATE_HOTKEY(true, 6, + TRANSLATE_NOOP("Hotkeys", + "Load Global State 6")) + MAKE_SAVE_STATE_HOTKEY(true, 6, + TRANSLATE_NOOP("Hotkeys", + "Save Global State 6")) + MAKE_LOAD_STATE_HOTKEY(true, 7, + TRANSLATE_NOOP("Hotkeys", + "Load Global State 7")) + MAKE_SAVE_STATE_HOTKEY( + true, 7, TRANSLATE_NOOP("Hotkeys", "Save Global State 7")) + MAKE_LOAD_STATE_HOTKEY( + true, 8, + TRANSLATE_NOOP("Hotkeys", "Load Global State 8")) + MAKE_SAVE_STATE_HOTKEY( + true, 8, + TRANSLATE_NOOP("Hotkeys", "Save Global State 8")) + MAKE_LOAD_STATE_HOTKEY( + true, 9, + TRANSLATE_NOOP("Hotkeys", "Load Global State 9")) + MAKE_SAVE_STATE_HOTKEY( + true, 9, + TRANSLATE_NOOP("Hotkeys", "Save Global State 9")) + MAKE_LOAD_STATE_HOTKEY( + true, 10, + TRANSLATE_NOOP("Hotkeys", + "Load Global State 10")) + MAKE_SAVE_STATE_HOTKEY( + true, 10, + TRANSLATE_NOOP("Hotkeys", + "Save Global State 10")) #undef MAKE_SAVE_STATE_HOTKEY #undef MAKE_LOAD_STATE_HOTKEY - END_HOTKEY_LIST() + END_HOTKEY_LIST() diff --git a/src/core/memory_card.cpp b/src/core/memory_card.cpp index da04d8511..6f01a0243 100644 --- a/src/core/memory_card.cpp +++ b/src/core/memory_card.cpp @@ -336,7 +336,7 @@ bool MemoryCard::SaveIfChanged(bool display_osd_message) Host::AddIconOSDMessage( std::move(osd_key), ICON_FA_SD_CARD, fmt::format(TRANSLATE_FS("OSDMessage", "Failed to save memory card to '{}'."), Path::GetFileName(display_name)), - 20.0f); + Host::OSD_ERROR_DURATION); } return false; @@ -346,7 +346,8 @@ bool MemoryCard::SaveIfChanged(bool display_osd_message) { Host::AddIconOSDMessage( std::move(osd_key), ICON_FA_SD_CARD, - fmt::format(TRANSLATE_FS("OSDMessage", "Saved memory card to '{}'."), Path::GetFileName(display_name)), 5.0f); + fmt::format(TRANSLATE_FS("OSDMessage", "Saved memory card to '{}'."), Path::GetFileName(display_name)), + Host::OSD_QUICK_DURATION); } return true; diff --git a/src/core/system.cpp b/src/core/system.cpp index dd44fc9bf..ebfc8d13a 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -2935,12 +2935,12 @@ void System::DoToggleCheats() } cl->SetMasterEnable(!cl->GetMasterEnable()); - Host::AddKeyedOSDMessage( - "ToggleCheats", + Host::AddIconOSDMessage( + "ToggleCheats", ICON_FA_EXCLAMATION_TRIANGLE, cl->GetMasterEnable() ? - fmt::format(TRANSLATE_FS("OSDMessage", "{} cheats are now active."), cl->GetEnabledCodeCount()) : - fmt::format(TRANSLATE_FS("OSDMessage", "{} cheats are now inactive."), cl->GetEnabledCodeCount()), - 10.0f); + TRANSLATE_PLURAL_STR("System", "%n cheat(s) are now active.", "", cl->GetEnabledCodeCount()) : + TRANSLATE_PLURAL_STR("System", "%n cheat(s) are now inactive.", "", cl->GetEnabledCodeCount()), + Host::OSD_QUICK_DURATION); } static bool LoadEXEToRAM(const char* filename, bool patch_bios) @@ -3993,25 +3993,26 @@ void System::WarnAboutUnsafeSettings() if (g_settings.cpu_overclock_active) { - append(ICON_FA_MICROCHIP, - fmt::format(TRANSLATE_FS("System", "CPU clock speed is set to {}% ({} / {}). This may crash games."), - g_settings.GetCPUOverclockPercent(), g_settings.cpu_overclock_numerator, - g_settings.cpu_overclock_denominator)); + append( + ICON_FA_MICROCHIP, + SmallString::from_format(TRANSLATE_FS("System", "CPU clock speed is set to {}% ({} / {}). This may crash games."), + g_settings.GetCPUOverclockPercent(), g_settings.cpu_overclock_numerator, + g_settings.cpu_overclock_denominator)); } if (g_settings.cdrom_read_speedup > 1) { - append( - ICON_FA_COMPACT_DISC, - fmt::format(TRANSLATE_FS("System", "CD-ROM read speedup set to {}x (effective speed {}x). This may crash games."), - g_settings.cdrom_read_speedup, g_settings.cdrom_read_speedup * 2)); + append(ICON_FA_COMPACT_DISC, + SmallString::from_format( + TRANSLATE_FS("System", "CD-ROM read speedup set to {}x (effective speed {}x). This may crash games."), + g_settings.cdrom_read_speedup, g_settings.cdrom_read_speedup * 2)); } if (g_settings.cdrom_seek_speedup != 1) { append(ICON_FA_COMPACT_DISC, - fmt::format(TRANSLATE_FS("System", "CD-ROM seek speedup set to {}. This may crash games."), - (g_settings.cdrom_seek_speedup == 0) ? - TinyString(TRANSLATE_SV("System", "Instant")) : - TinyString::from_format("{}x", g_settings.cdrom_seek_speedup))); + SmallString::from_format(TRANSLATE_FS("System", "CD-ROM seek speedup set to {}. This may crash games."), + (g_settings.cdrom_seek_speedup == 0) ? + TinyString(TRANSLATE_SV("System", "Instant")) : + TinyString::from_format("{}x", g_settings.cdrom_seek_speedup))); } if (g_settings.gpu_force_ntsc_timings) { @@ -4030,8 +4031,8 @@ void System::WarnAboutUnsafeSettings() if (s_cheat_list && s_cheat_list->GetEnabledCodeCount() > 0) { append(ICON_FA_EXCLAMATION_TRIANGLE, - fmt::format(TRANSLATE_FS("System", "{} cheats are enabled. This may crash games."), - s_cheat_list->GetEnabledCodeCount())); + TRANSLATE_PLURAL_STR("System", "%n cheat(s) are enabled. This may crash games.", "", + s_cheat_list->GetEnabledCodeCount())); } if (!messages.empty()) diff --git a/src/duckstation-qt/qttranslations.cpp b/src/duckstation-qt/qttranslations.cpp index 9d4cc205c..d824a2041 100644 --- a/src/duckstation-qt/qttranslations.cpp +++ b/src/duckstation-qt/qttranslations.cpp @@ -173,6 +173,11 @@ s32 Host::Internal::GetTranslatedStringImpl(std::string_view context, std::strin return static_cast(translated_size); } +std::string Host::TranslatePluralToString(const char* context, const char* msg, const char* disambiguation, int count) +{ + return qApp->translate(context, msg, disambiguation, count).toStdString(); +} + std::span> Host::GetAvailableLanguageList() { static constexpr const std::pair languages[] = {{"English", "en"}, diff --git a/src/duckstation-qt/translations/create-update-and-edit-language.bat b/src/duckstation-qt/translations/create-update-and-edit-language.bat index 675c6c6c1..548cd9188 100644 --- a/src/duckstation-qt/translations/create-update-and-edit-language.bat +++ b/src/duckstation-qt/translations/create-update-and-edit-language.bat @@ -66,7 +66,7 @@ REM A good .ts file has been passed ECHO Updating %filename%... ECHO. SET "linguist=..\..\..\dep\msvc\deps-x64\bin" -SET "context=.././ ../../core/ ../../util/ -tr-function-alias QT_TRANSLATE_NOOP+=TRANSLATE,QT_TRANSLATE_NOOP+=TRANSLATE_SV,QT_TRANSLATE_NOOP+=TRANSLATE_STR,QT_TRANSLATE_NOOP+=TRANSLATE_FS,QT_TRANSLATE_N_NOOP3+=TRANSLATE_FMT,QT_TRANSLATE_NOOP+=TRANSLATE_NOOP" +SET "context=.././ ../../core/ ../../util/ -tr-function-alias QT_TRANSLATE_NOOP+=TRANSLATE,QT_TRANSLATE_NOOP+=TRANSLATE_SV,QT_TRANSLATE_NOOP+=TRANSLATE_STR,QT_TRANSLATE_NOOP+=TRANSLATE_FS,QT_TRANSLATE_N_NOOP3+=TRANSLATE_FMT,QT_TRANSLATE_NOOP+=TRANSLATE_NOOP,translate+=TRANSLATE_PLURAL_STR,translate+=TRANSLATE_PLURAL_FS" "%linguist%\lupdate.exe" %context% -ts %filename% ECHO. diff --git a/src/duckstation-qt/translations/duckstation-qt_en.ts b/src/duckstation-qt/translations/duckstation-qt_en.ts index a76d8b4a4..40433c9bd 100644 --- a/src/duckstation-qt/translations/duckstation-qt_en.ts +++ b/src/duckstation-qt/translations/duckstation-qt_en.ts @@ -7,17 +7,46 @@ %n seconds - - - + + %n second + %n seconds - EmuThread + Achievements + + You have unlocked {} of %n achievements + Achievement popup + + You have unlocked {} of %n achievements + You have unlocked {} of %n achievements + + + + + and earned {} of %n points + Achievement popup + + and earned {} of %n points + and earned {} of %n points + + + + + %n achievements + Mastery popup + + %n achievement + %n achievements + + + + %n points - + Mastery popup + %n point %n points @@ -26,7 +55,7 @@ EmulationSettingsWidget - + Rewind for %n frame(s), lasting %1 second(s) will require up to %2MB of RAM and %3MB of VRAM. Rewind for %n frame, lasting %1 second(s) will require up to %2MB of RAM and %3MB of VRAM. @@ -34,6 +63,17 @@ + + GameList + + + %n minutes + + %n minute + %n minutes + + + InputBindingWidget @@ -46,9 +86,9 @@ - MemoryCardEditorDialog + MemoryCardEditorWindow - + %n block(s) free%1 %n block free%1 @@ -57,33 +97,29 @@ - OSDMessage + System - %n cheats are now active. - + + %n cheat(s) are now active. + %n cheat is now active. %n cheats are now active. - %n cheats are now inactive. - + + %n cheat(s) are now inactive. + %n cheat is now inactive. %n cheats are now inactive. - Saved %n cheats to '%s'. - - Saved %n cheat to '%s'. - Saved %n cheats to '%s'. - - - - %n cheats are enabled. This may result in instability. - - %n cheat is enabled. This may result in instability. - %n cheats are enabled. This may result in instability. + + %n cheat(s) are enabled. This may crash games. + + %n cheat is enabled. This may crash games. + %n cheats are enabled. This may crash games. diff --git a/src/duckstation-qt/translations/update-and-edit-english.bat b/src/duckstation-qt/translations/update-and-edit-english.bat index 18dc6cade..02046879a 100644 --- a/src/duckstation-qt/translations/update-and-edit-english.bat +++ b/src/duckstation-qt/translations/update-and-edit-english.bat @@ -1,7 +1,7 @@ @echo off set "linguist=..\..\..\dep\msvc\deps-x64\bin" -set context=../ ../../core/ ../../util/ -tr-function-alias QT_TRANSLATE_NOOP+=TRANSLATE,QT_TRANSLATE_NOOP+=TRANSLATE_SV,QT_TRANSLATE_NOOP+=TRANSLATE_STR,QT_TRANSLATE_NOOP+=TRANSLATE_FS,QT_TRANSLATE_N_NOOP3+=TRANSLATE_FMT,QT_TRANSLATE_NOOP+=TRANSLATE_NOOP -pluralonly +set context=../ ../../core/ ../../util/ -tr-function-alias QT_TRANSLATE_NOOP+=TRANSLATE,QT_TRANSLATE_NOOP+=TRANSLATE_SV,QT_TRANSLATE_NOOP+=TRANSLATE_STR,QT_TRANSLATE_NOOP+=TRANSLATE_FS,QT_TRANSLATE_N_NOOP3+=TRANSLATE_FMT,QT_TRANSLATE_NOOP+=TRANSLATE_NOOP,translate+=TRANSLATE_PLURAL_STR,translate+=TRANSLATE_PLURAL_FS -pluralonly -no-obsolete "%linguist%\lupdate.exe" %context% -ts duckstation-qt_en.ts pause diff --git a/src/duckstation-qt/translations/update-and-edit-language.bat b/src/duckstation-qt/translations/update-and-edit-language.bat index 6193487e6..195ff9cf6 100644 --- a/src/duckstation-qt/translations/update-and-edit-language.bat +++ b/src/duckstation-qt/translations/update-and-edit-language.bat @@ -3,7 +3,7 @@ if not defined lang (echo Please set your language first & pause & exit) set "linguist=..\..\..\dep\msvc\deps-x64\bin" -SET "context=.././ ../../core/ ../../util/ -tr-function-alias QT_TRANSLATE_NOOP+=TRANSLATE,QT_TRANSLATE_NOOP+=TRANSLATE_SV,QT_TRANSLATE_NOOP+=TRANSLATE_STR,QT_TRANSLATE_NOOP+=TRANSLATE_FS,QT_TRANSLATE_N_NOOP3+=TRANSLATE_FMT,QT_TRANSLATE_NOOP+=TRANSLATE_NOOP" +SET "context=.././ ../../core/ ../../util/ -tr-function-alias QT_TRANSLATE_NOOP+=TRANSLATE,QT_TRANSLATE_NOOP+=TRANSLATE_SV,QT_TRANSLATE_NOOP+=TRANSLATE_STR,QT_TRANSLATE_NOOP+=TRANSLATE_FS,QT_TRANSLATE_N_NOOP3+=TRANSLATE_FMT,QT_TRANSLATE_NOOP+=TRANSLATE_NOOP,translate+=TRANSLATE_PLURAL_STR,translate+=TRANSLATE_PLURAL_FS" "%linguist%\lupdate.exe" %context% -noobsolete -ts duckstation-qt_%lang%.ts pause diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index 220313254..ec9353c25 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -178,6 +178,23 @@ s32 Host::Internal::GetTranslatedStringImpl(std::string_view context, std::strin return static_cast(msg.size()); } +std::string Host::TranslatePluralToString(const char* context, const char* msg, const char* disambiguation, int count) +{ + TinyString count_str = TinyString::from_format("{}", count); + + std::string ret(msg); + for (;;) + { + std::string::size_type pos = ret.find("%n"); + if (pos == std::string::npos) + break; + + ret.replace(pos, pos + 2, count_str.view()); + } + + return ret; +} + void Host::LoadSettings(SettingsInterface& si, std::unique_lock& lock) { } diff --git a/src/util/host.h b/src/util/host.h index 56a89ab8d..5caba2f43 100644 --- a/src/util/host.h +++ b/src/util/host.h @@ -59,6 +59,9 @@ std::string_view TranslateToStringView(std::string_view context, std::string_vie /// Returns a localized version of the specified string within the specified context. std::string TranslateToString(std::string_view context, std::string_view msg); +/// Returns a localized version of the specified string within the specified context, adjusting for plurals using %n. +std::string TranslatePluralToString(const char* context, const char* msg, const char* disambiguation, int count); + /// Clears the translation cache. All previously used strings should be considered invalid. void ClearTranslationCache(); @@ -73,6 +76,10 @@ s32 GetTranslatedStringImpl(std::string_view context, std::string_view msg, char #define TRANSLATE_SV(context, msg) Host::TranslateToStringView(context, msg) #define TRANSLATE_STR(context, msg) Host::TranslateToString(context, msg) #define TRANSLATE_FS(context, msg) fmt::runtime(Host::TranslateToStringView(context, msg)) +#define TRANSLATE_PLURAL_STR(context, msg, disambiguation, count) \ + Host::TranslatePluralToString(context, msg, disambiguation, count) +#define TRANSLATE_PLURAL_FS(context, msg, disambiguation, count) \ + fmt::runtime(Host::TranslatePluralToString(context, msg, disambiguation, count)) // Does not translate the string at runtime, but allows the UI to in its own way. #define TRANSLATE_NOOP(context, msg) msg