i18n: rename help language state to ui language

This commit is contained in:
m1ngsama 2026-05-24 12:11:54 +08:00
parent 1f1c2398b6
commit 06a10e2df8
30 changed files with 216 additions and 210 deletions

View file

@ -3,6 +3,9 @@
## Unreleased ## Unreleased
### Changed ### Changed
- 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.
- Command names, aliases, help summaries, concise-manual command rows, and - Command names, aliases, help summaries, concise-manual command rows, and
unknown-command suggestions now share a dedicated `command_catalog` module. unknown-command suggestions now share a dedicated `command_catalog` module.
- COMMAND-mode output is now a small scrollable pager with `j/k`, page - COMMAND-mode output is now a small scrollable pager with `j/k`, page
@ -28,7 +31,7 @@
### Added ### Added
- Added a first i18n boundary: `TNT_LANG` / locale detection now chooses the - Added a first i18n boundary: `TNT_LANG` / locale detection now chooses the
default interactive UI language (`en` or `zh`) for username prompts, status default interactive UI language (`en` or `zh`) for username prompts, status
hints, help language, and `:support`. hints, help output, and `:support`.
- Added `:lang <en|zh>` so users can switch the interactive UI language for - Added `:lang <en|zh>` so users can switch the interactive UI language for
their current session. their current session.
- COMMAND-mode `:help`, unknown-command guidance, language command output, and - COMMAND-mode `:help`, unknown-command guidance, language command output, and

View file

@ -417,6 +417,9 @@ keys.
2. **Stable language identifiers** 2. **Stable language identifiers**
- Interactive `:lang` accepts only stable language codes: `en` and `zh`. - Interactive `:lang` accepts only stable language codes: `en` and `zh`.
- Code should name this concept `ui_lang`, not `help_lang`; the same value
controls prompts, status text, help, command output, MOTD chrome, and
system messages.
- Locale detection may accept locale-shaped values such as - Locale detection may accept locale-shaped values such as
`en_US.UTF-8`, `zh_CN.UTF-8`, `C`, and `POSIX`. `en_US.UTF-8`, `zh_CN.UTF-8`, `C`, and `POSIX`.
- Do not accept natural-language labels such as `english`, `chinese`, - Do not accept natural-language labels such as `english`, `chinese`,

View file

@ -4,9 +4,9 @@
#include "common.h" #include "common.h"
void cli_text_append_help(char *buffer, size_t buf_size, size_t *pos, 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_name, ui_lang_t lang);
const char *cli_text_invalid_port_format(help_lang_t lang); const char *cli_text_invalid_port_format(ui_lang_t lang);
const char *cli_text_unknown_option_format(help_lang_t lang); const char *cli_text_unknown_option_format(ui_lang_t lang);
const char *cli_text_short_usage_format(help_lang_t lang); const char *cli_text_short_usage_format(ui_lang_t lang);
#endif /* CLI_TEXT_H */ #endif /* CLI_TEXT_H */

View file

@ -30,8 +30,8 @@ bool command_catalog_match(const char *line, tnt_command_id_t *id,
const char **args); const char **args);
const char *command_catalog_suggest(const char *name); const char *command_catalog_suggest(const char *name);
void command_catalog_append_full(char *buffer, size_t buf_size, size_t *pos, void command_catalog_append_full(char *buffer, size_t buf_size, size_t *pos,
help_lang_t lang); ui_lang_t lang);
void command_catalog_append_manual(char *buffer, size_t buf_size, size_t *pos, void command_catalog_append_manual(char *buffer, size_t buf_size, size_t *pos,
help_lang_t lang); ui_lang_t lang);
#endif /* COMMAND_CATALOG_H */ #endif /* COMMAND_CATALOG_H */

View file

@ -44,11 +44,11 @@ typedef enum {
MODE_HELP MODE_HELP
} client_mode_t; } client_mode_t;
/* Help language */ /* UI language */
typedef enum { typedef enum {
LANG_EN, UI_LANG_EN,
LANG_ZH UI_LANG_ZH
} help_lang_t; } ui_lang_t;
/* Runtime helpers */ /* Runtime helpers */
const char* tnt_state_dir(void); const char* tnt_state_dir(void);

View file

@ -4,6 +4,6 @@
#include "common.h" #include "common.h"
void help_text_append_full(char *buffer, size_t buf_size, size_t *pos, void help_text_append_full(char *buffer, size_t buf_size, size_t *pos,
help_lang_t lang); ui_lang_t lang);
#endif /* HELP_TEXT_H */ #endif /* HELP_TEXT_H */

View file

@ -63,10 +63,10 @@ typedef enum {
I18N_EXEC_UNKNOWN_COMMAND_FORMAT I18N_EXEC_UNKNOWN_COMMAND_FORMAT
} i18n_text_id_t; } i18n_text_id_t;
bool i18n_try_parse_lang(const char *value, help_lang_t *lang); bool i18n_try_parse_ui_lang(const char *value, ui_lang_t *lang);
help_lang_t i18n_parse_lang(const char *value, help_lang_t fallback); ui_lang_t i18n_parse_ui_lang(const char *value, ui_lang_t fallback);
help_lang_t i18n_default_lang(void); ui_lang_t i18n_default_ui_lang(void);
const char *i18n_lang_code(help_lang_t lang); const char *i18n_ui_lang_code(ui_lang_t lang);
const char *i18n_text(help_lang_t lang, i18n_text_id_t id); const char *i18n_text(ui_lang_t lang, i18n_text_id_t id);
#endif /* I18N_H */ #endif /* I18N_H */

View file

@ -4,6 +4,6 @@
#include "common.h" #include "common.h"
void manual_append_interactive_panel(char *buffer, size_t buf_size, void manual_append_interactive_panel(char *buffer, size_t buf_size,
size_t *pos, help_lang_t lang); size_t *pos, ui_lang_t lang);
#endif /* MANUAL_H */ #endif /* MANUAL_H */

View file

@ -4,6 +4,6 @@
#include "common.h" #include "common.h"
void manual_text_append_interactive(char *buffer, size_t buf_size, void manual_text_append_interactive(char *buffer, size_t buf_size,
size_t *pos, help_lang_t lang); size_t *pos, ui_lang_t lang);
#endif /* MANUAL_TEXT_H */ #endif /* MANUAL_TEXT_H */

View file

@ -26,7 +26,7 @@ typedef struct client {
_Atomic int width; _Atomic int width;
_Atomic int height; _Atomic int height;
client_mode_t mode; client_mode_t mode;
help_lang_t help_lang; ui_lang_t ui_lang;
int scroll_pos; int scroll_pos;
bool follow_tail; /* NORMAL stays pinned to latest until user scrolls up */ bool follow_tail; /* NORMAL stays pinned to latest until user scrolls up */
int help_scroll_pos; int help_scroll_pos;

View file

@ -5,11 +5,11 @@
#include "message.h" #include "message.h"
void system_message_make_join(message_t *msg, const char *username, void system_message_make_join(message_t *msg, const char *username,
help_lang_t lang); ui_lang_t lang);
void system_message_make_leave(message_t *msg, const char *username, void system_message_make_leave(message_t *msg, const char *username,
help_lang_t lang); ui_lang_t lang);
void system_message_make_nick(message_t *msg, const char *old_name, void system_message_make_nick(message_t *msg, const char *old_name,
const char *new_name, help_lang_t lang); const char *new_name, ui_lang_t lang);
bool system_message_is_system(const message_t *msg); bool system_message_is_system(const message_t *msg);
bool system_message_is_join_leave(const message_t *msg); bool system_message_is_join_leave(const message_t *msg);

View file

@ -1,12 +1,12 @@
#include "cli_text.h" #include "cli_text.h"
void cli_text_append_help(char *buffer, size_t buf_size, size_t *pos, 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_name, ui_lang_t lang) {
const char *program = (program_name && program_name[0] != '\0') const char *program = (program_name && program_name[0] != '\0')
? program_name ? program_name
: "tnt"; : "tnt";
if (lang == LANG_ZH) { if (lang == UI_LANG_ZH) {
buffer_appendf(buffer, buf_size, pos, buffer_appendf(buffer, buf_size, pos,
"tnt %s - 匿名 SSH 聊天服务器\n\n" "tnt %s - 匿名 SSH 聊天服务器\n\n"
"用法: %s [选项]\n\n" "用法: %s [选项]\n\n"
@ -48,15 +48,15 @@ void cli_text_append_help(char *buffer, size_t buf_size, size_t *pos,
TNT_VERSION, program, DEFAULT_PORT); TNT_VERSION, program, DEFAULT_PORT);
} }
const char *cli_text_invalid_port_format(help_lang_t lang) { const char *cli_text_invalid_port_format(ui_lang_t lang) {
return lang == LANG_ZH ? "端口无效: %s\n" : "Invalid port: %s\n"; return lang == UI_LANG_ZH ? "端口无效: %s\n" : "Invalid port: %s\n";
} }
const char *cli_text_unknown_option_format(help_lang_t lang) { const char *cli_text_unknown_option_format(ui_lang_t lang) {
return lang == LANG_ZH ? "未知选项: %s\n" : "Unknown option: %s\n"; return lang == UI_LANG_ZH ? "未知选项: %s\n" : "Unknown option: %s\n";
} }
const char *cli_text_short_usage_format(help_lang_t lang) { const char *cli_text_short_usage_format(ui_lang_t lang) {
return lang == LANG_ZH ? "用法: %s [-p PORT] [-d DIR] [-h]\n" return lang == UI_LANG_ZH ? "用法: %s [-p PORT] [-d DIR] [-h]\n"
: "Usage: %s [-p PORT] [-d DIR] [-h]\n"; : "Usage: %s [-p PORT] [-d DIR] [-h]\n";
} }

View file

@ -221,11 +221,11 @@ const char *command_catalog_suggest(const char *name) {
} }
void command_catalog_append_full(char *buffer, size_t buf_size, size_t *pos, void command_catalog_append_full(char *buffer, size_t buf_size, size_t *pos,
help_lang_t lang) { ui_lang_t lang) {
for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); i++) { for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); i++) {
const char *usage = lang == LANG_ZH ? entries[i].full_usage_zh const char *usage = lang == UI_LANG_ZH ? entries[i].full_usage_zh
: entries[i].full_usage_en; : entries[i].full_usage_en;
const char *summary = lang == LANG_ZH ? entries[i].summary_zh const char *summary = lang == UI_LANG_ZH ? entries[i].summary_zh
: entries[i].summary_en; : entries[i].summary_en;
buffer_appendf(buffer, buf_size, pos, " %-40s - %s\n", buffer_appendf(buffer, buf_size, pos, " %-40s - %s\n",
usage, summary); usage, summary);
@ -233,7 +233,7 @@ void command_catalog_append_full(char *buffer, size_t buf_size, size_t *pos,
} }
void command_catalog_append_manual(char *buffer, size_t buf_size, size_t *pos, void command_catalog_append_manual(char *buffer, size_t buf_size, size_t *pos,
help_lang_t lang) { ui_lang_t lang) {
for (int group = 1; group <= 3; group++) { for (int group = 1; group <= 3; group++) {
bool first = true; bool first = true;
@ -243,7 +243,7 @@ void command_catalog_append_manual(char *buffer, size_t buf_size, size_t *pos,
if (entries[i].manual_group != group) { if (entries[i].manual_group != group) {
continue; continue;
} }
usage = lang == LANG_ZH ? entries[i].manual_usage_zh usage = lang == UI_LANG_ZH ? entries[i].manual_usage_zh
: entries[i].manual_usage_en; : entries[i].manual_usage_en;
if (!usage) { if (!usage) {
continue; continue;

View file

@ -93,17 +93,17 @@ void commands_dispatch(client_t *client) {
if (!command_catalog_match(cmd, &command_id, &arg)) { if (!command_catalog_match(cmd, &command_id, &arg)) {
const char *suggestion = command_catalog_suggest(cmd); const char *suggestion = command_catalog_suggest(cmd);
buffer_appendf(output, sizeof(output), &pos, buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_UNKNOWN_COMMAND_FORMAT), I18N_UNKNOWN_COMMAND_FORMAT),
cmd); cmd);
if (suggestion) { if (suggestion) {
buffer_appendf(output, sizeof(output), &pos, buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_DID_YOU_MEAN_FORMAT), I18N_DID_YOU_MEAN_FORMAT),
suggestion); suggestion);
} }
buffer_appendf(output, sizeof(output), &pos, "%s", buffer_appendf(output, sizeof(output), &pos, "%s",
i18n_text(client->help_lang, I18N_UNKNOWN_GUIDANCE)); i18n_text(client->ui_lang, I18N_UNKNOWN_GUIDANCE));
goto cmd_done; goto cmd_done;
} }
@ -112,7 +112,7 @@ void commands_dispatch(client_t *client) {
int total = g_room->client_count; int total = g_room->client_count;
buffer_appendf(output, sizeof(output), &pos, buffer_appendf(output, sizeof(output), &pos,
"\033[1;36m%s\033[0m \033[2;37m· %d\033[0m\n", "\033[1;36m%s\033[0m \033[2;37m· %d\033[0m\n",
i18n_text(client->help_lang, I18N_USERS_TITLE), total); i18n_text(client->ui_lang, I18N_USERS_TITLE), total);
time_t now = time(NULL); time_t now = time(NULL);
for (int i = 0; i < total; i++) { for (int i = 0; i < total; i++) {
@ -137,25 +137,25 @@ void commands_dispatch(client_t *client) {
} else if (command_id == TNT_COMMAND_HELP) { } else if (command_id == TNT_COMMAND_HELP) {
manual_append_interactive_panel(output, sizeof(output), &pos, manual_append_interactive_panel(output, sizeof(output), &pos,
client->help_lang); client->ui_lang);
} else if (command_id == TNT_COMMAND_LANG) { } else if (command_id == TNT_COMMAND_LANG) {
help_lang_t next_lang; ui_lang_t next_lang;
if (!arg || arg[0] == '\0') { if (!arg || arg[0] == '\0') {
buffer_appendf(output, sizeof(output), &pos, buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_LANG_CURRENT_FORMAT), I18N_LANG_CURRENT_FORMAT),
i18n_lang_code(client->help_lang)); i18n_ui_lang_code(client->ui_lang));
} else if (i18n_try_parse_lang(arg, &next_lang)) { } else if (i18n_try_parse_ui_lang(arg, &next_lang)) {
client->help_lang = next_lang; client->ui_lang = next_lang;
buffer_appendf(output, sizeof(output), &pos, buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_LANG_SET_FORMAT), I18N_LANG_SET_FORMAT),
i18n_lang_code(client->help_lang)); i18n_ui_lang_code(client->ui_lang));
} else { } else {
buffer_appendf(output, sizeof(output), &pos, buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_LANG_UNSUPPORTED_FORMAT), I18N_LANG_UNSUPPORTED_FORMAT),
arg); arg);
} }
@ -172,7 +172,7 @@ void commands_dispatch(client_t *client) {
if (target_name[0] == '\0' || rest[0] == '\0') { if (target_name[0] == '\0' || rest[0] == '\0') {
buffer_appendf(output, sizeof(output), &pos, "%s", buffer_appendf(output, sizeof(output), &pos, "%s",
i18n_text(client->help_lang, I18N_MSG_USAGE)); i18n_text(client->ui_lang, I18N_MSG_USAGE));
} else { } else {
bool found = false; bool found = false;
client_t *target = NULL; client_t *target = NULL;
@ -220,12 +220,12 @@ void commands_dispatch(client_t *client) {
if (found) { if (found) {
buffer_appendf(output, sizeof(output), &pos, buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_MSG_SENT_FORMAT), I18N_MSG_SENT_FORMAT),
target_name); target_name);
} else { } else {
buffer_appendf(output, sizeof(output), &pos, buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_MSG_USER_NOT_FOUND_FORMAT), I18N_MSG_USER_NOT_FOUND_FORMAT),
target_name); target_name);
} }
@ -245,12 +245,12 @@ void commands_dispatch(client_t *client) {
buffer_appendf(output, sizeof(output), &pos, buffer_appendf(output, sizeof(output), &pos,
"\033[1;36m%s\033[0m \033[2;37m· %d\033[0m\n", "\033[1;36m%s\033[0m \033[2;37m· %d\033[0m\n",
i18n_text(client->help_lang, I18N_INBOX_TITLE), i18n_text(client->ui_lang, I18N_INBOX_TITLE),
snap_count); snap_count);
if (snap_count == 0) { if (snap_count == 0) {
buffer_appendf(output, sizeof(output), &pos, buffer_appendf(output, sizeof(output), &pos,
" \033[2;37m%s\033[0m\n", " \033[2;37m%s\033[0m\n",
i18n_text(client->help_lang, I18N_INBOX_EMPTY)); i18n_text(client->ui_lang, I18N_INBOX_EMPTY));
} }
for (int i = 0; i < snap_count; i++) { for (int i = 0; i < snap_count; i++) {
char ts[20]; char ts[20];
@ -268,10 +268,10 @@ void commands_dispatch(client_t *client) {
if (new_name[0] == '\0') { if (new_name[0] == '\0') {
buffer_appendf(output, sizeof(output), &pos, "%s", buffer_appendf(output, sizeof(output), &pos, "%s",
i18n_text(client->help_lang, I18N_NICK_USAGE)); i18n_text(client->ui_lang, I18N_NICK_USAGE));
} else if (!is_valid_username(new_name)) { } else if (!is_valid_username(new_name)) {
buffer_appendf(output, sizeof(output), &pos, "%s", buffer_appendf(output, sizeof(output), &pos, "%s",
i18n_text(client->help_lang, I18N_NICK_INVALID)); i18n_text(client->ui_lang, I18N_NICK_INVALID));
} else { } else {
char validated_name[MAX_USERNAME_LEN]; char validated_name[MAX_USERNAME_LEN];
snprintf(validated_name, sizeof(validated_name), "%s", new_name); snprintf(validated_name, sizeof(validated_name), "%s", new_name);
@ -303,22 +303,22 @@ void commands_dispatch(client_t *client) {
if (taken) { if (taken) {
buffer_appendf(output, sizeof(output), &pos, buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_NICK_TAKEN_FORMAT), I18N_NICK_TAKEN_FORMAT),
validated_name); validated_name);
} else if (strcmp(validated_name, old_name) == 0) { } else if (strcmp(validated_name, old_name) == 0) {
buffer_appendf(output, sizeof(output), &pos, "%s", buffer_appendf(output, sizeof(output), &pos, "%s",
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_NICK_UNCHANGED)); I18N_NICK_UNCHANGED));
} else { } else {
message_t nick_msg; message_t nick_msg;
system_message_make_nick(&nick_msg, old_name, system_message_make_nick(&nick_msg, old_name,
client->username, client->help_lang); client->username, client->ui_lang);
room_broadcast(g_room, &nick_msg); room_broadcast(g_room, &nick_msg);
message_save(&nick_msg); message_save(&nick_msg);
buffer_appendf(output, sizeof(output), &pos, buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_NICK_CHANGED_FORMAT), I18N_NICK_CHANGED_FORMAT),
old_name, client->username); old_name, client->username);
} }
@ -332,7 +332,7 @@ void commands_dispatch(client_t *client) {
long val = strtol(arg, &endp, 10); long val = strtol(arg, &endp, 10);
if (*endp != '\0' || val < 1 || val > 50) { if (*endp != '\0' || val < 1 || val > 50) {
buffer_appendf(output, sizeof(output), &pos, "%s", buffer_appendf(output, sizeof(output), &pos, "%s",
i18n_text(client->help_lang, I18N_LAST_USAGE)); i18n_text(client->ui_lang, I18N_LAST_USAGE));
goto cmd_done; goto cmd_done;
} }
n = (int)val; n = (int)val;
@ -341,7 +341,7 @@ void commands_dispatch(client_t *client) {
message_t *last_msgs = NULL; message_t *last_msgs = NULL;
int last_count = message_load(&last_msgs, n); int last_count = message_load(&last_msgs, n);
buffer_appendf(output, sizeof(output), &pos, buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang, I18N_LAST_HEADER_FORMAT), i18n_text(client->ui_lang, I18N_LAST_HEADER_FORMAT),
last_count); last_count);
for (int i = 0; i < last_count; i++) { for (int i = 0; i < last_count; i++) {
char ts[20]; char ts[20];
@ -358,12 +358,12 @@ void commands_dispatch(client_t *client) {
while (*query == ' ') query++; while (*query == ' ') query++;
if (*query == '\0') { if (*query == '\0') {
buffer_appendf(output, sizeof(output), &pos, "%s", buffer_appendf(output, sizeof(output), &pos, "%s",
i18n_text(client->help_lang, I18N_SEARCH_USAGE)); i18n_text(client->ui_lang, I18N_SEARCH_USAGE));
} else { } else {
message_t *found = NULL; message_t *found = NULL;
int found_count = message_search(query, &found, 15); int found_count = message_search(query, &found, 15);
buffer_appendf(output, sizeof(output), &pos, buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_SEARCH_HEADER_FORMAT), I18N_SEARCH_HEADER_FORMAT),
query, found_count); query, found_count);
for (int i = 0; i < found_count; i++) { for (int i = 0; i < found_count; i++) {
@ -386,8 +386,8 @@ void commands_dispatch(client_t *client) {
} else if (command_id == TNT_COMMAND_MUTE_JOINS) { } else if (command_id == TNT_COMMAND_MUTE_JOINS) {
client->mute_joins = !client->mute_joins; client->mute_joins = !client->mute_joins;
buffer_appendf(output, sizeof(output), &pos, buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang, I18N_MUTE_JOINS_FORMAT), i18n_text(client->ui_lang, I18N_MUTE_JOINS_FORMAT),
i18n_text(client->help_lang, i18n_text(client->ui_lang,
client->mute_joins ? client->mute_joins ?
I18N_MUTE_JOINS_MUTED : I18N_MUTE_JOINS_MUTED :
I18N_MUTE_JOINS_UNMUTED)); I18N_MUTE_JOINS_UNMUTED));
@ -398,7 +398,7 @@ void commands_dispatch(client_t *client) {
} else if (command_id == TNT_COMMAND_CLEAR) { } else if (command_id == TNT_COMMAND_CLEAR) {
buffer_appendf(output, sizeof(output), &pos, "%s", buffer_appendf(output, sizeof(output), &pos, "%s",
i18n_text(client->help_lang, I18N_CLEAR_DONE)); i18n_text(client->ui_lang, I18N_CLEAR_DONE));
} }
cmd_done: cmd_done:

View file

@ -116,7 +116,7 @@ static void resolve_exec_username(const client_t *client, char *buffer,
} }
static int exec_command_help(client_t *client) { static int exec_command_help(client_t *client) {
const char *help_text = i18n_text(client->help_lang, I18N_EXEC_HELP); const char *help_text = i18n_text(client->ui_lang, I18N_EXEC_HELP);
return client_send(client, help_text, strlen(help_text)) == 0 ? 0 : 1; return client_send(client, help_text, strlen(help_text)) == 0 ? 0 : 1;
} }
@ -285,7 +285,7 @@ static int exec_command_tail(client_t *client, const char *args) {
if (parse_tail_count(args, &requested) < 0) { if (parse_tail_count(args, &requested) < 0) {
client_printf(client, "%s", client_printf(client, "%s",
i18n_text(client->help_lang, I18N_EXEC_TAIL_USAGE)); i18n_text(client->ui_lang, I18N_EXEC_TAIL_USAGE));
return 64; return 64;
} }
@ -339,7 +339,7 @@ static int exec_command_post(client_t *client, const char *args) {
if (!args || args[0] == '\0') { if (!args || args[0] == '\0') {
client_printf(client, "%s", client_printf(client, "%s",
i18n_text(client->help_lang, I18N_EXEC_POST_USAGE)); i18n_text(client->ui_lang, I18N_EXEC_POST_USAGE));
return 64; return 64;
} }
@ -349,13 +349,13 @@ static int exec_command_post(client_t *client, const char *args) {
if (content[0] == '\0') { if (content[0] == '\0') {
client_printf(client, "%s", client_printf(client, "%s",
i18n_text(client->help_lang, I18N_EXEC_POST_EMPTY)); i18n_text(client->ui_lang, I18N_EXEC_POST_EMPTY));
return 64; return 64;
} }
if (!utf8_is_valid_string(content)) { if (!utf8_is_valid_string(content)) {
client_printf(client, "%s", client_printf(client, "%s",
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_EXEC_POST_INVALID_UTF8)); I18N_EXEC_POST_INVALID_UTF8));
return 1; return 1;
} }
@ -426,7 +426,7 @@ int exec_dispatch(client_t *client) {
if (strcmp(cmd, "users") == 0) { if (strcmp(cmd, "users") == 0) {
if (args && strcmp(args, "--json") != 0) { if (args && strcmp(args, "--json") != 0) {
client_printf(client, "%s", client_printf(client, "%s",
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_EXEC_USERS_USAGE)); I18N_EXEC_USERS_USAGE));
return 64; return 64;
} }
@ -435,7 +435,7 @@ int exec_dispatch(client_t *client) {
if (strcmp(cmd, "stats") == 0) { if (strcmp(cmd, "stats") == 0) {
if (args && strcmp(args, "--json") != 0) { if (args && strcmp(args, "--json") != 0) {
client_printf(client, "%s", client_printf(client, "%s",
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_EXEC_STATS_USAGE)); I18N_EXEC_STATS_USAGE));
return 64; return 64;
} }
@ -452,7 +452,7 @@ int exec_dispatch(client_t *client) {
} }
client_printf(client, client_printf(client,
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_EXEC_UNKNOWN_COMMAND_FORMAT), I18N_EXEC_UNKNOWN_COMMAND_FORMAT),
cmd); cmd);
return 64; return 64;

View file

@ -3,8 +3,8 @@
#include "command_catalog.h" #include "command_catalog.h"
void help_text_append_full(char *buffer, size_t buf_size, size_t *pos, void help_text_append_full(char *buffer, size_t buf_size, size_t *pos,
help_lang_t lang) { ui_lang_t lang) {
if (lang == LANG_EN) { if (lang == UI_LANG_EN) {
buffer_appendf(buffer, buf_size, pos, buffer_appendf(buffer, buf_size, pos,
"TNT KEY REFERENCE\n" "TNT KEY REFERENCE\n"
"\n" "\n"

View file

@ -36,38 +36,38 @@ static bool starts_with_lang(const char *value, const char *prefix) {
return is_lang_boundary(value); return is_lang_boundary(value);
} }
bool i18n_try_parse_lang(const char *value, help_lang_t *lang) { bool i18n_try_parse_ui_lang(const char *value, ui_lang_t *lang) {
if (!value || value[0] == '\0') { if (!value || value[0] == '\0') {
return false; return false;
} }
if (starts_with_lang(value, "zh")) { if (starts_with_lang(value, "zh")) {
if (lang) *lang = LANG_ZH; if (lang) *lang = UI_LANG_ZH;
return true; return true;
} }
if (starts_with_lang(value, "en") || if (starts_with_lang(value, "en") ||
starts_with_lang(value, "c") || starts_with_lang(value, "c") ||
starts_with_lang(value, "posix")) { starts_with_lang(value, "posix")) {
if (lang) *lang = LANG_EN; if (lang) *lang = UI_LANG_EN;
return true; return true;
} }
return false; return false;
} }
help_lang_t i18n_parse_lang(const char *value, help_lang_t fallback) { ui_lang_t i18n_parse_ui_lang(const char *value, ui_lang_t fallback) {
help_lang_t lang; ui_lang_t lang;
if (i18n_try_parse_lang(value, &lang)) { if (i18n_try_parse_ui_lang(value, &lang)) {
return lang; return lang;
} }
return fallback; return fallback;
} }
help_lang_t i18n_default_lang(void) { ui_lang_t i18n_default_ui_lang(void) {
const char *explicit_lang = getenv("TNT_LANG"); const char *explicit_lang = getenv("TNT_LANG");
if (explicit_lang && explicit_lang[0] != '\0') { if (explicit_lang && explicit_lang[0] != '\0') {
return i18n_parse_lang(explicit_lang, LANG_EN); return i18n_parse_ui_lang(explicit_lang, UI_LANG_EN);
} }
const char *locale = getenv("LC_ALL"); const char *locale = getenv("LC_ALL");
@ -78,15 +78,15 @@ help_lang_t i18n_default_lang(void) {
locale = getenv("LANG"); locale = getenv("LANG");
} }
return i18n_parse_lang(locale, LANG_EN); return i18n_parse_ui_lang(locale, UI_LANG_EN);
} }
const char *i18n_lang_code(help_lang_t lang) { const char *i18n_ui_lang_code(ui_lang_t lang) {
return lang == LANG_ZH ? "zh" : "en"; return lang == UI_LANG_ZH ? "zh" : "en";
} }
const char *i18n_text(help_lang_t lang, i18n_text_id_t id) { const char *i18n_text(ui_lang_t lang, i18n_text_id_t id) {
if (lang == LANG_ZH) { if (lang == UI_LANG_ZH) {
switch (id) { switch (id) {
case I18N_USERNAME_PROMPT: case I18N_USERNAME_PROMPT:
return " 请输入用户名 (留空 anonymous): "; return " 请输入用户名 (留空 anonymous): ";

View file

@ -21,11 +21,11 @@
#include <time.h> #include <time.h>
static int g_idle_timeout = DEFAULT_IDLE_TIMEOUT; static int g_idle_timeout = DEFAULT_IDLE_TIMEOUT;
static help_lang_t g_default_lang = LANG_EN; static ui_lang_t g_default_ui_lang = UI_LANG_EN;
void input_init(void) { void input_init(void) {
g_idle_timeout = env_int("TNT_IDLE_TIMEOUT", DEFAULT_IDLE_TIMEOUT, 0, 86400); g_idle_timeout = env_int("TNT_IDLE_TIMEOUT", DEFAULT_IDLE_TIMEOUT, 0, 86400);
g_default_lang = i18n_default_lang(); g_default_ui_lang = i18n_default_ui_lang();
} }
static int read_username(client_t *client) { static int read_username(client_t *client) {
@ -34,7 +34,7 @@ static int read_username(client_t *client) {
char buf[4]; char buf[4];
tui_render_welcome(client); tui_render_welcome(client);
client_printf(client, "%s", i18n_text(client->help_lang, client_printf(client, "%s", i18n_text(client->ui_lang,
I18N_USERNAME_PROMPT)); I18N_USERNAME_PROMPT));
while (1) { while (1) {
@ -117,7 +117,7 @@ static int read_username(client_t *client) {
/* Validate username for security */ /* Validate username for security */
if (!is_valid_username(client->username)) { if (!is_valid_username(client->username)) {
client_printf(client, "%s", i18n_text(client->help_lang, client_printf(client, "%s", i18n_text(client->ui_lang,
I18N_INVALID_USERNAME)); I18N_INVALID_USERNAME));
strcpy(client->username, "anonymous"); strcpy(client->username, "anonymous");
} else { } else {
@ -258,11 +258,11 @@ static bool handle_key(client_t *client, unsigned char key, char *input) {
client->show_help = false; client->show_help = false;
tui_render_screen(client); tui_render_screen(client);
} else if (key == 'e' || key == 'E') { } else if (key == 'e' || key == 'E') {
client->help_lang = LANG_EN; client->ui_lang = UI_LANG_EN;
client->help_scroll_pos = 0; client->help_scroll_pos = 0;
tui_render_help(client); tui_render_help(client);
} else if (key == 'z' || key == 'Z') { } else if (key == 'z' || key == 'Z') {
client->help_lang = LANG_ZH; client->ui_lang = UI_LANG_ZH;
client->help_scroll_pos = 0; client->help_scroll_pos = 0;
tui_render_help(client); tui_render_help(client);
} else if (key == 'j') { } else if (key == 'j') {
@ -726,7 +726,7 @@ void input_run_session(client_t *client) {
/* Terminal size already set from PTY request */ /* Terminal size already set from PTY request */
client->mode = MODE_INSERT; client->mode = MODE_INSERT;
client->follow_tail = true; client->follow_tail = true;
client->help_lang = g_default_lang; client->ui_lang = g_default_ui_lang;
client->connected = true; client->connected = true;
client->command_history_count = 0; client->command_history_count = 0;
client->command_history_pos = 0; client->command_history_pos = 0;
@ -751,7 +751,7 @@ void input_run_session(client_t *client) {
/* Add to room */ /* Add to room */
if (room_add_client(g_room, client) < 0) { if (room_add_client(g_room, client) < 0) {
client_printf(client, "%s", i18n_text(client->help_lang, client_printf(client, "%s", i18n_text(client->ui_lang,
I18N_ROOM_FULL)); I18N_ROOM_FULL));
goto cleanup; goto cleanup;
} }
@ -765,7 +765,7 @@ void input_run_session(client_t *client) {
/* Broadcast join message */ /* Broadcast join message */
message_t join_msg; message_t join_msg;
system_message_make_join(&join_msg, client->username, client->help_lang); system_message_make_join(&join_msg, client->username, client->ui_lang);
room_broadcast(g_room, &join_msg); room_broadcast(g_room, &join_msg);
message_save(&join_msg); message_save(&join_msg);
@ -851,7 +851,7 @@ main_loop:
if (g_idle_timeout > 0 && joined_room && if (g_idle_timeout > 0 && joined_room &&
time(NULL) - client->last_active >= g_idle_timeout) { time(NULL) - client->last_active >= g_idle_timeout) {
client_printf(client, client_printf(client,
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_IDLE_TIMEOUT_FORMAT), I18N_IDLE_TIMEOUT_FORMAT),
g_idle_timeout / 60); g_idle_timeout / 60);
break; break;
@ -956,7 +956,7 @@ cleanup:
if (joined_room) { if (joined_room) {
message_t leave_msg; message_t leave_msg;
system_message_make_leave(&leave_msg, client->username, system_message_make_leave(&leave_msg, client->username,
client->help_lang); client->ui_lang);
client->connected = false; client->connected = false;
room_remove_client(g_room, client); room_remove_client(g_room, client);

View file

@ -20,7 +20,7 @@ static void signal_handler(int sig) {
int main(int argc, char **argv) { int main(int argc, char **argv) {
int port = DEFAULT_PORT; int port = DEFAULT_PORT;
help_lang_t lang = i18n_default_lang(); ui_lang_t lang = i18n_default_ui_lang();
/* Environment provides defaults; command-line flags override it. */ /* Environment provides defaults; command-line flags override it. */
const char *port_env = getenv("PORT"); const char *port_env = getenv("PORT");

View file

@ -2,7 +2,7 @@
#include "manual_text.h" #include "manual_text.h"
void manual_append_interactive_panel(char *buffer, size_t buf_size, void manual_append_interactive_panel(char *buffer, size_t buf_size,
size_t *pos, help_lang_t lang) { size_t *pos, ui_lang_t lang) {
if (!buffer || !pos) return; if (!buffer || !pos) return;
manual_text_append_interactive(buffer, buf_size, pos, lang); manual_text_append_interactive(buffer, buf_size, pos, lang);

View file

@ -3,8 +3,8 @@
#include "command_catalog.h" #include "command_catalog.h"
void manual_text_append_interactive(char *buffer, size_t buf_size, void manual_text_append_interactive(char *buffer, size_t buf_size,
size_t *pos, help_lang_t lang) { size_t *pos, ui_lang_t lang) {
if (lang == LANG_ZH) { if (lang == UI_LANG_ZH) {
buffer_appendf(buffer, buf_size, pos, buffer_appendf(buffer, buf_size, pos,
"\033[1;36mTNT(1) 帮助\033[0m\n" "\033[1;36mTNT(1) 帮助\033[0m\n"
"\n" "\n"

View file

@ -4,7 +4,7 @@
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
static void system_message_init(message_t *msg, help_lang_t lang) { static void system_message_init(message_t *msg, ui_lang_t lang) {
if (!msg) { if (!msg) {
return; return;
} }
@ -16,7 +16,7 @@ static void system_message_init(message_t *msg, help_lang_t lang) {
} }
void system_message_make_join(message_t *msg, const char *username, void system_message_make_join(message_t *msg, const char *username,
help_lang_t lang) { ui_lang_t lang) {
system_message_init(msg, lang); system_message_init(msg, lang);
if (!msg) { if (!msg) {
return; return;
@ -28,7 +28,7 @@ void system_message_make_join(message_t *msg, const char *username,
} }
void system_message_make_leave(message_t *msg, const char *username, void system_message_make_leave(message_t *msg, const char *username,
help_lang_t lang) { ui_lang_t lang) {
system_message_init(msg, lang); system_message_init(msg, lang);
if (!msg) { if (!msg) {
return; return;
@ -40,7 +40,7 @@ void system_message_make_leave(message_t *msg, const char *username,
} }
void system_message_make_nick(message_t *msg, const char *old_name, void system_message_make_nick(message_t *msg, const char *old_name,
const char *new_name, help_lang_t lang) { const char *new_name, ui_lang_t lang) {
system_message_init(msg, lang); system_message_init(msg, lang);
if (!msg) { if (!msg) {
return; return;

View file

@ -154,8 +154,8 @@ void tui_render_welcome(client_t *client) {
/* Lines, in display order. Width is computed in display columns. */ /* Lines, in display order. Width is computed in display columns. */
const char *line1 = "TNT · " TNT_VERSION; const char *line1 = "TNT · " TNT_VERSION;
const char *line2 = i18n_text(client->help_lang, I18N_WELCOME_SUBTITLE); const char *line2 = i18n_text(client->ui_lang, I18N_WELCOME_SUBTITLE);
const char *line3 = i18n_text(client->help_lang, I18N_WELCOME_TAGLINE); const char *line3 = i18n_text(client->ui_lang, I18N_WELCOME_TAGLINE);
int inner_w = utf8_string_width(line1); int inner_w = utf8_string_width(line1);
int w2 = utf8_string_width(line2); int w2 = utf8_string_width(line2);
@ -169,7 +169,7 @@ void tui_render_welcome(client_t *client) {
char fallback_text[96]; char fallback_text[96];
char fallback[128]; char fallback[128];
snprintf(fallback_text, sizeof(fallback_text), snprintf(fallback_text, sizeof(fallback_text),
i18n_text(client->help_lang, I18N_WELCOME_FALLBACK_FORMAT), i18n_text(client->ui_lang, I18N_WELCOME_FALLBACK_FORMAT),
TNT_VERSION); TNT_VERSION);
int n = snprintf(fallback, sizeof(fallback), ANSI_CLEAR ANSI_HOME "%s", int n = snprintf(fallback, sizeof(fallback), ANSI_CLEAR ANSI_HOME "%s",
fallback_text); fallback_text);
@ -355,7 +355,7 @@ void tui_render_screen(client_t *client) {
char online_buf[32]; char online_buf[32];
snprintf(online_buf, sizeof(online_buf), snprintf(online_buf, sizeof(online_buf),
i18n_text(client->help_lang, I18N_TITLE_ONLINE_FORMAT), i18n_text(client->ui_lang, I18N_TITLE_ONLINE_FORMAT),
online); online);
chips[chip_count].value = online_buf; chips[chip_count].value = online_buf;
chips[chip_count].value_color = "\033[37m"; chips[chip_count].value_color = "\033[37m";
@ -373,9 +373,9 @@ void tui_render_screen(client_t *client) {
chips[chip_count].value_color = mode_color; chips[chip_count].value_color = mode_color;
chip_count++; chip_count++;
const char *hint = i18n_text(client->help_lang, I18N_TITLE_HELP_HINT); const char *hint = i18n_text(client->ui_lang, I18N_TITLE_HELP_HINT);
int hint_width = utf8_string_width(hint); int hint_width = utf8_string_width(hint);
const char *mute_label = i18n_text(client->help_lang, I18N_TITLE_MUTED); const char *mute_label = i18n_text(client->ui_lang, I18N_TITLE_MUTED);
int mute_width = client->mute_joins ? utf8_string_width(mute_label) + 2 : 0; int mute_width = client->mute_joins ? utf8_string_width(mute_label) + 2 : 0;
/* Unread @-mentions chip — high-priority, gets a bright yellow star. /* Unread @-mentions chip — high-priority, gets a bright yellow star.
@ -623,7 +623,7 @@ void tui_render_command_output(client_t *client) {
buffer_appendf(buffer, sizeof(buffer), &pos, ANSI_CLEAR ANSI_HOME); buffer_appendf(buffer, sizeof(buffer), &pos, ANSI_CLEAR ANSI_HOME);
/* Title */ /* Title */
const char *title = i18n_text(client->help_lang, const char *title = i18n_text(client->ui_lang,
I18N_COMMAND_OUTPUT_TITLE); I18N_COMMAND_OUTPUT_TITLE);
char title_display[64]; char title_display[64];
utf8_ansi_truncate(title, title_display, sizeof(title_display), rw); utf8_ansi_truncate(title, title_display, sizeof(title_display), rw);
@ -676,7 +676,7 @@ void tui_render_command_output(client_t *client) {
} }
buffer_appendf(buffer, sizeof(buffer), &pos, buffer_appendf(buffer, sizeof(buffer), &pos,
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_COMMAND_OUTPUT_STATUS_FORMAT), I18N_COMMAND_OUTPUT_STATUS_FORMAT),
start + 1, max_scroll + 1); start + 1, max_scroll + 1);
@ -706,7 +706,7 @@ void tui_render_motd(client_t *client) {
buffer_appendf(buffer, sizeof(buffer), &pos, ANSI_CLEAR ANSI_HOME); buffer_appendf(buffer, sizeof(buffer), &pos, ANSI_CLEAR ANSI_HOME);
/* Top border with a localized title chip. */ /* Top border with a localized title chip. */
const char *title = i18n_text(client->help_lang, I18N_MOTD_TITLE); const char *title = i18n_text(client->ui_lang, I18N_MOTD_TITLE);
int title_w = utf8_string_width(title); int title_w = utf8_string_width(title);
int top_dash_fill = rw - 2 - title_w - 1; /* 2 corners, 1 leading ─ */ int top_dash_fill = rw - 2 - title_w - 1; /* 2 corners, 1 leading ─ */
if (top_dash_fill < 0) top_dash_fill = 0; if (top_dash_fill < 0) top_dash_fill = 0;
@ -758,7 +758,7 @@ void tui_render_motd(client_t *client) {
buffer_appendf(buffer, sizeof(buffer), &pos, "\r\n"); buffer_appendf(buffer, sizeof(buffer), &pos, "\r\n");
/* Bottom border with a localized continue hint. */ /* Bottom border with a localized continue hint. */
const char *footer = i18n_text(client->help_lang, const char *footer = i18n_text(client->ui_lang,
I18N_MOTD_CONTINUE_HINT); I18N_MOTD_CONTINUE_HINT);
int footer_w = utf8_string_width(footer); int footer_w = utf8_string_width(footer);
int bot_dash_fill = rw - 2 - footer_w - 1; int bot_dash_fill = rw - 2 - footer_w - 1;
@ -790,7 +790,7 @@ void tui_render_help(client_t *client) {
buffer_appendf(buffer, sizeof(buffer), &pos, ANSI_CLEAR ANSI_HOME); buffer_appendf(buffer, sizeof(buffer), &pos, ANSI_CLEAR ANSI_HOME);
/* Title */ /* Title */
const char *title = i18n_text(client->help_lang, I18N_HELP_TITLE); const char *title = i18n_text(client->ui_lang, I18N_HELP_TITLE);
int title_width = utf8_string_width(title); int title_width = utf8_string_width(title);
int padding = rw - title_width; int padding = rw - title_width;
if (padding < 0) padding = 0; if (padding < 0) padding = 0;
@ -805,7 +805,7 @@ void tui_render_help(client_t *client) {
size_t help_pos = 0; size_t help_pos = 0;
help_copy[0] = '\0'; help_copy[0] = '\0';
help_text_append_full(help_copy, sizeof(help_copy), &help_pos, help_text_append_full(help_copy, sizeof(help_copy), &help_pos,
client->help_lang); client->ui_lang);
/* Split into lines and display with scrolling */ /* Split into lines and display with scrolling */
char *lines[100]; char *lines[100];
@ -836,7 +836,7 @@ void tui_render_help(client_t *client) {
/* Status line */ /* Status line */
buffer_appendf(buffer, sizeof(buffer), &pos, buffer_appendf(buffer, sizeof(buffer), &pos,
i18n_text(client->help_lang, I18N_HELP_STATUS_FORMAT), i18n_text(client->ui_lang, I18N_HELP_STATUS_FORMAT),
start + 1, max_scroll + 1); start + 1, max_scroll + 1);
client_send(client, buffer, pos); client_send(client, buffer, pos);

View file

@ -13,13 +13,13 @@ void tui_status_append(char *buffer, size_t buf_size, size_t *pos,
"\033[2;37m\033[0m " "\033[2;37m\033[0m "
"\033[2;37m%s\033[0m" "\033[2;37m%s\033[0m"
"\033[K", "\033[K",
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_INSERT_HINT_WIDE)); I18N_INSERT_HINT_WIDE));
} else if (client->width >= 36) { } else if (client->width >= 36) {
buffer_appendf(buffer, buf_size, pos, buffer_appendf(buffer, buf_size, pos,
"\033[2;37m\033[0m " "\033[2;37m\033[0m "
"\033[2;37m%s\033[0m\033[K", "\033[2;37m%s\033[0m\033[K",
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_INSERT_HINT_NARROW)); I18N_INSERT_HINT_NARROW));
} else { } else {
buffer_appendf(buffer, buf_size, pos, "\033[2;37m\033[0m \033[K"); buffer_appendf(buffer, buf_size, pos, "\033[2;37m\033[0m \033[K");
@ -36,16 +36,16 @@ void tui_status_append(char *buffer, size_t buf_size, size_t *pos,
" \033[2;37m%d-%d / %d\033[0m" " \033[2;37m%d-%d / %d\033[0m"
" \033[33m▼ %d %s · %s\033[0m\033[K", " \033[33m▼ %d %s · %s\033[0m\033[K",
range_start, range_end, total, unseen, range_start, range_end, total, unseen,
i18n_text(client->help_lang, i18n_text(client->ui_lang,
I18N_NORMAL_NEW_MESSAGES), I18N_NORMAL_NEW_MESSAGES),
i18n_text(client->help_lang, I18N_NORMAL_LATEST)); i18n_text(client->ui_lang, I18N_NORMAL_LATEST));
} else { } else {
buffer_appendf(buffer, buf_size, pos, buffer_appendf(buffer, buf_size, pos,
"\033[7;33m NORMAL \033[0m" "\033[7;33m NORMAL \033[0m"
" \033[2;37m%d-%d / %d\033[0m" " \033[2;37m%d-%d / %d\033[0m"
" \033[2;37m%s\033[0m\033[K", " \033[2;37m%s\033[0m\033[K",
range_start, range_end, total, range_start, range_end, total,
i18n_text(client->help_lang, I18N_NORMAL_LATEST)); i18n_text(client->ui_lang, I18N_NORMAL_LATEST));
} }
} else if (client->mode == MODE_COMMAND) { } else if (client->mode == MODE_COMMAND) {
buffer_appendf(buffer, buf_size, pos, buffer_appendf(buffer, buf_size, pos,

View file

@ -19,31 +19,31 @@ TEST(help_matches_language) {
char output[2048] = {0}; char output[2048] = {0};
size_t pos = 0; size_t pos = 0;
cli_text_append_help(output, sizeof(output), &pos, "tnt", LANG_EN); cli_text_append_help(output, sizeof(output), &pos, "tnt", UI_LANG_EN);
assert(strstr(output, "anonymous SSH chat server") != NULL); assert(strstr(output, "anonymous SSH chat server") != NULL);
assert(strstr(output, "Usage: tnt [options]") != NULL); assert(strstr(output, "Usage: tnt [options]") != NULL);
assert(strstr(output, "TNT_LANG") != NULL); assert(strstr(output, "TNT_LANG") != NULL);
memset(output, 0, sizeof(output)); memset(output, 0, sizeof(output));
pos = 0; pos = 0;
cli_text_append_help(output, sizeof(output), &pos, "tnt", LANG_ZH); cli_text_append_help(output, sizeof(output), &pos, "tnt", UI_LANG_ZH);
assert(strstr(output, "匿名 SSH 聊天服务器") != NULL); assert(strstr(output, "匿名 SSH 聊天服务器") != NULL);
assert(strstr(output, "用法: tnt [选项]") != NULL); assert(strstr(output, "用法: tnt [选项]") != NULL);
assert(strstr(output, "TNT_LANG") != NULL); assert(strstr(output, "TNT_LANG") != NULL);
} }
TEST(error_formats_match_language) { TEST(error_formats_match_language) {
assert(strcmp(cli_text_invalid_port_format(LANG_EN), assert(strcmp(cli_text_invalid_port_format(UI_LANG_EN),
"Invalid port: %s\n") == 0); "Invalid port: %s\n") == 0);
assert(strcmp(cli_text_invalid_port_format(LANG_ZH), assert(strcmp(cli_text_invalid_port_format(UI_LANG_ZH),
"端口无效: %s\n") == 0); "端口无效: %s\n") == 0);
assert(strcmp(cli_text_unknown_option_format(LANG_EN), assert(strcmp(cli_text_unknown_option_format(UI_LANG_EN),
"Unknown option: %s\n") == 0); "Unknown option: %s\n") == 0);
assert(strcmp(cli_text_unknown_option_format(LANG_ZH), assert(strcmp(cli_text_unknown_option_format(UI_LANG_ZH),
"未知选项: %s\n") == 0); "未知选项: %s\n") == 0);
assert(strcmp(cli_text_short_usage_format(LANG_EN), assert(strcmp(cli_text_short_usage_format(UI_LANG_EN),
"Usage: %s [-p PORT] [-d DIR] [-h]\n") == 0); "Usage: %s [-p PORT] [-d DIR] [-h]\n") == 0);
assert(strcmp(cli_text_short_usage_format(LANG_ZH), assert(strcmp(cli_text_short_usage_format(UI_LANG_ZH),
"用法: %s [-p PORT] [-d DIR] [-h]\n") == 0); "用法: %s [-p PORT] [-d DIR] [-h]\n") == 0);
} }

View file

@ -61,8 +61,8 @@ TEST(generates_localized_help_sections) {
size_t en_pos = 0; size_t en_pos = 0;
size_t zh_pos = 0; size_t zh_pos = 0;
command_catalog_append_full(en, sizeof(en), &en_pos, LANG_EN); command_catalog_append_full(en, sizeof(en), &en_pos, UI_LANG_EN);
command_catalog_append_full(zh, sizeof(zh), &zh_pos, LANG_ZH); command_catalog_append_full(zh, sizeof(zh), &zh_pos, UI_LANG_ZH);
assert(strstr(en, ":users, :list, :who") != NULL); assert(strstr(en, ":users, :list, :who") != NULL);
assert(strstr(en, "Show online users") != NULL); assert(strstr(en, "Show online users") != NULL);

View file

@ -21,8 +21,8 @@ TEST(full_help_matches_language) {
size_t en_pos = 0; size_t en_pos = 0;
size_t zh_pos = 0; size_t zh_pos = 0;
help_text_append_full(en, sizeof(en), &en_pos, LANG_EN); help_text_append_full(en, sizeof(en), &en_pos, UI_LANG_EN);
help_text_append_full(zh, sizeof(zh), &zh_pos, LANG_ZH); help_text_append_full(zh, sizeof(zh), &zh_pos, UI_LANG_ZH);
assert(strstr(en, "TNT KEY REFERENCE") != NULL); assert(strstr(en, "TNT KEY REFERENCE") != NULL);
assert(strstr(en, "AVAILABLE COMMANDS") != NULL); assert(strstr(en, "AVAILABLE COMMANDS") != NULL);

View file

@ -17,133 +17,133 @@
static int tests_passed = 0; static int tests_passed = 0;
TEST(parse_explicit_languages) { TEST(parse_explicit_languages) {
help_lang_t lang; ui_lang_t lang;
assert(i18n_parse_lang("zh", LANG_EN) == LANG_ZH); assert(i18n_parse_ui_lang("zh", UI_LANG_EN) == UI_LANG_ZH);
assert(i18n_parse_lang("zh_CN.UTF-8", LANG_EN) == LANG_ZH); assert(i18n_parse_ui_lang("zh_CN.UTF-8", UI_LANG_EN) == UI_LANG_ZH);
assert(i18n_parse_lang("en", LANG_ZH) == LANG_EN); assert(i18n_parse_ui_lang("en", UI_LANG_ZH) == UI_LANG_EN);
assert(i18n_parse_lang("en_US.UTF-8", LANG_ZH) == LANG_EN); assert(i18n_parse_ui_lang("en_US.UTF-8", UI_LANG_ZH) == UI_LANG_EN);
assert(i18n_parse_lang("C", LANG_ZH) == LANG_EN); assert(i18n_parse_ui_lang("C", UI_LANG_ZH) == UI_LANG_EN);
assert(i18n_parse_lang("POSIX", LANG_ZH) == LANG_EN); assert(i18n_parse_ui_lang("POSIX", UI_LANG_ZH) == UI_LANG_EN);
assert(i18n_try_parse_lang("zh", &lang) == true); assert(i18n_try_parse_ui_lang("zh", &lang) == true);
assert(lang == LANG_ZH); assert(lang == UI_LANG_ZH);
assert(i18n_try_parse_lang("en", &lang) == true); assert(i18n_try_parse_ui_lang("en", &lang) == true);
assert(lang == LANG_EN); assert(lang == UI_LANG_EN);
assert(i18n_try_parse_lang("cn", &lang) == false); assert(i18n_try_parse_ui_lang("cn", &lang) == false);
assert(i18n_try_parse_lang("english", &lang) == false); assert(i18n_try_parse_ui_lang("english", &lang) == false);
assert(i18n_try_parse_lang("chinese", &lang) == false); assert(i18n_try_parse_ui_lang("chinese", &lang) == false);
assert(i18n_try_parse_lang("中文", &lang) == false); assert(i18n_try_parse_ui_lang("中文", &lang) == false);
assert(i18n_try_parse_lang("英文", &lang) == false); assert(i18n_try_parse_ui_lang("英文", &lang) == false);
assert(i18n_try_parse_lang("fr", &lang) == false); assert(i18n_try_parse_ui_lang("fr", &lang) == false);
} }
TEST(parse_unknown_uses_fallback) { TEST(parse_unknown_uses_fallback) {
assert(i18n_parse_lang(NULL, LANG_ZH) == LANG_ZH); assert(i18n_parse_ui_lang(NULL, UI_LANG_ZH) == UI_LANG_ZH);
assert(i18n_parse_lang("", LANG_EN) == LANG_EN); assert(i18n_parse_ui_lang("", UI_LANG_EN) == UI_LANG_EN);
assert(i18n_parse_lang("fr_FR.UTF-8", LANG_ZH) == LANG_ZH); assert(i18n_parse_ui_lang("fr_FR.UTF-8", UI_LANG_ZH) == UI_LANG_ZH);
} }
TEST(parse_ignores_surrounding_whitespace) { TEST(parse_ignores_surrounding_whitespace) {
help_lang_t lang; ui_lang_t lang;
assert(i18n_try_parse_lang(" zh ", &lang) == true); assert(i18n_try_parse_ui_lang(" zh ", &lang) == true);
assert(lang == LANG_ZH); assert(lang == UI_LANG_ZH);
assert(i18n_parse_lang("\ten_US.UTF-8\n", LANG_ZH) == LANG_EN); assert(i18n_parse_ui_lang("\ten_US.UTF-8\n", UI_LANG_ZH) == UI_LANG_EN);
assert(i18n_try_parse_lang(" english ", &lang) == false); assert(i18n_try_parse_ui_lang(" english ", &lang) == false);
assert(i18n_try_parse_lang("zh CN", &lang) == false); assert(i18n_try_parse_ui_lang("zh CN", &lang) == false);
setenv("TNT_LANG", " zh ", 1); setenv("TNT_LANG", " zh ", 1);
setenv("LC_ALL", "en_US.UTF-8", 1); setenv("LC_ALL", "en_US.UTF-8", 1);
assert(i18n_default_lang() == LANG_ZH); assert(i18n_default_ui_lang() == UI_LANG_ZH);
} }
TEST(default_prefers_tnt_lang) { TEST(default_prefers_tnt_lang) {
setenv("TNT_LANG", "zh_CN.UTF-8", 1); setenv("TNT_LANG", "zh_CN.UTF-8", 1);
setenv("LC_ALL", "en_US.UTF-8", 1); setenv("LC_ALL", "en_US.UTF-8", 1);
assert(i18n_default_lang() == LANG_ZH); assert(i18n_default_ui_lang() == UI_LANG_ZH);
setenv("TNT_LANG", "en", 1); setenv("TNT_LANG", "en", 1);
setenv("LC_ALL", "zh_CN.UTF-8", 1); setenv("LC_ALL", "zh_CN.UTF-8", 1);
assert(i18n_default_lang() == LANG_EN); assert(i18n_default_ui_lang() == UI_LANG_EN);
} }
TEST(default_uses_locale_when_no_tnt_lang) { TEST(default_uses_locale_when_no_tnt_lang) {
unsetenv("TNT_LANG"); unsetenv("TNT_LANG");
setenv("LC_ALL", "zh_CN.UTF-8", 1); setenv("LC_ALL", "zh_CN.UTF-8", 1);
assert(i18n_default_lang() == LANG_ZH); assert(i18n_default_ui_lang() == UI_LANG_ZH);
setenv("LC_ALL", "C", 1); setenv("LC_ALL", "C", 1);
assert(i18n_default_lang() == LANG_EN); assert(i18n_default_ui_lang() == UI_LANG_EN);
} }
TEST(text_lookup_matches_language) { TEST(text_lookup_matches_language) {
assert(strstr(i18n_text(LANG_EN, I18N_USERNAME_PROMPT), assert(strstr(i18n_text(UI_LANG_EN, I18N_USERNAME_PROMPT),
"display name") != NULL); "display name") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_USERNAME_PROMPT), assert(strstr(i18n_text(UI_LANG_ZH, I18N_USERNAME_PROMPT),
"用户名") != NULL); "用户名") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_WELCOME_SUBTITLE), assert(strstr(i18n_text(UI_LANG_EN, I18N_WELCOME_SUBTITLE),
"anonymous chat") != NULL); "anonymous chat") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_WELCOME_SUBTITLE), assert(strstr(i18n_text(UI_LANG_ZH, I18N_WELCOME_SUBTITLE),
"匿名聊天室") != NULL); "匿名聊天室") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_HELP_STATUS_FORMAT), assert(strstr(i18n_text(UI_LANG_EN, I18N_HELP_STATUS_FORMAT),
"KEY REFERENCE") != NULL); "KEY REFERENCE") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_HELP_STATUS_FORMAT), assert(strstr(i18n_text(UI_LANG_ZH, I18N_HELP_STATUS_FORMAT),
"按键参考") != NULL); "按键参考") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_COMMAND_OUTPUT_TITLE), assert(strstr(i18n_text(UI_LANG_EN, I18N_COMMAND_OUTPUT_TITLE),
"COMMAND") != NULL); "COMMAND") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_COMMAND_OUTPUT_TITLE), assert(strstr(i18n_text(UI_LANG_ZH, I18N_COMMAND_OUTPUT_TITLE),
"命令输出") != NULL); "命令输出") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_COMMAND_OUTPUT_STATUS_FORMAT), assert(strstr(i18n_text(UI_LANG_EN, I18N_COMMAND_OUTPUT_STATUS_FORMAT),
"q:close") != NULL); "q:close") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_COMMAND_OUTPUT_STATUS_FORMAT), assert(strstr(i18n_text(UI_LANG_ZH, I18N_COMMAND_OUTPUT_STATUS_FORMAT),
"q:关闭") != NULL); "q:关闭") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_MOTD_CONTINUE_HINT), assert(strstr(i18n_text(UI_LANG_EN, I18N_MOTD_CONTINUE_HINT),
"Press any key") != NULL); "Press any key") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_MOTD_CONTINUE_HINT), assert(strstr(i18n_text(UI_LANG_ZH, I18N_MOTD_CONTINUE_HINT),
"按任意键") != NULL); "按任意键") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_TITLE_ONLINE_FORMAT), assert(strstr(i18n_text(UI_LANG_EN, I18N_TITLE_ONLINE_FORMAT),
"online") != NULL); "online") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_TITLE_ONLINE_FORMAT), assert(strstr(i18n_text(UI_LANG_ZH, I18N_TITLE_ONLINE_FORMAT),
"在线") != NULL); "在线") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_IDLE_TIMEOUT_FORMAT), assert(strstr(i18n_text(UI_LANG_EN, I18N_IDLE_TIMEOUT_FORMAT),
"idle timeout") != NULL); "idle timeout") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_IDLE_TIMEOUT_FORMAT), assert(strstr(i18n_text(UI_LANG_ZH, I18N_IDLE_TIMEOUT_FORMAT),
"空闲超时") != NULL); "空闲超时") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_MSG_USAGE), assert(strstr(i18n_text(UI_LANG_EN, I18N_MSG_USAGE),
"msg <username>") != NULL); "msg <username>") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_MSG_USAGE), assert(strstr(i18n_text(UI_LANG_ZH, I18N_MSG_USAGE),
"用户名") != NULL); "用户名") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_SEARCH_HEADER_FORMAT), assert(strstr(i18n_text(UI_LANG_EN, I18N_SEARCH_HEADER_FORMAT),
"Search") != NULL); "Search") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_SEARCH_HEADER_FORMAT), assert(strstr(i18n_text(UI_LANG_ZH, I18N_SEARCH_HEADER_FORMAT),
"搜索") != NULL); "搜索") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_LANG_CURRENT_FORMAT), assert(strstr(i18n_text(UI_LANG_EN, I18N_LANG_CURRENT_FORMAT),
"lang <en|zh>") != NULL); "lang <en|zh>") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_LANG_CURRENT_FORMAT), assert(strstr(i18n_text(UI_LANG_ZH, I18N_LANG_CURRENT_FORMAT),
"lang <en|zh>") != NULL); "lang <en|zh>") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_UNKNOWN_COMMAND_FORMAT), assert(strstr(i18n_text(UI_LANG_EN, I18N_UNKNOWN_COMMAND_FORMAT),
"Unknown command") != NULL); "Unknown command") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_UNKNOWN_COMMAND_FORMAT), assert(strstr(i18n_text(UI_LANG_ZH, I18N_UNKNOWN_COMMAND_FORMAT),
"未知命令") != NULL); "未知命令") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_EXEC_HELP), assert(strstr(i18n_text(UI_LANG_EN, I18N_EXEC_HELP),
"TNT exec interface") != NULL); "TNT exec interface") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_EXEC_HELP), assert(strstr(i18n_text(UI_LANG_EN, I18N_EXEC_HELP),
"support") == NULL); "support") == NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_EXEC_HELP), assert(strstr(i18n_text(UI_LANG_ZH, I18N_EXEC_HELP),
"TNT exec 接口") != NULL); "TNT exec 接口") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_EXEC_HELP), assert(strstr(i18n_text(UI_LANG_ZH, I18N_EXEC_HELP),
"support") == NULL); "support") == NULL);
assert(strstr(i18n_text(LANG_EN, I18N_EXEC_POST_EMPTY), assert(strstr(i18n_text(UI_LANG_EN, I18N_EXEC_POST_EMPTY),
"message cannot be empty") != NULL); "message cannot be empty") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_EXEC_POST_EMPTY), assert(strstr(i18n_text(UI_LANG_ZH, I18N_EXEC_POST_EMPTY),
"消息不能为空") != NULL); "消息不能为空") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_EXEC_UNKNOWN_COMMAND_FORMAT), assert(strstr(i18n_text(UI_LANG_EN, I18N_EXEC_UNKNOWN_COMMAND_FORMAT),
"Unknown command") != NULL); "Unknown command") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_EXEC_UNKNOWN_COMMAND_FORMAT), assert(strstr(i18n_text(UI_LANG_ZH, I18N_EXEC_UNKNOWN_COMMAND_FORMAT),
"未知命令") != NULL); "未知命令") != NULL);
assert(strcmp(i18n_lang_code(LANG_EN), "en") == 0); assert(strcmp(i18n_ui_lang_code(UI_LANG_EN), "en") == 0);
assert(strcmp(i18n_lang_code(LANG_ZH), "zh") == 0); assert(strcmp(i18n_ui_lang_code(UI_LANG_ZH), "zh") == 0);
} }
int main(void) { int main(void) {

View file

@ -34,8 +34,8 @@ TEST(interactive_manual_matches_language) {
size_t en_pos = 0; size_t en_pos = 0;
size_t zh_pos = 0; size_t zh_pos = 0;
manual_text_append_interactive(en, sizeof(en), &en_pos, LANG_EN); manual_text_append_interactive(en, sizeof(en), &en_pos, UI_LANG_EN);
manual_text_append_interactive(zh, sizeof(zh), &zh_pos, LANG_ZH); manual_text_append_interactive(zh, sizeof(zh), &zh_pos, UI_LANG_ZH);
assert(strstr(en, "TNT(1) help") != NULL); assert(strstr(en, "TNT(1) help") != NULL);
assert(strstr(en, "Use") != NULL); assert(strstr(en, "Use") != NULL);

View file

@ -18,14 +18,14 @@ static int tests_passed = 0;
TEST(join_leave_follow_language) { TEST(join_leave_follow_language) {
message_t msg; message_t msg;
system_message_make_join(&msg, "alice", LANG_ZH); system_message_make_join(&msg, "alice", UI_LANG_ZH);
assert(strcmp(msg.username, "系统") == 0); assert(strcmp(msg.username, "系统") == 0);
assert(strstr(msg.content, "alice") != NULL); assert(strstr(msg.content, "alice") != NULL);
assert(strstr(msg.content, "加入了聊天室") != NULL); assert(strstr(msg.content, "加入了聊天室") != NULL);
assert(system_message_is_system(&msg)); assert(system_message_is_system(&msg));
assert(system_message_is_join_leave(&msg)); assert(system_message_is_join_leave(&msg));
system_message_make_leave(&msg, "bob", LANG_EN); system_message_make_leave(&msg, "bob", UI_LANG_EN);
assert(strcmp(msg.username, "system") == 0); assert(strcmp(msg.username, "system") == 0);
assert(strstr(msg.content, "bob") != NULL); assert(strstr(msg.content, "bob") != NULL);
assert(strstr(msg.content, "left the room") != NULL); assert(strstr(msg.content, "left the room") != NULL);
@ -36,7 +36,7 @@ TEST(join_leave_follow_language) {
TEST(nick_messages_are_system_events_not_join_leave) { TEST(nick_messages_are_system_events_not_join_leave) {
message_t msg; message_t msg;
system_message_make_nick(&msg, "old", "new", LANG_EN); system_message_make_nick(&msg, "old", "new", UI_LANG_EN);
assert(strcmp(msg.username, "system") == 0); assert(strcmp(msg.username, "system") == 0);
assert(strstr(msg.content, "old") != NULL); assert(strstr(msg.content, "old") != NULL);
assert(strstr(msg.content, "new") != NULL); assert(strstr(msg.content, "new") != NULL);
@ -44,7 +44,7 @@ TEST(nick_messages_are_system_events_not_join_leave) {
assert(system_message_is_system(&msg)); assert(system_message_is_system(&msg));
assert(!system_message_is_join_leave(&msg)); assert(!system_message_is_join_leave(&msg));
system_message_make_nick(&msg, "", "", LANG_ZH); system_message_make_nick(&msg, "", "", UI_LANG_ZH);
assert(strcmp(msg.username, "系统") == 0); assert(strcmp(msg.username, "系统") == 0);
assert(strstr(msg.content, "更名为") != NULL); assert(strstr(msg.content, "更名为") != NULL);
assert(system_message_is_system(&msg)); assert(system_message_is_system(&msg));