add locale_info_xml

This commit is contained in:
lixianjing 2025-04-09 08:41:55 +08:00
parent fdbf807ddb
commit 2e5b0291bc
6 changed files with 447 additions and 69 deletions

View File

@ -1,5 +1,8 @@
# 最新动态
2025/04/09
* 添加可从xml中解析本地化信息的locale_info完善预览程序可从strings.xml获取翻译文本(感谢培煌提供补丁)
2025/04/08
* 增加 rectf_intersect (感谢智明提供补丁)
* 修复nanovg的脏矩形获取不正常的问题(感谢智明提供补丁)

View File

@ -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"

223
src/base/locale_info_xml.c Normal file
View File

@ -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 <lipeihuang@zlg.cn> 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, "<root>");
str_append(result, content);
str_append(result, "</root>");
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;
}

View File

@ -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 <lipeihuang@zlg.cn> 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*/

View File

@ -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 =
"<string name=\"test\">\n<language name=\"en_US\">TEST</language>\n"
"<language name=\"zh_CN\">测试</language>\n</string>\n"
"<string name=\"cn\">\n<language name=\"en_US\">CN</language>\n"
"<language name=\"zh_CN\">中文</language>\n</string>\n"
"<string name=\"en\">\n<language name=\"en_US\">EN</language>\n"
"<language name=\"zh_CN\">英文</language>\n</string>\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);
}

View File

@ -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) {