Files
armorpaint/paint/sources/translator.c
T

238 lines
8.3 KiB
C
Raw Normal View History

2026-03-06 12:56:38 +01:00
2026-03-09 13:17:15 +01:00
#include "global.h"
2026-04-05 16:09:00 +02:00
// The font index is a value specific to font_cjk.ttc
i32_map_t *translator_cjk_font_indices = NULL;
char *translator_last_locale = "en";
char *_translator_load_translations_cjk_font_path;
char *_translator_load_translations_cjk_font_disk_path;
bool _translator_init_font_cjk;
char *_translator_init_font_font_path;
f32 _translator_init_font_font_scale;
2026-03-06 12:56:38 +01:00
// Mark strings as localizable in order to be parsed by the extract_locale script
// The string will not be translated to the currently selected locale though
2026-03-07 21:12:59 +01:00
char *_tr(char *s) {
2026-03-06 12:56:38 +01:00
return s;
}
// Localizes a string with the given placeholders replaced (format is "{placeholder_name}")
// If the string isn't available in the translation, this method will return the source English string
2026-03-15 11:37:45 +01:00
char *vtr(char *id, any_map_t *vars) {
2026-03-07 21:12:59 +01:00
char *translation = string_copy(id);
2026-03-06 12:56:38 +01:00
// English is the source language
2026-04-05 16:41:00 +02:00
if (!string_equals(g_config->locale, "en")) {
2026-03-06 12:56:38 +01:00
if (string_index_of(id, "\n") > -1) {
id = string_copy(string_replace_all(id, "\n", "\\n"));
}
2026-03-07 21:12:59 +01:00
char *s = any_map_get(translator_translations, id);
if (s != NULL) {
2026-03-06 12:56:38 +01:00
if (string_index_of(s, "\\n") > -1) {
s = string_copy(string_replace_all(s, "\\n", "\n"));
}
translation = string_copy(s);
}
}
2026-03-07 21:12:59 +01:00
if (vars != NULL) {
2026-03-31 18:47:10 +02:00
string_array_t *keys = map_keys(vars);
2026-03-06 12:56:38 +01:00
for (i32 i = 0; i < keys->length; ++i) {
2026-03-10 21:00:00 +01:00
char *search = string("{%s}", keys->buffer[i]);
translation = string_copy(string_replace_all(translation, search, any_map_get(vars, keys->buffer[i])));
2026-03-06 12:56:38 +01:00
}
}
return translation;
}
2026-03-15 11:37:45 +01:00
char *tr(char *id) {
return vtr(id, NULL);
}
2026-04-04 21:38:20 +02:00
void translator_init_font_on_next_frame(void *_) {
bool cjk = _translator_init_font_cjk;
char *font_path = _translator_init_font_font_path;
f32 font_scale = _translator_init_font_font_scale;
draw_font_t *f = data_get_font(font_path);
if (cjk) {
2026-04-21 10:52:27 +02:00
i32 font_index = i32_map_get(translator_cjk_font_indices, g_config->locale) != -1 ? i32_map_get(translator_cjk_font_indices, g_config->locale) : 0;
f->index = font_index;
2026-04-04 21:38:20 +02:00
f->glyphs_version = 0;
draw_font_init(f);
}
2026-06-08 15:35:04 +02:00
gc_unroot(g_font);
g_font = f;
gc_root(g_font);
2026-04-04 21:38:20 +02:00
// Scale up the font size and elements width a bit
2026-06-08 15:35:04 +02:00
g_theme->FONT_SIZE = math_floor(base_default_font_size * font_scale);
g_theme->ELEMENT_W = math_floor(base_default_element_w * (!string_equals(g_config->locale, "en") ? 1.4 : 1.0));
2026-04-04 21:38:20 +02:00
2026-06-08 11:42:44 +02:00
ui_set_font(g_ui, f);
2026-04-04 21:38:20 +02:00
ui_set_scale(UI_SCALE());
}
void translator_init_font(bool cjk, char *font_path, f32 font_scale) {
_translator_init_font_cjk = cjk;
gc_unroot(_translator_init_font_font_path);
_translator_init_font_font_path = string_copy(font_path);
gc_root(_translator_init_font_font_path);
_translator_init_font_font_scale = font_scale;
// Load and assign font with cjk characters
sys_notify_on_next_frame(&translator_init_font_on_next_frame, NULL);
}
void translator_extended_glyphs() {
// Basic Latin + Latin-1 Supplement + Latin Extended-A
draw_font_init_glyphs(32, 383);
// + Greek
for (i32 i = 880; i < 1023; ++i) {
draw_font_add_glyph(i);
}
// + Cyrillic
for (i32 i = 1024; i < 1119; ++i) {
draw_font_add_glyph(i);
}
}
2026-03-09 23:02:08 +01:00
void translator_load_translations_on_cjk_downloaded(char *url) {
if (!iron_file_exists(_translator_load_translations_cjk_font_disk_path)) {
// Fall back to English
2026-04-05 16:41:00 +02:00
g_config->locale = "en";
2026-03-09 23:02:08 +01:00
translator_extended_glyphs();
gc_unroot(translator_translations);
translator_translations = any_map_create();
gc_root(translator_translations);
translator_init_font(false, "font.ttf", 1.0);
}
else {
translator_init_font(true, _translator_load_translations_cjk_font_path, 1.4);
}
}
2026-03-06 12:56:38 +01:00
// (Re)loads translations for the specified locale
2026-03-07 21:12:59 +01:00
void translator_load_translations(char *new_locale) {
2026-03-06 12:56:38 +01:00
2026-03-07 21:12:59 +01:00
if (translator_cjk_font_indices == NULL) {
2026-03-06 12:56:38 +01:00
gc_unroot(translator_cjk_font_indices);
translator_cjk_font_indices = i32_map_create();
gc_root(translator_cjk_font_indices);
i32_map_set(translator_cjk_font_indices, "ja", 0);
i32_map_set(translator_cjk_font_indices, "ko", 1);
i32_map_set(translator_cjk_font_indices, "zh_cn", 2);
i32_map_set(translator_cjk_font_indices, "zh_tw", 3);
i32_map_set(translator_cjk_font_indices, "zh_tw.big5", 4);
}
if (string_equals(new_locale, "system")) {
2026-04-05 16:41:00 +02:00
g_config->locale = string_copy(iron_language());
2026-03-06 12:56:38 +01:00
}
// Check whether the requested or detected locale is available
2026-04-05 16:41:00 +02:00
if (!string_equals(g_config->locale, "en") && string_array_index_of(translator_get_supported_locales(), g_config->locale) == -1) {
2026-03-06 12:56:38 +01:00
// Fall back to English
2026-04-05 16:41:00 +02:00
g_config->locale = "en";
2026-03-06 12:56:38 +01:00
}
// No translations to load, as source strings are in English
// Clear existing translations if switching languages at runtime
gc_unroot(translator_translations);
translator_translations = any_map_create();
gc_root(translator_translations);
2026-04-05 16:41:00 +02:00
if (string_equals(g_config->locale, "en") && string_equals(translator_last_locale, "en")) {
2026-03-06 12:56:38 +01:00
// No need to generate extended font atlas for English locale
return;
}
gc_unroot(translator_last_locale);
2026-04-05 16:41:00 +02:00
translator_last_locale = string_copy(g_config->locale);
2026-03-06 12:56:38 +01:00
gc_root(translator_last_locale);
2026-04-05 16:41:00 +02:00
if (!string_equals(g_config->locale, "en")) {
2026-03-06 12:56:38 +01:00
// Load the translation file
2026-04-05 16:41:00 +02:00
char *translation_json = sys_buffer_to_string(iron_load_blob(string("data/locale/%s.json", g_config->locale)));
2026-03-06 12:56:38 +01:00
gc_unroot(translator_translations);
translator_translations = json_parse_to_map(translation_json);
gc_root(translator_translations);
}
// Generate extended font atlas
translator_extended_glyphs();
// Push additional char codes contained in translation file
2026-04-05 16:09:00 +02:00
bool cjk = false;
2026-03-31 18:47:10 +02:00
string_array_t *keys = map_keys(translator_translations);
2026-03-06 12:56:38 +01:00
for (i32 i = 0; i < keys->length; ++i) {
2026-03-07 21:12:59 +01:00
char *s = any_map_get(translator_translations, keys->buffer[i]);
2026-03-06 12:56:38 +01:00
for (i32 i = 0; char_code_at(s, i) != 0;) {
i32 l = 0;
2026-03-08 08:39:29 +01:00
i32 codepoint = string_utf8_decode((s) + i, &l);
2026-03-06 12:56:38 +01:00
i += l;
// Assume cjk in the > 1119 range
if (codepoint > 1119 && !draw_font_has_glyph(codepoint)) {
cjk = true;
draw_font_add_glyph(codepoint);
}
}
}
if (cjk) {
if (path_is_protected()) {
gc_unroot(_translator_load_translations_cjk_font_path);
_translator_load_translations_cjk_font_path = string_copy(iron_internal_save_path());
gc_root(_translator_load_translations_cjk_font_path);
gc_unroot(_translator_load_translations_cjk_font_disk_path);
_translator_load_translations_cjk_font_disk_path = string_copy(iron_internal_save_path());
gc_root(_translator_load_translations_cjk_font_disk_path);
}
else {
gc_unroot(_translator_load_translations_cjk_font_path);
_translator_load_translations_cjk_font_path = "";
gc_root(_translator_load_translations_cjk_font_path);
gc_unroot(_translator_load_translations_cjk_font_disk_path);
2026-03-10 21:00:00 +01:00
_translator_load_translations_cjk_font_disk_path = string("%s%s", path_data(), PATH_SEP);
2026-03-06 12:56:38 +01:00
gc_root(_translator_load_translations_cjk_font_disk_path);
}
gc_unroot(_translator_load_translations_cjk_font_path);
2026-03-10 21:00:00 +01:00
_translator_load_translations_cjk_font_path = string("%sfont_cjk.ttc", _translator_load_translations_cjk_font_path);
2026-03-06 12:56:38 +01:00
gc_root(_translator_load_translations_cjk_font_path);
gc_unroot(_translator_load_translations_cjk_font_disk_path);
2026-03-10 21:00:00 +01:00
_translator_load_translations_cjk_font_disk_path = string("%sfont_cjk.ttc", _translator_load_translations_cjk_font_disk_path);
2026-03-06 12:56:38 +01:00
gc_root(_translator_load_translations_cjk_font_disk_path);
if (!iron_file_exists(_translator_load_translations_cjk_font_disk_path)) {
file_download_to("https://github.com/armory3d/armorbase/raw/main/Assets/common/extra/font_cjk.ttc",
2026-03-09 23:02:08 +01:00
_translator_load_translations_cjk_font_disk_path, &translator_load_translations_on_cjk_downloaded, 20332392);
2026-03-06 12:56:38 +01:00
}
else {
translator_init_font(true, _translator_load_translations_cjk_font_path, 1.4);
}
}
else {
translator_init_font(false, "font.ttf", 1.0);
}
}
// Returns a list of supported locales (plus English and the automatically detected system locale)
2026-03-31 18:47:10 +02:00
string_array_t *translator_get_supported_locales() {
string_array_t *locales = any_array_create_from_raw(
2026-03-08 08:39:29 +01:00
(void *[]){
2026-03-06 12:56:38 +01:00
"system",
"en",
},
2);
2026-03-31 18:47:10 +02:00
string_array_t *files = file_read_directory(string("%s%slocale", path_data(), PATH_SEP));
2026-03-06 12:56:38 +01:00
for (i32 i = 0; i < files->length; ++i) {
2026-03-07 21:12:59 +01:00
char *locale_filename = files->buffer[i];
2026-03-06 12:56:38 +01:00
// Trim the ".json" file extension from file names
any_array_push(locales, substring(locale_filename, 0, string_length(locale_filename) - 5));
}
return locales;
}