diff --git a/GUI/main.cpp b/GUI/main.cpp index 81c08cc..3d26945 100644 --- a/GUI/main.cpp +++ b/GUI/main.cpp @@ -1,7 +1,6 @@ #include "mainwindow.h" #include "module.h" #include -#include extern const wchar_t* UPDATE_AVAILABLE; diff --git a/extensions/bingtranslate.cpp b/extensions/bingtranslate.cpp index 18e0e24..b91474b 100644 --- a/extensions/bingtranslate.cpp +++ b/extensions/bingtranslate.cpp @@ -1,9 +1,9 @@ -#include "network.h" -#include +#include "qtcommon.h" +#include "network.h" extern const wchar_t* TRANSLATION_ERROR; -extern Synchronized translateTo, authKey; +extern Synchronized translateTo, translateFrom, authKey; const char* TRANSLATION_PROVIDER = "Bing Translate"; const char* GET_API_KEY_FROM = "https://www.microsoft.com/en-us/translator/business/trial/#get-started"; @@ -78,6 +78,7 @@ QStringList languages "Welsh: cy", "Yucatec Maya: yua" }; +std::wstring autoDetectLanguage = L"auto-detect"; bool translateSelectedOnly = false, rateLimitAll = true, rateLimitSelected = false, useCache = true; int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 1000; @@ -85,23 +86,26 @@ int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 1000; std::pair Translate(const std::wstring& text) { if (!authKey->empty()) + { + std::wstring translateFromComponent = translateFrom.Copy() == autoDetectLanguage ? L"" : L"&from=" + translateFrom.Copy(); if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"api.cognitive.microsofttranslator.com", L"POST", - FormatString(L"/translate?api-version=3.0&to=%s", translateTo.Copy()).c_str(), + FormatString(L"/translate?api-version=3.0&to=%s%s", translateTo.Copy(), translateFromComponent).c_str(), FormatString(R"([{"text":"%s"}])", JSON::Escape(WideStringToString(text))), FormatString(L"Content-Type: application/json; charset=UTF-8\r\nOcp-Apim-Subscription-Key:%s", authKey.Copy()).c_str() }) if (auto translation = Copy(JSON::Parse(httpRequest.response)[0][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 (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) }; + } if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"www.bing.com", L"POST", - FormatString(L"/ttranslatev3?fromLang=auto-detect&to=%s&text=%s", translateTo.Copy(), Escape(text)).c_str() + FormatString(L"/ttranslatev3?fromLang=%s&to=%s&text=%s", translateFrom.Copy(), translateTo.Copy(), Escape(text)).c_str() }) if (auto translation = Copy(JSON::Parse(httpRequest.response)[0][L"translations"][0][L"text"].String())) return { true, translation.value() }; else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) }; diff --git a/extensions/deepltranslate.cpp b/extensions/deepltranslate.cpp index 846bf2c..63dfed3 100644 --- a/extensions/deepltranslate.cpp +++ b/extensions/deepltranslate.cpp @@ -5,7 +5,7 @@ extern const wchar_t* TRANSLATION_ERROR; extern const char* USE_PREV_SENTENCE_CONTEXT; -extern Synchronized translateTo, authKey; +extern Synchronized translateTo, translateFrom, authKey; const char* TRANSLATION_PROVIDER = "DeepL Translate"; const char* GET_API_KEY_FROM = "https://www.deepl.com/pro.html"; @@ -23,6 +23,7 @@ QStringList languages "Russian: RU", "Spanish: ES", }; +std::wstring autoDetectLanguage = L"auto"; bool translateSelectedOnly = true, rateLimitAll = true, rateLimitSelected = true, useCache = true; int tokenCount = 10, tokenRestoreDelay = 60000, maxSentenceSize = 1000; @@ -33,25 +34,28 @@ int keyType = CAT; std::pair Translate(const std::wstring& text) { if (!authKey->empty()) + { + std::string translateFromComponent = translateFrom.Copy() == autoDetectLanguage ? "" : "&source_lang=" + WideStringToString(translateFrom.Copy()); if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"api.deepl.com", L"POST", keyType == CAT ? L"/v1/translate" : L"/v2/translate", - FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), authKey.Copy(), translateTo.Copy()), + FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), authKey.Copy(), translateTo.Copy()) + translateFromComponent, L"Content-Type: application/x-www-form-urlencoded" }; httpRequest && (!httpRequest.response.empty() || (httpRequest = HttpRequest{ L"Mozilla/5.0 Textractor", L"api.deepl.com", L"POST", (keyType = !keyType) == CAT ? L"/v1/translate" : L"/v2/translate", - FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), authKey.Copy(), translateTo.Copy()), + FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), authKey.Copy(), translateTo.Copy()) + translateFromComponent, L"Content-Type: application/x-www-form-urlencoded" }))) // Response formatted as JSON: translation starts with text":" and ends with "}] 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 (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 int64_t r = _time64(nullptr), n = std::count(text.begin(), text.end(), L'i') + 1; @@ -68,7 +72,7 @@ std::pair Translate(const std::wstring& text) "timestamp": %lld, "lang": { "target_lang": "%S", - "source_lang_user_selected": "auto" + "source_lang_user_selected": "%S" }, "jobs": [{ "raw_en_sentence": "%s", @@ -80,7 +84,7 @@ std::pair Translate(const std::wstring& text) }] } } - )", id, r + (n - r % n), translateTo.Copy(), JSON::Escape(WideStringToString(text))); + )", id, r + (n - r % n), translateTo.Copy(), translateFrom.Copy(), JSON::Escape(WideStringToString(text))); // missing accept-encoding header since it fucks up HttpRequest if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", diff --git a/extensions/devtoolsdeepltranslate.cpp b/extensions/devtoolsdeepltranslate.cpp index 8950d9a..affad8b 100644 --- a/extensions/devtoolsdeepltranslate.cpp +++ b/extensions/devtoolsdeepltranslate.cpp @@ -3,7 +3,7 @@ #include extern const wchar_t* TRANSLATION_ERROR; -extern Synchronized translateTo; +extern Synchronized translateTo, translateFrom; extern QFormLayout* display; extern Settings settings; @@ -34,6 +34,7 @@ QStringList languages "Russian: ru", "Spanish: es", }; +std::wstring autoDetectLanguage = L"auto"; BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { @@ -115,10 +116,16 @@ std::pair Translate(const std::wstring& text) // DevTools can't handle concurrent translations yet static std::mutex translationMutex; std::scoped_lock lock(translationMutex); - DevTools::SendRequest(L"Page.navigate", FormatString(LR"({"url":"https://www.deepl.com/translator#any/%s/%s"})", translateTo.Copy(), Escape(text))); + DevTools::SendRequest(L"Page.navigate", FormatString(LR"({"url":"https://www.deepl.com/translator#%s/%s/%s"})", translateFrom.Copy(), translateTo.Copy(), Escape(text))); for (int retry = 0; ++retry < 100; Sleep(100)) - if (auto translation = Copy(DevTools::SendRequest( - L"Runtime.evaluate", LR"({"expression":"document.querySelector('#target-dummydiv').innerHTML","returnByValue":true})")[L"result"][L"value"].String()) - ) if (!translation->empty()) return { true, translation.value() }; + if (auto translation = Copy( + DevTools::SendRequest(L"Runtime.evaluate", LR"({"expression":"document.querySelector('#target-dummydiv').innerHTML","returnByValue":true})")[L"result"][L"value"].String() + )) if (!translation->empty()) return { true, translation.value() }; + if (auto errorMessage = Copy( + DevTools::SendRequest( + L"Runtime.evaluate", + LR"({"expression":"document.querySelector('div.lmt__system_notification').innerHTML","returnByValue":true})" + )[L"result"][L"value"].String() + )) return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, errorMessage.value()) }; return { false, TRANSLATION_ERROR }; -} \ No newline at end of file +} diff --git a/extensions/extrawindow.cpp b/extensions/extrawindow.cpp index 9801b27..92be065 100644 --- a/extensions/extrawindow.cpp +++ b/extensions/extrawindow.cpp @@ -42,11 +42,10 @@ QColor colorPrompt(QWidget* parent, QColor default, const QString& title, bool c return color; } -struct PrettyWindow : QDialog +struct PrettyWindow : QDialog, Localizer { PrettyWindow(const char* name) { - Localize(); ui.setupUi(this); ui.display->setGraphicsEffect(&outliner); setWindowFlags(Qt::FramelessWindowHint); @@ -159,7 +158,7 @@ public: PrettyWindow("Extra Window") { ui.display->setTextFormat(Qt::PlainText); - if (settings.contains(WINDOW) && QGuiApplication::screenAt(settings.value(WINDOW).toRect().bottomRight())) setGeometry(settings.value(WINDOW).toRect()); + if (settings.contains(WINDOW) && QApplication::screenAt(settings.value(WINDOW).toRect().bottomRight())) setGeometry(settings.value(WINDOW).toRect()); maxSentenceSize = settings.value(MAX_SENTENCE_SIZE, maxSentenceSize).toInt(); for (auto [name, default, slot] : Array{ diff --git a/extensions/googletranslate.cpp b/extensions/googletranslate.cpp index dfbb178..bf7f948 100644 --- a/extensions/googletranslate.cpp +++ b/extensions/googletranslate.cpp @@ -4,7 +4,7 @@ extern const wchar_t* TRANSLATION_ERROR; -extern Synchronized translateTo, authKey; +extern Synchronized translateTo, translateFrom, authKey; const char* TRANSLATION_PROVIDER = "Google Translate"; const char* GET_API_KEY_FROM = "https://codelabs.developers.google.com/codelabs/cloud-translation-intro"; @@ -120,6 +120,7 @@ QStringList languages "Yoruba: yo", "Zulu: zu" }; +std::wstring autoDetectLanguage = L"auto"; bool translateSelectedOnly = false, rateLimitAll = true, rateLimitSelected = false, useCache = true; int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 1000; @@ -127,23 +128,28 @@ int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 1000; std::pair Translate(const std::wstring& text) { if (!authKey->empty()) + { + std::wstring translateFromComponent = translateFrom.Copy() == autoDetectLanguage ? L"" : L"&source=" + translateFrom.Copy(); if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"translation.googleapis.com", L"POST", - FormatString(L"/language/translate/v2?format=text&target=%s&key=%s", translateTo.Copy(), authKey.Copy()).c_str(), + FormatString(L"/language/translate/v2?format=text&target=%s&key=%s%s", translateTo.Copy(), authKey.Copy(), translateFromComponent).c_str(), 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() }; else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) }; else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) }; + } if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"translate.google.com", L"POST", L"/_/TranslateWebserverUi/data/batchexecute?rpcids=MkEWBc", - "f.req=" + Escape(WideStringToString(FormatString(LR"([[["MkEWBc","[[\"%s\",\"auto\",\"%s\",true],[null]]",null,"generic"]]])", JSON::Escape((JSON::Escape(text))), translateTo.Copy()))), + "f.req=" + Escape(WideStringToString( + FormatString(LR"([[["MkEWBc","[[\"%s\",\"%s\",\"%s\",true],[null]]",null,"generic"]]])", JSON::Escape((JSON::Escape(text))), translateFrom.Copy(), translateTo.Copy()) + )), L"Content-Type: application/x-www-form-urlencoded" }) { diff --git a/extensions/lua.cpp b/extensions/lua.cpp index 0188896..5b102f3 100644 --- a/extensions/lua.cpp +++ b/extensions/lua.cpp @@ -41,13 +41,12 @@ bool logErrors = true; Synchronized script; std::atomic revCount = 0; -class Window : public QDialog +class Window : public QDialog, Localizer { public: Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint) { - Localize(); connect(&loadButton, &QPushButton::clicked, this, &Window::LoadScript); if (scriptEditor.toPlainText().isEmpty()) scriptEditor.setPlainText(LUA_INTRO); diff --git a/extensions/regexfilter.cpp b/extensions/regexfilter.cpp index f0d5c8b..56a174d 100644 --- a/extensions/regexfilter.cpp +++ b/extensions/regexfilter.cpp @@ -16,13 +16,12 @@ std::wstring replace; std::shared_mutex m; DWORD (*GetSelectedProcessId)() = nullptr; -class Window : public QDialog +class Window : public QDialog, Localizer { public: Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint) { - Localize(); ui.setupUi(this); connect(ui.regexEdit, &QLineEdit::textEdited, this, &Window::SetRegex); diff --git a/extensions/styler.cpp b/extensions/styler.cpp index 433024b..6b15e1d 100644 --- a/extensions/styler.cpp +++ b/extensions/styler.cpp @@ -1,20 +1,18 @@ #include "qtcommon.h" #include "extension.h" #include -#include #include extern const char* LOAD_SCRIPT; constexpr auto STYLE_SAVE_FILE = u8"Textractor.css"; -class Window : public QDialog +class Window : public QDialog, Localizer { public: Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint) { - Localize(); connect(&loadButton, &QPushButton::clicked, this, &Window::LoadScript); if (scriptEditor.toPlainText().isEmpty()) scriptEditor.setPlainText("/*https://doc.qt.io/qt-5/stylesheet-syntax.html*/"); diff --git a/extensions/threadlinker.cpp b/extensions/threadlinker.cpp index aa7b93b..0097171 100644 --- a/extensions/threadlinker.cpp +++ b/extensions/threadlinker.cpp @@ -11,13 +11,12 @@ extern const char* HEXADECIMAL; std::unordered_map> linkedTextHandles; std::shared_mutex m; -class Window : public QDialog +class Window : public QDialog, Localizer { public: Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint) { - Localize(); connect(&linkButton, &QPushButton::clicked, this, &Window::Link); layout.addWidget(&linkList); diff --git a/extensions/translatewrapper.cpp b/extensions/translatewrapper.cpp index 1f97bc1..7d2d699 100644 --- a/extensions/translatewrapper.cpp +++ b/extensions/translatewrapper.cpp @@ -8,6 +8,7 @@ extern const char* NATIVE_LANGUAGE; extern const char* TRANSLATE_TO; +extern const char* TRANSLATE_FROM; extern const char* TRANSLATE_SELECTED_THREAD_ONLY; extern const char* RATE_LIMIT_ALL_THREADS; extern const char* RATE_LIMIT_SELECTED_THREAD; @@ -21,16 +22,18 @@ extern const wchar_t* TOO_MANY_TRANS_REQUESTS; extern const char* TRANSLATION_PROVIDER; extern const char* GET_API_KEY_FROM; extern QStringList languages; +extern std::wstring autoDetectLanguage; extern bool translateSelectedOnly, rateLimitAll, rateLimitSelected, useCache; extern int tokenCount, tokenRestoreDelay, maxSentenceSize; std::pair Translate(const std::wstring& text); +// backwards compatibility const char* LANGUAGE = u8"Language"; const std::string TRANSLATION_CACHE_FILE = FormatString("%s Translation Cache.txt", TRANSLATION_PROVIDER); QFormLayout* display; Settings settings; -Synchronized translateTo = L"en", authKey; +Synchronized translateTo = L"en", translateFrom = L"auto", authKey; namespace { @@ -46,27 +49,38 @@ namespace } } -class Window : public QDialog +class Window : public QDialog, Localizer { public: Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint) { - Localize(); display = new QFormLayout(this); settings.beginGroup(TRANSLATION_PROVIDER); - auto languageCombo = new QComboBox(this); - languageCombo->addItems(languages); + auto translateToCombo = new QComboBox(this); + translateToCombo->addItems(languages); int language = -1; - if (settings.contains(LANGUAGE)) language = languageCombo->findText(settings.value(LANGUAGE).toString(), Qt::MatchEndsWith); - if (language < 0) language = languageCombo->findText(NATIVE_LANGUAGE, Qt::MatchStartsWith); - if (language < 0) language = languageCombo->findText("English", Qt::MatchStartsWith); - languageCombo->setCurrentIndex(language); - saveLanguage(languageCombo->currentText()); - display->addRow(TRANSLATE_TO, languageCombo); - connect(languageCombo, &QComboBox::currentTextChanged, this, &Window::saveLanguage); + if (settings.contains(LANGUAGE)) language = translateToCombo->findText(settings.value(LANGUAGE).toString(), Qt::MatchEndsWith); + if (settings.contains(TRANSLATE_TO)) language = translateToCombo->findText(settings.value(TRANSLATE_TO).toString(), Qt::MatchEndsWith); + if (language < 0) language = translateToCombo->findText(NATIVE_LANGUAGE, Qt::MatchStartsWith); + if (language < 0) language = translateToCombo->findText("English", Qt::MatchStartsWith); + translateToCombo->setCurrentIndex(language); + SaveTranslateTo(translateToCombo->currentText()); + display->addRow(TRANSLATE_TO, translateToCombo); + connect(translateToCombo, &QComboBox::currentTextChanged, this, &Window::SaveTranslateTo); + languages.push_front("?: " + S(autoDetectLanguage)); + auto translateFromCombo = new QComboBox(this); + translateFromCombo->addItems(languages); + language = -1; + if (settings.contains(TRANSLATE_FROM)) language = translateFromCombo->findText(settings.value(TRANSLATE_FROM).toString(), Qt::MatchEndsWith); + if (language < 0) language = translateFromCombo->findText(NATIVE_LANGUAGE, Qt::MatchStartsWith); + if (language < 0) language = translateFromCombo->findText("?", Qt::MatchStartsWith); + translateFromCombo->setCurrentIndex(language); + SaveTranslateFrom(translateFromCombo->currentText()); + display->addRow(TRANSLATE_FROM, translateFromCombo); + connect(translateFromCombo, &QComboBox::currentTextChanged, this, &Window::SaveTranslateFrom); for (auto [value, label] : Array{ { translateSelectedOnly, TRANSLATE_SELECTED_THREAD_ONLY }, { rateLimitAll, RATE_LIMIT_ALL_THREADS }, @@ -123,9 +137,13 @@ public: } private: - void saveLanguage(QString language) + void SaveTranslateTo(QString language) { - settings.setValue(LANGUAGE, S(translateTo->assign(S(language.split(": ")[1])))); + settings.setValue(TRANSLATE_TO, S(translateTo->assign(S(language.split(": ")[1])))); + } + void SaveTranslateFrom(QString language) + { + settings.setValue(TRANSLATE_FROM, S(translateFrom->assign(S(language.split(": ")[1])))); } } window; diff --git a/include/qtcommon.h b/include/qtcommon.h index 6cd3d91..bb8ceba 100644 --- a/include/qtcommon.h +++ b/include/qtcommon.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,7 @@ constexpr auto WINDOW = u8"Window"; struct Settings : QSettings { Settings(QObject* parent = nullptr) : QSettings(CONFIG_FILE, QSettings::IniFormat, parent) {} }; struct QTextFile : QFile { QTextFile(QString name, QIODevice::OpenMode mode) : QFile(name) { open(mode | QIODevice::Text); } }; +struct Localizer { Localizer() { Localize(); } }; inline std::wstring S(const QString& s) { return { s.toStdWString() }; } inline QString S(const std::string& s) { return QString::fromStdString(s); } inline QString S(const std::wstring& s) { return QString::fromStdWString(s); } diff --git a/text.cpp b/text.cpp index 0757595..a051545 100644 --- a/text.cpp +++ b/text.cpp @@ -134,6 +134,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* TRANSLATE_TO = u8"Translate to"; +const char* TRANSLATE_FROM = u8"Translate from"; const char* TRANSLATE_SELECTED_THREAD_ONLY = u8"Translate selected text thread only"; const char* RATE_LIMIT_ALL_THREADS = u8"Rate limit all text threads"; const char* RATE_LIMIT_SELECTED_THREAD = u8"Rate limit selected text thread";