Feature: Embed language resources, disk files act as optional patches

This commit is contained in:
yuanyuanxiang
2026-05-05 13:22:47 +02:00
parent 773c78ac0f
commit f11fc93ba8
5 changed files with 235 additions and 61 deletions

View File

@@ -1,11 +1,13 @@
#pragma once
#include <map>
#include <set>
#include <string>
#include <vector>
#include <locale.h>
#include <afxwin.h>
#include "common/IniParser.h"
#include "resource.h" // 用于内嵌语言资源 ID
// 设置线程区域为简体中文
// 这样 MBCS 程序在非中文系统上创建对话框时,也能正确解码 RC 资源中的 GBK 中文
@@ -60,12 +62,18 @@ public:
CreateDirectory(m_langDir, NULL);
}
// 获取可用的语言列表
// 获取可用的语言列表(包括内嵌语言)
std::vector<CString> GetAvailableLanguages()
{
std::vector<CString> langs;
CString searchPath = m_langDir + _T("\\*.ini");
std::set<CString> langSet; // 用于去重
// 1. 添加内嵌语言(始终可用)
langSet.insert(_T("en_US"));
langSet.insert(_T("zh_TW"));
// 2. 扫描磁盘上的语言文件
CString searchPath = m_langDir + _T("\\*.ini");
WIN32_FIND_DATA fd;
HANDLE hFind = FindFirstFile(searchPath, &fd);
if (hFind != INVALID_HANDLE_VALUE) {
@@ -73,30 +81,43 @@ public:
CString filename(fd.cFileName);
int dotPos = filename.ReverseFind(_T('.'));
if (dotPos > 0) {
langs.push_back(filename.Left(dotPos));
langSet.insert(filename.Left(dotPos));
}
} while (FindNextFile(hFind, &fd));
FindClose(hFind);
}
// 转为 vector 返回
for (const auto& lang : langSet) {
langs.push_back(lang);
}
return langs;
}
// 检查语言文件编码是否为 ANSI
// 返回 false 表示文件不存在或编码不是 ANSI检测 BOM 和 UTF-8 无 BOM
// 返回 false 表示编码不是 ANSI检测 BOM 和 UTF-8 无 BOM
// 内嵌语言en_US, zh_TW直接返回 true
bool CheckEncoding(const CString& langCode)
{
// 中文模式无需检查
if (langCode == _T("zh_CN") || langCode.IsEmpty()) {
TRACE("[LangEnc] zh_CN or empty, skip check\n");
return true;
}
// 内嵌语言无需检查(已确保编码正确)
if (langCode == _T("en_US") || langCode == _T("zh_TW")) {
TRACE("[LangEnc] builtin language, skip check\n");
return true;
}
CString langFile = m_langDir + _T("\\") + langCode + _T(".ini");
TRACE("[LangEnc] Checking: %s\n", (LPCSTR)langFile);
FILE* f = nullptr;
if (fopen_s(&f, (LPCSTR)langFile, "rb") != 0 || !f) {
TRACE("[LangEnc] fopen failed\n");
return false;
return false; // 非内嵌语言必须有磁盘文件
}
// 读取文件内容(最多检测前 4KB 即可判断)
@@ -164,26 +185,103 @@ public:
}
// 加载语言文件
// 优先从内嵌资源加载,然后用磁盘文件覆盖(如果存在)
bool Load(const CString& langCode)
{
m_strings.clear();
m_currentLang = langCode;
// 如果是中文,不需要加载翻译
// 中文模式:检查是否有补丁文件
if (langCode == _T("zh_CN") || langCode.IsEmpty()) {
// 尝试加载中文补丁文件(可选)
CString patchFile = m_langDir + _T("\\zh_CN.ini");
if (GetFileAttributes(patchFile) != INVALID_FILE_ATTRIBUTES) {
CIniParser ini;
if (ini.LoadFile((LPCSTR)patchFile)) {
const CIniParser::TKeyVal* pSection = ini.GetSection("Strings");
if (pSection) {
for (const auto& kv : *pSection) {
m_strings[CString(kv.first.c_str())] = CString(kv.second.c_str());
}
}
TRACE("[Lang] Loaded zh_CN patch: %d strings\n", (int)m_strings.size());
}
}
return true;
}
CString langFile = m_langDir + _T("\\") + langCode + _T(".ini");
// 1. 先从内嵌资源加载(英语和繁体中文)
bool hasBuiltin = LoadFromResource(langCode);
// 检查文件是否存在
if (GetFileAttributes(langFile) == INVALID_FILE_ATTRIBUTES) {
// 2. 再从磁盘文件加载(覆盖内嵌翻译)
CString langFile = m_langDir + _T("\\") + langCode + _T(".ini");
if (GetFileAttributes(langFile) != INVALID_FILE_ATTRIBUTES) {
CIniParser ini;
// 如果有内嵌资源,使用追加模式覆盖;否则使用普通加载
if (hasBuiltin) {
// 追加模式:磁盘文件中的翻译会覆盖内嵌翻译
if (ini.LoadFile((LPCSTR)langFile)) {
const CIniParser::TKeyVal* pSection = ini.GetSection("Strings");
if (pSection) {
for (const auto& kv : *pSection) {
m_strings[CString(kv.first.c_str())] = CString(kv.second.c_str());
}
}
TRACE("[Lang] Loaded disk file (override): %s\n", (LPCSTR)langFile);
}
} else {
// 无内嵌资源,直接从磁盘加载
if (ini.LoadFile((LPCSTR)langFile)) {
const CIniParser::TKeyVal* pSection = ini.GetSection("Strings");
if (pSection) {
for (const auto& kv : *pSection) {
m_strings[CString(kv.first.c_str())] = CString(kv.second.c_str());
}
}
TRACE("[Lang] Loaded disk file: %s\n", (LPCSTR)langFile);
return true;
}
}
}
return hasBuiltin || !m_strings.empty();
}
// 从内嵌资源加载语言数据
bool LoadFromResource(const CString& langCode)
{
UINT resID = 0;
if (langCode == _T("en_US")) {
resID = IDR_LANG_EN_US;
} else if (langCode == _T("zh_TW")) {
resID = IDR_LANG_ZH_TW;
} else {
return false; // 无内嵌资源
}
HRSRC hRes = FindResource(NULL, MAKEINTRESOURCE(resID), RT_RCDATA);
if (!hRes) {
TRACE("[Lang] Resource not found: %d\n", resID);
return false;
}
// 使用 CIniParser 解析,无文件大小限制,且不 trim key
HGLOBAL hData = LoadResource(NULL, hRes);
if (!hData) {
TRACE("[Lang] Failed to load resource: %d\n", resID);
return false;
}
const char* data = (const char*)LockResource(hData);
DWORD size = SizeofResource(NULL, hRes);
if (!data || size == 0) {
TRACE("[Lang] Empty resource: %d\n", resID);
return false;
}
// 使用 CIniParser 从内存解析
CIniParser ini;
if (!ini.LoadFile((LPCSTR)langFile)) {
if (!ini.LoadFromMemory(data, size)) {
TRACE("[Lang] Failed to parse resource: %d\n", resID);
return false;
}
@@ -194,6 +292,8 @@ public:
}
}
TRACE("[Lang] Loaded builtin resource: %s (%d strings)\n",
(LPCSTR)langCode, (int)m_strings.size());
return true;
}
@@ -625,24 +725,24 @@ protected:
AppendData(&dlgTemplate, sizeof(DLGTEMPLATE));
AppendWord(0); // 菜单
AppendWord(0); // 窗口类
AppendString(_T("选择语言 / Select Language"));
AppendString(_T("Select Language"));
AlignToDword();
// 静态文本
AddControl(0x0082, 15, 15, 40, 12, (WORD)-1,
SS_LEFT | WS_CHILD | WS_VISIBLE, _T("语言:"));
AddControl(0x0082, 15, 15, 50, 12, (WORD)-1,
SS_LEFT | WS_CHILD | WS_VISIBLE, _T("Language:"));
// ComboBox
AddControl(0x0085, 55, 13, 130, 150, 1001,
AddControl(0x0085, 65, 13, 120, 150, 1001,
CBS_DROPDOWNLIST | WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL, _T(""));
// 确定按钮
// OK 按钮
AddControl(0x0080, 45, 50, 50, 14, IDOK,
BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("确定"));
BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("OK"));
// 取消按钮
// Cancel 按钮
AddControl(0x0080, 105, 50, 50, 14, IDCANCEL,
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("取消"));
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("Cancel"));
return (LPCDLGTEMPLATE)m_templateBuffer.data();
}
@@ -703,8 +803,8 @@ protected:
m_comboLang.SubclassDlgItem(1001, this);
// 添加简体中文
int idx = m_comboLang.AddString(_T("简体中文"));
// 添加简体中文(显示为英语避免乱码)
int idx = m_comboLang.AddString(GetLanguageDisplayName(_T("zh_CN")));
m_langCodes.push_back(_T("zh_CN"));
m_comboLang.SetItemData(idx, 0);