translation_domain.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. /**************************************************************************/
  2. /* translation_domain.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #include "translation_domain.h"
  31. #include "core/string/translation.h"
  32. #include "core/string/translation_server.h"
  33. struct _character_accent_pair {
  34. const char32_t character;
  35. const char32_t *accented_character;
  36. };
  37. static _character_accent_pair _character_to_accented[] = {
  38. { 'A', U"Å" },
  39. { 'B', U"ß" },
  40. { 'C', U"Ç" },
  41. { 'D', U"Ð" },
  42. { 'E', U"É" },
  43. { 'F', U"F́" },
  44. { 'G', U"Ĝ" },
  45. { 'H', U"Ĥ" },
  46. { 'I', U"Ĩ" },
  47. { 'J', U"Ĵ" },
  48. { 'K', U"ĸ" },
  49. { 'L', U"Ł" },
  50. { 'M', U"Ḿ" },
  51. { 'N', U"й" },
  52. { 'O', U"Ö" },
  53. { 'P', U"Ṕ" },
  54. { 'Q', U"Q́" },
  55. { 'R', U"Ř" },
  56. { 'S', U"Ŝ" },
  57. { 'T', U"Ŧ" },
  58. { 'U', U"Ũ" },
  59. { 'V', U"Ṽ" },
  60. { 'W', U"Ŵ" },
  61. { 'X', U"X́" },
  62. { 'Y', U"Ÿ" },
  63. { 'Z', U"Ž" },
  64. { 'a', U"á" },
  65. { 'b', U"ḅ" },
  66. { 'c', U"ć" },
  67. { 'd', U"d́" },
  68. { 'e', U"é" },
  69. { 'f', U"f́" },
  70. { 'g', U"ǵ" },
  71. { 'h', U"h̀" },
  72. { 'i', U"í" },
  73. { 'j', U"ǰ" },
  74. { 'k', U"ḱ" },
  75. { 'l', U"ł" },
  76. { 'm', U"m̀" },
  77. { 'n', U"ή" },
  78. { 'o', U"ô" },
  79. { 'p', U"ṕ" },
  80. { 'q', U"q́" },
  81. { 'r', U"ŕ" },
  82. { 's', U"š" },
  83. { 't', U"ŧ" },
  84. { 'u', U"ü" },
  85. { 'v', U"ṽ" },
  86. { 'w', U"ŵ" },
  87. { 'x', U"x́" },
  88. { 'y', U"ý" },
  89. { 'z', U"ź" },
  90. };
  91. String TranslationDomain::_get_override_string(const String &p_message) const {
  92. String res;
  93. for (int i = 0; i < p_message.length(); i++) {
  94. if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) {
  95. res += p_message[i];
  96. res += p_message[i + 1];
  97. i++;
  98. continue;
  99. }
  100. res += '*';
  101. }
  102. return res;
  103. }
  104. String TranslationDomain::_double_vowels(const String &p_message) const {
  105. String res;
  106. for (int i = 0; i < p_message.length(); i++) {
  107. if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) {
  108. res += p_message[i];
  109. res += p_message[i + 1];
  110. i++;
  111. continue;
  112. }
  113. res += p_message[i];
  114. if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' ||
  115. p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') {
  116. res += p_message[i];
  117. }
  118. }
  119. return res;
  120. }
  121. String TranslationDomain::_replace_with_accented_string(const String &p_message) const {
  122. String res;
  123. for (int i = 0; i < p_message.length(); i++) {
  124. if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) {
  125. res += p_message[i];
  126. res += p_message[i + 1];
  127. i++;
  128. continue;
  129. }
  130. const char32_t *accented = _get_accented_version(p_message[i]);
  131. if (accented) {
  132. res += accented;
  133. } else {
  134. res += p_message[i];
  135. }
  136. }
  137. return res;
  138. }
  139. String TranslationDomain::_wrap_with_fakebidi_characters(const String &p_message) const {
  140. String res;
  141. char32_t fakebidiprefix = U'\u202e';
  142. char32_t fakebidisuffix = U'\u202c';
  143. res += fakebidiprefix;
  144. // The fake bidi unicode gets popped at every newline so pushing it back at every newline.
  145. for (int i = 0; i < p_message.length(); i++) {
  146. if (p_message[i] == '\n') {
  147. res += fakebidisuffix;
  148. res += p_message[i];
  149. res += fakebidiprefix;
  150. } else if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) {
  151. res += fakebidisuffix;
  152. res += p_message[i];
  153. res += p_message[i + 1];
  154. res += fakebidiprefix;
  155. i++;
  156. } else {
  157. res += p_message[i];
  158. }
  159. }
  160. res += fakebidisuffix;
  161. return res;
  162. }
  163. String TranslationDomain::_add_padding(const String &p_message, int p_length) const {
  164. String underscores = String("_").repeat(p_length * pseudolocalization.expansion_ratio / 2);
  165. String prefix = pseudolocalization.prefix + underscores;
  166. String suffix = underscores + pseudolocalization.suffix;
  167. return prefix + p_message + suffix;
  168. }
  169. const char32_t *TranslationDomain::_get_accented_version(char32_t p_character) const {
  170. if (!is_ascii_alphabet_char(p_character)) {
  171. return nullptr;
  172. }
  173. for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) {
  174. if (_character_to_accented[i].character == p_character) {
  175. return _character_to_accented[i].accented_character;
  176. }
  177. }
  178. return nullptr;
  179. }
  180. bool TranslationDomain::_is_placeholder(const String &p_message, int p_index) const {
  181. return p_index < p_message.length() - 1 && p_message[p_index] == '%' &&
  182. (p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' ||
  183. p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f');
  184. }
  185. StringName TranslationDomain::get_message_from_translations(const String &p_locale, const StringName &p_message, const StringName &p_context) const {
  186. StringName res;
  187. int best_score = 0;
  188. for (const Ref<Translation> &E : translations) {
  189. ERR_CONTINUE(E.is_null());
  190. int score = TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale());
  191. if (score > 0 && score >= best_score) {
  192. const StringName r = E->get_message(p_message, p_context);
  193. if (!r) {
  194. continue;
  195. }
  196. res = r;
  197. best_score = score;
  198. if (score == 10) {
  199. break; // Exact match, skip the rest.
  200. }
  201. }
  202. }
  203. return res;
  204. }
  205. StringName TranslationDomain::get_message_from_translations(const String &p_locale, const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
  206. StringName res;
  207. int best_score = 0;
  208. for (const Ref<Translation> &E : translations) {
  209. ERR_CONTINUE(E.is_null());
  210. int score = TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale());
  211. if (score > 0 && score >= best_score) {
  212. const StringName r = E->get_plural_message(p_message, p_message_plural, p_n, p_context);
  213. if (!r) {
  214. continue;
  215. }
  216. res = r;
  217. best_score = score;
  218. if (score == 10) {
  219. break; // Exact match, skip the rest.
  220. }
  221. }
  222. }
  223. return res;
  224. }
  225. PackedStringArray TranslationDomain::get_loaded_locales() const {
  226. PackedStringArray locales;
  227. for (const Ref<Translation> &E : translations) {
  228. ERR_CONTINUE(E.is_null());
  229. const String &locale = E->get_locale();
  230. if (!locales.has(locale)) {
  231. locales.push_back(locale);
  232. }
  233. }
  234. return locales;
  235. }
  236. Ref<Translation> TranslationDomain::get_translation_object(const String &p_locale) const {
  237. Ref<Translation> res;
  238. int best_score = 0;
  239. for (const Ref<Translation> &E : translations) {
  240. ERR_CONTINUE(E.is_null());
  241. int score = TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale());
  242. if (score > 0 && score >= best_score) {
  243. res = E;
  244. best_score = score;
  245. if (score == 10) {
  246. break; // Exact match, skip the rest.
  247. }
  248. }
  249. }
  250. return res;
  251. }
  252. void TranslationDomain::add_translation(const Ref<Translation> &p_translation) {
  253. translations.insert(p_translation);
  254. }
  255. void TranslationDomain::remove_translation(const Ref<Translation> &p_translation) {
  256. translations.erase(p_translation);
  257. }
  258. void TranslationDomain::clear() {
  259. translations.clear();
  260. }
  261. StringName TranslationDomain::translate(const StringName &p_message, const StringName &p_context) const {
  262. const String &locale = TranslationServer::get_singleton()->get_locale();
  263. StringName res = get_message_from_translations(locale, p_message, p_context);
  264. const String &fallback = TranslationServer::get_singleton()->get_fallback_locale();
  265. if (!res && fallback.length() >= 2) {
  266. res = get_message_from_translations(fallback, p_message, p_context);
  267. }
  268. if (!res) {
  269. return pseudolocalization.enabled ? pseudolocalize(p_message) : p_message;
  270. }
  271. return pseudolocalization.enabled ? pseudolocalize(res) : res;
  272. }
  273. StringName TranslationDomain::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
  274. const String &locale = TranslationServer::get_singleton()->get_locale();
  275. StringName res = get_message_from_translations(locale, p_message, p_message_plural, p_n, p_context);
  276. const String &fallback = TranslationServer::get_singleton()->get_fallback_locale();
  277. if (!res && fallback.length() >= 2) {
  278. res = get_message_from_translations(fallback, p_message, p_message_plural, p_n, p_context);
  279. }
  280. if (!res) {
  281. if (p_n == 1) {
  282. return p_message;
  283. }
  284. return p_message_plural;
  285. }
  286. return res;
  287. }
  288. bool TranslationDomain::is_pseudolocalization_enabled() const {
  289. return pseudolocalization.enabled;
  290. }
  291. void TranslationDomain::set_pseudolocalization_enabled(bool p_enabled) {
  292. pseudolocalization.enabled = p_enabled;
  293. }
  294. bool TranslationDomain::is_pseudolocalization_accents_enabled() const {
  295. return pseudolocalization.accents_enabled;
  296. }
  297. void TranslationDomain::set_pseudolocalization_accents_enabled(bool p_enabled) {
  298. pseudolocalization.accents_enabled = p_enabled;
  299. }
  300. bool TranslationDomain::is_pseudolocalization_double_vowels_enabled() const {
  301. return pseudolocalization.double_vowels_enabled;
  302. }
  303. void TranslationDomain::set_pseudolocalization_double_vowels_enabled(bool p_enabled) {
  304. pseudolocalization.double_vowels_enabled = p_enabled;
  305. }
  306. bool TranslationDomain::is_pseudolocalization_fake_bidi_enabled() const {
  307. return pseudolocalization.fake_bidi_enabled;
  308. }
  309. void TranslationDomain::set_pseudolocalization_fake_bidi_enabled(bool p_enabled) {
  310. pseudolocalization.fake_bidi_enabled = p_enabled;
  311. }
  312. bool TranslationDomain::is_pseudolocalization_override_enabled() const {
  313. return pseudolocalization.override_enabled;
  314. }
  315. void TranslationDomain::set_pseudolocalization_override_enabled(bool p_enabled) {
  316. pseudolocalization.override_enabled = p_enabled;
  317. }
  318. bool TranslationDomain::is_pseudolocalization_skip_placeholders_enabled() const {
  319. return pseudolocalization.skip_placeholders_enabled;
  320. }
  321. void TranslationDomain::set_pseudolocalization_skip_placeholders_enabled(bool p_enabled) {
  322. pseudolocalization.skip_placeholders_enabled = p_enabled;
  323. }
  324. float TranslationDomain::get_pseudolocalization_expansion_ratio() const {
  325. return pseudolocalization.expansion_ratio;
  326. }
  327. void TranslationDomain::set_pseudolocalization_expansion_ratio(float p_ratio) {
  328. pseudolocalization.expansion_ratio = p_ratio;
  329. }
  330. String TranslationDomain::get_pseudolocalization_prefix() const {
  331. return pseudolocalization.prefix;
  332. }
  333. void TranslationDomain::set_pseudolocalization_prefix(const String &p_prefix) {
  334. pseudolocalization.prefix = p_prefix;
  335. }
  336. String TranslationDomain::get_pseudolocalization_suffix() const {
  337. return pseudolocalization.suffix;
  338. }
  339. void TranslationDomain::set_pseudolocalization_suffix(const String &p_suffix) {
  340. pseudolocalization.suffix = p_suffix;
  341. }
  342. StringName TranslationDomain::pseudolocalize(const StringName &p_message) const {
  343. if (p_message.is_empty()) {
  344. return p_message;
  345. }
  346. String message = p_message;
  347. int length = message.length();
  348. if (pseudolocalization.override_enabled) {
  349. message = _get_override_string(message);
  350. }
  351. if (pseudolocalization.double_vowels_enabled) {
  352. message = _double_vowels(message);
  353. }
  354. if (pseudolocalization.accents_enabled) {
  355. message = _replace_with_accented_string(message);
  356. }
  357. if (pseudolocalization.fake_bidi_enabled) {
  358. message = _wrap_with_fakebidi_characters(message);
  359. }
  360. return _add_padding(message, length);
  361. }
  362. void TranslationDomain::_bind_methods() {
  363. ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationDomain::get_translation_object);
  364. ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationDomain::add_translation);
  365. ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationDomain::remove_translation);
  366. ClassDB::bind_method(D_METHOD("clear"), &TranslationDomain::clear);
  367. ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationDomain::translate, DEFVAL(StringName()));
  368. ClassDB::bind_method(D_METHOD("translate_plural", "message", "message_plural", "n", "context"), &TranslationDomain::translate_plural, DEFVAL(StringName()));
  369. ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationDomain::is_pseudolocalization_enabled);
  370. ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_enabled);
  371. ClassDB::bind_method(D_METHOD("is_pseudolocalization_accents_enabled"), &TranslationDomain::is_pseudolocalization_accents_enabled);
  372. ClassDB::bind_method(D_METHOD("set_pseudolocalization_accents_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_accents_enabled);
  373. ClassDB::bind_method(D_METHOD("is_pseudolocalization_double_vowels_enabled"), &TranslationDomain::is_pseudolocalization_double_vowels_enabled);
  374. ClassDB::bind_method(D_METHOD("set_pseudolocalization_double_vowels_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_double_vowels_enabled);
  375. ClassDB::bind_method(D_METHOD("is_pseudolocalization_fake_bidi_enabled"), &TranslationDomain::is_pseudolocalization_fake_bidi_enabled);
  376. ClassDB::bind_method(D_METHOD("set_pseudolocalization_fake_bidi_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_fake_bidi_enabled);
  377. ClassDB::bind_method(D_METHOD("is_pseudolocalization_override_enabled"), &TranslationDomain::is_pseudolocalization_override_enabled);
  378. ClassDB::bind_method(D_METHOD("set_pseudolocalization_override_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_override_enabled);
  379. ClassDB::bind_method(D_METHOD("is_pseudolocalization_skip_placeholders_enabled"), &TranslationDomain::is_pseudolocalization_skip_placeholders_enabled);
  380. ClassDB::bind_method(D_METHOD("set_pseudolocalization_skip_placeholders_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_skip_placeholders_enabled);
  381. ClassDB::bind_method(D_METHOD("get_pseudolocalization_expansion_ratio"), &TranslationDomain::get_pseudolocalization_expansion_ratio);
  382. ClassDB::bind_method(D_METHOD("set_pseudolocalization_expansion_ratio", "ratio"), &TranslationDomain::set_pseudolocalization_expansion_ratio);
  383. ClassDB::bind_method(D_METHOD("get_pseudolocalization_prefix"), &TranslationDomain::get_pseudolocalization_prefix);
  384. ClassDB::bind_method(D_METHOD("set_pseudolocalization_prefix", "prefix"), &TranslationDomain::set_pseudolocalization_prefix);
  385. ClassDB::bind_method(D_METHOD("get_pseudolocalization_suffix"), &TranslationDomain::get_pseudolocalization_suffix);
  386. ClassDB::bind_method(D_METHOD("set_pseudolocalization_suffix", "suffix"), &TranslationDomain::set_pseudolocalization_suffix);
  387. ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationDomain::pseudolocalize);
  388. ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled");
  389. ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_accents_enabled"), "set_pseudolocalization_accents_enabled", "is_pseudolocalization_accents_enabled");
  390. ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_double_vowels_enabled"), "set_pseudolocalization_double_vowels_enabled", "is_pseudolocalization_double_vowels_enabled");
  391. ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_fake_bidi_enabled"), "set_pseudolocalization_fake_bidi_enabled", "is_pseudolocalization_fake_bidi_enabled");
  392. ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_override_enabled"), "set_pseudolocalization_override_enabled", "is_pseudolocalization_override_enabled");
  393. ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_skip_placeholders_enabled"), "set_pseudolocalization_skip_placeholders_enabled", "is_pseudolocalization_skip_placeholders_enabled");
  394. ADD_PROPERTY(PropertyInfo(Variant::Type::FLOAT, "pseudolocalization_expansion_ratio"), "set_pseudolocalization_expansion_ratio", "get_pseudolocalization_expansion_ratio");
  395. ADD_PROPERTY(PropertyInfo(Variant::Type::STRING, "pseudolocalization_prefix"), "set_pseudolocalization_prefix", "get_pseudolocalization_prefix");
  396. ADD_PROPERTY(PropertyInfo(Variant::Type::STRING, "pseudolocalization_suffix"), "set_pseudolocalization_suffix", "get_pseudolocalization_suffix");
  397. }
  398. TranslationDomain::TranslationDomain() {
  399. }