LiteLoaderBDS-1.16.40/LiteLoader/Header/I18nAPI.h
2022-09-21 19:47:03 +08:00

506 lines
17 KiB
C++

#pragma once
//////////////////////////////////////////////////////
// For Internationalization
//
// [Usage - Translation]
//
// Translation::load("plugins/xxx/lang.json");
// ...
// tr("There are {0} days before {1} to come back", 3, "alex"); // return translated string [std::string]
// trc("There are {0} days before {1} to come back", 3, "alex"); // return translated string [const char*]
//
// ** In Translation File: plugins/xxx/lang.json
// {
// "zh_CN": {
// "There are {0} days before {1} to come back": "在{1}回来前还剩{0}天",
// "...": "...",
// "...": "..."
// }
// }
//
//
// [Usage - Text Encoding]
//
// Encoding local = TextEncoding::getLocalEncoding(); // Get local encoding
// Encoding code = TextEncoding::detectEncoding("你好吗?"); // Detect the encoding of text
//
// string hello = TextEncoding::fromUnicode(L"Hello"); // Convert Unicode wstring -> MBCS string
// wstring world = TextEncoding::toUnicode("World"); // Convert MBCS string -> Unicode wstring
//
// string tomorrow = TextEncoding::toUTF8("明天"); // Convert MBCS string of any encoding to UTF8 string
// string tonight = TextEncoding::convert("今天晚上", Encoding::CHINESE_GB, Encoding::UTF8);
// // Convert from one MBCS encoding to another
//
//////////////////////////////////////////////////////
#include "Global.h"
#include "LLAPI.h"
#include "Utils/FileHelper.h"
#include "Utils/PluginOwnData.h"
#include "third-party/Nlohmann/json.hpp"
#include "third-party/FMT/core.h"
#include "third-party/FMT/os.h"
#include <string>
#include "Utils/StringHelper.h"
/**
* @brief I18nBase API class.
*
*/
class I18nBase {
public:
using SubLangData = std::unordered_map<std::string, std::string>;
using LangData = std::map<std::string, SubLangData>;
enum class Type : char
{
None,
SingleFile,
MultiFile,
Custom,
};
Type type = Type::None;
LangData langData;
LangData defaultLangData;
std::string defaultLocaleName = "en_US";
virtual ~I18nBase() = default;
/**
* @brief Get the translation of the specified key.
*
* @param key The language key
* @param localeName The language code like en_US,zh_CN("" => this->defaultLocaleName)
* @return std::string The translation
* @see I18nBase::defaultLocaleName
*/
LIAPI virtual std::string get(const std::string& key, const std::string& localeName = "");
/**
* @breif Get the type of the i18n object.
*
* @return The type of the i18n object
*/
LIAPI virtual Type getType() = 0;
/**
* @brief Get the default language code of the i18n object.
*
* @return The default language code of the i18n object
*/
LIAPI virtual std::string getDefaultLocaleName();
/**
* @brief Clone a new i18n object.
*
* @return The new i18n object.
*/
LIAPI virtual I18nBase* clone();
static const constexpr char* POD_KEY = "ll_plugin_i18n"; ///< PluginOwnData key
};
/**
* @brief Lightweight and simple I18nBase support.
*
* @note Use this, all the language data will be saved in a single JSON file.
* So it is not recommended to use it in large plugins(that have a lot of strings to translate)
*/
class SingleFileI18N : public I18nBase {
public:
std::string filePath;
LIAPI void load(const std::string& fileName);
LIAPI void save();
SingleFileI18N() {
this->type = Type::SingleFile;
}
/**
* @brief Construct a SingleFileI18N object.
*
* @param filePath The path to the i18n file(json)
* @param pattern The i18n file pattern(SingleFile I18nBase supports `Mode::Normal` and `Mode::Normal`
* @param defaultLocaleName The default language code(if empty, default the system default language)
* @param defaultLangData The default translation data
*/
SingleFileI18N(const std::string& filePath, const std::string& defaultLocaleName = "",
const LangData& defaultLangData = {})
: filePath(filePath) {
this->type = Type::SingleFile;
this->defaultLangData = defaultLangData;
if (defaultLocaleName.empty()) {
this->defaultLocaleName = GetSystemLocaleName();
} else {
this->defaultLocaleName = defaultLocaleName;
}
load(filePath);
}
/// Copy constructor
SingleFileI18N(const SingleFileI18N& other) {
*this = other;
}
~SingleFileI18N() = default;
LIAPI Type getType();
};
class MultiFileI18N : public I18nBase {
public:
std::string dirPath;
LIAPI void load(const std::string& dirName);
LIAPI void save(bool nested = false);
MultiFileI18N() {
this->type = Type::MultiFile;
}
/**
* @brief Construct a heavy I18nBase object.
*
* @param dirPath The path to the i18n dir
* @param pattern The i18n file pattern
* @param defaultLocaleName The default language code
* @param defaultLangData The default translation data
*/
MultiFileI18N(const std::string& dirPath, const std::string& defaultLocaleName = "",
const LangData& defaultLangData = {})
: dirPath(dirPath) {
this->type = Type::MultiFile;
this->defaultLangData = defaultLangData;
if (defaultLocaleName.empty()) {
this->defaultLocaleName = GetSystemLocaleName();
} else {
this->defaultLocaleName = defaultLocaleName;
}
load(dirPath);
}
/// Copy constructor
MultiFileI18N(const MultiFileI18N& other) {
*this = other;
}
~MultiFileI18N() = default;
LIAPI Type getType();
};
#ifdef UNICODE
#include "third-party/compact_enc_det/compact_enc_det.h"
#define UNICODE
#else
#include "third-party/compact_enc_det/compact_enc_det.h"
#endif
namespace Translation {
template <bool B, class T = void>
using enable_if_t = typename std::enable_if<B, T>::type;
///////////////// tr Impl /////////////////
template <typename S, typename... Args, Translation::enable_if_t<(fmt::v8::detail::is_string<S>::value), int> = 0>
inline std::string trlImpl(HMODULE hPlugin, const std::string& localeName, const S& formatStr, Args&&... args) {
std::string realFormatStr = formatStr;
if (PluginOwnData::hasImpl(hPlugin, I18nBase::POD_KEY)) {
auto& i18n = PluginOwnData::getImpl<I18nBase>(hPlugin, I18nBase::POD_KEY);
realFormatStr = i18n.get(formatStr, localeName);
if (realFormatStr == formatStr) {
// If failed and the str dosn't match the args count, avoid fmt to avoid errors
auto argSz = sizeof...(args);
bool lastIsBracket = false;
size_t cnt = 0;
for (auto& c : formatStr) {
if (c == '{') {
if (lastIsBracket) {
cnt--;
lastIsBracket = false;
} else {
cnt++;
lastIsBracket = true;
continue;
}
}
if (lastIsBracket) {
lastIsBracket = false;
}
}
if (cnt != argSz) {
return formatStr;
}
}
}
// realFormatStr = FixCurlyBracket(realFormatStr);
if constexpr (0 == sizeof...(args)) {
// Avoid fmt if only one argument
return realFormatStr;
} else {
return fmt::format(realFormatStr, std::forward<Args>(args)...);
}
}
template <typename S, typename... Args, Translation::enable_if_t<(fmt::v8::detail::is_string<S>::value), int> = 0>
inline std::string trImpl(HMODULE hPlugin, const S& formatStr, Args&&... args) {
return trlImpl(hPlugin, "", formatStr, std::forward<Args>(args)...);
}
///////////////// trc Impl /////////////////
template <typename S, typename... Args, Translation::enable_if_t<(fmt::v8::detail::is_string<S>::value), int> = 0>
[[deprecated("Use trImpl(...).c_str() instead")]] inline const char* trcImpl(HMODULE hPlugin, const S& formatStr,
const Args&... args) {
std::string res = trImpl(hPlugin, formatStr, args...);
std::string name =
std::string(I18nBase::POD_KEY) + "_translation_" + fmt::v8::detail::to_string_view<S>(formatStr).data();
auto& str = PluginOwnData::setImpl<std::string>(hPlugin, name, res);
return str.c_str();
}
LIAPI I18nBase* loadI18nImpl(HMODULE hPlugin, const std::string& path, const std::string& defaultLocaleName,
const I18nBase::LangData& defaultLangData);
LIAPI I18nBase* loadFromImpl(HMODULE hPlugin, HMODULE hTarget);
/**
* @brief Load translation from a file or dir.
*
* @param path The path to the i18n file(json) or dir
* @param defaultLocaleName The default language code(if no lang code is specified, it will use this)
* @param defaultLangData The default translation data
* @return I18nBase* The pointer to the I18nBase object in PluginOwnData, null if failed
* @par Example
* 1. SingleFileI18N (1)
* @code
* // In the file plugins/xxx/language.json:
* // {"zh_CN": {"text": "文本"}, "en_US": {"text", "text"}}
* Translation::load("plugins/xxx/language.json");
* tr("text");
* @endcode
* 2. SingleFileI18N (2)
* @code
* // In the file plugins/xxx/language.json:
* // {"zh_CN": {"a.b.c.id.text": "文本"}, "en_US": {"a.b.c.id.text", "text"}}
* Translation::load("plugins/xxx/language.json");
* tr("a.b.c.d.id.text");
* @endcode
* 3. MultiFileI18N (1)
* @code
* // In the file plugins/xxx/LangPack/en.json:
* // {"text": "text"}
* // In the file plugins/xxx/LangPack/zh_CN.json:
* // {"text": "文本"}
* Translation::load("plugins/xxx/LangPack/");
* tr("text");
* @endcode
* 4. MultiFileI18N (2)
* @code
* // In the file plugins/xxx/LangPack/en.json:
* // {"a.b.c.d.text1": "text"}
* // In the file plugins/xxx/LangPack/zh_CN.json:
* // {"a.b.c.d.text1": "文本"}
* Translation::load("plugins/xxx/LangPack/");
* tr("a.b.c.d.text1");
* @endcode
* 5. MultiFileI18N Nested (3)
* @code
* // In the file plugins/xxx/LangPack/en.json:
* // {"a": {"b": {"c": {"d": {"text1": "text"}}}}}
* // In the file plugins/xxx/LangPack/zh_CN.json:
* // {"a": {"b": {"c": {"d": {"text1": "文本"}}}}}
* Translation::load("plugins/xxx/LangPack/");
* tr("a.b.c.d.text1");
* @endcode
*/
inline I18nBase* load(const std::string& path,
const std::string& defaultLocaleName = "",
const I18nBase::LangData& defaultLangData = {}) {
return loadI18nImpl(GetCurrentModule(), path, defaultLocaleName, defaultLangData);
}
/**
* Load i18n with custom i18n type.
*
* @param args... The args to pass to the i18n type constructor
* @return I18nBase* The pointer to the I18nBase object in PluginOwnData, null if failed
*/
template <typename T, typename... Args>
inline I18nBase* load(Args&&... args) {
try {
I18nBase* res = new T(std::forward<Args>(args)...);
return &PluginOwnData::setWithoutNewImpl<I18nBase>(GetCurrentModule(), I18nBase::POD_KEY, res);
} catch (...) {}
return nullptr;
}
/**
* @brief Load translation from another plugin.
*
* @param plugin The plugin name.
* @return I18nBase* The pointer to the I18nBase object in PluginOwnData, null if failed
*/
inline I18nBase* loadFrom(const std::string& plugin) {
if (LL::hasPlugin(plugin)) {
auto p = LL::getPlugin(plugin);
if (p) {
return loadFromImpl(GetCurrentModule(), p->handle);
}
}
return nullptr;
}
/**
* @brief Get the I18nBase object of a certain plugin.
*
* @param hPlugin The plugin handle(nullptr -> GetCurrentModule())
* @return I18nBase* The I18nBase pointer
*/
inline I18nBase* getI18N(HMODULE hPlugin = nullptr) {
auto handle = (hPlugin == nullptr ? GetCurrentModule() : hPlugin);
if (handle && PluginOwnData::hasImpl(handle, I18nBase::POD_KEY)) {
return &PluginOwnData::getImpl<I18nBase>(handle, I18nBase::POD_KEY);
}
return nullptr;
}
}; // namespace Translation
/**
* @brief Translate a str.
*
* @tparam S The string type
* @tparam Args ...
* @param formatStr The str to translate and format
* @param args The format arguments
* @return std::string The translated str
* @see fmt::format
* @see https://fmt.dev/latest/index.html
* @par Example
* @code
* tr(std::string("There are {0} days before {1} to come back"), 3, "alex");
* @endcode
*/
template <typename S, typename... Args, Translation::enable_if_t<(fmt::v8::detail::is_string<S>::value), int> = 0>
inline std::string tr(const S& formatStr, Args&&... args) {
return Translation::trImpl(GetCurrentModule(), formatStr, std::forward<Args>(args)...);
}
/**
* @brief Translate a str.
*
* @tparam Args ...
* @param formatStr The str to translate and format
* @param args The format arguments
* @return std::string The translated str
* @see fmt::format
* @see https://fmt.dev/latest/index.html
* @par Example
* @code
* tr("There are {0} days before {1} to come back", 3, "alex");
* @endcode
*/
template <typename... Args>
inline std::string tr(const char* formatStr, Args&&... args) {
return tr(std::string(formatStr), std::forward<Args>(args)...);
}
/**
* @brief Translate a str(c-style str).
*
* @tparam S The string type
* @tparam Args ...
* @param formatStr The str to translate and format
* @param args The format arguments
* @return const char* The translated str(c-style str)
* @see fmt::format
* @see https://fmt.dev/latest/index.html
* @par Example
* @code
* trc(std::string("There are {0} days before {1} to come back"), 3, "alex");
* @endcode
*/
template <typename S, typename... Args, Translation::enable_if_t<(fmt::v8::detail::is_string<S>::value), int> = 0>
inline const char* trc(const S& formatStr, Args&&... args) {
return Translation::trcImpl(GetCurrentModule(), formatStr, std::forward<Args>(args)...);
}
/**
* @brief Translate a str(c-style str).
*
* @tparam Args ...
* @param formatStr The str to translate and format
* @param args The format arguments
* @return const char* The translated str(c-style str)
* @see fmt::format
* @see https://fmt.dev/latest/index.html
* @par Example
* @code
* trc("There are {0} days before {1} to come back", 3, "alex");
* @endcode
*/
template <typename... Args>
inline const char* trc(const char* formatStr, Args&&... args) {
return trc(std::string(formatStr), std::forward<Args>(args)...);
}
/**
* @brief Translate a str to the specified language.
*
* @tparam S The string type
* @tparam Args ...
* @param localeName The language code like en_US
* @param formatStr The str to translate and format
* @param args The format arguments
* @return std::string The translated str
* @see fmt::format
* @see https://fmt.dev/latest/index.html
* @par Example
* @code
* trl("zh_CN", "There are {0} days before {1} to come back", 3, "alex");
* @endcode
*/
template <typename S, typename... Args>
inline std::string trl(const std::string& localeName, const S& formatStr, Args&&... args) {
return Translation::trlImpl(GetCurrentModule(), localeName, formatStr, std::forward<Args>(args)...);
}
/**
* @brief Translate a str to the specified language.
*
* @tparam Args ...
* @param localeName The language code like en_US
* @param formatStr The str to translate and format(c-style)
* @param args The format arguments
* @return std::string The translated str
* @see fmt::format
* @see https://fmt.dev/latest/index.html
* @par Example
* @code
* trl("zh_CN", "There are {0} days before {1} to come back", 3, "alex");
* @endcode
*/
template <typename... Args>
inline std::string trl(const std::string& localeName, const char* formatStr, Args&&... args) {
return trl(localeName, std::string(formatStr), std::forward<Args>(args)...);
}
namespace Translation {
namespace literals {
inline std::string operator""_tr(const char* str, size_t) {
return tr(str);
}
} // namespace literals
} // namespace Translation
// For text encoding
namespace TextEncoding {
LIAPI Encoding getLocalEncoding();
LIAPI Encoding detectEncoding(const std::string& text, bool* isReliable = nullptr);
LIAPI std::string fromUnicode(const std::wstring& text, Encoding to = Encoding::UTF8);
LIAPI std::wstring toUnicode(const std::string& text, Encoding from = Encoding::UTF8);
LIAPI std::string toUTF8(const std::string& text);
LIAPI std::string toUTF8(const std::string& text, Encoding from);
LIAPI std::string convert(const std::string& text, Encoding from, Encoding to);
} // namespace TextEncoding