summaryrefslogtreecommitdiff
path: root/core/src/Utils
diff options
context:
space:
mode:
authorrtk0c <[email protected]>2021-06-11 13:35:35 -0700
committerrtk0c <[email protected]>2021-06-11 13:35:35 -0700
commit8f7daa9bd100345d7e23639604c9a3a50ce6448b (patch)
tree4b0c4934f29dfca933e1e955a8af2e61c2719ca1 /core/src/Utils
parent222cfec6ad882196d8927f73e30d905daae89556 (diff)
Convert runtime-loaded l10n to string literals chosen at compile time
Diffstat (limited to 'core/src/Utils')
-rw-r--r--core/src/Utils/I18n.cpp300
-rw-r--r--core/src/Utils/I18n.hpp91
-rw-r--r--core/src/Utils/Macros.hpp5
-rw-r--r--core/src/Utils/fwd.hpp9
4 files changed, 12 insertions, 393 deletions
diff --git a/core/src/Utils/I18n.cpp b/core/src/Utils/I18n.cpp
deleted file mode 100644
index e5131cc..0000000
--- a/core/src/Utils/I18n.cpp
+++ /dev/null
@@ -1,300 +0,0 @@
-#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;
-}
diff --git a/core/src/Utils/I18n.hpp b/core/src/Utils/I18n.hpp
index 6285d60..e9eaac9 100644
--- a/core/src/Utils/I18n.hpp
+++ b/core/src/Utils/I18n.hpp
@@ -1,87 +1,10 @@
#pragma once
-#include "Utils/Sigslot.hpp"
-#include "Utils/fwd.hpp"
+#include "Utils/Macros.hpp"
-#include <cstddef>
-#include <optional>
-#include <span>
-#include <string>
-#include <string_view>
-#include <variant>
-#include <vector>
-
-class I18n
-{
-public:
- static inline Signal<> OnLanguageChange{};
- static inline Signal<> OnUnload{};
-
- static void Init();
- static void Shutdown();
-
- /// Discard in-memory mapping from key to locale entries.
- /// When any of the entry accessors are invoked, unloaded entries will be reloaded.
- static void Unload();
-
- static std::string_view GetLanguage();
- static bool SetLanguage(std::string_view lang);
-
- /* Entry accessors */
- /// Find the localized entry with the given key, return \c std::nullopt if does not exist. Reloads locale entries if they are currently unloaded.
- static std::optional<std::string_view> Lookup(std::string_view key);
- /// Find the localized entry with the given key, throw an exception if does not exist. EnsureLoaded locale entries if they are currently unloaded.
- static std::string_view LookupUnwrap(std::string_view key);
-
- /// Query the localized name of a locale, e.g. "en_US" -> "English - United States".
- /// If the queried locale does not exist, an empty string will be returned (existing locales can never have an empty localized name).
- static std::string_view LookupLanguage(std::string_view lang);
-};
-
-struct StringArgument
-{
- std::string Value;
-};
-
-struct IntArgument
-{
- int Value;
-};
-
-struct FloatArgument
-{
- double Value;
-};
-
-class BasicTranslation
-{
-private:
- std::string mContent;
-
-public:
- BasicTranslation(std::string_view key);
- const std::string& GetString() const;
- const char* Get() const;
-};
-
-class FormattedTranslation
-{
-public:
- using Element = std::variant<std::string, int>;
- using Argument = std::string;
-
-private:
- std::vector<Element> mParsedElements;
- size_t mNumArguments;
- size_t mMinimumResultLen;
-
-public:
- FormattedTranslation(std::string_view key);
- std::string Format(std::span<Argument> args);
-};
-
-class NumericTranslation
-{
-public:
- // TODO
-};
+#if !defined(TARGET_LOCALE)
+# define I18N_TEXT(defaultText, name) defaultText
+#else
+# include TARGET_LOCALE_FILE
+# define I18N_TEXT(defaultText, name) name
+#endif
diff --git a/core/src/Utils/Macros.hpp b/core/src/Utils/Macros.hpp
index 68b93fb..6958ed1 100644
--- a/core/src/Utils/Macros.hpp
+++ b/core/src/Utils/Macros.hpp
@@ -1,7 +1,12 @@
#pragma once
+#define STRINGIFY_IMPL(text) #text
+#define STRINGIFY(text) STRINGIFY_IMPL(text)
+
#define CONCAT_IMPL(a, b) a##b
#define CONCAT(a, b) CONCAT_IMPL(a, b)
+#define CONCAT_3(a, b, c) CONCAT(a, CONCAT(b, c))
+#define CONCAT_4(a, b, c, d) CONCAT(CONCAT(a, b), CONCAT(c, d))
#define UNIQUE_NAME(prefix) CONCAT(prefix, __COUNTER__)
#define UNIQUE_NAME_LINE(prefix) CONCAT(prefix, __LINE__)
diff --git a/core/src/Utils/fwd.hpp b/core/src/Utils/fwd.hpp
index 74e642d..5d1fe45 100644
--- a/core/src/Utils/fwd.hpp
+++ b/core/src/Utils/fwd.hpp
@@ -4,15 +4,6 @@
class RgbaColor;
class HsvColor;
-// I18n.hpp
-class I18n;
-struct StringArgument;
-struct IntArgument;
-struct FloatArgument;
-class BasicTranslation;
-class FormattedTranslation;
-class NumericTranslation;
-
// Sigslot.hpp
class SignalStub;
template <class... TArgs>