commit e9be5d5f3bf7398a4a5be6e6763c7b14247f1099 Author: gb <741021719@qq.com> Date: Tue Jan 17 18:09:00 2023 +0800 多语言包支持 diff --git a/app_language.cpp b/app_language.cpp new file mode 100644 index 0000000..f74aa2e --- /dev/null +++ b/app_language.cpp @@ -0,0 +1,535 @@ +#pragma once + +#include "app_language.h" + +#if defined(_WIN32) || defined(_WIN64) +#define PATH_SEPERATOR "\\" +#define THIS_MODULE_NAME "lang.dll" +#else +#define PATH_SEPERATOR "/" +#define THIS_MODULE_NAME "lang.so" +#endif + +#include +#include +#include +#include +#include + +// *.pak ... +//typedef struct _lang_pak +//{ +// uint32_t len; +// uint32_t crc; // crc check ... +// uint32_t ver; +// uint32_t* cps[]; // supported code-pages table offset, until '-1' +// struct +// { +// uint32_t name_id; +// char name[0]; +// }*name[]; // my name on my code-page +// ENDING address align 16 before +// +// struct +// { +// uint32_t id; +// uint32_t offset; // offset to the first structure +// }str_map[]; // Offset of corresponding ID string, until id == -1 +//}; +struct _cp_locale +{ + int cp; + const char* locale; +}g_known_locale[] = + { {936, "zh_CN"} + , {950, "zh_HK"} + , {950, "zh_TW"} + , {950, "zh_SG"} + , {20127, "en_"} + , {932, "ja_JP"} + , {863, "fr_"} + , {1144, "it_CH"} + , {1141, "de_"} + , {855, "ru_RU"} +}; + +class lang_mgr +{ + typedef struct _map_str + { + uint32_t name_id; // global unique + std::vector code_pages; + std::string name; + std::string file; + + bool operator==(int cp) + { + return std::find(code_pages.begin(), code_pages.end(), cp) != code_pages.end(); + } + }MAPSTR; + + std::vector code_pages_; + LANATTR **all_; + uint32_t os_cp_; + uint32_t cur_cp_; + std::string cur_lang_; + std::map map_off_; + + static lang_mgr* inst_; + + void clear(void) + { + map_off_.clear(); + cur_lang_.clear(); + cur_cp_ = -1; + } + uint32_t calculate_crc32(uint8_t* data, uint32_t len, uint32_t prev = 0) + { + return 0; + } + uint32_t get_cur_code_page_id(void) + { + return os_cp_; + } + bool parse_pak_digest(uint8_t* data, MAPSTR& ms) + { + uint32_t ver = *(uint32_t*)data, + val = 0, + *cps = (uint32_t*)(data + sizeof(uint32_t)); + + while (*cps != -1) + { + ms.code_pages.push_back(*cps++); + } + if (ms.code_pages.empty()) + return false; + + cps++; + ms.name_id = *cps++; + val = (uint8_t*)cps - data; + ms.name = (char*)data + val; + + return ms.name.length() ? true : false; + } + bool parse_pak(uint8_t* data, uint32_t* bgn, uint32_t* id, std::map& off) + { + uint32_t ver = *(uint32_t*)data, + val = 0, + *cps = (uint32_t*)(data + sizeof(uint32_t)); + + if (id) + *id = cps[0]; + while (*cps++ != -1); + cps++; + val = (uint8_t*)cps - data; + val += strlen((char*)data + val) + 1; + val += 15; + val /= 16; + val *= 16; + if (bgn) + *bgn = val; + + struct + { + uint32_t id; + uint32_t off; + }*go = nullptr; + *((void**)&go) = (void*)(data + val); + while (go->id != -1) + { + off[go->id] = (char*)data + go->off + val; + go++; + } + + return true; + } + bool load_language_pak(const char* file, bool at_init) + { + std::string cont(""), name(""); + uint32_t bgn = 0, id = 0, cur_id = -1; + bool ret = false; + FILE* src = fopen(file, "rb"); + std::map off; + + if (src) + { + int len = 0; + uint8_t* buf = nullptr; + + fseek(src, 0, SEEK_END); + len = ftell(src); + fseek(src, 0, SEEK_SET); + if (len) + { + buf = new uint8_t[len + 4]; + if (buf) + { + memset(buf, 0, len + 4); + len = fread(buf, 1, len, src); + if (len == *(uint32_t*)buf && calculate_crc32(buf + sizeof(uint32_t)* 2, len - sizeof(uint32_t) * 2) == ((uint32_t*)buf)[1]) + { + cont = std::string((char*)buf, len); + } + delete[] buf; + } + } + fclose(src); + } + if (cont.length()) + { + if (at_init) + { + MAPSTR ms; + + ms.file = file; + cur_id = get_cur_code_page_id(); + if (parse_pak_digest((uint8_t*)&cont[0], ms)) + { + code_pages_.push_back(std::move(ms)); + if (cur_id == id && map_off_.size() == 0) + { + ret = parse_pak((uint8_t*)&cont[0], &bgn, &id, off); + if (ret) + name = ms.name; + } + } + } + else + ret = parse_pak((uint8_t*)&cont[0], &bgn, &id, off); + } + + if (ret && ((at_init && cur_id == id) || !at_init)) + { + clear(); + cur_cp_ = id; + cur_lang_ = std::move(name); + map_off_ = std::move(off); + } + + return ret; + } + +#if defined(_WIN32) || defined(_WIN64) + static std::string u2a(const wchar_t* u, UINT cp = CP_ACP) + { + std::string a(""); + + if (u) + { + char stack[256] = { 0 }, * ansi = NULL; + int len = 0; + + len = WideCharToMultiByte(cp, 0, u, lstrlenW(u), NULL, 0, NULL, NULL); + ansi = new char[len + 2]; + len = WideCharToMultiByte(cp, 0, u, lstrlenW(u), ansi, len, NULL, NULL); + ansi[len--] = 0; + a = ansi; + delete[] ansi; + } + + return a; + } + static std::wstring a2u(const char* a, UINT cp = CP_ACP) + { + std::wstring u(L""); + + if (a) + { + wchar_t *unic = NULL; + int len = 0; + + len = MultiByteToWideChar(cp, 0, a, lstrlenA(a), NULL, 0); + unic = new wchar_t[len + 2]; + len = MultiByteToWideChar(cp, 0, a, lstrlenA(a), unic, len + 1); + unic[len--] = 0; + u = unic; + delete[] unic; + } + + return u; + } +#else + static std::string link_file(const char* lnk) + { + char path[512] = { 0 }; + int len = readlink(lnk, path, sizeof(path) - 1); + + return path; + } + static bool found_module(const char* file, void* param) + { + std::string* str = (std::string*)param; + const char* n = strrchr(file, PATH_SEPERATOR[0]); + + if (n++ == nullptr) + n = file; + if (strcasecmp(str->c_str(), n) == 0) + { + *str = file; + + return false; + } + else + return true; + } +#endif + + int enum_files(const char* dir, bool(*on_found)(const char*, void*), void* param, bool recursive = true) + { + int ret = 0; + +#if defined(_WIN32) || defined(_WIN64) + std::wstring root(a2u(dir, CP_UTF8) + L"\\"); + WIN32_FIND_DATAW fd = { 0 }; + HANDLE h = FindFirstFileW((a2u(dir, CP_UTF8) + L"\\*").c_str(), &fd); + if (h == INVALID_HANDLE_VALUE) + return GetLastError(); + do + { + if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + if (recursive && wcscmp(fd.cFileName, L".") && wcscmp(fd.cFileName, L"..")) + { + ret = enum_files(u2a((root + fd.cFileName).c_str(), CP_UTF8).c_str(), on_found, param, recursive); + if (ret == ERROR_CANCELLED) + break; + } + } + else if (!on_found(u2a((root + fd.cFileName).c_str(), CP_UTF8).c_str(), param)) + { + ret = ERROR_CANCELLED; + break; + } + } while (FindNextFileW(h, &fd)); + FindClose(h); +#else + DIR* pdir = nullptr; + struct dirent* ent = nullptr; + + pdir = opendir(dir); + if (!pdir) + return errno; + + while ((ent = readdir(pdir))) + { + if (ent->d_type & DT_DIR) + { + if (recursive) + { + if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..")) + { + std::string sub(dir); + sub += "/"; + sub += ent->d_name; + ret = enum_files(sub.c_str(), found_file, param, recursive); + if (ret == 0x5e17) + break; + } + } + } + else + { + std::string file(dir); + + file += "/"; + file += ent->d_name; + if (!found_file(link_file(file.c_str()).c_str(), param)) + { + ret = 0x5e17; + break; + } + } + } +#endif + + return ret; + } + static bool found_language_pak(const char* file, void* obj) + { + ((lang_mgr*)obj)->load_language_pak(file, true); + + return true; + } + std::string get_module_path(void) + { +#if defined(_WIN32) || defined(_WIN64) + wchar_t path[MAX_PATH] = { 0 }; + + GetModuleFileNameW(GetModuleHandleA(THIS_MODULE_NAME), path, _countof(path) - 1); + + return u2a(path, CP_UTF8); +#else + char path[256] = { 0 }; + std::string ret(THIS_MODULE_NAME); + + sprintf(path, "/proc/%u/map_files", getpid()); + enum_files(path, &lang_mgr::found_module, &ret); + + return ret; +#endif + } + + lang_mgr() : os_cp_(lang_mgr::get_os_code_page()), cur_cp_(-1), cur_lang_(""), all_(nullptr) + { + std::string path(get_module_path()); + size_t pos = path.rfind(PATH_SEPERATOR[0]); + char *str = nullptr; + LANATTR *st = nullptr; + int ind = 0; + + if (pos++ == std::string::npos) + pos = 0; + path.erase(pos); + path += "lang"; + enum_files(path.c_str(), &lang_mgr::found_language_pak, this, false); + + size_t len = sizeof(LANATTR*) * (code_pages_.size() + 1) + sizeof(LANATTR) * code_pages_.size(); + + pos = len; + for (const auto& v: code_pages_) + { + len += v.name.length() + 4; + } + + all_ = (LANATTR**)new char[len]; + memset(all_, 0, len); + str = (char*)all_ + pos; + st = (LANATTR*)((char*)all_ + sizeof(LANATTR*) * (code_pages_.size() + 1)); + for (const auto& v : code_pages_) + { + all_[ind++] = st; + st->cp = v.code_pages[0]; + st->name = str; + strcpy(str, v.name.c_str()); + str += v.name.length() + 2; + st++; + } + } + ~lang_mgr() + { + delete[](char*)all_; + } + +public: + static int get_os_code_page(void) + { + char* locale = setlocale(LC_ALL, ""); + int cp = 936; + + if (locale) + { + char* end = nullptr; + bool found = false; + + if (strstr(locale, "LC_CTYPE=")) + locale = strstr(locale, "LC_CTYPE=") + strlen("LC_CTYPE"); + end = strstr(locale, ";"); + if (end) + *end = 0; + for (int i = 0; i < sizeof(g_known_locale) / sizeof(g_known_locale[0]); ++i) + { + if (strstr(locale, g_known_locale[i].locale)) + { + cp = g_known_locale[i].cp; + found = true; + break; + } + } + if (!found && strrchr(locale, '.')) + { + // windows: Chinese (Simplified)_China.936 + locale = strrchr(locale, '.') + 1; + if (*locale >= '0' && *locale <= '9') + cp = atoi(locale); + } + *end = ';'; + } + + return cp; + } + +public: + static lang_mgr* instance(void) + { + if (!lang_mgr::inst_) + lang_mgr::inst_ = new lang_mgr(); + + return lang_mgr::inst_; + } + int get_cur_cp(void) + { + return cur_cp_; + } + std::string get_cur_name(void) + { + return cur_lang_; + } + LANATTR** get_all(void) + { + return all_; + } + int set_code_page(int cp) + { + if (cp == 0) + cp = lang_mgr::get_os_code_page(); + if (cp == cur_cp_) + return 0; + + std::vector::iterator it = std::find(code_pages_.begin(), code_pages_.end(), cp); + if (it == code_pages_.end()) + return ENOENT; + + load_language_pak(it->file.c_str(), false); + + return 0; + } + const char* get_string(uint32_t id, int* err) + { + if (map_off_[id].size()) + { + if (err) + *err = 0; + + return map_off_[id].c_str(); + } + else + { + if (err) + *err = ENOENT; + + return ""; + } + } + int supported_cps(void) + { + return code_pages_.size(); + } +}; +lang_mgr* lang_mgr::inst_ = nullptr; + +extern "C" +{ + int lang_initialize(void* param) + { + if (lang_mgr::instance()->supported_cps() > 0) + return lang_mgr::instance()->get_cur_cp(); + else + return -1; + } + LANATTR** lang_get_supported_languages(void) + { + return lang_mgr::instance()->get_all(); + } + int lang_get_cur_code_page(void) + { + return lang_mgr::instance()->get_cur_cp(); + } + int lang_set_code_page(int cp) + { + return lang_mgr::instance()->set_code_page(cp); + } + const char* lang_load_string(uint32_t id, int* err) + { + return lang_mgr::instance()->get_string(id, err); + } +} diff --git a/app_language.h b/app_language.h new file mode 100644 index 0000000..e8e5fa2 --- /dev/null +++ b/app_language.h @@ -0,0 +1,77 @@ +#pragma once + +#if defined(_WIN32) || defined(_WIN64) +#include +#ifndef uint32_t +typedef unsigned int uint32_t; +#endif +#ifndef LANG_EXPORT +#ifdef _DEBUG +#pragma comment(lib, "Debug/lang.lib") +#else +#pragma comment(lib, "Release/lang.lib") +#endif +#endif +#endif + +// purpose: APP language utility +// +// Date: 2023-01-16 +// +// Implement: 语言包封装为独立的模块,提供以下API供程序调用 +// +// 所需语言包按代码页区分为独立的文件,安装时部署在安装目录下的“lang”子目录下,扩展名为“.pak” + +extern "C" +{ +#pragma pack(push) +#pragma pack(1) + typedef struct _lan_attr + { + int cp; // code page + uint32_t id; // 语言名称全局唯一ID + char *name; // 以当前语言环境显示的名称,UTF-8 + }LANATTR; +#pragma pack(pop) + + // Function: 初始化语言包 + // + // Parameter: param - 保留供后期扩展 + // + // Return: 返回当前应用的语言包代码页,-1为语言包文件丢失,需要提醒用户程序文件损坏并退出 + extern int lang_initialize(void* param = nullptr); + + // Function: 获取APP当前支持的所有语言包 + // + // Parameter: 无 + // + // Return: LANATTR指针数组pptr,直到pptr[]为nullptr为止 + extern LANATTR** lang_get_supported_languages(void); + + // Function: 获取APP当前语言包/代码页 + // + // Parameter: 无 + // + // Return: 代码页 + extern int lang_get_cur_code_page(void); + + // Function: 设置APP语言包/代码页(APP运行时会自动选定当前代码页,无特殊需要不需要专门调用) + // + // Parameter: cp - 代码页,若为“0”,则自动匹配系统当前代码页 + // + // Return: 0 - 成功 + // ENOENT - APP不支持该代码页 + extern int lang_set_code_page(int cp = 0); + + // Function: 加载当前代码页的字符串资源 (UTF-8) + // + // Parameters: id - 字符串ID,全局唯一 + // + // err - 获取id对应的字符串错误码 + // 0 - 成功 + // ENOENT - 所需ID资源,在当前代码页中没找到 + // + // Return: 对应的字符串指针,如果失败返回长度为0的空字符串 ""。调用者不用释放返回指针,但也不要改动该指针内容 + extern const char* lang_load_string(uint32_t id, int* err); +} +