diff --git a/extensions/CMakeLists.txt b/extensions/CMakeLists.txt index 2d9c70c..a6d2e3b 100644 --- a/extensions/CMakeLists.txt +++ b/extensions/CMakeLists.txt @@ -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) diff --git a/extensions/bingtranslate.cpp b/extensions/bingtranslate.cpp index 02515b6..151425a 100644 --- a/extensions/bingtranslate.cpp +++ b/extensions/bingtranslate.cpp @@ -1,13 +1,10 @@ #include "extension.h" #include "network.h" -#include -#include +#include -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 translateTo = L"en"; -std::wstring translateTo = L"en"; - -BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +std::pair 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 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); - } -); diff --git a/extensions/googletranslate.cpp b/extensions/googletranslate.cpp index 3906fcb..ef2a263 100644 --- a/extensions/googletranslate.cpp +++ b/extensions/googletranslate.cpp @@ -1,15 +1,12 @@ #include "extension.h" -#include "util.h" #include "network.h" +#include "util.h" #include -#include -#include +#include -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 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 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 internet = NULL; - if (!internet) internet = WinHttpOpen(L"Mozilla/5.0 Textractor", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0); - static std::atomic 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); - } -); diff --git a/extensions/lua.cpp b/extensions/lua.cpp index 8449b3f..c1ed1af 100644 --- a/extensions/lua.cpp +++ b/extensions/lua.cpp @@ -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; } diff --git a/extensions/network.cpp b/extensions/network.cpp new file mode 100644 index 0000000..66e68e0 --- /dev/null +++ b/extensions/network.cpp @@ -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 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 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'; + } + } +} diff --git a/extensions/network.h b/extensions/network.h index 2d7026a..90a2653 100644 --- a/extensions/network.h +++ b/extensions/network.h @@ -1,60 +1,32 @@ #pragma once -#include "util.h" +#include "common.h" #include using InternetHandle = AutoHandle>; -inline std::optional 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 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> 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); diff --git a/extensions/threadlinker.cpp b/extensions/threadlinker.cpp index e55ff2b..a78f70f 100644 --- a/extensions/threadlinker.cpp +++ b/extensions/threadlinker.cpp @@ -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> 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); diff --git a/extensions/translatewrapper.cpp b/extensions/translatewrapper.cpp new file mode 100644 index 0000000..af62e06 --- /dev/null +++ b/extensions/translatewrapper.cpp @@ -0,0 +1,86 @@ +#include "extension.h" +#include "network.h" +#include +#include + +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 translateTo; +std::pair 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> tokens; + } rateLimiter; + Synchronized> 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); + } +); diff --git a/include/common.h b/include/common.h index fdb55d7..7624635 100644 --- a/include/common.h +++ b/include/common.h @@ -81,33 +81,39 @@ static struct operator T*() { static_assert(sizeof(T) < sizeof(DUMMY)); return (T*)DUMMY; } } DUMMY; +template +inline auto FormatArg(T arg) { return arg; } + +template +inline auto FormatArg(const std::basic_string& arg) { return arg.c_str(); } + #pragma warning(push) #pragma warning(disable: 4996) template 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 -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 diff --git a/text.cpp b/text.cpp index 122119b..36f5257 100644 --- a/text.cpp +++ b/text.cpp @@ -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