xref: /MusicPlayer2/MusicPlayer2/StrTable.cpp (revision d3dd9a0deed159ed78765b280a374a0f9687bbae)
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