Translation.cpp 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. // Copyright 2017 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include "DolphinQt/Translation.h"
  4. #include <algorithm>
  5. #include <cstring>
  6. #include <iterator>
  7. #include <string>
  8. #include <fmt/format.h>
  9. #include <QApplication>
  10. #include <QLocale>
  11. #include <QTranslator>
  12. #include "Common/FileUtil.h"
  13. #include "Common/IOFile.h"
  14. #include "Common/Logging/Log.h"
  15. #include "Common/MsgHandler.h"
  16. #include "Common/StringUtil.h"
  17. #include "Core/Config/MainSettings.h"
  18. #include "DolphinQt/QtUtils/ModalMessageBox.h"
  19. #include "UICommon/UICommon.h"
  20. constexpr u32 MO_MAGIC_NUMBER = 0x950412de;
  21. static u16 ReadU16(const char* data)
  22. {
  23. u16 value;
  24. std::memcpy(&value, data, sizeof(value));
  25. return value;
  26. }
  27. static u32 ReadU32(const char* data)
  28. {
  29. u32 value;
  30. std::memcpy(&value, data, sizeof(value));
  31. return value;
  32. }
  33. class MoIterator
  34. {
  35. public:
  36. using iterator_category = std::random_access_iterator_tag;
  37. using value_type = const char*;
  38. using difference_type = s64;
  39. using pointer = value_type;
  40. using reference = value_type;
  41. explicit MoIterator(const char* data, u32 table_offset, u32 index = 0)
  42. : m_data{data}, m_table_offset{table_offset}, m_index{index}
  43. {
  44. }
  45. // This is the actual underlying logic of accessing a Mo file. Patterned after the
  46. // boost::iterator_facade library, which nicely separates out application logic from
  47. // iterator-concept logic.
  48. void advance(difference_type n) { m_index += n; }
  49. difference_type distance_to(const MoIterator& other) const
  50. {
  51. return static_cast<difference_type>(other.m_index) - m_index;
  52. }
  53. reference dereference() const
  54. {
  55. u32 offset = ReadU32(&m_data[m_table_offset + m_index * 8 + 4]);
  56. return &m_data[offset];
  57. }
  58. // Needed for Iterator concept
  59. reference operator*() const { return dereference(); }
  60. MoIterator& operator++()
  61. {
  62. advance(1);
  63. return *this;
  64. }
  65. // Needed for InputIterator concept
  66. bool operator==(const MoIterator& other) const { return distance_to(other) == 0; }
  67. pointer operator->() const { return dereference(); }
  68. MoIterator operator++(int)
  69. {
  70. MoIterator tmp(*this);
  71. advance(1);
  72. return tmp;
  73. }
  74. // Needed for BidirectionalIterator concept
  75. MoIterator& operator--()
  76. {
  77. advance(-1);
  78. return *this;
  79. }
  80. MoIterator operator--(int)
  81. {
  82. MoIterator tmp(*this);
  83. advance(-1);
  84. return tmp;
  85. }
  86. // Needed for RandomAccessIterator concept
  87. bool operator<(const MoIterator& other) const { return distance_to(other) > 0; }
  88. bool operator<=(const MoIterator& other) const { return distance_to(other) >= 0; }
  89. bool operator>(const MoIterator& other) const { return distance_to(other) < 0; }
  90. bool operator>=(const MoIterator& other) const { return distance_to(other) <= 0; }
  91. reference operator[](difference_type n) const { return *(*this + n); }
  92. MoIterator& operator+=(difference_type n)
  93. {
  94. advance(n);
  95. return *this;
  96. }
  97. MoIterator& operator-=(difference_type n)
  98. {
  99. advance(-n);
  100. return *this;
  101. }
  102. friend MoIterator operator+(difference_type n, const MoIterator& it) { return it + n; }
  103. friend MoIterator operator+(const MoIterator& it, difference_type n)
  104. {
  105. MoIterator tmp(it);
  106. tmp += n;
  107. return tmp;
  108. }
  109. difference_type operator-(const MoIterator& other) const { return other.distance_to(*this); }
  110. friend MoIterator operator-(difference_type n, const MoIterator& it) { return it - n; }
  111. friend MoIterator operator-(const MoIterator& it, difference_type n)
  112. {
  113. MoIterator tmp(it);
  114. tmp -= n;
  115. return tmp;
  116. }
  117. private:
  118. const char* m_data;
  119. u32 m_table_offset;
  120. u32 m_index;
  121. };
  122. class MoFile
  123. {
  124. public:
  125. MoFile() = default;
  126. explicit MoFile(const std::string& filename)
  127. {
  128. File::IOFile file(filename, "rb");
  129. m_data.resize(file.GetSize());
  130. file.ReadBytes(m_data.data(), m_data.size());
  131. if (!file)
  132. {
  133. WARN_LOG_FMT(COMMON, "Error reading MO file '{}'", filename);
  134. m_data = {};
  135. return;
  136. }
  137. const u32 magic = ReadU32(&m_data[0]);
  138. if (magic != MO_MAGIC_NUMBER)
  139. {
  140. ERROR_LOG_FMT(COMMON, "MO file '{}' has bad magic number {:x}\n", filename, magic);
  141. m_data = {};
  142. return;
  143. }
  144. const u16 version_major = ReadU16(&m_data[4]);
  145. if (version_major > 1)
  146. {
  147. ERROR_LOG_FMT(COMMON, "MO file '{}' has unsupported version number {}", filename,
  148. version_major);
  149. m_data = {};
  150. return;
  151. }
  152. m_number_of_strings = ReadU32(&m_data[8]);
  153. m_offset_original_table = ReadU32(&m_data[12]);
  154. m_offset_translation_table = ReadU32(&m_data[16]);
  155. }
  156. u32 GetNumberOfStrings() const { return m_number_of_strings; }
  157. const char* Translate(const char* original_string) const
  158. {
  159. const MoIterator begin(m_data.data(), m_offset_original_table);
  160. const MoIterator end(m_data.data(), m_offset_original_table, m_number_of_strings);
  161. auto iter = std::lower_bound(begin, end, original_string,
  162. [](const char* a, const char* b) { return strcmp(a, b) < 0; });
  163. if (iter == end || strcmp(*iter, original_string) != 0)
  164. return nullptr;
  165. u32 offset = ReadU32(&m_data[m_offset_translation_table + std::distance(begin, iter) * 8 + 4]);
  166. return &m_data[offset];
  167. }
  168. private:
  169. std::vector<char> m_data;
  170. u32 m_number_of_strings = 0;
  171. u32 m_offset_original_table = 0;
  172. u32 m_offset_translation_table = 0;
  173. };
  174. class MoTranslator : public QTranslator
  175. {
  176. public:
  177. using QTranslator::QTranslator;
  178. bool isEmpty() const override { return m_mo_file.GetNumberOfStrings() == 0; }
  179. bool load(const std::string& filename)
  180. {
  181. m_mo_file = MoFile(filename);
  182. return !isEmpty();
  183. }
  184. QString translate(const char* context, const char* source_text,
  185. const char* disambiguation = nullptr, int n = -1) const override
  186. {
  187. const char* translated_text;
  188. if (disambiguation)
  189. {
  190. std::string combined_string = disambiguation;
  191. combined_string += '\4';
  192. combined_string += source_text;
  193. translated_text = m_mo_file.Translate(combined_string.c_str());
  194. }
  195. else
  196. {
  197. translated_text = m_mo_file.Translate(source_text);
  198. }
  199. return QString::fromUtf8(translated_text ? translated_text : source_text);
  200. }
  201. private:
  202. MoFile m_mo_file;
  203. };
  204. static QStringList FindPossibleLanguageCodes(const QString& exact_language_code)
  205. {
  206. QStringList possible_language_codes;
  207. possible_language_codes << exact_language_code;
  208. // Qt likes to separate language, script, and country by hyphen, but on disk they're separated by
  209. // underscores.
  210. possible_language_codes.replaceInStrings(QStringLiteral("-"), QStringLiteral("_"));
  211. // Try successively dropping subtags (like the stock QTranslator, and as specified by RFC 4647
  212. // "Matching of Language Tags").
  213. // Example: fr_Latn_CA -> fr_Latn -> fr
  214. for (auto lang : QStringList(possible_language_codes))
  215. {
  216. while (lang.contains(QLatin1Char('_')))
  217. {
  218. lang = lang.left(lang.lastIndexOf(QLatin1Char('_')));
  219. possible_language_codes << lang;
  220. }
  221. }
  222. // On macOS, Chinese (Simplified) and Chinese (Traditional) are represented as zh-Hans and
  223. // zh-Hant, but on Linux they're represented as zh-CN and zh-TW. Qt should probably include the
  224. // script subtags on Linux, but it doesn't.
  225. const int hans_index = possible_language_codes.indexOf(QStringLiteral("zh_Hans"));
  226. if (hans_index != -1)
  227. possible_language_codes.insert(hans_index + 1, QStringLiteral("zh_CN"));
  228. const int hant_index = possible_language_codes.indexOf(QStringLiteral("zh_Hant"));
  229. if (hant_index != -1)
  230. possible_language_codes.insert(hant_index + 1, QStringLiteral("zh_TW"));
  231. return possible_language_codes;
  232. }
  233. static bool TryInstallTranslator(const QString& exact_language_code)
  234. {
  235. for (const auto& qlang : FindPossibleLanguageCodes(exact_language_code))
  236. {
  237. std::string lang = qlang.toStdString();
  238. auto filename =
  239. #if defined _WIN32
  240. fmt::format("{}/Languages/{}.mo", File::GetExeDirectory(), lang)
  241. #elif defined __APPLE__
  242. fmt::format("{}/Contents/Resources/{}.lproj/dolphin-emu.mo", File::GetBundleDirectory(),
  243. lang)
  244. #elif defined LINUX_LOCAL_DEV
  245. fmt::format("{}/../Source/Core/DolphinQt/{}/dolphin-emu.mo", File::GetExeDirectory(), lang)
  246. #else
  247. fmt::format("{}/../locale/{}/LC_MESSAGES/dolphin-emu.mo", DATA_DIR, lang)
  248. #endif
  249. ;
  250. auto* translator = new MoTranslator(QApplication::instance());
  251. if (translator->load(filename))
  252. {
  253. QApplication::instance()->installTranslator(translator);
  254. QLocale::setDefault(QLocale(exact_language_code));
  255. UICommon::SetLocale(exact_language_code.toStdString());
  256. return true;
  257. }
  258. translator->deleteLater();
  259. }
  260. ERROR_LOG_FMT(COMMON, "No suitable translation file found");
  261. return false;
  262. }
  263. void Translation::Initialize()
  264. {
  265. // Hook up Dolphin internal translation
  266. Common::RegisterStringTranslator(
  267. [](const char* text) { return QObject::tr(text).toStdString(); });
  268. // Hook up Qt translations
  269. std::string configured_language = Config::Get(Config::MAIN_INTERFACE_LANGUAGE);
  270. if (!configured_language.empty())
  271. {
  272. if (TryInstallTranslator(QString::fromStdString(configured_language)))
  273. return;
  274. ModalMessageBox::warning(
  275. nullptr, QObject::tr("Error"),
  276. QObject::tr("Error loading selected language. Falling back to system default."));
  277. Config::SetBase(Config::MAIN_INTERFACE_LANGUAGE, "");
  278. }
  279. for (const auto& lang : QLocale::system().uiLanguages())
  280. {
  281. if (TryInstallTranslator(lang))
  282. break;
  283. }
  284. }