i18n: localize exec guidance text

This commit is contained in:
m1ngsama 2026-05-23 20:03:31 +08:00
parent 81c3f45864
commit fd6cdbf627
6 changed files with 133 additions and 21 deletions

View file

@ -33,6 +33,9 @@
use the shared i18n table instead of inline command-flow conditionals.
- Interactive and exec support guide copy now lives in a dedicated
`support_text` module, with focused language-selection unit coverage.
- Exec-mode help, usage errors, unknown-command feedback, and post validation
messages now follow `TNT_LANG` while preserving stable machine-readable
command output.
### Changed
- NORMAL mode now opens at the latest visible messages instead of the oldest

View file

@ -51,6 +51,14 @@ typedef enum {
I18N_UNKNOWN_COMMAND_FORMAT,
I18N_DID_YOU_MEAN_FORMAT,
I18N_UNKNOWN_GUIDANCE,
I18N_EXEC_HELP,
I18N_EXEC_USERS_USAGE,
I18N_EXEC_STATS_USAGE,
I18N_EXEC_TAIL_USAGE,
I18N_EXEC_POST_USAGE,
I18N_EXEC_POST_EMPTY,
I18N_EXEC_POST_INVALID_UTF8,
I18N_EXEC_UNKNOWN_COMMAND_FORMAT,
I18N_CONTINUE_PROMPT
} i18n_text_id_t;

View file

@ -2,6 +2,7 @@
#include "chat_room.h"
#include "client.h"
#include "common.h"
#include "i18n.h"
#include "input.h"
#include "message.h"
#include "ratelimit.h"
@ -116,21 +117,9 @@ static void resolve_exec_username(const client_t *client, char *buffer,
}
static int exec_command_help(client_t *client) {
static const char help_text[] =
"TNT exec interface\n"
"Commands:\n"
" help Show this help\n"
" health Print service health\n"
" users [--json] List online users\n"
" stats [--json] Print room statistics\n"
" tail [N] Print recent messages\n"
" tail -n N Print recent messages\n"
" post MESSAGE Post a message non-interactively\n"
" post \"/me act\" Post an action message\n"
" support Show quick support guide\n"
" exit Exit successfully\n";
const char *help_text = i18n_text(client->help_lang, I18N_EXEC_HELP);
return client_send(client, help_text, sizeof(help_text) - 1) == 0 ? 0 : 1;
return client_send(client, help_text, strlen(help_text)) == 0 ? 0 : 1;
}
static int exec_command_support(client_t *client) {
@ -304,7 +293,8 @@ static int exec_command_tail(client_t *client, const char *args) {
int rc;
if (parse_tail_count(args, &requested) < 0) {
client_printf(client, "tail: usage: tail [N] | tail -n N\n");
client_printf(client, "%s",
i18n_text(client->help_lang, I18N_EXEC_TAIL_USAGE));
return 64;
}
@ -357,7 +347,8 @@ static int exec_command_post(client_t *client, const char *args) {
};
if (!args || args[0] == '\0') {
client_printf(client, "post: usage: post MESSAGE\n");
client_printf(client, "%s",
i18n_text(client->help_lang, I18N_EXEC_POST_USAGE));
return 64;
}
@ -366,12 +357,15 @@ static int exec_command_post(client_t *client, const char *args) {
trim_ascii_whitespace(content);
if (content[0] == '\0') {
client_printf(client, "post: message cannot be empty\n");
client_printf(client, "%s",
i18n_text(client->help_lang, I18N_EXEC_POST_EMPTY));
return 64;
}
if (!utf8_is_valid_string(content)) {
client_printf(client, "post: invalid UTF-8 input\n");
client_printf(client, "%s",
i18n_text(client->help_lang,
I18N_EXEC_POST_INVALID_UTF8));
return 1;
}
@ -443,14 +437,18 @@ int exec_dispatch(client_t *client) {
}
if (strcmp(cmd, "users") == 0) {
if (args && strcmp(args, "--json") != 0) {
client_printf(client, "users: usage: users [--json]\n");
client_printf(client, "%s",
i18n_text(client->help_lang,
I18N_EXEC_USERS_USAGE));
return 64;
}
return exec_command_users(client, args != NULL);
}
if (strcmp(cmd, "stats") == 0) {
if (args && strcmp(args, "--json") != 0) {
client_printf(client, "stats: usage: stats [--json]\n");
client_printf(client, "%s",
i18n_text(client->help_lang,
I18N_EXEC_STATS_USAGE));
return 64;
}
return exec_command_stats(client, args != NULL);
@ -465,6 +463,9 @@ int exec_dispatch(client_t *client) {
return 0;
}
client_printf(client, "Unknown command: %s\n", cmd);
client_printf(client,
i18n_text(client->help_lang,
I18N_EXEC_UNKNOWN_COMMAND_FORMAT),
cmd);
return 64;
}

View file

@ -168,6 +168,33 @@ const char *i18n_text(help_lang_t lang, i18n_text_id_t id) {
return "你是想输入 :%s 吗?\n";
case I18N_UNKNOWN_GUIDANCE:
return "输入 :support 查看引导,或 :help 查看命令\n";
case I18N_EXEC_HELP:
return "TNT exec 接口\n"
"命令:\n"
" help 显示此帮助\n"
" health 输出服务健康状态\n"
" users [--json] 列出在线用户\n"
" stats [--json] 输出房间统计\n"
" tail [N] 输出最近消息\n"
" tail -n N 输出最近消息\n"
" post MESSAGE 非交互发送消息\n"
" post \"/me act\" 发送动作消息\n"
" support 显示快速支持指南\n"
" exit 成功退出\n";
case I18N_EXEC_USERS_USAGE:
return "users: 用法: users [--json]\n";
case I18N_EXEC_STATS_USAGE:
return "stats: 用法: stats [--json]\n";
case I18N_EXEC_TAIL_USAGE:
return "tail: 用法: tail [N] | tail -n N\n";
case I18N_EXEC_POST_USAGE:
return "post: 用法: post MESSAGE\n";
case I18N_EXEC_POST_EMPTY:
return "post: 消息不能为空\n";
case I18N_EXEC_POST_INVALID_UTF8:
return "post: 输入不是有效 UTF-8\n";
case I18N_EXEC_UNKNOWN_COMMAND_FORMAT:
return "未知命令: %s\n";
case I18N_CONTINUE_PROMPT:
return "\n按任意键继续...";
}
@ -271,6 +298,33 @@ const char *i18n_text(help_lang_t lang, i18n_text_id_t id) {
return "Did you mean :%s?\n";
case I18N_UNKNOWN_GUIDANCE:
return "Type :support for guidance or :help for commands\n";
case I18N_EXEC_HELP:
return "TNT exec interface\n"
"Commands:\n"
" help Show this help\n"
" health Print service health\n"
" users [--json] List online users\n"
" stats [--json] Print room statistics\n"
" tail [N] Print recent messages\n"
" tail -n N Print recent messages\n"
" post MESSAGE Post a message non-interactively\n"
" post \"/me act\" Post an action message\n"
" support Show quick support guide\n"
" exit Exit successfully\n";
case I18N_EXEC_USERS_USAGE:
return "users: usage: users [--json]\n";
case I18N_EXEC_STATS_USAGE:
return "stats: usage: stats [--json]\n";
case I18N_EXEC_TAIL_USAGE:
return "tail: usage: tail [N] | tail -n N\n";
case I18N_EXEC_POST_USAGE:
return "post: usage: post MESSAGE\n";
case I18N_EXEC_POST_EMPTY:
return "post: message cannot be empty\n";
case I18N_EXEC_POST_INVALID_UTF8:
return "post: invalid UTF-8 input\n";
case I18N_EXEC_UNKNOWN_COMMAND_FORMAT:
return "Unknown command: %s\n";
case I18N_CONTINUE_PROMPT:
return "\nPress any key to continue...";
}

View file

@ -87,6 +87,40 @@ else
FAIL=$((FAIL + 1))
fi
HELP_OUTPUT=$(ssh $SSH_OPTS localhost help 2>/dev/null || true)
printf '%s\n' "$HELP_OUTPUT" | grep -q '^TNT exec 接口$' &&
printf '%s\n' "$HELP_OUTPUT" | grep -q '^命令:$'
if [ $? -eq 0 ]; then
echo "✓ help follows TNT_LANG"
PASS=$((PASS + 1))
else
echo "✗ help output unexpected"
printf '%s\n' "$HELP_OUTPUT"
FAIL=$((FAIL + 1))
fi
UNKNOWN_OUTPUT=$(ssh $SSH_OPTS localhost nope 2>/dev/null || true)
printf '%s\n' "$UNKNOWN_OUTPUT" | grep -q '^未知命令: nope$'
if [ $? -eq 0 ]; then
echo "✓ unknown command follows TNT_LANG"
PASS=$((PASS + 1))
else
echo "✗ unknown command output unexpected"
printf '%s\n' "$UNKNOWN_OUTPUT"
FAIL=$((FAIL + 1))
fi
POST_USAGE=$(ssh $SSH_OPTS localhost post 2>/dev/null || true)
printf '%s\n' "$POST_USAGE" | grep -q '^post: 用法: post MESSAGE$'
if [ $? -eq 0 ]; then
echo "✓ post usage follows TNT_LANG"
PASS=$((PASS + 1))
else
echo "✗ post usage output unexpected"
printf '%s\n' "$POST_USAGE"
FAIL=$((FAIL + 1))
fi
POST_OUTPUT=$(ssh $SSH_OPTS execposter@localhost post "hello from exec" 2>/dev/null || true)
if [ "$POST_OUTPUT" = "posted" ]; then
echo "✓ post publishes a message"

View file

@ -98,6 +98,18 @@ TEST(text_lookup_matches_language) {
"Unknown command") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_UNKNOWN_COMMAND_FORMAT),
"未知命令") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_EXEC_HELP),
"TNT exec interface") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_EXEC_HELP),
"TNT exec 接口") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_EXEC_POST_EMPTY),
"message cannot be empty") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_EXEC_POST_EMPTY),
"消息不能为空") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_EXEC_UNKNOWN_COMMAND_FORMAT),
"Unknown command") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_EXEC_UNKNOWN_COMMAND_FORMAT),
"未知命令") != NULL);
assert(strcmp(i18n_lang_code(LANG_EN), "en") == 0);
assert(strcmp(i18n_lang_code(LANG_ZH), "zh") == 0);
}