diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 59478bb..5bd2048 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -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 diff --git a/include/i18n.h b/include/i18n.h index 9d19e71..c7f1193 100644 --- a/include/i18n.h +++ b/include/i18n.h @@ -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; diff --git a/src/exec.c b/src/exec.c index 37dfd37..319d661 100644 --- a/src/exec.c +++ b/src/exec.c @@ -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; } diff --git a/src/i18n.c b/src/i18n.c index d29bc02..90a333c 100644 --- a/src/i18n.c +++ b/src/i18n.c @@ -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..."; } diff --git a/tests/test_exec_mode.sh b/tests/test_exec_mode.sh index 3974dff..880f22d 100755 --- a/tests/test_exec_mode.sh +++ b/tests/test_exec_mode.sh @@ -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" diff --git a/tests/unit/test_i18n.c b/tests/unit/test_i18n.c index 4dc4780..ea587f6 100644 --- a/tests/unit/test_i18n.c +++ b/tests/unit/test_i18n.c @@ -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); }