i18n: split text catalog from language parsing

This commit is contained in:
m1ngsama 2026-05-24 12:18:21 +08:00
parent 06a10e2df8
commit 8fbd789dfb
10 changed files with 308 additions and 277 deletions

View file

@ -259,7 +259,8 @@ TNT/
│ ├── help_text.c # full-screen key reference content
│ ├── manual.c # concise manual panel rendering
│ ├── manual_text.c # concise manual content
│ ├── i18n.c # language selection and shared UI text
│ ├── i18n.c # UI language and locale selection
│ ├── i18n_text.c # shared UI text catalog
│ ├── ratelimit.c # connection limits and rate limiting
│ ├── tui.c # terminal UI rendering
│ ├── tui_status.c # status/input line rendering

View file

@ -3,6 +3,9 @@
## Unreleased
### Changed
- Split UI-language parsing from localized text lookup: `src/i18n.c` now owns
locale/code parsing, while `src/i18n_text.c` owns the table-driven text
catalog with coverage checks for every message ID.
- 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.

View file

@ -449,11 +449,11 @@ keys.
### Current Limitations
The current `src/i18n.c` implementation is a small-project translation table
implemented in C, not a full gettext catalog. It is acceptable for two
languages, but adding more languages should first split message lookup from
language parsing and move toward catalog-like storage. Do not grow the
current approach by adding ad hoc branches for every locale.
The current `src/i18n_text.c` implementation is a small-project translation
table implemented in C, not a full gettext catalog. It is acceptable for two
languages because message lookup is already split from language parsing in
`src/i18n.c`, but adding more languages should move toward catalog-like
storage instead of adding ad hoc branches for every locale.
Relevant conventions:
- POSIX locale variables: `LANG`, `LC_ALL`, `LC_MESSAGES`.

View file

@ -57,7 +57,8 @@ STRUCTURE
src/help_text.c full-screen key reference text
src/manual.c concise manual panel rendering
src/manual_text.c concise manual content
src/i18n.c language selection and shared text
src/i18n.c UI language and locale selection
src/i18n_text.c shared UI text catalog
src/ratelimit.c connection limits and rate limiting
src/tui.c rendering
src/tui_status.c status/input line rendering

View file

@ -60,7 +60,8 @@ typedef enum {
I18N_EXEC_POST_USAGE,
I18N_EXEC_POST_EMPTY,
I18N_EXEC_POST_INVALID_UTF8,
I18N_EXEC_UNKNOWN_COMMAND_FORMAT
I18N_EXEC_UNKNOWN_COMMAND_FORMAT,
I18N_TEXT_COUNT
} i18n_text_id_t;
bool i18n_try_parse_ui_lang(const char *value, ui_lang_t *lang);

View file

@ -32,5 +32,4 @@ void tui_clear_screen(struct client *client);
* itself afterwards. */
void tui_render_welcome(struct client *client);
/* Get help text based on language */
#endif /* TUI_H */

View file

@ -84,269 +84,3 @@ ui_lang_t i18n_default_ui_lang(void) {
const char *i18n_ui_lang_code(ui_lang_t lang) {
return lang == UI_LANG_ZH ? "zh" : "en";
}
const char *i18n_text(ui_lang_t lang, i18n_text_id_t id) {
if (lang == UI_LANG_ZH) {
switch (id) {
case I18N_USERNAME_PROMPT:
return " 请输入用户名 (留空 anonymous): ";
case I18N_INVALID_USERNAME:
return "用户名无效,已改用 anonymous。\r\n";
case I18N_ROOM_FULL:
return "房间已满\r\n";
case I18N_WELCOME_SUBTITLE:
return "匿名聊天室 · SSH";
case I18N_WELCOME_TAGLINE:
return "键盘友好的终端交流";
case I18N_WELCOME_FALLBACK_FORMAT:
return "TNT %s - SSH 匿名聊天室\r\n\r\n";
case I18N_INSERT_HINT_WIDE:
return "Enter 发送 · Esc 浏览 · :help";
case I18N_INSERT_HINT_NARROW:
return "Enter · Esc · :help";
case I18N_NORMAL_LATEST:
return "G 最新";
case I18N_NORMAL_NEW_MESSAGES:
return "新消息";
case I18N_HELP_TITLE:
return " 按键 ";
case I18N_HELP_STATUS_FORMAT:
return "-- 按键参考 -- (%d/%d) j/k:滚动 g/G:首尾 e/z:语言 q:关闭";
case I18N_COMMAND_OUTPUT_TITLE:
return " 命令输出 ";
case I18N_COMMAND_OUTPUT_STATUS_FORMAT:
return "-- 命令输出 -- (%d/%d) j/k:滚动 Ctrl-D/U:半页 g/G:首尾 q:关闭";
case I18N_MOTD_TITLE:
return " 公告 ";
case I18N_MOTD_CONTINUE_HINT:
return " 按任意键继续 ";
case I18N_TITLE_ONLINE_FORMAT:
return "在线 %d";
case I18N_TITLE_MUTED:
return "静音";
case I18N_TITLE_HELP_HINT:
return "? 按键";
case I18N_IDLE_TIMEOUT_FORMAT:
return "\r\n\033[33m已断开: 空闲超时 (%d 分钟)\033[0m\r\n";
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:
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";
case I18N_LANG_CURRENT_FORMAT:
return "当前语言: %s\n"
"用法: lang <en|zh>\n";
case I18N_LANG_SET_FORMAT:
return "语言已切换为: %s\n";
case I18N_LANG_UNSUPPORTED_FORMAT:
return "不支持的语言: %s\n"
"用法: lang <en|zh>\n";
case I18N_UNKNOWN_COMMAND_FORMAT:
return "未知命令: %s\n";
case I18N_DID_YOU_MEAN_FORMAT:
return "你是想输入 :%s 吗?\n";
case I18N_UNKNOWN_GUIDANCE:
return "输入 :help 查看帮助\n";
case I18N_EXEC_HELP:
return "TNT exec 接口\n"
"命令:\n"
" help 显示此帮助\n"
" health 输出服务健康状态\n"
" users [--json] 列出在线用户\n"
" stats [--json] 输出房间统计\n"
" tail [N] 输出最近消息\n"
" tail -n N 输出最近消息\n"
" post MESSAGE 非交互发送消息\n"
" post \"/me act\" 发送动作消息\n"
" exit 成功退出\n";
case I18N_EXEC_USERS_USAGE:
return "users: 用法: users [--json]\n";
case I18N_EXEC_STATS_USAGE:
return "stats: 用法: stats [--json]\n";
case I18N_EXEC_TAIL_USAGE:
return "tail: 用法: tail [N] | tail -n N\n";
case I18N_EXEC_POST_USAGE:
return "post: 用法: post MESSAGE\n";
case I18N_EXEC_POST_EMPTY:
return "post: 消息不能为空\n";
case I18N_EXEC_POST_INVALID_UTF8:
return "post: 输入不是有效 UTF-8\n";
case I18N_EXEC_UNKNOWN_COMMAND_FORMAT:
return "未知命令: %s\n";
}
}
switch (id) {
case I18N_USERNAME_PROMPT:
return " Enter display name (blank for anonymous): ";
case I18N_INVALID_USERNAME:
return "Invalid username. Using 'anonymous' instead.\r\n";
case I18N_ROOM_FULL:
return "Room is full\r\n";
case I18N_WELCOME_SUBTITLE:
return "anonymous chat · SSH";
case I18N_WELCOME_TAGLINE:
return "keyboard-first terminal chat";
case I18N_WELCOME_FALLBACK_FORMAT:
return "TNT %s - anonymous chat over SSH\r\n\r\n";
case I18N_INSERT_HINT_WIDE:
return "Enter send · Esc browse · :help";
case I18N_INSERT_HINT_NARROW:
return "Enter · Esc · :help";
case I18N_NORMAL_LATEST:
return "G latest";
case I18N_NORMAL_NEW_MESSAGES:
return "new";
case I18N_HELP_TITLE:
return " KEYS ";
case I18N_HELP_STATUS_FORMAT:
return "-- KEY REFERENCE -- (%d/%d) j/k:scroll g/G:top/bottom e/z:lang q:close";
case I18N_COMMAND_OUTPUT_TITLE:
return " COMMAND OUTPUT ";
case I18N_COMMAND_OUTPUT_STATUS_FORMAT:
return "-- COMMAND OUTPUT -- (%d/%d) j/k:scroll Ctrl-D/U:half g/G:top/bottom q:close";
case I18N_MOTD_TITLE:
return " NOTICE ";
case I18N_MOTD_CONTINUE_HINT:
return " Press any key ";
case I18N_TITLE_ONLINE_FORMAT:
return "online %d";
case I18N_TITLE_MUTED:
return "muted";
case I18N_TITLE_HELP_HINT:
return "? keys";
case I18N_IDLE_TIMEOUT_FORMAT:
return "\r\n\033[33mDisconnected: idle timeout (%d min)\033[0m\r\n";
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:
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";
case I18N_LANG_CURRENT_FORMAT:
return "Current language: %s\n"
"Usage: lang <en|zh>\n";
case I18N_LANG_SET_FORMAT:
return "Language set to: %s\n";
case I18N_LANG_UNSUPPORTED_FORMAT:
return "Unsupported language: %s\n"
"Usage: lang <en|zh>\n";
case I18N_UNKNOWN_COMMAND_FORMAT:
return "Unknown command: %s\n";
case I18N_DID_YOU_MEAN_FORMAT:
return "Did you mean :%s?\n";
case I18N_UNKNOWN_GUIDANCE:
return "Type :help for help\n";
case I18N_EXEC_HELP:
return "TNT exec interface\n"
"Commands:\n"
" help Show this help\n"
" health Print service health\n"
" users [--json] List online users\n"
" stats [--json] Print room statistics\n"
" tail [N] Print recent messages\n"
" tail -n N Print recent messages\n"
" post MESSAGE Post a message non-interactively\n"
" post \"/me act\" Post an action message\n"
" exit Exit successfully\n";
case I18N_EXEC_USERS_USAGE:
return "users: usage: users [--json]\n";
case I18N_EXEC_STATS_USAGE:
return "stats: usage: stats [--json]\n";
case I18N_EXEC_TAIL_USAGE:
return "tail: usage: tail [N] | tail -n N\n";
case I18N_EXEC_POST_USAGE:
return "post: usage: post MESSAGE\n";
case I18N_EXEC_POST_EMPTY:
return "post: message cannot be empty\n";
case I18N_EXEC_POST_INVALID_UTF8:
return "post: invalid UTF-8 input\n";
case I18N_EXEC_UNKNOWN_COMMAND_FORMAT:
return "Unknown command: %s\n";
}
return "";
}

278
src/i18n_text.c Normal file
View file

@ -0,0 +1,278 @@
#include "i18n.h"
typedef struct {
const char *en;
const char *zh;
} i18n_text_entry_t;
static const i18n_text_entry_t text_catalog[I18N_TEXT_COUNT] = {
[I18N_USERNAME_PROMPT] = {
" Enter display name (blank for anonymous): ",
" 请输入用户名 (留空 anonymous): "
},
[I18N_INVALID_USERNAME] = {
"Invalid username. Using 'anonymous' instead.\r\n",
"用户名无效,已改用 anonymous。\r\n"
},
[I18N_ROOM_FULL] = {
"Room is full\r\n",
"房间已满\r\n"
},
[I18N_WELCOME_SUBTITLE] = {
"anonymous chat · SSH",
"匿名聊天室 · SSH"
},
[I18N_WELCOME_TAGLINE] = {
"keyboard-first terminal chat",
"键盘友好的终端交流"
},
[I18N_WELCOME_FALLBACK_FORMAT] = {
"TNT %s - anonymous chat over SSH\r\n\r\n",
"TNT %s - SSH 匿名聊天室\r\n\r\n"
},
[I18N_INSERT_HINT_WIDE] = {
"Enter send · Esc browse · :help",
"Enter 发送 · Esc 浏览 · :help"
},
[I18N_INSERT_HINT_NARROW] = {
"Enter · Esc · :help",
"Enter · Esc · :help"
},
[I18N_NORMAL_LATEST] = {
"G latest",
"G 最新"
},
[I18N_NORMAL_NEW_MESSAGES] = {
"new",
"新消息"
},
[I18N_HELP_TITLE] = {
" KEYS ",
" 按键 "
},
[I18N_HELP_STATUS_FORMAT] = {
"-- KEY REFERENCE -- (%d/%d) j/k:scroll g/G:top/bottom e/z:lang q:close",
"-- 按键参考 -- (%d/%d) j/k:滚动 g/G:首尾 e/z:语言 q:关闭"
},
[I18N_COMMAND_OUTPUT_TITLE] = {
" COMMAND OUTPUT ",
" 命令输出 "
},
[I18N_COMMAND_OUTPUT_STATUS_FORMAT] = {
"-- COMMAND OUTPUT -- (%d/%d) j/k:scroll Ctrl-D/U:half g/G:top/bottom q:close",
"-- 命令输出 -- (%d/%d) j/k:滚动 Ctrl-D/U:半页 g/G:首尾 q:关闭"
},
[I18N_MOTD_TITLE] = {
" NOTICE ",
" 公告 "
},
[I18N_MOTD_CONTINUE_HINT] = {
" Press any key ",
" 按任意键继续 "
},
[I18N_TITLE_ONLINE_FORMAT] = {
"online %d",
"在线 %d"
},
[I18N_TITLE_MUTED] = {
"muted",
"静音"
},
[I18N_TITLE_HELP_HINT] = {
"? keys",
"? 按键"
},
[I18N_IDLE_TIMEOUT_FORMAT] = {
"\r\n\033[33mDisconnected: idle timeout (%d min)\033[0m\r\n",
"\r\n\033[33m已断开: 空闲超时 (%d 分钟)\033[0m\r\n"
},
[I18N_SYSTEM_USERNAME] = {
"system",
"系统"
},
[I18N_SYSTEM_JOIN_FORMAT] = {
"%s joined the room",
"%s 加入了聊天室"
},
[I18N_SYSTEM_LEAVE_FORMAT] = {
"%s left the room",
"%s 离开了聊天室"
},
[I18N_SYSTEM_NICK_FORMAT] = {
"%s renamed to %s",
"%s 更名为 %s"
},
[I18N_USERS_TITLE] = {
"Online users",
"在线用户"
},
[I18N_MSG_USAGE] = {
"Usage: msg <username> <message>\n"
" w <username> <message>\n",
"用法: msg <用户名> <消息>\n"
" w <用户名> <消息>\n"
},
[I18N_MSG_SENT_FORMAT] = {
"Whisper sent to %s\n",
"悄悄话已发送给 %s\n"
},
[I18N_MSG_USER_NOT_FOUND_FORMAT] = {
"User '%s' not found\n",
"未找到用户 '%s'\n"
},
[I18N_INBOX_TITLE] = {
"Whispers",
"悄悄话"
},
[I18N_INBOX_EMPTY] = {
"(empty)",
"(空)"
},
[I18N_NICK_USAGE] = {
"Usage: nick <new_username>\n",
"用法: nick <新用户名>\n"
},
[I18N_NICK_INVALID] = {
"Invalid username\n",
"用户名无效\n"
},
[I18N_NICK_TAKEN_FORMAT] = {
"Nickname '%s' is already taken\n",
"昵称 '%s' 已被使用\n"
},
[I18N_NICK_UNCHANGED] = {
"Nickname unchanged\n",
"昵称未变化\n"
},
[I18N_NICK_CHANGED_FORMAT] = {
"Nickname changed: %s -> %s\n",
"昵称已修改: %s -> %s\n"
},
[I18N_LAST_USAGE] = {
"Usage: last [N] (N: 1-50, default 10)\n",
"用法: last [N] (N: 1-50默认 10)\n"
},
[I18N_LAST_HEADER_FORMAT] = {
"--- Last %d message(s) ---\n",
"--- 最近 %d 条消息 ---\n"
},
[I18N_SEARCH_USAGE] = {
"Usage: search <keyword>\n",
"用法: search <关键词>\n"
},
[I18N_SEARCH_HEADER_FORMAT] = {
"--- Search: \"%s\" (%d match(es)) ---\n",
"--- 搜索: \"%s\" (%d 条匹配) ---\n"
},
[I18N_MUTE_JOINS_FORMAT] = {
"Join/leave notifications: %s\n",
"加入/离开提示: %s\n"
},
[I18N_MUTE_JOINS_MUTED] = {
"muted",
"已静音"
},
[I18N_MUTE_JOINS_UNMUTED] = {
"unmuted",
"已开启"
},
[I18N_CLEAR_DONE] = {
"Command output cleared\n",
"命令输出已清空\n"
},
[I18N_LANG_CURRENT_FORMAT] = {
"Current language: %s\n"
"Usage: lang <en|zh>\n",
"当前语言: %s\n"
"用法: lang <en|zh>\n"
},
[I18N_LANG_SET_FORMAT] = {
"Language set to: %s\n",
"语言已切换为: %s\n"
},
[I18N_LANG_UNSUPPORTED_FORMAT] = {
"Unsupported language: %s\n"
"Usage: lang <en|zh>\n",
"不支持的语言: %s\n"
"用法: lang <en|zh>\n"
},
[I18N_UNKNOWN_COMMAND_FORMAT] = {
"Unknown command: %s\n",
"未知命令: %s\n"
},
[I18N_DID_YOU_MEAN_FORMAT] = {
"Did you mean :%s?\n",
"你是想输入 :%s 吗?\n"
},
[I18N_UNKNOWN_GUIDANCE] = {
"Type :help for help\n",
"输入 :help 查看帮助\n"
},
[I18N_EXEC_HELP] = {
"TNT exec interface\n"
"Commands:\n"
" help Show this help\n"
" health Print service health\n"
" users [--json] List online users\n"
" stats [--json] Print room statistics\n"
" tail [N] Print recent messages\n"
" tail -n N Print recent messages\n"
" post MESSAGE Post a message non-interactively\n"
" post \"/me act\" Post an action message\n"
" exit Exit successfully\n",
"TNT exec 接口\n"
"命令:\n"
" help 显示此帮助\n"
" health 输出服务健康状态\n"
" users [--json] 列出在线用户\n"
" stats [--json] 输出房间统计\n"
" tail [N] 输出最近消息\n"
" tail -n N 输出最近消息\n"
" post MESSAGE 非交互发送消息\n"
" post \"/me act\" 发送动作消息\n"
" exit 成功退出\n"
},
[I18N_EXEC_USERS_USAGE] = {
"users: usage: users [--json]\n",
"users: 用法: users [--json]\n"
},
[I18N_EXEC_STATS_USAGE] = {
"stats: usage: stats [--json]\n",
"stats: 用法: stats [--json]\n"
},
[I18N_EXEC_TAIL_USAGE] = {
"tail: usage: tail [N] | tail -n N\n",
"tail: 用法: tail [N] | tail -n N\n"
},
[I18N_EXEC_POST_USAGE] = {
"post: usage: post MESSAGE\n",
"post: 用法: post MESSAGE\n"
},
[I18N_EXEC_POST_EMPTY] = {
"post: message cannot be empty\n",
"post: 消息不能为空\n"
},
[I18N_EXEC_POST_INVALID_UTF8] = {
"post: invalid UTF-8 input\n",
"post: 输入不是有效 UTF-8\n"
},
[I18N_EXEC_UNKNOWN_COMMAND_FORMAT] = {
"Unknown command: %s\n",
"未知命令: %s\n"
}
};
const char *i18n_text(ui_lang_t lang, i18n_text_id_t id) {
if (id < 0 || id >= I18N_TEXT_COUNT) {
return "";
}
const i18n_text_entry_t *entry = &text_catalog[id];
if (lang == UI_LANG_ZH && entry->zh) {
return entry->zh;
}
if (entry->en) {
return entry->en;
}
return "";
}

View file

@ -18,6 +18,7 @@ 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
I18N_TEXT_SRC = ../../src/i18n_text.c
SYSTEM_MESSAGE_SRC = ../../src/system_message.c
HELP_TEXT_SRC = ../../src/help_text.c
MANUAL_TEXT_SRC = ../../src/manual_text.c
@ -41,10 +42,10 @@ test_chat_room: test_chat_room.c $(CHAT_ROOM_SRC) $(MESSAGE_SRC) $(UTF8_SRC) $(C
test_history_view: test_history_view.c $(HISTORY_VIEW_SRC)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
test_i18n: test_i18n.c $(I18N_SRC)
test_i18n: test_i18n.c $(I18N_SRC) $(I18N_TEXT_SRC)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
test_system_message: test_system_message.c $(SYSTEM_MESSAGE_SRC) $(I18N_SRC)
test_system_message: test_system_message.c $(SYSTEM_MESSAGE_SRC) $(I18N_SRC) $(I18N_TEXT_SRC)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
test_command_catalog: test_command_catalog.c $(COMMAND_CATALOG_SRC) $(COMMON_SRC)

View file

@ -146,6 +146,18 @@ TEST(text_lookup_matches_language) {
assert(strcmp(i18n_ui_lang_code(UI_LANG_ZH), "zh") == 0);
}
TEST(text_catalog_is_complete) {
for (int id = 0; id < I18N_TEXT_COUNT; id++) {
assert(i18n_text(UI_LANG_EN, (i18n_text_id_t)id)[0] != '\0');
assert(i18n_text(UI_LANG_ZH, (i18n_text_id_t)id)[0] != '\0');
}
assert(strcmp(i18n_text(UI_LANG_EN,
(i18n_text_id_t)I18N_TEXT_COUNT), "") == 0);
assert(strcmp(i18n_text(UI_LANG_ZH,
(i18n_text_id_t)I18N_TEXT_COUNT), "") == 0);
}
int main(void) {
printf("Running i18n unit tests...\n\n");
@ -155,6 +167,7 @@ int main(void) {
RUN_TEST(default_prefers_tnt_lang);
RUN_TEST(default_uses_locale_when_no_tnt_lang);
RUN_TEST(text_lookup_matches_language);
RUN_TEST(text_catalog_is_complete);
printf("\n✓ All %d tests passed!\n", tests_passed);
return 0;