diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt
index 982f55b71..d40b8047c 100644
--- a/src/duckstation-qt/CMakeLists.txt
+++ b/src/duckstation-qt/CMakeLists.txt
@@ -133,7 +133,6 @@ set(SRCS
qtprogresscallback.cpp
qtprogresscallback.h
qtthemes.cpp
- qttranslations.cpp
qtutils.cpp
qtutils.h
resource.h
diff --git a/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj
index 679b5ee53..83f04c2df 100644
--- a/src/duckstation-qt/duckstation-qt.vcxproj
+++ b/src/duckstation-qt/duckstation-qt.vcxproj
@@ -43,7 +43,6 @@
-
diff --git a/src/duckstation-qt/duckstation-qt.vcxproj.filters b/src/duckstation-qt/duckstation-qt.vcxproj.filters
index 7550e53de..dc7acb673 100644
--- a/src/duckstation-qt/duckstation-qt.vcxproj.filters
+++ b/src/duckstation-qt/duckstation-qt.vcxproj.filters
@@ -35,7 +35,6 @@
-
diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp
index d9d8f28e5..ee6312cb4 100644
--- a/src/duckstation-qt/qthost.cpp
+++ b/src/duckstation-qt/qthost.cpp
@@ -59,6 +59,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -78,6 +79,17 @@
LOG_CHANNEL(Host);
+#if 0
+// Mac application menu strings
+QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Services")
+QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Hide %1")
+QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Hide Others")
+QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Show All")
+QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Preferences...")
+QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Quit %1")
+QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "About %1")
+#endif
+
static constexpr u32 SETTINGS_VERSION = 3;
static constexpr u32 SETTINGS_SAVE_DELAY = 1000;
@@ -110,6 +122,7 @@ static void SetDefaultSettings(SettingsInterface& si, bool system, bool controll
static void MigrateSettings();
static void SaveSettings();
static bool RunSetupWizard();
+static void UpdateFontOrder(std::string_view language);
static std::optional DownloadFile(QWidget* parent, const QString& title, std::string url, std::vector* data);
static void InitializeEarlyConsole();
static void HookSignals();
@@ -121,6 +134,7 @@ static bool ParseCommandLineParametersAndInitializeConfig(QApplication& app,
static INISettingsInterface s_base_settings_interface;
static std::unique_ptr s_settings_save_timer;
+static std::vector s_translators;
static bool s_batch_mode = false;
static bool s_nogui_mode = false;
static bool s_start_fullscreen_ui = false;
@@ -2219,6 +2233,196 @@ std::string Host::FormatNumber(NumberFormatType type, double value)
return ret;
}
+void QtHost::UpdateApplicationLanguage(QWidget* dialog_parent)
+{
+ for (QTranslator* translator : s_translators)
+ {
+ qApp->removeTranslator(translator);
+ translator->deleteLater();
+ }
+ s_translators.clear();
+
+ // Fix old language names.
+ const std::string language = Host::GetBaseStringSettingValue("Main", "Language", GetDefaultLanguage());
+ const QString qlanguage = QString::fromStdString(language);
+
+ // install the base qt translation first
+#ifndef __APPLE__
+ const QString base_dir = QStringLiteral("%1/translations").arg(qApp->applicationDirPath());
+#else
+ const QString base_dir = QStringLiteral("%1/../Resources/translations").arg(qApp->applicationDirPath());
+#endif
+
+ // Qt base uses underscores instead of hyphens.
+ const QString qtbase_language = QString(qlanguage).replace(QChar('-'), QChar('_'));
+ QString base_path(QStringLiteral("%1/qt_%2.qm").arg(base_dir).arg(qtbase_language));
+ bool has_base_ts = QFile::exists(base_path);
+ if (!has_base_ts)
+ {
+ // Try without the country suffix.
+ const int index = qlanguage.lastIndexOf('-');
+ if (index > 0)
+ {
+ base_path = QStringLiteral("%1/qt_%2.qm").arg(base_dir).arg(qlanguage.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(
+ dialog_parent, QStringLiteral("Translation Error"),
+ QStringLiteral("Failed to find load base translation file for '%1':\n%2").arg(qlanguage).arg(base_path));
+ delete base_translator;
+ }
+ else
+ {
+ s_translators.push_back(base_translator);
+ qApp->installTranslator(base_translator);
+ }
+ }
+
+ const QString path = QStringLiteral("%1/duckstation-qt_%3.qm").arg(base_dir).arg(qlanguage);
+ if (!QFile::exists(path))
+ {
+ QMessageBox::warning(
+ dialog_parent, QStringLiteral("Translation Error"),
+ QStringLiteral("Failed to find translation file for language '%1':\n%2").arg(qlanguage).arg(path));
+ return;
+ }
+
+ QTranslator* translator = new QTranslator(qApp);
+ if (!translator->load(path))
+ {
+ QMessageBox::warning(
+ dialog_parent, QStringLiteral("Translation Error"),
+ QStringLiteral("Failed to load translation file for language '%1':\n%2").arg(qlanguage).arg(path));
+ delete translator;
+ return;
+ }
+
+ INFO_LOG("Loaded translation file for language {}", qlanguage.toUtf8().constData());
+ qApp->installTranslator(translator);
+ s_translators.push_back(translator);
+
+ // We end up here both on language change, and on startup.
+ UpdateFontOrder(language);
+}
+
+s32 Host::Internal::GetTranslatedStringImpl(std::string_view context, std::string_view msg,
+ std::string_view disambiguation, 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 std::string temp_disambiguation(disambiguation);
+ const QString translated_msg = qApp->translate(temp_context.c_str(), temp_msg.c_str(),
+ disambiguation.empty() ? nullptr : temp_disambiguation.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);
+}
+
+std::string Host::TranslatePluralToString(const char* context, const char* msg, const char* disambiguation, int count)
+{
+ return qApp->translate(context, msg, disambiguation, count).toStdString();
+}
+
+SmallString Host::TranslatePluralToSmallString(const char* context, const char* msg, const char* disambiguation,
+ int count)
+{
+ const QString qstr = qApp->translate(context, msg, disambiguation, count);
+ SmallString ret;
+
+#ifdef _WIN32
+ // Cheeky way to avoid heap allocations.
+ static_assert(sizeof(*qstr.utf16()) == sizeof(wchar_t));
+ ret.assign(std::wstring_view(reinterpret_cast(qstr.utf16()), qstr.length()));
+#else
+ const QByteArray utf8 = qstr.toUtf8();
+ ret.assign(utf8.constData(), utf8.length());
+#endif
+
+ return ret;
+}
+
+std::span> Host::GetAvailableLanguageList()
+{
+ static constexpr const std::pair languages[] = {{"English", "en"},
+ {"Español de Latinoamérica", "es"},
+ {"Español de España", "es-ES"},
+ {"Français", "fr"},
+ {"Bahasa Indonesia", "id"},
+ {"日本語", "ja"},
+ {"한국어", "ko"},
+ {"Italiano", "it"},
+ {"Polski", "pl"},
+ {"Português (Pt)", "pt-PT"},
+ {"Português (Br)", "pt-BR"},
+ {"Русский", "ru"},
+ {"Svenska", "sv"},
+ {"Türkçe", "tr"},
+ {"简体中文", "zh-CN"}};
+
+ return languages;
+}
+
+bool Host::ChangeLanguage(const char* new_language)
+{
+ Host::RunOnUIThread([new_language = std::string(new_language)]() {
+ Host::SetBaseStringSettingValue("Main", "Language", new_language.c_str());
+ Host::CommitBaseSettingChanges();
+ QtHost::UpdateApplicationLanguage(g_main_window);
+ g_main_window->recreate();
+ });
+ return true;
+}
+
+const char* QtHost::GetDefaultLanguage()
+{
+ // TODO: Default system language instead.
+ return "en";
+}
+
+void QtHost::UpdateFontOrder(std::string_view language)
+{
+ // Why is this a thing? Because we want all glyphs to be available, but don't want to conflict
+ // between codepoints shared between Chinese and Japanese. Therefore we prioritize the language
+ // that the user has selected.
+ ImGuiManager::TextFontOrder font_order;
+#define TF(name) ImGuiManager::TextFont::name
+ if (language == "ja")
+ font_order = {TF(Default), TF(Japanese), TF(Chinese), TF(Korean)};
+ else if (language == "ko")
+ font_order = {TF(Default), TF(Korean), TF(Japanese), TF(Chinese)};
+ else if (language == "zh-CN")
+ font_order = {TF(Default), TF(Chinese), TF(Japanese), TF(Korean)};
+ else
+ font_order = ImGuiManager::GetDefaultTextFontOrder();
+#undef TF
+
+ if (g_emu_thread)
+ {
+ Host::RunOnCPUThread([font_order]() mutable {
+ GPUThread::RunOnThread([font_order]() mutable { ImGuiManager::SetTextFontOrder(font_order); });
+ Host::ClearTranslationCache();
+ });
+ }
+ else
+ {
+ // Startup, safe to set directly.
+ ImGuiManager::SetTextFontOrder(font_order);
+ Host::ClearTranslationCache();
+ }
+}
+
void Host::ReportDebuggerMessage(std::string_view message)
{
INFO_LOG("Debugger message: {}", message);
diff --git a/src/duckstation-qt/qttranslations.cpp b/src/duckstation-qt/qttranslations.cpp
deleted file mode 100644
index 10feea13e..000000000
--- a/src/duckstation-qt/qttranslations.cpp
+++ /dev/null
@@ -1,276 +0,0 @@
-// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin and contributors.
-// SPDX-License-Identifier: CC-BY-NC-ND-4.0
-
-#include "mainwindow.h"
-#include "qthost.h"
-
-#include "core/gpu_thread.h"
-#include "core/host.h"
-
-#include "util/imgui_manager.h"
-
-#include "common/assert.h"
-#include "common/file_system.h"
-#include "common/log.h"
-#include "common/path.h"
-#include "common/small_string.h"
-#include "common/string_util.h"
-
-#include "fmt/format.h"
-#include "imgui.h"
-
-#include
-#include
-#include
-#include
-
-#include
-#include
-
-#ifdef _WIN32
-#include "common/windows_headers.h"
-#include
-#include
-#endif
-
-LOG_CHANNEL(Host);
-
-#if 0
-// Qt internal strings we'd like to have translated
-QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Services")
-QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Hide %1")
-QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Hide Others")
-QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Show All")
-QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Preferences...")
-QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Quit %1")
-QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "About %1")
-#endif
-
-namespace QtHost {
-struct FontOrderInfo
-{
- const char* language;
- ImGuiManager::TextFontOrder font_order;
-};
-
-static QString FixLanguageName(const QString& language);
-static void UpdateFontOrder(std::string_view language);
-static const FontOrderInfo* GetFontOrderInfo(std::string_view language);
-
-static std::vector s_translators;
-} // namespace QtHost
-
-void QtHost::UpdateApplicationLanguage(QWidget* dialog_parent)
-{
- for (QTranslator* translator : s_translators)
- {
- qApp->removeTranslator(translator);
- translator->deleteLater();
- }
- s_translators.clear();
-
- // Fix old language names.
- const std::string config_language = Host::GetBaseStringSettingValue("Main", "Language", GetDefaultLanguage());
- const QString language = FixLanguageName(QString::fromStdString(config_language));
-
- // install the base qt translation first
-#ifndef __APPLE__
- const QString base_dir = QStringLiteral("%1/translations").arg(qApp->applicationDirPath());
-#else
- const QString base_dir = QStringLiteral("%1/../Resources/translations").arg(qApp->applicationDirPath());
-#endif
-
- // Qt base uses underscores instead of hyphens.
- const QString qt_language = QString(language).replace(QChar('-'), QChar('_'));
- QString base_path(QStringLiteral("%1/qt_%2.qm").arg(base_dir).arg(qt_language));
- bool has_base_ts = QFile::exists(base_path);
- if (!has_base_ts)
- {
- // Try without the country suffix.
- const int index = language.lastIndexOf('-');
- 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(
- dialog_parent, 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/duckstation-qt_%3.qm").arg(base_dir).arg(language);
- if (!QFile::exists(path))
- {
- QMessageBox::warning(
- dialog_parent, QStringLiteral("Translation Error"),
- QStringLiteral("Failed to find translation file for language '%1':\n%2").arg(language).arg(path));
- return;
- }
-
- QTranslator* translator = new QTranslator(qApp);
- if (!translator->load(path))
- {
- QMessageBox::warning(
- dialog_parent, QStringLiteral("Translation Error"),
- QStringLiteral("Failed to load translation file for language '%1':\n%2").arg(language).arg(path));
- delete translator;
- return;
- }
-
- INFO_LOG("Loaded translation file for language {}", language.toUtf8().constData());
- qApp->installTranslator(translator);
- s_translators.push_back(translator);
-
- // We end up here both on language change, and on startup.
- UpdateFontOrder(config_language);
-}
-
-QString QtHost::FixLanguageName(const QString& language)
-{
- if (language == QStringLiteral("pt-br"))
- return QStringLiteral("pt-BR");
- else if (language == QStringLiteral("pt-pt"))
- return QStringLiteral("pt-PT");
- else if (language == QStringLiteral("zh-cn"))
- return QStringLiteral("zh-CN");
- else
- return language;
-}
-
-s32 Host::Internal::GetTranslatedStringImpl(std::string_view context, std::string_view msg,
- std::string_view disambiguation, 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 std::string temp_disambiguation(disambiguation);
- const QString translated_msg = qApp->translate(temp_context.c_str(), temp_msg.c_str(),
- disambiguation.empty() ? nullptr : temp_disambiguation.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);
-}
-
-std::string Host::TranslatePluralToString(const char* context, const char* msg, const char* disambiguation, int count)
-{
- return qApp->translate(context, msg, disambiguation, count).toStdString();
-}
-
-SmallString Host::TranslatePluralToSmallString(const char* context, const char* msg, const char* disambiguation,
- int count)
-{
- const QString qstr = qApp->translate(context, msg, disambiguation, count);
- SmallString ret;
-
-#ifdef _WIN32
- // Cheeky way to avoid heap allocations.
- static_assert(sizeof(*qstr.utf16()) == sizeof(wchar_t));
- ret.assign(std::wstring_view(reinterpret_cast(qstr.utf16()), qstr.length()));
-#else
- const QByteArray utf8 = qstr.toUtf8();
- ret.assign(utf8.constData(), utf8.length());
-#endif
-
- return ret;
-}
-
-std::span> Host::GetAvailableLanguageList()
-{
- static constexpr const std::pair languages[] = {{"English", "en"},
- {"Español de Latinoamérica", "es"},
- {"Español de España", "es-ES"},
- {"Français", "fr"},
- {"Bahasa Indonesia", "id"},
- {"日本語", "ja"},
- {"한국어", "ko"},
- {"Italiano", "it"},
- {"Polski", "pl"},
- {"Português (Pt)", "pt-PT"},
- {"Português (Br)", "pt-BR"},
- {"Русский", "ru"},
- {"Svenska", "sv"},
- {"Türkçe", "tr"},
- {"简体中文", "zh-CN"}};
-
- return languages;
-}
-
-bool Host::ChangeLanguage(const char* new_language)
-{
- Host::RunOnUIThread([new_language = std::string(new_language)]() {
- Host::SetBaseStringSettingValue("Main", "Language", new_language.c_str());
- Host::CommitBaseSettingChanges();
- QtHost::UpdateApplicationLanguage(g_main_window);
- g_main_window->recreate();
- });
- return true;
-}
-
-const char* QtHost::GetDefaultLanguage()
-{
- // TODO: Default system language instead.
- return "en";
-}
-
-void QtHost::UpdateFontOrder(std::string_view language)
-{
- ImGuiManager::TextFontOrder font_order = ImGuiManager::GetDefaultTextFontOrder();
- if (const FontOrderInfo* fo = GetFontOrderInfo(language))
- font_order = fo->font_order;
-
- if (g_emu_thread)
- {
- Host::RunOnCPUThread([font_order]() mutable {
- GPUThread::RunOnThread([font_order]() mutable { ImGuiManager::SetTextFontOrder(font_order); });
- Host::ClearTranslationCache();
- });
- }
- else
- {
- // Startup, safe to set directly.
- ImGuiManager::SetTextFontOrder(font_order);
- Host::ClearTranslationCache();
- }
-}
-
-#define TF(name) ImGuiManager::TextFont::name
-
-// Why is this a thing? Because we want all glyphs to be available, but don't want to conflict
-// between codepoints shared between Chinese and Japanese. Therefore we prioritize the language
-// that the user has selected.
-static constexpr const QtHost::FontOrderInfo s_font_order[] = {
- {"ja", {TF(Default), TF(Japanese), TF(Chinese), TF(Korean)}},
- {"ko", {TF(Default), TF(Korean), TF(Japanese), TF(Chinese)}},
- {"zh-CN", {TF(Default), TF(Chinese), TF(Japanese), TF(Korean)}},
-};
-
-#undef TF
-
-const QtHost::FontOrderInfo* QtHost::GetFontOrderInfo(std::string_view language)
-{
- for (const FontOrderInfo& it : s_font_order)
- {
- if (language == it.language)
- return ⁢
- }
-
- return nullptr;
-}