summaryrefslogtreecommitdiff
path: root/core/src/Utils/I18n.cpp
diff options
context:
space:
mode:
authorrtk0c <[email protected]>2021-03-31 20:19:18 -0700
committerrtk0c <[email protected]>2021-03-31 20:19:18 -0700
commit44f5fa5c8f258e8fc1f7d7e2e45e0485bd6cc490 (patch)
tree3f09a1cce46d38f5a8c6266150e67af3802d4b95 /core/src/Utils/I18n.cpp
parent31950890c939862f79c817053c106bf711c63a64 (diff)
Complete items tab (UI and serialization)
Diffstat (limited to 'core/src/Utils/I18n.cpp')
-rw-r--r--core/src/Utils/I18n.cpp520
1 files changed, 280 insertions, 240 deletions
diff --git a/core/src/Utils/I18n.cpp b/core/src/Utils/I18n.cpp
index 50edf67..edc5469 100644
--- a/core/src/Utils/I18n.cpp
+++ b/core/src/Utils/I18n.cpp
@@ -1,240 +1,280 @@
-#include "I18n.hpp"
-
-#include "Utils/String.hpp"
-
-#include <json/reader.h>
-#include <json/value.h>
-#include <tsl/array_map.h>
-#include <filesystem>
-#include <fstream>
-#include <stdexcept>
-#include <string_view>
-#include <utility>
-
-namespace fs = std::filesystem;
-namespace {
-
-struct LanguageInfo {
- std::string codeName;
- std::string localeName;
- fs::path file;
-};
-
-class I18nState {
-public:
- static I18nState& Get() {
- static I18nState instance;
- return instance;
- }
-
-public:
- tsl::array_map<char, LanguageInfo> LocaleInfos;
- tsl::array_map<char, std::string> CurrentEntries;
- LanguageInfo* CurrentLanguage = nullptr;
-};
-
-std::string FindLocalizedName(const fs::path& localeFile) {
- std::ifstream ifs{ localeFile };
- if (!ifs) {
- throw std::runtime_error("Failed to open locale file.");
- }
-
- Json::Value root;
- ifs >> root;
- if (auto& name = root["$localized_name"]; name.isString()) {
- return std::string(name.asCString());
- } else {
- throw std::runtime_error("Failed to find $localized_name in language file.");
- }
-}
-
-} // namespace
-
-void I18n::Init() {
- auto& state = I18nState::Get();
-
- auto dir = fs::current_path() / "locale";
- if (!fs::exists(dir)) {
- throw std::runtime_error("Failed to find locale directory.");
- }
-
- for (auto& elm : fs::directory_iterator{ dir }) {
- if (!elm.is_regular_file()) continue;
-
- auto& path = elm.path();
- auto codeName = path.stem().string();
-
- state.LocaleInfos.emplace(
- codeName,
- LanguageInfo{
- .codeName = codeName,
- .localeName = FindLocalizedName(path),
- .file = path,
- });
- }
-}
-
-void I18n::Shutdown() {
- // Nothing to do yet
-}
-
-Signal<> I18n::OnReload{};
-
-void I18n::ReloadLocales() {
- auto& state = I18nState::Get();
- OnReload();
-}
-
-std::string_view I18n::GetLanguage() {
- auto& state = I18nState::Get();
- return state.CurrentLanguage->localeName;
-}
-
-bool I18n::SetLanguage(std::string_view lang) {
- auto& state = I18nState::Get();
- if (state.CurrentLanguage &&
- state.CurrentLanguage->codeName == lang)
- {
- return false;
- }
-
- if (auto iter = state.LocaleInfos.find(lang); iter != state.LocaleInfos.end()) {
- state.CurrentLanguage = &iter.value();
- state.CurrentEntries.clear();
-
- auto& file = state.CurrentLanguage->file;
- std::ifstream ifs{ file };
- Json::Value root;
- ifs >> root;
-
- for (auto name : root.getMemberNames()) {
- if (name == "$localized_name") {
- continue;
- }
-
- auto& value = root[name];
- if (value.isString()) {
- state.CurrentEntries.insert(name, value.asCString());
- }
- }
- }
- ReloadLocales();
- return true;
-}
-
-std::optional<std::string_view> I18n::Lookup(std::string_view key) {
- auto& state = I18nState::Get();
- auto iter = state.CurrentEntries.find(key);
- if (iter != state.CurrentEntries.end()) {
- return iter.value();
- } else {
- return std::nullopt;
- }
-}
-
-std::string_view I18n::LookupUnwrap(std::string_view key) {
- auto o = Lookup(key);
- if (!o) {
- std::string msg;
- msg.append("Unable to find locale for '");
- msg.append(key);
- msg.append("'.");
- throw std::runtime_error(std::move(msg));
- };
- return o.value();
-}
-
-BasicTranslation::BasicTranslation(std::string_view key)
- // Assuming the string is null terminated, which it is here (because we store interally using std::string)
- // TODO properly use std::string_view when imgui supports it
- : mContent{ I18n::LookupUnwrap(key).data() } {
-}
-
-const char* BasicTranslation::Get() const {
- return mContent;
-}
-
-FormattedTranslation::FormattedTranslation(std::string_view key) {
- auto src = I18n::LookupUnwrap(key);
-
- mMinimumResultLen = 0;
-
- bool escape = false;
- bool matchingCloseBrace = false;
- std::string buf;
- for (char c : src) {
- switch (c) {
- case '\\': {
- // Disallow double (or more) escaping
- if (escape) throw std::runtime_error("Cannot escape '\\'.");
-
- escape = true;
- continue;
- }
-
- case '{': {
- // Escaping an opeing brace cause the whole "argument" (if any) gets parsed as a part of the previous literal
- if (escape) {
- buf += '{';
- break;
- }
-
- // Generate literal
- mMinimumResultLen += buf.size();
- mParsedElements.push_back(Element{ std::move(buf) }); // Should also clear buf
-
- matchingCloseBrace = true;
- } break;
- case '}': {
- if (escape) throw std::runtime_error("Cannot escape '}', put \\ before the '{' if intended to escape braces.");
-
- // If there is no pairing '{', simply treat this as a normal character
- // (escaping for closing braces)
- if (!matchingCloseBrace) {
- buf += '}';
- break;
- }
-
- // Generate argument
- if (buf.empty()) {
- // No index given, default to use current argument's index
- auto currArgIdx = (int)mNumArguments;
- mParsedElements.push_back(Element{ currArgIdx });
- } else {
- // Use provided index
- int argIdx = std::stoi(buf);
- mParsedElements.push_back(Element{ argIdx });
- buf.clear();
- }
- } break;
-
- default: {
- if (escape) throw std::runtime_error("Cannot escape normal character '" + std::to_string(c) + "'.");
-
- buf += c;
- } break;
- }
-
- escape = false;
- }
-}
-
-std::string FormattedTranslation::Format(std::span<Argument> args) {
- if (args.size() != mNumArguments) {
- throw std::runtime_error("Invalid number of arguments for FormattedTranslation::Format, expected " + std::to_string(mNumArguments) + " but found " + std::to_string(args.size()) + ".");
- }
-
- std::string result;
- result.reserve(mMinimumResultLen);
-
- for (auto& elm : mParsedElements) {
- if (auto literal = std::get_if<std::string>(&elm)) {
- result.append(*literal);
- }
- if (auto idx = std::get_if<int>(&elm)) {
- result.append(args[*idx]);
- }
- }
-
- return result;
-}
+#include "I18n.hpp"
+
+#include <json/reader.h>
+#include <json/value.h>
+#include <tsl/array_map.h>
+#include <filesystem>
+#include <fstream>
+#include <stdexcept>
+#include <string_view>
+#include <utility>
+
+namespace fs = std::filesystem;
+using namespace std::literals::string_view_literals;
+
+namespace {
+
+struct LanguageInfo {
+ std::string CodeName;
+ std::string LocaleName;
+ fs::path File;
+};
+
+class I18nState {
+public:
+ static I18nState& Get() {
+ static I18nState instance;
+ return instance;
+ }
+
+public:
+ tsl::array_map<char, LanguageInfo> LocaleInfos;
+ tsl::array_map<char, std::string> CurrentEntries;
+ LanguageInfo* CurrentLanguage = nullptr;
+ bool Unloaded = false;
+
+ void Unload() {
+ Unloaded = true;
+ CurrentEntries = {};
+ }
+
+ void EnsureLoaded() {
+ if (Unloaded) {
+ Unloaded = false;
+ Reload();
+ }
+ }
+
+ void Reload() {
+ if (!CurrentLanguage) return;
+
+ std::ifstream ifs(CurrentLanguage->File);
+ Json::Value root;
+ ifs >> root;
+
+ for (auto name : root.getMemberNames()) {
+ if (name == "$localized_name") {
+ continue;
+ }
+
+ auto& value = root[name];
+ if (value.isString()) {
+ CurrentEntries.insert(name, value.asCString());
+ }
+ }
+ }
+};
+
+std::string FindLocalizedName(const fs::path& localeFile) {
+ std::ifstream ifs(localeFile);
+ if (!ifs) {
+ throw std::runtime_error("Failed to open locale file.");
+ }
+
+ Json::Value root;
+ ifs >> root;
+ if (auto& name = root["$localized_name"]; name.isString()) {
+ return std::string(name.asCString());
+ } else {
+ throw std::runtime_error("Failed to find $localized_name in language file.");
+ }
+}
+
+} // namespace
+
+void I18n::Init() {
+ auto& state = I18nState::Get();
+
+ auto dir = fs::current_path() / "locale";
+ if (!fs::exists(dir)) {
+ throw std::runtime_error("Failed to find locale directory.");
+ }
+
+ for (auto& elm : fs::directory_iterator{ dir }) {
+ if (!elm.is_regular_file()) continue;
+
+ auto& path = elm.path();
+ auto codeName = path.stem().string();
+
+ state.LocaleInfos.emplace(
+ codeName,
+ LanguageInfo{
+ .CodeName = codeName,
+ .LocaleName = FindLocalizedName(path),
+ .File = path,
+ });
+ }
+}
+
+void I18n::Shutdown() {
+ auto& state = I18nState::Get();
+ state.LocaleInfos.clear();
+ state.CurrentEntries.clear();
+ state.CurrentLanguage = nullptr;
+ state.Unloaded = false;
+}
+
+void I18n::Unload() {
+ auto& state = I18nState::Get();
+ state.Unload();
+ OnUnload();
+}
+
+std::string_view I18n::GetLanguage() {
+ auto& state = I18nState::Get();
+ return state.CurrentLanguage->CodeName;
+}
+
+bool I18n::SetLanguage(std::string_view lang) {
+ auto& state = I18nState::Get();
+ if (state.CurrentLanguage &&
+ state.CurrentLanguage->CodeName == lang)
+ {
+ return false;
+ }
+
+ if (auto iter = state.LocaleInfos.find(lang); iter != state.LocaleInfos.end()) {
+ state.CurrentLanguage = &iter.value();
+ state.Reload();
+ }
+
+ OnLanguageChange();
+ return true;
+}
+
+std::optional<std::string_view> I18n::Lookup(std::string_view key) {
+ auto& state = I18nState::Get();
+ state.EnsureLoaded();
+
+ auto iter = state.CurrentEntries.find(key);
+ if (iter != state.CurrentEntries.end()) {
+ return iter.value();
+ } else {
+ return std::nullopt;
+ }
+}
+
+std::string_view I18n::LookupUnwrap(std::string_view key) {
+ auto o = Lookup(key);
+ if (!o) {
+ std::string msg;
+ msg.append("Unable to find locale for '");
+ msg.append(key);
+ msg.append("'.");
+ throw std::runtime_error(std::move(msg));
+ };
+ return o.value();
+}
+
+std::string_view I18n::LookupLanguage(std::string_view lang) {
+ auto& state = I18nState::Get();
+ auto iter = state.LocaleInfos.find(lang);
+ if (iter != state.LocaleInfos.end()) {
+ return iter.value().LocaleName;
+ } else {
+ return ""sv;
+ }
+}
+
+BasicTranslation::BasicTranslation(std::string_view key)
+ : mContent{ I18n::LookupUnwrap(key) } {
+}
+
+const std::string& BasicTranslation::GetString() const {
+ return mContent;
+}
+
+const char* BasicTranslation::Get() const {
+ return mContent.c_str();
+}
+
+FormattedTranslation::FormattedTranslation(std::string_view key) {
+ auto src = I18n::LookupUnwrap(key);
+
+ mMinimumResultLen = 0;
+
+ bool escape = false;
+ bool matchingCloseBrace = false;
+ std::string buf;
+ for (char c : src) {
+ switch (c) {
+ case '\\': {
+ // Disallow double (or more) escaping
+ if (escape) {
+ buf += '\\';
+ escape = false;
+ break;
+ }
+
+ escape = true;
+ } break;
+
+ case '{': {
+ // Escaping an opening brace cause the whole "argument" (if any) gets parsed as a part of the previous literal
+ if (escape) {
+ buf += '{';
+ escape = false;
+ break;
+ }
+
+ // Generate literal
+ mMinimumResultLen += buf.size();
+ mParsedElements.push_back(Element{ std::move(buf) }); // Should also clear buf
+
+ matchingCloseBrace = true;
+ } break;
+ case '}': {
+ if (escape) {
+ throw std::runtime_error("Cannot escape '}', put \\ before the '{' if intended to escape braces.");
+ }
+
+ // If there is no pairing '{', simply treat this as a normal character
+ // (escaping for closing braces)
+ if (!matchingCloseBrace) {
+ buf += '}';
+ break;
+ }
+
+ // Generate argument
+ if (buf.empty()) {
+ // No index given, default to use current argument's index
+ auto currArgIdx = (int)mNumArguments;
+ mParsedElements.push_back(Element{ currArgIdx });
+ } else {
+ // Use provided index
+ int argIdx = std::stoi(buf);
+ mParsedElements.push_back(Element{ argIdx });
+ buf.clear();
+ }
+ } break;
+
+ default: {
+ if (escape) {
+ throw std::runtime_error("Cannot escape normal character '" + std::to_string(c) + "'.");
+ }
+
+ buf += c;
+ } break;
+ }
+ }
+}
+
+std::string FormattedTranslation::Format(std::span<Argument> args) {
+ if (args.size() != mNumArguments) {
+ throw std::runtime_error("Invalid number of arguments for FormattedTranslation::Format, expected " + std::to_string(mNumArguments) + " but found " + std::to_string(args.size()) + ".");
+ }
+
+ std::string result;
+ result.reserve(mMinimumResultLen);
+
+ for (auto& elm : mParsedElements) {
+ if (auto literal = std::get_if<std::string>(&elm)) {
+ result.append(*literal);
+ }
+ if (auto idx = std::get_if<int>(&elm)) {
+ result.append(args[*idx]);
+ }
+ }
+
+ return result;
+}