Lang - 508364/NetLeaf GitHub Wiki

Lang 模块

概述

Lang(Language)模块提供统一的多语言错误消息翻译功能,支持无限语言、外部文件加载、异步加载,以及变量替换({{<var>nl.lang.NAME</var>}} 格式)和条件表达式求值。


v2.4.0 新增:变量替换与条件控制

变量类型

typedef enum {
    NL_VAR_TYPE_STRING,   // 字符串
    NL_VAR_TYPE_INTEGER,  // 整数
    NL_VAR_TYPE_FLOAT,    // 浮点数
    NL_VAR_TYPE_BOOL,     // 布尔值
} nl_var_type_t;

变量管理 API

// 设置字符串变量
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)

除静态设置外,变量还支持动态求值与外部来源:

// 动态变量:每次访问都调用 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}}、{{<var>name</var>}} 替换与条件表达式。

模板替换 {{VAR_NAME}}

// 将 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 <var> 标签格式替换 {{<var>nl.lang.NAME</var>}}

// 将 input 中的 {{<var>nl.lang.VAR_NAME</var>}} 替换为 lang 变量值
// 也支持裸格式 {{<var>VAR_NAME</var>}}(无 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 扩展格式:{{<var>nl.lang.变量名</var>}} — 从 Lang 变量存储中取值
  • 通用硬编码格式:{{<var>变量名</var>}} — 同样从 Lang 变量存储中取值(无前缀)
  • 变量类型自动识别:字符串直接输出,整数/浮点数/布尔值格式化为字符串

示例:

nl_lang_var_set("user", "Alice");
nl_lang_var_set_int("code", 404);

char buf[256];
// Lang 扩展格式
nl_lang_var_replace_html("用户 {{<var>nl.lang.user</var>}} 报错 ({{<var>nl.lang.code</var>}})", buf, sizeof(buf));
// 结果: "用户 Alice 报错 (404)"

// 裸格式(效果相同)
nl_lang_var_replace_html("用户 {{<var>user</var>}} 报错", buf, sizeof(buf));
// 结果: "用户 Alice 报错"

// 通过 get_error_with_vars 便捷使用
nl_lang_set_error(lib_id, 404, "en_us", "{{<var>nl.lang.user</var>}} not found");
nl_lang_get_error_with_vars(lib_id, 404, buf, sizeof(buf));
// 结果: "Alice not found"

条件表达式求值

// 评估条件表达式,返回 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:

// 脚本执行结果结构
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);

使用示例

#include "netleaf_lang.h"
#include <stdio.h>

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 参考

语言设置

// 设置当前语言(必须 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);

库注册

// 注册库及其支持的语言
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);

错误消息注册

// 添加错误消息(所有语言)
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 声明错误表并一次注册(中英双语):

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) 已通过该机制注册各自的返回码/错误码。

错误消息查找

// 获取当前语言的错误消息
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);

// 获取错误消息并替换其中的变量(支持 {{<var>nl.lang.NAME</var>}} 格式)
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);

多文件支持

// 加载多个语言文件
int nl_lang_load_json_multi(int lib_id, const char** files, int file_count);

多库共享文件

// 共享文件配置结构
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);

错误码重复检测

// 检查错误码冲突
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);

异步加载

// 异步回调类型
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:基本用法

#include "netleaf_lang.h"
#include <stdio.h>

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:分开注册语言

#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:自定义错误码

#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:多库共享文件

#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:异步加载

#include "netleaf_lang.h"
#include <stdio.h>

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:错误码冲突检测

#include "netleaf_lang.h"
#include <stdio.h>

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 文件格式

{
  "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. 使用共享文件减少重复配置
⚠️ **GitHub.com Fallback** ⚠️