Merge pull request #3349 from FearlessTobi/translationnns
[WIP] yuzu: Port translation support from Citra v2
This commit is contained in:
commit
9943a478fe
|
@ -5,7 +5,7 @@ cd /yuzu
|
|||
ccache -s
|
||||
|
||||
mkdir build || true && cd build
|
||||
cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON
|
||||
cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON
|
||||
|
||||
ninja
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ cd /yuzu
|
|||
ccache -s
|
||||
|
||||
mkdir build || true && cd build
|
||||
cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release
|
||||
cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON
|
||||
ninja
|
||||
|
||||
ccache -s
|
||||
|
|
|
@ -13,6 +13,7 @@ project(yuzu)
|
|||
option(ENABLE_SDL2 "Enable the SDL2 frontend" ON)
|
||||
|
||||
option(ENABLE_QT "Enable the Qt frontend" ON)
|
||||
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" ON "ENABLE_QT;MSVC" OFF)
|
||||
|
||||
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||
|
@ -224,6 +225,10 @@ if(ENABLE_QT)
|
|||
if (YUZU_USE_QT_WEB_ENGINE)
|
||||
find_package(Qt5 COMPONENTS WebEngineCore WebEngineWidgets)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT_TRANSLATION)
|
||||
find_package(Qt5 REQUIRED COMPONENTS LinguistTools ${QT_PREFIX_HINT})
|
||||
endif()
|
||||
if (NOT Qt5_FOUND)
|
||||
list(APPEND CONAN_REQUIRED_LIBS "qt/5.14.1@bincrafters/stable")
|
||||
endif()
|
||||
|
|
|
@ -24,6 +24,8 @@ Most of the development happens on GitHub. It's also where [our central reposito
|
|||
|
||||
If you want to contribute please take a look at the [Contributor's Guide](https://github.com/yuzu-emu/yuzu/wiki/Contributing) and [Developer Information](https://github.com/yuzu-emu/yuzu/wiki/Developer-Information). You should also contact any of the developers on Discord in order to know about the current state of the emulator.
|
||||
|
||||
If you want to contribute to the user interface translation, please check out the [yuzu project on transifex](https://www.transifex.com/yuzu-emulator/yuzu). We centralize translation work there, and periodically upstream translations.
|
||||
|
||||
### Building
|
||||
|
||||
* __Windows__: [Windows Build](https://github.com/yuzu-emu/yuzu/wiki/Building-For-Windows)
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
# Ignore the source language file
|
||||
en.ts
|
|
@ -0,0 +1,8 @@
|
|||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[yuzu.emulator]
|
||||
file_filter = <lang>.ts
|
||||
source_file = en.ts
|
||||
source_lang = en
|
||||
type = QT
|
|
@ -0,0 +1 @@
|
|||
This directory stores translation patches (TS files) for yuzu Qt frontend. This directory is linked with [yuzu project on transifex](https://www.transifex.com/yuzu-emulator/yuzu), so you can update the translation by executing `tx pull -a`. If you want to contribute to the translation, please go the transifex link and submit your translation there. This directory on the main repo will be synchronized with transifex periodically. Do not directly open PRs on github to modify the translation.
|
|
@ -133,11 +133,44 @@ file(GLOB COMPAT_LIST
|
|||
file(GLOB_RECURSE ICONS ${PROJECT_SOURCE_DIR}/dist/icons/*)
|
||||
file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*)
|
||||
|
||||
if (ENABLE_QT_TRANSLATION)
|
||||
set(YUZU_QT_LANGUAGES "${PROJECT_SOURCE_DIR}/dist/languages" CACHE PATH "Path to the translation bundle for the Qt frontend")
|
||||
option(GENERATE_QT_TRANSLATION "Generate en.ts as the translation source file" OFF)
|
||||
|
||||
# Update source TS file if enabled
|
||||
if (GENERATE_QT_TRANSLATION)
|
||||
get_target_property(SRCS yuzu SOURCES)
|
||||
qt5_create_translation(QM_FILES ${SRCS} ${UIS} ${YUZU_QT_LANGUAGES}/en.ts)
|
||||
add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts)
|
||||
endif()
|
||||
|
||||
# Find all TS files except en.ts
|
||||
file(GLOB_RECURSE LANGUAGES_TS ${YUZU_QT_LANGUAGES}/*.ts)
|
||||
list(REMOVE_ITEM LANGUAGES_TS ${YUZU_QT_LANGUAGES}/en.ts)
|
||||
|
||||
# Compile TS files to QM files
|
||||
qt5_add_translation(LANGUAGES_QM ${LANGUAGES_TS})
|
||||
|
||||
# Build a QRC file from the QM file list
|
||||
set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc)
|
||||
file(WRITE ${LANGUAGES_QRC} "<RCC><qresource prefix=\"languages\">\n")
|
||||
foreach (QM ${LANGUAGES_QM})
|
||||
get_filename_component(QM_FILE ${QM} NAME)
|
||||
file(APPEND ${LANGUAGES_QRC} "<file>${QM_FILE}</file>\n")
|
||||
endforeach (QM)
|
||||
file(APPEND ${LANGUAGES_QRC} "</qresource></RCC>")
|
||||
|
||||
# Add the QRC file to package in all QM files
|
||||
qt5_add_resources(LANGUAGES ${LANGUAGES_QRC})
|
||||
else()
|
||||
set(LANGUAGES)
|
||||
endif()
|
||||
|
||||
target_sources(yuzu
|
||||
PRIVATE
|
||||
${COMPAT_LIST}
|
||||
${ICONS}
|
||||
${LANGUAGES}
|
||||
${THEMES}
|
||||
)
|
||||
|
||||
|
|
|
@ -611,6 +611,7 @@ void Config::ReadPathValues() {
|
|||
}
|
||||
}
|
||||
UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList();
|
||||
UISettings::values.language = ReadSetting(QStringLiteral("language"), QString{}).toString();
|
||||
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
@ -1095,6 +1096,7 @@ void Config::SavePathValues() {
|
|||
}
|
||||
qt_config->endArray();
|
||||
WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files);
|
||||
WriteSetting(QStringLiteral("language"), UISettings::values.language, QString{});
|
||||
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
|
|||
SetConfiguration();
|
||||
PopulateSelectionList();
|
||||
|
||||
connect(ui->uiTab, &ConfigureUi::LanguageChanged, this, &ConfigureDialog::OnLanguageChanged);
|
||||
connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,
|
||||
&ConfigureDialog::UpdateVisibleTabs);
|
||||
|
||||
|
@ -98,6 +99,14 @@ void ConfigureDialog::PopulateSelectionList() {
|
|||
}
|
||||
}
|
||||
|
||||
void ConfigureDialog::OnLanguageChanged(const QString& locale) {
|
||||
emit LanguageChanged(locale);
|
||||
// first apply the configuration, and then restore the display
|
||||
ApplyConfiguration();
|
||||
RetranslateUI();
|
||||
SetConfiguration();
|
||||
}
|
||||
|
||||
void ConfigureDialog::UpdateVisibleTabs() {
|
||||
const auto items = ui->selectorList->selectedItems();
|
||||
if (items.isEmpty()) {
|
||||
|
|
|
@ -22,6 +22,12 @@ public:
|
|||
|
||||
void ApplyConfiguration();
|
||||
|
||||
private slots:
|
||||
void OnLanguageChanged(const QString& locale);
|
||||
|
||||
signals:
|
||||
void LanguageChanged(const QString& locale);
|
||||
|
||||
private:
|
||||
void changeEvent(QEvent* event) override;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
#include <QDirIterator>
|
||||
#include "common/common_types.h"
|
||||
#include "core/settings.h"
|
||||
#include "ui_configure_ui.h"
|
||||
|
@ -29,6 +30,8 @@ constexpr std::array row_text_names{
|
|||
ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureUi) {
|
||||
ui->setupUi(this);
|
||||
|
||||
InitializeLanguageComboBox();
|
||||
|
||||
for (const auto& theme : UISettings::themes) {
|
||||
ui->theme_combobox->addItem(QString::fromUtf8(theme.first),
|
||||
QString::fromUtf8(theme.second));
|
||||
|
@ -72,6 +75,8 @@ void ConfigureUi::RequestGameListUpdate() {
|
|||
|
||||
void ConfigureUi::SetConfiguration() {
|
||||
ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
|
||||
ui->language_combobox->setCurrentIndex(
|
||||
ui->language_combobox->findData(UISettings::values.language));
|
||||
ui->show_add_ons->setChecked(UISettings::values.show_add_ons);
|
||||
ui->icon_size_combobox->setCurrentIndex(
|
||||
ui->icon_size_combobox->findData(UISettings::values.icon_size));
|
||||
|
@ -100,6 +105,25 @@ void ConfigureUi::RetranslateUI() {
|
|||
}
|
||||
}
|
||||
|
||||
void ConfigureUi::InitializeLanguageComboBox() {
|
||||
ui->language_combobox->addItem(tr("<System>"), QString{});
|
||||
ui->language_combobox->addItem(tr("English"), QStringLiteral("en"));
|
||||
QDirIterator it(QStringLiteral(":/languages"), QDirIterator::NoIteratorFlags);
|
||||
while (it.hasNext()) {
|
||||
QString locale = it.next();
|
||||
locale.truncate(locale.lastIndexOf(QLatin1Char{'.'}));
|
||||
locale.remove(0, locale.lastIndexOf(QLatin1Char{'/'}) + 1);
|
||||
const QString lang = QLocale::languageToString(QLocale(locale).language());
|
||||
ui->language_combobox->addItem(lang, locale);
|
||||
}
|
||||
|
||||
// Unlike other configuration changes, interface language changes need to be reflected on the
|
||||
// interface immediately. This is done by passing a signal to the main window, and then
|
||||
// retranslating when passing back.
|
||||
connect(ui->language_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&ConfigureUi::OnLanguageChanged);
|
||||
}
|
||||
|
||||
void ConfigureUi::InitializeIconSizeComboBox() {
|
||||
for (const auto& size : default_icon_sizes) {
|
||||
ui->icon_size_combobox->addItem(QString::fromUtf8(size.second), size.first);
|
||||
|
@ -147,3 +171,10 @@ void ConfigureUi::UpdateSecondRowComboBox(bool init) {
|
|||
ui->row_2_text_combobox->removeItem(
|
||||
ui->row_2_text_combobox->findData(ui->row_1_text_combobox->currentData()));
|
||||
}
|
||||
|
||||
void ConfigureUi::OnLanguageChanged(int index) {
|
||||
if (index == -1)
|
||||
return;
|
||||
|
||||
emit LanguageChanged(ui->language_combobox->itemData(index).toString());
|
||||
}
|
||||
|
|
|
@ -20,6 +20,12 @@ public:
|
|||
|
||||
void ApplyConfiguration();
|
||||
|
||||
private slots:
|
||||
void OnLanguageChanged(int index);
|
||||
|
||||
signals:
|
||||
void LanguageChanged(const QString& locale);
|
||||
|
||||
private:
|
||||
void RequestGameListUpdate();
|
||||
|
||||
|
@ -28,6 +34,7 @@ private:
|
|||
void changeEvent(QEvent*) override;
|
||||
void RetranslateUI();
|
||||
|
||||
void InitializeLanguageComboBox();
|
||||
void InitializeIconSizeComboBox();
|
||||
void InitializeRowComboBoxes();
|
||||
|
||||
|
|
|
@ -13,17 +13,39 @@
|
|||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="HorizontalLayout">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="VerticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="GeneralGroupBox">
|
||||
<widget class="QGroupBox" name="general_groupBox">
|
||||
<property name="title">
|
||||
<string>General</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_change_language_info">
|
||||
<property name="text">
|
||||
<string>Note: Changing language will apply your configuration.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="language_label">
|
||||
<property name="text">
|
||||
<string>Interface language:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="language_combobox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
|
@ -119,8 +141,6 @@
|
|||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
|
|
@ -191,6 +191,8 @@ GMainWindow::GMainWindow()
|
|||
provider(std::make_unique<FileSys::ManualContentProvider>()) {
|
||||
InitializeLogging();
|
||||
|
||||
LoadTranslation();
|
||||
|
||||
setAcceptDrops(true);
|
||||
ui.setupUi(this);
|
||||
statusBar()->hide();
|
||||
|
@ -2048,6 +2050,9 @@ void GMainWindow::OnConfigure() {
|
|||
const bool old_discord_presence = UISettings::values.enable_discord_presence;
|
||||
|
||||
ConfigureDialog configure_dialog(this, hotkey_registry);
|
||||
connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this,
|
||||
&GMainWindow::OnLanguageChanged);
|
||||
|
||||
const auto result = configure_dialog.exec();
|
||||
if (result != QDialog::Accepted) {
|
||||
return;
|
||||
|
@ -2620,6 +2625,43 @@ void GMainWindow::UpdateUITheme() {
|
|||
QIcon::setThemeSearchPaths(theme_paths);
|
||||
}
|
||||
|
||||
void GMainWindow::LoadTranslation() {
|
||||
// If the selected language is English, no need to install any translation
|
||||
if (UISettings::values.language == QStringLiteral("en")) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool loaded;
|
||||
|
||||
if (UISettings::values.language.isEmpty()) {
|
||||
// If the selected language is empty, use system locale
|
||||
loaded = translator.load(QLocale(), {}, {}, QStringLiteral(":/languages/"));
|
||||
} else {
|
||||
// Otherwise load from the specified file
|
||||
loaded = translator.load(UISettings::values.language, QStringLiteral(":/languages/"));
|
||||
}
|
||||
|
||||
if (loaded) {
|
||||
qApp->installTranslator(&translator);
|
||||
} else {
|
||||
UISettings::values.language = QStringLiteral("en");
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::OnLanguageChanged(const QString& locale) {
|
||||
if (UISettings::values.language != QStringLiteral("en")) {
|
||||
qApp->removeTranslator(&translator);
|
||||
}
|
||||
|
||||
UISettings::values.language = locale;
|
||||
LoadTranslation();
|
||||
ui.retranslateUi(this);
|
||||
UpdateWindowTitle();
|
||||
|
||||
if (emulation_running)
|
||||
ui.action_Start->setText(tr("Continue"));
|
||||
}
|
||||
|
||||
void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
|
||||
#ifdef USE_DISCORD_PRESENCE
|
||||
if (state) {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include <QMainWindow>
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
|
@ -225,6 +226,7 @@ private slots:
|
|||
void OnCaptureScreenshot();
|
||||
void OnCoreError(Core::System::ResultStatus, std::string);
|
||||
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
|
||||
void OnLanguageChanged(const QString& locale);
|
||||
|
||||
private:
|
||||
std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
|
||||
|
@ -237,6 +239,7 @@ private:
|
|||
void HideMouseCursor();
|
||||
void ShowMouseCursor();
|
||||
void OpenURL(const QUrl& url);
|
||||
void LoadTranslation();
|
||||
|
||||
Ui::MainWindow ui;
|
||||
|
||||
|
@ -285,6 +288,8 @@ private:
|
|||
|
||||
HotkeyRegistry hotkey_registry;
|
||||
|
||||
QTranslator translator;
|
||||
|
||||
// Install progress dialog
|
||||
QProgressDialog* install_progress;
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@ struct Values {
|
|||
bool game_dir_deprecated_deepscan;
|
||||
QVector<UISettings::GameDir> game_dirs;
|
||||
QStringList recent_files;
|
||||
QString language;
|
||||
|
||||
QString theme;
|
||||
|
||||
|
|
Reference in New Issue