123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- // Copyright 2017 Dolphin Emulator Project
- // SPDX-License-Identifier: GPL-2.0-or-later
- #include "DolphinQt/Translation.h"
- #include <algorithm>
- #include <cstring>
- #include <iterator>
- #include <string>
- #include <fmt/format.h>
- #include <QApplication>
- #include <QLocale>
- #include <QTranslator>
- #include "Common/FileUtil.h"
- #include "Common/IOFile.h"
- #include "Common/Logging/Log.h"
- #include "Common/MsgHandler.h"
- #include "Common/StringUtil.h"
- #include "Core/Config/MainSettings.h"
- #include "DolphinQt/QtUtils/ModalMessageBox.h"
- #include "UICommon/UICommon.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 static_cast<difference_type>(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; }
- 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)
- {
- WARN_LOG_FMT(COMMON, "Error reading MO file '{}'", filename);
- m_data = {};
- return;
- }
- const u32 magic = ReadU32(&m_data[0]);
- if (magic != MO_MAGIC_NUMBER)
- {
- ERROR_LOG_FMT(COMMON, "MO file '{}' has bad magic number {:x}\n", filename, magic);
- m_data = {};
- return;
- }
- const u16 version_major = ReadU16(&m_data[4]);
- if (version_major > 1)
- {
- ERROR_LOG_FMT(COMMON, "MO file '{}' has unsupported version number {}", filename,
- 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 (iter == end || strcmp(*iter, original_string) != 0)
- return nullptr;
- 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
- {
- const char* translated_text;
- if (disambiguation)
- {
- std::string combined_string = disambiguation;
- combined_string += '\4';
- combined_string += source_text;
- translated_text = m_mo_file.Translate(combined_string.c_str());
- }
- else
- {
- translated_text = m_mo_file.Translate(source_text);
- }
- return QString::fromUtf8(translated_text ? translated_text : source_text);
- }
- private:
- MoFile m_mo_file;
- };
- static 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.
- const int hans_index = possible_language_codes.indexOf(QStringLiteral("zh_Hans"));
- if (hans_index != -1)
- possible_language_codes.insert(hans_index + 1, QStringLiteral("zh_CN"));
- const int hant_index = possible_language_codes.indexOf(QStringLiteral("zh_Hant"));
- if (hant_index != -1)
- possible_language_codes.insert(hant_index + 1, 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
- fmt::format("{}/Languages/{}.mo", File::GetExeDirectory(), lang)
- #elif defined __APPLE__
- fmt::format("{}/Contents/Resources/{}.lproj/dolphin-emu.mo", File::GetBundleDirectory(),
- lang)
- #elif defined LINUX_LOCAL_DEV
- fmt::format("{}/../Source/Core/DolphinQt/{}/dolphin-emu.mo", File::GetExeDirectory(), lang)
- #else
- fmt::format("{}/../locale/{}/LC_MESSAGES/dolphin-emu.mo", DATA_DIR, lang)
- #endif
- ;
- auto* translator = new MoTranslator(QApplication::instance());
- if (translator->load(filename))
- {
- QApplication::instance()->installTranslator(translator);
- QLocale::setDefault(QLocale(exact_language_code));
- UICommon::SetLocale(exact_language_code.toStdString());
- return true;
- }
- translator->deleteLater();
- }
- ERROR_LOG_FMT(COMMON, "No suitable translation file found");
- return false;
- }
- void Translation::Initialize()
- {
- // Hook up Dolphin internal translation
- Common::RegisterStringTranslator(
- [](const char* text) { return QObject::tr(text).toStdString(); });
- // Hook up Qt translations
- std::string configured_language = Config::Get(Config::MAIN_INTERFACE_LANGUAGE);
- if (!configured_language.empty())
- {
- if (TryInstallTranslator(QString::fromStdString(configured_language)))
- return;
- ModalMessageBox::warning(
- nullptr, QObject::tr("Error"),
- QObject::tr("Error loading selected language. Falling back to system default."));
- Config::SetBase(Config::MAIN_INTERFACE_LANGUAGE, "");
- }
- for (const auto& lang : QLocale::system().uiLanguages())
- {
- if (TryInstallTranslator(lang))
- break;
- }
- }
|