Translator.hx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. package arm;
  2. import haxe.io.Bytes;
  3. import haxe.Json;
  4. import arm.sys.File;
  5. import arm.sys.Path;
  6. class Translator {
  7. static var translations: Map<String, String> = [];
  8. // The font index is a value specific to font_cjk.ttc.
  9. static var cjkFontIndices: Map<String, Int> = [
  10. "ja" => 0,
  11. "ko" => 1,
  12. "zh_cn" => 2,
  13. "zh_tw" => 3,
  14. "zh_tw.big5" => 4
  15. ];
  16. static var lastLocale = "en";
  17. // Mark strings as localizable in order to be parsed by the extract_locale script.
  18. // The string will not be translated to the currently selected locale though.
  19. public static inline function _tr(s: String) {
  20. return s;
  21. }
  22. // Localizes a string with the given placeholders replaced (format is `{placeholderName}`).
  23. // If the string isn't available in the translation, this method will return the source English string.
  24. public static function tr(id: String, vars: Map<String, String> = null): String {
  25. var translation = id;
  26. // English is the source language
  27. if (Config.raw.locale != "en" && translations.exists(id)) {
  28. translation = translations[id];
  29. }
  30. if (vars != null) {
  31. for (key => value in vars) {
  32. translation = translation.replace('{$key}', Std.string(value));
  33. }
  34. }
  35. return translation;
  36. }
  37. // (Re)loads translations for the specified locale
  38. public static function loadTranslations(newLocale: String) {
  39. if (newLocale == "system") {
  40. Config.raw.locale = Krom.language();
  41. }
  42. // Check whether the requested or detected locale is available
  43. if (Config.raw.locale != "en" && getSupportedLocales().indexOf(Config.raw.locale) == -1) {
  44. // Fall back to English
  45. Config.raw.locale = "en";
  46. }
  47. // No translations to load, as source strings are in English
  48. // Clear existing translations if switching languages at runtime
  49. translations.clear();
  50. if (Config.raw.locale == "en" && lastLocale == "en") {
  51. // No need to generate extended font atlas for English locale
  52. return;
  53. }
  54. lastLocale = Config.raw.locale;
  55. if (Config.raw.locale != "en") {
  56. // Load the translation file
  57. var translationJson = Bytes.ofData(Krom.loadBlob('data/locale/${Config.raw.locale}.json')).toString();
  58. var data: haxe.DynamicAccess<String> = Json.parse(translationJson);
  59. for (key => value in data) {
  60. translations[Std.string(key)] = value;
  61. }
  62. }
  63. // Generate extended font atlas
  64. extendedGlyphs();
  65. // Push additional char codes contained in translation file
  66. var cjk = false;
  67. for (s in translations) {
  68. for (i in 0...s.length) {
  69. // Assume cjk in the > 1119 range for now
  70. if (s.charCodeAt(i) > 1119 && kha.graphics2.Graphics.fontGlyphs.indexOf(s.charCodeAt(i)) == -1) {
  71. if (!cjk) {
  72. kha.graphics2.Graphics.fontGlyphs = [for (i in 32...127) i];
  73. cjk = true;
  74. }
  75. kha.graphics2.Graphics.fontGlyphs.push(s.charCodeAt(i));
  76. }
  77. }
  78. }
  79. var newFont = { path: "font.ttf", scale: 1.0 };
  80. if (cjk) {
  81. newFont = { path: (Path.isProtected() ? Krom.savePath() : "") + "font_cjk.ttc", scale: 1.4 };
  82. var cjkFontPath = (Path.isProtected() ? Krom.savePath() : Path.data() + Path.sep) + "font_cjk.ttc";
  83. if (!File.exists(cjkFontPath)) {
  84. File.download("https://github.com/armory3d/armorbase/raw/main/Assets/common/extra/font_cjk.ttc", cjkFontPath, function() {
  85. if (!File.exists(cjkFontPath)) {
  86. // Fall back to English
  87. Config.raw.locale = "en";
  88. extendedGlyphs();
  89. translations.clear();
  90. newFont = { path: "font.ttf", scale: 1.0 };
  91. initFont(false, newFont);
  92. }
  93. else initFont(true, newFont);
  94. }, 20332392);
  95. }
  96. else initFont(true, newFont);
  97. }
  98. else initFont(false, newFont);
  99. }
  100. static function initFont(cjk: Bool, newFont: Dynamic) {
  101. kha.graphics2.Graphics.fontGlyphs.sort(Reflect.compare);
  102. // Load and assign font with cjk characters
  103. iron.App.notifyOnInit(function() {
  104. iron.data.Data.getFont(newFont.path, function(f: kha.Font) {
  105. if (cjk) {
  106. var fontIndex = cjkFontIndices.exists(Config.raw.locale) ? cjkFontIndices[Config.raw.locale] : 0;
  107. f.setFontIndex(fontIndex);
  108. }
  109. App.font = f;
  110. // Scale up the font size and elements width a bit
  111. App.theme.FONT_SIZE = Std.int(App.defaultFontSize * newFont.scale);
  112. App.theme.ELEMENT_W = Std.int(App.defaultElementW * (Config.raw.locale != "en" ? 1.4 : 1.0));
  113. var uis = App.getUIs();
  114. for (ui in uis) {
  115. ui.ops.font = f;
  116. ui.setScale(ui.ops.scaleFactor);
  117. }
  118. });
  119. });
  120. }
  121. static function extendedGlyphs() {
  122. // Basic Latin + Latin-1 Supplement + Latin Extended-A
  123. kha.graphics2.Graphics.fontGlyphs = [for (i in 32...383) i];
  124. // + Greek
  125. for (i in 880...1023) kha.graphics2.Graphics.fontGlyphs.push(i);
  126. // + Cyrillic
  127. for (i in 1024...1119) kha.graphics2.Graphics.fontGlyphs.push(i);
  128. }
  129. // Returns a list of supported locales (plus English and the automatically detected system locale)
  130. public static function getSupportedLocales(): Array<String> {
  131. var locales = ["system", "en"];
  132. for (localeFilename in File.readDirectory(Path.data() + Path.sep + "locale")) {
  133. // Trim the `.json` file extension from file names
  134. locales.push(localeFilename.substr(0, -5));
  135. }
  136. return locales;
  137. }
  138. }