# 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. **使用共享文件减少重复配置**