1 #include "stdafx.h"
2 #include "StrTable.h"
3 #include "Common.h"
4 #include "IniHelper.h"
5 #include "resource.h"
6
StrTable()7 StrTable::StrTable()
8 {
9 }
10
~StrTable()11 StrTable::~StrTable()
12 {
13 }
14
15 const wstring StrTable::error_str = { L"<error>" };
16
Init(const wstring & language_dir,wstring & language_tag_setting)17 bool StrTable::Init(const wstring& language_dir, wstring& language_tag_setting)
18 {
19 bool expected = false;
20 if (!initialized.compare_exchange_strong(expected, true))
21 return false;
22
23 auto InitMapFromIniHelper = [this](const CIniHelper& ini)
24 {
25 static const wstring MenuAppName = L"menu.";
26 ini.GetAllKeyValues(L"text", m_text_string_table);
27 ini.GetAllKeyValues(L"scintlla", m_scintilla_string_table);
28 const auto& list = ini.GetAllAppName(MenuAppName);
29 for (const auto& item : list)
30 ini.GetAllKeyValues(MenuAppName + item, m_menu_string_table[item]);
31 };
32
33 std::map<wstring, LanguageInfo> list; // 使用map自动去重与排序
34
35 { // 加载内嵌资源作为默认值确保外部翻译文件不完整/不存在时的正常使用
36 // 存在外部en-US文件时这里设置的list项目会被覆写(下面根据LanguageInfo.file_name是否为空判断是否存在外部英文翻译)
37 list.emplace(L"en-US", LanguageInfo{ L"", L"English <Default>", L"en-US", L"Segoe UI", { L"<MusicPlayer2>" } });
38 CIniHelper default_ini(IDR_STRING_TABLE);
39 InitMapFromIniHelper(default_ini);
40 } // 即使最终决定是en-US接下来也再加载一次外部翻译,使文本无须重新编译即可修改
41
42 bool succeed{};
43 vector<wstring> files;
44 CCommon::GetFiles(language_dir + L"*.ini", files);
45 for (const wstring& file_name : files)
46 {
47 CIniHelper file(language_dir + file_name);
48 wstring tag = file.GetString(L"general", L"BCP_47", L"");
49 if (tag.empty()) continue;
50 auto& item = list[tag];
51 item.file_name = file_name;
52 item.display_name = file.GetString(L"general", L"DISPLAY_NAME", file_name.c_str());
53 item.bcp_47 = tag;
54 item.default_font_name = file.GetString(L"general", L"DEFAULT_FONT", L""); // 字体默认值为空(跟随系统)
55 file.GetStringList(L"general", L"TRANSLATOR", item.translator, vector<wstring>{ L"<Unknown>" });
56 if (language_tag_setting == tag)
57 {
58 InitMapFromIniHelper(file);
59 m_default_font_name = item.default_font_name;
60 m_language_tag.push_back(tag); // 此处的tag可能不存在于当前系统,设置线程语言的api会略过完全不支持的语言(可以自动匹配相近的语言)
61 succeed = true;
62 }
63 }
64
65 if (!succeed) // 设置是"跟随系统"或之前的语言配置文件没有找到
66 {
67 language_tag_setting.clear(); // 更改设置为“跟随系统”
68 vector<wstring> user_language_list;
69 CCommon::GetThreadLanguageList(user_language_list);
70 // 在系统已安装的语言列表中优先匹配一个程序已有的语言
71 for (const wstring& tag : user_language_list)
72 {
73 auto iter = list.find(tag);
74 if (iter == list.end())
75 continue;
76 CIniHelper file(language_dir + iter->second.file_name);
77 InitMapFromIniHelper(file);
78 m_default_font_name = iter->second.default_font_name;
79 m_language_tag.push_back(tag);
80 succeed = true;
81 break;
82 }
83 // TODO: 添加模糊匹配近似语言(不清楚安装了不同语言包的windows的默认语言列表会是什么)
84 }
85 if (!succeed) // 系统语言列表中没有找到程序支持的语言
86 {
87 // 如果仍然没有匹配的语言那么加载en-US的语言文件(如果存在)
88 const LanguageInfo& en = list[L"en-US"];
89 if (!en.file_name.empty()) // 如果没有加载到外部en-US翻译那么这里会为空
90 {
91 CIniHelper file(language_dir + en.file_name);
92 InitMapFromIniHelper(file);
93 m_default_font_name = en.default_font_name;
94 m_language_tag.push_back(L"en-US");
95 succeed = true;
96 }
97 }
98
99 // TODO: 检查系统是否已安装此字体(未测试:我担心其中使用的字体枚举API当系统字体非常多时出现严重的效率问题)
100 if (m_default_font_name.empty() || m_default_font_name.size() > LF_FACESIZE - 1/* || !CCommon::IsFontInstalled(m_default_font_name)*/)
101 m_default_font_name = CCommon::GetSystemDefaultUIFont();
102 // 将map去重又排序的结果装入m_language_list
103 std::transform(list.begin(), list.end(), std::back_inserter(m_language_list),
104 [](const auto& pair) { return std::move(pair.second); });
105 return succeed;
106 }
107
LoadText(const wstring & key) const108 const wstring& StrTable::LoadText(const wstring& key) const
109 {
110 // 查找key而不是使用[]是为了避免发生任何写入,这样不使用读写锁也有线程安全
111 auto iter = m_text_string_table.find(key);
112 if (iter != m_text_string_table.end())
113 return iter->second;
114 else // 程序中试图读取不存在于<language>.ini中的键或当前还未进行初始化
115 {
116 std::lock_guard<std::mutex> lock(error_mutex);
117 m_unknown_key.insert(key);
118 return error_str;
119 }
120 }
121
LoadTextFormat(const wstring & key,const std::initializer_list<CVariant> & paras) const122 wstring StrTable::LoadTextFormat(const wstring& key, const std::initializer_list<CVariant>& paras) const
123 {
124 // 查找key而不是使用[]是为了避免发生任何写入,这样不使用读写锁也有线程安全
125 auto iter = m_text_string_table.find(key);
126 if (iter == m_text_string_table.end()) // 程序中试图读取不存在于<language>.ini中的键或当前还未进行初始化
127 {
128 std::lock_guard<std::mutex> lock(error_mutex);
129 m_unknown_key.insert(key);
130 return error_str;
131 }
132 wstring str{ iter->second }; // 复制以避免原始字符串修改
133 int index{ 1 };
134 for (const auto& para : paras)
135 {
136 wstring format_str{ L"<%" + std::to_wstring(index) + L"%>" };
137 if (!CCommon::StringReplace(str, format_str, para.ToString().GetString()))
138 {
139 // 当前取得的翻译字符串中缺少paras指定的<%序号%>占位符
140 std::lock_guard<std::mutex> lock(error_mutex);
141 m_error_para_key.insert(key);
142 continue;
143 }
144 ++index;
145 }
146 return str;
147 }
148
LoadMenuText(const wstring & menu_name,const wstring & key) const149 const wstring& StrTable::LoadMenuText(const wstring& menu_name, const wstring& key) const
150 {
151 // 查找key而不是使用[]是为了避免发生任何写入,这样不使用读写锁也有线程安全
152 auto iter_name = m_menu_string_table.find(menu_name);
153 if (iter_name == m_menu_string_table.end())
154 {
155 std::lock_guard<std::mutex> lock(error_mutex);
156 m_unknown_key.insert(menu_name);
157 return error_str;
158 }
159 const auto& key_map = iter_name->second;
160 auto iter_key = key_map.find(key);
161 if (iter_key == key_map.end())
162 {
163 std::lock_guard<std::mutex> lock(error_mutex);
164 m_unknown_key.insert(key);
165 return error_str;
166 }
167 return iter_key->second;
168 }
169
GetScintillaStrMap() const170 const std::map<wstring, wstring>& StrTable::GetScintillaStrMap() const
171 {
172 return m_scintilla_string_table;
173 }
174