forked from dolphin-emu/dolphin
		
	
		
			
				
	
	
		
			316 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			316 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
// Copyright 2017 Dolphin Emulator Project
 | 
						|
// Licensed under GPLv2+
 | 
						|
// Refer to the license.txt file included.
 | 
						|
 | 
						|
#include "DolphinQt/Translation.h"
 | 
						|
 | 
						|
#include <QApplication>
 | 
						|
#include <QLocale>
 | 
						|
#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"
 | 
						|
 | 
						|
#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; }
 | 
						|
  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)
 | 
						|
    {
 | 
						|
      WARN_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 (iter == end || 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;
 | 
						|
};
 | 
						|
 | 
						|
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
 | 
						|
        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())
 | 
						|
#elif defined LINUX_LOCAL_DEV
 | 
						|
        File::GetExeDirectory() +
 | 
						|
        StringFromFormat("/../Source/Core/DolphinQt/%s/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);
 | 
						|
 | 
						|
      QLocale::setDefault(QLocale(exact_language_code));
 | 
						|
      UICommon::SetLocale(exact_language_code.toStdString());
 | 
						|
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
    translator->deleteLater();
 | 
						|
  }
 | 
						|
  ERROR_LOG(COMMON, "No suitable translation file found");
 | 
						|
  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;
 | 
						|
 | 
						|
    ModalMessageBox::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;
 | 
						|
  }
 | 
						|
}
 |