i18n: add session language command

This commit is contained in:
m1ngsama 2026-05-23 18:10:54 +08:00
parent 0c27976763
commit 2e69283e5c
9 changed files with 68 additions and 5 deletions

View file

@ -96,6 +96,7 @@ Ctrl+C - Exit chat
:search <keyword> - Search full message history (case-insensitive)
:mute-joins - Toggle join/leave system notifications
:support - Show quick support guide
:lang <en|zh> - Switch UI language for this session
:help - Show available commands
:clear - Clear command output
:q, :quit, :exit - Disconnect

View file

@ -6,6 +6,8 @@
- Added a first i18n boundary: `TNT_LANG` / locale detection now chooses the
default interactive UI language (`en` or `zh`) for username prompts, status
hints, help language, and `:support`.
- Added `:lang <en|zh>` so users can switch the interactive UI language for
their current session.
### Changed
- NORMAL mode now opens at the latest visible messages instead of the oldest

View file

@ -13,6 +13,7 @@ typedef enum {
I18N_NORMAL_NEW_MESSAGES
} i18n_text_id_t;
bool i18n_try_parse_lang(const char *value, help_lang_t *lang);
help_lang_t i18n_parse_lang(const char *value, help_lang_t fallback);
help_lang_t i18n_default_lang(void);
const char *i18n_lang_code(help_lang_t lang);

View file

@ -8,6 +8,7 @@
#include "chat_room.h"
#include "client.h"
#include "common.h"
#include "i18n.h"
#include "message.h"
#include "support.h"
#include "tui.h"
@ -82,7 +83,8 @@ static const char *suggest_command(const char *cmd) {
static const char *commands[] = {
"list", "users", "who", "nick", "name", "msg", "w", "inbox",
"last", "search", "mute-joins", "mute", "support", "guide",
"help", "commands", "clear", "cls", "q", "quit", "exit"
"lang", "language", "help", "commands", "clear", "cls",
"q", "quit", "exit"
};
const char *best = NULL;
int best_distance = 99;
@ -177,6 +179,7 @@ void commands_dispatch(client_t *client) {
"search <keyword> - Search message history\n"
"mute-joins - Toggle join/leave notices\n"
"support - Show quick support guide\n"
"lang [en|zh] - Show or switch UI language\n"
"help, commands - Show this help\n"
"clear, cls - Clear command output\n"
"q, quit, exit - Disconnect\n"
@ -191,6 +194,34 @@ void commands_dispatch(client_t *client) {
support_append_interactive_panel(output, sizeof(output), &pos,
client->help_lang);
} else if (strcmp(cmd, "lang") == 0 || strcmp(cmd, "language") == 0 ||
strncmp(cmd, "lang ", 5) == 0 ||
strncmp(cmd, "language ", 9) == 0) {
char *arg = NULL;
help_lang_t next_lang;
if (strncmp(cmd, "lang ", 5) == 0) {
arg = cmd + 5;
} else if (strncmp(cmd, "language ", 9) == 0) {
arg = cmd + 9;
}
if (!arg || arg[0] == '\0') {
buffer_appendf(output, sizeof(output), &pos,
"Current language: %s\n"
"Usage: lang <en|zh>\n",
i18n_lang_code(client->help_lang));
} else if (i18n_try_parse_lang(arg, &next_lang)) {
client->help_lang = next_lang;
buffer_appendf(output, sizeof(output), &pos,
"Language set to: %s\n",
i18n_lang_code(client->help_lang));
} else {
buffer_appendf(output, sizeof(output), &pos,
"Unsupported language: %s\n"
"Usage: lang <en|zh>\n", arg);
}
} else if (strncmp(cmd, "msg ", 4) == 0 || strncmp(cmd, "w ", 2) == 0) {
char *rest = (cmd[0] == 'w') ? cmd + 2 : cmd + 4;
while (*rest == ' ') rest++;

View file

@ -17,23 +17,33 @@ static bool starts_with_lang(const char *value, const char *prefix) {
return *value == '\0' || *value == '_' || *value == '-' || *value == '.';
}
help_lang_t i18n_parse_lang(const char *value, help_lang_t fallback) {
bool i18n_try_parse_lang(const char *value, help_lang_t *lang) {
if (!value || value[0] == '\0') {
return fallback;
return false;
}
if (starts_with_lang(value, "zh") ||
starts_with_lang(value, "cn") ||
starts_with_lang(value, "chinese")) {
return LANG_ZH;
if (lang) *lang = LANG_ZH;
return true;
}
if (starts_with_lang(value, "en") ||
starts_with_lang(value, "c") ||
starts_with_lang(value, "posix")) {
return LANG_EN;
if (lang) *lang = LANG_EN;
return true;
}
return false;
}
help_lang_t i18n_parse_lang(const char *value, help_lang_t fallback) {
help_lang_t lang;
if (i18n_try_parse_lang(value, &lang)) {
return lang;
}
return fallback;
}

View file

@ -784,6 +784,7 @@ const char* tui_get_help_text(help_lang_t lang) {
" :search <keyword> - Search message history\n"
" :mute-joins - Toggle join/leave notices\n"
" :support - Show quick support guide\n"
" :lang <en|zh> - Switch UI language\n"
" :help - Show available commands\n"
" :clear - Clear command output\n"
" :q, :quit, :exit - Disconnect\n"
@ -838,6 +839,7 @@ const char* tui_get_help_text(help_lang_t lang) {
" :search <关键词> - 搜索消息历史\n"
" :mute-joins - 切换加入/离开提示\n"
" :support - 显示快速支持指南\n"
" :lang <en|zh> - 切换界面语言\n"
" :help - 显示可用命令\n"
" :clear - 清空命令输出\n"
" :q, :quit, :exit - 断开连接\n"

View file

@ -148,6 +148,13 @@ send -- "support\r"
expect "支持"
expect "Press any key"
send -- "q"
expect "NORMAL"
send -- ":"
expect ":"
send -- "lang en\r"
expect "Language set to: en"
expect "Press any key"
send -- "q"
sleep 0.2
send -- "\003"
sleep 0.2

View file

@ -17,11 +17,19 @@
static int tests_passed = 0;
TEST(parse_explicit_languages) {
help_lang_t lang;
assert(i18n_parse_lang("zh", LANG_EN) == LANG_ZH);
assert(i18n_parse_lang("zh_CN.UTF-8", LANG_EN) == LANG_ZH);
assert(i18n_parse_lang("cn", LANG_EN) == LANG_ZH);
assert(i18n_parse_lang("en", LANG_ZH) == LANG_EN);
assert(i18n_parse_lang("en_US.UTF-8", LANG_ZH) == LANG_EN);
assert(i18n_try_parse_lang("zh", &lang) == true);
assert(lang == LANG_ZH);
assert(i18n_try_parse_lang("en", &lang) == true);
assert(lang == LANG_EN);
assert(i18n_try_parse_lang("fr", &lang) == false);
}
TEST(parse_unknown_uses_fallback) {

1
tnt.1
View file

@ -121,6 +121,7 @@ l l.
:search \fIkeyword\fR Case\-insensitive search across full message history
:mute\-joins Toggle join/leave system notifications on/off
:support Show quick support guide
:lang \fIen|zh\fR Switch UI language for this session
:help Show available commands
:clear Clear command output
:q, :quit, :exit Disconnect