mirror of
				https://github.com/dolphin-emu/dolphin.git
				synced 2025-10-27 10:29:07 +00:00 
			
		
		
		
	The inequality operator is automatically generated by the compiler if `operator==` is defined.
		
			
				
	
	
		
			331 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			331 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // 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;
 | |
|   }
 | |
| }
 |