translator.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. let translator_translations: map_t<string, string> = map_create();
  2. // The font index is a value specific to font_cjk.ttc
  3. let translator_cjk_font_indices: map_t<string, i32> = null;
  4. let translator_last_locale: string = "en";
  5. // Mark strings as localizable in order to be parsed by the extract_locale script
  6. // The string will not be translated to the currently selected locale though
  7. function _tr(s: string) {
  8. return s;
  9. }
  10. // Localizes a string with the given placeholders replaced (format is "{placeholder_name}")
  11. // If the string isn't available in the translation, this method will return the source English string
  12. function tr(id: string, vars: map_t<string, string> = null): string {
  13. let translation: string = id;
  14. // English is the source language
  15. if (config_raw.locale != "en" && map_get(translator_translations, id) != null) {
  16. translation = map_get(translator_translations, id);
  17. }
  18. if (vars != null) {
  19. let keys: string[] = map_keys(vars);
  20. for (let i: i32 = 0; i < keys.length; ++i) {
  21. translation = string_replace_all(translation, "{" + keys[i] + "}", any_to_string(map_get(vars, keys[i])));
  22. }
  23. }
  24. return translation;
  25. }
  26. // (Re)loads translations for the specified locale
  27. function translator_load_translations(new_locale: string) {
  28. if (translator_cjk_font_indices == null) {
  29. translator_cjk_font_indices = map_create();
  30. map_set(translator_cjk_font_indices, "ja", 0);
  31. map_set(translator_cjk_font_indices, "ko", 1);
  32. map_set(translator_cjk_font_indices, "zh_cn", 2);
  33. map_set(translator_cjk_font_indices, "zh_tw", 3);
  34. map_set(translator_cjk_font_indices, "zh_tw.big5", 4);
  35. }
  36. if (new_locale == "system") {
  37. config_raw.locale = krom_language();
  38. }
  39. // Check whether the requested or detected locale is available
  40. if (config_raw.locale != "en" && array_index_of(translator_get_supported_locales(), config_raw.locale) == -1) {
  41. // Fall back to English
  42. config_raw.locale = "en";
  43. }
  44. // No translations to load, as source strings are in English
  45. // Clear existing translations if switching languages at runtime
  46. translator_translations.clear();
  47. if (config_raw.locale == "en" && translator_last_locale == "en") {
  48. // No need to generate extended font atlas for English locale
  49. return;
  50. }
  51. translator_last_locale = config_raw.locale;
  52. if (config_raw.locale != "en") {
  53. // Load the translation file
  54. let translation_json: string = sys_buffer_to_string(krom_load_blob("data/locale/" + config_raw.locale + ".json"));
  55. let data: map_t<string, string> = json_parse_to_map(translation_json);
  56. let keys: string[] = map_keys(data);
  57. for (let i: i32 = 0; i < keys.length; ++i) {
  58. let field: string = keys[i];
  59. map_set(translator_translations, field, map_get(data, field));
  60. }
  61. }
  62. // Generate extended font atlas
  63. translator_extended_glyphs();
  64. // Push additional char codes contained in translation file
  65. let cjk: bool = false;
  66. let keys: string[] = map_keys(translator_translations);
  67. for (let i: i32 = 0; i < keys.length; ++i) {
  68. let s: string = map_get(translator_translations, keys[i]);
  69. for (let i: i32 = 0; i < s.length; ++i) {
  70. // Assume cjk in the > 1119 range for now
  71. if (char_code_at(s, i) > 1119 && array_index_of(_g2_font_glyphs, char_code_at(s, i)) == -1) {
  72. if (!cjk) {
  73. _g2_font_glyphs = _g2_make_glyphs(32, 127);
  74. cjk = true;
  75. }
  76. array_push(_g2_font_glyphs, char_code_at(s, i));
  77. }
  78. }
  79. }
  80. if (cjk) {
  81. let cjk_font_path: string = (path_is_protected() ? krom_save_path() : "") + "font_cjk.ttc";
  82. let cjk_font_disk_path: string = (path_is_protected() ? krom_save_path() : path_data() + path_sep) + "font_cjk.ttc";
  83. if (!file_exists(cjk_font_disk_path)) {
  84. file_download("https://github.com/armory3d/armorbase/raw/main/Assets/common/extra/font_cjk.ttc", cjk_font_disk_path, function () {
  85. let cjk_font_path: string = (path_is_protected() ? krom_save_path() : "") + "font_cjk.ttc";
  86. let cjk_font_disk_path: string = (path_is_protected() ? krom_save_path() : path_data() + path_sep) + "font_cjk.ttc";
  87. if (!file_exists(cjk_font_disk_path)) {
  88. // Fall back to English
  89. config_raw.locale = "en";
  90. translator_extended_glyphs();
  91. translator_translations.clear();
  92. translator_init_font(false, "font.ttf", 1.0);
  93. }
  94. else {
  95. translator_init_font(true, cjk_font_path, 1.4);
  96. }
  97. }, 20332392);
  98. }
  99. else {
  100. translator_init_font(true, cjk_font_path, 1.4);
  101. }
  102. }
  103. else {
  104. translator_init_font(false, "font.ttf", 1.0);
  105. }
  106. }
  107. let _translator_init_font_cjk: bool;
  108. let _translator_init_font_font_path: string;
  109. let _translator_init_font_font_scale: f32;
  110. function translator_init_font(cjk: bool, font_path: string, font_scale: f32) {
  111. array_sort(_g2_font_glyphs, function (a: i32, b: i32) {
  112. return a - b;
  113. });
  114. _translator_init_font_cjk = cjk;
  115. _translator_init_font_font_path = font_path;
  116. _translator_init_font_font_scale = font_scale;
  117. // Load and assign font with cjk characters
  118. app_notify_on_init(function () {
  119. let cjk: bool = _translator_init_font_cjk;
  120. let font_path: string = _translator_init_font_font_path;
  121. let font_scale: f32 = _translator_init_font_font_scale;
  122. let f: g2_font_t = data_get_font(font_path);
  123. if (cjk) {
  124. let font_index: i32 = map_get(translator_cjk_font_indices, config_raw.locale) != null ? map_get(translator_cjk_font_indices, config_raw.locale) : 0;
  125. g2_font_set_font_index(f, font_index);
  126. }
  127. base_font = f;
  128. // Scale up the font size and elements width a bit
  129. base_theme.FONT_SIZE = math_floor(base_default_font_size * font_scale);
  130. base_theme.ELEMENT_W = math_floor(base_default_element_w * (config_raw.locale != "en" ? 1.4 : 1.0));
  131. let uis: zui_t[] = base_get_uis();
  132. for (let i: i32 = 0; i < uis.length; ++i) {
  133. let ui: zui_t = uis[i];
  134. zui_set_font(ui, f);
  135. zui_set_scale(ui, zui_SCALE(ui));
  136. }
  137. });
  138. }
  139. function translator_extended_glyphs() {
  140. // Basic Latin + Latin-1 Supplement + Latin Extended-A
  141. _g2_font_glyphs = _g2_make_glyphs(32, 383);
  142. // + Greek
  143. for (let i: i32 = 880; i < 1023; ++i) {
  144. array_push(_g2_font_glyphs, i);
  145. }
  146. // + Cyrillic
  147. for (let i: i32 = 1024; i < 1119; ++i) {
  148. array_push(_g2_font_glyphs, i);
  149. }
  150. }
  151. // Returns a list of supported locales (plus English and the automatically detected system locale)
  152. function translator_get_supported_locales(): string[] {
  153. let locales: string[] = ["system", "en"];
  154. for (let locale_filename of file_read_directory(path_data() + path_sep + "locale")) {
  155. // Trim the ".json" file extension from file names
  156. array_push(locales, substring(locale_filename, 0, locale_filename.length - 5));
  157. }
  158. return locales;
  159. }