Feature: Embed language resources, disk files act as optional patches
This commit is contained in:
@@ -526,14 +526,13 @@ BOOL CMy2015RemoteApp::InitInstance()
|
||||
SetChineseThreadLocale();
|
||||
|
||||
// 加载语言包(必须在显示任何文本之前)
|
||||
// 内嵌资源支持 en_US 和 zh_TW,无需外部文件
|
||||
auto lang = THIS_CFG.GetStr("settings", "Language", "en_US");
|
||||
auto langDir = THIS_CFG.GetStr("settings", "LangDir", "./lang");
|
||||
langDir = langDir.empty() ? "./lang" : langDir;
|
||||
if (PathFileExists(langDir.c_str())) {
|
||||
g_Lang.Init(langDir.c_str());
|
||||
g_Lang.Load(lang.c_str());
|
||||
Mprintf("语言包目录已经指定[%s], 语言数量: %d\n", langDir.c_str(), g_Lang.GetLanguageCount());
|
||||
}
|
||||
g_Lang.Init(langDir.c_str()); // 初始化目录(用于磁盘补丁文件)
|
||||
g_Lang.Load(lang.c_str()); // 加载语言(优先内嵌资源,再覆盖磁盘文件)
|
||||
Mprintf("语言: %s, 目录: %s\n", lang.c_str(), langDir.c_str());
|
||||
|
||||
// 创建并显示启动画面
|
||||
CSplashDlg* pSplash = new CSplashDlg();
|
||||
|
||||
Binary file not shown.
@@ -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);
|
||||
|
||||
|
||||
@@ -968,6 +968,11 @@
|
||||
#define IDC_STATIC_TRIGGER_TYPE 2544
|
||||
#define IDC_STATIC_TRIGGER_ACTION 2545
|
||||
|
||||
// 内嵌语言资源 (RCDATA)
|
||||
// 注意:避免与 IDB_BITMAP_TRIGGER(372) 和 IDB_BITMAP_WEBDESKTOP(373) 冲突
|
||||
#define IDR_LANG_EN_US 380
|
||||
#define IDR_LANG_ZH_TW 381
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
|
||||
Reference in New Issue
Block a user