diff options
author | rtk0c <[email protected]> | 2021-06-11 13:35:35 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2021-06-11 13:35:35 -0700 |
commit | 8f7daa9bd100345d7e23639604c9a3a50ce6448b (patch) | |
tree | 4b0c4934f29dfca933e1e955a8af2e61c2719ca1 /core/src/Utils | |
parent | 222cfec6ad882196d8927f73e30d905daae89556 (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.cpp | 300 | ||||
-rw-r--r-- | core/src/Utils/I18n.hpp | 91 | ||||
-rw-r--r-- | core/src/Utils/Macros.hpp | 5 | ||||
-rw-r--r-- | core/src/Utils/fwd.hpp | 9 |
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> |