feat(locale): load every locale in locale.xml on request

Locale::LoadFromFile can keep the other locales' phrases too, and
GetPhrase(id, locale) answers in one of them, falling back to the default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-29 20:42:01 -05:00
parent 8cbae8bb22
commit 40d9364770
2 changed files with 52 additions and 10 deletions

View File

@@ -3,12 +3,20 @@
#include "Logger.h"
namespace {
std::unordered_map<std::string, std::string> g_Phrases;
std::unordered_map<std::string, std::string> g_Phrases; // the default locale's
// The other locales' phrases (only loaded with allLocales), by locale
std::unordered_map<std::string, std::unordered_map<std::string, std::string>> g_Others;
std::vector<std::string> g_Locales;
bool g_Loaded = false;
const std::string g_Empty;
}
void Locale::LoadFromFile(const std::string& filepath, const std::string& locale) {
void Locale::LoadFromFile(const std::string& filepath, const std::string& locale, bool allLocales) {
g_Phrases.clear();
g_Others.clear();
g_Locales.clear();
g_Loaded = false;
tinyxml2::XMLDocument doc;
if (doc.LoadFile(filepath.c_str()) != tinyxml2::XML_SUCCESS) {
LOG("Failed to load locale file: %s", filepath.c_str());
@@ -18,6 +26,18 @@ void Locale::LoadFromFile(const std::string& filepath, const std::string& locale
auto* root = doc.FirstChildElement("localization");
if (!root) return;
g_Locales.push_back(locale);
if (allLocales) {
if (auto* locales = root->FirstChildElement("locales")) {
for (auto* entry = locales->FirstChildElement("locale"); entry; entry = entry->NextSiblingElement("locale")) {
const char* name = entry->GetText();
if (!name || locale == name || g_Others.contains(name)) continue;
g_Locales.push_back(name);
g_Others[name];
}
}
}
auto* phrases = root->FirstChildElement("phrases");
if (!phrases) return;
@@ -27,18 +47,20 @@ void Locale::LoadFromFile(const std::string& filepath, const std::string& locale
for (auto* trans = phrase->FirstChildElement("translation"); trans; trans = trans->NextSiblingElement("translation")) {
const char* loc = trans->Attribute("locale");
if (loc && locale == loc) {
const char* text = trans->GetText();
if (text) {
g_Phrases[id] = text;
}
break;
const char* text = trans->GetText();
if (!loc || !text) continue;
if (locale == loc) {
g_Phrases[id] = text;
if (!allLocales) break;
} else if (allLocales) {
const auto other = g_Others.find(loc);
if (other != g_Others.end()) other->second[id] = text;
}
}
}
g_Loaded = true;
LOG("Loaded %zu locale phrases from %s (locale: %s)", g_Phrases.size(), filepath.c_str(), locale.c_str());
LOG("Loaded %zu locale phrases from %s (locale: %s, %zu other locale(s))", g_Phrases.size(), filepath.c_str(), locale.c_str(), g_Others.size());
}
const std::string& Locale::GetPhrase(const std::string& phraseId) {
@@ -47,6 +69,17 @@ const std::string& Locale::GetPhrase(const std::string& phraseId) {
return g_Empty;
}
const std::string& Locale::GetPhrase(const std::string& phraseId, const std::string& locale) {
if (const auto other = g_Others.find(locale); other != g_Others.end()) {
if (const auto it = other->second.find(phraseId); it != other->second.end()) return it->second;
}
return GetPhrase(phraseId);
}
const std::vector<std::string>& Locale::GetLocales() {
return g_Locales;
}
bool Locale::IsLoaded() {
return g_Loaded;
}