mirror of
https://oauth2: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@github.com/m1ngsama/TNT.git
synced 2026-06-26 05:34:39 +08:00
i18n: module system event messages
This commit is contained in:
parent
1d8fcea3fa
commit
07e47e65c8
12 changed files with 259 additions and 31 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -13,3 +13,4 @@ tests/unit/test_message
|
|||
tests/unit/test_chat_room
|
||||
tests/unit/test_history_view
|
||||
tests/unit/test_i18n
|
||||
tests/unit/test_system_message
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@
|
|||
the i18n table instead of being scattered through command flow logic.
|
||||
- TUI title-bar status labels, including online count, mute marker, and help
|
||||
hint, now follow the session UI language.
|
||||
- Join, leave, and nickname-change system messages now use a dedicated
|
||||
`system_message` module, follow the sender's session language, and keep
|
||||
`:mute-joins` filtering compatible with both Chinese and English logs.
|
||||
|
||||
### Changed
|
||||
- NORMAL mode now opens at the latest visible messages instead of the oldest
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ typedef enum {
|
|||
I18N_TITLE_ONLINE_FORMAT,
|
||||
I18N_TITLE_MUTED,
|
||||
I18N_TITLE_HELP_HINT,
|
||||
I18N_SYSTEM_USERNAME,
|
||||
I18N_SYSTEM_JOIN_FORMAT,
|
||||
I18N_SYSTEM_LEAVE_FORMAT,
|
||||
I18N_SYSTEM_NICK_FORMAT,
|
||||
I18N_USERS_TITLE,
|
||||
I18N_MSG_USAGE,
|
||||
I18N_MSG_SENT_FORMAT,
|
||||
|
|
|
|||
17
include/system_message.h
Normal file
17
include/system_message.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef SYSTEM_MESSAGE_H
|
||||
#define SYSTEM_MESSAGE_H
|
||||
|
||||
#include "common.h"
|
||||
#include "message.h"
|
||||
|
||||
void system_message_make_join(message_t *msg, const char *username,
|
||||
help_lang_t lang);
|
||||
void system_message_make_leave(message_t *msg, const char *username,
|
||||
help_lang_t lang);
|
||||
void system_message_make_nick(message_t *msg, const char *old_name,
|
||||
const char *new_name, help_lang_t lang);
|
||||
|
||||
bool system_message_is_system(const message_t *msg);
|
||||
bool system_message_is_join_leave(const message_t *msg);
|
||||
|
||||
#endif /* SYSTEM_MESSAGE_H */
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
#include "i18n.h"
|
||||
#include "message.h"
|
||||
#include "support.h"
|
||||
#include "system_message.h"
|
||||
#include "tui.h"
|
||||
#include "utf8.h"
|
||||
#include <stdio.h>
|
||||
|
|
@ -425,10 +426,9 @@ void commands_dispatch(client_t *client) {
|
|||
i18n_text(client->help_lang,
|
||||
I18N_NICK_UNCHANGED));
|
||||
} else {
|
||||
message_t nick_msg = { .timestamp = time(NULL) };
|
||||
snprintf(nick_msg.username, MAX_USERNAME_LEN, "系统");
|
||||
snprintf(nick_msg.content, MAX_MESSAGE_LEN,
|
||||
"%s 更名为 %s", old_name, client->username);
|
||||
message_t nick_msg;
|
||||
system_message_make_nick(&nick_msg, old_name,
|
||||
client->username, client->help_lang);
|
||||
room_broadcast(g_room, &nick_msg);
|
||||
message_save(&nick_msg);
|
||||
|
||||
|
|
|
|||
16
src/i18n.c
16
src/i18n.c
|
|
@ -101,6 +101,14 @@ const char *i18n_text(help_lang_t lang, i18n_text_id_t id) {
|
|||
return "静音";
|
||||
case I18N_TITLE_HELP_HINT:
|
||||
return "? 帮助";
|
||||
case I18N_SYSTEM_USERNAME:
|
||||
return "系统";
|
||||
case I18N_SYSTEM_JOIN_FORMAT:
|
||||
return "%s 加入了聊天室";
|
||||
case I18N_SYSTEM_LEAVE_FORMAT:
|
||||
return "%s 离开了聊天室";
|
||||
case I18N_SYSTEM_NICK_FORMAT:
|
||||
return "%s 更名为 %s";
|
||||
case I18N_USERS_TITLE:
|
||||
return "在线用户";
|
||||
case I18N_MSG_USAGE:
|
||||
|
|
@ -174,6 +182,14 @@ const char *i18n_text(help_lang_t lang, i18n_text_id_t id) {
|
|||
return "muted";
|
||||
case I18N_TITLE_HELP_HINT:
|
||||
return "? help";
|
||||
case I18N_SYSTEM_USERNAME:
|
||||
return "system";
|
||||
case I18N_SYSTEM_JOIN_FORMAT:
|
||||
return "%s joined the room";
|
||||
case I18N_SYSTEM_LEAVE_FORMAT:
|
||||
return "%s left the room";
|
||||
case I18N_SYSTEM_NICK_FORMAT:
|
||||
return "%s renamed to %s";
|
||||
case I18N_USERS_TITLE:
|
||||
return "Online users";
|
||||
case I18N_MSG_USAGE:
|
||||
|
|
|
|||
18
src/input.c
18
src/input.c
|
|
@ -8,6 +8,7 @@
|
|||
#include "i18n.h"
|
||||
#include "message.h"
|
||||
#include "ratelimit.h"
|
||||
#include "system_message.h"
|
||||
#include "tui.h"
|
||||
#include "utf8.h"
|
||||
#include <libssh/callbacks.h>
|
||||
|
|
@ -701,12 +702,8 @@ void input_run_session(client_t *client) {
|
|||
bracketed_paste_enabled = true;
|
||||
|
||||
/* Broadcast join message */
|
||||
message_t join_msg = {
|
||||
.timestamp = time(NULL),
|
||||
};
|
||||
strncpy(join_msg.username, "系统", MAX_USERNAME_LEN - 1);
|
||||
join_msg.username[MAX_USERNAME_LEN - 1] = '\0';
|
||||
snprintf(join_msg.content, MAX_MESSAGE_LEN, "%s 加入了聊天室", client->username);
|
||||
message_t join_msg;
|
||||
system_message_make_join(&join_msg, client->username, client->help_lang);
|
||||
room_broadcast(g_room, &join_msg);
|
||||
message_save(&join_msg);
|
||||
|
||||
|
|
@ -892,12 +889,9 @@ cleanup:
|
|||
|
||||
/* Broadcast leave message */
|
||||
if (joined_room) {
|
||||
message_t leave_msg = {
|
||||
.timestamp = time(NULL),
|
||||
};
|
||||
strncpy(leave_msg.username, "系统", MAX_USERNAME_LEN - 1);
|
||||
leave_msg.username[MAX_USERNAME_LEN - 1] = '\0';
|
||||
snprintf(leave_msg.content, MAX_MESSAGE_LEN, "%s 离开了聊天室", client->username);
|
||||
message_t leave_msg;
|
||||
system_message_make_leave(&leave_msg, client->username,
|
||||
client->help_lang);
|
||||
|
||||
client->connected = false;
|
||||
room_remove_client(g_room, client);
|
||||
|
|
|
|||
72
src/system_message.c
Normal file
72
src/system_message.c
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#include "system_message.h"
|
||||
#include "i18n.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
static void system_message_init(message_t *msg, help_lang_t lang) {
|
||||
if (!msg) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(msg, 0, sizeof(*msg));
|
||||
msg->timestamp = time(NULL);
|
||||
snprintf(msg->username, sizeof(msg->username), "%s",
|
||||
i18n_text(lang, I18N_SYSTEM_USERNAME));
|
||||
}
|
||||
|
||||
void system_message_make_join(message_t *msg, const char *username,
|
||||
help_lang_t lang) {
|
||||
system_message_init(msg, lang);
|
||||
if (!msg) {
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(msg->content, sizeof(msg->content),
|
||||
i18n_text(lang, I18N_SYSTEM_JOIN_FORMAT),
|
||||
username ? username : "");
|
||||
}
|
||||
|
||||
void system_message_make_leave(message_t *msg, const char *username,
|
||||
help_lang_t lang) {
|
||||
system_message_init(msg, lang);
|
||||
if (!msg) {
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(msg->content, sizeof(msg->content),
|
||||
i18n_text(lang, I18N_SYSTEM_LEAVE_FORMAT),
|
||||
username ? username : "");
|
||||
}
|
||||
|
||||
void system_message_make_nick(message_t *msg, const char *old_name,
|
||||
const char *new_name, help_lang_t lang) {
|
||||
system_message_init(msg, lang);
|
||||
if (!msg) {
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(msg->content, sizeof(msg->content),
|
||||
i18n_text(lang, I18N_SYSTEM_NICK_FORMAT),
|
||||
old_name ? old_name : "", new_name ? new_name : "");
|
||||
}
|
||||
|
||||
bool system_message_is_system(const message_t *msg) {
|
||||
if (!msg) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return strcmp(msg->username, "系统") == 0 ||
|
||||
strcmp(msg->username, "system") == 0;
|
||||
}
|
||||
|
||||
bool system_message_is_join_leave(const message_t *msg) {
|
||||
if (!system_message_is_system(msg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return strstr(msg->content, "加入了聊天室") != NULL ||
|
||||
strstr(msg->content, "离开了聊天室") != NULL ||
|
||||
strstr(msg->content, "joined the room") != NULL ||
|
||||
strstr(msg->content, "left the room") != NULL;
|
||||
}
|
||||
23
src/tui.c
23
src/tui.c
|
|
@ -4,16 +4,11 @@
|
|||
#include "chat_room.h"
|
||||
#include "history_view.h"
|
||||
#include "i18n.h"
|
||||
#include "system_message.h"
|
||||
#include "tui_status.h"
|
||||
#include "utf8.h"
|
||||
#include <unistd.h>
|
||||
|
||||
static bool is_join_leave_msg(const message_t *msg) {
|
||||
if (strcmp(msg->username, "系统") != 0) return false;
|
||||
return strstr(msg->content, "加入了聊天室") != NULL ||
|
||||
strstr(msg->content, "离开了聊天室") != NULL;
|
||||
}
|
||||
|
||||
static const char *username_color(const char *name) {
|
||||
static const char *colors[] = {
|
||||
"\033[31m", "\033[32m", "\033[33m",
|
||||
|
|
@ -37,7 +32,7 @@ static void format_message_colored(const message_t *msg, char *buffer,
|
|||
* marker so they can scan their own contributions when scrolling. */
|
||||
bool is_self = false;
|
||||
if (my_username && my_username[0] != '\0' &&
|
||||
strcmp(msg->username, "系统") != 0) {
|
||||
!system_message_is_system(msg)) {
|
||||
if (strcmp(msg->username, "*") == 0) {
|
||||
/* /me message: content starts with the actor's username */
|
||||
size_t un_len = strlen(my_username);
|
||||
|
|
@ -54,7 +49,7 @@ static void format_message_colored(const message_t *msg, char *buffer,
|
|||
|
||||
bool mentioned = false;
|
||||
if (my_username && my_username[0] != '\0' &&
|
||||
strcmp(msg->username, "系统") != 0) {
|
||||
!system_message_is_system(msg)) {
|
||||
char mention[MAX_USERNAME_LEN + 2];
|
||||
snprintf(mention, sizeof(mention), "@%s", my_username);
|
||||
if (strstr(msg->content, mention) != NULL) {
|
||||
|
|
@ -64,7 +59,7 @@ static void format_message_colored(const message_t *msg, char *buffer,
|
|||
const char *hl_start = mentioned ? "\033[1;33m" : "";
|
||||
const char *hl_end = mentioned ? "\033[0m" : "";
|
||||
|
||||
if (strcmp(msg->username, "系统") == 0) {
|
||||
if (system_message_is_system(msg)) {
|
||||
snprintf(buffer, buf_size,
|
||||
"%s\033[90m--> %s\033[0m", gutter, msg->content);
|
||||
} else if (strcmp(msg->username, "*") == 0) {
|
||||
|
|
@ -80,7 +75,7 @@ static void format_message_colored(const message_t *msg, char *buffer,
|
|||
|
||||
/* Plain-text version for width calculation — gutter is 1 column. */
|
||||
char plain[MAX_MESSAGE_LEN + 128];
|
||||
if (strcmp(msg->username, "系统") == 0) {
|
||||
if (system_message_is_system(msg)) {
|
||||
snprintf(plain, sizeof(plain), " --> %s", msg->content);
|
||||
} else if (strcmp(msg->username, "*") == 0) {
|
||||
snprintf(plain, sizeof(plain), " %s * %s", time_str, msg->content);
|
||||
|
|
@ -94,7 +89,7 @@ static void format_message_colored(const message_t *msg, char *buffer,
|
|||
* 1-column gutter so the budget math comes out right. */
|
||||
int prefix_width;
|
||||
char prefix_plain[256];
|
||||
if (strcmp(msg->username, "系统") == 0) {
|
||||
if (system_message_is_system(msg)) {
|
||||
snprintf(prefix_plain, sizeof(prefix_plain), " --> ");
|
||||
} else if (strcmp(msg->username, "*") == 0) {
|
||||
snprintf(prefix_plain, sizeof(prefix_plain), " %s * ", time_str);
|
||||
|
|
@ -107,7 +102,7 @@ static void format_message_colored(const message_t *msg, char *buffer,
|
|||
if (content_width < 4) content_width = 4;
|
||||
|
||||
char truncated_content[MAX_MESSAGE_LEN];
|
||||
if (strcmp(msg->username, "系统") == 0) {
|
||||
if (system_message_is_system(msg)) {
|
||||
strncpy(truncated_content, msg->content, sizeof(truncated_content) - 1);
|
||||
truncated_content[sizeof(truncated_content) - 1] = '\0';
|
||||
} else if (strcmp(msg->username, "*") == 0) {
|
||||
|
|
@ -118,7 +113,7 @@ static void format_message_colored(const message_t *msg, char *buffer,
|
|||
}
|
||||
utf8_truncate(truncated_content, content_width);
|
||||
|
||||
if (strcmp(msg->username, "系统") == 0) {
|
||||
if (system_message_is_system(msg)) {
|
||||
snprintf(buffer, buf_size,
|
||||
"%s\033[90m--> %s\033[0m", gutter, truncated_content);
|
||||
} else if (strcmp(msg->username, "*") == 0) {
|
||||
|
|
@ -325,7 +320,7 @@ void tui_render_screen(client_t *client) {
|
|||
if (client->mute_joins && msg_snapshot) {
|
||||
int filtered = 0;
|
||||
for (int i = 0; i < snapshot_count; i++) {
|
||||
if (!is_join_leave_msg(&msg_snapshot[i])) {
|
||||
if (!system_message_is_join_leave(&msg_snapshot[i])) {
|
||||
msg_snapshot[filtered++] = msg_snapshot[i];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -322,6 +322,48 @@ else
|
|||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
|
||||
SYSTEM_MESSAGES_SCRIPT="$STATE_DIR/system-messages.expect"
|
||||
cat >"$SYSTEM_MESSAGES_SCRIPT" <<EOF
|
||||
set timeout 10
|
||||
spawn ssh $SSH_OPTS anonymous@127.0.0.1
|
||||
sleep 1
|
||||
send -- "systemuser\r"
|
||||
expect ":support"
|
||||
send -- "\033"
|
||||
expect "NORMAL"
|
||||
send -- ":"
|
||||
expect ":"
|
||||
send -- "lang en\r"
|
||||
expect "Language set to: en"
|
||||
expect "Press any key"
|
||||
send -- "q"
|
||||
expect "NORMAL"
|
||||
send -- ":"
|
||||
expect ":"
|
||||
send -- "nick systemuser2\r"
|
||||
expect "Nickname changed: systemuser -> systemuser2"
|
||||
expect "Press any key"
|
||||
send -- "q"
|
||||
sleep 0.2
|
||||
send -- "\003"
|
||||
sleep 0.2
|
||||
send -- "\003"
|
||||
expect eof
|
||||
EOF
|
||||
|
||||
if expect "$SYSTEM_MESSAGES_SCRIPT" >"$STATE_DIR/system-messages.log" 2>&1 &&
|
||||
grep -q 'system|systemuser renamed to systemuser2' "$STATE_DIR/messages.log" &&
|
||||
grep -q 'system|systemuser2 left the room' "$STATE_DIR/messages.log"; then
|
||||
echo "✓ system messages follow session language"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo "x localized system messages failed"
|
||||
sed -n '1,220p' "$STATE_DIR/system-messages.log" 2>/dev/null || true
|
||||
cat "$STATE_DIR/messages.log" 2>/dev/null || true
|
||||
sed -n '1,120p' "$STATE_DIR/server.log"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
|
||||
printf '维护窗口\n' >"$STATE_DIR/motd.txt"
|
||||
MOTD_SCRIPT="$STATE_DIR/motd.expect"
|
||||
cat >"$MOTD_SCRIPT" <<EOF
|
||||
|
|
|
|||
|
|
@ -16,8 +16,9 @@ COMMON_SRC = ../../src/common.c
|
|||
CHAT_ROOM_SRC = ../../src/chat_room.c
|
||||
HISTORY_VIEW_SRC = ../../src/history_view.c
|
||||
I18N_SRC = ../../src/i18n.c
|
||||
SYSTEM_MESSAGE_SRC = ../../src/system_message.c
|
||||
|
||||
TESTS = test_utf8 test_message test_chat_room test_history_view test_i18n
|
||||
TESTS = test_utf8 test_message test_chat_room test_history_view test_i18n test_system_message
|
||||
|
||||
.PHONY: all clean run
|
||||
|
||||
|
|
@ -38,6 +39,9 @@ test_history_view: test_history_view.c $(HISTORY_VIEW_SRC)
|
|||
test_i18n: test_i18n.c $(I18N_SRC)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
test_system_message: test_system_message.c $(SYSTEM_MESSAGE_SRC) $(I18N_SRC)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
run: all
|
||||
@echo "=== Running UTF-8 Tests ==="
|
||||
./test_utf8
|
||||
|
|
@ -53,6 +57,9 @@ run: all
|
|||
@echo ""
|
||||
@echo "=== Running i18n Tests ==="
|
||||
./test_i18n
|
||||
@echo ""
|
||||
@echo "=== Running System Message Tests ==="
|
||||
./test_system_message
|
||||
|
||||
clean:
|
||||
rm -f $(TESTS) *.o test_messages.log
|
||||
|
|
|
|||
77
tests/unit/test_system_message.c
Normal file
77
tests/unit/test_system_message.c
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/* Unit tests for localized system event messages */
|
||||
|
||||
#include "../../include/system_message.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(join_leave_follow_language) {
|
||||
message_t msg;
|
||||
|
||||
system_message_make_join(&msg, "alice", LANG_ZH);
|
||||
assert(strcmp(msg.username, "系统") == 0);
|
||||
assert(strstr(msg.content, "alice") != NULL);
|
||||
assert(strstr(msg.content, "加入了聊天室") != NULL);
|
||||
assert(system_message_is_system(&msg));
|
||||
assert(system_message_is_join_leave(&msg));
|
||||
|
||||
system_message_make_leave(&msg, "bob", LANG_EN);
|
||||
assert(strcmp(msg.username, "system") == 0);
|
||||
assert(strstr(msg.content, "bob") != NULL);
|
||||
assert(strstr(msg.content, "left the room") != NULL);
|
||||
assert(system_message_is_system(&msg));
|
||||
assert(system_message_is_join_leave(&msg));
|
||||
}
|
||||
|
||||
TEST(nick_messages_are_system_events_not_join_leave) {
|
||||
message_t msg;
|
||||
|
||||
system_message_make_nick(&msg, "old", "new", LANG_EN);
|
||||
assert(strcmp(msg.username, "system") == 0);
|
||||
assert(strstr(msg.content, "old") != NULL);
|
||||
assert(strstr(msg.content, "new") != NULL);
|
||||
assert(strstr(msg.content, "renamed") != NULL);
|
||||
assert(system_message_is_system(&msg));
|
||||
assert(!system_message_is_join_leave(&msg));
|
||||
|
||||
system_message_make_nick(&msg, "旧", "新", LANG_ZH);
|
||||
assert(strcmp(msg.username, "系统") == 0);
|
||||
assert(strstr(msg.content, "更名为") != NULL);
|
||||
assert(system_message_is_system(&msg));
|
||||
assert(!system_message_is_join_leave(&msg));
|
||||
}
|
||||
|
||||
TEST(legacy_system_names_are_recognized) {
|
||||
message_t msg = {0};
|
||||
|
||||
snprintf(msg.username, sizeof(msg.username), "系统");
|
||||
snprintf(msg.content, sizeof(msg.content), "alice 离开了聊天室");
|
||||
assert(system_message_is_system(&msg));
|
||||
assert(system_message_is_join_leave(&msg));
|
||||
|
||||
snprintf(msg.username, sizeof(msg.username), "system");
|
||||
snprintf(msg.content, sizeof(msg.content), "alice joined the room");
|
||||
assert(system_message_is_system(&msg));
|
||||
assert(system_message_is_join_leave(&msg));
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("Running system message unit tests...\n\n");
|
||||
|
||||
RUN_TEST(join_leave_follow_language);
|
||||
RUN_TEST(nick_messages_are_system_events_not_join_leave);
|
||||
RUN_TEST(legacy_system_names_are_recognized);
|
||||
|
||||
printf("\n✓ All %d tests passed!\n", tests_passed);
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in a new issue