diff --git a/common/vsprops/QtCompile.props b/common/vsprops/QtCompile.props index 082668d70f..e3caedf6c2 100644 --- a/common/vsprops/QtCompile.props +++ b/common/vsprops/QtCompile.props @@ -167,20 +167,25 @@ /> - + + Outputs="@(BaseTsFiles -> '$(QtTsOutDir)%(RecursiveDir)%(Filename.Replace('qtbase_', 'qt_'))%(Extension)')"> + + + + %(Identity) + %(RecursiveDir) + + - + diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt index 86a19a381e..8884a336fd 100644 --- a/pcsx2-qt/CMakeLists.txt +++ b/pcsx2-qt/CMakeLists.txt @@ -26,6 +26,7 @@ target_sources(pcsx2-qt PRIVATE PrecompiledHeader.h SettingWidgetBinder.h Themes.cpp + Translations.cpp QtHost.cpp QtHost.h QtKeyCodes.cpp @@ -173,6 +174,10 @@ if(USE_ACHIEVEMENTS) ) endif() +set(TS_FILES + Translations/pcsx2-qt_en.ts +) + target_precompile_headers(pcsx2-qt PRIVATE PrecompiledHeader.h) target_include_directories(pcsx2-qt PRIVATE @@ -191,6 +196,24 @@ target_link_libraries(pcsx2-qt PRIVATE Qt6::Network ) +if(WIN32) + set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION "${CMAKE_SOURCE_DIR}/bin/translations") + qt_add_lrelease(pcsx2-qt TS_FILES ${TS_FILES}) +elseif(APPLE) + # Hopefully macdeployqt will pick these up. + qt_add_lrelease(pcsx2-qt TS_FILES ${TS_FILES}) +else() + # TODO: Copy base translations. + qt_add_lrelease(pcsx2-qt TS_FILES ${TS_FILES} QM_FILES_OUTPUT_VARIABLE QM_FILES) + set(QM_OUTPUT_DIR "$/translations") + add_custom_command(TARGET pcsx2-qt POST_BUILD COMMAND "${CMAKE_COMMAND}" -E make_directory "${QM_OUTPUT_DIR}") + foreach (QM_FILE IN LISTS QM_FILES) + get_filename_component(QM_FILE_NAME ${QM_FILE} NAME) + add_custom_command(TARGET pcsx2-qt POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${QM_FILE}" "${QM_OUTPUT_DIR}/${QM_FILE_NAME}") + endforeach() +endif() + + # Currently, 7z is only needed for the Windows updater. if(WIN32) target_link_libraries(pcsx2-qt PRIVATE diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index 4f4c4ae761..91a529d25a 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -1394,6 +1394,12 @@ void MainWindow::updateTheme() m_game_list_widget->refreshImages(); } +void MainWindow::updateLanguage() +{ + QtHost::InstallTranslator(); + recreate(); +} + void MainWindow::onInputRecNewActionTriggered() { const bool wasPaused = s_vm_paused; @@ -2064,6 +2070,11 @@ SettingsDialog* MainWindow::getSettingsDialog() { m_settings_dialog = new SettingsDialog(this); connect(m_settings_dialog->getInterfaceSettingsWidget(), &InterfaceSettingsWidget::themeChanged, this, &MainWindow::updateTheme); + connect(m_settings_dialog->getInterfaceSettingsWidget(), &InterfaceSettingsWidget::languageChanged, this, [this]() { + // reopen settings dialog after it applies + updateLanguage(); + g_main_window->doSettings("Interface"); + }); } return m_settings_dialog; diff --git a/pcsx2-qt/MainWindow.h b/pcsx2-qt/MainWindow.h index 6eea22df28..102c631f00 100644 --- a/pcsx2-qt/MainWindow.h +++ b/pcsx2-qt/MainWindow.h @@ -159,6 +159,7 @@ private Q_SLOTS: void onToolsOpenDataDirectoryTriggered(); void onToolsCoverDownloaderTriggered(); void updateTheme(); + void updateLanguage(); void onScreenshotActionTriggered(); void onSaveGSDumpActionTriggered(); void onBlockDumpActionToggled(bool checked); diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index 897d0f2a58..370f462ccc 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -1179,23 +1179,6 @@ void Host::SetFullscreen(bool enabled) g_emu_thread->setFullscreen(enabled, true); } -s32 Host::Internal::GetTranslatedStringImpl( - const std::string_view& context, const std::string_view& msg, char* tbuf, size_t tbuf_space) -{ - // This is really awful. Thankfully we're caching the results... - const std::string temp_context(context); - const std::string temp_msg(msg); - const QString translated_msg = qApp->translate(temp_context.c_str(), temp_msg.c_str()); - const QByteArray translated_utf8 = translated_msg.toUtf8(); - const size_t translated_size = translated_utf8.size(); - if (translated_size > tbuf_space) - return -1; - else if (translated_size > 0) - std::memcpy(tbuf, translated_utf8.constData(), translated_size); - - return static_cast(translated_size); -} - alignas(16) static SysMtgsThread s_mtgs_thread; SysMtgsThread& GetMTGS() @@ -1237,6 +1220,7 @@ bool QtHost::InitializeConfig() LogSink::SetBlockSystemConsole(QtHost::InNoGUIMode()); VMManager::Internal::LoadStartupSettings(); + InstallTranslator(); return true; } diff --git a/pcsx2-qt/QtHost.h b/pcsx2-qt/QtHost.h index 9d029c62c2..beaeb65b10 100644 --- a/pcsx2-qt/QtHost.h +++ b/pcsx2-qt/QtHost.h @@ -216,6 +216,9 @@ namespace QtHost /// Default theme name for the platform. const char* GetDefaultThemeName(); + /// Default language for the platform. + const char* GetDefaultLanguage(); + /// Sets application theme according to settings. void UpdateApplicationTheme(); @@ -234,6 +237,12 @@ namespace QtHost /// Executes a function on the UI thread. void RunOnUIThread(const std::function& func, bool block = false); + /// Returns a list of supported languages and codes (suffixes for translation files). + std::vector> GetAvailableLanguageList(); + + /// Call when the language changes. + void InstallTranslator(); + /// Returns the application name and version, optionally including debug/devel config indicator. QString GetAppNameAndVersion(); diff --git a/pcsx2-qt/Settings/ControllerBindingWidgets.cpp b/pcsx2-qt/Settings/ControllerBindingWidgets.cpp index dd8f0fdc77..158e770311 100644 --- a/pcsx2-qt/Settings/ControllerBindingWidgets.cpp +++ b/pcsx2-qt/Settings/ControllerBindingWidgets.cpp @@ -116,8 +116,8 @@ void ControllerBindingWidget::onTypeChanged() if (has_settings) { const gsl::span settings(cinfo->settings, cinfo->num_settings); - m_settings_widget = - new ControllerCustomSettingsWidget(settings, m_config_section, std::string(), cinfo->name, getDialog(), m_ui.stackedWidget); + m_settings_widget = new ControllerCustomSettingsWidget( + settings, m_config_section, std::string(), "Pad", getDialog(), m_ui.stackedWidget); m_ui.stackedWidget->addWidget(m_settings_widget); } @@ -343,7 +343,7 @@ ControllerMacroEditWidget::ControllerMacroEditWidget(ControllerMacroWidget* pare continue; QListWidgetItem* item = new QListWidgetItem(); - item->setText(QString::fromUtf8(bi.display_name)); + item->setText(qApp->translate("Pad", bi.display_name)); item->setCheckState((std::find(m_binds.begin(), m_binds.end(), &bi) != m_binds.end()) ? Qt::Checked : Qt::Unchecked); m_ui.bindList->addItem(item); } @@ -374,7 +374,7 @@ QString ControllerMacroEditWidget::getSummary() const { if (!str.isEmpty()) str += static_cast('/'); - str += QString::fromUtf8(bi->name); + str += qApp->translate("Pad", bi->display_name); } return str.isEmpty() ? tr("Not Configured") : str; } @@ -950,7 +950,7 @@ void USBDeviceWidget::populatePages() if (!settings.empty()) { m_settings_widget = new ControllerCustomSettingsWidget( - settings, m_config_section, m_device_type + "_", m_device_type.c_str(), m_dialog, m_ui.stackedWidget); + settings, m_config_section, m_device_type + "_", "USB", m_dialog, m_ui.stackedWidget); m_ui.stackedWidget->addWidget(m_settings_widget); } diff --git a/pcsx2-qt/Settings/ControllerSettingsDialog.cpp b/pcsx2-qt/Settings/ControllerSettingsDialog.cpp index baf9fcf1db..3fefc5b356 100644 --- a/pcsx2-qt/Settings/ControllerSettingsDialog.cpp +++ b/pcsx2-qt/Settings/ControllerSettingsDialog.cpp @@ -401,7 +401,7 @@ void ControllerSettingsDialog::createWidgets() m_ui.settingsContainer->addWidget(m_port_bindings[global_slot]); const PAD::ControllerInfo* ci = PAD::GetControllerInfo(m_port_bindings[global_slot]->getControllerType()); - const QString display_name(ci ? QString::fromUtf8(ci->display_name) : QStringLiteral("Unknown")); + const QString display_name(ci ? qApp->translate("Pad", ci->display_name) : QStringLiteral("Unknown")); QListWidgetItem* item = new QListWidgetItem(); //: Controller Port is an official term from Sony. Find the official translation for your language inside the console's manual. @@ -457,7 +457,7 @@ void ControllerSettingsDialog::updateListDescription(u32 global_slot, Controller const bool mtap_enabled = getBoolValue("Pad", (port == 0) ? "MultitapPort1" : "MultitapPort2", false); const PAD::ControllerInfo* ci = PAD::GetControllerInfo(widget->getControllerType()); - const QString display_name(ci ? QString::fromUtf8(ci->display_name) : QStringLiteral("Unknown")); + const QString display_name(ci ? qApp->translate("Pad", ci->display_name) : QStringLiteral("Unknown")); //: Controller Port is an official term from Sony. Find the official translation for your language inside the console's manual. item->setText(mtap_enabled ? (tr("Controller Port %1%2\n%3").arg(port + 1).arg(s_mtap_slot_names[slot]).arg(display_name)) : diff --git a/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp b/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp index bceae748bc..7060cf98e5 100644 --- a/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp +++ b/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp @@ -95,6 +95,10 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsDialog* dialog, QWidget QtHost::GetDefaultThemeName()); connect(m_ui.theme, QOverload::of(&QComboBox::currentIndexChanged), [this]() { emit themeChanged(); }); + populateLanguages(); + SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.language, "UI", "Language", QtHost::GetDefaultLanguage()); + connect(m_ui.language, QOverload::of(&QComboBox::currentIndexChanged), [this]() { emit languageChanged(); }); + // Per-game settings is special, we don't want to bind it if we're editing per-game settings. m_ui.perGameSettings->setEnabled(!dialog->isPerGameSettings()); if (!dialog->isPerGameSettings()) @@ -181,9 +185,6 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsDialog* dialog, QWidget m_ui.disableWindowResizing, tr("Disable Window Resizing"), tr("Unchecked"), tr("Prevents the main window from being resized.")); - // Not yet used, disable the options - m_ui.language->setDisabled(true); - onRenderToSeparateWindowChanged(); } @@ -193,3 +194,9 @@ void InterfaceSettingsWidget::onRenderToSeparateWindowChanged() { m_ui.hideMainWindow->setEnabled(m_ui.renderToSeparateWindow->isChecked()); } + +void InterfaceSettingsWidget::populateLanguages() +{ + for (const std::pair& it : QtHost::GetAvailableLanguageList()) + m_ui.language->addItem(it.first, it.second); +} diff --git a/pcsx2-qt/Settings/InterfaceSettingsWidget.h b/pcsx2-qt/Settings/InterfaceSettingsWidget.h index 665ab71546..06d44e3ca0 100644 --- a/pcsx2-qt/Settings/InterfaceSettingsWidget.h +++ b/pcsx2-qt/Settings/InterfaceSettingsWidget.h @@ -31,10 +31,13 @@ public: Q_SIGNALS: void themeChanged(); + void languageChanged(); private Q_SLOTS: void onRenderToSeparateWindowChanged(); private: + void populateLanguages(); + Ui::InterfaceSettingsWidget m_ui; }; diff --git a/pcsx2-qt/Translations.cpp b/pcsx2-qt/Translations.cpp new file mode 100644 index 0000000000..88f06f7e5d --- /dev/null +++ b/pcsx2-qt/Translations.cpp @@ -0,0 +1,218 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 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 PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "QtHost.h" + +#include "common/Console.h" +#include "common/StringUtil.h" + +#include "pcsx2/ImGui/ImGuiManager.h" + +#include "fmt/format.h" +#include "imgui.h" + +#include +#include +#include +#include + +#include + +#ifdef _WIN32 +#include "common/RedtapeWindows.h" +#include +#include +#endif + +namespace QtHost +{ + static const ImWchar* GetGlyphRangesJapanese(); + static const ImWchar* GetGlyphRangesChinese(); + static std::string GetFontPath(const char* name); +} // namespace QtHost + +static std::vector s_translators; + +void QtHost::InstallTranslator() +{ + for (QTranslator* translator : s_translators) + { + qApp->removeTranslator(translator); + translator->deleteLater(); + } + s_translators.clear(); + + const QString language = + QString::fromStdString(Host::GetBaseStringSettingValue("UI", "Language", GetDefaultLanguage())); + + // Install the base qt translation first. + const QString base_dir = QStringLiteral("%1/translations").arg(qApp->applicationDirPath()); + QString base_path = QStringLiteral("%1/qt_%2.qm").arg(base_dir).arg(language); + bool has_base_ts = QFile::exists(base_path); + if (!has_base_ts) + { + // Try without the country suffix. + const int index = language.indexOf('-'); + if (index > 0) + { + base_path = QStringLiteral("%1/qt_%2.qm").arg(base_dir).arg(language.left(index)); + has_base_ts = QFile::exists(base_path); + } + } + if (has_base_ts) + { + QTranslator* base_translator = new QTranslator(qApp); + if (!base_translator->load(base_path)) + { + QMessageBox::warning(nullptr, QStringLiteral("Translation Error"), + QStringLiteral("Failed to find load base translation file for '%1':\n%2").arg(language).arg(base_path)); + delete base_translator; + } + else + { + s_translators.push_back(base_translator); + qApp->installTranslator(base_translator); + } + } + + const QString path = QStringLiteral("%1/pcsx2-qt_%3.qm").arg(base_dir).arg(language); + if (!QFile::exists(path)) + { +#ifdef PCSX2_DEVBUILD + // For now, until we're sure this works on all platforms, we won't block users from starting if they're missing. + QMessageBox::warning(nullptr, QStringLiteral("Translation Error"), + QStringLiteral("Failed to find translation file for language '%1':\n%2").arg(language).arg(path)); +#endif + return; + } + + QTranslator* translator = new QTranslator(qApp); + if (!translator->load(path)) + { + QMessageBox::warning(nullptr, QStringLiteral("Translation Error"), + QStringLiteral("Failed to load translation file for language '%1':\n%2").arg(language).arg(path)); + delete translator; + return; + } + + Console.WriteLn(Color_StrongYellow, "Loaded translation file for language %s", language.toUtf8().constData()); + qApp->installTranslator(translator); + s_translators.push_back(translator); + +#ifdef _WIN32 + if (language == QStringLiteral("ja")) + { + ImGuiManager::SetFontPath(GetFontPath("msgothic.ttc")); + ImGuiManager::SetFontRange(GetGlyphRangesJapanese()); + } + else if (language == QStringLiteral("zh-cn")) + { + ImGuiManager::SetFontPath(GetFontPath("msyh.ttc")); + ImGuiManager::SetFontRange(GetGlyphRangesChinese()); + } +#endif +} + +static std::string QtHost::GetFontPath(const char* name) +{ +#ifdef _WIN32 + PWSTR folder_path; + if (FAILED(SHGetKnownFolderPath(FOLDERID_Fonts, 0, nullptr, &folder_path))) + return fmt::format("C:\\Windows\\Fonts\\%s", name); + + std::string font_path(StringUtil::WideStringToUTF8String(folder_path)); + CoTaskMemFree(folder_path); + font_path += "\\"; + font_path += name; + return font_path; +#else + return name; +#endif +} + +std::vector> QtHost::GetAvailableLanguageList() +{ + return { + {QStringLiteral("English"), QStringLiteral("en")}, + }; +} + +const char* QtHost::GetDefaultLanguage() +{ + return "en"; +} + +s32 Host::Internal::GetTranslatedStringImpl( + const std::string_view& context, const std::string_view& msg, char* tbuf, size_t tbuf_space) +{ + // This is really awful. Thankfully we're caching the results... + const std::string temp_context(context); + const std::string temp_msg(msg); + const QString translated_msg = qApp->translate(temp_context.c_str(), temp_msg.c_str()); + const QByteArray translated_utf8 = translated_msg.toUtf8(); + const size_t translated_size = translated_utf8.size(); + if (translated_size > tbuf_space) + return -1; + else if (translated_size > 0) + std::memcpy(tbuf, translated_utf8.constData(), translated_size); + + return static_cast(translated_size); +} + +static const ImWchar* QtHost::GetGlyphRangesJapanese() +{ + // clang-format off + // auto update by generate_update_glyph_ranges.py with pcsx2-qt_ja.ts + static const char16_t chars[] = u"、。あいかがきこさしすずせたってでとなにのはべまみらりるれをんァィイェエカキクグゲシジスセタダチッデトドバパフブプボポミムメモャュョラリルレロンー一中了今件体使信入出制効動取口可変始定実度強後得態択挿易更有本検無状獲用画的索終績能自行覧解設読起送速選開除難面高"; + const int chars_length = sizeof(chars) / sizeof(chars[0]); + // clang-format on + + static ImWchar base_ranges[] = { + 0x0020, 0x007E, // Basic Latin + }; + const int base_length = sizeof(base_ranges) / sizeof(base_ranges[0]); + + static ImWchar full_ranges[base_length + chars_length * 2 + 1] = {0}; + memcpy(full_ranges, base_ranges, sizeof(base_ranges)); + for (int i = 0; i < chars_length; i++) + { + full_ranges[base_length + i * 2] = full_ranges[base_length + i * 2 + 1] = chars[i]; + } + return full_ranges; +} + +static const ImWchar* QtHost::GetGlyphRangesChinese() +{ + // clang-format off + // auto update by generate_update_glyph_ranges.py with pcsx2-qt_zh-cn.ts + static const char16_t chars[] = u""; + const int chars_length = sizeof(chars) / sizeof(chars[0]); + // clang-format on + + static ImWchar base_ranges[] = { + 0x0020, 0x007E, // Basic Latin + }; + const int base_length = sizeof(base_ranges) / sizeof(base_ranges[0]); + + static ImWchar full_ranges[base_length + chars_length * 2 + 1] = {0}; + memcpy(full_ranges, base_ranges, sizeof(base_ranges)); + for (int i = 0; i < chars_length; i++) + { + full_ranges[base_length + i * 2] = full_ranges[base_length + i * 2 + 1] = chars[i]; + } + return full_ranges; +} diff --git a/pcsx2-qt/Translations/generate_update_glyph_ranges.py b/pcsx2-qt/Translations/generate_update_glyph_ranges.py new file mode 100644 index 0000000000..0981245454 --- /dev/null +++ b/pcsx2-qt/Translations/generate_update_glyph_ranges.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +import sys +import os +import re +import xml.etree.ElementTree as ET + +src_path = os.path.join(os.path.dirname(__file__), "..", "Translations.cpp") + +def parse_xml(path): + translations = "" + tree = ET.parse(path) + root = tree.getroot() + for node in root.findall("context/message/translation"): + if node.text: + translations += node.text + + chars = list(set([ord(ch) for ch in translations if ord(ch) >= 0x2000])) + chars.sort() + chars = "".join([chr(ch) for ch in chars]) + return chars + +def update_src_file(ts_file, chars): + ts_name = os.path.basename(ts_file) + pattern = re.compile('(// auto update.*' + ts_name + '.*\n[^"]+")[^"]*(".*)') + with open(src_path, "r", encoding="utf-8") as f: + original = f.read() + update = pattern.sub("\\1" + chars + "\\2", original) + if original != update: + with open(src_path, "w", encoding="utf-8") as f: + f.write(update) + print("Updated character list.") + else: + print("Character list is unchanged.") + +if __name__ == "__main__": + if len(sys.argv) < 2: + print(f"usage: {sys.argv[0]} ") + sys.exit(1) + + chars = parse_xml(sys.argv[1]) + #print(chars) + print(f"{len(chars)} character(s) detected.") + update_src_file(sys.argv[1], chars) diff --git a/pcsx2-qt/Translations/update_en_translation.bat b/pcsx2-qt/Translations/update_en_translation.bat new file mode 100644 index 0000000000..1ac4a76fb5 --- /dev/null +++ b/pcsx2-qt/Translations/update_en_translation.bat @@ -0,0 +1,19 @@ +@echo off + +set QTBIN=..\..\3rdparty\qt\6.5.0\msvc2022_64\bin +set SRCDIRS=../ ../../pcsx2/ + +set OPTS=-tr-function-alias QT_TRANSLATE_NOOP+=TRANSLATE,QT_TRANSLATE_NOOP+=TRANSLATE_SV,QT_TRANSLATE_NOOP+=TRANSLATE_STR,QT_TRANSLATE_N_NOOP3+=TRANSLATE_FMT,QT_TRANSLATE_NOOP+=TRANSLATE_NOOP + +"%QTBIN%\lupdate.exe" %SRCDIRS% %OPTS% -no-obsolete -source-language en -ts pcsx2-qt_en.ts + +echo. +echo ****************************************************************************************************************** +echo * PLEASE READ * +echo ****************************************************************************************************************** +echo. +echo Make sure you have deleted the build (x64) directory from pcsx2-qt, otherwise you will have polluted the .ts file. +echo If you did not, then you should reset/checkout the ts file, delete the build directory, and run this script again. +echo. +echo. +pause diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj index b47735f1be..abc1d08b4f 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj +++ b/pcsx2-qt/pcsx2-qt.vcxproj @@ -39,6 +39,7 @@ %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\include %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\lzma\include %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\simpleini\include + %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\imgui\include %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\demangler\include %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include;%(AdditionalIncludeDirectories); %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\sdl2\include;$(SolutionDir)3rdparty\sdl2\SDL\include @@ -138,6 +139,7 @@ + @@ -365,6 +367,9 @@ + + Document + Document diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters index 2c43296983..9ad6014506 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj.filters +++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters @@ -25,6 +25,9 @@ {f4084ca0-d9d5-4584-b9d2-063db9f67de2} + + {ad04f939-64a0-4039-97aa-a38b8aa46855} + @@ -328,6 +331,7 @@ moc + @@ -619,4 +623,9 @@ Settings + + + Translations + + \ No newline at end of file diff --git a/tests/ctest/core/StubHost.cpp b/tests/ctest/core/StubHost.cpp index 62a9565138..8a510e111a 100644 --- a/tests/ctest/core/StubHost.cpp +++ b/tests/ctest/core/StubHost.cpp @@ -191,6 +191,18 @@ void Host::VSyncOnCPUThread() { } +s32 Host::Internal::GetTranslatedStringImpl( + const std::string_view& context, const std::string_view& msg, char* tbuf, size_t tbuf_space) +{ + if (msg.size() > tbuf_space) + return -1; + else if (msg.empty()) + return 0; + + std::memcpy(tbuf, msg.data(), msg.size()); + return static_cast(msg.size()); +} + #ifdef ENABLE_ACHIEVEMENTS void Host::OnAchievementsRefreshed() {