reorganize translation

This commit is contained in:
Akash Mozumdar 2019-06-13 04:01:29 -04:00
parent e7fff79f8f
commit 76804dd0aa
10 changed files with 252 additions and 245 deletions

View File

@ -4,11 +4,11 @@ find_qt5(Core Widgets)
cmake_policy(SET CMP0037 OLD)
add_library(Bing\ Translate MODULE bingtranslate.cpp extensionimpl.cpp)
add_library(Bing\ Translate MODULE bingtranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
add_library(Copy\ to\ Clipboard MODULE copyclipboard.cpp extensionimpl.cpp)
add_library(Extra\ Newlines MODULE extranewlines.cpp extensionimpl.cpp)
add_library(Extra\ Window MODULE extrawindow.cpp extensionimpl.cpp)
add_library(Google\ Translate MODULE googletranslate.cpp extensionimpl.cpp)
add_library(Google\ Translate MODULE googletranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
add_library(Lua MODULE lua.cpp extensionimpl.cpp)
add_library(Regex\ Filter MODULE regexfilter.cpp extensionimpl.cpp)
add_library(Remove\ Repetition MODULE removerepeat.cpp extensionimpl.cpp)

View File

@ -1,13 +1,10 @@
#include "extension.h"
#include "network.h"
#include <QInputDialog>
#include <QTimer>
#include <QStringList>
extern const char* SELECT_LANGUAGE;
extern const wchar_t* TOO_MANY_TRANS_REQUESTS;
extern const wchar_t* TRANSLATION_ERROR;
extern const char* BING_PROMPT;
const char* TRANSLATION_PROVIDER = "Bing";
QStringList languages
{
"English: en",
@ -56,85 +53,29 @@ QStringList languages
"Vietnamese: vi",
"Welsh: cy"
};
Synchronized<std::wstring> translateTo = L"en";
std::wstring translateTo = L"en";
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
std::pair<bool, std::wstring> Translate(const std::wstring& text)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
QTimer::singleShot(0, []
{
translateTo = QInputDialog::getItem(
nullptr,
SELECT_LANGUAGE,
BING_PROMPT,
languages,
0, false, nullptr,
Qt::WindowCloseButtonHint
).split(" ")[1].toStdWString();
});
}
break;
case DLL_PROCESS_DETACH:
{
}
break;
}
return TRUE;
std::wstring translateFrom;
if (HttpRequest httpRequest{
L"Mozilla/5.0 Textractor",
L"www.bing.com",
L"POST",
FormatString(L"/tdetect?text=%s", Escape(text)).c_str(),
WINHTTP_FLAG_ESCAPE_DISABLE | WINHTTP_FLAG_SECURE,
}) translateFrom = httpRequest.response;
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
if (HttpRequest httpRequest{
L"Mozilla/5.0 Textractor",
L"www.bing.com",
L"POST",
FormatString(L"/ttranslate?from=%s&to=%s&text=%s", translateFrom, translateTo->c_str(), Escape(text)).c_str(),
WINHTTP_FLAG_ESCAPE_DISABLE | WINHTTP_FLAG_SECURE,
})
// Response formatted as JSON: translation starts with :" and ends with "}
if (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L":\"(.+)\"\\}"))) return { true, results[1] };
else return { false, TRANSLATION_ERROR };
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
}
// This function detects language and returns it if translateFrom is empty
std::wstring Translate(const std::wstring& text, std::wstring translateFrom, std::wstring translateTo)
{
static std::atomic<HINTERNET> internet = NULL;
if (!internet) internet = WinHttpOpen(L"Mozilla/5.0 Textractor", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0);
std::wstring escapedText;
for (unsigned char ch : WideStringToString(text))
{
wchar_t escapedChar[4] = {};
swprintf_s<4>(escapedChar, L"%%%02X", (int)ch);
escapedText += escapedChar;
}
std::wstring location = translateFrom.empty()
? L"/tdetect?text=" + escapedText
: L"/ttranslate?from=" + translateFrom + L"&to=" + translateTo + L"&text=" + escapedText;
std::wstring translation;
if (internet)
if (InternetHandle connection = WinHttpConnect(internet, L"www.bing.com", INTERNET_DEFAULT_HTTPS_PORT, 0))
if (InternetHandle request = WinHttpOpenRequest(connection, L"POST", location.c_str(), NULL, NULL, NULL, WINHTTP_FLAG_ESCAPE_DISABLE | WINHTTP_FLAG_SECURE))
if (WinHttpSendRequest(request, NULL, 0, NULL, 0, 0, NULL))
if (auto response = ReceiveHttpRequest(request))
if (translateFrom.empty()) translation = response.value();
// Response formatted as JSON: translation starts with :" and ends with "}
else if (std::wsmatch results; std::regex_search(response.value(), results, std::wregex(L":\"(.+)\"\\}"))) translation = results[1];
Unescape(translation);
return translation;
}
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{
if (sentenceInfo["text number"] == 0) return false;
static RateLimiter rateLimiter(30, 60 * 1000);
std::wstring translation;
if (!(rateLimiter.Request() || sentenceInfo["current select"])) translation = TOO_MANY_TRANS_REQUESTS;
else translation = Translate(sentence, Translate(sentence, L"", translateTo), translateTo);
if (translation.empty()) translation = TRANSLATION_ERROR;
sentence += L"\n" + translation;
return true;
}
TEST(
{
std::wstring test = L"こんにちは";
ProcessSentence(test, { SentenceInfo::DUMMY });
assert(test.find(L"Hello") != std::wstring::npos);
}
);

View File

@ -1,15 +1,12 @@
#include "extension.h"
#include "util.h"
#include "network.h"
#include "util.h"
#include <ctime>
#include <QInputDialog>
#include <QTimer>
#include <QStringList>
extern const char* SELECT_LANGUAGE;
extern const wchar_t* TOO_MANY_TRANS_REQUESTS;
extern const wchar_t* TRANSLATION_ERROR;
extern const char* GOOGLE_PROMPT;
const char* TRANSLATION_PROVIDER = "Google";
QStringList languages
{
"English: en",
@ -73,49 +70,19 @@ QStringList languages
"Yiddish: yi",
"Zulu: zu"
};
std::wstring translateTo = L"en";
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
QTimer::singleShot(0, []
{
translateTo = QInputDialog::getItem(
nullptr,
SELECT_LANGUAGE,
GOOGLE_PROMPT,
languages,
0, false, nullptr,
Qt::WindowCloseButtonHint
).split(" ")[1].toStdWString();
});
}
break;
case DLL_PROCESS_DETACH:
{
}
break;
}
return TRUE;
}
Synchronized<std::wstring> translateTo = L"en";
std::wstring GetTranslationUri(const std::wstring& text, unsigned TKK)
{
// If no TKK available, use this uri. Can't use too much or google will detect unauthorized access
if (!TKK) return L"/translate_a/single?client=gtx&dt=ld&dt=rm&dt=t&tl=" + translateTo + L"&q=" + text;
if (!TKK) return FormatString(L"/translate_a/single?client=gtx&dt=ld&dt=rm&dt=t&tl=%s&q=%s", translateTo->c_str(), text);
// Artikash 8/19/2018: reverse engineered from translate.google.com
std::wstring escapedText;
unsigned a = _time64(NULL) / 3600, b = a; // <- the first part of TKK
for (unsigned char ch : WideStringToString(text))
{
wchar_t escapedChar[4] = {};
swprintf_s<4>(escapedChar, L"%%%02X", (int)ch);
escapedText += escapedChar;
escapedText += FormatString(L"%%%02X", (int)ch);
a += ch;
a += a << 10;
a ^= a >> 6;
@ -127,56 +94,28 @@ std::wstring GetTranslationUri(const std::wstring& text, unsigned TKK)
a %= 1000000;
b ^= a;
return L"/translate_a/single?client=t&dt=ld&dt=rm&dt=t&tl=" + translateTo + L"&tk=" + std::to_wstring(a) + L"." + std::to_wstring(b) + L"&q=" + escapedText;
return FormatString(L"/translate_a/single?client=t&dt=ld&dt=rm&dt=t&tl=%s&tk=%u.%u&q=%s", translateTo->c_str(), a, b, escapedText);
}
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
std::pair<bool, std::wstring> Translate(const std::wstring& text)
{
if (sentenceInfo["text number"] == 0) return false;
static unsigned TKK = 0;
if (!TKK)
if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"translate.google.com", L"GET", L"/" })
if (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"(\\d{7,})'")))
_InterlockedCompareExchange(&TKK, stoll(results[1]), 0);
static std::atomic<HINTERNET> internet = NULL;
if (!internet) internet = WinHttpOpen(L"Mozilla/5.0 Textractor", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0);
static std::atomic<unsigned> TKK = 0;
static RateLimiter rateLimiter(30, 60 * 1000);
std::wstring translation;
if (!(rateLimiter.Request() || sentenceInfo["current select"])) translation = TOO_MANY_TRANS_REQUESTS;
else if (internet)
if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"translate.google.com", L"GET", GetTranslationUri(text, TKK).c_str() })
{
if (!TKK)
if (InternetHandle connection = WinHttpConnect(internet, L"translate.google.com", INTERNET_DEFAULT_HTTPS_PORT, 0))
if (InternetHandle request = WinHttpOpenRequest(connection, L"GET", L"/", NULL, NULL, NULL, WINHTTP_FLAG_SECURE))
if (WinHttpSendRequest(request, NULL, 0, NULL, 0, 0, NULL))
if (auto response = ReceiveHttpRequest(request))
if (std::wsmatch results; std::regex_search(response.value(), results, std::wregex(L"(\\d{7,})'"))) TKK = stoll(results[1]);
if (InternetHandle connection = WinHttpConnect(internet, L"translate.google.com", INTERNET_DEFAULT_HTTPS_PORT, 0))
if (InternetHandle request = WinHttpOpenRequest(connection, L"GET", GetTranslationUri(sentence, TKK).c_str(), NULL, NULL, NULL, WINHTTP_FLAG_ESCAPE_DISABLE | WINHTTP_FLAG_SECURE))
if (WinHttpSendRequest(request, NULL, 0, NULL, 0, 0, NULL))
if (auto response = ReceiveHttpRequest(request))
// Response formatted as JSON: starts with [[["
if (response.value()[0] == L'[')
{
for (std::wsmatch results; std::regex_search(response.value(), results, std::wregex(L"\\[\"(.*?)\",[n\"]")); response = results.suffix())
translation += std::wstring(results[1]) + L" ";
Unescape(translation);
}
else
{
translation = TRANSLATION_ERROR + (L" (TKK=" + std::to_wstring(TKK) + L")");
TKK = 0;
}
// Response formatted as JSON: starts with "[[[" and translation is enclosed in quotes followed by a comma
if (httpRequest.response[0] == L'[')
{
std::wstring translation;
for (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"\\[\"(.*?)\",[n\"]")); httpRequest.response = results.suffix())
translation += std::wstring(results[1]) + L" ";
if (!translation.empty()) return { true, translation };
}
return { false, FormatString(L"%s (TKK=%u)", TRANSLATION_ERROR, _InterlockedExchange(&TKK, 0)) };
}
if (translation.empty()) translation = TRANSLATION_ERROR;
sentence += L"\n" + translation;
return true;
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
}
TEST(
{
std::wstring test = L"こんにちは";
ProcessSentence(test, { SentenceInfo::DUMMY });
assert(test.find(L"Hello") != std::wstring::npos);
}
);

View File

@ -113,7 +113,7 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
luaL_dostring(L, "ProcessSentence = nil");
if (luaL_dostring(L, script->c_str()) != LUA_OK)
{
sentence += L"\n" + FormatWideString(LUA_ERROR, StringToWideString(lua_tolstring(L, 1, nullptr)).c_str());
sentence += L"\n" + FormatString(LUA_ERROR, StringToWideString(lua_tolstring(L, 1, nullptr)));
lua_settop(L, 0);
return logErrors;
}
@ -121,7 +121,7 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
if (lua_getglobal(L, "ProcessSentence") != LUA_TFUNCTION)
{
sentence += L"\n" + FormatWideString(LUA_ERROR, L"ProcessSentence is not a function");
sentence += L"\n" + FormatString(LUA_ERROR, L"ProcessSentence is not a function");
lua_settop(L, 0);
return logErrors;
}
@ -135,7 +135,7 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
}
if (lua_pcallk(L, 2, 1, 0, NULL, NULL) != LUA_OK)
{
sentence += L"\n" + FormatWideString(LUA_ERROR, StringToWideString(lua_tolstring(L, 1, nullptr)).c_str());
sentence += L"\n" + FormatString(LUA_ERROR, StringToWideString(lua_tolstring(L, 1, nullptr)));
lua_settop(L, 0);
return logErrors;
}

66
extensions/network.cpp Normal file
View File

@ -0,0 +1,66 @@
#include "network.h"
#include "util.h"
HttpRequest::HttpRequest(
const wchar_t* agentName,
const wchar_t* serverName,
const wchar_t* action,
const wchar_t* objectName,
DWORD requestFlags,
const wchar_t* httpVersion,
const wchar_t* referrer,
const wchar_t** acceptTypes,
const wchar_t* headers,
void* body,
DWORD bodyLength
)
{
static std::atomic<HINTERNET> internet = NULL;
if (!internet) internet = WinHttpOpen(agentName, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0);
if (internet)
if (InternetHandle connection = WinHttpConnect(internet, serverName, INTERNET_DEFAULT_HTTPS_PORT, 0))
if (InternetHandle request = WinHttpOpenRequest(connection, action, objectName, httpVersion, referrer, acceptTypes, requestFlags))
if (WinHttpSendRequest(request, headers, -1UL, body, bodyLength, bodyLength, NULL))
{
WinHttpReceiveResponse(request, NULL);
std::string data;
DWORD availableSize, downloadedSize;
do
{
availableSize = 0;
WinHttpQueryDataAvailable(request, &availableSize);
if (!availableSize) break;
std::vector<char> buffer(availableSize);
WinHttpReadData(request, buffer.data(), availableSize, &downloadedSize);
data.append(buffer.data(), downloadedSize);
} while (availableSize > 0);
response = StringToWideString(data);
this->connection = std::move(connection);
this->request = std::move(request);
}
else errorCode = GetLastError();
else errorCode = GetLastError();
else errorCode = GetLastError();
else errorCode = GetLastError();
}
std::wstring Escape(const std::wstring& text)
{
std::wstring escaped;
for (unsigned char ch : WideStringToString(text)) escaped += FormatString(L"%%%02X", (int)ch);
return escaped;
}
void Unescape(std::wstring& text)
{
for (int i = 0; i < text.size(); ++i)
{
if (text[i] == L'\\')
{
text[i] = 0x200b;
if (text[i + 1] == L'r') text[i + 1] = 0x200b; // 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';
}
}
}

View File

@ -1,60 +1,32 @@
#pragma once
#include "util.h"
#include "common.h"
#include <winhttp.h>
using InternetHandle = AutoHandle<Functor<WinHttpCloseHandle>>;
inline std::optional<std::wstring> ReceiveHttpRequest(HINTERNET request)
struct HttpRequest
{
WinHttpReceiveResponse(request, NULL);
std::string data;
DWORD dwSize, dwDownloaded;
do
{
dwSize = 0;
WinHttpQueryDataAvailable(request, &dwSize);
if (!dwSize) break;
std::vector<char> buffer(dwSize);
WinHttpReadData(request, buffer.data(), dwSize, &dwDownloaded);
data.append(buffer.data(), dwDownloaded);
} while (dwSize > 0);
HttpRequest(
const wchar_t* agentName,
const wchar_t* serverName,
const wchar_t* action,
const wchar_t* objectName,
DWORD requestFlags = WINHTTP_FLAG_SECURE | WINHTTP_FLAG_ESCAPE_DISABLE,
const wchar_t* httpVersion = NULL,
const wchar_t* referrer = NULL,
const wchar_t** acceptTypes = NULL,
const wchar_t* headers = NULL,
void* body = NULL,
DWORD bodyLength = 0
);
operator bool() { return errorCode == ERROR_SUCCESS; }
if (data.empty()) return {};
return StringToWideString(data);
}
inline void Unescape(std::wstring& text)
{
for (int i = 0; i < text.size(); ++i)
{
if (text[i] == L'\\')
{
text[i] = 0x200b;
if (text[i + 1] == L'r') text[i + 1] = 0x200b; // 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';
}
}
}
class RateLimiter
{
public:
RateLimiter(int tokenCount, int delay) : tokenCount(tokenCount), delay(delay) {}
bool Request()
{
auto tokens = this->tokens.Acquire();
tokens->push_back(GetTickCount());
if (tokens->size() > tokenCount * 5) tokens->erase(tokens->begin(), tokens->begin() + tokenCount * 3);
tokens->erase(std::remove_if(tokens->begin(), tokens->end(), [this](DWORD token) { return GetTickCount() - token > delay; }), tokens->end());
return tokens->size() < tokenCount;
}
const int tokenCount, delay;
private:
Synchronized<std::vector<DWORD>> tokens;
std::wstring response;
InternetHandle connection = NULL;
InternetHandle request = NULL;
DWORD errorCode = ERROR_SUCCESS;
};
std::wstring Escape(const std::wstring& text);
void Unescape(std::wstring& text);

View File

@ -11,6 +11,7 @@ extern const char* THREAD_LINKER;
extern const char* LINK;
extern const char* THREAD_LINK_FROM;
extern const char* THREAD_LINK_TO;
extern const char* HEXADECIMAL;
std::mutex m;
std::unordered_map<int64_t, std::unordered_multiset<int64_t>> linkedTextHandles;
@ -29,8 +30,8 @@ struct : QMainWindow
connect(addLink, &QPushButton::clicked, [=]
{
bool ok1, ok2, ok3, ok4;
int from = QInputDialog::getText(this, THREAD_LINK_FROM, "", QLineEdit::Normal, "0x", &ok1, Qt::WindowCloseButtonHint).toInt(&ok2, 16);
int to = QInputDialog::getText(this, THREAD_LINK_TO, "", QLineEdit::Normal, "0x", &ok3, Qt::WindowCloseButtonHint).toInt(&ok4, 16);
int from = QInputDialog::getText(this, THREAD_LINK_FROM, HEXADECIMAL, QLineEdit::Normal, "", &ok1, Qt::WindowCloseButtonHint).toInt(&ok2, 16);
int to = QInputDialog::getText(this, THREAD_LINK_TO, HEXADECIMAL, QLineEdit::Normal, "", &ok3, Qt::WindowCloseButtonHint).toInt(&ok4, 16);
if (ok1 && ok2 && ok3 && ok4)
{
std::lock_guard l(m);

View File

@ -0,0 +1,86 @@
#include "extension.h"
#include "network.h"
#include <QTimer>
#include <QInputDialog>
extern const char* SELECT_LANGUAGE;
extern const char* SELECT_LANGUAGE_MESSAGE;
extern const wchar_t* TOO_MANY_TRANS_REQUESTS;
extern const char* TRANSLATION_PROVIDER;
extern QStringList languages;
extern Synchronized<std::wstring> translateTo;
std::pair<bool, std::wstring> Translate(const std::wstring& text);
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
QTimer::singleShot(0, []
{
translateTo->assign(QInputDialog::getItem(
nullptr,
SELECT_LANGUAGE,
QString(SELECT_LANGUAGE_MESSAGE).arg(TRANSLATION_PROVIDER),
languages,
0,
false,
nullptr,
Qt::WindowCloseButtonHint)
.split(": ")[1]
.toStdWString()
);
});
}
break;
case DLL_PROCESS_DETACH:
{
}
break;
}
return TRUE;
}
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{
if (sentenceInfo["text number"] == 0) return false;
static class
{
public:
bool Request()
{
auto tokens = this->tokens.Acquire();
tokens->push_back(GetTickCount());
if (tokens->size() > tokenCount * 5) tokens->erase(tokens->begin(), tokens->begin() + tokenCount * 3);
tokens->erase(std::remove_if(tokens->begin(), tokens->end(), [this](DWORD token) { return GetTickCount() - token > delay; }), tokens->end());
return tokens->size() < tokenCount;
}
private:
const int tokenCount = 30, delay = 60 * 1000;
Synchronized<std::vector<DWORD>> tokens;
} rateLimiter;
Synchronized<std::unordered_map<std::wstring, std::wstring>> translationCache;
bool cache = false;
std::wstring translation;
if (translationCache->count(sentence) != 0) translation = translationCache->at(sentence);
else if (!(rateLimiter.Request() || sentenceInfo["current select"])) translation = TOO_MANY_TRANS_REQUESTS;
else std::tie(cache, translation) = Translate(sentence);
if (cache) translationCache->insert({ sentence, translation });
Unescape(translation);
sentence += L"\n" + translation;
return true;
}
TEST(
{
std::wstring test = L"こんにちは";
ProcessSentence(test, { SentenceInfo::DUMMY });
assert(test.find(L"Hello") != std::wstring::npos);
}
);

View File

@ -81,33 +81,39 @@ static struct
operator T*() { static_assert(sizeof(T) < sizeof(DUMMY)); return (T*)DUMMY; }
} DUMMY;
template <typename T>
inline auto FormatArg(T arg) { return arg; }
template <typename C>
inline auto FormatArg(const std::basic_string<C>& arg) { return arg.c_str(); }
#pragma warning(push)
#pragma warning(disable: 4996)
template <typename... Args>
inline std::string FormatString(const char* format, Args... args)
{
std::string buffer(snprintf(nullptr, 0, format, args...), '\0');
sprintf(buffer.data(), format, args...);
std::string buffer(snprintf(nullptr, 0, format, FormatArg(args)...), '\0');
sprintf(buffer.data(), format, FormatArg(args)...);
return buffer;
}
template <typename... Args>
inline std::wstring FormatWideString(const wchar_t* format, Args... args)
inline std::wstring FormatString(const wchar_t* format, Args... args)
{
std::wstring buffer(_snwprintf(nullptr, 0, format, args...), L'\0');
_swprintf(buffer.data(), format, args...);
std::wstring buffer(_snwprintf(nullptr, 0, format, FormatArg(args)...), L'\0');
_swprintf(buffer.data(), format, FormatArg(args)...);
return buffer;
}
#pragma warning(pop)
#ifdef _DEBUG
#define TEST(...) inline auto TEST_RUNNER_DUMMY = CreateThread(nullptr, 0, [](auto) { __VA_ARGS__; return 0UL; }, NULL, 0, nullptr);
#define TEST(...) static auto _ = CreateThread(nullptr, 0, [](auto) { __VA_ARGS__; return 0UL; }, NULL, 0, nullptr);
#else
#define TEST(...)
#endif
#ifdef _DEBUG
#define TEST_SYNC(...) inline auto TEST_RUNNER_DUMMY = [] { __VA_ARGS__; return 0UL; }();
#define TEST_SYNC(...) static auto _ = [] { __VA_ARGS__; return 0UL; }();
#else
#define TEST_SYNC(...)
#endif

View File

@ -103,8 +103,7 @@ const char* READ_ERROR = u8"Textractor: Reader ERROR (likely an incorrect R-code
const char* HIJACK_ERROR = u8"Textractor: Hijack ERROR";
const char* COULD_NOT_FIND = u8"Textractor: could not find text";
const char* SELECT_LANGUAGE = u8"Select language";
const char* BING_PROMPT = u8"What language should Bing translate to?";
const char* GOOGLE_PROMPT = u8"What language should Google translate to?";
const char* SELECT_LANGUAGE_MESSAGE = u8"What language should %1 translate to?";
const wchar_t* TOO_MANY_TRANS_REQUESTS = L"Too many translation requests: refuse to make more";
const wchar_t* TRANSLATION_ERROR = L"Error while translating";
const char* EXTRA_WINDOW_INFO = u8R"(Right click to change settings
@ -149,8 +148,9 @@ const char* THREAD_LINKER = u8"Thread Linker";
const char* LINK = u8"Link";
const char* THREAD_LINK_FROM = u8"Thread number to link from";
const char* THREAD_LINK_TO = u8"Thread number to link to";
const char* HEXADECIMAL = u8"Hexadecimal";
inline auto _ = []
static auto _ = []
{
#ifdef TURKISH
ATTACH = u8"Oyuna bağla";
@ -236,8 +236,7 @@ Estoy buscando un nuevo trabajo: por favor envíame un correo si estás contrata
HIJACK_ERROR = u8"Textractor: Hijack ERROR";
COULD_NOT_FIND = u8"Textractor: no se puede encontrar texto";
SELECT_LANGUAGE = u8"Seleccionar lenguaje";
BING_PROMPT = u8"¿A qué idioma debe traducir Bing?";
GOOGLE_PROMPT = u8"¿A qué idioma debe traducir Google?";
SELECT_LANGUAGE_MESSAGE = u8"¿A qué idioma debe traducir %1?";
TOO_MANY_TRANS_REQUESTS = L"Demasiadas peticiones de traducción: no se puede hacer más";
TRANSLATION_ERROR = L"Error al traducir";
EXTRA_WINDOW_INFO = u8R"(Clic derecho para configurar
@ -303,8 +302,7 @@ Clic y arrastra los bordes de la ventana para moverla, o en la esquina inferior
HIJACK_ERROR = u8"Textractor: Hijack 错误";
COULD_NOT_FIND = u8"Textractor: 无法找到文本";
SELECT_LANGUAGE = u8"选择语言";
BING_PROMPT = u8"想要使用 Bing 翻译到哪种语言?";
GOOGLE_PROMPT = u8"想要使用 Google 翻译到哪种语言?";
SELECT_LANGUAGE_MESSAGE = u8"想要使用 %1 翻译到哪种语言?";
TOO_MANY_TRANS_REQUESTS = L"太多翻译请求: 拒绝生成更多";
TRANSLATION_ERROR = L"翻译时出错";
EXTRA_WINDOW_INFO = u8R"(右键修改设置
@ -373,8 +371,7 @@ I'm currently looking for a new job: email me if you know anyone hiring US softw
HIJACK_ERROR = u8"Textractor: Hijack ERROR";
COULD_NOT_FIND = u8"Textractor: невозможно найти текст";
SELECT_LANGUAGE = u8"Выберете язык";
BING_PROMPT = u8"На какой язык переводить в Bing?";
GOOGLE_PROMPT = u8"На какой язык переводить в Google?";
SELECT_LANGUAGE_MESSAGE = u8"На какой язык переводить в %1?";
TOO_MANY_TRANS_REQUESTS = L"Слишком много запросов для перевода: отклонено";
TRANSLATION_ERROR = L"Ошибка при переводе";
EXTRA_WINDOW_INFO = u8R"(Правый клик для изменения настроек
@ -473,8 +470,7 @@ Jika kamu menyukai project ini, tolong sebarluaskan project ini :))";
HIJACK_ERROR = u8"Textractor: Hijack ERROR";
COULD_NOT_FIND = u8"Textractor: tidak dapat menemukan teks";
SELECT_LANGUAGE = u8"Pilih bahasa";
BING_PROMPT = u8"Bahasa apakah yang Bing harus terjemahkan?";
GOOGLE_PROMPT = u8"Bahasa apakah yang Google harus terjemahkan?";
SELECT_LANGUAGE_MESSAGE = u8"Bahasa apakah yang %1 harus terjemahkan?";
TOO_MANY_TRANS_REQUESTS = L"Terlalu banyak permintaan terjemahan: menolak untuk menerjemahkan";
TRANSLATION_ERROR = L"Terjadi kesalahan ketika menerjemahkan";
EXTRA_WINDOW_INFO = u8R"(Klik kanan untuk merubah pengaturan