# Lang 模块 ## 概述 Lang(Language)模块提供统一的多语言错误消息翻译功能,支持无限语言、外部文件加载、异步加载,以及**变量替换**(`{{nl.lang.NAME}}` 格式)和**条件表达式求值**。 --- ## v2.4.0 新增:变量替换与条件控制 ### 变量类型 ```c typedef enum { NL_VAR_TYPE_STRING, // 字符串 NL_VAR_TYPE_INTEGER, // 整数 NL_VAR_TYPE_FLOAT, // 浮点数 NL_VAR_TYPE_BOOL, // 布尔值 } nl_var_type_t; ``` ### 变量管理 API ```c // 设置字符串变量 int nl_lang_var_set(const char* name, const char* value); // 设置整数/浮点/布尔变量 int nl_lang_var_set_int(const char* name, long long value); int nl_lang_var_set_float(const char* name, double value); int nl_lang_var_set_bool(const char* name, int value); // 获取变量 const char* nl_lang_var_get(const char* name); long long nl_lang_var_get_int(const char* name, long long default_val); double nl_lang_var_get_float(const char* name, double default_val); int nl_lang_var_get_bool(const char* name, int default_val); // 查询与清理 int nl_lang_var_exists(const char* name); int nl_lang_var_remove(const char* name); void nl_lang_var_clear_all(void); ``` ### 动态变量与外部变量(v2.4.1) 除静态设置外,变量还支持**动态求值**与**外部来源**: ```c // 动态变量:每次访问都调用 provider 计算值 int nl_lang_var_set_provider(const char* name, nl_var_provider_t provider, void* userdata); int nl_lang_var_is_dynamic(const char* name); // 外部变量:绑定到进程环境变量(每次访问实时读取) int nl_lang_var_bind_env(const char* name, const char* env_name); // 外部变量:批量导入 int nl_lang_var_load_env(const char* prefix); // 导入环境变量(可前缀过滤) int nl_lang_var_load_file(const char* filepath, const char* prefix); // 导入 .env 风格文件 ``` - provider 在**锁外**调用,避免回调中再次访问变量 API 造成死锁;返回 0 时回退到静态值。 - `nl_lang_var_set*` 设置的静态值可作为动态/外部变量的**兜底值**。 - 动态/外部变量同样适用于 `{{name}}`、`{{name}}` 替换与条件表达式。 ### 模板替换 `{{VAR_NAME}}` ```c // 将 input 中的 {{VAR_NAME}} 替换为实际值,结果写入 output const char* nl_lang_var_replace(const char* input, char* output, size_t output_size); ``` 支持多类型变量:`{{name}}`、`{{count}}`、`{{price}}`、`{{active}}`,支持变量名前后空格(自动 trim)。 ### HTML `` 标签格式替换 `{{nl.lang.NAME}}` ```c // 将 input 中的 {{nl.lang.VAR_NAME}} 替换为 lang 变量值 // 也支持裸格式 {{VAR_NAME}}(无 nl.lang. 前缀) const char* nl_lang_var_replace_html(const char* input, char* output, size_t output_size); // 获取错误消息并替换其中的变量(便捷函数) const char* nl_lang_get_error_with_vars(int lib_id, int error_code, char* buffer, size_t buffer_size); ``` 格式说明: - **Lang 扩展格式**:`{{nl.lang.变量名}}` — 从 Lang 变量存储中取值 - **通用硬编码格式**:`{{变量名}}` — 同样从 Lang 变量存储中取值(无前缀) - 变量类型自动识别:字符串直接输出,整数/浮点数/布尔值格式化为字符串 示例: ```c nl_lang_var_set("user", "Alice"); nl_lang_var_set_int("code", 404); char buf[256]; // Lang 扩展格式 nl_lang_var_replace_html("用户 {{nl.lang.user}} 报错 ({{nl.lang.code}})", buf, sizeof(buf)); // 结果: "用户 Alice 报错 (404)" // 裸格式(效果相同) nl_lang_var_replace_html("用户 {{user}} 报错", buf, sizeof(buf)); // 结果: "用户 Alice 报错" // 通过 get_error_with_vars 便捷使用 nl_lang_set_error(lib_id, 404, "en_us", "{{nl.lang.user}} not found"); nl_lang_get_error_with_vars(lib_id, 404, buf, sizeof(buf)); // 结果: "Alice not found" ``` ### 条件表达式求值 ```c // 评估条件表达式,返回 1(真)或 0(假) int nl_lang_var_condition_eval(const char* expr); ``` 支持的表达式语法: - 数值比较:`age > 18`、`price >= 3.14`、`count == 0` - 字符串比较:`user == 'Alice'`、`status != 'disabled'` - 运算符:`==` `!=` `>=` `<=` `>` `<` - 支持字面量(数字、单引号字符串、`true`/`false`/`null`)和变量引用 ### 脚本引擎注册(v2.4.0 新增) 通过回调注册脚本引擎,未来可接入 Lua / Python / JavaScript: ```c // 脚本执行结果结构 typedef struct { int status; // 0=成功, !=0=失败 int exit_code; int result_type; // 0=无结果, 1=字符串, 2=整数, 3=浮点数, 4=布尔 const char* result_str; long long result_int; double result_float; int result_bool; } nl_script_result_t; // 脚本执行回调类型 typedef nl_script_result_t* (*nl_script_exec_func_t)( const char* script, const char* lang_code, int error_code, void* userdata); // 注册/注销脚本引擎 int nl_lang_register_script_engine(const char* engine_id, nl_script_exec_func_t exec_func, void* userdata); void nl_lang_unregister_script_engine(const char* engine_id); // 执行脚本 int nl_lang_execute_script(const char* engine_id, const char* script, const char* lang_code, int error_code, char* result, size_t result_size); // 获取已注册引擎列表 const char** nl_lang_get_script_engines(int* count); ``` ### 使用示例 ```c #include "netleaf_lang.h" #include int main() { nl_lang_init(); // 设置变量 nl_lang_var_set("user", "Alice"); nl_lang_var_set_int("age", 25); nl_lang_var_set_float("price", 3.14); nl_lang_var_set_bool("active", 1); // 模板替换 char buf[256]; nl_lang_var_replace("Hello {{ user }}, age={{ age }}", buf, sizeof(buf)); printf("%s\n", buf); // Hello Alice, age=25 // 条件求值 printf("age>18: %d\n", nl_lang_var_condition_eval("age > 18")); // 1 printf("user=='Alice': %d\n", nl_lang_var_condition_eval("user == 'Alice'")); // 1 printf("price>=3.0: %d\n", nl_lang_var_condition_eval("price >= 3.0")); // 1 // 脚本引擎(示例 mock) // nl_script_result_t* my_exec(...) { ... return &result; } // nl_lang_register_script_engine("myengine", my_exec, NULL); nl_lang_shutdown(); return 0; } ``` --- ## 特性 - ✅ **无限语言支持**: 支持任意语言(en_us, zh_cn, ja_jp, ko_kr 等) - ✅ **语言代码格式**: 强制 `xx_xx` 格式(忽略大小写,自动规范化) - ✅ **分开注册**: 支持按语言分开注册错误消息 - ✅ **自定义语言代码**: 支持注册自定义语言代码 - ✅ **多文件支持**: 一个库可加载多个语言文件 - ✅ **多库共享**: 多个库可共用一个翻译文件(需显示声明) - ✅ **错误码检测**: 防止重复注册错误码 - ✅ **异步加载**: 支持异步加载语言文件 - ✅ **跨平台**: Windows / Linux / macOS --- ## 平台支持 | 平台 | 状态 | |------|------| | Windows | ✅ 完全支持 | | Linux | ✅ 完全支持 | | macOS | ✅ 完全支持 | --- ## 语言代码格式 格式强制为 `xx_xx`(语言_国家),但**忽略大小写**: | 输入 | 规范化后 | 状态 | |------|----------|------| | `en_us` | `en_us` | ✅ 有效 | | `EN_US` | `en_us` | ✅ 有效 | | `Zh_Cn` | `zh_cn` | ✅ 有效 | | `ja_JP` | `ja_jp` | ✅ 有效 | | `en` | - | ❌ 无效 | | `en-US` | - | ❌ 无效 | --- ## API 参考 ### 语言设置 ```c // 设置当前语言(必须 xx_xx 格式) int nl_lang_set(const char* code); // "en_us", "zh_cn", "ja_jp" // 获取当前语言 const char* nl_lang_get(void); // 设置默认语言 int nl_lang_set_default(const char* code); // 获取默认语言 const char* nl_lang_get_default(void); // 验证语言代码格式 int nl_lang_validate_code(const char* code); // 获取所有注册的语言 const char** nl_lang_get_all(void); ``` ### 库注册 ```c // 注册库及其支持的语言 int nl_lang_register_lib(int lib_id, const char** languages, int lang_count); // 注册单个语言到库(可多次调用) int nl_lang_register_language(int lib_id, const char* lang_code); // 注销库 void nl_lang_unregister_lib(int lib_id); // 注销单个语言 int nl_lang_unregister_language(int lib_id, const char* lang_code); // 检查库是否已注册 int nl_lang_is_registered(int lib_id); // 检查语言是否已注册 int nl_lang_has_language(int lib_id, const char* lang_code); ``` ### 错误消息注册 ```c // 添加错误消息(所有语言) int nl_lang_add_error(int lib_id, int code, const char** messages); // 设置单个语言的错误消息(自动创建条目) int nl_lang_set_error(int lib_id, int code, const char* lang_code, const char* message); // 移除错误消息 int nl_lang_remove_error(int lib_id, int code); // 移除单个语言的错误消息 int nl_lang_remove_error_single(int lib_id, int code, const char* lang_code); // 注册自定义错误码 int nl_lang_register_custom_code(int lib_id, int code); // 检查错误码是否存在 int nl_lang_has_error(int lib_id, int code); // 获取所有错误码 int* nl_lang_get_error_codes(int lib_id, int* count); ``` ### 表驱动错误注册(v2.4.1) 为便于维护,可用 `NL_ERROR_BEGIN / NL_ERROR / NL_ERROR_END` 声明错误表并**一次注册**(中英双语): ```c NL_ERROR_BEGIN(my_errors) NL_ERROR(0, "Success", "成功") NL_ERROR(-1, "Failed", "失败") NL_ERROR_END nl_lang_register_errors(NL_LIB_MY, my_errors); // 注册 en_us + zh_cn ``` > 各内置模块(autoroute / autocomplete / errorpage / ipc / linkagg / tls / mqtt / mqtt_server) > 已通过该机制注册各自的返回码/错误码。 ### 错误消息查找 ```c // 获取当前语言的错误消息 const char* nl_lang_get_error(int lib_id, int error_code); // 获取指定语言的错误消息 const char* nl_lang_get_error_for(int lib_id, int error_code, const char* lang_code); // 获取错误消息并替换其中的变量(支持 {{nl.lang.NAME}} 格式) const char* nl_lang_get_error_with_vars(int lib_id, int error_code, char* buffer, size_t buffer_size); // 格式化错误消息 const char* nl_lang_format_error(char* buffer, size_t buffer_size, int lib_id, int error_code, const char* context); ``` ### 多文件支持 ```c // 加载多个语言文件 int nl_lang_load_json_multi(int lib_id, const char** files, int file_count); ``` ### 多库共享文件 ```c // 共享文件配置结构 typedef struct { const char* filepath; // JSON 文件路径 int* lib_ids; // 共享此文件的库 ID 数组 int lib_count; // 库数量 int allow_duplicate_codes; // 是否允许跨库重复错误码 } nl_lang_shared_file_t; // 注册共享文件 int nl_lang_register_shared_file(const nl_lang_shared_file_t* config); // 加载共享文件 int nl_lang_load_shared_file(const char* filepath); // 检查库是否在共享组中 int nl_lang_is_shared_lib(int lib_id); // 获取共享库列表 int* nl_lang_get_shared_libraries(int lib_id, int* count); ``` ### 错误码重复检测 ```c // 检查错误码冲突 int nl_lang_check_code_conflict(int lib_id, int code); // 返回: 0 = 无冲突, lib_id = 冲突的库 ID // 启用/禁用严格重复检查 void nl_lang_set_strict_duplicates(int enabled); // 获取严格检查设置 int nl_lang_get_strict_duplicates(void); ``` ### 异步加载 ```c // 异步回调类型 typedef void (*nl_lang_async_callback_t)(int lib_id, int result, void* user_data); // 异步加载 JSON 文件 int nl_lang_load_json_async(int lib_id, const char* filepath, nl_lang_async_callback_t callback, void* user_data); // 异步加载多个文件 int nl_lang_load_json_multi_async(int lib_id, const char** files, int file_count, nl_lang_async_callback_t callback, void* user_data); // 异步加载 URL int nl_lang_load_url_async(int lib_id, const char* url, nl_lang_async_callback_t callback, void* user_data); // 检查异步加载状态 int nl_lang_is_async_loading(int lib_id); // 等待异步完成 int nl_lang_wait_async(int lib_id, int timeout_ms); // 取消异步加载 int nl_lang_cancel_async(int lib_id); // 获取异步进度 int nl_lang_get_async_progress(int lib_id); ``` --- ## 库标识符 | 库 | 标识符 | 值 | |----|--------|-----| | Core | `NL_LIB_CORE` | 0x0000 | | IPC | `NL_LIB_IPC` | 0x0001 | | LinkAgg | `NL_LIB_LINKAGG` | 0x0002 | | Auto-route | `NL_LIB_AUTOROUTE` | 0x0003 | | Auto-complete | `NL_LIB_AUTOCOMPLETE` | 0x0004 | | ErrorPage | `NL_LIB_ERRORPAGE` | 0x0005 | | Lang | `NL_LIB_LANG` | 0x0006 | --- ## 使用示例 ### 示例 1:基本用法 ```c #include "netleaf_lang.h" #include int main() { // 设置语言 nl_lang_set("zh_cn"); // 或 "en_us", "ja_jp" 等 // 获取错误消息 const char* msg = nl_lang_get_error(NL_LIB_LINKAGG, -9); printf("错误: %s\n", msg); // 输出: "不接入,ID被占用" // 切换语言 nl_lang_set("en_us"); msg = nl_lang_get_error(NL_LIB_LINKAGG, -9); printf("Error: %s\n", msg); // 输出: "ID already in use, not connected" return 0; } ``` ### 示例 2:分开注册语言 ```c #include "netleaf_lang.h" int main() { // 注册库 nl_lang_register_lib_name(NL_LIB_LINKAGG, "linkagg"); // 分开注册语言 nl_lang_register_language(NL_LIB_LINKAGG, "zh_cn"); nl_lang_register_language(NL_LIB_LINKAGG, "en_us"); nl_lang_register_language(NL_LIB_LINKAGG, "ja_jp"); // 后续添加 // 分开注册错误消息(按语言) nl_lang_set_error(NL_LIB_LINKAGG, 0, "zh_cn", "成功"); nl_lang_set_error(NL_LIB_LINKAGG, 0, "en_us", "Success"); nl_lang_set_error(NL_LIB_LINKAGG, 0, "ja_jp", "成功"); nl_lang_set_error(NL_LIB_LINKAGG, -9, "zh_cn", "不接入,ID被占用"); nl_lang_set_error(NL_LIB_LINKAGG, -9, "en_us", "ID already in use"); nl_lang_set_error(NL_LIB_LINKAGG, -9, "ja_jp", "IDが使用中"); return 0; } ``` ### 示例 3:自定义错误码 ```c #include "netleaf_lang.h" int main() { // 注册自定义错误码(个性化体验) nl_lang_register_custom_code(NL_LIB_LINKAGG, -100); nl_lang_set_error(NL_LIB_LINKAGG, -100, "zh_cn", "业务逻辑错误"); nl_lang_set_error(NL_LIB_LINKAGG, -100, "en_us", "Business logic error"); // 使用自定义错误码 const char* msg = nl_lang_get_error(NL_LIB_LINKAGG, -100); printf("%s\n", msg); return 0; } ``` ### 示例 4:多库共享文件 ```c #include "netleaf_lang.h" int main() { // 配置共享文件 int lib_ids[] = {NL_LIB_IPC, NL_LIB_LINKAGG, NL_LIB_AUTOROUTE}; nl_lang_shared_file_t config = { .filepath = "errors_shared.json", .lib_ids = lib_ids, .lib_count = 3, .allow_duplicate_codes = 0 // 不允许重复错误码 }; // 注册共享文件 nl_lang_register_shared_file(&config); // 加载共享文件 nl_lang_load_shared_file("errors_shared.json"); return 0; } ``` ### 示例 5:异步加载 ```c #include "netleaf_lang.h" #include void on_load_complete(int lib_id, int result, void* user_data) { if (result == 0) { printf("Language file loaded successfully for lib %d\n", lib_id); } else { printf("Failed to load language file: %d\n", result); } } int main() { // 异步加载语言文件 nl_lang_load_json_async(NL_LIB_LINKAGG, "errors_linkagg.json", on_load_complete, NULL); // 检查加载状态 while (nl_lang_is_async_loading(NL_LIB_LINKAGG)) { printf("Progress: %d%%\n", nl_lang_get_async_progress(NL_LIB_LINKAGG)); // 做其他工作... } // 或等待完成 nl_lang_wait_async(NL_LIB_LINKAGG, 5000); // 最多等待 5 秒 return 0; } ``` ### 示例 6:错误码冲突检测 ```c #include "netleaf_lang.h" #include int main() { // 启用严格重复检查 nl_lang_set_strict_duplicates(1); // 检查错误码是否会冲突 int conflict = nl_lang_check_code_conflict(NL_LIB_LINKAGG, -9); if (conflict != 0) { printf("Error code -9 conflicts with lib %d\n", conflict); } return 0; } ``` --- ## JSON 文件格式 ```json { "lib_id": 2, "languages": ["en_us", "zh_cn", "ja_jp"], "errors": { "0": { "en_us": "Success", "zh_cn": "成功", "ja_jp": "成功" }, "-9": { "en_us": "ID already in use, not connected", "zh_cn": "不接入,ID被占用", "ja_jp": "IDが使用中" } } } ``` --- ## 错误码参考 ### LinkAgg 错误码 | 错误码 | en_us | zh_cn | |--------|-------|-------| | 0 | Success | 成功 | | -1 | Unknown error | 未知错误 | | -2 | Platform not supported | 平台不支持 | | -8 | Exceeded maximum backends (512) | 超过最大后端数量(512个) | | -9 | ID already in use, not connected | 不接入,ID被占用 | | -10 | Invalid ID format (expected xxx.xxx) | ID格式错误(应为xxx.xxx) | ### IPC 错误码 | 错误码 | en_us | zh_cn | |--------|-------|-------| | 0 | Success | 成功 | | -2 | Platform not supported (macOS) | 平台不支持(macOS) | | -5 | Connection failed | 连接失败 | | -10 | Path too long | 路径过长 | --- ## 最佳实践 1. **程序初始化时设置语言** 2. **分开注册语言以支持渐进式添加** 3. **使用异步加载避免阻塞主线程** 4. **启用严格重复检查防止冲突** 5. **使用共享文件减少重复配置**