mirror of https://github.com/PCSX2/pcsx2.git
Qt: Localization support
This commit is contained in:
parent
c359223fd4
commit
adcfca4db3
|
@ -167,20 +167,25 @@
|
||||||
/>
|
/>
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<!--Copies base translation files-->
|
<!--Copies base translation files, need to rename them from qtbase_ to qt_ -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<BaseTsFiles Include="$(QtTranslationsDir)\*.qm" />
|
<BaseTsFiles Include="$(QtTranslationsDir)\*.qm" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Target Name="QtCopyBaseTranslations"
|
<Target Name="QtCopyBaseTranslations"
|
||||||
AfterTargets="Build"
|
AfterTargets="Build"
|
||||||
Inputs="@(BaseTsFiles)"
|
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" />
|
<Message Text="Copying base translation files" Importance="High" />
|
||||||
<Copy
|
<Copy SourceFiles="@(TempItems->'%(OriginalPath)')"
|
||||||
SourceFiles="@(BaseTsFiles)"
|
DestinationFiles="@(TempItems->'$(QtTsOutDir)%(SavedRecursiveDir)%(Identity)')"
|
||||||
DestinationFolder="$(QtTsOutDir)"
|
SkipUnchangedFiles="true" />
|
||||||
SkipUnchangedFiles="true"
|
|
||||||
/>
|
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<!--Compiles all translation files-->
|
<!--Compiles all translation files-->
|
||||||
|
|
|
@ -26,6 +26,7 @@ target_sources(pcsx2-qt PRIVATE
|
||||||
PrecompiledHeader.h
|
PrecompiledHeader.h
|
||||||
SettingWidgetBinder.h
|
SettingWidgetBinder.h
|
||||||
Themes.cpp
|
Themes.cpp
|
||||||
|
Translations.cpp
|
||||||
QtHost.cpp
|
QtHost.cpp
|
||||||
QtHost.h
|
QtHost.h
|
||||||
QtKeyCodes.cpp
|
QtKeyCodes.cpp
|
||||||
|
@ -173,6 +174,10 @@ if(USE_ACHIEVEMENTS)
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
set(TS_FILES
|
||||||
|
Translations/pcsx2-qt_en.ts
|
||||||
|
)
|
||||||
|
|
||||||
target_precompile_headers(pcsx2-qt PRIVATE PrecompiledHeader.h)
|
target_precompile_headers(pcsx2-qt PRIVATE PrecompiledHeader.h)
|
||||||
|
|
||||||
target_include_directories(pcsx2-qt PRIVATE
|
target_include_directories(pcsx2-qt PRIVATE
|
||||||
|
@ -191,6 +196,24 @@ target_link_libraries(pcsx2-qt PRIVATE
|
||||||
Qt6::Network
|
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.
|
# Currently, 7z is only needed for the Windows updater.
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(pcsx2-qt PRIVATE
|
target_link_libraries(pcsx2-qt PRIVATE
|
||||||
|
|
|
@ -1394,6 +1394,12 @@ void MainWindow::updateTheme()
|
||||||
m_game_list_widget->refreshImages();
|
m_game_list_widget->refreshImages();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::updateLanguage()
|
||||||
|
{
|
||||||
|
QtHost::InstallTranslator();
|
||||||
|
recreate();
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::onInputRecNewActionTriggered()
|
void MainWindow::onInputRecNewActionTriggered()
|
||||||
{
|
{
|
||||||
const bool wasPaused = s_vm_paused;
|
const bool wasPaused = s_vm_paused;
|
||||||
|
@ -2064,6 +2070,11 @@ SettingsDialog* MainWindow::getSettingsDialog()
|
||||||
{
|
{
|
||||||
m_settings_dialog = new SettingsDialog(this);
|
m_settings_dialog = new SettingsDialog(this);
|
||||||
connect(m_settings_dialog->getInterfaceSettingsWidget(), &InterfaceSettingsWidget::themeChanged, this, &MainWindow::updateTheme);
|
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;
|
return m_settings_dialog;
|
||||||
|
|
|
@ -159,6 +159,7 @@ private Q_SLOTS:
|
||||||
void onToolsOpenDataDirectoryTriggered();
|
void onToolsOpenDataDirectoryTriggered();
|
||||||
void onToolsCoverDownloaderTriggered();
|
void onToolsCoverDownloaderTriggered();
|
||||||
void updateTheme();
|
void updateTheme();
|
||||||
|
void updateLanguage();
|
||||||
void onScreenshotActionTriggered();
|
void onScreenshotActionTriggered();
|
||||||
void onSaveGSDumpActionTriggered();
|
void onSaveGSDumpActionTriggered();
|
||||||
void onBlockDumpActionToggled(bool checked);
|
void onBlockDumpActionToggled(bool checked);
|
||||||
|
|
|
@ -1179,23 +1179,6 @@ void Host::SetFullscreen(bool enabled)
|
||||||
g_emu_thread->setFullscreen(enabled, true);
|
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;
|
alignas(16) static SysMtgsThread s_mtgs_thread;
|
||||||
|
|
||||||
SysMtgsThread& GetMTGS()
|
SysMtgsThread& GetMTGS()
|
||||||
|
@ -1237,6 +1220,7 @@ bool QtHost::InitializeConfig()
|
||||||
|
|
||||||
LogSink::SetBlockSystemConsole(QtHost::InNoGUIMode());
|
LogSink::SetBlockSystemConsole(QtHost::InNoGUIMode());
|
||||||
VMManager::Internal::LoadStartupSettings();
|
VMManager::Internal::LoadStartupSettings();
|
||||||
|
InstallTranslator();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -216,6 +216,9 @@ namespace QtHost
|
||||||
/// 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();
|
||||||
|
|
||||||
|
@ -234,6 +237,12 @@ namespace QtHost
|
||||||
/// Executes a function on the UI thread.
|
/// Executes a function on the UI thread.
|
||||||
void RunOnUIThread(const std::function<void()>& func, bool block = false);
|
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.
|
/// Returns the application name and version, optionally including debug/devel config indicator.
|
||||||
QString GetAppNameAndVersion();
|
QString GetAppNameAndVersion();
|
||||||
|
|
||||||
|
|
|
@ -116,8 +116,8 @@ void ControllerBindingWidget::onTypeChanged()
|
||||||
if (has_settings)
|
if (has_settings)
|
||||||
{
|
{
|
||||||
const gsl::span<const SettingInfo> settings(cinfo->settings, cinfo->num_settings);
|
const gsl::span<const SettingInfo> settings(cinfo->settings, cinfo->num_settings);
|
||||||
m_settings_widget =
|
m_settings_widget = new ControllerCustomSettingsWidget(
|
||||||
new ControllerCustomSettingsWidget(settings, m_config_section, std::string(), cinfo->name, getDialog(), m_ui.stackedWidget);
|
settings, m_config_section, std::string(), "Pad", getDialog(), m_ui.stackedWidget);
|
||||||
m_ui.stackedWidget->addWidget(m_settings_widget);
|
m_ui.stackedWidget->addWidget(m_settings_widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,7 +343,7 @@ ControllerMacroEditWidget::ControllerMacroEditWidget(ControllerMacroWidget* pare
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
QListWidgetItem* item = new QListWidgetItem();
|
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);
|
item->setCheckState((std::find(m_binds.begin(), m_binds.end(), &bi) != m_binds.end()) ? Qt::Checked : Qt::Unchecked);
|
||||||
m_ui.bindList->addItem(item);
|
m_ui.bindList->addItem(item);
|
||||||
}
|
}
|
||||||
|
@ -374,7 +374,7 @@ QString ControllerMacroEditWidget::getSummary() const
|
||||||
{
|
{
|
||||||
if (!str.isEmpty())
|
if (!str.isEmpty())
|
||||||
str += static_cast<QChar>('/');
|
str += static_cast<QChar>('/');
|
||||||
str += QString::fromUtf8(bi->name);
|
str += qApp->translate("Pad", bi->display_name);
|
||||||
}
|
}
|
||||||
return str.isEmpty() ? tr("Not Configured") : str;
|
return str.isEmpty() ? tr("Not Configured") : str;
|
||||||
}
|
}
|
||||||
|
@ -950,7 +950,7 @@ void USBDeviceWidget::populatePages()
|
||||||
if (!settings.empty())
|
if (!settings.empty())
|
||||||
{
|
{
|
||||||
m_settings_widget = new ControllerCustomSettingsWidget(
|
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);
|
m_ui.stackedWidget->addWidget(m_settings_widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -401,7 +401,7 @@ void ControllerSettingsDialog::createWidgets()
|
||||||
m_ui.settingsContainer->addWidget(m_port_bindings[global_slot]);
|
m_ui.settingsContainer->addWidget(m_port_bindings[global_slot]);
|
||||||
|
|
||||||
const PAD::ControllerInfo* ci = PAD::GetControllerInfo(m_port_bindings[global_slot]->getControllerType());
|
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();
|
QListWidgetItem* item = new QListWidgetItem();
|
||||||
//: Controller Port is an official term from Sony. Find the official translation for your language inside the console's manual.
|
//: 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 bool mtap_enabled = getBoolValue("Pad", (port == 0) ? "MultitapPort1" : "MultitapPort2", false);
|
||||||
|
|
||||||
const PAD::ControllerInfo* ci = PAD::GetControllerInfo(widget->getControllerType());
|
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.
|
//: 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)) :
|
item->setText(mtap_enabled ? (tr("Controller Port %1%2\n%3").arg(port + 1).arg(s_mtap_slot_names[slot]).arg(display_name)) :
|
||||||
|
|
|
@ -95,6 +95,10 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsDialog* dialog, QWidget
|
||||||
QtHost::GetDefaultThemeName());
|
QtHost::GetDefaultThemeName());
|
||||||
connect(m_ui.theme, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]() { emit themeChanged(); });
|
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.
|
// 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());
|
m_ui.perGameSettings->setEnabled(!dialog->isPerGameSettings());
|
||||||
if (!dialog->isPerGameSettings())
|
if (!dialog->isPerGameSettings())
|
||||||
|
@ -181,9 +185,6 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsDialog* dialog, QWidget
|
||||||
m_ui.disableWindowResizing, tr("Disable Window Resizing"), tr("Unchecked"),
|
m_ui.disableWindowResizing, tr("Disable Window Resizing"), tr("Unchecked"),
|
||||||
tr("Prevents the main window from being resized."));
|
tr("Prevents the main window from being resized."));
|
||||||
|
|
||||||
// Not yet used, disable the options
|
|
||||||
m_ui.language->setDisabled(true);
|
|
||||||
|
|
||||||
onRenderToSeparateWindowChanged();
|
onRenderToSeparateWindowChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,3 +194,9 @@ void InterfaceSettingsWidget::onRenderToSeparateWindowChanged()
|
||||||
{
|
{
|
||||||
m_ui.hideMainWindow->setEnabled(m_ui.renderToSeparateWindow->isChecked());
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -31,10 +31,13 @@ public:
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void themeChanged();
|
void themeChanged();
|
||||||
|
void languageChanged();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void onRenderToSeparateWindowChanged();
|
void onRenderToSeparateWindowChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void populateLanguages();
|
||||||
|
|
||||||
Ui::InterfaceSettingsWidget m_ui;
|
Ui::InterfaceSettingsWidget m_ui;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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)
|
|
@ -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
|
|
@ -39,6 +39,7 @@
|
||||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\include</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\include</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\lzma\include</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\lzma\include</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\simpleini\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\demangler\include</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include;%(AdditionalIncludeDirectories);</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>
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\sdl2\include;$(SolutionDir)3rdparty\sdl2\SDL\include</AdditionalIncludeDirectories>
|
||||||
|
@ -138,6 +139,7 @@
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="QtKeyCodes.cpp" />
|
<ClCompile Include="QtKeyCodes.cpp" />
|
||||||
<ClCompile Include="QtUtils.cpp" />
|
<ClCompile Include="QtUtils.cpp" />
|
||||||
|
<ClCompile Include="Translations.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<QtMoc Include="Settings\InterfaceSettingsWidget.h" />
|
<QtMoc Include="Settings\InterfaceSettingsWidget.h" />
|
||||||
|
@ -365,6 +367,9 @@
|
||||||
</QtUi>
|
</QtUi>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<QtTs Include="Translations\pcsx2-qt_en.ts">
|
||||||
|
<FileType>Document</FileType>
|
||||||
|
</QtTs>
|
||||||
<QtUi Include="Settings\DebugSettingsWidget.ui">
|
<QtUi Include="Settings\DebugSettingsWidget.ui">
|
||||||
<FileType>Document</FileType>
|
<FileType>Document</FileType>
|
||||||
</QtUi>
|
</QtUi>
|
||||||
|
|
|
@ -25,6 +25,9 @@
|
||||||
<Filter Include="Debugger\Models">
|
<Filter Include="Debugger\Models">
|
||||||
<UniqueIdentifier>{f4084ca0-d9d5-4584-b9d2-063db9f67de2}</UniqueIdentifier>
|
<UniqueIdentifier>{f4084ca0-d9d5-4584-b9d2-063db9f67de2}</UniqueIdentifier>
|
||||||
</Filter>
|
</Filter>
|
||||||
|
<Filter Include="Translations">
|
||||||
|
<UniqueIdentifier>{ad04f939-64a0-4039-97aa-a38b8aa46855}</UniqueIdentifier>
|
||||||
|
</Filter>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="..\pcsx2\windows\PCSX2.rc" />
|
<ResourceCompile Include="..\pcsx2\windows\PCSX2.rc" />
|
||||||
|
@ -328,6 +331,7 @@
|
||||||
<ClCompile Include="$(IntDir)Settings\moc_GameCheatSettingsWidget.cpp">
|
<ClCompile Include="$(IntDir)Settings\moc_GameCheatSettingsWidget.cpp">
|
||||||
<Filter>moc</Filter>
|
<Filter>moc</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="Translations.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Manifest Include="..\pcsx2\windows\PCSX2.manifest">
|
<Manifest Include="..\pcsx2\windows\PCSX2.manifest">
|
||||||
|
@ -619,4 +623,9 @@
|
||||||
<Filter>Settings</Filter>
|
<Filter>Settings</Filter>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<QtTs Include="Translations\pcsx2-qt_en.ts">
|
||||||
|
<Filter>Translations</Filter>
|
||||||
|
</QtTs>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -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
|
#ifdef ENABLE_ACHIEVEMENTS
|
||||||
void Host::OnAchievementsRefreshed()
|
void Host::OnAchievementsRefreshed()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue