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

383 lines
12 KiB
C++

#include <I18nAPI.h>
#include <Utils/StringHelper.h>
#include <Main/LiteLoader.h>
using namespace std;
namespace fs = std::filesystem;
std::vector<std::string> GeneralLanguages{
"en", "zh"
};
std::string I18nBase::get(const std::string& key, const std::string& langCode) {
auto& langc = (langCode.empty() ? defaultLocaleName : langCode);
auto langType = langc.substr(0, 2);
if (langData.count(langc)) { // If there is lang data for the language
auto& lang = langData[langc];
if (lang.count(key)) { // If there is matched key in the language data
return lang[key];
}
}
// Search for the similar language in langData
for (auto& [lc, ld] : langData) {
if (lc.length() < 2) {
continue;
}
if (lc.substr(0, 2) == langType) {
if (ld.count(key)) {
return ld[key];
}
}
}
if (!defaultLangData.empty()) {
// If not found, try falling back to the default language data
if (defaultLangData.count(langc)) {
auto& lang = defaultLangData[langc];
if (lang.count(key)) {
return lang[key]; // Fall back
}
}
// Search for the similar language
for (auto& [lc, ld] : defaultLangData) {
if (lc.length() < 2) {
continue;
}
if (lc.substr(0, 2) == langType) {
if (ld.count(key)) {
return ld[key];
}
}
}
}
// Try finding general languages
for (auto& lang : GeneralLanguages) {
for (auto& [lc, ld] : langData) {
if (lc.length() < 2) {
continue;
}
if (lc.substr(0, 2) == lang) {
if (ld.count(key)) {
return ld[key];
}
}
}
}
// Finally, not found, return the key
return key;
}
std::string I18nBase::getDefaultLocaleName() {
return this->defaultLocaleName;
}
I18nBase* I18nBase::clone() {
if (getType() == Type::SingleFile) {
return new SingleFileI18N(*(SingleFileI18N*)this);
} else if (getType() == Type::MultiFile) {
return new MultiFileI18N(*(MultiFileI18N*)this);
}
return nullptr;
}
////////////////////////////////////////// SingleFileI18N //////////////////////////////////////////
void SingleFileI18N::load(const std::string& fileName) {
this->filePath = fileName;
if (!fs::exists(fileName)) {
fs::create_directories(fs::path(fileName).parent_path());
std::fstream file(fileName, std::ios::out | std::ios::app);
nlohmann::json j = defaultLangData;
file << std::setw(4) << j; // Dump default language data
file.close();
langData = defaultLangData;
return; // Skip parsing
}
std::fstream file(fileName, std::ios::in);
nlohmann::json j;
file >> j;
langData = j.get<LangData>();
file.close();
// Replenish the missing keys
for (auto& [lang, dat] : langData) {
if (defaultLangData.count(lang)) {
for (auto& [k, v] : defaultLangData[lang]) {
if (!dat.count(k)) {
dat[k] = v;
}
}
}
}
save();
}
void SingleFileI18N::save() {
std::fstream file;
if (fs::exists(filePath))
file.open(filePath, std::ios::out | std::ios::ate);
else
file.open(filePath, std::ios::out | std::ios::app);
nlohmann::json j = langData;
file << std::setw(4) << j;
file.close();
}
I18nBase::Type SingleFileI18N::getType() {
return Type::SingleFile;
}
////////////////////////////////////////// MultiFileI18N //////////////////////////////////////////
I18nBase::SubLangData NestedHelper(const nlohmann::json& j, const std::string& prefix = "") {
I18nBase::SubLangData data;
if (!j.is_object()) {
// throw std::exception("Error when parsing I18nBase data: The value must be object!");
return data; // Empty
}
for (auto it = j.begin(); it != j.end(); ++it) {
auto& val = it.value();
if (val.is_object()) {
data.merge(NestedHelper(val, prefix + it.key() + '.'));
} else if (val.is_string()) {
data.emplace(prefix + it.key(), val.get<std::string>());
} else {
continue; // Ignore
}
}
return data;
}
void MultiFileI18N::load(const std::string& dirName) {
this->dirPath = dirName;
if (!fs::exists(dirName) || fs::is_empty(dirName)) {
if (this->defaultLangData.empty()) {
logger.error("Language files NOT FOUND! This may cause many errors!");
return;
}
this->langData = this->defaultLangData;
save();
return;
}
for (auto& f : fs::directory_iterator(dirName)) {
if (!f.is_regular_file() || f.path().extension() != ".json") {
continue;
}
auto langName = f.path().stem().string();
std::fstream file(f.path().wstring(), std::ios::in);
nlohmann::json j;
file >> j;
auto data = NestedHelper(j);
this->langData.emplace(langName, data);
}
}
void MultiFileI18N::save(bool nested) {
if (!fs::exists(dirPath)) {
fs::create_directories(dirPath);
for (auto& [lc, lv] : this->defaultLangData) {
auto fileName = fs::path(dirPath).append(lc + ".json").wstring();
std::fstream file;
if (fs::exists(fileName)) {
file.open(fileName, std::ios::out | std::ios::ate);
} else {
file.open(fileName, std::ios::out | std::ios::app);
}
if (nested) {
auto out = nlohmann::json::object();
for (auto& [k, v] : lv) {
auto keys = SplitStrWithPattern(k, ".");
std::string name = keys.back();
keys.pop_back();
nlohmann::json* j = &out;
for (auto& key : keys) {
j->emplace(key, nlohmann::json::object());
j = &j->at(key);
}
j->emplace(name, v);
}
file << nlohmann::json(out).dump(4);
} else {
file << nlohmann::json(lv).dump(4);
}
}
}
}
I18nBase::Type MultiFileI18N::getType() {
return Type::MultiFile;
}
namespace Translation {
I18nBase* loadI18nImpl(HMODULE hPlugin, const std::string& path, const std::string& defaultLocaleName,
const I18nBase::LangData& defaultLangData) {
try {
I18nBase* res = nullptr;
if (path.ends_with('/') || path.ends_with('\\') || fs::is_directory(path)) { // Directory
res = new MultiFileI18N(path, defaultLocaleName, defaultLangData);
} else {
res = new SingleFileI18N(path, defaultLocaleName, defaultLangData);
}
return &PluginOwnData::setWithoutNewImpl<I18nBase>(hPlugin, I18nBase::POD_KEY, res);
} catch (const std::exception& e) {
logger.error("Fail to load translation file <{}> !", path);
logger.error("- {}", TextEncoding::toUTF8(e.what()));
} catch (...) { logger.error("Fail to load translation file <{}> !", path); }
return nullptr;
}
I18nBase* loadFromImpl(HMODULE hPlugin, HMODULE hTarget) {
try {
auto& i18n = PluginOwnData::getImpl<I18nBase>(hTarget, I18nBase::POD_KEY);
return &PluginOwnData::setWithoutNewImpl<I18nBase>(hPlugin, I18nBase::POD_KEY, i18n.clone());
} catch (const std::exception& e) {
logger.error("Fail to load translation from another plugin!", e.what());
logger.error("- {}", e.what());
} catch (...) { logger.error("Fail to load translation from another plugin!"); }
return nullptr;
}
}; // namespace Translation
///////////////////////////// Encoding-CodePage Map /////////////////////////////
#undef UNICODE
namespace TextEncoding {
const std::unordered_map<Encoding, UINT> Encoding_CodePage_Map = {
{Encoding::ISO_8859_1, 28591},
{Encoding::ISO_8859_2, 28592},
{Encoding::ISO_8859_3, 28593},
{Encoding::ISO_8859_4, 28594},
{Encoding::ISO_8859_5, 28595},
{Encoding::ISO_8859_6, 28596},
{Encoding::ISO_8859_7, 28597},
{Encoding::ISO_8859_8, 28598},
{Encoding::ISO_8859_9, 28599},
//{Encoding::ISO_8859_10,}, //?
{Encoding::JAPANESE_EUC_JP, 51932},
{Encoding::JAPANESE_SHIFT_JIS, 932},
{Encoding::JAPANESE_JIS, 932},
{Encoding::CHINESE_BIG5, 950},
{Encoding::CHINESE_GB, 936},
{Encoding::CHINESE_EUC_CN, 51936},
{Encoding::KOREAN_EUC_KR, 51949},
//{Encoding::UNICODE, },
{Encoding::CHINESE_EUC_DEC, 51936},
{Encoding::CHINESE_CNS, 20000},
{Encoding::CHINESE_BIG5_CP950, 950},
{Encoding::JAPANESE_CP932, 932},
{Encoding::UTF8, CP_UTF8},
{Encoding::UNKNOWN_ENCODING, CP_ACP},
{Encoding::ASCII_7BIT, 28591},
{Encoding::RUSSIAN_KOI8_R, 20866},
{Encoding::RUSSIAN_CP1251, 1251},
{Encoding::MSFT_CP1252, 1252},
{Encoding::RUSSIAN_KOI8_RU, 20866},
{Encoding::MSFT_CP1250, 1250},
{Encoding::ISO_8859_15, 28605},
{Encoding::MSFT_CP1254, 1254},
{Encoding::MSFT_CP1257, 1257},
{Encoding::ISO_8859_11, 10021}, //?
{Encoding::MSFT_CP874, 874},
{Encoding::MSFT_CP1256, 1256},
{Encoding::MSFT_CP1255, 1255},
{Encoding::ISO_8859_8_I, 38598},
{Encoding::HEBREW_VISUAL, 28598},
{Encoding::CZECH_CP852, 852},
//{Encoding::CZECH_CSN_369103, }, //?
{Encoding::MSFT_CP1253, 1253},
{Encoding::RUSSIAN_CP866, 866},
{Encoding::ISO_8859_13, 28603},
{Encoding::ISO_2022_KR, 50225},
{Encoding::GBK, 936},
{Encoding::GB18030, 54936},
{Encoding::BIG5_HKSCS, 950},
{Encoding::ISO_2022_CN, 50227},
{Encoding::TSCII, 57004}, //?
{Encoding::TAMIL_MONO, 57004}, //?
{Encoding::TAMIL_BI, 57004}, //?
//{Encoding::JAGRAN, }, //?
{Encoding::MACINTOSH_ROMAN, 10000},
{Encoding::UTF7, CP_UTF7},
{Encoding::UTF16BE, 1201},
{Encoding::UTF16LE, 1200},
{Encoding::UTF32BE, 12001},
{Encoding::UTF32LE, 12000},
//{Encoding::BINARYENC, },
{Encoding::HZ_GB_2312, 52936},
//{Encoding::TAM_ELANGO, }, //?
//{Encoding::TAM_LTTMBARANI, }, //?
//{Encoding::TAM_SHREE, }, //?
//{Encoding::TAM_TBOOMIS, }, //?
//{Encoding::TAM_TMNEWS, }, //?
//{Encoding::TAM_WEBTAMIL, }, //?
{Encoding::KDDI_SHIFT_JIS, 932},
{Encoding::DOCOMO_SHIFT_JIS, 932},
{Encoding::SOFTBANK_SHIFT_JIS, 932},
{Encoding::KDDI_ISO_2022_JP, 50220},
{Encoding::SOFTBANK_ISO_2022_JP, 50220},
};
}
#define UNICODE
///////////////////////////// Encoding-CodePage Map /////////////////////////////
namespace TextEncoding {
Encoding getLocalEncoding() {
UINT page = GetACP();
for (auto& [k, v] : Encoding_CodePage_Map) {
if (v == page)
return k;
}
return default_encoding();
}
Encoding detectEncoding(const std::string& text, bool* isReliable) {
bool temp;
int bytes_consumed;
return DetectEncoding(
text.c_str(), (int)text.size(),
nullptr, nullptr, nullptr,
UNKNOWN_ENCODING,
UNKNOWN_LANGUAGE,
CompactEncDet::WEB_CORPUS,
false,
&bytes_consumed,
isReliable ? isReliable : &temp);
}
std::string fromUnicode(const std::wstring& text, Encoding encoding) {
try {
return wstr2str(text, Encoding_CodePage_Map.at(encoding));
} catch (...) {
return "";
}
}
std::wstring toUnicode(const std::string& text, Encoding encoding) {
try {
return str2wstr(text, Encoding_CodePage_Map.at(encoding));
} catch (...) {
return L"";
}
}
std::string toUTF8(const std::string& text) {
return convert(text, detectEncoding(text), Encoding::UTF8);
}
std::string toUTF8(const std::string& text, Encoding from) {
return convert(text, from, Encoding::UTF8);
}
std::string convert(const std::string& text, Encoding from, Encoding to) {
if (text.empty() || from == to)
return text;
wstring uni = toUnicode(text, from);
if (uni.empty())
return "";
return fromUnicode(uni, to);
}
} // namespace TextEncoding