diff options
author | rtk0c <[email protected]> | 2021-03-31 20:19:18 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2021-03-31 20:19:18 -0700 |
commit | 44f5fa5c8f258e8fc1f7d7e2e45e0485bd6cc490 (patch) | |
tree | 3f09a1cce46d38f5a8c6266150e67af3802d4b95 /core/src/Utils/I18n.cpp | |
parent | 31950890c939862f79c817053c106bf711c63a64 (diff) |
Complete items tab (UI and serialization)
Diffstat (limited to 'core/src/Utils/I18n.cpp')
-rw-r--r-- | core/src/Utils/I18n.cpp | 520 |
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; +} |