Skip to content

Commit

Permalink
I18N: Switch to getting categories by index instead of by string lookup
Browse files Browse the repository at this point in the history
Also gets rid of the shared_ptr usage, and generally makes things nicer.

Needed for later config refactorings, good to get in early.
  • Loading branch information
hrydgard committed Apr 7, 2023
1 parent 07b8b6f commit ee6234e
Show file tree
Hide file tree
Showing 65 changed files with 534 additions and 515 deletions.
127 changes: 67 additions & 60 deletions Common/Data/Text/I18n.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#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"

Expand All @@ -14,12 +15,52 @@ std::string I18NRepo::LanguageID() {
return languageID_;
}

I18NRepo::I18NRepo() {
cats_[(size_t)I18NCat::AUDIO].SetName("Audio");
cats_[(size_t)I18NCat::CONTROLS].SetName("Controls");
cats_[(size_t)I18NCat::SYSTEM].SetName("System");
cats_[(size_t)I18NCat::CWCHEATS].SetName("CwCheats");
cats_[(size_t)I18NCat::DESKTOPUI].SetName("DesktopUI");
cats_[(size_t)I18NCat::DEVELOPER].SetName("Developer");
cats_[(size_t)I18NCat::DIALOG].SetName("Dialog");
cats_[(size_t)I18NCat::ERRORS].SetName("Error");
cats_[(size_t)I18NCat::GAME].SetName("Game");
cats_[(size_t)I18NCat::GRAPHICS].SetName("Graphics");
cats_[(size_t)I18NCat::INSTALLZIP].SetName("InstallZip");
cats_[(size_t)I18NCat::KEYMAPPING].SetName("KeyMapping");
cats_[(size_t)I18NCat::MAINMENU].SetName("MainMenu");
cats_[(size_t)I18NCat::MAINSETTINGS].SetName("MainSettings");
cats_[(size_t)I18NCat::MAPPABLECONTROLS].SetName("MappableControls");
cats_[(size_t)I18NCat::NETWORKING].SetName("Networking");
cats_[(size_t)I18NCat::PAUSE].SetName("Pause");
cats_[(size_t)I18NCat::POSTSHADERS].SetName("PostShaders");
cats_[(size_t)I18NCat::PSPCREDITS].SetName("PSPCredits");
cats_[(size_t)I18NCat::MEMSTICK].SetName("MemStick");
cats_[(size_t)I18NCat::REMOTEISO].SetName("RemoteISO");
cats_[(size_t)I18NCat::REPORTING].SetName("Reporting");
cats_[(size_t)I18NCat::SAVEDATA].SetName("Savedata");
cats_[(size_t)I18NCat::SCREEN].SetName("Screen");
cats_[(size_t)I18NCat::SEARCH].SetName("Search");
cats_[(size_t)I18NCat::STORE].SetName("Store");
cats_[(size_t)I18NCat::SYSINFO].SetName("SysInfo");
cats_[(size_t)I18NCat::SYSTEM].SetName("System");
cats_[(size_t)I18NCat::TEXTURESHADERS].SetName("TextureShaders");
cats_[(size_t)I18NCat::THEMES].SetName("Themes");
cats_[(size_t)I18NCat::UI_ELEMENTS].SetName("UI Elements");
cats_[(size_t)I18NCat::UPGRADE].SetName("Upgrade");
cats_[(size_t)I18NCat::VR].SetName("VR");
}

void I18NRepo::Clear() {
std::lock_guard<std::mutex> guard(catsLock_);
for (auto iter = cats_.begin(); iter != cats_.end(); ++iter) {
iter->second.reset();
for (auto &iter : cats_) {
iter.Clear();
}
cats_.clear();
}

void I18NCategory::Clear() {
map_.clear();
missedKeyLog_.clear();
}

const char *I18NCategory::T(const char *key, const char *def) {
Expand All @@ -32,15 +73,13 @@ const char *I18NCategory::T(const char *key, const char *def) {

auto iter = map_.find(modifiedKey);
if (iter != map_.end()) {
// INFO_LOG(SYSTEM, "translation key found in %s: %s", name_.c_str(), key);
return iter->second.text.c_str();
} else {
std::lock_guard<std::mutex> guard(missedKeyLock_);
if (def)
missedKeyLog_[key] = def;
else
missedKeyLog_[key] = modifiedKey;
// INFO_LOG(SYSTEM, "Missed translation key in %s: %s", name_.c_str(), key);
return def ? def : key;
}
}
Expand All @@ -50,21 +89,25 @@ void I18NCategory::SetMap(const std::map<std::string, std::string> &m) {
if (map_.find(iter->first) == map_.end()) {
std::string text = ReplaceAll(iter->second, "\\n", "\n");
map_[iter->first] = I18NEntry(text);
// INFO_LOG(SYSTEM, "Language entry: %s -> %s", iter->first.c_str(), text.c_str());
}
}
}

std::shared_ptr<I18NCategory> I18NRepo::GetCategory(const char *category) {
I18NCategory *I18NRepo::GetCategory(I18NCat category) {
std::lock_guard<std::mutex> guard(catsLock_);
auto iter = cats_.find(category);
if (iter != cats_.end()) {
return iter->second;
} else {
I18NCategory *c = new I18NCategory(this, category);
cats_[category].reset(c);
return cats_[category];
if (category != I18NCat::NONE)
return &cats_[(size_t)category];
else
return nullptr;
}

I18NCategory *I18NRepo::GetCategoryByName(const char *name) {
for (auto &iter : cats_) {
if (!strcmp(iter.GetName(), name)) {
return &iter;
}
}
return nullptr;
}

Path I18NRepo::GetIniPath(const std::string &languageID) const {
Expand Down Expand Up @@ -99,63 +142,27 @@ bool I18NRepo::LoadIni(const std::string &languageID, const Path &overridePath)
const std::vector<Section> &sections = ini.Sections();

std::lock_guard<std::mutex> guard(catsLock_);
for (auto iter = sections.begin(); iter != sections.end(); ++iter) {
if (iter->name() != "") {
cats_[iter->name()].reset(LoadSection(&(*iter), iter->name().c_str()));
for (auto &iter : sections) {
I18NCategory *cat = GetCategoryByName(iter.name().c_str());
if (cat) {
cat->LoadSection(iter);
}
}

languageID_ = languageID;
return true;
}

std::map<std::string, std::vector<std::string>> I18NRepo::GetMissingKeys() const {
std::map<std::string, std::vector<std::string>> ret;
void I18NRepo::LogMissingKeys() const {
std::lock_guard<std::mutex> guard(catsLock_);
for (auto &cat : cats_) {
for (auto &key : cat.second->Missed()) {
ret[cat.first].push_back(key.first);
for (auto &key : cat.Missed()) {
INFO_LOG(SYSTEM, "Missing translation [%s]: %s (%s)", cat.GetName(), key.first.c_str(), key.second.c_str());
}
}
return ret;
}

I18NCategory *I18NRepo::LoadSection(const Section *section, const char *name) {
I18NCategory *cat = new I18NCategory(this, name);
std::map<std::string, std::string> sectionMap = section->ToMap();
cat->SetMap(sectionMap);
return cat;
}

// This is a very light touched save variant - it won't overwrite
// anything, only create new entries.
void I18NRepo::SaveIni(const std::string &languageID) {
IniFile ini;
ini.Load(GetIniPath(languageID));
std::lock_guard<std::mutex> guard(catsLock_);
for (auto iter = cats_.begin(); iter != cats_.end(); ++iter) {
std::string categoryName = iter->first;
Section *section = ini.GetOrCreateSection(categoryName.c_str());
SaveSection(ini, section, iter->second);
}
ini.Save(GetIniPath(languageID));
}

void I18NRepo::SaveSection(IniFile &ini, Section *section, std::shared_ptr<I18NCategory> cat) {
const std::map<std::string, std::string> &missed = cat->Missed();

for (auto iter = missed.begin(); iter != missed.end(); ++iter) {
if (!section->Exists(iter->first.c_str())) {
std::string text = ReplaceAll(iter->second, "\n", "\\n");
section->Set(iter->first, text);
}
}

const std::map<std::string, I18NEntry> &entries = cat->GetMap();
for (auto iter = entries.begin(); iter != entries.end(); ++iter) {
std::string text = ReplaceAll(iter->second.text, "\n", "\\n");
section->Set(iter->first, text);
}

cat->ClearMissed();
void I18NCategory::LoadSection(const Section &section) {
std::map<std::string, std::string> sectionMap = section.ToMap();
SetMap(sectionMap);
}
85 changes: 54 additions & 31 deletions Common/Data/Text/I18n.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
// As usual, everything is UTF-8. Nothing else allowed.

#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
Expand All @@ -23,6 +22,43 @@ class I18NRepo;
class IniFile;
class Section;

enum class I18NCat : uint8_t {
AUDIO = 0,
CONTROLS,
CWCHEATS,
DESKTOPUI,
DEVELOPER,
DIALOG,
ERRORS, // Can't name it ERROR, clashes with many defines.
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,
CATEGORY_COUNT,
NONE = CATEGORY_COUNT,
};

struct I18NEntry {
I18NEntry(const std::string &t) : text(t), readFlag(false) {}
I18NEntry() : readFlag(false) {}
Expand All @@ -39,9 +75,10 @@ struct I18NCandidate {

class I18NCategory {
public:
I18NCategory() {}
// NOTE: Name must be a global constant string - it is not copied.
I18NCategory(const char *name) : name_(name) {}
const char *T(const char *key, const char *def = 0);
explicit I18NCategory(const char *name) : name_(name) {}
const char *T(const char *key, const char *def = nullptr);
const char *T(const std::string &key) {
return T(key.c_str(), nullptr);
}
Expand All @@ -54,10 +91,13 @@ class I18NCategory {
const std::map<std::string, I18NEntry> &GetMap() { return map_; }
void ClearMissed() { missedKeyLog_.clear(); }
const char *GetName() const { return name_.c_str(); }
void Clear();

private:
I18NCategory(I18NRepo *repo, const char *name) : name_(name) {}
void SetName(const char *name) { name_ = name; }
void SetMap(const std::map<std::string, std::string> &m);
void LoadSection(const Section &section);

std::string name_;

Expand All @@ -67,62 +107,45 @@ class I18NCategory {

// Noone else can create these.
friend class I18NRepo;

DISALLOW_COPY_AND_ASSIGN(I18NCategory);
};

class I18NRepo {
public:
I18NRepo() {}
I18NRepo();
~I18NRepo();

bool IniExists(const std::string &languageID) const;
bool LoadIni(const std::string &languageID, const Path &overridePath = Path()); // NOT the filename!
void SaveIni(const std::string &languageID);

std::string LanguageID();

std::shared_ptr<I18NCategory> GetCategory(const char *categoryName);
bool HasCategory(const char *categoryName) const {
std::lock_guard<std::mutex> guard(catsLock_);
return cats_.find(categoryName) != cats_.end();
I18NCategory *GetCategory(I18NCat category);
I18NCategory *GetCategoryByName(const char *name);

const char *T(I18NCat category, const char *key, const char *def = nullptr) {
return cats_[(size_t)category].T(key, def);
}
const char *T(const char *category, const char *key, const char *def = 0);

std::map<std::string, std::vector<std::string>> GetMissingKeys() const;
void LogMissingKeys() const;

private:
Path GetIniPath(const std::string &languageID) const;
void Clear();
I18NCategory *LoadSection(const Section *section, const char *name);
void SaveSection(IniFile &ini, Section *section, std::shared_ptr<I18NCategory> cat);

mutable std::mutex catsLock_;
std::map<std::string, std::shared_ptr<I18NCategory>> cats_;
I18NCategory cats_[(size_t)I18NCat::CATEGORY_COUNT];
std::string languageID_;

DISALLOW_COPY_AND_ASSIGN(I18NRepo);
};

extern I18NRepo i18nrepo;

// These are simply talking to the one global instance of I18NRepo.

inline std::shared_ptr<I18NCategory> GetI18NCategory(const char *categoryName) {
if (!categoryName)
return nullptr;
return i18nrepo.GetCategory(categoryName);
}

inline bool I18NCategoryLoaded(const char *categoryName) {
return i18nrepo.HasCategory(categoryName);
inline I18NCategory *GetI18NCategory(I18NCat cat) {
return i18nrepo.GetCategory(cat);
}

inline const char *T(const char *category, const char *key, const char *def = 0) {
inline const char *T(I18NCat category, const char *key, const char *def = nullptr) {
return i18nrepo.T(category, key, def);
}

inline std::map<std::string, std::vector<std::string>> GetI18NMissingKeys() {
return i18nrepo.GetMissingKeys();
}

12 changes: 7 additions & 5 deletions Common/UI/PopupScreens.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,19 @@ UI::EventReturn ListPopupScreen::OnListChoice(UI::EventParams &e) {
return UI::EVENT_DONE;
}

PopupContextMenuScreen::PopupContextMenuScreen(const ContextMenuItem *items, size_t itemCount, I18NCategory *category, UI::View *sourceView)
PopupContextMenuScreen::PopupContextMenuScreen(const ContextMenuItem *items, size_t itemCount, I18NCat category, UI::View *sourceView)
: PopupScreen("", "", ""), items_(items), itemCount_(itemCount), category_(category), sourceView_(sourceView)
{
enabled_.resize(itemCount, true);
SetPopupOrigin(sourceView);
}

void PopupContextMenuScreen::CreatePopupContents(UI::ViewGroup *parent) {
auto category = GetI18NCategory(category_);

for (size_t i = 0; i < itemCount_; i++) {
if (items_[i].imageID) {
Choice *choice = new Choice(category_->T(items_[i].text), ImageID(items_[i].imageID));
Choice *choice = new Choice(category->T(items_[i].text), ImageID(items_[i].imageID));
parent->Add(choice);
if (enabled_[i]) {
choice->OnClick.Add([=](EventParams &p) {
Expand Down Expand Up @@ -90,7 +92,7 @@ std::string ChopTitle(const std::string &title) {
UI::EventReturn PopupMultiChoice::HandleClick(UI::EventParams &e) {
restoreFocus_ = HasFocus();

auto category = category_ ? GetI18NCategory(category_) : nullptr;
auto category = GetI18NCategory(category_);

std::vector<std::string> choices;
for (int i = 0; i < numChoices_; i++) {
Expand Down Expand Up @@ -289,7 +291,7 @@ void SliderPopupScreen::UpdateTextBox() {
void SliderPopupScreen::CreatePopupContents(UI::ViewGroup *parent) {
using namespace UI;
UIContext &dc = *screenManager()->getUIContext();
auto di = GetI18NCategory("Dialog");
auto di = GetI18NCategory(I18NCat::DIALOG);

sliderValue_ = *value_;
if (disabled_ && sliderValue_ < 0)
Expand Down Expand Up @@ -337,7 +339,7 @@ void SliderPopupScreen::CreatePopupContents(UI::ViewGroup *parent) {
void SliderFloatPopupScreen::CreatePopupContents(UI::ViewGroup *parent) {
using namespace UI;
UIContext &dc = *screenManager()->getUIContext();
auto di = GetI18NCategory("Dialog");
auto di = GetI18NCategory(I18NCat::DIALOG);

sliderValue_ = *value_;
LinearLayout *vert = parent->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(UI::Margins(10, 10))));
Expand Down
Loading

0 comments on commit ee6234e

Please sign in to comment.