i18n: centralize command output text

This commit is contained in:
m1ngsama 2026-05-23 19:11:29 +08:00
parent 9159586716
commit aca68824ac
5 changed files with 161 additions and 112 deletions

View file

@ -18,6 +18,8 @@
- Common command usage errors now stay in the session language, and bare
`:search`, `:msg`, and `:nick` show usage instead of falling through to
unknown-command guidance.
- Command output text for common interactive commands is now centralized in
the i18n table instead of being scattered through command flow logic.
### Changed
- NORMAL mode now opens at the latest visible messages instead of the oldest

View file

@ -15,7 +15,26 @@ typedef enum {
I18N_HELP_STATUS_FORMAT,
I18N_COMMAND_OUTPUT_TITLE,
I18N_MOTD_TITLE,
I18N_MOTD_CONTINUE_HINT
I18N_MOTD_CONTINUE_HINT,
I18N_USERS_TITLE,
I18N_MSG_USAGE,
I18N_MSG_SENT_FORMAT,
I18N_MSG_USER_NOT_FOUND_FORMAT,
I18N_INBOX_TITLE,
I18N_INBOX_EMPTY,
I18N_NICK_USAGE,
I18N_NICK_INVALID,
I18N_NICK_TAKEN_FORMAT,
I18N_NICK_UNCHANGED,
I18N_NICK_CHANGED_FORMAT,
I18N_LAST_USAGE,
I18N_LAST_HEADER_FORMAT,
I18N_SEARCH_USAGE,
I18N_SEARCH_HEADER_FORMAT,
I18N_MUTE_JOINS_FORMAT,
I18N_MUTE_JOINS_MUTED,
I18N_MUTE_JOINS_UNMUTED,
I18N_CLEAR_DONE
} i18n_text_id_t;
bool i18n_try_parse_lang(const char *value, help_lang_t *lang);

View file

@ -193,15 +193,9 @@ void commands_dispatch(client_t *client) {
strcmp(cmd, "who") == 0) {
pthread_rwlock_rdlock(&g_room->lock);
int total = g_room->client_count;
if (client->help_lang == LANG_ZH) {
buffer_appendf(output, sizeof(output), &pos,
"\033[1;36m在线用户\033[0m "
"\033[2;37m· %d\033[0m\n", total);
} else {
buffer_appendf(output, sizeof(output), &pos,
"\033[1;36mOnline users\033[0m "
"\033[2;37m· %d\033[0m\n", total);
}
buffer_appendf(output, sizeof(output), &pos,
"\033[1;36m%s\033[0m \033[2;37m· %d\033[0m\n",
i18n_text(client->help_lang, I18N_USERS_TITLE), total);
time_t now = time(NULL);
for (int i = 0; i < total; i++) {
@ -290,15 +284,8 @@ void commands_dispatch(client_t *client) {
while (*rest == ' ') rest++;
if (target_name[0] == '\0' || rest[0] == '\0') {
if (client->help_lang == LANG_ZH) {
buffer_appendf(output, sizeof(output), &pos,
"用法: msg <用户名> <消息>\n"
" w <用户名> <消息>\n");
} else {
buffer_appendf(output, sizeof(output), &pos,
"Usage: msg <username> <message>\n"
" w <username> <message>\n");
}
buffer_appendf(output, sizeof(output), &pos, "%s",
i18n_text(client->help_lang, I18N_MSG_USAGE));
} else {
bool found = false;
client_t *target = NULL;
@ -345,21 +332,15 @@ void commands_dispatch(client_t *client) {
}
if (found) {
if (client->help_lang == LANG_ZH) {
buffer_appendf(output, sizeof(output), &pos,
"悄悄话已发送给 %s\n", target_name);
} else {
buffer_appendf(output, sizeof(output), &pos,
"Whisper sent to %s\n", target_name);
}
buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang,
I18N_MSG_SENT_FORMAT),
target_name);
} else {
if (client->help_lang == LANG_ZH) {
buffer_appendf(output, sizeof(output), &pos,
"未找到用户 '%s'\n", target_name);
} else {
buffer_appendf(output, sizeof(output), &pos,
"User '%s' not found\n", target_name);
}
buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang,
I18N_MSG_USER_NOT_FOUND_FORMAT),
target_name);
}
}
@ -375,20 +356,14 @@ void commands_dispatch(client_t *client) {
pthread_mutex_unlock(&client->io_lock);
client->unread_whispers = 0;
if (client->help_lang == LANG_ZH) {
buffer_appendf(output, sizeof(output), &pos,
"\033[1;36m悄悄话\033[0m "
"\033[2;37m· %d\033[0m\n", snap_count);
} else {
buffer_appendf(output, sizeof(output), &pos,
"\033[1;36mWhispers\033[0m "
"\033[2;37m· %d\033[0m\n", snap_count);
}
buffer_appendf(output, sizeof(output), &pos,
"\033[1;36m%s\033[0m \033[2;37m· %d\033[0m\n",
i18n_text(client->help_lang, I18N_INBOX_TITLE),
snap_count);
if (snap_count == 0) {
buffer_appendf(output, sizeof(output), &pos,
client->help_lang == LANG_ZH ?
" \033[2;37m(空)\033[0m\n" :
" \033[2;37m(empty)\033[0m\n");
" \033[2;37m%s\033[0m\n",
i18n_text(client->help_lang, I18N_INBOX_EMPTY));
}
for (int i = 0; i < snap_count; i++) {
char ts[20];
@ -406,15 +381,11 @@ void commands_dispatch(client_t *client) {
while (*new_name == ' ') new_name++;
if (new_name[0] == '\0') {
buffer_appendf(output, sizeof(output), &pos,
client->help_lang == LANG_ZH ?
"用法: nick <新用户名>\n" :
"Usage: nick <new_username>\n");
buffer_appendf(output, sizeof(output), &pos, "%s",
i18n_text(client->help_lang, I18N_NICK_USAGE));
} else if (!is_valid_username(new_name)) {
buffer_appendf(output, sizeof(output), &pos,
client->help_lang == LANG_ZH ?
"用户名无效\n" :
"Invalid username\n");
buffer_appendf(output, sizeof(output), &pos, "%s",
i18n_text(client->help_lang, I18N_NICK_INVALID));
} else {
char validated_name[MAX_USERNAME_LEN];
snprintf(validated_name, sizeof(validated_name), "%s", new_name);
@ -445,20 +416,14 @@ void commands_dispatch(client_t *client) {
pthread_rwlock_unlock(&g_room->lock);
if (taken) {
if (client->help_lang == LANG_ZH) {
buffer_appendf(output, sizeof(output), &pos,
"昵称 '%s' 已被使用\n",
validated_name);
} else {
buffer_appendf(output, sizeof(output), &pos,
"Nickname '%s' is already taken\n",
validated_name);
}
} else if (strcmp(validated_name, old_name) == 0) {
buffer_appendf(output, sizeof(output), &pos,
client->help_lang == LANG_ZH ?
"昵称未变化\n" :
"Nickname unchanged\n");
i18n_text(client->help_lang,
I18N_NICK_TAKEN_FORMAT),
validated_name);
} else if (strcmp(validated_name, old_name) == 0) {
buffer_appendf(output, sizeof(output), &pos, "%s",
i18n_text(client->help_lang,
I18N_NICK_UNCHANGED));
} else {
message_t nick_msg = { .timestamp = time(NULL) };
snprintf(nick_msg.username, MAX_USERNAME_LEN, "系统");
@ -467,15 +432,10 @@ void commands_dispatch(client_t *client) {
room_broadcast(g_room, &nick_msg);
message_save(&nick_msg);
if (client->help_lang == LANG_ZH) {
buffer_appendf(output, sizeof(output), &pos,
"昵称已修改: %s -> %s\n",
old_name, client->username);
} else {
buffer_appendf(output, sizeof(output), &pos,
"Nickname changed: %s -> %s\n",
old_name, client->username);
}
buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang,
I18N_NICK_CHANGED_FORMAT),
old_name, client->username);
}
}
@ -487,10 +447,8 @@ void commands_dispatch(client_t *client) {
char *endp;
long val = strtol(arg, &endp, 10);
if (*endp != '\0' || val < 1 || val > 50) {
buffer_appendf(output, sizeof(output), &pos,
client->help_lang == LANG_ZH ?
"用法: last [N] (N: 1-50默认 10)\n" :
"Usage: last [N] (N: 1-50, default 10)\n");
buffer_appendf(output, sizeof(output), &pos, "%s",
i18n_text(client->help_lang, I18N_LAST_USAGE));
goto cmd_done;
}
n = (int)val;
@ -498,13 +456,9 @@ void commands_dispatch(client_t *client) {
message_t *last_msgs = NULL;
int last_count = message_load(&last_msgs, n);
if (client->help_lang == LANG_ZH) {
buffer_appendf(output, sizeof(output), &pos,
"--- 最近 %d 条消息 ---\n", last_count);
} else {
buffer_appendf(output, sizeof(output), &pos,
"--- Last %d message(s) ---\n", last_count);
}
buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang, I18N_LAST_HEADER_FORMAT),
last_count);
for (int i = 0; i < last_count; i++) {
char ts[20];
struct tm tmi;
@ -519,22 +473,15 @@ void commands_dispatch(client_t *client) {
char *query = cmd + 6;
while (*query == ' ') query++;
if (*query == '\0') {
buffer_appendf(output, sizeof(output), &pos,
client->help_lang == LANG_ZH ?
"用法: search <关键词>\n" :
"Usage: search <keyword>\n");
buffer_appendf(output, sizeof(output), &pos, "%s",
i18n_text(client->help_lang, I18N_SEARCH_USAGE));
} else {
message_t *found = NULL;
int found_count = message_search(query, &found, 15);
if (client->help_lang == LANG_ZH) {
buffer_appendf(output, sizeof(output), &pos,
"--- 搜索: \"%s\" (%d 条匹配) ---\n",
query, found_count);
} else {
buffer_appendf(output, sizeof(output), &pos,
"--- Search: \"%s\" (%d match(es)) ---\n",
query, found_count);
}
buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang,
I18N_SEARCH_HEADER_FORMAT),
query, found_count);
for (int i = 0; i < found_count; i++) {
char ts[20];
struct tm tmi;
@ -554,15 +501,12 @@ void commands_dispatch(client_t *client) {
} else if (strcmp(cmd, "mute-joins") == 0 || strcmp(cmd, "mute") == 0) {
client->mute_joins = !client->mute_joins;
if (client->help_lang == LANG_ZH) {
buffer_appendf(output, sizeof(output), &pos,
"加入/离开提示: %s\n",
client->mute_joins ? "已静音" : "已开启");
} else {
buffer_appendf(output, sizeof(output), &pos,
"Join/leave notifications: %s\n",
client->mute_joins ? "muted" : "unmuted");
}
buffer_appendf(output, sizeof(output), &pos,
i18n_text(client->help_lang, I18N_MUTE_JOINS_FORMAT),
i18n_text(client->help_lang,
client->mute_joins ?
I18N_MUTE_JOINS_MUTED :
I18N_MUTE_JOINS_UNMUTED));
} else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0 ||
strcmp(cmd, "exit") == 0) {
@ -570,10 +514,8 @@ void commands_dispatch(client_t *client) {
return;
} else if (strcmp(cmd, "clear") == 0 || strcmp(cmd, "cls") == 0) {
buffer_appendf(output, sizeof(output), &pos,
client->help_lang == LANG_ZH ?
"命令输出已清空\n" :
"Command output cleared\n");
buffer_appendf(output, sizeof(output), &pos, "%s",
i18n_text(client->help_lang, I18N_CLEAR_DONE));
} else if (cmd[0] == '\0') {
/* Empty command */

View file

@ -95,6 +95,45 @@ const char *i18n_text(help_lang_t lang, i18n_text_id_t id) {
return " 公告 ";
case I18N_MOTD_CONTINUE_HINT:
return " 按任意键继续 ";
case I18N_USERS_TITLE:
return "在线用户";
case I18N_MSG_USAGE:
return "用法: msg <用户名> <消息>\n"
" w <用户名> <消息>\n";
case I18N_MSG_SENT_FORMAT:
return "悄悄话已发送给 %s\n";
case I18N_MSG_USER_NOT_FOUND_FORMAT:
return "未找到用户 '%s'\n";
case I18N_INBOX_TITLE:
return "悄悄话";
case I18N_INBOX_EMPTY:
return "(空)";
case I18N_NICK_USAGE:
return "用法: nick <新用户名>\n";
case I18N_NICK_INVALID:
return "用户名无效\n";
case I18N_NICK_TAKEN_FORMAT:
return "昵称 '%s' 已被使用\n";
case I18N_NICK_UNCHANGED:
return "昵称未变化\n";
case I18N_NICK_CHANGED_FORMAT:
return "昵称已修改: %s -> %s\n";
case I18N_LAST_USAGE:
return "用法: last [N] (N: 1-50默认 10)\n";
case I18N_LAST_HEADER_FORMAT:
return "--- 最近 %d 条消息 ---\n";
case I18N_SEARCH_USAGE:
return "用法: search <关键词>\n";
case I18N_SEARCH_HEADER_FORMAT:
return "--- 搜索: \"%s\" (%d 条匹配) ---\n";
case I18N_MUTE_JOINS_FORMAT:
return "加入/离开提示: %s\n";
case I18N_MUTE_JOINS_MUTED:
return "已静音";
case I18N_MUTE_JOINS_UNMUTED:
return "已开启";
case I18N_CLEAR_DONE:
return "命令输出已清空\n";
}
}
@ -123,6 +162,45 @@ const char *i18n_text(help_lang_t lang, i18n_text_id_t id) {
return " NOTICE ";
case I18N_MOTD_CONTINUE_HINT:
return " Press any key ";
case I18N_USERS_TITLE:
return "Online users";
case I18N_MSG_USAGE:
return "Usage: msg <username> <message>\n"
" w <username> <message>\n";
case I18N_MSG_SENT_FORMAT:
return "Whisper sent to %s\n";
case I18N_MSG_USER_NOT_FOUND_FORMAT:
return "User '%s' not found\n";
case I18N_INBOX_TITLE:
return "Whispers";
case I18N_INBOX_EMPTY:
return "(empty)";
case I18N_NICK_USAGE:
return "Usage: nick <new_username>\n";
case I18N_NICK_INVALID:
return "Invalid username\n";
case I18N_NICK_TAKEN_FORMAT:
return "Nickname '%s' is already taken\n";
case I18N_NICK_UNCHANGED:
return "Nickname unchanged\n";
case I18N_NICK_CHANGED_FORMAT:
return "Nickname changed: %s -> %s\n";
case I18N_LAST_USAGE:
return "Usage: last [N] (N: 1-50, default 10)\n";
case I18N_LAST_HEADER_FORMAT:
return "--- Last %d message(s) ---\n";
case I18N_SEARCH_USAGE:
return "Usage: search <keyword>\n";
case I18N_SEARCH_HEADER_FORMAT:
return "--- Search: \"%s\" (%d match(es)) ---\n";
case I18N_MUTE_JOINS_FORMAT:
return "Join/leave notifications: %s\n";
case I18N_MUTE_JOINS_MUTED:
return "muted";
case I18N_MUTE_JOINS_UNMUTED:
return "unmuted";
case I18N_CLEAR_DONE:
return "Command output cleared\n";
}
return "";

View file

@ -74,6 +74,14 @@ TEST(text_lookup_matches_language) {
"Press any key") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_MOTD_CONTINUE_HINT),
"按任意键") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_MSG_USAGE),
"msg <username>") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_MSG_USAGE),
"用户名") != NULL);
assert(strstr(i18n_text(LANG_EN, I18N_SEARCH_HEADER_FORMAT),
"Search") != NULL);
assert(strstr(i18n_text(LANG_ZH, I18N_SEARCH_HEADER_FORMAT),
"搜索") != NULL);
assert(strcmp(i18n_lang_code(LANG_EN), "en") == 0);
assert(strcmp(i18n_lang_code(LANG_ZH), "zh") == 0);
}