forked from Public-Mirror/Textractor
improve reliability of translations
This commit is contained in:
parent
1b71b3ee86
commit
268f40771d
@ -147,7 +147,7 @@ namespace
|
|||||||
{
|
{
|
||||||
QMultiHash<QString, DWORD> allProcesses;
|
QMultiHash<QString, DWORD> allProcesses;
|
||||||
for (auto [processId, processName] : GetAllProcesses())
|
for (auto [processId, processName] : GetAllProcesses())
|
||||||
if (processName && (showSystemProcesses || processName->find(L":\\Windows\\") == std::wstring::npos))
|
if (processName && (showSystemProcesses || processName->find(L":\\Windows\\") == std::string::npos))
|
||||||
allProcesses.insert(QFileInfo(S(processName.value())).fileName(), processId);
|
allProcesses.insert(QFileInfo(S(processName.value())).fileName(), processId);
|
||||||
|
|
||||||
QStringList processList(allProcesses.uniqueKeys());
|
QStringList processList(allProcesses.uniqueKeys());
|
||||||
@ -202,7 +202,7 @@ namespace
|
|||||||
{
|
{
|
||||||
if (auto processName = GetModuleFilename(selectedProcessId)) if (int last = processName->rfind(L'\\') + 1)
|
if (auto processName = GetModuleFilename(selectedProcessId)) if (int last = processName->rfind(L'\\') + 1)
|
||||||
{
|
{
|
||||||
std::wstring configFile = std::wstring(processName.value()).replace(last, std::wstring::npos, GAME_CONFIG_FILE);
|
std::wstring configFile = std::wstring(processName.value()).replace(last, std::string::npos, GAME_CONFIG_FILE);
|
||||||
if (!std::filesystem::exists(configFile)) QTextFile(S(configFile), QFile::WriteOnly).write("see https://github.com/Artikash/Textractor/wiki/Game-configuration-file");
|
if (!std::filesystem::exists(configFile)) QTextFile(S(configFile), QFile::WriteOnly).write("see https://github.com/Artikash/Textractor/wiki/Game-configuration-file");
|
||||||
if (std::filesystem::exists(configFile)) _wspawnlp(_P_DETACH, L"notepad", L"notepad", configFile.c_str(), NULL);
|
if (std::filesystem::exists(configFile)) _wspawnlp(_P_DETACH, L"notepad", L"notepad", configFile.c_str(), NULL);
|
||||||
else QMessageBox::critical(This, GAME_CONFIG, QString(FAILED_TO_CREATE_CONFIG_FILE).arg(S(configFile)));
|
else QMessageBox::critical(This, GAME_CONFIG, QString(FAILED_TO_CREATE_CONFIG_FILE).arg(S(configFile)));
|
||||||
@ -649,7 +649,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
if (arg[1] == L'p' || arg[1] == L'P')
|
if (arg[1] == L'p' || arg[1] == L'P')
|
||||||
if (DWORD processId = _wtoi(arg.substr(2).c_str())) Host::InjectProcess(processId);
|
if (DWORD processId = _wtoi(arg.substr(2).c_str())) Host::InjectProcess(processId);
|
||||||
else for (auto [processId, processName] : processes)
|
else for (auto [processId, processName] : processes)
|
||||||
if (processName.value_or(L"").find(L"\\" + arg.substr(2)) != std::wstring::npos) Host::InjectProcess(processId);
|
if (processName.value_or(L"").find(L"\\" + arg.substr(2)) != std::string::npos) Host::InjectProcess(processId);
|
||||||
|
|
||||||
std::thread([]
|
std::thread([]
|
||||||
{
|
{
|
||||||
|
@ -91,12 +91,11 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
|||||||
L"api.cognitive.microsofttranslator.com",
|
L"api.cognitive.microsofttranslator.com",
|
||||||
L"POST",
|
L"POST",
|
||||||
FormatString(L"/translate?api-version=3.0&to=%s", translateTo.Copy()).c_str(),
|
FormatString(L"/translate?api-version=3.0&to=%s", translateTo.Copy()).c_str(),
|
||||||
FormatString(R"([{"text":"%s"}])", JSON::Escape(text)),
|
FormatString(R"([{"text":"%s"}])", JSON::Escape(WideStringToString(text))),
|
||||||
FormatString(L"Content-Type: application/json; charset=UTF-8\r\nOcp-Apim-Subscription-Key:%s", apiKey.Copy()).c_str()
|
FormatString(L"Content-Type: application/json; charset=UTF-8\r\nOcp-Apim-Subscription-Key:%s", apiKey.Copy()).c_str()
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
// Response formatted as JSON: translation starts with text":" and ends with ","to
|
if (auto translation = Copy(JSON::Parse(httpRequest.response)[0][L"translations"][0][L"text"].String())) return { true, translation.value() };
|
||||||
if (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"text\":\"(.+?)\",\""))) return { true, results[1] };
|
|
||||||
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
||||||
}
|
}
|
||||||
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
||||||
@ -107,8 +106,7 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
|||||||
L"POST",
|
L"POST",
|
||||||
FormatString(L"/ttranslatev3?fromLang=auto-detect&to=%s&text=%s", translateTo.Copy(), Escape(text)).c_str()
|
FormatString(L"/ttranslatev3?fromLang=auto-detect&to=%s&text=%s", translateTo.Copy(), Escape(text)).c_str()
|
||||||
})
|
})
|
||||||
// Response formatted as JSON: translation starts with text":" and ends with ","to
|
if (auto translation = Copy(JSON::Parse(httpRequest.response)[0][L"translations"][0][L"text"].String())) return { true, translation.value() };
|
||||||
if (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"text\":\"(.+?)\",\""))) return { true, results[1] };
|
|
||||||
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
||||||
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
||||||
}
|
}
|
||||||
|
@ -50,14 +50,14 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
|||||||
L"Content-Type: application/x-www-form-urlencoded"
|
L"Content-Type: application/x-www-form-urlencoded"
|
||||||
})))
|
})))
|
||||||
// Response formatted as JSON: translation starts with text":" and ends with "}]
|
// Response formatted as JSON: translation starts with text":" and ends with "}]
|
||||||
if (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"text\":\"(.+?)\"\\}\\]"))) return { true, results[1] };
|
if (auto translation = Copy(JSON::Parse(httpRequest.response)[L"translations"][0][L"text"].String())) return { true, translation.value() };
|
||||||
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
||||||
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
||||||
|
|
||||||
// the following code was reverse engineered from the DeepL website; it's as close as I could make it but I'm not sure what parts of this could be removed and still have it work
|
// the following code was reverse engineered from the DeepL website; it's as close as I could make it but I'm not sure what parts of this could be removed and still have it work
|
||||||
int64_t r = _time64(nullptr), n = std::count(text.begin(), text.end(), L'i') + 1;
|
int64_t r = _time64(nullptr), n = std::count(text.begin(), text.end(), L'i') + 1;
|
||||||
thread_local auto generator = std::mt19937(std::random_device()());
|
thread_local auto generator = std::mt19937(std::random_device()());
|
||||||
int id = 10000 * std::uniform_int_distribution(0, 9999)(generator);
|
int id = 10000 * std::uniform_int_distribution(0, 9999)(generator) + 1;
|
||||||
// user_preferred_langs? what should priority be? does timestamp do anything? other translation quality options?
|
// user_preferred_langs? what should priority be? does timestamp do anything? other translation quality options?
|
||||||
auto body = FormatString(R"(
|
auto body = FormatString(R"(
|
||||||
{
|
{
|
||||||
@ -81,7 +81,7 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)", id + 1, r + (n - r % n), translateTo.Copy(), JSON::Escape(text));
|
)", id, r + (n - r % n), translateTo.Copy(), JSON::Escape(WideStringToString(text)));
|
||||||
// missing accept-encoding header since it fucks up HttpRequest
|
// missing accept-encoding header since it fucks up HttpRequest
|
||||||
if (HttpRequest httpRequest{
|
if (HttpRequest httpRequest{
|
||||||
L"Mozilla/5.0 Textractor",
|
L"Mozilla/5.0 Textractor",
|
||||||
@ -93,8 +93,7 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
|||||||
L"https://www.deepl.com/translator",
|
L"https://www.deepl.com/translator",
|
||||||
WINHTTP_FLAG_SECURE
|
WINHTTP_FLAG_SECURE
|
||||||
})
|
})
|
||||||
// Response formatted as JSON: translation starts with preprocessed_sentence":" and ends with ","
|
if (auto translation = Copy(JSON::Parse(httpRequest.response)[L"result"][L"translations"][0][L"beams"][0][L"postprocessed_sentence"].String())) return { true, translation.value() };
|
||||||
if (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"postprocessed_sentence\":\"(.+?)\",\""))) return { true, results[1] };
|
|
||||||
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
||||||
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
||||||
}
|
}
|
||||||
|
@ -197,7 +197,7 @@ public:
|
|||||||
void AddSentence(QString sentence)
|
void AddSentence(QString sentence)
|
||||||
{
|
{
|
||||||
if (sentence.size() > maxSentenceSize) sentence = SENTENCE_TOO_BIG;
|
if (sentence.size() > maxSentenceSize) sentence = SENTENCE_TOO_BIG;
|
||||||
if (!showOriginal) sentence = sentence.section('\n', sentence.count('\n') / 2 + 1);
|
if (!showOriginal) sentence = sentence.section("\n----\n", sentence.count("\n----\n") / 2 + 1);
|
||||||
sanitize(sentence);
|
sanitize(sentence);
|
||||||
sentence.chop(std::distance(std::remove(sentence.begin(), sentence.end(), QChar::Tabulation), sentence.end()));
|
sentence.chop(std::distance(std::remove(sentence.begin(), sentence.end(), QChar::Tabulation), sentence.end()));
|
||||||
sentenceHistory.push_back(sentence);
|
sentenceHistory.push_back(sentence);
|
||||||
|
@ -125,37 +125,6 @@ QStringList languages
|
|||||||
bool translateSelectedOnly = false, rateLimitAll = true, rateLimitSelected = false, useCache = true;
|
bool translateSelectedOnly = false, rateLimitAll = true, rateLimitSelected = false, useCache = true;
|
||||||
int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 500;
|
int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 500;
|
||||||
|
|
||||||
unsigned TKK = 0;
|
|
||||||
|
|
||||||
std::wstring GetTranslationUri(const std::wstring& text)
|
|
||||||
{
|
|
||||||
// If no TKK available, use this uri. Can't use too much or google will detect unauthorized access
|
|
||||||
if (!TKK) return FormatString(L"/translate_a/single?client=gtx&dt=ld&dt=rm&dt=t&tl=%s&q=%s", translateTo.Copy(), Escape(text));
|
|
||||||
|
|
||||||
// reverse engineered from translate.google.com
|
|
||||||
std::wstring escapedText;
|
|
||||||
unsigned a = time(NULL) / 3600, b = a; // the first part of TKK
|
|
||||||
for (unsigned char ch : WideStringToString(text))
|
|
||||||
{
|
|
||||||
escapedText += FormatString(L"%%%02X", (int)ch);
|
|
||||||
a += ch;
|
|
||||||
a += a << 10;
|
|
||||||
a ^= a >> 6;
|
|
||||||
}
|
|
||||||
a += a << 3;
|
|
||||||
a ^= a >> 11;
|
|
||||||
a += a << 15;
|
|
||||||
a ^= TKK;
|
|
||||||
a %= 1000000;
|
|
||||||
|
|
||||||
return FormatString(L"/translate_a/single?client=webapp&dt=ld&dt=rm&dt=t&sl=auto&tl=%s&tk=%u.%u&q=%s", translateTo.Copy(), a, a ^ b, escapedText);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsHash(const std::wstring& result)
|
|
||||||
{
|
|
||||||
return result.size() == 32 && std::all_of(result.begin(), result.end(), [](char ch) { return (ch >= L'0' && ch <= L'9') || (ch >= L'a' && ch <= L'z'); });
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||||
{
|
{
|
||||||
if (!apiKey->empty())
|
if (!apiKey->empty())
|
||||||
@ -164,31 +133,40 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
|||||||
L"translation.googleapis.com",
|
L"translation.googleapis.com",
|
||||||
L"POST",
|
L"POST",
|
||||||
FormatString(L"/language/translate/v2?format=text&target=%s&key=%s", translateTo.Copy(), apiKey.Copy()).c_str(),
|
FormatString(L"/language/translate/v2?format=text&target=%s&key=%s", translateTo.Copy(), apiKey.Copy()).c_str(),
|
||||||
FormatString(R"({"q":["%s"]})", JSON::Escape(text))
|
FormatString(R"({"q":["%s"]})", JSON::Escape(WideStringToString(text)))
|
||||||
})
|
})
|
||||||
{
|
if (auto translation = Copy(JSON::Parse(httpRequest.response)[L"data"][L"translations"][0][L"translatedText"].String())) return { true, translation.value() };
|
||||||
// Response formatted as JSON: starts with "translatedText": " and translation is enclosed in quotes followed by a comma
|
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
||||||
if (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"\"translatedText\": \"(.+?)\","))) return { true, results[1] };
|
|
||||||
return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
|
||||||
}
|
|
||||||
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
||||||
|
|
||||||
if (!TKK)
|
if (HttpRequest httpRequest{
|
||||||
if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"translate.google.com", L"GET", L"/" })
|
L"Mozilla/5.0 Textractor",
|
||||||
if (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"(\\d{7,})'")))
|
L"translate.google.com",
|
||||||
_InterlockedCompareExchange(&TKK, stoll(results[1]), 0);
|
L"POST",
|
||||||
|
L"/_/TranslateWebserverUi/data/batchexecute?rpcids=MkEWBc",
|
||||||
if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"translate.googleapis.com", L"GET", GetTranslationUri(text).c_str() })
|
"f.req=" + Escape(WideStringToString(FormatString(LR"([[["MkEWBc","[[\"%s\",\"auto\",\"%s\",true],[null]]",null,"generic"]]])", JSON::Escape((JSON::Escape(text))), translateTo.Copy()))),
|
||||||
|
L"Content-Type: application/x-www-form-urlencoded"
|
||||||
|
})
|
||||||
{
|
{
|
||||||
// Response formatted as JSON: starts with "[[[" and translation is enclosed in quotes followed by a comma
|
if (auto start = httpRequest.response.find(L"[["); start != std::string::npos)
|
||||||
if (httpRequest.response[0] == L'[')
|
|
||||||
{
|
{
|
||||||
std::wstring translation;
|
if (auto blob = Copy(JSON::Parse(httpRequest.response.substr(start))[0][2].String())) if (auto translations = Copy(JSON::Parse(blob.value())[1][0].Array()))
|
||||||
for (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"\\[\"(.*?)\",[n\"]")); httpRequest.response = results.suffix())
|
{
|
||||||
if (!IsHash(results[1])) translation += std::wstring(results[1]) + L" ";
|
std::wstring translation;
|
||||||
if (!translation.empty()) return { true, translation };
|
if (translations->size() == 1 && (translations = Copy(translations.value()[0][5].Array())))
|
||||||
|
{
|
||||||
|
for (const auto& sentence : translations.value()) if (sentence[0].String()) (translation += *sentence[0].String()) += L" ";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (const auto& conjugation : translations.value())
|
||||||
|
if (auto sentence = conjugation[0].String()) if (auto gender = conjugation[2].String()) translation += FormatString(L"%s %s\n", *sentence, *gender);
|
||||||
|
}
|
||||||
|
if (!translation.empty()) return { true, translation };
|
||||||
|
return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, blob.value()) };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return { false, FormatString(L"%s (TKK=%u): %s", TRANSLATION_ERROR, _InterlockedExchange(&TKK, 0), httpRequest.response) };
|
return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
||||||
}
|
}
|
||||||
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
||||||
}
|
}
|
||||||
|
@ -54,44 +54,11 @@ std::wstring Escape(const std::wstring& text)
|
|||||||
return escaped;
|
return escaped;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace JSON
|
std::string Escape(const std::string& text)
|
||||||
{
|
{
|
||||||
void Unescape(std::wstring& text)
|
std::string escaped;
|
||||||
{
|
for (unsigned char ch : text) escaped += FormatString("%%%02X", (int)ch);
|
||||||
for (int i = 0; i < text.size(); ++i)
|
return escaped;
|
||||||
{
|
|
||||||
if (text[i] == L'\\')
|
|
||||||
{
|
|
||||||
text[i] = 0;
|
|
||||||
if (text[i + 1] == L'r') text[i + 1] = 0; // for some reason \r gets displayed as a newline
|
|
||||||
if (text[i + 1] == L'n') text[i + 1] = L'\n';
|
|
||||||
if (text[i + 1] == L't') text[i + 1] = L'\t';
|
|
||||||
if (text[i + 1] == L'\\') ++i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
text.erase(std::remove(text.begin(), text.end(), 0), text.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Escape(const std::wstring& text)
|
|
||||||
{
|
|
||||||
std::string escaped = WideStringToString(text);
|
|
||||||
int oldSize = escaped.size();
|
|
||||||
escaped.resize(escaped.size() + std::count_if(escaped.begin(), escaped.end(), [](char ch) { return ch == '\n' || ch == '\r' || ch == '\t' || ch == '\\' || ch == '"'; }));
|
|
||||||
auto out = escaped.rbegin();
|
|
||||||
for (int i = oldSize - 1; i >= 0; --i)
|
|
||||||
{
|
|
||||||
if (escaped[i] == '\n') *out++ = 'n';
|
|
||||||
else if (escaped[i] == '\t') *out++ = 't';
|
|
||||||
else if (escaped[i] == '\r') *out++ = 'r';
|
|
||||||
else if (escaped[i] == '\\' || escaped[i] == '"') *out++ = escaped[i];
|
|
||||||
else
|
|
||||||
{
|
|
||||||
*out++ = escaped[i];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
*out++ = '\\';
|
|
||||||
}
|
|
||||||
escaped.erase(std::remove_if(escaped.begin(), escaped.end(), [](unsigned char ch) { return ch < 0x20; }), escaped.end());
|
|
||||||
return escaped;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TEST(assert(JSON::Parse(LR"([{"string":"hello world","boolean":false,"number": 1.67e+4,"null": null,"array":[]},"hello world"])")))
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <winhttp.h>
|
#include <winhttp.h>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
using InternetHandle = AutoHandle<Functor<WinHttpCloseHandle>>;
|
using InternetHandle = AutoHandle<Functor<WinHttpCloseHandle>>;
|
||||||
|
|
||||||
@ -28,9 +29,179 @@ struct HttpRequest
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::wstring Escape(const std::wstring& text);
|
std::wstring Escape(const std::wstring& text);
|
||||||
|
std::string Escape(const std::string& text);
|
||||||
|
|
||||||
namespace JSON
|
namespace JSON
|
||||||
{
|
{
|
||||||
void Unescape(std::wstring& text);
|
inline std::wstring UTF(int charCode)
|
||||||
std::string Escape(const std::wstring& text);
|
{
|
||||||
|
return { (wchar_t)charCode };
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
std::pair<std::basic_string<C>, int> Unescape(std::basic_string_view<C> text)
|
||||||
|
{
|
||||||
|
std::basic_string<C> unescaped;
|
||||||
|
int i = 0;
|
||||||
|
for (; i < text.size(); ++i)
|
||||||
|
{
|
||||||
|
char ch = text[i];
|
||||||
|
if (ch == '"') return { unescaped, i + 1 };
|
||||||
|
if (ch == '\\')
|
||||||
|
{
|
||||||
|
ch = text[i + 1];
|
||||||
|
if (ch == 'u' && std::isxdigit(text[i + 2]) && std::isxdigit(text[i + 3]) && std::isxdigit(text[i + 4]) && std::isxdigit(text[i + 5]))
|
||||||
|
{
|
||||||
|
char charCode[] = { text[i + 2], text[i + 3], text[i + 4], text[i + 5], 0 };
|
||||||
|
unescaped += UTF(strtol(charCode, nullptr, 16));
|
||||||
|
i += 5;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (auto [original, value] : Array<char, char>{ { 'b', '\b' }, {'f', '\f'}, {'n', '\n'}, {'r', '\r'}, {'t', '\t'} }) if (ch == original)
|
||||||
|
{
|
||||||
|
unescaped.push_back(value);
|
||||||
|
goto replaced;
|
||||||
|
}
|
||||||
|
unescaped.push_back(ch);
|
||||||
|
replaced: i += 1;
|
||||||
|
}
|
||||||
|
else unescaped.push_back(ch);
|
||||||
|
}
|
||||||
|
return { unescaped, i };
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
std::basic_string<C> Escape(std::basic_string<C> text)
|
||||||
|
{
|
||||||
|
int oldSize = text.size();
|
||||||
|
text.resize(text.size() + std::count_if(text.begin(), text.end(), [](auto ch) { return ch == '\n' || ch == '\r' || ch == '\t' || ch == '\\' || ch == '"'; }));
|
||||||
|
auto out = text.rbegin();
|
||||||
|
for (int i = oldSize - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
if (text[i] == '\n') *out++ = 'n';
|
||||||
|
else if (text[i] == '\t') *out++ = 't';
|
||||||
|
else if (text[i] == '\r') *out++ = 'r';
|
||||||
|
else if (text[i] == '\\' || text[i] == '"') *out++ = text[i];
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*out++ = text[i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
*out++ = '\\';
|
||||||
|
}
|
||||||
|
text.erase(std::remove_if(text.begin(), text.end(), [](uint64_t ch) { return ch < 0x20 || ch == 0x7f; }), text.end());
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
struct Value : private std::variant<std::monostate, std::nullopt_t, bool, double, std::basic_string<C>, std::vector<Value<C>>, std::unordered_map<std::basic_string<C>, Value<C>>>
|
||||||
|
{
|
||||||
|
using std::variant<std::monostate, std::nullopt_t, bool, double, std::basic_string<C>, std::vector<Value<C>>, std::unordered_map<std::basic_string<C>, Value<C>>>::variant;
|
||||||
|
|
||||||
|
explicit operator bool() const { return index(); }
|
||||||
|
bool IsNull() const { return index() == 1; }
|
||||||
|
auto Boolean() const { return std::get_if<bool>(this); }
|
||||||
|
auto String() const { return std::get_if<std::basic_string<C>>(this); }
|
||||||
|
auto Array() const { return std::get_if<std::vector<Value<C>>>(this); }
|
||||||
|
auto Object() const { return std::get_if<std::unordered_map<std::basic_string<C>, Value<C>>>(this); }
|
||||||
|
|
||||||
|
const Value<C>& operator[](std::basic_string<C> key) const
|
||||||
|
{
|
||||||
|
static const Value<C> failure;
|
||||||
|
if (auto object = Object()) if (auto it = object->find(key); it != object->end()) return it->second;
|
||||||
|
return failure;
|
||||||
|
}
|
||||||
|
const Value<C>& operator[](int i) const
|
||||||
|
{
|
||||||
|
static const Value<C> failure;
|
||||||
|
if (auto array = Array()) if (i < array->size()) return array->at(i);
|
||||||
|
return failure;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
Value<C> Parse(std::basic_string_view<C> text, int64_t& i, int depth)
|
||||||
|
{
|
||||||
|
if (depth > 25) return {};
|
||||||
|
C ch;
|
||||||
|
auto SkipWhitespace = [&]
|
||||||
|
{
|
||||||
|
while (i < text.size() && (text[i] == ' ' || text[i] == '\n' || text[i] == '\r' || text[i] == '\t')) ++i;
|
||||||
|
if (i >= text.size()) return true;
|
||||||
|
ch = text[i];
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
auto ExtractString = [&]
|
||||||
|
{
|
||||||
|
i += 1;
|
||||||
|
auto [string, length] = Unescape(text.substr(i));
|
||||||
|
i += length;
|
||||||
|
return string;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (SkipWhitespace()) return {};
|
||||||
|
|
||||||
|
static C nullStr[] = { 'n', 'u', 'l', 'l' }, trueStr[] = { 't', 'r', 'u', 'e' }, falseStr[] = { 'f', 'a', 'l', 's', 'e' };
|
||||||
|
if (ch == nullStr[0])
|
||||||
|
if (std::char_traits<C>::compare(text.data() + i, nullStr, std::size(nullStr)) == 0) return i += std::size(nullStr), std::nullopt;
|
||||||
|
else return {};
|
||||||
|
if (ch == trueStr[0])
|
||||||
|
if (std::char_traits<C>::compare(text.data() + i, trueStr, std::size(trueStr)) == 0) return i += std::size(trueStr), true;
|
||||||
|
else return {};
|
||||||
|
if (ch == falseStr[0])
|
||||||
|
if (std::char_traits<C>::compare(text.data() + i, falseStr, std::size(falseStr)) == 0) return i += std::size(falseStr), false;
|
||||||
|
else return {};
|
||||||
|
|
||||||
|
if (ch == '-' || (ch >= '0' && ch <= '9'))
|
||||||
|
{
|
||||||
|
// no numbers currently used, add a actual parser when needed
|
||||||
|
while (i < text.size() && ((text[i] >= '0' && text[i] <= '9') || text[i] == '-' || text[i] == '+' || text[i] == 'e' || text[i] == 'E' || text[i] == '.')) ++i;
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '"') return ExtractString();
|
||||||
|
|
||||||
|
if (ch == '[')
|
||||||
|
{
|
||||||
|
std::vector<Value<C>> array;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
i += 1;
|
||||||
|
if (SkipWhitespace()) return {};
|
||||||
|
if (ch == ']') return i += 1, Value<C>(array);
|
||||||
|
if (!array.emplace_back(Parse(text, i, depth + 1))) return {};
|
||||||
|
if (SkipWhitespace()) return {};
|
||||||
|
if (ch == ']') return i += 1, Value<C>(array);
|
||||||
|
if (ch != ',') return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '{')
|
||||||
|
{
|
||||||
|
std::unordered_map<std::basic_string<C>, Value<C>> object;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
i += 1;
|
||||||
|
if (SkipWhitespace()) return {};
|
||||||
|
if (ch == '}') return i += 1, Value<C>(object);
|
||||||
|
if (ch != '"') return {};
|
||||||
|
auto key = ExtractString();
|
||||||
|
if (SkipWhitespace() || ch != ':') return {};
|
||||||
|
i += 1;
|
||||||
|
if (!(object[std::move(key)] = Parse(text, i, depth + 1))) return {};
|
||||||
|
if (SkipWhitespace()) return {};
|
||||||
|
if (ch == '}') return i += 1, Value<C>(object);
|
||||||
|
if (ch != ',') return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
Value<C> Parse(const std::basic_string<C>& text)
|
||||||
|
{
|
||||||
|
int64_t start = 0;
|
||||||
|
return Parse((std::basic_string_view<C>)text, start, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ extern int tokenCount, tokenRestoreDelay, maxSentenceSize;
|
|||||||
std::pair<bool, std::wstring> Translate(const std::wstring& text);
|
std::pair<bool, std::wstring> Translate(const std::wstring& text);
|
||||||
|
|
||||||
const char* LANGUAGE = u8"Language";
|
const char* LANGUAGE = u8"Language";
|
||||||
const std::string TRANSLATION_CACHE_FILE = FormatString("%s Cache.txt", TRANSLATION_PROVIDER);
|
const std::string TRANSLATION_CACHE_FILE = FormatString("%s Translation Cache.txt", TRANSLATION_PROVIDER);
|
||||||
|
|
||||||
QFormLayout* display;
|
QFormLayout* display;
|
||||||
Settings settings;
|
Settings settings;
|
||||||
@ -160,9 +160,9 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
|||||||
if (cache) translationCache->try_emplace(sentence, translation);
|
if (cache) translationCache->try_emplace(sentence, translation);
|
||||||
if (cache && translationCache->size() > savedSize + 50) SaveCache();
|
if (cache && translationCache->size() > savedSize + 50) SaveCache();
|
||||||
|
|
||||||
JSON::Unescape(translation);
|
for (int i = 0; i < translation.size(); ++i) if (translation[i] == '\r' && translation[i + 1] == '\n') translation[i] = 0x200b; // for some reason \r appears as newline - no need to double
|
||||||
sentence += L"\n" + translation;
|
if (!translation.empty()) (sentence += L"\n----\n") += translation;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(assert(Translate(L"こんにちは").second.find(L"ello") != std::wstring::npos));
|
TEST(assert(Translate(L"こんにちは").second.find(L"ello") != std::string::npos));
|
||||||
|
@ -27,23 +27,15 @@ constexpr bool x64 = true;
|
|||||||
constexpr bool x64 = false;
|
constexpr bool x64 = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template <typename T, typename... Xs>
|
template <typename T, typename... Xs> struct ArrayImpl { using Type = std::tuple<T, Xs...>[]; };
|
||||||
struct ArrayImpl { using Type = std::tuple<T, Xs...>[]; };
|
template <typename T> struct ArrayImpl<T> { using Type = T[]; };
|
||||||
template <typename T>
|
template <typename... Ts> using Array = typename ArrayImpl<Ts...>::Type;
|
||||||
struct ArrayImpl<T> { using Type = T[]; };
|
|
||||||
template <typename... Ts>
|
|
||||||
using Array = typename ArrayImpl<Ts...>::Type;
|
|
||||||
|
|
||||||
template <auto F>
|
template <auto F> using Functor = std::integral_constant<std::remove_reference_t<decltype(F)>, F>; // shouldn't need remove_reference_t but MSVC is bugged
|
||||||
using Functor = std::integral_constant<std::remove_reference_t<decltype(F)>, F>;
|
|
||||||
|
|
||||||
template <typename V>
|
|
||||||
struct Identity { V operator()(V v) const { return v; } };
|
|
||||||
|
|
||||||
struct PermissivePointer
|
struct PermissivePointer
|
||||||
{
|
{
|
||||||
template <typename T>
|
template <typename T> operator T*() { return (T*)p; }
|
||||||
operator T*() { return (T*)p; }
|
|
||||||
void* p;
|
void* p;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -77,29 +69,23 @@ public:
|
|||||||
|
|
||||||
Locker Acquire() { return { std::unique_lock(m), contents }; }
|
Locker Acquire() { return { std::unique_lock(m), contents }; }
|
||||||
Locker operator->() { return Acquire(); }
|
Locker operator->() { return Acquire(); }
|
||||||
|
T Copy() { return Acquire().contents; }
|
||||||
T Copy()
|
|
||||||
{
|
|
||||||
return Acquire().contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
T contents;
|
T contents;
|
||||||
M m;
|
M m;
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct
|
static struct // should be inline but MSVC (linker) is bugged
|
||||||
{
|
{
|
||||||
BYTE DUMMY[100];
|
inline static BYTE DUMMY[100];
|
||||||
template <typename T>
|
template <typename T> operator T*() { static_assert(sizeof(T) < sizeof(DUMMY)); return (T*)DUMMY; }
|
||||||
operator T*() { static_assert(sizeof(T) < sizeof(DUMMY)); return (T*)DUMMY; }
|
|
||||||
} DUMMY;
|
} DUMMY;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> std::optional<std::remove_cv_t<T>> Copy(T* ptr) { if (ptr) return *ptr; return {}; }
|
||||||
inline auto FormatArg(T arg) { return arg; }
|
|
||||||
|
|
||||||
template <typename C>
|
template <typename T> inline auto FormatArg(T arg) { return arg; }
|
||||||
inline auto FormatArg(const std::basic_string<C>& arg) { return arg.c_str(); }
|
template <typename C> inline auto FormatArg(const std::basic_string<C>& arg) { return arg.c_str(); }
|
||||||
|
|
||||||
#pragma warning(push)
|
#pragma warning(push)
|
||||||
#pragma warning(disable: 4996)
|
#pragma warning(disable: 4996)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user