i18n: cycle help language with one key

This commit is contained in:
m1ngsama 2026-05-24 15:06:34 +08:00
parent f2942e9c9e
commit 155e535b8a
9 changed files with 31 additions and 12 deletions

View file

@ -46,6 +46,8 @@
- Language selection is limited to stable codes (`en`, `zh`) and - Language selection is limited to stable codes (`en`, `zh`) and
locale-shaped environment values; natural-language labels are not accepted locale-shaped environment values; natural-language labels are not accepted
as command arguments. as command arguments.
- Full-screen help now uses `l` to cycle the UI language through the i18n
module instead of hard-coding one key per language.
- Documented i18n and user-facing text rules for English-first source text, - Documented i18n and user-facing text rules for English-first source text,
stable command syntax, concise help copy, and translation-only localization. stable command syntax, concise help copy, and translation-only localization.

View file

@ -58,6 +58,7 @@ typedef enum {
bool i18n_try_parse_ui_lang(const char *value, ui_lang_t *lang); bool i18n_try_parse_ui_lang(const char *value, ui_lang_t *lang);
ui_lang_t i18n_parse_ui_lang(const char *value, ui_lang_t fallback); ui_lang_t i18n_parse_ui_lang(const char *value, ui_lang_t fallback);
ui_lang_t i18n_default_ui_lang(void); ui_lang_t i18n_default_ui_lang(void);
ui_lang_t i18n_next_ui_lang(ui_lang_t lang);
const char *i18n_ui_lang_code(ui_lang_t lang); const char *i18n_ui_lang_code(ui_lang_t lang);
const char *i18n_text(ui_lang_t lang, i18n_text_id_t id); const char *i18n_text(ui_lang_t lang, i18n_text_id_t id);

View file

@ -56,7 +56,7 @@ void help_text_append_full(char *buffer, size_t buf_size, size_t *pos,
" Ctrl+D/U - Scroll half page down/up\n" " Ctrl+D/U - Scroll half page down/up\n"
" Ctrl+F/B - Scroll full page down/up\n" " Ctrl+F/B - Scroll full page down/up\n"
" g/G - Jump to top/bottom\n" " g/G - Jump to top/bottom\n"
" e/z - Switch English/Chinese\n"); " l - Cycle UI language\n");
return; return;
} }
@ -111,5 +111,5 @@ void help_text_append_full(char *buffer, size_t buf_size, size_t *pos,
" Ctrl+D/U - 向下/上滚动半页\n" " Ctrl+D/U - 向下/上滚动半页\n"
" Ctrl+F/B - 向下/上滚动整页\n" " Ctrl+F/B - 向下/上滚动整页\n"
" g/G - 跳到顶部/底部\n" " g/G - 跳到顶部/底部\n"
" e/z - 切换英文/中文\n"); " l - 切换界面语言\n");
} }

View file

@ -81,6 +81,10 @@ ui_lang_t i18n_default_ui_lang(void) {
return i18n_parse_ui_lang(locale, UI_LANG_EN); return i18n_parse_ui_lang(locale, UI_LANG_EN);
} }
ui_lang_t i18n_next_ui_lang(ui_lang_t lang) {
return lang == UI_LANG_EN ? UI_LANG_ZH : UI_LANG_EN;
}
const char *i18n_ui_lang_code(ui_lang_t lang) { const char *i18n_ui_lang_code(ui_lang_t lang) {
return lang == UI_LANG_ZH ? "zh" : "en"; return lang == UI_LANG_ZH ? "zh" : "en";
} }

View file

@ -51,8 +51,8 @@ static const i18n_text_entry_t text_catalog[I18N_TEXT_COUNT] = {
" 按键 " " 按键 "
}, },
[I18N_HELP_STATUS_FORMAT] = { [I18N_HELP_STATUS_FORMAT] = {
"-- KEY REFERENCE -- (%d/%d) j/k:scroll g/G:top/bottom e/z:lang q:close", "-- KEY REFERENCE -- (%d/%d) j/k:scroll g/G:top/bottom l:lang q:close",
"-- 按键参考 -- (%d/%d) j/k:滚动 g/G:首尾 e/z:语言 q:关闭" "-- 按键参考 -- (%d/%d) j/k:滚动 g/G:首尾 l:语言 q:关闭"
}, },
[I18N_COMMAND_OUTPUT_TITLE] = { [I18N_COMMAND_OUTPUT_TITLE] = {
" COMMAND OUTPUT ", " COMMAND OUTPUT ",

View file

@ -257,12 +257,8 @@ static bool handle_key(client_t *client, unsigned char key, char *input) {
if (key == 'q' || key == 27) { if (key == 'q' || key == 27) {
client->show_help = false; client->show_help = false;
tui_render_screen(client); tui_render_screen(client);
} else if (key == 'e' || key == 'E') { } else if (key == 'l' || key == 'L') {
client->ui_lang = UI_LANG_EN; client->ui_lang = i18n_next_ui_lang(client->ui_lang);
client->help_scroll_pos = 0;
tui_render_help(client);
} else if (key == 'z' || key == 'Z') {
client->ui_lang = UI_LANG_ZH;
client->help_scroll_pos = 0; client->help_scroll_pos = 0;
tui_render_help(client); tui_render_help(client);
} else if (key == 'j') { } else if (key == 'j') {

View file

@ -149,6 +149,14 @@ expect "TNT\\(1\\) 帮助"
expect "q:关闭" expect "q:关闭"
send -- "q" send -- "q"
expect "NORMAL" expect "NORMAL"
send -- "?"
expect "TNT 按键参考"
expect "l:语言"
send -- "l"
expect "TNT KEY REFERENCE"
expect "l:lang"
send -- "q"
expect "NORMAL"
send -- ":" send -- ":"
expect ":" expect ":"
send -- "lang en\r" send -- "lang en\r"

View file

@ -31,7 +31,8 @@ TEST(full_help_matches_language) {
assert(strstr(en, ":inbox") != NULL); assert(strstr(en, ":inbox") != NULL);
assert(strstr(en, ":support") == NULL); assert(strstr(en, ":support") == NULL);
assert(strstr(en, ":commands") == NULL); assert(strstr(en, ":commands") == NULL);
assert(strstr(en, "Switch English/Chinese") != NULL); assert(strstr(en, "Cycle UI language") != NULL);
assert(strstr(en, "Switch English/Chinese") == NULL);
assert(strstr(zh, "TNT 按键参考") != NULL); assert(strstr(zh, "TNT 按键参考") != NULL);
assert(strstr(zh, "可用命令") != NULL); assert(strstr(zh, "可用命令") != NULL);
@ -43,7 +44,8 @@ TEST(full_help_matches_language) {
assert(strstr(zh, "@用户名") == NULL); assert(strstr(zh, "@用户名") == NULL);
assert(strstr(zh, ":support") == NULL); assert(strstr(zh, ":support") == NULL);
assert(strstr(zh, ":commands") == NULL); assert(strstr(zh, ":commands") == NULL);
assert(strstr(zh, "切换英文/中文") != NULL); assert(strstr(zh, "切换界面语言") != NULL);
assert(strstr(zh, "切换英文/中文") == NULL);
assert_ascii_angle_placeholders(zh); assert_ascii_angle_placeholders(zh);
} }

View file

@ -89,8 +89,12 @@ TEST(text_lookup_matches_language) {
"匿名聊天室") != NULL); "匿名聊天室") != NULL);
assert(strstr(i18n_text(UI_LANG_EN, I18N_HELP_STATUS_FORMAT), assert(strstr(i18n_text(UI_LANG_EN, I18N_HELP_STATUS_FORMAT),
"KEY REFERENCE") != NULL); "KEY REFERENCE") != NULL);
assert(strstr(i18n_text(UI_LANG_EN, I18N_HELP_STATUS_FORMAT),
"l:lang") != NULL);
assert(strstr(i18n_text(UI_LANG_ZH, I18N_HELP_STATUS_FORMAT), assert(strstr(i18n_text(UI_LANG_ZH, I18N_HELP_STATUS_FORMAT),
"按键参考") != NULL); "按键参考") != NULL);
assert(strstr(i18n_text(UI_LANG_ZH, I18N_HELP_STATUS_FORMAT),
"l:语言") != NULL);
assert(strstr(i18n_text(UI_LANG_EN, I18N_COMMAND_OUTPUT_TITLE), assert(strstr(i18n_text(UI_LANG_EN, I18N_COMMAND_OUTPUT_TITLE),
"COMMAND") != NULL); "COMMAND") != NULL);
assert(strstr(i18n_text(UI_LANG_ZH, I18N_COMMAND_OUTPUT_TITLE), assert(strstr(i18n_text(UI_LANG_ZH, I18N_COMMAND_OUTPUT_TITLE),
@ -141,6 +145,8 @@ TEST(text_lookup_matches_language) {
"未知命令") != NULL); "未知命令") != NULL);
assert(strcmp(i18n_ui_lang_code(UI_LANG_EN), "en") == 0); assert(strcmp(i18n_ui_lang_code(UI_LANG_EN), "en") == 0);
assert(strcmp(i18n_ui_lang_code(UI_LANG_ZH), "zh") == 0); assert(strcmp(i18n_ui_lang_code(UI_LANG_ZH), "zh") == 0);
assert(i18n_next_ui_lang(UI_LANG_EN) == UI_LANG_ZH);
assert(i18n_next_ui_lang(UI_LANG_ZH) == UI_LANG_EN);
} }
TEST(text_catalog_is_complete) { TEST(text_catalog_is_complete) {