feat(test): 添加单元测试框架并完善国际化支持

- 新增 CMake 测试框架配置,支持 safe_string、string_ext 和 path_manager 模块的单元测试
- 实现 Windows API Mock 机制,便于测试编码转换函数
- 添加 error_code 模块的字符串表示函数,支持英文错误日志
- 在 UI 回调函数中集成国际化翻译,覆盖新建、编辑、导入导出等操作提示
- 扩展 string_list 功能,新增重复路径检查函数
- 更新翻译文件,同步所有用户界面的中英文文本
This commit is contained in:
2026-04-30 11:38:05 +08:00
parent ceed90aea8
commit cf19a37a97
20 changed files with 1778 additions and 55 deletions
+20 -3
View File
@@ -18,11 +18,19 @@ int btn_new_cb(Ihandle *self)
{
Ihandle *dlg = IupGetDialog(self);
char buffer[PATH_BUFFER_SIZE] = "";
if (custom_input_dialog("新建环境变量", "请输入路径:", buffer, sizeof(buffer)))
if (custom_input_dialog(_("New Environment Variable"), _("Please enter a path:"), buffer, sizeof(buffer)))
{
if (strlen(buffer) > 0)
{
StringList *raw_data = get_current_raw_data(dlg);
// 检查是否已存在重复路径
if (string_list_contains(raw_data, buffer))
{
IupMessage(_("Warning"), _("This path already exists and will not be added again."));
return IUP_DEFAULT;
}
add_string_list(raw_data, buffer);
Ihandle *current_list = get_current_list(dlg);
@@ -51,7 +59,7 @@ int btn_edit_cb(Ihandle *self)
char buffer[PATH_BUFFER_SIZE];
safe_strcpy(buffer, sizeof(buffer), string_list_get(raw_data, selected - 1));
if (custom_input_dialog("编辑环境变量", "编辑路径:", buffer, sizeof(buffer)))
if (custom_input_dialog(_("Edit Environment Variable"), _("Edit path:"), buffer, sizeof(buffer)))
{
if (strlen(buffer) > 0)
{
@@ -91,6 +99,15 @@ int btn_browse_cb(Ihandle *self)
if (value)
{
StringList *raw_data = get_current_raw_data(dlg);
// 检查是否已存在重复路径
if (string_list_contains(raw_data, value))
{
IupMessage(_("Warning"), _("This path already exists and will not be added again."));
IupDestroy(filedlg);
return IUP_DEFAULT;
}
add_string_list(raw_data, value);
Ihandle *current_list = get_current_list(dlg);
@@ -113,7 +130,7 @@ int btn_del_cb(Ihandle *self)
if (selected == 0)
{
IupMessage("提示", "请先选择要删除的项");
IupMessage(_("Info"), _("Please select an item to delete first"));
return IUP_DEFAULT;
}
+13 -13
View File
@@ -24,7 +24,7 @@ int btn_import_cb(Ihandle *self)
if (!check_admin())
{
IupMessage("错误", "需要管理员权限才能导入 PATH");
IupMessage(_("Error"), _("Administrator privileges are required to import PATH!"));
return IUP_DEFAULT;
}
@@ -52,7 +52,7 @@ int btn_import_cb(Ihandle *self)
if (!has_system && !has_user)
{
IupMessage("错误", "文件中没有找到有效的路径!");
IupMessage(_("Error"), _("No valid paths found in file!"));
clear_string_list(&imported.system);
clear_string_list(&imported.user);
IupDestroy(filedlg);
@@ -62,14 +62,14 @@ int btn_import_cb(Ihandle *self)
int choice = 0;
if (has_system && has_user)
{
choice = IupAlarm("导入选项", "请选择导入目标:",
"仅系统变量", "仅用户变量", "全部导入");
choice = IupAlarm(_("Import Options"), _("Please select import target:"),
_("System Variables Only"), _("User Variables Only"), _("Import All"));
}
else if (has_system)
{
// TXT 文件导入时,让用户选择目标(系统变量或用户变量)
choice = IupAlarm("导入选项", "请选择导入目标:",
"导入到系统变量", "导入到用户变量", NULL);
// TXT file import: let user choose target (system or user)
choice = IupAlarm(_("Import Options"), _("Please select import target:"),
_("Import to System"), _("Import to User"), NULL);
// IupAlarm 返回 1 或 2,转换为 1(系统) 或 2(用户)
}
else
@@ -108,8 +108,8 @@ int btn_import_cb(Ihandle *self)
clear_string_list(&imported.user);
char msg[256];
snprintf(msg, sizeof(msg), "成功导入 %d 个路径!", total_imported);
IupMessage("导入成功", msg);
snprintf(msg, sizeof(msg), _("Successfully imported %d paths!"), total_imported);
IupMessage(_("Import Success"), msg);
Ihandle *lbl_status = IupGetDialogChild(dlg, CTRL_LBL_STATUS);
if (lbl_status)
@@ -118,7 +118,7 @@ int btn_import_cb(Ihandle *self)
else
{
log_error("Import failed: error code %d", import_result);
IupMessage("错误", "导入失败,请检查文件格式是否正确!");
IupMessage(_("Error"), _("Import failed, please check if the file format is correct!"));
}
}
}
@@ -171,14 +171,14 @@ int btn_export_cb(Ihandle *self)
if (export_result == ERR_OK)
{
char msg[512];
snprintf(msg, sizeof(msg), "成功导出!\n系统变量: %d\n用户变量: %d\n\n保存位置: %s",
snprintf(msg, sizeof(msg), _("Export successful!\nSystem variables: %d\nUser variables: %d\n\nSave location: %s"),
data.system.count, data.user.count, filepath);
IupMessage("导出成功", msg);
IupMessage(_("Export Success"), msg);
}
else
{
log_error("Export failed: error code %d", export_result);
IupMessage("错误", "导出失败!");
IupMessage(_("Error"), _("Export failed!"));
}
}
}
+3 -3
View File
@@ -61,7 +61,7 @@ int btn_clean_cb(Ihandle *self)
if (!raw_data || raw_data->count == 0)
return IUP_DEFAULT;
if (IupAlarm("确认清理", "此操作将移除当前列表中所有【无效路径】和【重复路径】。\n确定要继续吗?", "确定", "取消", NULL) != 1)
if (IupAlarm(_("Confirm Cleanup"), _("This operation will remove all 【invalid paths】 and 【duplicate paths】 from the current list.\nAre you sure you want to continue?"), _("Confirm"), _("Cancel"), NULL) != 1)
{
return IUP_DEFAULT;
}
@@ -74,8 +74,8 @@ int btn_clean_cb(Ihandle *self)
sync_string_list_to_ui(current_list, raw_data);
char msg[128];
snprintf(msg, sizeof(msg), "清理完成!共移除了 %d 个无效或重复路径。", removed);
IupMessage("提示", msg);
snprintf(msg, sizeof(msg), _("Cleanup completed! Removed %d invalid or duplicate paths."), removed);
IupMessage(_("Info"), msg);
return IUP_DEFAULT;
}
+32 -32
View File
@@ -24,7 +24,7 @@ int btn_ok_cb(Ihandle *self)
if (!check_admin())
{
IupMessage("错误", "需要管理员权限才能保存更改!");
IupMessage(_("Error"), _("Administrator privileges are required to save changes!"));
return IUP_DEFAULT;
}
@@ -32,11 +32,11 @@ int btn_ok_cb(Ihandle *self)
char custom_backup_dir[MAX_PATH] = "";
int do_backup = 1; // 是否执行备份
int backup_choice = IupAlarm("备份设置",
"是否自定义备份目录?\n\n"
"选择「使用默认」将备份到 %APPDATA%/PathEditor/backups/\n"
"选择「自定义目录」可选择其他位置",
"使用默认", "自定义目录", "跳过备份");
int backup_choice = IupAlarm(_("Backup Settings"),
_("Would you like to customize the backup directory?\n\n"
"Select 'Use Default' to backup to %%APPDATA%%/PathEditor/backups/\n"
"Select 'Custom Directory' to choose another location"),
_("Use Default"), _("Custom Directory"), _("Skip Backup"));
if (backup_choice == 2) // 自定义目录
{
@@ -56,13 +56,13 @@ int btn_ok_cb(Ihandle *self)
if (strlen(custom_backup_dir) == 0)
{
IupMessage("提示", "未选择目录,将使用默认备份路径。");
IupMessage(_("Hint"), _("No directory selected, will use default backup path."));
}
}
else if (backup_choice == 3) // 跳过备份
{
int skip_confirm = IupAlarm("确认", "确定跳过备份吗?\n跳过备份可能导致无法恢复!",
"确定跳过", "返回备份", NULL);
int skip_confirm = IupAlarm(_("Confirm"), _("Are you sure you want to skip backup?\nSkipping backup may cause inability to recover!"),
_("Skip Anyway"), _("Go Back"), NULL);
if (skip_confirm != 1)
{
// 用户反悔,重新询问
@@ -89,7 +89,7 @@ int btn_ok_cb(Ihandle *self)
char msg[512];
snprintf(msg, sizeof(msg), "备份失败!原因:%s\n\n是否继续保存?\n(继续保存可能导致无法恢复)", reason);
int choice = IupAlarm("警告", msg, "继续保存", "取消", NULL);
int choice = IupAlarm(_("Warning"), msg, _("Continue Saving"), _("Cancel"), NULL);
if (choice != 1)
return IUP_DEFAULT;
}
@@ -104,22 +104,22 @@ int btn_ok_cb(Ihandle *self)
{
log_info("Saved system paths: %d, user paths: %d", ctx->sys_paths.count, ctx->user_paths.count);
SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)L"Environment", SMTO_ABORTIFHUNG, 5000, NULL);
IupMessage("成功", "系统和用户 PATH 环境变量均已更新!");
IupMessage(_("Success"), _("Both system and user PATH environment variables have been updated!"));
if (lbl_status)
IupSetAttribute(lbl_status, "TITLE", lua_config_get_string("status", "saved"));
}
else if (sys_ok == ERR_OK)
{
IupMessage("提示", "系统变量保存成功,但用户变量保存失败。");
IupMessage(_("Info"), _("System variables saved successfully, but user variables failed to save."));
}
else if (user_ok == ERR_OK)
{
IupMessage("提示", "用户变量保存成功,但系统变量保存失败。");
IupMessage(_("Info"), _("User variables saved successfully, but system variables failed to save."));
}
else
{
log_error("Failed to save paths: sys=%d, user=%d", sys_ok, user_ok);
IupMessage("错误", "保存失败!");
IupMessage(_("Error"), _("Failed to save!"));
if (lbl_status)
IupSetAttribute(lbl_status, "TITLE", lua_config_get_string("status", "error"));
}
@@ -146,7 +146,7 @@ void load_all_paths(void)
if (load_system_paths(&ctx->sys_paths) != ERR_OK)
{
log_error("Failed to load system paths");
IupMessage("错误", "无法打开系统环境变量注册表键,请尝试以管理员身份运行。");
IupMessage(_("Error"), _("Unable to open system environment variable registry key, please try running as administrator."));
}
else
{
@@ -182,23 +182,23 @@ int btn_lang_cb(Ihandle *self)
// 按钮回调:帮助
int btn_help_cb(Ihandle *self)
{
IupMessage("使用说明",
"1. 本程序用于编辑系统环境变量 PATH\n"
"2. 必须以【管理员身份】运行才能保存更改。\n"
"3. 操作说明:\n"
" - 新建:添加新路径到列表末尾。\n"
" - 编辑:修改选中的路径。\n"
" - 浏览:从文件系统选择目录添加。\n"
" - 删除:移除选中的路径。\n"
" - 上移/下移:调整路径优先级。\n"
" - 导入/导出:备份和恢复 PATH 配置。\n"
"4. 点击【确定】保存更改并生效。\n"
"5. 注意:某些正在运行的程序可能需要重启才能识别新的环境变量。\n\n"
"--------------------------------------------------\n"
"作者:LHY\n"
"邮箱:3364451258@qq.com\n"
"GitHubhttps://github.com/LHY0125/PathEditor\n"
"记得给我的项目点个star");
IupMessage(_("Usage Instructions"),
_("1. This program is used to edit system environment variable PATH.\n"
"2. Must run as 【Administrator】 to save changes.\n"
"3. Operations:\n"
" - New: Add new path to end of list.\n"
" - Edit: Modify selected path.\n"
" - Browse: Select directory from file system to add.\n"
" - Delete: Remove selected path.\n"
" - Up/Down: Adjust path priority.\n"
" - Import/Export: Backup and restore PATH configuration.\n"
"4. Click 【OK】 to save changes and apply.\n"
"5. Note: Some running programs may need to restart to recognize new environment variables.\n\n"
"--------------------------------------------------\n"
"Author: LHY\n"
"Email: 3364451258@qq.com\n"
"GitHub: https://github.com/LHY0125/PathEditor\n"
"Don't forget to star my project!"));
return IUP_DEFAULT;
}
+40
View File
@@ -0,0 +1,40 @@
#include "utils/error_code.h"
#include <string.h>
const char* error_code_to_string(ErrorCode code)
{
switch (code)
{
case ERR_OK:
return "Success";
case ERR_FAILED:
return "Operation failed";
case ERR_NULL_PTR:
return "Null pointer error";
case ERR_OUT_OF_MEMORY:
return "Out of memory";
case ERR_FILE_NOT_FOUND:
return "File not found";
case ERR_PERMISSION_DENIED:
return "Permission denied";
case ERR_INVALID_FORMAT:
return "Invalid format";
case ERR_REGISTRY_FAILED:
return "Registry operation failed";
case ERR_NOT_FOUND:
return "Item not found";
case ERR_EXISTS:
return "Item already exists";
case ERR_INVALID_INDEX:
return "Invalid index";
default:
return "Unknown error";
}
}
// 注意:error_code_to_message 需要链接 intl 库,
// 如果不需要在错误码模块使用国际化,可以直接使用 error_code_to_string
const char* error_code_to_message(ErrorCode code)
{
return error_code_to_string(code);
}
+13
View File
@@ -126,4 +126,17 @@ void clear_string_list(StringList *list)
list->items = NULL;
list->count = 0;
list->capacity = 0;
}
// 检查字符串列表中是否存在指定路径(不区分大小写)
int string_list_contains(const StringList *list, const char *str)
{
if (!list || !str)
return 0;
for (int i = 0; i < list->count; i++)
{
if (list->items[i] && _stricmp(list->items[i], str) == 0)
return 1;
}
return 0;
}