Qt: Localization support

This commit is contained in:
Stenzek 2023-06-19 22:03:10 +10:00 committed by Connor McLaughlin
parent c359223fd4
commit adcfca4db3
16 changed files with 384 additions and 34 deletions

View File

@ -167,20 +167,25 @@
/>
</Target>
<!--Copies base translation files-->
<!--Copies base translation files, need to rename them from qtbase_ to qt_ -->
<ItemGroup>
<BaseTsFiles Include="$(QtTranslationsDir)\*.qm" />
</ItemGroup>
<Target Name="QtCopyBaseTranslations"
AfterTargets="Build"
Inputs="@(BaseTsFiles)"
Outputs="@(BaseTsFiles -> '$(QtTsOutDir)%(RecursiveDir)%(Filename)%(Extension)')">
Outputs="@(BaseTsFiles -> '$(QtTsOutDir)%(RecursiveDir)%(Filename.Replace('qtbase_', 'qt_'))%(Extension)')">
<!-- https://stackoverflow.com/a/15379344 -->
<ItemGroup>
<TempItems Include="@(BaseTsFiles->'%(Filename)%(Extension)'->Replace('qtbase_', 'qt_'))">
<OriginalPath>%(Identity)</OriginalPath>
<SavedRecursiveDir>%(RecursiveDir)</SavedRecursiveDir>
</TempItems>
</ItemGroup>
<Message Text="Copying base translation files" Importance="High" />
<Copy
SourceFiles="@(BaseTsFiles)"
DestinationFolder="$(QtTsOutDir)"
SkipUnchangedFiles="true"
/>
<Copy SourceFiles="@(TempItems->'%(OriginalPath)')"
DestinationFiles="@(TempItems->'$(QtTsOutDir)%(SavedRecursiveDir)%(Identity)')"
SkipUnchangedFiles="true" />
</Target>
<!--Compiles all translation files-->

View File

@ -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 "$<TARGET_FILE_DIR:pcsx2-qt>/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

View File

@ -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;

View File

@ -159,6 +159,7 @@ private Q_SLOTS:
void onToolsOpenDataDirectoryTriggered();
void onToolsCoverDownloaderTriggered();
void updateTheme();
void updateLanguage();
void onScreenshotActionTriggered();
void onSaveGSDumpActionTriggered();
void onBlockDumpActionToggled(bool checked);

View File

@ -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<s32>(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;
}

View File

@ -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<void()>& func, bool block = false);
/// Returns a list of supported languages and codes (suffixes for translation files).
std::vector<std::pair<QString, QString>> GetAvailableLanguageList();
/// Call when the language changes.
void InstallTranslator();
/// Returns the application name and version, optionally including debug/devel config indicator.
QString GetAppNameAndVersion();

View File

@ -116,8 +116,8 @@ void ControllerBindingWidget::onTypeChanged()
if (has_settings)
{
const gsl::span<const SettingInfo> 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<QChar>('/');
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);
}

View File

@ -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)) :

View File

@ -95,6 +95,10 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsDialog* dialog, QWidget
QtHost::GetDefaultThemeName());
connect(m_ui.theme, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]() { emit themeChanged(); });
populateLanguages();
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.language, "UI", "Language", QtHost::GetDefaultLanguage());
connect(m_ui.language, QOverload<int>::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<QString, QString>& it : QtHost::GetAvailableLanguageList())
m_ui.language->addItem(it.first, it.second);
}

View File

@ -31,10 +31,13 @@ public:
Q_SIGNALS:
void themeChanged();
void languageChanged();
private Q_SLOTS:
void onRenderToSeparateWindowChanged();
private:
void populateLanguages();
Ui::InterfaceSettingsWidget m_ui;
};

218
pcsx2-qt/Translations.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <QtCore/QFile>
#include <QtCore/QTranslator>
#include <QtGui/QGuiApplication>
#include <QtWidgets/QMessageBox>
#include <vector>
#ifdef _WIN32
#include "common/RedtapeWindows.h"
#include <KnownFolders.h>
#include <ShlObj.h>
#endif
namespace QtHost
{
static const ImWchar* GetGlyphRangesJapanese();
static const ImWchar* GetGlyphRangesChinese();
static std::string GetFontPath(const char* name);
} // namespace QtHost
static std::vector<QTranslator*> 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<std::pair<QString, QString>> 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<s32>(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;
}

View File

@ -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]} <pcsx2-qt_*.ts path>")
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)

View File

@ -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

View File

@ -39,6 +39,7 @@
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\lzma\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\simpleini\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\imgui\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\demangler\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\sdl2\include;$(SolutionDir)3rdparty\sdl2\SDL\include</AdditionalIncludeDirectories>
@ -138,6 +139,7 @@
</ClCompile>
<ClCompile Include="QtKeyCodes.cpp" />
<ClCompile Include="QtUtils.cpp" />
<ClCompile Include="Translations.cpp" />
</ItemGroup>
<ItemGroup>
<QtMoc Include="Settings\InterfaceSettingsWidget.h" />
@ -365,6 +367,9 @@
</QtUi>
</ItemGroup>
<ItemGroup>
<QtTs Include="Translations\pcsx2-qt_en.ts">
<FileType>Document</FileType>
</QtTs>
<QtUi Include="Settings\DebugSettingsWidget.ui">
<FileType>Document</FileType>
</QtUi>

View File

@ -25,6 +25,9 @@
<Filter Include="Debugger\Models">
<UniqueIdentifier>{f4084ca0-d9d5-4584-b9d2-063db9f67de2}</UniqueIdentifier>
</Filter>
<Filter Include="Translations">
<UniqueIdentifier>{ad04f939-64a0-4039-97aa-a38b8aa46855}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\pcsx2\windows\PCSX2.rc" />
@ -328,6 +331,7 @@
<ClCompile Include="$(IntDir)Settings\moc_GameCheatSettingsWidget.cpp">
<Filter>moc</Filter>
</ClCompile>
<ClCompile Include="Translations.cpp" />
</ItemGroup>
<ItemGroup>
<Manifest Include="..\pcsx2\windows\PCSX2.manifest">
@ -619,4 +623,9 @@
<Filter>Settings</Filter>
</None>
</ItemGroup>
<ItemGroup>
<QtTs Include="Translations\pcsx2-qt_en.ts">
<Filter>Translations</Filter>
</QtTs>
</ItemGroup>
</Project>

View File

@ -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<s32>(msg.size());
}
#ifdef ENABLE_ACHIEVEMENTS
void Host::OnAchievementsRefreshed()
{