Qt: Implement 'System Language' language option

pull/3493/head
Stenzek 3 months ago
parent 7f5f90338f
commit c82351a14a
No known key found for this signature in database

@ -4097,18 +4097,13 @@ void FullscreenUI::DrawInterfaceSettingsPage()
// Have to do this the annoying way, because it's host-derived. // Have to do this the annoying way, because it's host-derived.
const auto language_list = Host::GetAvailableLanguageList(); const auto language_list = Host::GetAvailableLanguageList();
TinyString current_language = bsi->GetTinyStringValue("Main", "Language", ""); TinyString current_language = bsi->GetTinyStringValue("Main", "Language", "");
const char* current_language_name = "Unknown";
for (const auto& [language, code] : language_list)
{
if (current_language == code)
current_language_name = language;
}
if (MenuButtonWithValue(FSUI_ICONVSTR(ICON_FA_LANGUAGE, "Language"), if (MenuButtonWithValue(FSUI_ICONVSTR(ICON_FA_LANGUAGE, "Language"),
FSUI_VSTR("Chooses the language used for UI elements."), current_language_name)) FSUI_VSTR("Chooses the language used for UI elements."),
Host::GetLanguageName(current_language)))
{ {
ImGuiFullscreen::ChoiceDialogOptions options; ImGuiFullscreen::ChoiceDialogOptions options;
for (const auto& [language, code] : language_list) for (const auto& [language, code] : language_list)
options.emplace_back(fmt::format("{} [{}]", language, code), (current_language == code)); options.emplace_back(Host::GetLanguageName(code), (current_language == code));
OpenChoiceDialog(FSUI_ICONVSTR(ICON_FA_LANGUAGE, "UI Language"), false, std::move(options), OpenChoiceDialog(FSUI_ICONVSTR(ICON_FA_LANGUAGE, "UI Language"), false, std::move(options),
[language_list](s32 index, const std::string& title, bool checked) { [language_list](s32 index, const std::string& title, bool checked) {
if (static_cast<u32>(index) >= language_list.size()) if (static_cast<u32>(index) >= language_list.size())

@ -72,6 +72,9 @@ void ReportDebuggerMessage(std::string_view message);
/// Returns a list of supported languages and codes (suffixes for translation files). /// Returns a list of supported languages and codes (suffixes for translation files).
std::span<const std::pair<const char*, const char*>> GetAvailableLanguageList(); std::span<const std::pair<const char*, const char*>> GetAvailableLanguageList();
/// Returns the localized language name for the specified language code.
const char* GetLanguageName(std::string_view language_code);
/// Refreshes the UI when the language is changed. /// Refreshes the UI when the language is changed.
bool ChangeLanguage(const char* new_language); bool ChangeLanguage(const char* new_language);

@ -337,6 +337,11 @@ std::span<const std::pair<const char*, const char*>> Host::GetAvailableLanguageL
return {}; return {};
} }
const char* Host::GetLanguageName(std::string_view language_code)
{
return "";
}
bool Host::ChangeLanguage(const char* new_language) bool Host::ChangeLanguage(const char* new_language)
{ {
return false; return false;

@ -133,6 +133,7 @@ set(SRCS
qtprogresscallback.cpp qtprogresscallback.cpp
qtprogresscallback.h qtprogresscallback.h
qtthemes.cpp qtthemes.cpp
qttranslations.inl
qtutils.cpp qtutils.cpp
qtutils.h qtutils.h
resource.h resource.h

@ -49,8 +49,6 @@ const char* InterfaceSettingsWidget::THEME_VALUES[] = {
nullptr, nullptr,
}; };
const char* InterfaceSettingsWidget::DEFAULT_THEME_NAME = "darkfusion";
InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* dialog, QWidget* parent) InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* dialog, QWidget* parent)
: QWidget(parent), m_dialog(dialog) : QWidget(parent), m_dialog(dialog)
{ {
@ -86,8 +84,7 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* dialog, QWidget
connect(m_ui.theme, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]() { emit themeChanged(); }); connect(m_ui.theme, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]() { emit themeChanged(); });
populateLanguageDropdown(m_ui.language); populateLanguageDropdown(m_ui.language);
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.language, "Main", "Language", SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.language, "Main", "Language", {});
QtHost::GetDefaultLanguage());
connect(m_ui.language, QOverload<int>::of(&QComboBox::currentIndexChanged), this, connect(m_ui.language, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&InterfaceSettingsWidget::onLanguageChanged); &InterfaceSettingsWidget::onLanguageChanged);
@ -180,16 +177,8 @@ void InterfaceSettingsWidget::populateLanguageDropdown(QComboBox* cb)
{ {
for (const auto& [language, code] : Host::GetAvailableLanguageList()) for (const auto& [language, code] : Host::GetAvailableLanguageList())
{ {
QString icon_filename(QStringLiteral(":/icons/flags/%1.png").arg(QLatin1StringView(code))); cb->addItem(QtUtils::GetIconForTranslationLanguage(code), QString::fromUtf8(Host::GetLanguageName(code)),
if (!QFile::exists(icon_filename)) QString::fromLatin1(code));
{
// try without the suffix (e.g. es-es -> es)
const char* pos = std::strrchr(code, '-');
if (pos)
icon_filename = QStringLiteral(":/icons/flags/%1.png").arg(QLatin1StringView(pos));
}
cb->addItem(QIcon(icon_filename), QString::fromUtf8(language), QString::fromLatin1(code));
} }
} }

@ -27,6 +27,8 @@ private Q_SLOTS:
void onLanguageChanged(); void onLanguageChanged();
private: private:
void setupAdditionalUi();
Ui::InterfaceSettingsWidget m_ui; Ui::InterfaceSettingsWidget m_ui;
SettingsWindow* m_dialog; SettingsWindow* m_dialog;
@ -34,5 +36,4 @@ private:
public: public:
static const char* THEME_NAMES[]; static const char* THEME_NAMES[];
static const char* THEME_VALUES[]; static const char* THEME_VALUES[];
static const char* DEFAULT_THEME_NAME;
}; };

@ -124,6 +124,7 @@ static void SaveSettings();
static bool RunSetupWizard(); static bool RunSetupWizard();
static void UpdateFontOrder(std::string_view language); static void UpdateFontOrder(std::string_view language);
static void UpdateApplicationLocale(std::string_view language); static void UpdateApplicationLocale(std::string_view language);
static std::string_view GetSystemLanguage();
static std::optional<bool> DownloadFile(QWidget* parent, const QString& title, std::string url, std::vector<u8>* data); static std::optional<bool> DownloadFile(QWidget* parent, const QString& title, std::string url, std::vector<u8>* data);
static void InitializeEarlyConsole(); static void InitializeEarlyConsole();
static void HookSignals(); static void HookSignals();
@ -2263,9 +2264,11 @@ void QtHost::UpdateApplicationLanguage(QWidget* dialog_parent)
} }
s_translators.clear(); s_translators.clear();
// Fix old language names. // Fixup automatic language.
const std::string language = Host::GetBaseStringSettingValue("Main", "Language", GetDefaultLanguage()); std::string language = Host::GetBaseStringSettingValue("Main", "Language", "");
const QString qlanguage = QString::fromStdString(language); if (language.empty())
language = GetSystemLanguage();
QString qlanguage = QString::fromStdString(language);
// install the base qt translation first // install the base qt translation first
#ifndef __APPLE__ #ifndef __APPLE__
@ -2377,25 +2380,64 @@ SmallString Host::TranslatePluralToSmallString(const char* context, const char*
std::span<const std::pair<const char*, const char*>> Host::GetAvailableLanguageList() std::span<const std::pair<const char*, const char*>> Host::GetAvailableLanguageList()
{ {
static constexpr const std::pair<const char*, const char*> languages[] = {{"English", "en"}, static constexpr const std::pair<const char*, const char*> languages[] = {
{"Español de Latinoamérica", "es"}, {QT_TRANSLATE_NOOP("QtHost", "System Language"), ""},
{"Español de España", "es-ES"},
{"Français", "fr"}, #define TRANSLATION_LIST_ENTRY(name, our_translation_code, locale_code) \
{"Bahasa Indonesia", "id"}, {name " [" our_translation_code "]", our_translation_code},
{"日本語", "ja"}, #include "qttranslations.inl"
{"한국어", "ko"}, #undef TRANSLATION_LIST_ENTRY
{"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; return languages;
} }
const char* Host::GetLanguageName(std::string_view language_code)
{
for (const auto& [name, code] : GetAvailableLanguageList())
{
if (language_code == code)
return Host::TranslateToCString("QtHost", name);
}
return TRANSLATE("QtHost", "Unknown");
}
std::string_view QtHost::GetSystemLanguage()
{
std::string locname = QLocale::system().name().toStdString();
// Does this match any of our translations?
for (const auto& [lname, lcode] : Host::GetAvailableLanguageList())
{
if (locname == lcode)
return lcode;
}
// Check for a partial match, e.g. "zh" for "zh-CN".
if (const std::string::size_type pos = locname.find('-'); pos != std::string::npos)
{
const std::string_view plocname = std::string_view(locname).substr(0, pos);
for (const auto& [lname, lcode] : Host::GetAvailableLanguageList())
{
// Only some languages have a country code, so we need to check both.
const std::string_view lcodev(lcode);
if (lcodev == plocname)
{
return lcode;
}
else if (const std::string_view::size_type lpos = lcodev.find('-'); lpos != std::string::npos)
{
if (lcodev.substr(0, lpos) == plocname)
return lcode;
}
}
}
// Fallback to English.
return "en";
}
bool Host::ChangeLanguage(const char* new_language) bool Host::ChangeLanguage(const char* new_language)
{ {
Host::RunOnUIThread([new_language = std::string(new_language)]() { Host::RunOnUIThread([new_language = std::string(new_language)]() {
@ -2407,12 +2449,6 @@ bool Host::ChangeLanguage(const char* new_language)
return true; return true;
} }
const char* QtHost::GetDefaultLanguage()
{
// TODO: Default system language instead.
return "en";
}
void QtHost::UpdateFontOrder(std::string_view language) 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 // Why is this a thing? Because we want all glyphs to be available, but don't want to conflict

@ -339,9 +339,6 @@ const QLocale& GetApplicationLocale();
/// Default theme name for the platform. /// Default theme name for the platform.
const char* GetDefaultThemeName(); const char* GetDefaultThemeName();
/// Default language for the platform.
const char* GetDefaultLanguage();
/// Sets application theme according to settings. /// Sets application theme according to settings.
void UpdateApplicationTheme(); void UpdateApplicationTheme();
@ -366,9 +363,6 @@ bool CanRenderToMainWindow();
/// Returns true if the separate-window display widget should use the main window coordinates. /// Returns true if the separate-window display widget should use the main window coordinates.
bool UseMainWindowGeometryForDisplayWindow(); bool UseMainWindowGeometryForDisplayWindow();
/// Default language for the platform.
const char* GetDefaultLanguage();
/// Call when the language changes. /// Call when the language changes.
void UpdateApplicationLanguage(QWidget* dialog_parent); void UpdateApplicationLanguage(QWidget* dialog_parent);

@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com> and contributors. // SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: CC-BY-NC-ND-4.0 // SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "interfacesettingswidget.h"
#include "qthost.h" #include "qthost.h"
#include "util/imgui_fullscreen.h" #include "util/imgui_fullscreen.h"
@ -42,8 +41,7 @@ void QtHost::UpdateApplicationTheme()
void QtHost::SetStyleFromSettings() void QtHost::SetStyleFromSettings()
{ {
const TinyString theme = const TinyString theme = Host::GetBaseTinyStringSettingValue("UI", "Theme", QtHost::GetDefaultThemeName());
Host::GetBaseTinyStringSettingValue("UI", "Theme", InterfaceSettingsWidget::DEFAULT_THEME_NAME);
if (theme == "qdarkstyle") if (theme == "qdarkstyle")
{ {
@ -398,8 +396,7 @@ void QtHost::SetIconThemeFromStyle()
const char* Host::GetDefaultFullscreenUITheme() const char* Host::GetDefaultFullscreenUITheme()
{ {
const TinyString theme = const TinyString theme = Host::GetBaseTinyStringSettingValue("UI", "Theme", QtHost::GetDefaultThemeName());
Host::GetBaseTinyStringSettingValue("UI", "Theme", InterfaceSettingsWidget::DEFAULT_THEME_NAME);
if (theme == "cobaltsky") if (theme == "cobaltsky")
return "CobaltSky"; return "CobaltSky";

@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
// TRANSLATION_LIST_ENTRY(name, our_translation_code, locale_code)
TRANSLATION_LIST_ENTRY("English", "en", "en-US")
TRANSLATION_LIST_ENTRY("Español de Latinoamérica", "es", "es-ES")
TRANSLATION_LIST_ENTRY("Español de España", "es-ES", "es-ES")
TRANSLATION_LIST_ENTRY("Français", "fr", "fr-FR")
TRANSLATION_LIST_ENTRY("Bahasa Indonesia", "id", "id-ID")
TRANSLATION_LIST_ENTRY("日本語", "ja", "ja-JA")
TRANSLATION_LIST_ENTRY("한국어", "ko", "ko-KO")
TRANSLATION_LIST_ENTRY("Italiano", "it", "it-IT")
TRANSLATION_LIST_ENTRY("Polski", "pl", "pl-PL")
TRANSLATION_LIST_ENTRY("Português (Pt)", "pt-PT", "pt-PT")
TRANSLATION_LIST_ENTRY("Português (Br)", "pt-BR", "pt-BR")
TRANSLATION_LIST_ENTRY("Русский", "ru", "ru-RU")
TRANSLATION_LIST_ENTRY("Svenska", "sv", "sv-SV")
TRANSLATION_LIST_ENTRY("Türkçe", "tr", "tr-TR")
TRANSLATION_LIST_ENTRY("简体中文", "zh-CN", "zh-CN")

@ -257,6 +257,31 @@ void QtUtils::ResizePotentiallyFixedSizeWindow(QWidget* widget, int width, int h
widget->resize(width, height); widget->resize(width, height);
} }
QIcon QtUtils::GetIconForTranslationLanguage(std::string_view language_name)
{
QString icon_path;
if (!language_name.empty())
{
const QLatin1StringView qlanguage_name(language_name.data(), language_name.length());
icon_path = QStringLiteral(":/icons/flags/%1.png").arg(qlanguage_name);
if (!QFile::exists(icon_path))
{
// try without the suffix (e.g. es-es -> es)
const int index = qlanguage_name.indexOf('-');
if (index >= 0)
icon_path = QStringLiteral(":/icons/flags/%1.png").arg(qlanguage_name.left(index));
}
}
else
{
// no language specified, use the default icon
icon_path = QStringLiteral(":/icons/applications-system.png");
}
return QIcon(icon_path);
}
QIcon QtUtils::GetIconForRegion(ConsoleRegion region) QIcon QtUtils::GetIconForRegion(ConsoleRegion region)
{ {
switch (region) switch (region)

@ -103,6 +103,9 @@ void SetWindowResizeable(QWidget* widget, bool resizeable);
/// Adjusts the fixed size for a window if it's not resizeable. /// Adjusts the fixed size for a window if it's not resizeable.
void ResizePotentiallyFixedSizeWindow(QWidget* widget, int width, int height); void ResizePotentiallyFixedSizeWindow(QWidget* widget, int width, int height);
/// Returns icon for language.
QIcon GetIconForTranslationLanguage(std::string_view language_name);
/// Returns icon for region. /// Returns icon for region.
QIcon GetIconForRegion(ConsoleRegion region); QIcon GetIconForRegion(ConsoleRegion region);
QIcon GetIconForRegion(DiscRegion region); QIcon GetIconForRegion(DiscRegion region);

@ -183,7 +183,7 @@ void SetupWizardDialog::setupUi()
connect(m_ui.next, &QPushButton::clicked, this, &SetupWizardDialog::nextPage); connect(m_ui.next, &QPushButton::clicked, this, &SetupWizardDialog::nextPage);
connect(m_ui.cancel, &QPushButton::clicked, this, &SetupWizardDialog::confirmCancel); connect(m_ui.cancel, &QPushButton::clicked, this, &SetupWizardDialog::confirmCancel);
setupLanguagePage(); setupLanguagePage(true);
setupBIOSPage(); setupBIOSPage();
setupGameListPage(); setupGameListPage();
setupControllerPage(true); setupControllerPage(true);
@ -191,20 +191,27 @@ void SetupWizardDialog::setupUi()
setupAchievementsPage(true); setupAchievementsPage(true);
} }
void SetupWizardDialog::setupLanguagePage() void SetupWizardDialog::setupLanguagePage(bool initial)
{ {
SettingWidgetBinder::DisconnectWidget(m_ui.theme);
m_ui.theme->clear();
SettingWidgetBinder::BindWidgetToEnumSetting(nullptr, m_ui.theme, "UI", "Theme", InterfaceSettingsWidget::THEME_NAMES, SettingWidgetBinder::BindWidgetToEnumSetting(nullptr, m_ui.theme, "UI", "Theme", InterfaceSettingsWidget::THEME_NAMES,
InterfaceSettingsWidget::THEME_VALUES, InterfaceSettingsWidget::THEME_VALUES, QtHost::GetDefaultThemeName(),
InterfaceSettingsWidget::DEFAULT_THEME_NAME, "InterfaceSettingsWidget"); "MainWindow");
connect(m_ui.theme, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &SetupWizardDialog::themeChanged); connect(m_ui.theme, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &SetupWizardDialog::themeChanged);
SettingWidgetBinder::DisconnectWidget(m_ui.language);
m_ui.language->clear();
InterfaceSettingsWidget::populateLanguageDropdown(m_ui.language); InterfaceSettingsWidget::populateLanguageDropdown(m_ui.language);
SettingWidgetBinder::BindWidgetToStringSetting(nullptr, m_ui.language, "Main", "Language", SettingWidgetBinder::BindWidgetToStringSetting(nullptr, m_ui.language, "Main", "Language", {});
QtHost::GetDefaultLanguage());
connect(m_ui.language, QOverload<int>::of(&QComboBox::currentIndexChanged), this, connect(m_ui.language, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&SetupWizardDialog::languageChanged); &SetupWizardDialog::languageChanged);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.autoUpdateEnabled, "AutoUpdater", "CheckAtStartup", true); if (initial)
{
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.autoUpdateEnabled, "AutoUpdater", "CheckAtStartup",
true);
}
} }
void SetupWizardDialog::themeChanged() void SetupWizardDialog::themeChanged()
@ -218,6 +225,7 @@ void SetupWizardDialog::languageChanged()
// Skip the recreation, since we don't have many dynamic UI elements. // Skip the recreation, since we don't have many dynamic UI elements.
QtHost::UpdateApplicationLanguage(this); QtHost::UpdateApplicationLanguage(this);
m_ui.retranslateUi(this); m_ui.retranslateUi(this);
setupLanguagePage(false);
setupControllerPage(false); setupControllerPage(false);
setupGraphicsPage(false); setupGraphicsPage(false);
setupAchievementsPage(false); setupAchievementsPage(false);
@ -631,7 +639,8 @@ void SetupWizardDialog::setupAchievementsPage(bool initial)
{ {
if (initial) if (initial)
{ {
m_ui.achievementsIconLabel->setPixmap(QPixmap(QString::fromStdString(QtHost::GetResourcePath("images/ra-icon.webp", true)))); m_ui.achievementsIconLabel->setPixmap(
QPixmap(QString::fromStdString(QtHost::GetResourcePath("images/ra-icon.webp", true))));
QFont title_font(m_ui.achievementsTitleLabel->font()); QFont title_font(m_ui.achievementsTitleLabel->font());
title_font.setBold(true); title_font.setBold(true);
title_font.setPixelSize(20); title_font.setPixelSize(20);

@ -64,7 +64,7 @@ private:
}; };
void setupUi(); void setupUi();
void setupLanguagePage(); void setupLanguagePage(bool initial);
void setupBIOSPage(); void setupBIOSPage();
void setupGameListPage(); void setupGameListPage();
void setupControllerPage(bool initial); void setupControllerPage(bool initial);

@ -191,6 +191,11 @@ std::span<const std::pair<const char*, const char*>> Host::GetAvailableLanguageL
return {}; return {};
} }
const char* Host::GetLanguageName(std::string_view language_code)
{
return "";
}
bool Host::ChangeLanguage(const char* new_language) bool Host::ChangeLanguage(const char* new_language)
{ {
return false; return false;

Loading…
Cancel
Save