From 96800c6f9785d0fc9822da24421a7e6ac9014dd9 Mon Sep 17 00:00:00 2001 From: Francesco Saltori Date: Thu, 14 Sep 2023 12:47:59 +0200 Subject: [PATCH] Additional localization fixes (#966) --- .github/workflows/generate_pot.yml | 4 +- .../Tools/DownloadManager/DownloadManager.cpp | 20 +++--- src/gui/CemuApp.cpp | 2 +- src/gui/GameProfileWindow.cpp | 4 +- src/gui/GameUpdateWindow.cpp | 22 +++---- src/gui/GeneralSettings2.cpp | 10 +-- src/gui/GeneralSettings2.h | 2 +- src/gui/GraphicPacksWindow2.cpp | 15 ++--- src/gui/MemorySearcherTool.cpp | 4 +- src/gui/TitleManager.cpp | 2 +- src/gui/components/wxDownloadManagerList.cpp | 8 +-- src/gui/components/wxDownloadManagerList.h | 2 +- src/gui/components/wxGameList.cpp | 19 +++--- src/gui/components/wxTitleManagerList.cpp | 12 ++-- src/gui/components/wxTitleManagerList.h | 2 +- src/gui/wxgui.h | 63 +------------------ 16 files changed, 68 insertions(+), 123 deletions(-) diff --git a/.github/workflows/generate_pot.yml b/.github/workflows/generate_pot.yml index f2675574..7dfa86f8 100644 --- a/.github/workflows/generate_pot.yml +++ b/.github/workflows/generate_pot.yml @@ -29,8 +29,8 @@ jobs: - name: "Generate POT file using xgettext" run: > find src -name *.cpp -o -name *.hpp -o -name *.h | - xargs xgettext --from-code=utf-8 - -k_ -kwxTRANSLATE -w 100 + xargs xgettext --from-code=utf-8 -w 100 + --keyword="_" --keyword="wxTRANSLATE" --keyword="wxPLURAL:1,2" --check=space-ellipsis --omit-header -o cemu.pot diff --git a/src/Cemu/Tools/DownloadManager/DownloadManager.cpp b/src/Cemu/Tools/DownloadManager/DownloadManager.cpp index 200d1641..ec39b928 100644 --- a/src/Cemu/Tools/DownloadManager/DownloadManager.cpp +++ b/src/Cemu/Tools/DownloadManager/DownloadManager.cpp @@ -424,7 +424,7 @@ bool DownloadManager::syncAccountTickets() bool DownloadManager::syncSystemTitleTickets() { - setStatusMessage(std::string(_("Downloading system tickets...")), DLMGR_STATUS_CODE::CONNECTING); + setStatusMessage(_("Downloading system tickets...").utf8_string(), DLMGR_STATUS_CODE::CONNECTING); // todo - add GetAuth() function NAPI::AuthInfo authInfo; authInfo.accountId = m_authInfo.nnidAccountName; @@ -486,7 +486,7 @@ bool DownloadManager::syncSystemTitleTickets() // build list of updates for which either an installed game exists or the base title ticket is cached bool DownloadManager::syncUpdateTickets() { - setStatusMessage(std::string(_("Retrieving update information...")), DLMGR_STATUS_CODE::CONNECTING); + setStatusMessage(_("Retrieving update information...").utf8_string(), DLMGR_STATUS_CODE::CONNECTING); // download update version list downloadTitleVersionList(); if (!m_hasTitleVersionList) @@ -566,7 +566,7 @@ bool DownloadManager::syncTicketCache() setStatusMessage(msg, DLMGR_STATUS_CODE::CONNECTING); prepareIDBE(ticketInfo.titleId); } - setStatusMessage(std::string(_("Connected. Right click entries in the list to start downloading")), DLMGR_STATUS_CODE::CONNECTED); + setStatusMessage(_("Connected. Right click entries in the list to start downloading").utf8_string(), DLMGR_STATUS_CODE::CONNECTED); return true; } @@ -652,7 +652,7 @@ void DownloadManager::_handle_connect() // reset login state m_iasToken.serviceAccountId.clear(); m_iasToken.deviceToken.clear(); - setStatusMessage(std::string(_("Logging in..")), DLMGR_STATUS_CODE::CONNECTING); + setStatusMessage(_("Logging in...").utf8_string(), DLMGR_STATUS_CODE::CONNECTING); // retrieve ECS AccountId + DeviceToken from cache if (s_nupFileCache) { @@ -675,7 +675,7 @@ void DownloadManager::_handle_connect() cemuLog_log(LogType::Force, "Failed to request IAS token"); cemu_assert_debug(false); m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(std::string(_("Login failed. Outdated or incomplete online files?")), DLMGR_STATUS_CODE::FAILED); + setStatusMessage(_("Login failed. Outdated or incomplete online files?").utf8_string(), DLMGR_STATUS_CODE::FAILED); return; } } @@ -683,16 +683,16 @@ void DownloadManager::_handle_connect() if (!_connect_queryAccountStatusAndServiceURLs()) { m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(std::string(_("Failed to query account status. Invalid account information?")), DLMGR_STATUS_CODE::FAILED); + setStatusMessage(_("Failed to query account status. Invalid account information?").utf8_string(), DLMGR_STATUS_CODE::FAILED); return; } // load ticket cache and sync - setStatusMessage(std::string(_("Updating ticket cache")), DLMGR_STATUS_CODE::CONNECTING); + setStatusMessage(_("Updating ticket cache").utf8_string(), DLMGR_STATUS_CODE::CONNECTING); loadTicketCache(); if (!syncTicketCache()) { m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(std::string(_("Failed to request tickets (invalid NNID?)")), DLMGR_STATUS_CODE::FAILED); + setStatusMessage(_("Failed to request tickets (invalid NNID?)").utf8_string(), DLMGR_STATUS_CODE::FAILED); return; } searchForIncompleteDownloads(); @@ -716,7 +716,7 @@ void DownloadManager::connect( if (nnidAccountName.empty()) { m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(std::string(_("This account is not linked with an NNID")), DLMGR_STATUS_CODE::FAILED); + setStatusMessage(_("This account is not linked with an NNID").utf8_string(), DLMGR_STATUS_CODE::FAILED); return; } runManager(); @@ -726,7 +726,7 @@ void DownloadManager::connect( { cemuLog_log(LogType::Force, "DLMgr: Invalid password hash"); m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(std::string(_("Failed. Account does not have password set")), DLMGR_STATUS_CODE::FAILED); + setStatusMessage(_("Failed. Account does not have password set").utf8_string(), DLMGR_STATUS_CODE::FAILED); return; } m_authInfo.region = region; diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index 03496305..74ef6848 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -169,7 +169,7 @@ bool CemuApp::OnInit() "Thank you for testing the in-development build of Cemu for macOS.\n \n" "The macOS port is currently purely experimental and should not be considered stable or ready for issue-free gameplay. " "There are also known issues with degraded performance due to the use of MoltenVk and Rosetta for ARM Macs. We appreciate your patience while we improve Cemu for macOS."); - wxMessageDialog dialog(nullptr, message, "Preview version", wxCENTRE | wxOK | wxICON_WARNING); + wxMessageDialog dialog(nullptr, message, _("Preview version"), wxCENTRE | wxOK | wxICON_WARNING); dialog.SetOKLabel(_("I understand")); dialog.ShowModal(); GetConfig().did_show_macos_disclaimer = true; diff --git a/src/gui/GameProfileWindow.cpp b/src/gui/GameProfileWindow.cpp index 17affc84..f15395e4 100644 --- a/src/gui/GameProfileWindow.cpp +++ b/src/gui/GameProfileWindow.cpp @@ -166,7 +166,7 @@ GameProfileWindow::GameProfileWindow(wxWindow* parent, uint64_t title_id) for (int i = 0; i < 8; ++i) { - profile_sizer->Add(new wxStaticText(panel, wxID_ANY, fmt::format("{} {}", _("Controller").utf8_string(), (i + 1))), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + profile_sizer->Add(new wxStaticText(panel, wxID_ANY, formatWxString(_("Controller {}"), i + 1)), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_controller_profile[i] = new wxComboBox(panel, wxID_ANY,"", wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_DROPDOWN| wxCB_READONLY); m_controller_profile[i]->SetMinSize(wxSize(250, -1)); @@ -244,7 +244,7 @@ void GameProfileWindow::SetProfileInt(gameProfileIntegerOption_t& option, wxChec void GameProfileWindow::ApplyProfile() { if(m_game_profile.m_gameName) - this->SetTitle(fmt::format("{} - {}", _("Edit game profile").utf8_string(), m_game_profile.m_gameName.value())); + this->SetTitle(_("Edit game profile") + " - " + m_game_profile.m_gameName.value()); // general m_load_libs->SetValue(m_game_profile.m_loadSharedLibraries.value()); diff --git a/src/gui/GameUpdateWindow.cpp b/src/gui/GameUpdateWindow.cpp index e422cbe6..184d5fde 100644 --- a/src/gui/GameUpdateWindow.cpp +++ b/src/gui/GameUpdateWindow.cpp @@ -10,24 +10,24 @@ #include "gui/helpers/wxHelpers.h" #include "wxHelper.h" -std::string _GetTitleIdTypeStr(TitleId titleId) +wxString _GetTitleIdTypeStr(TitleId titleId) { TitleIdParser tip(titleId); switch (tip.GetType()) { case TitleIdParser::TITLE_TYPE::AOC: - return _("DLC").utf8_string(); + return _("DLC"); case TitleIdParser::TITLE_TYPE::BASE_TITLE: - return _("Base game").utf8_string(); + return _("Base game"); case TitleIdParser::TITLE_TYPE::BASE_TITLE_DEMO: - return _("Demo").utf8_string(); + return _("Demo"); case TitleIdParser::TITLE_TYPE::SYSTEM_TITLE: case TitleIdParser::TITLE_TYPE::SYSTEM_OVERLAY_TITLE: - return _("System title").utf8_string(); + return _("System title"); case TitleIdParser::TITLE_TYPE::SYSTEM_DATA: - return _("System data title").utf8_string(); + return _("System data title"); case TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE: - return _("Update").utf8_string(); + return _("Update"); default: break; } @@ -57,8 +57,8 @@ bool GameUpdateWindow::ParseUpdate(const fs::path& metaPath) if (tip.GetType() != tipOther.GetType()) { - std::string typeStrToInstall = _GetTitleIdTypeStr(m_title_info.GetAppTitleId()); - std::string typeStrCurrentlyInstalled = _GetTitleIdTypeStr(tmp.GetAppTitleId()); + auto typeStrToInstall = _GetTitleIdTypeStr(m_title_info.GetAppTitleId()); + auto typeStrCurrentlyInstalled = _GetTitleIdTypeStr(tmp.GetAppTitleId()); auto wxMsg = _("It seems that there is already a title installed at the target location but it has a different type.\nCurrently installed: \'{}\' Installing: \'{}\'\n\nThis can happen for titles which were installed with very old Cemu versions.\nDo you still want to continue with the installation? It will replace the currently installed title."); wxMessageDialog dialog(this, formatWxString(wxMsg, typeStrCurrentlyInstalled, typeStrToInstall), _("Warning"), wxCENTRE | wxYES_NO | wxICON_EXCLAMATION); @@ -131,8 +131,8 @@ bool GameUpdateWindow::ParseUpdate(const fs::path& metaPath) const fs::space_info targetSpace = fs::space(ActiveSettings::GetMlcPath()); if (targetSpace.free <= m_required_size) { - auto string = wxStringFormat(_("Not enough space available.\nRequired: {0} MB\nAvailable: {1} MB"), L"%lld %lld", (m_required_size / 1024 / 1024), (targetSpace.free / 1024 / 1024)); - throw std::runtime_error(string); + auto string = formatWxString(_("Not enough space available.\nRequired: {0} MB\nAvailable: {1} MB"), (m_required_size / 1024 / 1024), (targetSpace.free / 1024 / 1024)); + throw std::runtime_error(string.utf8_string()); } return true; diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp index bbe1c474..e069c10a 100644 --- a/src/gui/GeneralSettings2.cpp +++ b/src/gui/GeneralSettings2.cpp @@ -2043,17 +2043,17 @@ void GeneralSettings2::OnShowOnlineValidator(wxCommandEvent& event) wxMessageBox(err, _("Online Status"), wxOK | wxCENTRE | wxICON_INFORMATION); } -std::string GeneralSettings2::GetOnlineAccountErrorMessage(OnlineAccountError error) +wxString GeneralSettings2::GetOnlineAccountErrorMessage(OnlineAccountError error) { switch (error) { case OnlineAccountError::kNoAccountId: - return _("AccountId missing (The account is not connected to a NNID)").utf8_string(); + return _("AccountId missing (The account is not connected to a NNID)"); case OnlineAccountError::kNoPasswordCached: - return _("IsPasswordCacheEnabled is set to false (The remember password option on your Wii U must be enabled for this account before dumping it)").utf8_string(); + return _("IsPasswordCacheEnabled is set to false (The remember password option on your Wii U must be enabled for this account before dumping it)"); case OnlineAccountError::kPasswordCacheEmpty: - return _("AccountPasswordCache is empty (The remember password option on your Wii U must be enabled for this account before dumping it)").utf8_string(); + return _("AccountPasswordCache is empty (The remember password option on your Wii U must be enabled for this account before dumping it)"); case OnlineAccountError::kNoPrincipalId: - return _("PrincipalId missing").utf8_string(); + return _("PrincipalId missing"); default: return "no error"; } diff --git a/src/gui/GeneralSettings2.h b/src/gui/GeneralSettings2.h index b667faf0..2846af38 100644 --- a/src/gui/GeneralSettings2.h +++ b/src/gui/GeneralSettings2.h @@ -101,7 +101,7 @@ private: void OnShowOnlineValidator(wxCommandEvent& event); void OnOnlineEnable(wxCommandEvent& event); void OnAccountServiceChanged(wxCommandEvent& event); - std::string GetOnlineAccountErrorMessage(OnlineAccountError error); + static wxString GetOnlineAccountErrorMessage(OnlineAccountError error); // updates cemu audio devices void UpdateAudioDevice(); diff --git a/src/gui/GraphicPacksWindow2.cpp b/src/gui/GraphicPacksWindow2.cpp index c03c6fdf..2b618e86 100644 --- a/src/gui/GraphicPacksWindow2.cpp +++ b/src/gui/GraphicPacksWindow2.cpp @@ -445,7 +445,7 @@ void GraphicPacksWindow2::OnTreeSelectionChanged(wxTreeEvent& event) m_graphic_pack_name->SetLabel(wxHelper::FromUtf8(m_gp_name)); if (gp->GetDescription().empty()) - m_gp_description = _("This graphic pack has no description"); + m_gp_description = _("This graphic pack has no description").utf8_string(); else m_gp_description = gp->GetDescription(); @@ -609,7 +609,7 @@ void GraphicPacksWindow2::OnCheckForUpdates(wxCommandEvent& event) // check if enabled graphic packs are lost: const auto& new_packs = GraphicPack2::GetGraphicPacks(); - std::stringstream str; + std::stringstream lost_packs; for(const auto& p : old_packs) { if (!p->IsEnabled()) @@ -622,15 +622,16 @@ void GraphicPacksWindow2::OnCheckForUpdates(wxCommandEvent& event) if(it == new_packs.cend()) { - str << p->GetPath() << std::endl; + lost_packs << p->GetPath() << "\n"; } } - const auto packs = str.str(); - if(!packs.empty()) + const auto lost_packs_str = lost_packs.str(); + if (!lost_packs_str.empty()) { - wxMessageBox(fmt::format("{}\n \n{} \n{}", _("This update removed or renamed the following graphic packs:").utf8_string(), packs, _("You may need to set them up again.").utf8_string()), - _("Warning"), wxOK | wxCENTRE | wxICON_INFORMATION, this); + wxString message = _("This update removed or renamed the following graphic packs:"); + message << "\n \n" << lost_packs_str << " \n" << _("You may need to set them up again."); + wxMessageBox(message, _("Warning"), wxOK | wxCENTRE | wxICON_INFORMATION, this); } } } diff --git a/src/gui/MemorySearcherTool.cpp b/src/gui/MemorySearcherTool.cpp index 5e711dd9..fadebc44 100644 --- a/src/gui/MemorySearcherTool.cpp +++ b/src/gui/MemorySearcherTool.cpp @@ -472,9 +472,7 @@ bool MemorySearcherTool::VerifySearchValue() const void MemorySearcherTool::FillResultList() { - //char text[128]; - //sprintf(text, "Results (%u)", (uint32)m_searchBuffer.size()); - auto text = wxStringFormat(_("Results ({0})"), L"%llu", m_searchBuffer.size()); + auto text = formatWxString(_("Results ({0})"), m_searchBuffer.size()); m_textEntryTable->SetLabelText(text); m_listResults->DeleteAllItems(); diff --git a/src/gui/TitleManager.cpp b/src/gui/TitleManager.cpp index a36b3f74..669a1aaf 100644 --- a/src/gui/TitleManager.cpp +++ b/src/gui/TitleManager.cpp @@ -799,7 +799,7 @@ void TitleManager::SetConnected(bool state) void TitleManager::Callback_ConnectStatusUpdate(std::string statusText, DLMGR_STATUS_CODE statusCode) { TitleManager* titleManager = static_cast(DownloadManager::GetInstance()->getUserData()); - titleManager->SetDownloadStatusText(statusText); + titleManager->SetDownloadStatusText(wxString::FromUTF8(statusText)); if (statusCode == DLMGR_STATUS_CODE::FAILED) { auto* evt = new wxCommandEvent(wxEVT_DL_DISCONNECT_COMPLETE); diff --git a/src/gui/components/wxDownloadManagerList.cpp b/src/gui/components/wxDownloadManagerList.cpp index ca2d7a71..14bf5cbe 100644 --- a/src/gui/components/wxDownloadManagerList.cpp +++ b/src/gui/components/wxDownloadManagerList.cpp @@ -501,16 +501,16 @@ wxString wxDownloadManagerList::GetTitleEntryText(const TitleEntry& entry, ItemC return wxEmptyString; } -std::string wxDownloadManagerList::GetTranslatedTitleEntryType(EntryType type) +wxString wxDownloadManagerList::GetTranslatedTitleEntryType(EntryType type) { switch (type) { case EntryType::Base: - return _("base").utf8_string(); + return _("base"); case EntryType::Update: - return _("update").utf8_string(); + return _("update"); case EntryType::DLC: - return _("DLC").utf8_string(); + return _("DLC"); default: return std::to_string(static_cast>(type)); } diff --git a/src/gui/components/wxDownloadManagerList.h b/src/gui/components/wxDownloadManagerList.h index b0051076..3a6b853a 100644 --- a/src/gui/components/wxDownloadManagerList.h +++ b/src/gui/components/wxDownloadManagerList.h @@ -150,6 +150,6 @@ private: bool SortFunc(std::span sortColumnOrder, const Type_t& v1, const Type_t& v2); static wxString GetTitleEntryText(const TitleEntry& entry, ItemColumn column); - static std::string GetTranslatedTitleEntryType(EntryType entryType); + static wxString GetTranslatedTitleEntryType(EntryType entryType); std::future m_context_worker; }; diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index ebbab044..a64b49bf 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -1009,15 +1009,20 @@ void wxGameList::OnGameEntryUpdatedByTitleId(wxTitleIdEvent& event) if (iosu::pdm::GetStatForGamelist(baseTitleId, playTimeStat)) { // time played - uint32 timePlayed = playTimeStat.numMinutesPlayed * 60; - if (timePlayed == 0) + uint32 minutesPlayed = playTimeStat.numMinutesPlayed; + if (minutesPlayed == 0) SetItem(index, ColumnGameTime, wxEmptyString); - else if (timePlayed < 60) - SetItem(index, ColumnGameTime, fmt::format("{} seconds", timePlayed)); - else if (timePlayed < 60 * 60) - SetItem(index, ColumnGameTime, fmt::format("{} minutes", timePlayed / 60)); + else if (minutesPlayed < 60) + SetItem(index, ColumnGameTime, formatWxString(wxPLURAL("{} minute", "{} minutes", minutesPlayed), minutesPlayed)); else - SetItem(index, ColumnGameTime, fmt::format("{} hours {} minutes", timePlayed / 3600, (timePlayed / 60) % 60)); + { + uint32 hours = minutesPlayed / 60; + uint32 minutes = minutesPlayed % 60; + wxString hoursText = formatWxString(wxPLURAL("{} hour", "{} hours", hours), hours); + wxString minutesText = formatWxString(wxPLURAL("{} minute", "{} minutes", minutes), minutes); + SetItem(index, ColumnGameTime, hoursText + " " + minutesText); + } + // last played if (playTimeStat.last_played.year != 0) { diff --git a/src/gui/components/wxTitleManagerList.cpp b/src/gui/components/wxTitleManagerList.cpp index bae986ca..aad46c52 100644 --- a/src/gui/components/wxTitleManagerList.cpp +++ b/src/gui/components/wxTitleManagerList.cpp @@ -963,20 +963,20 @@ wxString wxTitleManagerList::GetTitleEntryText(const TitleEntry& entry, ItemColu return wxEmptyString; } -std::string wxTitleManagerList::GetTranslatedTitleEntryType(EntryType type) +wxString wxTitleManagerList::GetTranslatedTitleEntryType(EntryType type) { switch (type) { case EntryType::Base: - return _("base").utf8_string(); + return _("base"); case EntryType::Update: - return _("update").utf8_string(); + return _("update"); case EntryType::Dlc: - return _("DLC").utf8_string(); + return _("DLC"); case EntryType::Save: - return _("save").utf8_string(); + return _("save"); case EntryType::System: - return _("system").utf8_string(); + return _("system"); default: return std::to_string(static_cast>(type)); } diff --git a/src/gui/components/wxTitleManagerList.h b/src/gui/components/wxTitleManagerList.h index 043c78f6..07556068 100644 --- a/src/gui/components/wxTitleManagerList.h +++ b/src/gui/components/wxTitleManagerList.h @@ -132,7 +132,7 @@ private: bool SortFunc(int column, const Type_t& v1, const Type_t& v2); static wxString GetTitleEntryText(const TitleEntry& entry, ItemColumn column); - static std::string GetTranslatedTitleEntryType(EntryType entryType); + static wxString GetTranslatedTitleEntryType(EntryType entryType); std::future m_context_worker; uint64 m_callbackIdTitleList; diff --git a/src/gui/wxgui.h b/src/gui/wxgui.h index 098449d6..bb4352d1 100644 --- a/src/gui/wxgui.h +++ b/src/gui/wxgui.h @@ -1,5 +1,7 @@ #pragma once +#define wxNO_UNSAFE_WXSTRING_CONV 1 + #include #ifndef WX_PRECOMP #include @@ -36,67 +38,6 @@ extern bool g_inputConfigWindowHasFocus; -// wx helper functions -#include -struct wxStringFormatParameters -{ - sint32 parameter_index; - sint32 parameter_count; - - wchar_t* token_buffer; - wchar_t* substitude_parameter; -}; - -template -wxString wxStringFormat(std::wstring& format, wxStringFormatParameters& parameters) -{ - return format; -} - -template -wxString wxStringFormat(std::wstring& format, wxStringFormatParameters& parameters, T arg, Args... args) -{ - wchar_t tmp[64]; - swprintf(tmp, 64, LR"(\{[%d]+\})", parameters.parameter_index); - const std::wregex placeholder_regex(tmp); - - auto result = format; - while (std::regex_search(result, placeholder_regex)) - { - result = std::regex_replace(result, placeholder_regex, parameters.substitude_parameter, std::regex_constants::format_first_only); - result = wxString::Format(wxString(result), arg); - } - - parameters.parameter_index++; - if (parameters.parameter_index == parameters.parameter_count) - return result; - - parameters.substitude_parameter = std::wcstok(nullptr, LR"( )", ¶meters.token_buffer); - return wxStringFormat(result, parameters, args...); -} - -template -wxString wxStringFormat(const wxString& format, const wchar_t* parameters, T... args) -{ - const auto parameter_count = std::count(parameters, parameters + wcslen(parameters), '%'); - if (parameter_count == 0) - return format; - - const auto copy = wcsdup(parameters); - - wxStringFormatParameters para; - para.substitude_parameter = std::wcstok(copy, LR"( )", ¶.token_buffer); - para.parameter_count = parameter_count; - para.parameter_index = 0; - - auto tmp_string = format.ToStdWstring(); - auto result = wxStringFormat(tmp_string, para, args...); - - free(copy); - - return result; -} - inline bool SendSliderEvent(wxSlider* slider, int new_value) { wxCommandEvent cevent(wxEVT_SLIDER, slider->GetId());