commit
5576af061b
|
@ -28,6 +28,7 @@ set(SRCS
|
||||||
Resources.cpp
|
Resources.cpp
|
||||||
Settings.cpp
|
Settings.cpp
|
||||||
ToolBar.cpp
|
ToolBar.cpp
|
||||||
|
Translation.cpp
|
||||||
WiiUpdate.cpp
|
WiiUpdate.cpp
|
||||||
WiiUpdate.h
|
WiiUpdate.h
|
||||||
Config/ControllersWindow.cpp
|
Config/ControllersWindow.cpp
|
||||||
|
@ -99,6 +100,39 @@ set(DOLPHINQT2_BINARY dolphin-emu-qt2)
|
||||||
add_executable(${DOLPHINQT2_BINARY} ${SRCS} ${UI_HEADERS})
|
add_executable(${DOLPHINQT2_BINARY} ${SRCS} ${UI_HEADERS})
|
||||||
target_link_libraries(${DOLPHINQT2_BINARY} ${LIBS} Qt5::Widgets)
|
target_link_libraries(${DOLPHINQT2_BINARY} ${LIBS} Qt5::Widgets)
|
||||||
|
|
||||||
|
# Handle localization
|
||||||
|
find_package(Gettext)
|
||||||
|
if(GETTEXT_MSGMERGE_EXECUTABLE AND GETTEXT_MSGFMT_EXECUTABLE)
|
||||||
|
set(pot_file "${CMAKE_SOURCE_DIR}/Languages/po/dolphin-emu.pot")
|
||||||
|
file(GLOB LINGUAS ${CMAKE_SOURCE_DIR}/Languages/po/*.po)
|
||||||
|
|
||||||
|
target_sources(dolphin-emu-qt2 PRIVATE ${pot_file} ${LINGUAS})
|
||||||
|
source_group("Localization" FILES ${LINGUAS})
|
||||||
|
source_group("Localization\\\\Generated" FILES ${pot_file})
|
||||||
|
|
||||||
|
foreach(po ${LINGUAS})
|
||||||
|
get_filename_component(lang ${po} NAME_WE)
|
||||||
|
set(mo_dir ${CMAKE_CURRENT_BINARY_DIR}/${lang})
|
||||||
|
set(mo ${mo_dir}/dolphin-emu.mo)
|
||||||
|
|
||||||
|
target_sources(dolphin-emu-qt2 PRIVATE ${mo})
|
||||||
|
source_group("Localization\\\\Generated" FILES ${mo})
|
||||||
|
|
||||||
|
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||||
|
set_source_files_properties(${mo} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/${lang}.lproj")
|
||||||
|
else()
|
||||||
|
install(FILES ${mo} DESTINATION share/locale/${lang}/LC_MESSAGES)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_custom_command(OUTPUT ${mo}
|
||||||
|
COMMAND mkdir -p ${mo_dir}
|
||||||
|
COMMAND ${GETTEXT_MSGMERGE_EXECUTABLE} --quiet --update --backup=none -s ${po} ${pot_file}
|
||||||
|
COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} -o ${mo} ${po}
|
||||||
|
DEPENDS ${po}
|
||||||
|
)
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
# Note: This is copied from DolphinQt, based on the DolphinWX version.
|
# Note: This is copied from DolphinQt, based on the DolphinWX version.
|
||||||
|
|
||||||
|
|
|
@ -218,6 +218,7 @@
|
||||||
<ClCompile Include="Settings\InterfacePane.cpp" />
|
<ClCompile Include="Settings\InterfacePane.cpp" />
|
||||||
<ClCompile Include="Settings\PathPane.cpp" />
|
<ClCompile Include="Settings\PathPane.cpp" />
|
||||||
<ClCompile Include="ToolBar.cpp" />
|
<ClCompile Include="ToolBar.cpp" />
|
||||||
|
<ClCompile Include="Translation.cpp" />
|
||||||
<ClCompile Include="WiiUpdate.cpp" />
|
<ClCompile Include="WiiUpdate.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!--Put standard C/C++ headers here-->
|
<!--Put standard C/C++ headers here-->
|
||||||
|
@ -243,6 +244,7 @@
|
||||||
<ClInclude Include="QtUtils\ListTabWidget.h" />
|
<ClInclude Include="QtUtils\ListTabWidget.h" />
|
||||||
<ClInclude Include="Resources.h" />
|
<ClInclude Include="Resources.h" />
|
||||||
<ClInclude Include="Settings\PathPane.h" />
|
<ClInclude Include="Settings\PathPane.h" />
|
||||||
|
<ClInclude Include="Translation.h" />
|
||||||
<ClInclude Include="WiiUpdate.h" />
|
<ClInclude Include="WiiUpdate.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include "DolphinQt2/QtUtils/RunOnObject.h"
|
#include "DolphinQt2/QtUtils/RunOnObject.h"
|
||||||
#include "DolphinQt2/Resources.h"
|
#include "DolphinQt2/Resources.h"
|
||||||
#include "DolphinQt2/Settings.h"
|
#include "DolphinQt2/Settings.h"
|
||||||
|
#include "DolphinQt2/Translation.h"
|
||||||
#include "UICommon/CommandLineParse.h"
|
#include "UICommon/CommandLineParse.h"
|
||||||
#include "UICommon/UICommon.h"
|
#include "UICommon/UICommon.h"
|
||||||
|
|
||||||
|
@ -75,6 +76,9 @@ int main(int argc, char* argv[])
|
||||||
// Hook up alerts from core
|
// Hook up alerts from core
|
||||||
RegisterMsgAlertHandler(QtMsgAlertHandler);
|
RegisterMsgAlertHandler(QtMsgAlertHandler);
|
||||||
|
|
||||||
|
// Hook up translations
|
||||||
|
Translation::Initialize();
|
||||||
|
|
||||||
// Whenever the event loop is about to go to sleep, dispatch the jobs
|
// Whenever the event loop is about to go to sleep, dispatch the jobs
|
||||||
// queued in the Core first.
|
// queued in the Core first.
|
||||||
QObject::connect(QAbstractEventDispatcher::instance(), &QAbstractEventDispatcher::aboutToBlock,
|
QObject::connect(QAbstractEventDispatcher::instance(), &QAbstractEventDispatcher::aboutToBlock,
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QFormLayout>
|
#include <QFormLayout>
|
||||||
#include <QGroupBox>
|
#include <QGroupBox>
|
||||||
|
#include <QMessageBox>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
@ -19,6 +20,56 @@
|
||||||
|
|
||||||
#include "DolphinQt2/Settings.h"
|
#include "DolphinQt2/Settings.h"
|
||||||
|
|
||||||
|
static QComboBox* MakeLanguageComboBox()
|
||||||
|
{
|
||||||
|
static const struct
|
||||||
|
{
|
||||||
|
const QString name;
|
||||||
|
const char* id;
|
||||||
|
} languages[] = {
|
||||||
|
{QStringLiteral("Bahasa Melayu"), "ms"}, // Malay
|
||||||
|
{QStringLiteral("Catal\u00E0"), "ca"}, // Catalan
|
||||||
|
{QStringLiteral("\u010Ce\u0161tina"), "cs"}, // Czech
|
||||||
|
{QStringLiteral("Dansk"), "da"}, // Danish
|
||||||
|
{QStringLiteral("Deutsch"), "de"}, // German
|
||||||
|
{QStringLiteral("English"), "en"}, // English
|
||||||
|
{QStringLiteral("Espa\u00F1ol"), "es"}, // Spanish
|
||||||
|
{QStringLiteral("Fran\u00E7ais"), "fr"}, // French
|
||||||
|
{QStringLiteral("Hrvatski"), "hr"}, // Croatian
|
||||||
|
{QStringLiteral("Italiano"), "it"}, // Italian
|
||||||
|
{QStringLiteral("Magyar"), "hu"}, // Hungarian
|
||||||
|
{QStringLiteral("Nederlands"), "nl"}, // Dutch
|
||||||
|
{QStringLiteral("Norsk bokm\u00E5l"), "nb"}, // Norwegian
|
||||||
|
{QStringLiteral("Polski"), "pl"}, // Polish
|
||||||
|
{QStringLiteral("Portugu\u00EAs"), "pt"}, // Portuguese
|
||||||
|
{QStringLiteral("Portugu\u00EAs (Brasil)"), "pt_BR"}, // Portuguese (Brazil)
|
||||||
|
{QStringLiteral("Rom\u00E2n\u0103"), "ro"}, // Romanian
|
||||||
|
{QStringLiteral("Srpski"), "sr"}, // Serbian
|
||||||
|
{QStringLiteral("Svenska"), "sv"}, // Swedish
|
||||||
|
{QStringLiteral("T\u00FCrk\u00E7e"), "tr"}, // Turkish
|
||||||
|
{QStringLiteral("\u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC"), "el"}, // Greek
|
||||||
|
{QStringLiteral("\u0420\u0443\u0441\u0441\u043A\u0438\u0439"), "ru"}, // Russian
|
||||||
|
{QStringLiteral("\u0627\u0644\u0639\u0631\u0628\u064A\u0629"), "ar"}, // Arabic
|
||||||
|
{QStringLiteral("\u0641\u0627\u0631\u0633\u06CC"), "fa"}, // Farsi
|
||||||
|
{QStringLiteral("\uD55C\uAD6D\uC5B4"), "ko"}, // Korean
|
||||||
|
{QStringLiteral("\u65E5\u672C\u8A9E"), "ja"}, // Japanese
|
||||||
|
{QStringLiteral("\u7B80\u4F53\u4E2D\u6587"), "zh_CN"}, // Simplified Chinese
|
||||||
|
{QStringLiteral("\u7E41\u9AD4\u4E2D\u6587"), "zh_TW"}, // Traditional Chinese
|
||||||
|
};
|
||||||
|
|
||||||
|
auto* combobox = new QComboBox();
|
||||||
|
combobox->addItem(QObject::tr("<System Language>"), QStringLiteral(""));
|
||||||
|
for (const auto& lang : languages)
|
||||||
|
combobox->addItem(lang.name, QString::fromLatin1(lang.id));
|
||||||
|
|
||||||
|
// The default, QComboBox::AdjustToContentsOnFirstShow, causes a noticeable pause when opening the
|
||||||
|
// SettingWindow for the first time. The culprit seems to be non-Latin graphemes in the above
|
||||||
|
// list. QComboBox::AdjustToContents still has some lag but it's much less noticeable.
|
||||||
|
combobox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||||
|
|
||||||
|
return combobox;
|
||||||
|
}
|
||||||
|
|
||||||
InterfacePane::InterfacePane(QWidget* parent) : QWidget(parent)
|
InterfacePane::InterfacePane(QWidget* parent) : QWidget(parent)
|
||||||
{
|
{
|
||||||
CreateLayout();
|
CreateLayout();
|
||||||
|
@ -48,9 +99,7 @@ void InterfacePane::CreateUI()
|
||||||
auto* combobox_layout = new QFormLayout;
|
auto* combobox_layout = new QFormLayout;
|
||||||
groupbox_layout->addLayout(combobox_layout);
|
groupbox_layout->addLayout(combobox_layout);
|
||||||
|
|
||||||
m_combobox_language = new QComboBox;
|
m_combobox_language = MakeLanguageComboBox();
|
||||||
// TODO: Support more languages other then English
|
|
||||||
m_combobox_language->addItem(tr("English"));
|
|
||||||
combobox_layout->addRow(tr("&Language:"), m_combobox_language);
|
combobox_layout->addRow(tr("&Language:"), m_combobox_language);
|
||||||
|
|
||||||
// Theme Combobox
|
// Theme Combobox
|
||||||
|
@ -111,8 +160,8 @@ void InterfacePane::ConnectLayout()
|
||||||
&InterfacePane::OnSaveConfig);
|
&InterfacePane::OnSaveConfig);
|
||||||
connect(m_combobox_theme, static_cast<void (QComboBox::*)(const QString&)>(&QComboBox::activated),
|
connect(m_combobox_theme, static_cast<void (QComboBox::*)(const QString&)>(&QComboBox::activated),
|
||||||
&Settings::Instance(), &Settings::SetThemeName);
|
&Settings::Instance(), &Settings::SetThemeName);
|
||||||
connect(m_combobox_language, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated),
|
connect(m_combobox_language, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this,
|
||||||
[this](int index) { OnSaveConfig(); });
|
&InterfacePane::OnSaveConfig);
|
||||||
connect(m_checkbox_confirm_on_stop, &QCheckBox::clicked, this, &InterfacePane::OnSaveConfig);
|
connect(m_checkbox_confirm_on_stop, &QCheckBox::clicked, this, &InterfacePane::OnSaveConfig);
|
||||||
connect(m_checkbox_use_panic_handlers, &QCheckBox::clicked, this, &InterfacePane::OnSaveConfig);
|
connect(m_checkbox_use_panic_handlers, &QCheckBox::clicked, this, &InterfacePane::OnSaveConfig);
|
||||||
connect(m_checkbox_enable_osd, &QCheckBox::clicked, this, &InterfacePane::OnSaveConfig);
|
connect(m_checkbox_enable_osd, &QCheckBox::clicked, this, &InterfacePane::OnSaveConfig);
|
||||||
|
@ -128,6 +177,8 @@ void InterfacePane::LoadConfig()
|
||||||
m_checkbox_top_window->setChecked(startup_params.bKeepWindowOnTop);
|
m_checkbox_top_window->setChecked(startup_params.bKeepWindowOnTop);
|
||||||
m_checkbox_render_to_window->setChecked(startup_params.bRenderToMain);
|
m_checkbox_render_to_window->setChecked(startup_params.bRenderToMain);
|
||||||
m_checkbox_use_builtin_title_database->setChecked(startup_params.m_use_builtin_title_database);
|
m_checkbox_use_builtin_title_database->setChecked(startup_params.m_use_builtin_title_database);
|
||||||
|
m_combobox_language->setCurrentIndex(m_combobox_language->findData(
|
||||||
|
QString::fromStdString(SConfig::GetInstance().m_InterfaceLanguage)));
|
||||||
m_combobox_theme->setCurrentIndex(
|
m_combobox_theme->setCurrentIndex(
|
||||||
m_combobox_theme->findText(QString::fromStdString(SConfig::GetInstance().theme_name)));
|
m_combobox_theme->findText(QString::fromStdString(SConfig::GetInstance().theme_name)));
|
||||||
|
|
||||||
|
@ -155,5 +206,14 @@ void InterfacePane::OnSaveConfig()
|
||||||
settings.m_show_active_title = m_checkbox_show_active_title->isChecked();
|
settings.m_show_active_title = m_checkbox_show_active_title->isChecked();
|
||||||
settings.m_PauseOnFocusLost = m_checkbox_pause_on_focus_lost->isChecked();
|
settings.m_PauseOnFocusLost = m_checkbox_pause_on_focus_lost->isChecked();
|
||||||
|
|
||||||
|
auto new_language = m_combobox_language->currentData().toString().toStdString();
|
||||||
|
if (new_language != SConfig::GetInstance().m_InterfaceLanguage)
|
||||||
|
{
|
||||||
|
SConfig::GetInstance().m_InterfaceLanguage = new_language;
|
||||||
|
QMessageBox::information(
|
||||||
|
this, tr("Restart Required"),
|
||||||
|
tr("You must restart Dolphin in order for the change to take effect."));
|
||||||
|
}
|
||||||
|
|
||||||
settings.SaveSettings();
|
settings.SaveSettings();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,298 @@
|
||||||
|
// Copyright 2017 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "DolphinQt2/Translation.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QLocale>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QTranslator>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
#include "Common/File.h"
|
||||||
|
#include "Common/FileUtil.h"
|
||||||
|
#include "Common/Logging/Log.h"
|
||||||
|
#include "Common/MsgHandler.h"
|
||||||
|
#include "Common/StringUtil.h"
|
||||||
|
#include "Core/ConfigManager.h"
|
||||||
|
|
||||||
|
constexpr u32 MO_MAGIC_NUMBER = 0x950412de;
|
||||||
|
|
||||||
|
static u16 ReadU16(const char* data)
|
||||||
|
{
|
||||||
|
u16 value;
|
||||||
|
std::memcpy(&value, data, sizeof(value));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 ReadU32(const char* data)
|
||||||
|
{
|
||||||
|
u32 value;
|
||||||
|
std::memcpy(&value, data, sizeof(value));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MoIterator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using iterator_category = std::random_access_iterator_tag;
|
||||||
|
using value_type = const char*;
|
||||||
|
using difference_type = s64;
|
||||||
|
using pointer = value_type;
|
||||||
|
using reference = value_type;
|
||||||
|
|
||||||
|
explicit MoIterator(const char* data, u32 table_offset, u32 index = 0)
|
||||||
|
: m_data{data}, m_table_offset{table_offset}, m_index{index}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the actual underlying logic of accessing a Mo file. Patterned after the
|
||||||
|
// boost::iterator_facade library, which nicely separates out application logic from
|
||||||
|
// iterator-concept logic.
|
||||||
|
void advance(difference_type n) { m_index += n; }
|
||||||
|
difference_type distance_to(const MoIterator& other) const { return other.m_index - m_index; }
|
||||||
|
reference dereference() const
|
||||||
|
{
|
||||||
|
u32 offset = ReadU32(&m_data[m_table_offset + m_index * 8 + 4]);
|
||||||
|
return &m_data[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Needed for Iterator concept
|
||||||
|
reference operator*() const { return dereference(); }
|
||||||
|
MoIterator& operator++()
|
||||||
|
{
|
||||||
|
advance(1);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Needed for InputIterator concept
|
||||||
|
bool operator==(const MoIterator& other) const { return distance_to(other) == 0; }
|
||||||
|
bool operator!=(const MoIterator& other) const { return !(*this == other); }
|
||||||
|
pointer operator->() const { return dereference(); }
|
||||||
|
MoIterator operator++(int)
|
||||||
|
{
|
||||||
|
MoIterator tmp(*this);
|
||||||
|
advance(1);
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Needed for BidirectionalIterator concept
|
||||||
|
MoIterator& operator--()
|
||||||
|
{
|
||||||
|
advance(-1);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
MoIterator operator--(int)
|
||||||
|
{
|
||||||
|
MoIterator tmp(*this);
|
||||||
|
advance(-1);
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Needed for RandomAccessIterator concept
|
||||||
|
bool operator<(const MoIterator& other) const { return distance_to(other) > 0; }
|
||||||
|
bool operator<=(const MoIterator& other) const { return distance_to(other) >= 0; }
|
||||||
|
bool operator>(const MoIterator& other) const { return distance_to(other) < 0; }
|
||||||
|
bool operator>=(const MoIterator& other) const { return distance_to(other) <= 0; }
|
||||||
|
reference operator[](difference_type n) const { return *(*this + n); }
|
||||||
|
MoIterator& operator+=(difference_type n)
|
||||||
|
{
|
||||||
|
advance(n);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
MoIterator& operator-=(difference_type n)
|
||||||
|
{
|
||||||
|
advance(-n);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
friend MoIterator operator+(difference_type n, const MoIterator& it) { return it + n; }
|
||||||
|
friend MoIterator operator+(const MoIterator& it, difference_type n)
|
||||||
|
{
|
||||||
|
MoIterator tmp(it);
|
||||||
|
tmp += n;
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
difference_type operator-(const MoIterator& other) const { return other.distance_to(*this); }
|
||||||
|
friend MoIterator operator-(difference_type n, const MoIterator& it) { return it - n; }
|
||||||
|
friend MoIterator operator-(const MoIterator& it, difference_type n)
|
||||||
|
{
|
||||||
|
MoIterator tmp(it);
|
||||||
|
tmp -= n;
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const char* m_data;
|
||||||
|
u32 m_table_offset;
|
||||||
|
u32 m_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MoFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MoFile() = default;
|
||||||
|
explicit MoFile(const std::string& filename)
|
||||||
|
{
|
||||||
|
File::IOFile file(filename, "rb");
|
||||||
|
m_data.resize(file.GetSize());
|
||||||
|
file.ReadBytes(m_data.data(), m_data.size());
|
||||||
|
|
||||||
|
if (!file)
|
||||||
|
{
|
||||||
|
ERROR_LOG(COMMON, "Error reading MO file '%s'", filename.c_str());
|
||||||
|
m_data = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 magic = ReadU32(&m_data[0]);
|
||||||
|
if (magic != MO_MAGIC_NUMBER)
|
||||||
|
{
|
||||||
|
ERROR_LOG(COMMON, "MO file '%s' has bad magic number %x\n", filename.c_str(), magic);
|
||||||
|
m_data = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 version_major = ReadU16(&m_data[4]);
|
||||||
|
if (version_major > 1)
|
||||||
|
{
|
||||||
|
ERROR_LOG(COMMON, "MO file '%s' has unsupported version number %i", filename.c_str(),
|
||||||
|
version_major);
|
||||||
|
m_data = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_number_of_strings = ReadU32(&m_data[8]);
|
||||||
|
m_offset_original_table = ReadU32(&m_data[12]);
|
||||||
|
m_offset_translation_table = ReadU32(&m_data[16]);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetNumberOfStrings() const { return m_number_of_strings; }
|
||||||
|
const char* Translate(const char* original_string) const
|
||||||
|
{
|
||||||
|
const MoIterator begin(m_data.data(), m_offset_original_table);
|
||||||
|
const MoIterator end(m_data.data(), m_offset_original_table, m_number_of_strings);
|
||||||
|
auto iter = std::lower_bound(begin, end, original_string,
|
||||||
|
[](const char* a, const char* b) { return strcmp(a, b) < 0; });
|
||||||
|
|
||||||
|
if (strcmp(*iter, original_string) != 0)
|
||||||
|
return original_string;
|
||||||
|
|
||||||
|
u32 offset = ReadU32(&m_data[m_offset_translation_table + std::distance(begin, iter) * 8 + 4]);
|
||||||
|
return &m_data[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<char> m_data;
|
||||||
|
u32 m_number_of_strings = 0;
|
||||||
|
u32 m_offset_original_table = 0;
|
||||||
|
u32 m_offset_translation_table = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MoTranslator : public QTranslator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using QTranslator::QTranslator;
|
||||||
|
|
||||||
|
bool isEmpty() const override { return m_mo_file.GetNumberOfStrings() == 0; }
|
||||||
|
bool load(const std::string& filename)
|
||||||
|
{
|
||||||
|
m_mo_file = MoFile(filename);
|
||||||
|
return !isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString translate(const char* context, const char* source_text,
|
||||||
|
const char* disambiguation = nullptr, int n = -1) const override
|
||||||
|
{
|
||||||
|
return QString::fromUtf8(m_mo_file.Translate(source_text));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
MoFile m_mo_file;
|
||||||
|
};
|
||||||
|
|
||||||
|
QStringList FindPossibleLanguageCodes(const QString& exact_language_code)
|
||||||
|
{
|
||||||
|
QStringList possible_language_codes;
|
||||||
|
possible_language_codes << exact_language_code;
|
||||||
|
|
||||||
|
// Qt likes to separate language, script, and country by hyphen, but on disk they're separated by
|
||||||
|
// underscores.
|
||||||
|
possible_language_codes.replaceInStrings(QStringLiteral("-"), QStringLiteral("_"));
|
||||||
|
|
||||||
|
// Try successively dropping subtags (like the stock QTranslator, and as specified by RFC 4647
|
||||||
|
// "Matching of Language Tags").
|
||||||
|
// Example: fr_Latn_CA -> fr_Latn -> fr
|
||||||
|
for (auto lang : QStringList(possible_language_codes))
|
||||||
|
{
|
||||||
|
while (lang.contains(QLatin1Char('_')))
|
||||||
|
{
|
||||||
|
lang = lang.left(lang.lastIndexOf(QLatin1Char('_')));
|
||||||
|
possible_language_codes << lang;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// On macOS, Chinese (Simplified) and Chinese (Traditional) are represented as zh-Hans and
|
||||||
|
// zh-Hant, but on Linux they're represented as zh-CN and zh-TW. Qt should probably include the
|
||||||
|
// script subtags on Linux, but it doesn't.
|
||||||
|
if (possible_language_codes.contains(QStringLiteral("zh_Hans")))
|
||||||
|
possible_language_codes << QStringLiteral("zh_CN");
|
||||||
|
if (possible_language_codes.contains(QStringLiteral("zh_Hant")))
|
||||||
|
possible_language_codes << QStringLiteral("zh_TW");
|
||||||
|
|
||||||
|
return possible_language_codes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool TryInstallTranslator(const QString& exact_language_code)
|
||||||
|
{
|
||||||
|
for (const auto& qlang : FindPossibleLanguageCodes(exact_language_code))
|
||||||
|
{
|
||||||
|
std::string lang = qlang.toStdString();
|
||||||
|
auto filename =
|
||||||
|
#if defined _WIN32
|
||||||
|
File::GetExeDirectory() + StringFromFormat("/Languages/%s/dolphin-emu.mo", lang.c_str())
|
||||||
|
#elif defined __APPLE__
|
||||||
|
File::GetBundleDirectory() +
|
||||||
|
StringFromFormat("/Contents/Resources/%s.lproj/dolphin-emu.mo", lang.c_str())
|
||||||
|
#else
|
||||||
|
StringFromFormat(DATA_DIR "/../locale/%s/LC_MESSAGES/dolphin-emu.mo", lang.c_str())
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
|
||||||
|
auto* translator = new MoTranslator(QApplication::instance());
|
||||||
|
if (translator->load(filename))
|
||||||
|
{
|
||||||
|
QApplication::instance()->installTranslator(translator);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
translator->deleteLater();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Translation::Initialize()
|
||||||
|
{
|
||||||
|
// Hook up Dolphin internal translation
|
||||||
|
RegisterStringTranslator([](const char* text) { return QObject::tr(text).toStdString(); });
|
||||||
|
|
||||||
|
// Hook up Qt translations
|
||||||
|
auto& configured_language = SConfig::GetInstance().m_InterfaceLanguage;
|
||||||
|
if (!configured_language.empty())
|
||||||
|
{
|
||||||
|
if (TryInstallTranslator(QString::fromStdString(configured_language)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
QMessageBox::warning(
|
||||||
|
nullptr, QObject::tr("Error"),
|
||||||
|
QObject::tr("Error loading selected language. Falling back to system default."));
|
||||||
|
configured_language.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& lang : QLocale::system().uiLanguages())
|
||||||
|
{
|
||||||
|
if (TryInstallTranslator(lang))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright 2017 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Translation
|
||||||
|
{
|
||||||
|
void Initialize();
|
||||||
|
}
|
|
@ -36,11 +36,10 @@
|
||||||
static const std::array<std::string, 29> language_ids{{
|
static const std::array<std::string, 29> language_ids{{
|
||||||
"",
|
"",
|
||||||
|
|
||||||
"ms", "ca", "cs", "da", "de", "en", "es", "fr", "hr", "it", "hu", "nl",
|
"ms", "ca", "cs", "da", "de", "en", "es", "fr", "hr", "it",
|
||||||
"nb", // wxWidgets won't accept "no"
|
"hu", "nl", "nb", "pl", "pt", "pt_BR", "ro", "sr", "sv", "tr",
|
||||||
"pl", "pt", "pt_BR", "ro", "sr", "sv", "tr",
|
|
||||||
|
|
||||||
"el", "ru", "ar", "fa", "ko", "ja", "zh_CN", "zh_TW",
|
"el", "ru", "ar", "fa", "ko", "ja", "zh_CN", "zh_TW",
|
||||||
}};
|
}};
|
||||||
|
|
||||||
InterfaceConfigPane::InterfaceConfigPane(wxWindow* parent, wxWindowID id) : wxPanel(parent, id)
|
InterfaceConfigPane::InterfaceConfigPane(wxWindow* parent, wxWindowID id) : wxPanel(parent, id)
|
||||||
|
|
Loading…
Reference in New Issue