i18n: keep command placeholders locale neutral

This commit is contained in:
m1ngsama 2026-05-24 12:26:16 +08:00
parent 8fbd789dfb
commit 01439507d5
9 changed files with 40 additions and 22 deletions

View file

@ -6,6 +6,8 @@
- Split UI-language parsing from localized text lookup: `src/i18n.c` now owns
locale/code parsing, while `src/i18n_text.c` owns the table-driven text
catalog with coverage checks for every message ID.
- Kept command placeholders stable across localized output: Chinese help and
usage text now uses ASCII metavariables such as `<user>` and `<message>`.
- Renamed the internal language state from help-oriented names to
UI-language names (`ui_lang_t`, `client->ui_lang`, and
`i18n_*_ui_lang`) so future i18n work has a correctly named seam.

View file

@ -22,10 +22,10 @@ static const command_catalog_entry_t entries[] = {
},
{
{TNT_COMMAND_MSG, "msg", {"msg", "w", NULL}, true},
":msg <user> <text>, :w <user> <text>",
":msg <用户> <文本>, :w <用户> <文本>",
"Whisper to user", "私聊",
":msg <user> <text>", ":msg <用户> <文本>", 2
":msg <user> <message>, :w <user> <message>",
":msg <user> <message>, :w <user> <message>",
"Send private message", "发送私信",
":msg <user> <message>", ":msg <user> <message>", 2
},
{
{TNT_COMMAND_INBOX, "inbox", {"inbox", NULL}, false},
@ -35,9 +35,9 @@ static const command_catalog_entry_t entries[] = {
},
{
{TNT_COMMAND_NICK, "nick", {"nick", "name", NULL}, true},
":nick <name>, :name <name>", ":nick <名字>, :name <名字>",
":nick <name>, :name <name>", ":nick <name>, :name <name>",
"Change nickname", "更改昵称",
":nick <name>", ":nick <名字>", 2
":nick <name>", ":nick <name>", 2
},
{
{TNT_COMMAND_LAST, "last", {"last", NULL}, true},
@ -47,9 +47,9 @@ static const command_catalog_entry_t entries[] = {
},
{
{TNT_COMMAND_SEARCH, "search", {"search", NULL}, true},
":search <keyword>", ":search <关键词>",
":search <keyword>", ":search <keyword>",
"Search message history", "搜索消息历史",
":search <keyword>", ":search <>", 1
":search <keyword>", ":search <keyword>", 1
},
{
{TNT_COMMAND_MUTE_JOINS, "mute-joins", {"mute-joins", "mute", NULL}, false},

View file

@ -102,8 +102,8 @@ void help_text_append_full(char *buffer, size_t buf_size, size_t *pos,
" g/G - 跳到顶部/底部\n"
"\n"
"特殊消息:\n"
" /me <动作> - 发送动作 (如 /me 挥手)\n"
" @用户名 - 提及用户 (响铃+高亮)\n"
" /me <action> - 发送动作 (如 /me waves)\n"
" @username - 提及用户 (响铃+高亮)\n"
"\n"
"帮助界面按键:\n"
" q, ESC - 关闭帮助\n"

View file

@ -107,10 +107,10 @@ static const i18n_text_entry_t text_catalog[I18N_TEXT_COUNT] = {
"在线用户"
},
[I18N_MSG_USAGE] = {
"Usage: msg <username> <message>\n"
" w <username> <message>\n",
"用法: msg <用户名> <消息>\n"
" w <用户名> <消息>\n"
"Usage: msg <user> <message>\n"
" w <user> <message>\n",
"用法: msg <user> <message>\n"
" w <user> <message>\n"
},
[I18N_MSG_SENT_FORMAT] = {
"Whisper sent to %s\n",
@ -129,8 +129,8 @@ static const i18n_text_entry_t text_catalog[I18N_TEXT_COUNT] = {
"(空)"
},
[I18N_NICK_USAGE] = {
"Usage: nick <new_username>\n",
"用法: nick <新用户名>\n"
"Usage: nick <name>\n",
"用法: nick <name>\n"
},
[I18N_NICK_INVALID] = {
"Invalid username\n",
@ -158,7 +158,7 @@ static const i18n_text_entry_t text_catalog[I18N_TEXT_COUNT] = {
},
[I18N_SEARCH_USAGE] = {
"Usage: search <keyword>\n",
"用法: search <关键词>\n"
"用法: search <keyword>\n"
},
[I18N_SEARCH_HEADER_FORMAT] = {
"--- Search: \"%s\" (%d match(es)) ---\n",

View file

@ -266,21 +266,21 @@ expect "NORMAL"
send -- ":"
expect ":"
send -- "search\r"
expect "用法: search <关键词>"
expect "用法: search <keyword>"
expect "q:关闭"
send -- "q"
expect "NORMAL"
send -- ":"
expect ":"
send -- "msg\r"
expect "用法: msg <用户名> <消息>"
expect "用法: msg <user> <message>"
expect "q:关闭"
send -- "q"
expect "NORMAL"
send -- ":"
expect ":"
send -- "nick\r"
expect "用法: nick <新用户名>"
expect "用法: nick <name>"
expect "q:关闭"
send -- "q"
expect "NORMAL"

View file

@ -66,10 +66,14 @@ TEST(generates_localized_help_sections) {
assert(strstr(en, ":users, :list, :who") != NULL);
assert(strstr(en, "Show online users") != NULL);
assert(strstr(en, ":msg <user> <message>") != NULL);
assert(strstr(en, ":support") == NULL);
assert(strstr(zh, ":users, :list, :who") != NULL);
assert(strstr(zh, "显示在线用户") != NULL);
assert(strstr(zh, ":msg <user> <message>") != NULL);
assert(strstr(zh, "<用户>") == NULL);
assert(strstr(zh, "<消息>") == NULL);
assert(strstr(zh, ":support") == NULL);
}

View file

@ -36,6 +36,10 @@ TEST(full_help_matches_language) {
assert(strstr(zh, "可用命令") != NULL);
assert(strstr(zh, "命令输出按键") != NULL);
assert(strstr(zh, ":inbox") != NULL);
assert(strstr(zh, "/me <action>") != NULL);
assert(strstr(zh, "@username") != NULL);
assert(strstr(zh, "<动作>") == NULL);
assert(strstr(zh, "@用户名") == NULL);
assert(strstr(zh, ":support") == NULL);
assert(strstr(zh, ":commands") == NULL);
assert(strstr(zh, "切换英文/中文") != NULL);

View file

@ -111,13 +111,19 @@ TEST(text_lookup_matches_language) {
assert(strstr(i18n_text(UI_LANG_ZH, I18N_IDLE_TIMEOUT_FORMAT),
"空闲超时") != NULL);
assert(strstr(i18n_text(UI_LANG_EN, I18N_MSG_USAGE),
"msg <username>") != NULL);
"msg <user>") != NULL);
assert(strstr(i18n_text(UI_LANG_ZH, I18N_MSG_USAGE),
"用户名") != NULL);
"msg <user>") != NULL);
assert(strstr(i18n_text(UI_LANG_ZH, I18N_MSG_USAGE),
"<用户>") == NULL);
assert(strstr(i18n_text(UI_LANG_ZH, I18N_NICK_USAGE),
"nick <name>") != NULL);
assert(strstr(i18n_text(UI_LANG_EN, I18N_SEARCH_HEADER_FORMAT),
"Search") != NULL);
assert(strstr(i18n_text(UI_LANG_ZH, I18N_SEARCH_HEADER_FORMAT),
"搜索") != NULL);
assert(strstr(i18n_text(UI_LANG_ZH, I18N_SEARCH_USAGE),
"search <keyword>") != NULL);
assert(strstr(i18n_text(UI_LANG_EN, I18N_LANG_CURRENT_FORMAT),
"lang <en|zh>") != NULL);
assert(strstr(i18n_text(UI_LANG_ZH, I18N_LANG_CURRENT_FORMAT),

View file

@ -52,6 +52,8 @@ TEST(interactive_manual_matches_language) {
assert(strstr(zh, "命令") != NULL);
assert(strstr(zh, ":lang en|zh") != NULL);
assert(strstr(zh, ":mute-joins") != NULL);
assert(strstr(zh, ":msg <user> <message>") != NULL);
assert(strstr(zh, "<用户>") == NULL);
assert(strstr(zh, ":mute-joins, :clear, :q") != NULL);
assert(strstr(zh, ":support") == NULL);
assert(strstr(zh, ":commands") == NULL);