Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Common/Data/Text/I18n.cpp
5673 views
1
#include <cstring>
2
3
#include "Common/Data/Text/I18n.h"
4
#include "Common/Data/Format/IniFile.h"
5
#include "Common/File/VFS/VFS.h"
6
#include "Common/Log.h"
7
8
#include "Common/StringUtils.h"
9
10
// Don't forget to update the constants in the header file if you change this.
11
static const char * const g_categoryNames[(size_t)I18NCat::CATEGORY_COUNT] = {
12
"Audio",
13
"Controls",
14
"CwCheats",
15
"DesktopUI",
16
"Developer",
17
"Dialog",
18
"Error",
19
"Game",
20
"Graphics",
21
"InstallZip",
22
"KeyMapping",
23
"MainMenu",
24
"MainSettings",
25
"MappableControls",
26
"Networking",
27
"Pause",
28
"PostShaders",
29
"PSPCredits",
30
"MemStick",
31
"RemoteISO",
32
"Reporting",
33
"Savedata",
34
"Screen",
35
"Search",
36
"Store",
37
"SysInfo",
38
"System",
39
"TextureShaders",
40
"Themes",
41
"UI Elements",
42
"VR",
43
"Achievements",
44
"PSPSettings",
45
};
46
47
I18NRepo g_i18nrepo;
48
49
std::string I18NRepo::LanguageID() {
50
return languageID_;
51
}
52
53
I18NRepo::I18NRepo() {
54
Clear();
55
}
56
57
void I18NRepo::Clear() {
58
std::lock_guard<std::mutex> guard(catsLock_);
59
for (auto &iter : cats_) {
60
// Initialize with empty categories, so that early lookups don't crash.
61
iter = std::make_shared<I18NCategory>();
62
}
63
}
64
65
I18NCategory::I18NCategory(const Section &section) {
66
std::map<std::string, std::string> sectionMap = section.ToMap();
67
SetMap(sectionMap);
68
name_ = section.name().c_str();
69
}
70
71
void I18NCategory::Clear() {
72
map_.clear();
73
missedKeyLog_.clear();
74
}
75
76
std::string_view I18NCategory::T(std::string_view key, std::string_view def) {
77
auto iter = map_.find(key);
78
if (iter != map_.end()) {
79
return iter->second.text.c_str();
80
} else {
81
if (map_.empty()) {
82
// Too early. This is probably in desktop-ui translation.
83
return !def.empty() ? def : key;
84
}
85
if (key != "Font") {
86
// Font is allowed to be missing.
87
INFO_LOG(Log::UI, "Missing translation [%s] %.*s (%.*s)", name_.c_str(), STR_VIEW(key), STR_VIEW(def));
88
std::lock_guard<std::mutex> guard(missedKeyLock_);
89
std::string missedKey(key);
90
if (!def.empty())
91
missedKeyLog_[missedKey] = def;
92
else
93
missedKeyLog_[missedKey] = missedKey;
94
}
95
return !def.empty() ? def : key;
96
}
97
}
98
99
const char *I18NCategory::T_cstr(const char *key, const char *def) {
100
auto iter = map_.find(key);
101
if (iter != map_.end()) {
102
return iter->second.text.c_str();
103
} else {
104
if (map_.empty()) {
105
// Too early. This is probably in desktop-ui translation.
106
return def ? def : key;
107
}
108
std::string missedKey(key);
109
if (missedKey != "Font") {
110
INFO_LOG(Log::UI, "Missing translation %s (%s)", key, def);
111
112
std::lock_guard<std::mutex> guard(missedKeyLock_);
113
if (def)
114
missedKeyLog_[missedKey] = def;
115
else
116
missedKeyLog_[missedKey] = std::string(key);
117
}
118
return def ? def : key;
119
}
120
}
121
122
void I18NCategory::SetMap(const std::map<std::string, std::string> &m) {
123
for (const auto &[key, value] : m) {
124
if (map_.find(key) == map_.end()) {
125
std::string text = ReplaceAll(value, "\\n", "\n");
126
_dbg_assert_(key.find('\n') == std::string::npos);
127
map_[key] = I18NEntry(text);
128
}
129
}
130
}
131
132
std::map<std::string, std::string, std::less<>> I18NCategory::Missed() const {
133
std::lock_guard<std::mutex> guard(missedKeyLock_);
134
return missedKeyLog_;
135
}
136
137
std::shared_ptr<I18NCategory> I18NRepo::GetCategory(I18NCat category) {
138
std::lock_guard<std::mutex> guard(catsLock_);
139
if (category != I18NCat::NONE)
140
return cats_[(size_t)category];
141
else
142
return nullptr;
143
}
144
145
Path I18NRepo::GetIniPath(const std::string &languageID) const {
146
return Path("lang") / (languageID + ".ini");
147
}
148
149
bool I18NRepo::IniExists(const std::string &languageID) const {
150
File::FileInfo info;
151
if (!g_VFS.Exists(GetIniPath(languageID).ToString().c_str()))
152
return false;
153
return true;
154
}
155
156
bool I18NRepo::LoadIni(const std::string &languageID, const Path &overridePath) {
157
IniFile ini;
158
Path iniPath;
159
160
// INFO_LOG(Log::UI, "Loading lang ini %s", iniPath.c_str());
161
if (!overridePath.empty()) {
162
iniPath = overridePath / (languageID + ".ini");
163
} else {
164
iniPath = GetIniPath(languageID);
165
}
166
167
if (!ini.LoadFromVFS(g_VFS, iniPath.ToString()))
168
return false;
169
170
Clear();
171
172
const std::vector<std::unique_ptr<Section>> &sections = ini.Sections();
173
174
std::lock_guard<std::mutex> guard(catsLock_);
175
for (auto &section : sections) {
176
for (size_t i = 0; i < (size_t)I18NCat::CATEGORY_COUNT; i++) {
177
if (!strcmp(section->name().c_str(), g_categoryNames[i])) {
178
cats_[i].reset(new I18NCategory(*section.get()));
179
}
180
}
181
}
182
183
languageID_ = languageID;
184
return true;
185
}
186
187
void I18NRepo::LogMissingKeys() const {
188
std::lock_guard<std::mutex> guard(catsLock_);
189
for (size_t i = 0; i < (size_t)I18NCat::CATEGORY_COUNT; i++) {
190
auto &cat = cats_[i];
191
for (auto &key : cat->Missed()) {
192
INFO_LOG(Log::UI, "Missing translation [%s]: %s (%s)", g_categoryNames[i], key.first.c_str(), key.second.c_str());
193
}
194
}
195
}
196
197
std::shared_ptr<I18NCategory> GetI18NCategory(I18NCat category) {
198
if (category == I18NCat::NONE) {
199
return std::shared_ptr<I18NCategory>();
200
}
201
std::shared_ptr<I18NCategory> cat = g_i18nrepo.GetCategory(category);
202
_dbg_assert_(cat);
203
return cat;
204
}
205
206