i18n: localize startup cli text

This commit is contained in:
m1ngsama 2026-05-23 20:08:18 +08:00
parent fd6cdbf627
commit 73655d0e70
7 changed files with 157 additions and 21 deletions

1
.gitignore vendored
View file

@ -16,3 +16,4 @@ tests/unit/test_i18n
tests/unit/test_system_message
tests/unit/test_help_text
tests/unit/test_support_text
tests/unit/test_cli_text

View file

@ -36,6 +36,8 @@
- Exec-mode help, usage errors, unknown-command feedback, and post validation
messages now follow `TNT_LANG` while preserving stable machine-readable
command output.
- Startup CLI help and option errors now live in a dedicated `cli_text` module
and follow `TNT_LANG` / locale for English and Chinese users.
### Changed
- NORMAL mode now opens at the latest visible messages instead of the oldest

12
include/cli_text.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef CLI_TEXT_H
#define CLI_TEXT_H
#include "common.h"
void cli_text_append_help(char *buffer, size_t buf_size, size_t *pos,
const char *program_name, help_lang_t lang);
const char *cli_text_invalid_port_format(help_lang_t lang);
const char *cli_text_unknown_option_format(help_lang_t lang);
const char *cli_text_short_usage_format(help_lang_t lang);
#endif /* CLI_TEXT_H */

62
src/cli_text.c Normal file
View file

@ -0,0 +1,62 @@
#include "cli_text.h"
void cli_text_append_help(char *buffer, size_t buf_size, size_t *pos,
const char *program_name, help_lang_t lang) {
const char *program = (program_name && program_name[0] != '\0')
? program_name
: "tnt";
if (lang == LANG_ZH) {
buffer_appendf(buffer, buf_size, pos,
"tnt %s - 匿名 SSH 聊天服务器\n\n"
"用法: %s [选项]\n\n"
"选项:\n"
" -p, --port PORT 监听 PORT (默认: %d)\n"
" -d, --state-dir DIR 将主机密钥和日志存放在 DIR\n"
" -V, --version 显示版本\n"
" -h, --help 显示此帮助\n"
"\n"
"环境变量:\n"
" PORT 默认监听端口\n"
" TNT_STATE_DIR 状态目录\n"
" TNT_ACCESS_TOKEN 要求 SSH 认证使用此密码\n"
" TNT_LANG UI 语言: en 或 zh (默认跟随 locale)\n"
" TNT_MAX_CONNECTIONS 全局连接数限制 (默认: 64)\n"
" TNT_RATE_LIMIT 设为 0 可禁用速率限制\n"
" TNT_IDLE_TIMEOUT 空闲断开时间,单位秒 (默认: 1800)\n",
TNT_VERSION, program, DEFAULT_PORT);
return;
}
buffer_appendf(buffer, buf_size, pos,
"tnt %s - anonymous SSH chat server\n\n"
"Usage: %s [options]\n\n"
"Options:\n"
" -p, --port PORT Listen on PORT (default: %d)\n"
" -d, --state-dir DIR Store host key and logs in DIR\n"
" -V, --version Show version\n"
" -h, --help Show this help\n"
"\n"
"Environment:\n"
" PORT Default listening port\n"
" TNT_STATE_DIR State directory\n"
" TNT_ACCESS_TOKEN Require this password for SSH auth\n"
" TNT_LANG UI language: en or zh (default: locale)\n"
" TNT_MAX_CONNECTIONS Global connection limit (default: 64)\n"
" TNT_RATE_LIMIT Set to 0 to disable rate limiting\n"
" TNT_IDLE_TIMEOUT Idle disconnect timeout in seconds (default: 1800)\n",
TNT_VERSION, program, DEFAULT_PORT);
}
const char *cli_text_invalid_port_format(help_lang_t lang) {
return lang == LANG_ZH ? "端口无效: %s\n" : "Invalid port: %s\n";
}
const char *cli_text_unknown_option_format(help_lang_t lang) {
return lang == LANG_ZH ? "未知选项: %s\n" : "Unknown option: %s\n";
}
const char *cli_text_short_usage_format(help_lang_t lang) {
return lang == LANG_ZH ? "用法: %s [-p PORT] [-d DIR] [-h]\n"
: "Usage: %s [-p PORT] [-d DIR] [-h]\n";
}

View file

@ -1,7 +1,9 @@
#include "common.h"
#include "ssh_server.h"
#include "chat_room.h"
#include "cli_text.h"
#include "common.h"
#include "i18n.h"
#include "message.h"
#include "ssh_server.h"
#include <signal.h>
#include <unistd.h>
@ -18,6 +20,7 @@ static void signal_handler(int sig) {
int main(int argc, char **argv) {
int port = DEFAULT_PORT;
help_lang_t lang = i18n_default_lang();
/* Environment provides defaults; command-line flags override it. */
const char *port_env = getenv("PORT");
@ -36,7 +39,8 @@ int main(int argc, char **argv) {
char *end;
long val = strtol(argv[i + 1], &end, 10);
if (*end != '\0' || val <= 0 || val > 65535) {
fprintf(stderr, "Invalid port: %s\n", argv[i + 1]);
fprintf(stderr, cli_text_invalid_port_format(lang),
argv[i + 1]);
return 1;
}
port = (int)val;
@ -52,25 +56,15 @@ int main(int argc, char **argv) {
printf("tnt %s\n", TNT_VERSION);
return 0;
} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
printf("tnt %s - anonymous SSH chat server\n\n", TNT_VERSION);
printf("Usage: %s [options]\n\n", argv[0]);
printf("Options:\n");
printf(" -p, --port PORT Listen on PORT (default: %d)\n", DEFAULT_PORT);
printf(" -d, --state-dir DIR Store host key and logs in DIR\n");
printf(" -V, --version Show version\n");
printf(" -h, --help Show this help\n");
printf("\nEnvironment:\n");
printf(" PORT Default listening port\n");
printf(" TNT_STATE_DIR State directory\n");
printf(" TNT_ACCESS_TOKEN Require this password for SSH auth\n");
printf(" TNT_LANG UI language: en or zh (default: locale)\n");
printf(" TNT_MAX_CONNECTIONS Global connection limit (default: 64)\n");
printf(" TNT_RATE_LIMIT Set to 0 to disable rate limiting\n");
printf(" TNT_IDLE_TIMEOUT Idle disconnect timeout in seconds (default: 1800)\n");
char output[2048] = {0};
size_t pos = 0;
cli_text_append_help(output, sizeof(output), &pos, argv[0], lang);
fputs(output, stdout);
return 0;
} else {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
fprintf(stderr, "Usage: %s [-p PORT] [-d DIR] [-h]\n", argv[0]);
fprintf(stderr, cli_text_unknown_option_format(lang), argv[i]);
fprintf(stderr, cli_text_short_usage_format(lang), argv[0]);
return 1;
}
}

View file

@ -13,6 +13,7 @@ endif
UTF8_SRC = ../../src/utf8.c
MESSAGE_SRC = ../../src/message.c
COMMON_SRC = ../../src/common.c
CLI_TEXT_SRC = ../../src/cli_text.c
CHAT_ROOM_SRC = ../../src/chat_room.c
HISTORY_VIEW_SRC = ../../src/history_view.c
I18N_SRC = ../../src/i18n.c
@ -20,7 +21,7 @@ SYSTEM_MESSAGE_SRC = ../../src/system_message.c
HELP_TEXT_SRC = ../../src/help_text.c
SUPPORT_TEXT_SRC = ../../src/support_text.c
TESTS = test_utf8 test_message test_chat_room test_history_view test_i18n test_system_message test_help_text test_support_text
TESTS = test_utf8 test_message test_chat_room test_history_view test_i18n test_system_message test_help_text test_support_text test_cli_text
.PHONY: all clean run
@ -50,6 +51,9 @@ test_help_text: test_help_text.c $(HELP_TEXT_SRC) $(COMMON_SRC)
test_support_text: test_support_text.c $(SUPPORT_TEXT_SRC)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
test_cli_text: test_cli_text.c $(CLI_TEXT_SRC) $(COMMON_SRC)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
run: all
@echo "=== Running UTF-8 Tests ==="
./test_utf8
@ -74,6 +78,9 @@ run: all
@echo ""
@echo "=== Running Support Text Tests ==="
./test_support_text
@echo ""
@echo "=== Running CLI Text Tests ==="
./test_cli_text
clean:
rm -f $(TESTS) *.o test_messages.log

View file

@ -0,0 +1,58 @@
/* Unit tests for command-line help and error text */
#include "../../include/cli_text.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#define TEST(name) static void test_##name()
#define RUN_TEST(name) do { \
printf("Running %s... ", #name); \
test_##name(); \
printf("\n"); \
tests_passed++; \
} while(0)
static int tests_passed = 0;
TEST(help_matches_language) {
char output[2048] = {0};
size_t pos = 0;
cli_text_append_help(output, sizeof(output), &pos, "tnt", LANG_EN);
assert(strstr(output, "anonymous SSH chat server") != NULL);
assert(strstr(output, "Usage: tnt [options]") != NULL);
assert(strstr(output, "TNT_LANG") != NULL);
memset(output, 0, sizeof(output));
pos = 0;
cli_text_append_help(output, sizeof(output), &pos, "tnt", LANG_ZH);
assert(strstr(output, "匿名 SSH 聊天服务器") != NULL);
assert(strstr(output, "用法: tnt [选项]") != NULL);
assert(strstr(output, "TNT_LANG") != NULL);
}
TEST(error_formats_match_language) {
assert(strcmp(cli_text_invalid_port_format(LANG_EN),
"Invalid port: %s\n") == 0);
assert(strcmp(cli_text_invalid_port_format(LANG_ZH),
"端口无效: %s\n") == 0);
assert(strcmp(cli_text_unknown_option_format(LANG_EN),
"Unknown option: %s\n") == 0);
assert(strcmp(cli_text_unknown_option_format(LANG_ZH),
"未知选项: %s\n") == 0);
assert(strcmp(cli_text_short_usage_format(LANG_EN),
"Usage: %s [-p PORT] [-d DIR] [-h]\n") == 0);
assert(strcmp(cli_text_short_usage_format(LANG_ZH),
"用法: %s [-p PORT] [-d DIR] [-h]\n") == 0);
}
int main(void) {
printf("Running CLI text unit tests...\n\n");
RUN_TEST(help_matches_language);
RUN_TEST(error_formats_match_language);
printf("\n✓ All %d tests passed!\n", tests_passed);
return 0;
}