Files
SimpleRemoter/server/2015Remote/LangManager.h
2026-01-28 21:54:48 +01:00

685 lines
21 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include <map>
#include <string>
#include <vector>
#include <afxwin.h>
// 语言管理类 - 支持多语言切换
class CLangManager
{
private:
std::map<CString, CString> m_strings; // 中文 -> 目标语言
CString m_currentLang; // 当前语言代码
CString m_langDir; // 语言文件目录
CLangManager() {}
CLangManager(const CLangManager&) = delete;
CLangManager& operator=(const CLangManager&) = delete;
public:
static CLangManager& Instance()
{
static CLangManager instance;
return instance;
}
// 初始化语言目录
void Init(const CString& langDir = _T(""))
{
if (langDir.IsEmpty()) {
// 默认使用 exe 所在目录下的 lang 文件夹
TCHAR path[MAX_PATH];
GetModuleFileName(NULL, path, MAX_PATH);
CString exePath(path);
int pos = exePath.ReverseFind(_T('\\'));
if (pos > 0) {
m_langDir = exePath.Left(pos) + _T("\\lang");
}
} else {
m_langDir = langDir;
}
// 确保目录存在
CreateDirectory(m_langDir, NULL);
}
// 获取可用的语言列表
std::vector<CString> GetAvailableLanguages()
{
std::vector<CString> langs;
CString searchPath = m_langDir + _T("\\*.ini");
WIN32_FIND_DATA fd;
HANDLE hFind = FindFirstFile(searchPath, &fd);
if (hFind != INVALID_HANDLE_VALUE) {
do {
CString filename(fd.cFileName);
int dotPos = filename.ReverseFind(_T('.'));
if (dotPos > 0) {
langs.push_back(filename.Left(dotPos));
}
} while (FindNextFile(hFind, &fd));
FindClose(hFind);
}
return langs;
}
// 检查语言文件编码是否为 ANSI
// 返回 false 表示文件不存在或编码不是 ANSI检测 BOM 和 UTF-8 无 BOM
bool CheckEncoding(const CString& langCode)
{
if (langCode == _T("zh_CN") || langCode.IsEmpty()) {
TRACE("[LangEnc] zh_CN or empty, 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;
}
// 读取文件内容(最多检测前 4KB 即可判断)
unsigned char buf[4096];
size_t n = fread(buf, 1, sizeof(buf), f);
fclose(f);
TRACE("[LangEnc] Read %zu bytes\n", n);
if (n == 0) return false;
// 检测 BOM
if (n >= 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) {
TRACE("[LangEnc] Detected UTF-8 BOM\n");
return false;
}
if (n >= 2 && buf[0] == 0xFF && buf[1] == 0xFE) {
TRACE("[LangEnc] Detected UTF-16 LE BOM\n");
return false;
}
if (n >= 2 && buf[0] == 0xFE && buf[1] == 0xFF) {
TRACE("[LangEnc] Detected UTF-16 BE BOM\n");
return false;
}
// 检测 UTF-8 无 BOM扫描是否存在合法的 UTF-8 多字节序列
// 中文 UTF-8 为 3 字节 (E0-EF + 80-BF + 80-BF)
// GBK 为 2 字节 (81-FE + 40-FE),字节模式不同
int utf8SeqCount = 0;
for (size_t i = 0; i < n; ) {
unsigned char c = buf[i];
if (c < 0x80) {
i++;
} else if ((c & 0xE0) == 0xC0 && i + 1 < n
&& (buf[i+1] & 0xC0) == 0x80) {
utf8SeqCount++; // 2 字节 UTF-8
i += 2;
} else if ((c & 0xF0) == 0xE0 && i + 2 < n
&& (buf[i+1] & 0xC0) == 0x80
&& (buf[i+2] & 0xC0) == 0x80) {
utf8SeqCount++; // 3 字节 UTF-8中文
i += 3;
} else if ((c & 0xF8) == 0xF0 && i + 3 < n
&& (buf[i+1] & 0xC0) == 0x80
&& (buf[i+2] & 0xC0) == 0x80
&& (buf[i+3] & 0xC0) == 0x80) {
utf8SeqCount++; // 4 字节 UTF-8
i += 4;
} else {
// 高字节不符合 UTF-8 规则
// 但如果在缓冲区末尾,可能是多字节序列被截断,不应误判
if (i + 3 >= n && c >= 0xC0) {
TRACE("[LangEnc] Truncated at offset %zu: 0x%02X, skip\n", i, c);
break; // 缓冲区尾部截断,跳出循环按已有结果判断
}
// 确实是 ANSI/GBK
TRACE("[LangEnc] GBK byte at offset %zu: 0x%02X → ANSI\n", i, c);
return true;
}
}
TRACE("[LangEnc] utf8SeqCount=%d → %s\n", utf8SeqCount,
utf8SeqCount > 0 ? "UTF-8 (not ANSI)" : "pure ASCII (ANSI)");
// 存在多字节序列且全部符合 UTF-8 规则 → 判定为 UTF-8
return (utf8SeqCount == 0);
}
// 加载语言文件
bool Load(const CString& langCode)
{
m_strings.clear();
m_currentLang = langCode;
// 如果是中文,不需要加载翻译
if (langCode == _T("zh_CN") || langCode.IsEmpty()) {
return true;
}
CString langFile = m_langDir + _T("\\") + langCode + _T(".ini");
// 检查文件是否存在
if (GetFileAttributes(langFile) == INVALID_FILE_ATTRIBUTES) {
return false;
}
// 读取 [Strings] 节的所有键值对
TCHAR buffer[32768] = { 0 }; // 用于获取所有键名
GetPrivateProfileSection(_T("Strings"), buffer, sizeof(buffer)/sizeof(TCHAR), langFile);
// 解析键值对 (格式: key=value\0key=value\0\0)
TCHAR* p = buffer;
while (*p) {
CString line(p);
int eqPos = line.Find(_T('='));
if (eqPos > 0) {
CString key = line.Left(eqPos);
CString value = line.Mid(eqPos + 1);
m_strings[key] = value;
}
p += _tcslen(p) + 1;
}
return true;
}
// 获取翻译字符串
// key: 中文原文
// 返回: 翻译后的文本,如果没有翻译则返回原文
CString Get(const CString& key)
{
if (m_currentLang == _T("zh_CN") || m_currentLang.IsEmpty()) {
return key; // 中文直接返回
}
auto it = m_strings.find(key);
if (it != m_strings.end()) {
return it->second;
}
return key; // 没有翻译则返回原文
}
// 获取翻译字符串 (std::string 版本)
std::string Get(const std::string& key)
{
CString result = Get(CString(key.c_str()));
CT2A ansi(result);
return std::string(ansi);
}
// 获取当前语言
CString GetCurrentLanguage() const
{
return m_currentLang;
}
// 是否为中文模式(无需翻译)
bool IsChinese() const
{
return m_currentLang.IsEmpty() || m_currentLang == _T("zh_CN");
}
// 获取可用语言数量(包括内置的简体中文)
// 返回 1 表示只有简体中文,无其他语言文件
size_t GetLanguageCount()
{
auto langs = GetAvailableLanguages();
// 检查是否有 zh_CN.ini 文件
bool hasZhCN = false;
for (const auto& lang : langs) {
if (lang == _T("zh_CN")) {
hasZhCN = true;
break;
}
}
// 简体中文始终存在,若 zh_CN.ini 不存在则额外 +1
return hasZhCN ? langs.size() : langs.size() + 1;
}
};
// 全局访问宏
#define g_Lang CLangManager::Instance()
// 翻译宏 - 用于代码中的字符串字面量
// 用法: _TR("中文字符串")
#define _TR(str) g_Lang.Get(CString(_T(str)))
// 翻译宏 - 用于格式化函数 (sprintf_s, _stprintf_s 等可变参数函数)
// 用法: _stprintf_s(buf, _TRF("连接 %s 失败"), ip);
#define _TRF(str) ((LPCTSTR)_TR(str))
// 翻译函数 - 用于 CString 变量或 LPCTSTR
// 用法: _L(strVar) 或 _L(_T("中文"))
inline CString _L(const CString& str) { return g_Lang.Get(str); }
inline CString _L(LPCTSTR str) { return g_Lang.Get(CString(str)); }
// 翻译宏 - 用于格式化函数中的变量 (返回 LPCTSTR)
// 用法: _stprintf_s(buf, _LF(strVar), arg);
// 注意: 必须是宏,函数版本会导致悬空指针
#define _LF(str) ((LPCTSTR)_L(str))
// CString::Format 的多语言版本 (用于全局替换 .FormatL)
// 用法: str.FormatLL("连接 %s 失败", ip);
// 展开: str.FormatL(_TR("连接 %s 失败"), ip);
// 注意: 不需要翻译的字符串也可以用,找不到翻译会返回原文
#define FormatL(fmt, ...) Format(_TR(fmt), __VA_ARGS__)
// ============================================
// 带自动翻译的 MFC 函数宏 (L 后缀 = Language)
// ============================================
// MessageBox 系列
// MFC 成员函数版本 (CWnd::MessageBox)
#define MessageBoxL(text, caption, type) \
MessageBox(_TR(text), _TR(caption), type)
// 全局 API 版本 (::MessageBox / ::MessageBoxA / ::MessageBoxW)
#define MessageBoxAPI_L(hwnd, text, caption, type) \
::MessageBox(hwnd, _TR(text), _TR(caption), type)
// 简写hwnd 为 NULL 时
#define MsgBoxL(text, caption, type) \
::MessageBox(NULL, _TR(text), _TR(caption), type)
#define AfxMessageBoxL(text, type) \
AfxMessageBox(_TR(text), type)
// SetWindowText / SetDlgItemText
#define SetWindowTextL(text) \
SetWindowText(_TR(text))
#define SetDlgItemTextL(id, text) \
SetDlgItemText(id, _TR(text))
// 列表控件
#define InsertColumnL(index, text, format, width) \
InsertColumn(index, _TR(text), format, width)
#define InsertItemL(index, text) \
InsertItem(index, _TR(text))
#define SetItemTextL(item, subitem, text) \
SetItemText(item, subitem, _TR(text))
// ComboBox / ListBox
#define AddStringL(text) \
AddString(_TR(text))
#define InsertStringL(index, text) \
InsertString(index, _TR(text))
// Tab 控件
#define InsertTabItemL(index, text) \
InsertItem(index, _TR(text))
// 状态栏
#define SetPaneTextL(index, text) \
SetPaneText(index, _TR(text))
// 菜单
#define AppendMenuL(flags, id, text) \
AppendMenu(flags, id, _TR(text))
#define AppendMenuSeparator(p) \
AppendMenu(p)
#define InsertMenuL(pos, flags, id, text) \
InsertMenu(pos, flags, id, _TR(text))
#define ModifyMenuL(pos, flags, id, text) \
ModifyMenu(pos, flags, id, _TR(text))
// 翻译对话框所有控件
inline void TranslateDialog(CWnd* pWnd)
{
if (g_Lang.IsChinese()) {
return; // 中文模式不需要翻译
}
if (!pWnd || !pWnd->GetSafeHwnd()) {
return;
}
// 翻译对话框标题
CString title;
pWnd->GetWindowText(title);
if (!title.IsEmpty()) {
CString newTitle = g_Lang.Get(title);
if (newTitle != title) {
pWnd->SetWindowText(newTitle);
}
}
// 遍历所有子控件
CWnd* pChild = pWnd->GetWindow(GW_CHILD);
while (pChild) {
// 获取控件文本
CString text;
pChild->GetWindowText(text);
if (!text.IsEmpty()) {
CString newText = g_Lang.Get(text);
if (newText != text) {
pChild->SetWindowText(newText);
}
}
// 如果是菜单按钮或有子菜单,也需要处理
// 递归处理子窗口(如 GroupBox 内的控件)
if (pChild->GetWindow(GW_CHILD)) {
TranslateDialog(pChild);
}
pChild = pChild->GetNextWindow();
}
}
// 翻译菜单
inline void TranslateMenu(CMenu* pMenu)
{
if (!pMenu || !pMenu->GetSafeHmenu() || g_Lang.IsChinese()) {
return;
}
UINT count = pMenu->GetMenuItemCount();
for (UINT i = 0; i < count; i++) {
CString text;
pMenu->GetMenuString(i, text, MF_BYPOSITION);
if (!text.IsEmpty()) {
CString newText = g_Lang.Get(text);
if (newText != text) {
// 保留快捷键部分 (Tab 后的内容,如 Ctrl+S)
int tabPos = text.Find(_T('\t'));
if (tabPos > 0) {
CString shortcut = text.Mid(tabPos);
int newTabPos = newText.Find(_T('\t'));
if (newTabPos < 0) {
newText += shortcut;
}
}
// 检查是否是弹出菜单(有子菜单)
CMenu* pSubMenu = pMenu->GetSubMenu(i);
if (pSubMenu) {
// 弹出菜单使用 MF_POPUP
pMenu->ModifyMenu(i, MF_BYPOSITION | MF_POPUP | MF_STRING,
(UINT_PTR)pSubMenu->GetSafeHmenu(), newText);
} else {
// 普通菜单项
pMenu->ModifyMenu(i, MF_BYPOSITION | MF_STRING,
pMenu->GetMenuItemID(i), newText);
}
}
}
// 递归处理子菜单
CMenu* pSubMenu = pMenu->GetSubMenu(i);
if (pSubMenu) {
TranslateMenu(pSubMenu);
}
}
}
// 加载菜单并翻译 (用于 LoadMenu 动态加载菜单后自动翻译)
inline BOOL LoadMenuL(CMenu& menu, UINT nIDResource)
{
if (!menu.LoadMenu(nIDResource)) return FALSE;
TranslateMenu(&menu);
return TRUE;
}
inline BOOL LoadMenuL(CMenu* pMenu, UINT nIDResource)
{
if (!pMenu || !pMenu->LoadMenu(nIDResource)) return FALSE;
TranslateMenu(pMenu);
return TRUE;
}
// 翻译列表控件表头
inline void TranslateListHeader(CListCtrl* pList)
{
if (!pList || g_Lang.IsChinese()) {
return;
}
CHeaderCtrl* pHeader = pList->GetHeaderCtrl();
if (!pHeader) {
return;
}
int count = pHeader->GetItemCount();
for (int i = 0; i < count; i++) {
HDITEM hdi;
TCHAR text[256] = { 0 };
hdi.mask = HDI_TEXT;
hdi.pszText = text;
hdi.cchTextMax = 256;
if (pHeader->GetItem(i, &hdi)) {
CString newText = g_Lang.Get(CString(text));
if (newText != text) {
hdi.pszText = (LPTSTR)(LPCTSTR)newText;
pHeader->SetItem(i, &hdi);
}
}
}
}
// 支持多语言的对话框基类 (基于 CDialog)
// 用法: 将 class CMyDlg : public CDialog 改为 class CMyDlg : public CDialogLang
class CDialogLang : public CDialog
{
public:
CDialogLang(){}
CDialogLang(UINT nIDTemplate, CWnd* pParent = NULL)
: CDialog(nIDTemplate, pParent) {}
CDialogLang(LPCTSTR lpszTemplateName, CWnd* pParent = NULL)
: CDialog(lpszTemplateName, pParent) {}
protected:
virtual BOOL OnInitDialog() override
{
BOOL ret = __super::OnInitDialog();
TranslateDialog(this);
TranslateMenu(GetMenu()); // 自动翻译菜单
return ret;
}
};
// 支持多语言的对话框基类 (基于 CDialogEx)
// 用法: 将 class CMyDlg : public CDialogEx 改为 class CMyDlg : public CDialogLangEx
class CDialogLangEx : public CDialogEx
{
public:
CDialogLangEx(UINT nIDTemplate, CWnd* pParent = NULL)
: CDialogEx(nIDTemplate, pParent) {}
CDialogLangEx(LPCTSTR lpszTemplateName, CWnd* pParent = NULL)
: CDialogEx(lpszTemplateName, pParent) {}
protected:
virtual BOOL OnInitDialog() override
{
BOOL ret = __super::OnInitDialog();
TranslateDialog(this);
TranslateMenu(GetMenu()); // 自动翻译菜单
return ret;
}
};
// ============================================
// 语言选择对话框(动态创建,无需 RC 资源)
// ============================================
class CLangSelectDlg : public CDialog
{
public:
CString m_strSelectedLang;
CComboBox m_comboLang;
CLangSelectDlg(CWnd* pParent = NULL) : CDialog(), m_pParent(pParent) {}
virtual INT_PTR DoModal() override
{
InitModalIndirect(CreateDialogTemplate(), m_pParent);
return CDialog::DoModal();
}
// 静态方法:显示对话框并返回选择的语言代码
// 返回空字符串表示用户取消
static CString Show(CWnd* pParent = NULL)
{
CLangSelectDlg dlg(pParent);
if (dlg.DoModal() == IDOK) {
return dlg.m_strSelectedLang;
}
return _T("");
}
protected:
CWnd* m_pParent;
std::vector<BYTE> m_templateBuffer;
std::vector<CString> m_langCodes;
// 语言代码到显示名称的映射
static CString GetLanguageDisplayName(const CString& langCode)
{
if (langCode == _T("zh_CN")) return _T("简体中文");
if (langCode == _T("zh_TW")) return _T("繁體中文");
if (langCode == _T("en_US")) return _T("English");
return langCode;
}
LPCDLGTEMPLATE CreateDialogTemplate()
{
const WORD DLG_WIDTH = 200;
const WORD DLG_HEIGHT = 75;
m_templateBuffer.clear();
DLGTEMPLATE dlgTemplate = { 0 };
dlgTemplate.style = DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU;
dlgTemplate.cdit = 4;
dlgTemplate.cx = DLG_WIDTH;
dlgTemplate.cy = DLG_HEIGHT;
AppendData(&dlgTemplate, sizeof(DLGTEMPLATE));
AppendWord(0); // 菜单
AppendWord(0); // 窗口类
AppendString(_T("选择语言 / Select Language"));
AlignToDword();
// 静态文本
AddControl(0x0082, 15, 15, 40, 12, (WORD)-1,
SS_LEFT | WS_CHILD | WS_VISIBLE, _T("语言:"));
// ComboBox
AddControl(0x0085, 55, 13, 130, 150, 1001,
CBS_DROPDOWNLIST | WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL, _T(""));
// 确定按钮
AddControl(0x0080, 45, 50, 50, 14, IDOK,
BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("确定"));
// 取消按钮
AddControl(0x0080, 105, 50, 50, 14, IDCANCEL,
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, _T("取消"));
return (LPCDLGTEMPLATE)m_templateBuffer.data();
}
void AppendData(const void* data, size_t size)
{
const BYTE* p = (const BYTE*)data;
m_templateBuffer.insert(m_templateBuffer.end(), p, p + size);
}
void AppendWord(WORD w) { AppendData(&w, sizeof(WORD)); }
void AppendString(LPCTSTR str)
{
#ifdef UNICODE
AppendData(str, (_tcslen(str) + 1) * sizeof(WCHAR));
#else
int len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
std::vector<WCHAR> wstr(len);
MultiByteToWideChar(CP_ACP, 0, str, -1, wstr.data(), len);
AppendData(wstr.data(), len * sizeof(WCHAR));
#endif
}
void AlignToDword()
{
while (m_templateBuffer.size() % 4 != 0)
m_templateBuffer.push_back(0);
}
void AddControl(WORD classAtom, short x, short y, short cx, short cy,
WORD id, DWORD style, LPCTSTR text)
{
AlignToDword();
DLGITEMTEMPLATE item = { 0 };
item.style = style;
item.x = x; item.y = y; item.cx = cx; item.cy = cy;
item.id = id;
AppendData(&item, sizeof(DLGITEMTEMPLATE));
AppendWord(0xFFFF);
AppendWord(classAtom);
AppendString(text);
AppendWord(0);
}
virtual BOOL OnInitDialog() override
{
CDialog::OnInitDialog();
// 翻译对话框控件(标题、标签、按钮)
TranslateDialog(this);
m_comboLang.SubclassDlgItem(1001, this);
// 添加简体中文
int idx = m_comboLang.AddString(_T("简体中文"));
m_langCodes.push_back(_T("zh_CN"));
m_comboLang.SetItemData(idx, 0);
// 添加其他语言
auto langs = g_Lang.GetAvailableLanguages();
for (const auto& lang : langs) {
if (lang == _T("zh_CN")) continue;
CString displayName = GetLanguageDisplayName(lang);
idx = m_comboLang.AddString(displayName);
m_comboLang.SetItemData(idx, m_langCodes.size());
m_langCodes.push_back(lang);
}
// 选中当前语言
CString currentLang = g_Lang.GetCurrentLanguage();
if (currentLang.IsEmpty()) currentLang = _T("zh_CN");
for (size_t i = 0; i < m_langCodes.size(); i++) {
if (m_langCodes[i] == currentLang) {
m_comboLang.SetCurSel((int)i);
break;
}
}
CenterWindow();
return TRUE;
}
virtual void OnOK() override
{
int sel = m_comboLang.GetCurSel();
if (sel >= 0) {
size_t idx = (size_t)m_comboLang.GetItemData(sel);
if (idx < m_langCodes.size()) {
m_strSelectedLang = m_langCodes[idx];
}
}
CDialog::OnOK();
}
};