diff --git a/docs/changes.md b/docs/changes.md index accc494d8..140f8a18e 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -1,5 +1,8 @@ # 最新动态 +2025/04/09 + * 添加可从xml中解析本地化信息的locale_info,完善预览程序可从strings.xml获取翻译文本(感谢培煌提供补丁) + 2025/04/08 * 增加 rectf_intersect (感谢智明提供补丁) * 修复nanovg的脏矩形获取不正常的问题(感谢智明提供补丁) diff --git a/src/awtk_base.h b/src/awtk_base.h index 3cf999752..1db81d89a 100644 --- a/src/awtk_base.h +++ b/src/awtk_base.h @@ -55,6 +55,7 @@ #include "base/lcd_profile.h" #include "base/line_break.h" #include "base/locale_info.h" +#include "base/locale_info_xml.h" #include "base/main_loop.h" #include "base/pixel.h" #include "base/pixel_pack_unpack.h" diff --git a/src/base/locale_info_xml.c b/src/base/locale_info_xml.c new file mode 100644 index 000000000..21d1c149b --- /dev/null +++ b/src/base/locale_info_xml.c @@ -0,0 +1,223 @@ +/** + * File: locale_info_xml.c + * Author: AWTK Develop Team + * Brief: locale_info_xml + * + * Copyright (c) 2018 - 2025 Guangzhou ZHIYUAN Electronics Co.,Ltd. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * License file for more details. + * + */ + +/** + * History: + * ================================================================ + * 2025-04-08 Li PeiHuang created + * + */ + +#include "tkc/fs.h" +#include "base/locale_info_xml.h" +#include "base/assets_manager.h" +#include "base/locale_info.h" + +static const char* locale_info_xml_get_language_str(locale_info_t* locale_info) { + locale_info_xml_t* xml_info = (locale_info_xml_t*)locale_info; + if (xml_info == NULL) { + return NULL; + } + + str_clear(xml_info->language); + str_set(xml_info->language, locale_info->language); + str_append(xml_info->language, "_"); + str_append(xml_info->language, locale_info->country); + + return xml_info->language->str; +} + +static int32_t locale_info_xml_get_node_size(locale_info_xml_t* xml_info, conf_node_t* node) { + if (node == NULL) { // node == NULL, 则获取root节点的size + return conf_doc_get_int(xml_info->doc, "root.#size", -1); + } else { + return conf_node_count_children(node); + } +} + +static conf_node_t* locale_info_xml_get_node_by_text(locale_info_t* locale_info, const char* text) { + value_t v; + char path[MAX_PATH + 1] = {0}; + locale_info_xml_t* xml_info = (locale_info_xml_t*)locale_info; + int32_t size = locale_info_xml_get_node_size(xml_info, NULL); + + for (int32_t i = 0; i < size; i++) { + memset(path, 0, sizeof(path)); + tk_snprintf(path, sizeof(path), "root.[%d].name", i); + + conf_node_t* name_node = conf_doc_find_node(xml_info->doc, NULL, path, FALSE); + if (conf_node_get_value(name_node, &v) != RET_OK) { + continue; + } + const char* name = value_str(&v); + + if (tk_str_eq(name, text)) { + return name_node->parent; + } + } + + return NULL; +} + +static conf_node_t* locale_info_xml_get_node_by_language(locale_info_t* locale_info, + conf_node_t* node, const char* language) { + value_t v; + char path[MAX_PATH + 1] = {0}; + locale_info_xml_t* xml_info = (locale_info_xml_t*)locale_info; + int32_t size = locale_info_xml_get_node_size(xml_info, node); + + for (int32_t i = 0; i < size; i++) { + memset(path, 0, sizeof(path)); + tk_snprintf(path, sizeof(path), "[%d].name", i); + conf_node_t* name_node = conf_doc_find_node(xml_info->doc, node, path, FALSE); + if (conf_node_get_value(name_node, &v) != RET_OK) { + continue; + } + const char* name = value_str(&v); + + if (tk_str_eq(name, language)) { + return name_node->parent; + } + } + + return NULL; +} + +static const char* locale_info_xml_get_node_text(locale_info_t* locale_info, conf_node_t* node) { + locale_info_xml_t* xml_info = (locale_info_xml_t*)locale_info; + conf_node_t* text_node = NULL; + + if (node == NULL) { + return conf_doc_get_str(xml_info->doc, "root.@text", NULL); + } else { + text_node = conf_doc_find_node(xml_info->doc, node, "@text", FALSE); + if (conf_node_get_value(text_node, xml_info->text_value) == RET_OK) { + return value_str(xml_info->text_value); + } + } + + return NULL; +} + +static const char* locale_info_xml_get_tr(void* ctx, const char* text) { + locale_info_t* info = (locale_info_t*)ctx; + if (info == NULL) { + return NULL; + } + + const char* lang = locale_info_xml_get_language_str(info); + conf_node_t* t_node = locale_info_xml_get_node_by_text(info, text); + if (t_node != NULL) { + conf_node_t* l_node = locale_info_xml_get_node_by_language(info, t_node, lang); + if (l_node != NULL) { + return locale_info_xml_get_node_text(info, l_node); + } + } + + return NULL; +} + +locale_info_t* locale_info_xml_create(const char* language, const char* country) { + locale_info_t* info = locale_info_create(language, country); + locale_info_xml_t* xml_info = TKMEM_ZALLOC(locale_info_xml_t); + memcpy(xml_info, info, sizeof(locale_info_t)); + TKMEM_FREE(info); + + xml_info->doc = NULL; + xml_info->strings_url = str_create(0); + xml_info->language = str_create(0); + xml_info->text_value = value_create(); + + info = (locale_info_t*)xml_info; + locale_info_set_fallback_tr2(info, locale_info_xml_get_tr, info); + + return info; +} + +static str_t* get_strings_content(const char* path, str_t* result) { + if (fs_file_exist(os_fs(), path)) { + uint32_t size; + char* content = (char*)file_read(path, &size); + if (content != NULL) { + str_set(result, ""); + str_append(result, content); + str_append(result, ""); + + TKMEM_FREE(content); + } + } + + return result; +} + +static ret_t locale_info_xml_reload(locale_info_xml_t* xml_info) { + if (xml_info == NULL) { + return RET_BAD_PARAMS; + } + + if (xml_info->doc != NULL) { + conf_doc_destroy(xml_info->doc); + } + + str_t* content = str_create(0); + content = get_strings_content(xml_info->strings_url->str, content); + + xml_info->doc = conf_doc_load_xml(content->str); + str_destroy(content); + + return RET_OK; +} + +ret_t locale_info_xml_set_url(locale_info_t* locale_info, const char* strings_url) { + locale_info_xml_t* xml_info = (locale_info_xml_t*)locale_info; + if (xml_info == NULL) { + return RET_BAD_PARAMS; + } + + str_set(xml_info->strings_url, strings_url); + locale_info_xml_reload(xml_info); + + return RET_OK; +} + +ret_t locale_info_xml_set_assets_manager(locale_info_t* locale_info, + assets_manager_t* assets_manager) { + ret_t ret = locale_info_set_assets_manager(locale_info, assets_manager); + if (ret != RET_OK) { + return ret; + } + + const char* res_root = assets_manager_get_res_root(assets_manager); + const char* theme = assets_manager_get_theme_name(assets_manager); + + if (res_root != NULL && theme != NULL) { + char path[MAX_PATH + 1] = {0}; + tk_snprintf(path, sizeof(path), "%s/%s/strings/strings.xml", res_root, theme); + locale_info_xml_set_url(locale_info, path); + } + + return ret; +} + +ret_t locale_info_xml_destroy(locale_info_t* locale_info) { + locale_info_xml_t* xml_info = (locale_info_xml_t*)locale_info; + + conf_doc_destroy(xml_info->doc); + str_destroy(xml_info->strings_url); + str_destroy(xml_info->language); + value_destroy(xml_info->text_value); + locale_info_destroy(locale_info); + + return RET_OK; +} \ No newline at end of file diff --git a/src/base/locale_info_xml.h b/src/base/locale_info_xml.h new file mode 100644 index 000000000..a218040bb --- /dev/null +++ b/src/base/locale_info_xml.h @@ -0,0 +1,94 @@ +/** + * File: locale_info_xml.h + * Author: AWTK Develop Team + * Brief: locale_info_xml + * + * Copyright (c) 2018 - 2025 Guangzhou ZHIYUAN Electronics Co.,Ltd. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * License file for more details. + * + */ + +/** + * History: + * ================================================================ + * 2025-04-08 Li PeiHuang created + * + */ + +#ifndef TK_LOCALE_INFO_XML_H +#define TK_LOCALE_INFO_XML_H + +#include "base/locale_info.h" +#include "conf_io/conf_xml.h" + +BEGIN_C_DECLS + +/** + * @class locale_info_xml_t + * @parent locale_info_t + * @annotation ["scriptable"] + * 本地化信息。 + * locale_info_t 的子类。 + * 提供从 xml 文件中获取本地化信息的功能。 + * + * 注意:fallback_tr2 回调已被设置用于从xml文件中获取本地化信息,不可再重复设置,否则将导致功能失效! + * + */ +typedef struct _locale_info_xml_t { + locale_info_t info; + + /*private*/ + conf_doc_t* doc; + str_t* strings_url; + str_t* language; + value_t* text_value; +} locale_info_xml_t; + +/** + * @method locale_info_xml_create + * 创建locale_info_xml。 + * @annotation ["constructor"] + * @param {const char*} language 语言。 + * @param {const char*} country 国家或地区。 + * + * @return {locale_info_t*} 返回locale_info对象。 + */ +locale_info_t* locale_info_xml_create(const char* language, const char* country); + +/** + * @method locale_info_xml_set_url + * 设置本地化文件路径。 + * @param {locale_info_t*} locale_info locale_info 对象。 + * @param {const char*} strings_url 本地化文件路径。 + * + * @return {ret_t} 返回RET_OK表示成功,否则表示失败。 + */ +ret_t locale_info_xml_set_url(locale_info_t* locale_info, const char* strings_url); + +/** + * @method locale_info_xml_set_assets_manager + * 设置资源管理器对象。 + * @param {locale_info_t*} locale_info locale_info 对象。 + * @param {assets_manager_t*} assets_manager 资源管理器。 + * + * @return {ret_t} 返回RET_OK表示成功,否则表示失败。 + */ +ret_t locale_info_xml_set_assets_manager(locale_info_t* locale_info, + assets_manager_t* assets_manager); + +/** + * @method locale_info_xml_destroy + * 释放全部资源并销毁 locale_info_xml 对象。 + * @param {locale_info_t*} locale_info locale_info 对象。 + * + * @return {ret_t} 返回RET_OK表示成功,否则表示失败。 + */ +ret_t locale_info_xml_destroy(locale_info_t* locale_info); + +END_C_DECLS + +#endif /*TK_LOCALE_INFO_XML_H*/ \ No newline at end of file diff --git a/tests/locale_test.cc b/tests/locale_test.cc index 035fc00c8..b2e86f529 100644 --- a/tests/locale_test.cc +++ b/tests/locale_test.cc @@ -1,4 +1,6 @@ #include "base/locale_info.h" +#include "base/locale_info_xml.h" +#include "tkc/fs.h" #include "gtest/gtest.h" #include "common.h" @@ -48,3 +50,37 @@ TEST(Locale, basic) { locale_info_destroy(locale_info); } + +static ret_t prepare_test_file(const char* file_name) { + const char* content = + "\nTEST\n" + "测试\n\n" + "\nCN\n" + "中文\n\n" + "\nEN\n" + "英文\n\n"; + + return file_write(file_name, content, strlen(content)); +} + +TEST(Locale, xml_basic) { + const char* file_name = "locale_info_xml_test.xml"; + prepare_test_file(file_name); + + const char* str = "test"; + uint32_t id = 0; + locale_info_t* locale_info = locale_info_xml_create("en", "US"); + locale_info_xml_set_url(locale_info, file_name); + + ASSERT_EQ(string("TEST"), string(locale_info_tr(locale_info, str))); + locale_info_change(locale_info, "zh", "CN"); + assert_str_eq(L"测试", locale_info_tr(locale_info, str)); + + str = "cn"; + assert_str_eq(L"中文", locale_info_tr(locale_info, str)); + locale_info_change(locale_info, "en", "US"); + ASSERT_EQ(string("CN"), string(locale_info_tr(locale_info, str))); + + locale_info_xml_destroy(locale_info); + file_remove(file_name); +} diff --git a/tools/preview_ui/preview_ui.c b/tools/preview_ui/preview_ui.c index af24a8405..b0bc13dcd 100644 --- a/tools/preview_ui/preview_ui.c +++ b/tools/preview_ui/preview_ui.c @@ -24,9 +24,7 @@ #endif #include "awtk.h" -#include "tkc/dl.h" -#include "tkc/log.h" -#include "base/timer.h" +#include "base/locale_info_xml.h" #include "ui_loader/ui_loader_xml.h" #include "ui_loader/ui_loader_default.h" @@ -51,6 +49,7 @@ static const char* s_theme = NULL; static const char* s_log_level = NULL; static const char* s_fps = NULL; static bool_t s_enable_std_font = FALSE; +static locale_info_t* s_old_locale_info = NULL; #undef APP_RES_ROOT // 以便可以通过命令行参数指定res目录 @@ -66,68 +65,68 @@ static bool_t s_enable_std_font = FALSE; { log_debug(usage, argv[0]); } #endif /*WIN32 && !NDEBUG*/ -#define ON_CMD_LINE(argc, argv) \ - { \ - const char* usage = \ - "Usage: %s ui=xxx [lcd_w=800] [lcd_h=480] [res_root=xxx] " \ - "[language=xxx] [theme=xxx] [system_bar=xxx] [bottom_system_bar=xxx] " \ - "[plugins_path=xxx] [render_mode=xxx] [enable_std_font=xxx] [enable_console=xxx]\n"; \ - if (argc >= 2) { \ - char key[TK_NAME_LEN + 1]; \ - int i = 1; \ - \ - for (i = 1; i < argc; i++) { \ - const char* p = argv[i]; \ - const char* val = strstr(p, "="); \ - uint32_t len = (uint32_t)(val - p); \ - \ - if (*(argv[i]) == '\0') { \ - continue; \ - } \ - \ - if (val == NULL || val == p || *(val + 1) == '\0' || len > TK_NAME_LEN) { \ - log_debug(usage, argv[0]); \ - exit(0); \ - } \ - \ - tk_strncpy(key, p, len); \ - key[len] = '\0'; \ - \ - if (tk_str_icmp(key, "res_root") == 0) { \ - s_res_root = val + 1; \ - } else if (tk_str_icmp(key, "ui") == 0) { \ - s_ui = val + 1; \ - } else if (tk_str_icmp(key, "system_bar") == 0) { \ - s_system_bar = val + 1; \ - } else if (tk_str_icmp(key, "bottom_system_bar") == 0) { \ - s_system_bar_bottom = val + 1; \ - } else if (tk_str_icmp(key, "lcd_w") == 0) { \ - lcd_w = tk_atoi(val + 1); \ - } else if (tk_str_icmp(key, "lcd_h") == 0) { \ - lcd_h = tk_atoi(val + 1); \ - } else if (tk_str_icmp(key, "plugins_path") == 0) { \ - s_plugins_path = val + 1; \ - } else if (tk_str_icmp(key, "render_mode") == 0) { \ - s_render_mode = val + 1; \ - } else if (tk_str_icmp(key, "language") == 0) { \ - s_language = val + 1; \ - } else if (tk_str_icmp(key, "theme") == 0) { \ - s_theme = val + 1; \ - } else if (tk_str_icmp(key, "log_level") == 0) { \ - s_log_level = val + 1; \ - } else if (tk_str_icmp(key, "fps") == 0) { \ - s_fps = val + 1; \ - } else if (tk_str_icmp(key, "enable_std_font") == 0) { \ - s_enable_std_font = tk_atob(val + 1); \ - } else { \ - SET_ENABLE_CONSOLE() \ - } \ - } \ - \ - } else { \ - log_debug(usage, argv[0]); \ - exit(0); \ - } \ +#define ON_CMD_LINE(argc, argv) \ + { \ + const char* usage = \ + "Usage: %s ui=xxx [lcd_w=800] [lcd_h=480] [res_root=xxx] " \ + "[language=xxx] [theme=xxx] [system_bar=xxx] [bottom_system_bar=xxx] " \ + "[plugins_path=xxx] [render_mode=xxx] [enable_std_font=xxx] [enable_console=xxx]\n"; \ + if (argc >= 2) { \ + char key[TK_NAME_LEN + 1]; \ + int i = 1; \ + \ + for (i = 1; i < argc; i++) { \ + const char* p = argv[i]; \ + const char* val = strstr(p, "="); \ + uint32_t len = (uint32_t)(val - p); \ + \ + if (*(argv[i]) == '\0') { \ + continue; \ + } \ + \ + if (val == NULL || val == p || *(val + 1) == '\0' || len > TK_NAME_LEN) { \ + log_debug(usage, argv[0]); \ + exit(0); \ + } \ + \ + tk_strncpy(key, p, len); \ + key[len] = '\0'; \ + \ + if (tk_str_icmp(key, "res_root") == 0) { \ + s_res_root = val + 1; \ + } else if (tk_str_icmp(key, "ui") == 0) { \ + s_ui = val + 1; \ + } else if (tk_str_icmp(key, "system_bar") == 0) { \ + s_system_bar = val + 1; \ + } else if (tk_str_icmp(key, "bottom_system_bar") == 0) { \ + s_system_bar_bottom = val + 1; \ + } else if (tk_str_icmp(key, "lcd_w") == 0) { \ + lcd_w = tk_atoi(val + 1); \ + } else if (tk_str_icmp(key, "lcd_h") == 0) { \ + lcd_h = tk_atoi(val + 1); \ + } else if (tk_str_icmp(key, "plugins_path") == 0) { \ + s_plugins_path = val + 1; \ + } else if (tk_str_icmp(key, "render_mode") == 0) { \ + s_render_mode = val + 1; \ + } else if (tk_str_icmp(key, "language") == 0) { \ + s_language = val + 1; \ + } else if (tk_str_icmp(key, "theme") == 0) { \ + s_theme = val + 1; \ + } else if (tk_str_icmp(key, "log_level") == 0) { \ + s_log_level = val + 1; \ + } else if (tk_str_icmp(key, "fps") == 0) { \ + s_fps = val + 1; \ + } else if (tk_str_icmp(key, "enable_std_font") == 0) { \ + s_enable_std_font = tk_atob(val + 1); \ + } else { \ + SET_ENABLE_CONSOLE() \ + } \ + } \ + \ + } else { \ + log_debug(usage, argv[0]); \ + exit(0); \ + } \ } static ret_t get_default_res_root(char path[MAX_PATH + 1]) { @@ -251,18 +250,33 @@ static ret_t refresh_in_timer(const timer_info_t* info) { return RET_REPEAT; } +static ret_t get_res_strings_file_path(char* result, size_t size) { + return path_build(result, size, s_res_root, s_theme, "strings", "strings.xml", NULL); +} + static ret_t application_on_launch(void) { // 当程序初始化完成时调用,全局只触发一次。 if (s_language != NULL && *s_language != '\0') { + char language[TK_NAME_LEN + 1] = {0}; + const char* country = NULL; const char* p = strstr(s_language, "_"); + if (p != NULL) { - char language[TK_NAME_LEN + 1] = {0}; tk_strncpy(language, s_language, tk_min((size_t)(p - s_language), sizeof(language))); - locale_info_change(locale_info(), language, p + 1); + country = (p + 1); } else { - locale_info_change(locale_info(), s_language, ""); + country = ""; } + + s_old_locale_info = locale_info(); + locale_info_t* info = locale_info_xml_create(language, country); + locale_info_set(info); + assets_manager_t* am = s_old_locale_info->assets_manager != NULL + ? s_old_locale_info->assets_manager + : assets_manager(); + locale_info_xml_set_assets_manager(info, am); + locale_info_change(info, s_language, country); } widget_set_style_str(window_manager(), "bg_color", "white"); @@ -273,6 +287,9 @@ static ret_t application_on_launch(void) { static ret_t application_on_exit(void) { // 当程序退出时调用,全局只触发一次。 + locale_info_t* info = locale_info(); + locale_info_set(s_old_locale_info); + locale_info_xml_destroy(info); return RET_OK; } @@ -338,6 +355,7 @@ static ret_t application_init(void) { if (s_system_bar_bottom != NULL && *s_system_bar_bottom != '\0') { window_open(s_system_bar_bottom); } + preview_ui(s_ui); return RET_OK; @@ -385,7 +403,10 @@ ret_t assets_init(void) { res_root = assets_manager_get_res_root(am); tk_snprintf(path, sizeof(path), "%s/assets/default/raw/ui", res_root); if (!dir_exist(path)) { - run_default = TRUE; + tk_snprintf(path, sizeof(path), "%s/default/ui", res_root); + if (!dir_exist(path)) { + run_default = TRUE; + } } if (run_default) {