sdk(ui): 字符串资源外置(i18n 雏形 §13)—— meow_str_* + 内嵌双语表 + TF 覆盖表 + 控件接入
公开 SDK 前把 SDK/App 硬编码文案外置:新增 meow_str/meow_strf/meow_str_set_locale/ meow_str_locale,按当前 locale 取串(永不 NULL),第三方开发者可做多语言。 - 新增 include/meow_ui_str.h + meow_ui_str.c:取串优先级 = TF 覆盖表 > 内嵌当前语言 (en_us→en 其余→zh) > 内嵌内置默认(zh_cn) > 原样返回 id。内嵌双语表(common.* 21 条 + ime.* 9 条);热路径零 malloc(编译期静态表 + ≤256 静态覆盖数组);纯 libc 宿主可单测。 - locale 存系统设置 settings.conf 的 ui.locale(默认 zh_cn,首次调用惰性读取); meow_str_set_locale 即时切换 + 重载覆盖表 + best-effort 持久化(流式改写保留其它行)。 - TF 覆盖表 MeowData/<pkg>/strings/<locale>.txt(id=text 行,UTF-8,'=' 两侧去空白); 无卡/无文件/无该 id → 回落内嵌。meow_strf 用调用方缓冲(snprintf 语义,重入安全)。 - SDK 控件接入:dialog 默认按钮 OK/CANCEL + INPUT 占位、IME 键面/徽标(空格/确定/拼英/ ?123/清/拼音/(无候选)/中)改走 meow_str;空态/加载态给 App 提供 common.no_data/loading。 - 测试:新增 test_meow_ui_str.c(内嵌回退/TF 覆盖优先级/未知 id 回退/切 locale 即时生效/ 查找正确性/meow_strf,tmp+env 两模式);全量宿主回归(widgets/dispatch/box/anim/scroll/ listvirt/theme/ime_fix/pinyin/overlay/notif/sdk_input_keys/p1p2p3/config/vol/sb_collapse/ notify_*) 0 回归;交叉 zig-mipsel -Wall -Wextra 0 warning。 - 文档:MEOW_APP_SDK.md 新增 §13(字符串资源/i18n,遗留顺延为 §14);docs/APP_DEV_GUIDE.md 新增 §7.11。各宿主 test_*.c 头注释补 meow_ui_str.c 编译依赖。 边界(§13.7):仅 zh_cn/en_us 整串取用;不做复数规则 / BiDi / RTL / 动态字体回退(雏形)。
This commit is contained in:
1 parent
d7bf62f448
commit
54857bc0fd
23 files changed
+615
-29
No files matched your search
@@ -10,7 +10,7 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架
|
||||
rm -f meow_ui.a *.o
|
||||
|
||||
# -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移)
|
||||
OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_pinyin meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view meow_ui_nav meow_ui_media meow_ui_audio stb_image meow_ui_box meow_ui_anim meow_ui_scroll"
|
||||
OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_pinyin meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view meow_ui_nav meow_ui_media meow_ui_audio stb_image meow_ui_box meow_ui_anim meow_ui_scroll meow_ui_str"
|
||||
CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd"
|
||||
for f in $OBJS; do
|
||||
echo "== cc $f =="
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* meow_ui_str.h — SDK 字符串资源(i18n 雏形,§13.meow_str_*)。
|
||||
*
|
||||
* 定位:公开 SDK 前,系统/App UI 文案不得再硬编码在 C 源码里——第三方开发者必须能按
|
||||
* 系统 locale 拿到对应语言文案,且能在不改 SDK 二进制的情况下用 TF 卡覆盖/追加。
|
||||
* 本头声明字符串资源框架的**对外 API**(追加式,既有 App 零改动)。
|
||||
*
|
||||
* 单表双语内置(zh_cn = 内置默认 / en_us),四层取值优先级(见 meow_ui_str.c):
|
||||
* meow_str(id):按当前 locale 取串;override 覆盖表 > 内嵌当前语言 > 内嵌内置默认(zh_cn);
|
||||
* 全程不可得则原样返回 id(调用方永不见 NULL)。
|
||||
* meow_strf(out,outsz,id,...):取串后按 snprintf 语义格式化进调用方缓冲(自带 NUL 终止)。
|
||||
* meow_str_set_locale(locale):切当前 locale("zh_cn"|"en_us"),即时生效 + 重载 TF 覆盖表
|
||||
* + best-effort 持久化到板载 settings.conf 的 ui.locale。
|
||||
*
|
||||
* 依赖:纯 libc(stdio/stdlib/string/stdarg),宿主 gcc 可直接编译单测;不依赖其它 SDK 对象。
|
||||
* - locale 初值:启动首次调用惰性读系统设置 settings.conf 的 `ui.locale`,缺省 "zh_cn"。
|
||||
* - TF 覆盖表:`<MEOW_DATA_DIR>/../strings/<locale>.txt`(即 MeowData/<pkg>/strings/,见
|
||||
* §9.2 数据目录约定),`id=text` 行格式 UTF-8;无卡/无文件/不含该 id → 内嵌表兜底。
|
||||
* - 热路径(meow_str)零 malloc:内嵌为编译期静态表,覆盖表为启动加载的静态数组。
|
||||
*
|
||||
* 边界(§13 明示不做,写清理由):
|
||||
* 不做复数规则 / BiDi / RTL / 动态字体回退——当前形态是「按 locale 取整串」的 i18n 雏形,
|
||||
* 面向 480×720 单行/短文案 UI;复数变体需占位符格式化协议,BiDi/RTL 需文本整形 + 布局镜像,
|
||||
* 两者都超出「字符串资源外置」的最小必要范围,留待后续批次。
|
||||
*/
|
||||
#ifndef MEOW_UI_STR_H
|
||||
#define MEOW_UI_STR_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 按当前 locale 取 id 对应的文案。id 形如 `域.名`(common.ok / player.pause)。
|
||||
* 返回静态字符串(内嵌表或覆盖表;永不返回 NULL)。未知 id 原样返回 id。 */
|
||||
const char *meow_str(const char *id);
|
||||
|
||||
/* 取 id 文案后按 snprintf 语义格式化进 out(outsz 为缓冲字节数,含 NUL)。
|
||||
* 返回 snprintf 样式的「应写入字符数」(不截断);out==NULL 或 outsz<=0 返回 -1。
|
||||
* 设计理由:调用方自带缓冲,避免静态/线程局部缓冲在 IME/模态等重入路径的竞态,
|
||||
* 且与 SDK 既有 snprintf 习惯一致(无格式参数时等价深拷贝 meow_str 结果)。 */
|
||||
int meow_strf(char *out, int outsz, const char *id, ...);
|
||||
|
||||
/* 设当前 locale(识别 "zh_cn"|"en_us";任意串作为覆盖表文件名 + 内嵌按非 en 处理为内置默认)。
|
||||
* 即时生效(下次 meow_str 即按新 locale);重载该 locale 的 TF 覆盖表;
|
||||
* best-effort 持久化到 settings.conf 的 ui.locale(失败静默,运行时仍切换)。
|
||||
* 须在 App 主线程(UI 线程)调用,避免与 meow_str 并发写覆盖表。 */
|
||||
void meow_str_set_locale(const char *locale);
|
||||
|
||||
/* 当前生效 locale(内部缓冲;未初始化时读 settings 后返回,默认 "zh_cn")。 */
|
||||
const char *meow_str_locale(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* MEOW_UI_STR_H */
|
||||
@@ -37,6 +37,7 @@
|
||||
* include/meow_ui_box.h 线性布局引擎(§12.1:meow_box,固定/weight/wrap/对齐/嵌套)
|
||||
* include/meow_ui_anim.h 动画基座+UI 节拍器(§12.2:tween/easing/池+脏区联动)
|
||||
* include/meow_ui_scroll.h 滚动物理(§12.3:速度跟踪/fling/边界回弹/snap)
|
||||
* include/meow_ui_str.h 字符串资源/i18n(§13:meow_str/meow_strf/meow_str_set_locale)
|
||||
*
|
||||
* 其余模块头(非伞头、App 按需显式 include,均保持不动):
|
||||
* meow_ui_view.h / meow_ui_nav.h / meow_ui_media.h / meow_ui_sys.h / meow_ui_vol.h
|
||||
@@ -69,5 +70,6 @@
|
||||
#include "include/meow_ui_box.h"
|
||||
#include "include/meow_ui_anim.h"
|
||||
#include "include/meow_ui_scroll.h"
|
||||
#include "include/meow_ui_str.h"
|
||||
|
||||
#endif /* MEOW_UI_H */
|
||||
@@ -125,7 +125,7 @@ static void dlg_card(meow_dialog *d, const meow_theme *th, const dlg_geom *g)
|
||||
int y = g->row_y;
|
||||
if (d->type == MEOW_DLG_INPUT) {
|
||||
char buf[300];
|
||||
snprintf(buf, sizeof buf, "%s", d->ime.buf[0] ? d->ime.buf : "(tap OK to edit...)");
|
||||
snprintf(buf, sizeof buf, "%s", d->ime.buf[0] ? d->ime.buf : meow_str("common.tap_ok_edit"));
|
||||
meow_ui_text(g->dx + pad, y, buf, th->font_body, th->onSurfaceVariant);
|
||||
} else {
|
||||
for (int i = 0; i < nmsg; i++) {
|
||||
@@ -138,10 +138,10 @@ static void dlg_card(meow_dialog *d, const meow_theme *th, const dlg_geom *g)
|
||||
int bx = g->dx + pad, cx = bx + g->btn_w + 12, by = g->btn_y;
|
||||
meow_ui_draw_rect_round(bx, by, g->btn_w, g->btn_h, 10, th->primary);
|
||||
meow_ui_text_center(bx + g->btn_w / 2, by + (g->btn_h - meow_ui_text_height(th->font_body)) / 2,
|
||||
"CANCEL", th->font_body, th->primary);
|
||||
meow_str("common.cancel"), th->font_body, th->primary);
|
||||
meow_gfx_fill_round(cx, by, g->btn_w, g->btn_h, 10, th->primary);
|
||||
meow_ui_text_center(cx + g->btn_w / 2, by + (g->btn_h - meow_ui_text_height(th->font_body)) / 2,
|
||||
"OK", th->font_body, th->onPrimary);
|
||||
meow_str("common.ok"), th->font_body, th->onPrimary);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-11
@@ -362,10 +362,10 @@ static const char *py_key_label(const struct kbkey *k, char *tmp)
|
||||
switch (k->kind) {
|
||||
case K_CH: { char c = (char)tolower((unsigned char)k->ch); tmp[0] = c; tmp[1] = 0; return tmp; }
|
||||
case K_BACK: return "⌫";
|
||||
case K_SPACE: return "空格";
|
||||
case K_ENTER: return "确定";
|
||||
case K_SWITCH: return "拼/英";
|
||||
case K_SYMBOL: return "?123";
|
||||
case K_SPACE: return meow_str("ime.space");
|
||||
case K_ENTER: return meow_str("ime.enter");
|
||||
case K_SWITCH: return meow_str("ime.switch");
|
||||
case K_SYMBOL: return meow_str("ime.symbol");
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
@@ -398,9 +398,9 @@ static void meow_ime_draw_py(const meow_theme *th)
|
||||
snprintf(pybuf, sizeof pybuf, "%s_", g_py_buf);
|
||||
meow_ui_text(14, PY_COMBO_Y + 8, pybuf, th->font_body, th->onSurface);
|
||||
meow_gfx_fill_round(PY_CLEAR_X, PY_COMBO_Y + 4, PY_CLEAR_W, PY_BADGE_H, 8, th->secondaryContainer);
|
||||
meow_ui_text_center(PY_CLEAR_X + PY_CLEAR_W / 2, PY_COMBO_Y + 4 + (PY_BADGE_H - th20) / 2, "清", 20, th->onSecondaryContainer);
|
||||
meow_ui_text_center(PY_CLEAR_X + PY_CLEAR_W / 2, PY_COMBO_Y + 4 + (PY_BADGE_H - th20) / 2, meow_str("ime.clear"), 20, th->onSecondaryContainer);
|
||||
meow_gfx_fill_round(PY_BADGE_X, PY_BADGE_Y, PY_BADGE_W, PY_BADGE_H, 8, th->primaryContainer);
|
||||
meow_ui_text_center(PY_BADGE_X + PY_BADGE_W / 2, PY_BADGE_Y + (PY_BADGE_H - th20) / 2, "拼音", 20, th->onPrimaryContainer);
|
||||
meow_ui_text_center(PY_BADGE_X + PY_BADGE_W / 2, PY_BADGE_Y + (PY_BADGE_H - th20) / 2, meow_str("ime.pinyin_mode"), 20, th->onPrimaryContainer);
|
||||
|
||||
/* 候选栏:◀ ▶ 翻页 + 横排候选(高亮第 0 项) */
|
||||
meow_ui_fill_rect(0, PY_CAND_Y, 480, PY_CAND_H, th->surfaceContainerHighest);
|
||||
@@ -409,7 +409,7 @@ static void meow_ime_draw_py(const meow_theme *th)
|
||||
meow_gfx_fill_round(PY_NEXT_X, PY_CAND_Y, PY_NEXT_W, PY_CAND_H, 8, th->surfaceContainerLow);
|
||||
meow_ui_text_center(PY_NEXT_X + PY_NEXT_W / 2, PY_CAND_Y + (PY_CAND_H - th20) / 2, "▶", 20, th->onSurface);
|
||||
if (g_py_nc == 0) {
|
||||
meow_ui_text(PY_CAND_X0, PY_CAND_Y + (PY_CAND_H - th20) / 2, "(无候选)", th->font_body, th->onSurfaceVariant);
|
||||
meow_ui_text(PY_CAND_X0, PY_CAND_Y + (PY_CAND_H - th20) / 2, meow_str("ime.no_candidate"), th->font_body, th->onSurfaceVariant);
|
||||
} else {
|
||||
int x = PY_CAND_X0;
|
||||
for (int i = 0; i < PY_PAGE; i++) {
|
||||
@@ -458,7 +458,7 @@ void meow_ime_draw(void)
|
||||
{
|
||||
int th20 = meow_ui_text_height(20);
|
||||
meow_gfx_fill_round(ASCII_MID_X, KB_TOP + 7, ASCII_MID_W, 28, 8, th->primaryContainer);
|
||||
meow_ui_text_center(ASCII_MID_X + ASCII_MID_W / 2, KB_TOP + 7 + (28 - th20) / 2, "中", 20, th->onPrimaryContainer);
|
||||
meow_ui_text_center(ASCII_MID_X + ASCII_MID_W / 2, KB_TOP + 7 + (28 - th20) / 2, meow_str("ime.to_pinyin"), 20, th->onPrimaryContainer);
|
||||
}
|
||||
/* 键 */
|
||||
for (int i = 0; i < g_nkeys; i++) {
|
||||
@@ -471,9 +471,9 @@ void meow_ime_draw(void)
|
||||
char label[2] = { k->ch, 0 };
|
||||
if (is_letter(k->ch)) label[0] = g_shift ? (char)toupper((unsigned char)k->ch)
|
||||
: (char)tolower((unsigned char)k->ch);
|
||||
if (k->kind == K_SPACE) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, "SPACE", th->font_body, fg); }
|
||||
else if (k->kind == K_ENTER) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, "OK", 20, fg); }
|
||||
else if (k->kind == K_ESC) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, "X", 20, fg); }
|
||||
if (k->kind == K_SPACE) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, meow_str("ime.space"), th->font_body, fg); }
|
||||
else if (k->kind == K_ENTER) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, meow_str("ime.enter"), 20, fg); }
|
||||
else if (k->kind == K_ESC) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, meow_str("ime.esc"), 20, fg); }
|
||||
else { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, label, 20, fg); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* meow_ui_str.c — SDK 字符串资源框架(i18n 雏形,§13.meow_str_*)。
|
||||
*
|
||||
* 背景:SDK/系统 UI 文案此前全部硬编码在 C 源码(dialog 的 "OK/CANCEL"、IME 的 "空格/确定/
|
||||
* 拼音/(无候选)" 等),第三方开发者无法按语言取串,公开 SDK 前必须外置。本 TU 提供
|
||||
* 「按 locale 取整串」的最小框架:内嵌双语默认表(编译进 meow_ui.a,必可用)+ TF 卡覆盖表
|
||||
* (MeowData/<pkg>/strings/<locale>.txt,开发者可加壳不改二进制)+ locale 存系统设置
|
||||
* settings.conf 的 `ui.locale`(默认 zh_cn)。
|
||||
*
|
||||
* 取值优先级(meow_str,四层,逐层回落,永不返回 NULL):
|
||||
* ① 当前 locale 的 TF 覆盖表(meow_ui_str_set_locale 加载,静态数组,≤256 条)
|
||||
* ② 内嵌表中当前语言(en_us→en,其余→zh)
|
||||
* ③ 内嵌表中内置默认(zh_cn,即 locale 缺省/未知语言时回退)
|
||||
* ④ 仍不可得 → 原样返回 id(调用方可展示 id / 打点,绝不见 NULL)
|
||||
*
|
||||
* 设计要点:
|
||||
* - 零 malloc 热路径:内嵌为编译期静态表;覆盖表为启动/切 locale 时的静态数组,
|
||||
* meow_str 只做线性扫描(表 ≤256,O(n) 可接受),不申请任何堆。
|
||||
* - 纯 libc:不依赖其它 SDK TU,宿主 gcc 直接可单测(同 meow_ui_config.c / meow_ui_vol.c)。
|
||||
* - locale 初值惰性读 settings.conf ui.locale(默认 zh_cn);meow_str_set_locale 即时生效。
|
||||
* - 覆盖表解析兼容 "key = value"('=' 两侧去空白),忽略空行/无 '=' 行;UTF-8 原样透传。
|
||||
* - 幂等:settings 只读一次;覆盖表按需加载。locale 修改须在 App 主线程(防并发写覆盖表)。
|
||||
*
|
||||
* 边界(§13 明示不做):无复数规则/BiDi/RTL/动态字体回退,理由见 meow_ui_str.h。
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#define MEOW_STR_SETTINGS_FILE "/usr/data/meow/settings.conf" /* 板载系统设置(与 theme/vol 同源) */
|
||||
#define MEOW_STR_LOCALE_MAX 16
|
||||
#define MEOW_STR_OVERRIDE_MAX 256 /* 覆盖表上限(§13 性能约束 ≤256) */
|
||||
#define MEOW_STR_OVR_ID_MAX 64
|
||||
#define MEOW_STR_OVR_TEXT_MAX 128
|
||||
#define MEOW_STR_PATH_MAX 384
|
||||
|
||||
/* ---- 内嵌双语默认表(编译进二进制,必可用;zh = 内置默认)----
|
||||
* id 规范:`域.名`(common.* 为 SDK 自带通用组;ime.* 为 SDK 自带 IME 键面组;
|
||||
* 第三方 App 可自建 域(如 player.pause)经 TF 覆盖表追加,不改 SDK 二进制)。 */
|
||||
typedef struct meow_str_embed {
|
||||
const char *id;
|
||||
const char *zh;
|
||||
const char *en;
|
||||
} meow_str_embed;
|
||||
|
||||
static const meow_str_embed g_embed[] = {
|
||||
/* common.*:通用 UI(dialog 默认按钮 / 空态占位可复用) */
|
||||
{ "common.ok", "确定", "OK" },
|
||||
{ "common.cancel", "取消", "Cancel" },
|
||||
{ "common.back", "返回", "Back" },
|
||||
{ "common.confirm", "确认", "Confirm" },
|
||||
{ "common.done", "完成", "Done" },
|
||||
{ "common.save", "保存", "Save" },
|
||||
{ "common.delete", "删除", "Delete" },
|
||||
{ "common.edit", "编辑", "Edit" },
|
||||
{ "common.search", "搜索", "Search" },
|
||||
{ "common.input", "输入", "Input" },
|
||||
{ "common.please_input", "请输入", "Please input" },
|
||||
{ "common.select", "请选择", "Select" },
|
||||
{ "common.loading", "加载中...", "Loading..." },
|
||||
{ "common.no_data", "暂无数据", "No data" },
|
||||
{ "common.retry", "重试", "Retry" },
|
||||
{ "common.failed", "失败", "Failed" },
|
||||
{ "common.retry_fail", "重试失败", "Retry failed" },
|
||||
{ "common.close", "关闭", "Close" },
|
||||
{ "common.unknown", "未知", "Unknown" },
|
||||
{ "common.settings", "设置", "Settings" },
|
||||
{ "common.tap_ok_edit", "点击确定编辑", "tap OK to edit..." },
|
||||
/* ime.*:SDK 内置 IME 键面/徽标(meow_ui_ime.c 接入) */
|
||||
{ "ime.space", "空格", "Space" },
|
||||
{ "ime.enter", "确定", "OK" },
|
||||
{ "ime.esc", "取消", "Cancel" },
|
||||
{ "ime.switch", "拼/英", "PY/EN" },
|
||||
{ "ime.symbol", "?123", "?123" },
|
||||
{ "ime.clear", "清", "Clear" },
|
||||
{ "ime.pinyin_mode", "拼音", "Pinyin" },
|
||||
{ "ime.no_candidate", "(无候选)", "(none)" },
|
||||
{ "ime.to_pinyin", "中", "中" },
|
||||
};
|
||||
#define MEOW_STR_EMBED_N (sizeof(g_embed) / sizeof(g_embed[0]))
|
||||
|
||||
/* ---- 运行时状态(静态,零 malloc)---- */
|
||||
static char g_locale[MEOW_STR_LOCALE_MAX] = "zh_cn";
|
||||
static int g_locale_loaded = 0; /* 1 = 已从 settings 读过一次 */
|
||||
static int g_override_loaded = 0; /* 1 = 覆盖表已按当前 locale 加载 */
|
||||
|
||||
/* 覆盖表(当前 locale):线性静态数组(id/text 各自定长),≤ MEOW_STR_OVERRIDE_MAX。 */
|
||||
static char g_ov_id[MEOW_STR_OVERRIDE_MAX][MEOW_STR_OVR_ID_MAX];
|
||||
static char g_ov_text[MEOW_STR_OVERRIDE_MAX][MEOW_STR_OVR_TEXT_MAX];
|
||||
static int g_ov_n = 0;
|
||||
|
||||
/* ---- settings.conf 读(同 meow_ui_theme.c get_setting_str 口径:'=' 切分,精确匹配 key)----
|
||||
* 返回 0=命中并把值拷入 buf;-1=未命中/文件不可读/超长。 */
|
||||
static int str_setting_get(const char *key, char *buf, int buflen)
|
||||
{
|
||||
FILE *f = fopen(MEOW_STR_SETTINGS_FILE, "r");
|
||||
if (!f || buflen <= 0) return -1;
|
||||
char line[160];
|
||||
int kl = (int)strlen(key);
|
||||
int found = 0;
|
||||
while (fgets(line, sizeof line, f)) {
|
||||
char *eq = strchr(line, '=');
|
||||
if (!eq) continue;
|
||||
char *k = line;
|
||||
while (*k == ' ' || *k == '\t') k++;
|
||||
int n = (int)(eq - k);
|
||||
while (n > 0 && (k[n-1] == ' ' || k[n-1] == '\t')) n--;
|
||||
if (n != kl || strncmp(k, key, (size_t)kl) != 0) continue;
|
||||
const char *v = eq + 1;
|
||||
while (*v == ' ' || *v == '\t') v++;
|
||||
int m = 0;
|
||||
while (*v && *v != '\n' && *v != '\r' && m < buflen - 1) buf[m++] = *v++;
|
||||
while (m > 0 && (buf[m-1] == ' ' || buf[m-1] == '\t')) m--;
|
||||
buf[m] = 0;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
fclose(f);
|
||||
return found ? 0 : -1;
|
||||
}
|
||||
|
||||
/* ---- settings.conf 写一个字符串键(best-effort):流式拷贝到 .tmp,命中键行改写为
|
||||
* "key = value"(launcher 落盘格式),键缺失则末尾追加;rename 原子替换。保留其余注释/设置,
|
||||
* 绝不 O_TRUNC 整文件(同 meow_ui_vol_set 思路)。失败静默返回 -1(读不改状态、不崩)。 */
|
||||
static int str_setting_set(const char *key, const char *val)
|
||||
{
|
||||
if (!key || !key[0] || !val) return -1;
|
||||
FILE *in = fopen(MEOW_STR_SETTINGS_FILE, "r");
|
||||
char tmp[MEOW_STR_PATH_MAX];
|
||||
int kl = (int)strlen(key);
|
||||
snprintf(tmp, sizeof tmp, "%s.tmp", MEOW_STR_SETTINGS_FILE);
|
||||
FILE *out = fopen(tmp, "w");
|
||||
if (!out) { if (in) fclose(in); return -1; }
|
||||
|
||||
int found = 0;
|
||||
char line[256];
|
||||
while (in && fgets(line, sizeof line, in)) {
|
||||
char *eq = strchr(line, '=');
|
||||
char *k = line;
|
||||
while (*k == ' ' || *k == '\t') k++;
|
||||
int n = eq ? (int)(eq - k) : 0;
|
||||
while (n > 0 && (k[n-1] == ' ' || k[n-1] == '\t')) n--;
|
||||
if (eq && n == kl && strncmp(k, key, (size_t)kl) == 0) {
|
||||
fprintf(out, "%s = %s\n", key, val);
|
||||
found = 1;
|
||||
} else {
|
||||
fputs(line, out);
|
||||
}
|
||||
}
|
||||
if (in) fclose(in);
|
||||
if (!found) fprintf(out, "%s = %s\n", key, val);
|
||||
fclose(out);
|
||||
|
||||
if (rename(tmp, MEOW_STR_SETTINGS_FILE) != 0) { remove(tmp); return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- TF 覆盖表路径:<MEOW_DATA_DIR 去尾 /data>/strings/<locale>.txt(= MeowData/<pkg>/strings/)。
|
||||
* 无 MEOW_DATA_DIR(手动跑/宿主/无卡)→ path 置空(覆盖表不加载,内嵌兜底)。返回 path 是否有效。 */
|
||||
static int str_override_path(char *path, int n)
|
||||
{
|
||||
const char *env = getenv("MEOW_DATA_DIR");
|
||||
if (!env || !env[0] || n <= 0) { if (n > 0) path[0] = 0; return 0; }
|
||||
char base[MEOW_STR_PATH_MAX];
|
||||
snprintf(base, sizeof base, "%s", env);
|
||||
size_t bn = strlen(base);
|
||||
/* launcher 注入的 MEOW_DATA_DIR = <pkg>/data;去尾 "/data" 回到 <pkg>,再拼 strings/。 */
|
||||
if (bn >= 5 && strcmp(base + bn - 5, "/data") == 0) base[bn - 5] = 0;
|
||||
if (snprintf(path, (size_t)n, "%s/strings/%s.txt", base, g_locale) >= n) { path[0] = 0; return 0; }
|
||||
return path[0] != 0;
|
||||
}
|
||||
|
||||
/* 解析覆盖表(`id=text` 行,'=' 两侧去空白;空行/无 '=' 行跳过;UTF-8 原样透传)。
|
||||
* 覆盖优先级:同名覆盖内嵌,未覆盖该 id 则回退内嵌。 */
|
||||
static void str_load_override(void)
|
||||
{
|
||||
char path[MEOW_STR_PATH_MAX];
|
||||
g_ov_n = 0;
|
||||
if (!str_override_path(path, sizeof path)) return;
|
||||
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) return;
|
||||
char line[MEOW_STR_OVR_TEXT_MAX + 16];
|
||||
while (fgets(line, sizeof line, f) && g_ov_n < MEOW_STR_OVERRIDE_MAX) {
|
||||
char *eq = strchr(line, '=');
|
||||
if (!eq) continue; /* 注释/空行/非 key=value */
|
||||
char *k = line;
|
||||
while (*k == ' ' || *k == '\t') k++; /* id 左去空白 */
|
||||
int klen = (int)(eq - k);
|
||||
while (klen > 0 && (k[klen-1] == ' ' || k[klen-1] == '\t')) klen--;
|
||||
if (klen <= 0 || klen >= MEOW_STR_OVR_ID_MAX) continue;
|
||||
const char *v = eq + 1;
|
||||
while (*v == ' ' || *v == '\t') v++; /* text 左去空白 */
|
||||
int vlen = 0;
|
||||
while (v[vlen] && v[vlen] != '\n' && v[vlen] != '\r' && vlen < MEOW_STR_OVR_TEXT_MAX - 1)
|
||||
vlen++;
|
||||
while (vlen > 0 && (v[vlen-1] == ' ' || v[vlen-1] == '\t')) vlen--; /* text 右去空白 */
|
||||
if (vlen <= 0) continue;
|
||||
|
||||
memcpy(g_ov_id[g_ov_n], k, (size_t)klen);
|
||||
g_ov_id[g_ov_n][klen] = 0;
|
||||
memcpy(g_ov_text[g_ov_n], v, (size_t)vlen);
|
||||
g_ov_text[g_ov_n][vlen] = 0;
|
||||
g_ov_n++;
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
/* 惰性初值:首次调用读 settings.conf ui.locale(缺省 "zh_cn")。幂等(只读一次)。 */
|
||||
static void str_init_locale(void)
|
||||
{
|
||||
if (g_locale_loaded) return;
|
||||
char buf[MEOW_STR_LOCALE_MAX];
|
||||
if (str_setting_get("ui.locale", buf, sizeof buf) == 0 && buf[0])
|
||||
snprintf(g_locale, sizeof g_locale, "%s", buf);
|
||||
else
|
||||
snprintf(g_locale, sizeof g_locale, "zh_cn");
|
||||
g_locale_loaded = 1;
|
||||
}
|
||||
|
||||
/* 惰性加载覆盖表(对当前 g_locale)。幂等(只加载一次直到 set_locale 重载)。 */
|
||||
static void str_ensure_override(void)
|
||||
{
|
||||
if (g_override_loaded) return;
|
||||
str_load_override();
|
||||
g_override_loaded = 1;
|
||||
}
|
||||
|
||||
/* meow_str / meow_str_locale 前置:确保 locale 初值 + 覆盖表就绪。 */
|
||||
static void str_init(void)
|
||||
{
|
||||
str_init_locale();
|
||||
str_ensure_override();
|
||||
}
|
||||
|
||||
/* 持久化 ui.locale(best-effort)。失败静默(运行时仍切换,仅不跨启动)。 */
|
||||
static void str_persist_locale(void)
|
||||
{
|
||||
str_setting_set("ui.locale", g_locale);
|
||||
}
|
||||
|
||||
const char *meow_str_locale(void)
|
||||
{
|
||||
str_init();
|
||||
return g_locale;
|
||||
}
|
||||
|
||||
const char *meow_str(const char *id)
|
||||
{
|
||||
if (!id) return "";
|
||||
str_init();
|
||||
int en = (strcmp(g_locale, "en_us") == 0);
|
||||
|
||||
/* ① 当前 locale 的 TF 覆盖表(最高优先) */
|
||||
for (int i = 0; i < g_ov_n; i++)
|
||||
if (strcmp(g_ov_id[i], id) == 0) return g_ov_text[i];
|
||||
|
||||
/* ② 内嵌当前语言(en_us→en;其余→zh)③ 兜底内嵌内置默认(zh)——同一表内按语言取。 */
|
||||
for (size_t i = 0; i < MEOW_STR_EMBED_N; i++)
|
||||
if (strcmp(g_embed[i].id, id) == 0) return en ? g_embed[i].en : g_embed[i].zh;
|
||||
|
||||
/* ④ 未知 id:原样返回调用方(永不 NULL)。 */
|
||||
return id;
|
||||
}
|
||||
|
||||
int meow_strf(char *out, int outsz, const char *id, ...)
|
||||
{
|
||||
if (!out || outsz <= 0) return -1;
|
||||
const char *s = meow_str(id);
|
||||
va_list ap;
|
||||
va_start(ap, id);
|
||||
int r = vsnprintf(out, (size_t)outsz, s, ap);
|
||||
va_end(ap);
|
||||
return r;
|
||||
}
|
||||
|
||||
void meow_str_set_locale(const char *locale)
|
||||
{
|
||||
if (!locale || !locale[0]) return;
|
||||
str_init(); /* 确保 locale 初值 + 覆盖表基座就绪 */
|
||||
snprintf(g_locale, sizeof g_locale, "%s", locale);
|
||||
str_load_override(); /* 按新 locale 重载覆盖表 */
|
||||
g_override_loaded = 1;
|
||||
str_persist_locale(); /* best-effort 持久化(失败静默) */
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -Wall -Wextra -o test_ime_fix test_ime_fix.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_ui_draw.c meow_ui_view.c meow_ui_img.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* (meow_ui_widget.c 新 W_LISTVIEW 引用 meow_ui_img_scaled → 需链 meow_ui_img.c+stb_image.c+-lm)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/draw/activity/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_anim test_meow_ui_anim.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 编译(宿主 gcc,链真实 widget/draw/activity/theme/gfx + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_box test_meow_ui_box.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* 打桩 font/global;完整超集便于链接):
|
||||
* gcc -Wall -Wextra -o test_meow_ui_dispatch test_meow_ui_dispatch.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_listvirt test_meow_ui_listvirt.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/draw/activity/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_scroll test_meow_ui_scroll.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* test_meow_ui_str.c — meow_str_* 宿主单测(2026-09-02,§13.i18n 雏形)。
|
||||
*
|
||||
* 编译(宿主,meow_ui_str 为纯 libc,无需 SDK 其它对象):
|
||||
* gcc -Wall -Wextra -o test_meow_ui_str test_meow_ui_str.c meow_ui_str.c -I.
|
||||
*
|
||||
* 两种模式(覆盖表按 locale 懒加载,分两次跑):
|
||||
* ./test_meow_ui_str tmp → unsetenv MEOW_DATA_DIR,仅验证内嵌双语表(无覆盖表)
|
||||
* ./test_meow_ui_str env → 设 MEOW_DATA_DIR=<mkdtemp>/data,写 <mkdtemp>/strings/en_us.txt,
|
||||
* 验证 TF 覆盖表优先级(覆盖>内嵌)+ locale 即时生效
|
||||
*
|
||||
* 覆盖矩阵:
|
||||
* T1 内嵌回退(默认 zh_cn):common.ok/loading/no_data 等取 zh
|
||||
* T2 切 locale 即时生效:set_locale(en_us) → 内嵌 en;set_locale(zh_cn) → 内嵌 zh(无需重初始化)
|
||||
* T3 未知 id 回退:meow_str("nope.missing") 原样返回 id;meow_str(NULL) 返回 ""
|
||||
* T4 TF 覆盖优先级:override > 内嵌当前语言;未覆盖 id 回落内嵌;自定义 id 命中
|
||||
* T5 查找正确性:一批 id 的 zh/en 值逐一断言
|
||||
* T6 meow_strf:取串并按 snprintf 语义写调用方缓冲(与 meow_str 一致)
|
||||
*
|
||||
* 注:set_locale 的 ui.locale 持久化目标是板载 /usr/data/meow/settings.conf,宿主无该目录
|
||||
* → 静默失败(运行时仍切换,仅不跨启动);不影响本测试断言。
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond, msg) do { \
|
||||
if (cond) { printf(" PASS: %s\n", msg); } \
|
||||
else { printf(" FAIL: %s\n", msg); g_fail = 1; } \
|
||||
} while (0)
|
||||
|
||||
/* 找内嵌某 id 的 zh/en 期望值(供 T5 逐项对照)。不在表内返回 NULL。 */
|
||||
static const char *expect(const char *id, int en)
|
||||
{
|
||||
/* 与 meow_ui_str.c 的 g_embed[] 对齐(此处只列 T5 抽查的子集) */
|
||||
static const struct { const char *id; const char *zh; const char *en; } t[] = {
|
||||
{ "common.ok", "确定", "OK" },
|
||||
{ "common.cancel", "取消", "Cancel" },
|
||||
{ "common.back", "返回", "Back" },
|
||||
{ "common.confirm", "确认", "Confirm" },
|
||||
{ "common.loading", "加载中...", "Loading..." },
|
||||
{ "common.no_data", "暂无数据", "No data" },
|
||||
{ "common.retry_fail", "重试失败", "Retry failed" },
|
||||
{ "common.tap_ok_edit", "点击确定编辑", "tap OK to edit..." },
|
||||
{ "ime.space", "空格", "Space" },
|
||||
{ "ime.enter", "确定", "OK" },
|
||||
{ "ime.esc", "取消", "Cancel" },
|
||||
{ "ime.clear", "清", "Clear" },
|
||||
{ "ime.pinyin_mode", "拼音", "Pinyin" },
|
||||
{ "ime.no_candidate", "(无候选)", "(none)" },
|
||||
};
|
||||
for (size_t i = 0; i < sizeof t / sizeof t[0]; i++)
|
||||
if (strcmp(t[i].id, id) == 0) return en ? t[i].en : t[i].zh;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *mode = (argc > 1) ? argv[1] : "tmp";
|
||||
char *tmpdir = NULL;
|
||||
|
||||
if (strcmp(mode, "env") == 0) {
|
||||
/* 建 <mkdtemp>/data + <mkdtemp>/strings/en_us.txt(meow_str 由 MEOW_DATA_DIR 去尾 /data
|
||||
* 得 <mkdtemp>/,再拼 strings/<locale>.txt)。 */
|
||||
tmpdir = strdup("/tmp/meowstrtestXXXXXX");
|
||||
if (!mkdtemp(tmpdir)) { printf("FATAL: mkdtemp failed\n"); return 2; }
|
||||
char datadir[512], strdir[512];
|
||||
snprintf(datadir, sizeof datadir, "%s/data", tmpdir);
|
||||
snprintf(strdir, sizeof strdir, "%s/strings", tmpdir);
|
||||
mkdir(datadir, 0755);
|
||||
mkdir(strdir, 0755);
|
||||
char file[512];
|
||||
snprintf(file, sizeof file, "%s/strings/en_us.txt", tmpdir);
|
||||
FILE *f = fopen(file, "w");
|
||||
if (!f) { printf("FATAL: cannot write override file\n"); free(tmpdir); return 2; }
|
||||
/* 混合:正常行 / '=' 两侧带空格 / 空行 / 无 '=' 行(应被跳过) */
|
||||
fprintf(f, "# comment line (no equals)\n");
|
||||
fprintf(f, "common.ok=确认OK\n");
|
||||
fprintf(f, "common.custom=自定义覆盖\n");
|
||||
fprintf(f, "common.loading = 加载中覆盖\n");
|
||||
fprintf(f, "badline-without-equals\n");
|
||||
fprintf(f, "\n");
|
||||
fclose(f);
|
||||
setenv("MEOW_DATA_DIR", datadir, 1);
|
||||
} else {
|
||||
unsetenv("MEOW_DATA_DIR");
|
||||
}
|
||||
|
||||
printf("== meow_str test [%s] ==\n", mode);
|
||||
printf(" locale = %s\n", meow_str_locale());
|
||||
|
||||
/* --- T1 内嵌回退(默认 zh_cn,无覆盖表/无 zh_cn.txt)--- */
|
||||
CHECK(strcmp(meow_str("common.ok"), "确定") == 0, "T1 zh common.ok = 确定");
|
||||
CHECK(strcmp(meow_str("common.cancel"), "取消") == 0, "T1 zh common.cancel = 取消");
|
||||
CHECK(strcmp(meow_str("common.loading"), "加载中...") == 0, "T1 zh common.loading = 加载中...");
|
||||
CHECK(strcmp(meow_str("common.no_data"), "暂无数据") == 0, "T1 zh common.no_data = 暂无数据");
|
||||
CHECK(strcmp(meow_str("ime.space"), "空格") == 0, "T1 zh ime.space = 空格");
|
||||
|
||||
/* --- T3 未知 id 回退(永不 NULL)--- */
|
||||
CHECK(strcmp(meow_str("nope.missing"), "nope.missing") == 0, "T3 未知 id 原样返回 id");
|
||||
CHECK(strcmp(meow_str(NULL), "") == 0, "T3 meow_str(NULL) = \"\"");
|
||||
|
||||
/* --- T6 meow_strf:与 meow_str 一致,snprintf 语义 --- */
|
||||
{
|
||||
char buf[128];
|
||||
int r = meow_strf(buf, sizeof buf, "common.ok");
|
||||
CHECK(r == (int)strlen("确定") && strcmp(buf, "确定") == 0, "T6 meow_strf zh common.ok = 确定");
|
||||
meow_strf(buf, sizeof buf, "common.no_data");
|
||||
CHECK(strcmp(buf, meow_str("common.no_data")) == 0, "T6 meow_strf == meow_str");
|
||||
CHECK(meow_strf(NULL, 10, "common.ok") == -1, "T6 meow_strf(out=NULL) = -1");
|
||||
CHECK(meow_strf(buf, 0, "common.ok") == -1, "T6 meow_strf(outsz=0) = -1");
|
||||
}
|
||||
|
||||
/* --- T2 切 locale 即时生效(无需重初始化)---
|
||||
* 用「未在覆盖表」的 id 证明 locale 即时切换(common.ok 等在 env 会被覆盖,留给 T4 验覆盖路径)。 */
|
||||
meow_str_set_locale("en_us");
|
||||
CHECK(strcmp(meow_str_locale(), "en_us") == 0, "T2 locale = en_us");
|
||||
CHECK(strcmp(meow_str("common.cancel"), "Cancel") == 0, "T2 en common.cancel = Cancel");
|
||||
CHECK(strcmp(meow_str("common.no_data"), "No data") == 0, "T2 en common.no_data = No data");
|
||||
CHECK(strcmp(meow_str("ime.space"), "Space") == 0, "T2 en ime.space = Space");
|
||||
|
||||
/* --- T4 TF 覆盖优先级(仅 env 模式)--- */
|
||||
if (strcmp(mode, "env") == 0) {
|
||||
/* ① 覆盖内嵌当前语言(en_us*下 common.ok 应是覆盖值"确认OK"而非内嵌"OK") */
|
||||
CHECK(strcmp(meow_str("common.ok"), "确认OK") == 0, "T4 @en override 覆盖 common.ok");
|
||||
/* ② 覆盖自定义 id(内嵌无 → 覆盖命中) */
|
||||
CHECK(strcmp(meow_str("common.custom"), "自定义覆盖") == 0, "T4 @en override 自定义 id");
|
||||
/* ③ '=' 两侧带空格也可解析 */
|
||||
CHECK(strcmp(meow_str("common.loading"), "加载中覆盖") == 0, "T4 @en override 带空格行");
|
||||
/* ④ 未覆盖 id 回落内嵌当前语言 */
|
||||
CHECK(strcmp(meow_str("common.cancel"), "Cancel") == 0, "T4 @en 未覆盖 → 内嵌 en");
|
||||
CHECK(strcmp(meow_str("ime.clear"), "Clear") == 0, "T4 @en ime.clear 未覆盖 → 内嵌 en");
|
||||
/* ⑤ 自定义 id 在 zh(无 zh_cn.txt,覆盖表清空)→ 内嵌无 → 原样返回 id */
|
||||
meow_str_set_locale("zh_cn");
|
||||
CHECK(strcmp(meow_str("common.ok"), "确定") == 0, "T4 @zh 覆盖清除 → 内嵌 zh");
|
||||
CHECK(strcmp(meow_str("common.custom"), "common.custom") == 0, "T4 @zh 自定义 id 原样回退");
|
||||
meow_str_set_locale("en_us"); /* 回到 en 供 T5 */
|
||||
}
|
||||
|
||||
/* --- T5 查找正确性:一批 id 的 zh/en 逐一断言(仅 tmp 模式——无覆盖干扰,
|
||||
* 才能验证内嵌表本身;env 模式的内嵌 vs 覆盖已由 T2/T4 覆盖,此处跳过多余断言)--- */
|
||||
if (strcmp(mode, "tmp") == 0) {
|
||||
const char *ids[] = { "common.ok", "common.cancel", "common.back", "common.confirm",
|
||||
"common.loading", "common.no_data", "common.retry_fail",
|
||||
"common.tap_ok_edit", "ime.space", "ime.enter", "ime.esc",
|
||||
"ime.clear", "ime.pinyin_mode", "ime.no_candidate" };
|
||||
meow_str_set_locale("zh_cn");
|
||||
for (size_t i = 0; i < sizeof ids / sizeof ids[0]; i++) {
|
||||
if (expect(ids[i], 0)) {
|
||||
char msg[128];
|
||||
snprintf(msg, sizeof msg, "T5 zh %s == 期望", ids[i]);
|
||||
CHECK(strcmp(meow_str(ids[i]), expect(ids[i], 0)) == 0, msg);
|
||||
}
|
||||
}
|
||||
meow_str_set_locale("en_us");
|
||||
for (size_t i = 0; i < sizeof ids / sizeof ids[0]; i++) {
|
||||
if (expect(ids[i], 1)) {
|
||||
char msg[128];
|
||||
snprintf(msg, sizeof msg, "T5 en %s == 期望", ids[i]);
|
||||
CHECK(strcmp(meow_str(ids[i]), expect(ids[i], 1)) == 0, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tmpdir) free(tmpdir);
|
||||
if (g_fail) { printf("\nRESULT: FAIL (%d)\n", g_fail); return 1; }
|
||||
printf("\nRESULT: ALL PASS\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_theme test_meow_ui_theme.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_ui_draw.c meow_ui_view.c meow_ui_img.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_widgets test_meow_ui_widgets.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_ui_draw.c meow_ui_view.c meow_ui_img.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:MEOW_UI_DUMP=/tmp/b2.ppm ./test_meow_ui_widgets (可选,dump 屏幕)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/ime/media/notify_ipc + 打桩 font/global):
|
||||
* gcc -Wall -Wextra -o test_notif_gesture test_notif_gesture.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_ime.c meow_ui_media.c meow_notify_ipc.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_ime.c meow_ui_str.c meow_ui_media.c meow_notify_ipc.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:./test_notif_gesture
|
||||
*/
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -Wall -Wextra -o test_overlay_suppress test_overlay_suppress.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_ui_draw.c meow_ui_view.c meow_notify_ipc.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread
|
||||
*/
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* 2) 编译本文件 + 其余 hello 模块 + SDK 源 + 打桩,链接 app.o:
|
||||
* gcc -Wall -Wextra -DMEOW_UI_HOST_TEST -o test_p1p2p3_repro test_p1p2p3_repro.c app.o \
|
||||
* test_host_stub.c meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_ui_img.c meow_ui_config.c meow_ui_notify.c meow_notify_ipc.c meow_ui_draw.c \
|
||||
* meow_ui_view.c meow_ui_nav.c meow_ui_media.c stb_image.c \
|
||||
* ../hello_app/ui/home/home.c ../hello_app/ui/full.c ../hello_app/ui/demo/demo.c \
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* 编译(宿主 gcc,同 test_ime_fix):
|
||||
* gcc -Wall -Wextra -o test_pinyin test_pinyin.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_img.c meow_ui_media.c \
|
||||
* meow_notify_ipc.c stb_image.c -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -Wall -o test_sdk_fixes test_sdk_fixes.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_ui_draw.c meow_ui_view.c meow_notify_ipc.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread
|
||||
*/
|
||||
|
||||
@@ -82,6 +82,8 @@ static void umbrella_smoke(void)
|
||||
(void)&meow_notify_send; (void)&meow_notify_category_str; (void)&meow_notify_update_progress;
|
||||
/* 配置(§9.2) */
|
||||
(void)&meow_config_dir; (void)&meow_config_int; (void)&meow_config_remove;
|
||||
/* 字符串资源/i18n(§13,追加式) */
|
||||
(void)&meow_str; (void)&meow_strf; (void)&meow_str_set_locale; (void)&meow_str_locale;
|
||||
/* 字体(§5.7/§10) */
|
||||
(void)&meow_font_enumerate; (void)&meow_font_tier; (void)&meow_font_select_by_id;
|
||||
/* 音频(§11) */
|
||||
|
||||
Reference in new issue
Block a user