ppsspp/Common/Data/Text/I18n.cpp
Henrik Rydgård d82ecf1d3e IniFile: Store sections in unique_ptrs, instead of directly.
This fixes an issue when you create two sections consecutively and
retain pointers to them, and then modify them, such as happens in the
postshader ini initialization. Previously, one of the section pointers
could get invalidated since the section vector got resized. Now, the
pointed-to sections don't move around in memory, only the list of them
does.
2023-08-13 13:41:43 +02:00

175 lines
4 KiB
C++

#include <cstring>
#include "Common/Data/Text/I18n.h"
#include "Common/Data/Format/IniFile.h"
#include "Common/File/VFS/VFS.h"
#include "Common/Log.h"
#include "Common/StringUtils.h"
static const char * const g_categoryNames[(size_t)I18NCat::CATEGORY_COUNT] = {
"Audio",
"Controls",
"CwCheats",
"DesktopUI",
"Developer",
"Dialog",
"Error",
"Game",
"Graphics",
"InstallZip",
"KeyMapping",
"MainMenu",
"MainSettings",
"MappableControls",
"Networking",
"Pause",
"PostShaders",
"PSPCredits",
"MemStick",
"RemoteISO",
"Reporting",
"Savedata",
"Screen",
"Search",
"Store",
"SysInfo",
"System",
"TextureShaders",
"Themes",
"UI Elements",
"Upgrade",
"VR",
"Achievements",
"PSPSettings",
};
I18NRepo g_i18nrepo;
std::string I18NRepo::LanguageID() {
return languageID_;
}
I18NRepo::I18NRepo() {
Clear();
}
void I18NRepo::Clear() {
std::lock_guard<std::mutex> guard(catsLock_);
for (auto &iter : cats_) {
// Initialize with empty categories, so that early lookups don't crash.
iter = std::shared_ptr<I18NCategory>(new I18NCategory());
}
}
I18NCategory::I18NCategory(const Section &section) {
std::map<std::string, std::string> sectionMap = section.ToMap();
SetMap(sectionMap);
}
void I18NCategory::Clear() {
map_.clear();
missedKeyLog_.clear();
}
const char *I18NCategory::T(const char *key, const char *def) {
if (!key) {
return "ERROR";
}
// Replace the \n's with \\n's so that key values with newlines will be found correctly.
std::string modifiedKey = key;
modifiedKey = ReplaceAll(modifiedKey, "\n", "\\n");
auto iter = map_.find(modifiedKey);
if (iter != map_.end()) {
return iter->second.text.c_str();
} else {
std::lock_guard<std::mutex> guard(missedKeyLock_);
if (def)
missedKeyLog_[key] = def;
else
missedKeyLog_[key] = modifiedKey;
return def ? def : key;
}
}
void I18NCategory::SetMap(const std::map<std::string, std::string> &m) {
for (auto iter = m.begin(); iter != m.end(); ++iter) {
if (map_.find(iter->first) == map_.end()) {
std::string text = ReplaceAll(iter->second, "\\n", "\n");
map_[iter->first] = I18NEntry(text);
}
}
}
std::shared_ptr<I18NCategory> I18NRepo::GetCategory(I18NCat category) {
std::lock_guard<std::mutex> guard(catsLock_);
if (category != I18NCat::NONE)
return cats_[(size_t)category];
else
return nullptr;
}
Path I18NRepo::GetIniPath(const std::string &languageID) const {
return Path("lang") / (languageID + ".ini");
}
bool I18NRepo::IniExists(const std::string &languageID) const {
File::FileInfo info;
if (!g_VFS.GetFileInfo(GetIniPath(languageID).ToString().c_str(), &info))
return false;
if (!info.exists)
return false;
return true;
}
bool I18NRepo::LoadIni(const std::string &languageID, const Path &overridePath) {
IniFile ini;
Path iniPath;
// INFO_LOG(SYSTEM, "Loading lang ini %s", iniPath.c_str());
if (!overridePath.empty()) {
iniPath = overridePath / (languageID + ".ini");
} else {
iniPath = GetIniPath(languageID);
}
if (!ini.LoadFromVFS(g_VFS, iniPath.ToString()))
return false;
Clear();
const std::vector<std::unique_ptr<Section>> &sections = ini.Sections();
std::lock_guard<std::mutex> guard(catsLock_);
for (auto &section : sections) {
for (size_t i = 0; i < (size_t)I18NCat::CATEGORY_COUNT; i++) {
if (!strcmp(section->name().c_str(), g_categoryNames[i])) {
cats_[i].reset(new I18NCategory(*section.get()));
}
}
}
languageID_ = languageID;
return true;
}
void I18NRepo::LogMissingKeys() const {
std::lock_guard<std::mutex> guard(catsLock_);
for (size_t i = 0; i < (size_t)I18NCat::CATEGORY_COUNT; i++) {
auto &cat = cats_[i];
for (auto &key : cat->Missed()) {
INFO_LOG(SYSTEM, "Missing translation [%s]: %s (%s)", g_categoryNames[i], key.first.c_str(), key.second.c_str());
}
}
}
std::shared_ptr<I18NCategory> GetI18NCategory(I18NCat category) {
if (category == I18NCat::NONE) {
return std::shared_ptr<I18NCategory>();
}
std::shared_ptr<I18NCategory> cat = g_i18nrepo.GetCategory(category);
_dbg_assert_(cat);
return cat;
}