improve reliability of translations

This commit is contained in:
Akash Mozumdar 2020-12-14 06:26:01 -07:00
parent 1b71b3ee86
commit 268f40771d
9 changed files with 234 additions and 135 deletions

View File

@ -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([]
{ {

View File

@ -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) };
} }

View File

@ -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) };
} }

View File

@ -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);

View File

@ -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) };
} }

View File

@ -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"])")))

View File

@ -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);
}
} }

View File

@ -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));

View File

@ -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)