improve translation extension ui, add language detection for papago and systran, add more languages to systran, improve deepl translation and bing error handling
This commit is contained in:
parent
444974ee8a
commit
ac95d873f1
@ -1,100 +1,212 @@
|
||||
#include "qtcommon.h"
|
||||
#include "translatewrapper.h"
|
||||
#include "network.h"
|
||||
|
||||
extern const wchar_t* TRANSLATION_ERROR;
|
||||
|
||||
extern Synchronized<std::wstring> 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";
|
||||
QStringList languages
|
||||
extern const QStringList languagesTo
|
||||
{
|
||||
"Afrikaans: af",
|
||||
"Arabic: ar",
|
||||
"Bangla: bn",
|
||||
"Bosnian: bs",
|
||||
"Bulgarian: bg",
|
||||
"Cantonese (traditional): yue",
|
||||
"Catalan: ca",
|
||||
"Chinese (simplified): zh-Hans",
|
||||
"Chinese (traditional): zh-Hant",
|
||||
"Croatian: hr",
|
||||
"Czech: cs",
|
||||
"Danish: da",
|
||||
"Dutch: nl",
|
||||
"English: en",
|
||||
"Estonian: et",
|
||||
"Fijian: fj",
|
||||
"Filipino: fil",
|
||||
"Finnish: fi",
|
||||
"French: fr",
|
||||
"German: de",
|
||||
"Greek: el",
|
||||
"Haitian Creole: ht",
|
||||
"Hebrew: he",
|
||||
"Hindi: hi",
|
||||
"Hmong Daw: mww",
|
||||
"Hungarian: hu",
|
||||
"Icelandic: is",
|
||||
"Indonesian: id",
|
||||
"Irish: ga",
|
||||
"Italian: it",
|
||||
"Japanese: ja",
|
||||
"Kannada: kn",
|
||||
"Klingon: tlh",
|
||||
"Korean: ko",
|
||||
"Latvian: lv",
|
||||
"Lithuanian: lt",
|
||||
"Malagasy: mg",
|
||||
"Malay: ms",
|
||||
"Malayalam: ml",
|
||||
"Maltese: mt",
|
||||
"Maori: mi",
|
||||
"Norwegian: nb",
|
||||
"Persian: fa",
|
||||
"Polish: pl",
|
||||
"Portuguese (Brazil): pt",
|
||||
"Portuguese (Portugal): pt-pt",
|
||||
"Punjabi: pa",
|
||||
"Romanian: ro",
|
||||
"Russian: ru",
|
||||
"Samoan: sm",
|
||||
"Serbian (Cyrillic): sr-Cyrl",
|
||||
"Serbian (Latin): sr-Latn",
|
||||
"Slovak: sk",
|
||||
"Slovenian: sl",
|
||||
"Spanish: es",
|
||||
"Swahili: sw",
|
||||
"Swedish: sv",
|
||||
"Tahitian: ty",
|
||||
"Tamil: ta",
|
||||
"Telugu: te",
|
||||
"Thai: th",
|
||||
"Tongan: to",
|
||||
"Turkish: tr",
|
||||
"Ukrainian: uk",
|
||||
"Urdu: ur",
|
||||
"Vietnamese: vi",
|
||||
"Welsh: cy",
|
||||
"Yucatec Maya: yua"
|
||||
"Afrikaans",
|
||||
"Albanian",
|
||||
"Amharic",
|
||||
"Arabic",
|
||||
"Armenian",
|
||||
"Assamese",
|
||||
"Azerbaijani",
|
||||
"Bangla",
|
||||
"Bosnian (Latin)",
|
||||
"Bulgarian",
|
||||
"Cantonese (Traditional)",
|
||||
"Catalan",
|
||||
"Chinese (Simplified)",
|
||||
"Chinese (Traditional)",
|
||||
"Croatian",
|
||||
"Czech",
|
||||
"Danish",
|
||||
"Dari",
|
||||
"Dutch",
|
||||
"English",
|
||||
"Estonian",
|
||||
"Fijian",
|
||||
"Filipino",
|
||||
"Finnish",
|
||||
"French",
|
||||
"French (Canada)",
|
||||
"German",
|
||||
"Greek",
|
||||
"Gujarati",
|
||||
"Haitian Creole",
|
||||
"Hebrew",
|
||||
"Hindi",
|
||||
"Hmong Daw",
|
||||
"Hungarian",
|
||||
"Icelandic",
|
||||
"Indonesian",
|
||||
"Inuktitut",
|
||||
"Irish",
|
||||
"Italian",
|
||||
"Japanese",
|
||||
"Kannada",
|
||||
"Kazakh",
|
||||
"Khmer",
|
||||
"Klingon",
|
||||
"Korean",
|
||||
"Kurdish (Central)",
|
||||
"Kurdish (Northern)",
|
||||
"Lao",
|
||||
"Latvian",
|
||||
"Lithuanian",
|
||||
"Malagasy",
|
||||
"Malay",
|
||||
"Malayalam",
|
||||
"Maltese",
|
||||
"Maori",
|
||||
"Marathi",
|
||||
"Myanmar",
|
||||
"Nepali",
|
||||
"Norwegian",
|
||||
"Odia",
|
||||
"Pashto",
|
||||
"Persian",
|
||||
"Polish",
|
||||
"Portuguese (Brazil)",
|
||||
"Portuguese (Portugal)",
|
||||
"Punjabi",
|
||||
"Queretaro Otomi",
|
||||
"Romanian",
|
||||
"Russian",
|
||||
"Samoan",
|
||||
"Serbian (Cyrillic)",
|
||||
"Serbian (Latin)",
|
||||
"Slovak",
|
||||
"Slovenian",
|
||||
"Spanish",
|
||||
"Swahili",
|
||||
"Swedish",
|
||||
"Tahitian",
|
||||
"Tamil",
|
||||
"Telugu",
|
||||
"Thai",
|
||||
"Tigrinya",
|
||||
"Tongan",
|
||||
"Turkish",
|
||||
"Ukrainian",
|
||||
"Urdu",
|
||||
"Vietnamese",
|
||||
"Welsh",
|
||||
"Yucatec Maya"
|
||||
}, languagesFrom = languagesTo;
|
||||
extern const std::unordered_map<std::wstring, std::wstring> codes
|
||||
{
|
||||
{ { L"Afrikaans" }, { L"af" } },
|
||||
{ { L"Albanian" }, { L"sq" } },
|
||||
{ { L"Amharic" }, { L"am" } },
|
||||
{ { L"Arabic" }, { L"ar" } },
|
||||
{ { L"Armenian" }, { L"hy" } },
|
||||
{ { L"Assamese" }, { L"as" } },
|
||||
{ { L"Azerbaijani" }, { L"az" } },
|
||||
{ { L"Bangla" }, { L"bn" } },
|
||||
{ { L"Bosnian (Latin)" }, { L"bs" } },
|
||||
{ { L"Bulgarian" }, { L"bg" } },
|
||||
{ { L"Cantonese (Traditional)" }, { L"yue" } },
|
||||
{ { L"Catalan" }, { L"ca" } },
|
||||
{ { L"Chinese (Simplified)" }, { L"zh-Hans" } },
|
||||
{ { L"Chinese (Traditional)" }, { L"zh-Hant" } },
|
||||
{ { L"Croatian" }, { L"hr" } },
|
||||
{ { L"Czech" }, { L"cs" } },
|
||||
{ { L"Danish" }, { L"da" } },
|
||||
{ { L"Dari" }, { L"prs" } },
|
||||
{ { L"Dutch" }, { L"nl" } },
|
||||
{ { L"English" }, { L"en" } },
|
||||
{ { L"Estonian" }, { L"et" } },
|
||||
{ { L"Fijian" }, { L"fj" } },
|
||||
{ { L"Filipino" }, { L"fil" } },
|
||||
{ { L"Finnish" }, { L"fi" } },
|
||||
{ { L"French" }, { L"fr" } },
|
||||
{ { L"French (Canada)" }, { L"fr-ca" } },
|
||||
{ { L"German" }, { L"de" } },
|
||||
{ { L"Greek" }, { L"el" } },
|
||||
{ { L"Gujarati" }, { L"gu" } },
|
||||
{ { L"Haitian Creole" }, { L"ht" } },
|
||||
{ { L"Hebrew" }, { L"he" } },
|
||||
{ { L"Hindi" }, { L"hi" } },
|
||||
{ { L"Hmong Daw" }, { L"mww" } },
|
||||
{ { L"Hungarian" }, { L"hu" } },
|
||||
{ { L"Icelandic" }, { L"is" } },
|
||||
{ { L"Indonesian" }, { L"id" } },
|
||||
{ { L"Inuktitut" }, { L"iu" } },
|
||||
{ { L"Irish" }, { L"ga" } },
|
||||
{ { L"Italian" }, { L"it" } },
|
||||
{ { L"Japanese" }, { L"ja" } },
|
||||
{ { L"Kannada" }, { L"kn" } },
|
||||
{ { L"Kazakh" }, { L"kk" } },
|
||||
{ { L"Khmer" }, { L"km" } },
|
||||
{ { L"Klingon" }, { L"tlh-Latn" } },
|
||||
{ { L"Korean" }, { L"ko" } },
|
||||
{ { L"Kurdish (Central)" }, { L"ku" } },
|
||||
{ { L"Kurdish (Northern)" }, { L"kmr" } },
|
||||
{ { L"Lao" }, { L"lo" } },
|
||||
{ { L"Latvian" }, { L"lv" } },
|
||||
{ { L"Lithuanian" }, { L"lt" } },
|
||||
{ { L"Malagasy" }, { L"mg" } },
|
||||
{ { L"Malay" }, { L"ms" } },
|
||||
{ { L"Malayalam" }, { L"ml" } },
|
||||
{ { L"Maltese" }, { L"mt" } },
|
||||
{ { L"Maori" }, { L"mi" } },
|
||||
{ { L"Marathi" }, { L"mr" } },
|
||||
{ { L"Myanmar" }, { L"my" } },
|
||||
{ { L"Nepali" }, { L"ne" } },
|
||||
{ { L"Norwegian" }, { L"nb" } },
|
||||
{ { L"Odia" }, { L"or" } },
|
||||
{ { L"Pashto" }, { L"ps" } },
|
||||
{ { L"Persian" }, { L"fa" } },
|
||||
{ { L"Polish" }, { L"pl" } },
|
||||
{ { L"Portuguese (Brazil)" }, { L"pt" } },
|
||||
{ { L"Portuguese (Portugal)" }, { L"pt-pt" } },
|
||||
{ { L"Punjabi" }, { L"pa" } },
|
||||
{ { L"Queretaro Otomi" }, { L"otq" } },
|
||||
{ { L"Romanian" }, { L"ro" } },
|
||||
{ { L"Russian" }, { L"ru" } },
|
||||
{ { L"Samoan" }, { L"sm" } },
|
||||
{ { L"Serbian (Cyrillic)" }, { L"sr-Cyrl" } },
|
||||
{ { L"Serbian (Latin)" }, { L"sr-Latn" } },
|
||||
{ { L"Slovak" }, { L"sk" } },
|
||||
{ { L"Slovenian" }, { L"sl" } },
|
||||
{ { L"Spanish" }, { L"es" } },
|
||||
{ { L"Swahili" }, { L"sw" } },
|
||||
{ { L"Swedish" }, { L"sv" } },
|
||||
{ { L"Tahitian" }, { L"ty" } },
|
||||
{ { L"Tamil" }, { L"ta" } },
|
||||
{ { L"Telugu" }, { L"te" } },
|
||||
{ { L"Thai" }, { L"th" } },
|
||||
{ { L"Tigrinya" }, { L"ti" } },
|
||||
{ { L"Tongan" }, { L"to" } },
|
||||
{ { L"Turkish" }, { L"tr" } },
|
||||
{ { L"Ukrainian" }, { L"uk" } },
|
||||
{ { L"Urdu" }, { L"ur" } },
|
||||
{ { L"Vietnamese" }, { L"vi" } },
|
||||
{ { L"Welsh" }, { L"cy" } },
|
||||
{ { L"Yucatec Maya" }, { L"yua" } },
|
||||
{ { L"?" }, { L"auto-detect" } }
|
||||
};
|
||||
std::wstring autoDetectLanguage = L"auto-detect";
|
||||
|
||||
bool translateSelectedOnly = false, rateLimitAll = true, rateLimitSelected = false, useCache = true, useFilter = true;
|
||||
int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 1000;
|
||||
|
||||
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
|
||||
{
|
||||
if (!authKey->empty())
|
||||
if (!tlp.authKey.empty())
|
||||
{
|
||||
std::wstring translateFromComponent = translateFrom.Copy() == autoDetectLanguage ? L"" : L"&from=" + translateFrom.Copy();
|
||||
std::wstring translateFromComponent = tlp.translateFrom == L"?" ? L"" : L"&from=" + codes.at(tlp.translateFrom);
|
||||
if (HttpRequest httpRequest{
|
||||
L"Mozilla/5.0 Textractor",
|
||||
L"api.cognitive.microsofttranslator.com",
|
||||
L"POST",
|
||||
FormatString(L"/translate?api-version=3.0&to=%s%s", translateTo.Copy(), translateFromComponent).c_str(),
|
||||
FormatString(L"/translate?api-version=3.0&to=%s%s", codes.at(tlp.translateTo), 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()
|
||||
FormatString(L"Content-Type: application/json; charset=UTF-8\r\nOcp-Apim-Subscription-Key:%s", tlp.authKey).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) };
|
||||
@ -102,15 +214,17 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||
}
|
||||
|
||||
static Synchronized<std::wstring> token;
|
||||
if (token->empty()) if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"www.bing.com", L"GET", L"translator" })
|
||||
if (auto tokenPos = httpRequest.response.find(L"[" + std::to_wstring(time(nullptr) / 10)); tokenPos != std::string::npos)
|
||||
if (token->empty())
|
||||
if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"www.bing.com", L"GET", L"translator" })
|
||||
if (auto tokenPos = httpRequest.response.find(L"[" + std::to_wstring(time(nullptr) / 100)); tokenPos != std::string::npos)
|
||||
token->assign(FormatString(L"&key=%s&token=%s", httpRequest.response.substr(tokenPos + 1, 13), httpRequest.response.substr(tokenPos + 16, 32)));
|
||||
if (token->empty()) return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, L"token missing") };
|
||||
else return { false, FormatString(L"%s: %s\ntoken not found", TRANSLATION_ERROR, httpRequest.response) };
|
||||
else return { false, FormatString(L"%s: could not acquire token", TRANSLATION_ERROR) };
|
||||
if (HttpRequest httpRequest{
|
||||
L"Mozilla/5.0 Textractor",
|
||||
L"www.bing.com",
|
||||
L"POST",
|
||||
FormatString(L"/ttranslatev3?fromLang=%s&to=%s&text=%s%s", translateFrom.Copy(), translateTo.Copy(), Escape(text), token.Copy()).c_str()
|
||||
FormatString(L"/ttranslatev3?fromLang=%s&to=%s&text=%s%s", codes.at(tlp.translateFrom), codes.at(tlp.translateTo), Escape(text), token.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 (token=%s): %s", TRANSLATION_ERROR, std::exchange(token.Acquire().contents, L""), httpRequest.response) };
|
||||
|
@ -1,75 +1,126 @@
|
||||
#include "qtcommon.h"
|
||||
#include "translatewrapper.h"
|
||||
#include "network.h"
|
||||
#include <random>
|
||||
|
||||
extern const wchar_t* TRANSLATION_ERROR;
|
||||
|
||||
extern Synchronized<std::wstring> translateTo, translateFrom, authKey;
|
||||
|
||||
const char* TRANSLATION_PROVIDER = "DeepL Translate";
|
||||
const char* GET_API_KEY_FROM = "https://www.deepl.com/pro.html";
|
||||
QStringList languages
|
||||
extern const QStringList languagesTo
|
||||
{
|
||||
"Bulgarian: BG",
|
||||
"Chinese: ZH",
|
||||
"Czech: CS",
|
||||
"Danish: DA",
|
||||
"Dutch: NL",
|
||||
"English: EN",
|
||||
"Estonian: ET",
|
||||
"Finnish: FI",
|
||||
"French: FR",
|
||||
"German: DE",
|
||||
"Greek: EL",
|
||||
"Hungarian: HU",
|
||||
"Italian: IT",
|
||||
"Japanese: JA",
|
||||
"Latvian: LV",
|
||||
"Lithuanian: LT",
|
||||
"Polish: PL",
|
||||
"Portuguese: PT",
|
||||
"Romanian: RO",
|
||||
"Russian: RU",
|
||||
"Slovak: SK",
|
||||
"Slovenian: SL",
|
||||
"Spanish: ES",
|
||||
"Swedish: SV"
|
||||
"Bulgarian",
|
||||
"Chinese (Simplified)",
|
||||
"Czech",
|
||||
"Danish",
|
||||
"Dutch",
|
||||
"English (American)",
|
||||
"English (British)",
|
||||
"Estonian",
|
||||
"Finnish",
|
||||
"French",
|
||||
"German",
|
||||
"Greek",
|
||||
"Hungarian",
|
||||
"Italian",
|
||||
"Japanese",
|
||||
"Latvian",
|
||||
"Lithuanian",
|
||||
"Polish",
|
||||
"Portuguese (Brazil)",
|
||||
"Portuguese (Portugal)",
|
||||
"Romanian",
|
||||
"Russian",
|
||||
"Slovak",
|
||||
"Slovenian",
|
||||
"Spanish",
|
||||
"Swedish"
|
||||
},
|
||||
languagesFrom
|
||||
{
|
||||
"Bulgarian",
|
||||
"Chinese",
|
||||
"Czech",
|
||||
"Danish",
|
||||
"Dutch",
|
||||
"English",
|
||||
"Estonian",
|
||||
"Finnish",
|
||||
"French",
|
||||
"German",
|
||||
"Greek",
|
||||
"Hungarian",
|
||||
"Italian",
|
||||
"Japanese",
|
||||
"Latvian",
|
||||
"Lithuanian",
|
||||
"Polish",
|
||||
"Portuguese",
|
||||
"Romanian",
|
||||
"Russian",
|
||||
"Slovak",
|
||||
"Slovenian",
|
||||
"Spanish",
|
||||
"Swedish"
|
||||
};
|
||||
extern const std::unordered_map<std::wstring, std::wstring> codes
|
||||
{
|
||||
{ { L"Bulgarian" }, { L"BG" } },
|
||||
{ { L"Chinese" }, { L"ZH" } },
|
||||
{ { L"Chinese (Simplified)" }, { L"ZH" } },
|
||||
{ { L"Czech" }, { L"CS" } },
|
||||
{ { L"Danish" }, { L"DA" } },
|
||||
{ { L"Dutch" }, { L"NL" } },
|
||||
{ { L"English" }, { L"EN" } },
|
||||
{ { L"English (American)" }, { L"EN-US" } },
|
||||
{ { L"English (British)" }, { L"EN-GB" } },
|
||||
{ { L"Estonian" }, { L"ET" } },
|
||||
{ { L"Finnish" }, { L"FI" } },
|
||||
{ { L"French" }, { L"FR" } },
|
||||
{ { L"German" }, { L"DE" } },
|
||||
{ { L"Greek" }, { L"EL" } },
|
||||
{ { L"Hungarian" }, { L"HU" } },
|
||||
{ { L"Italian" }, { L"IT" } },
|
||||
{ { L"Japanese" }, { L"JA" } },
|
||||
{ { L"Latvian" }, { L"LV" } },
|
||||
{ { L"Lithuanian" }, { L"LT" } },
|
||||
{ { L"Polish" }, { L"PL" } },
|
||||
{ { L"Portuguese" }, { L"PT" } },
|
||||
{ { L"Portuguese (Brazil)" }, { L"PT-BR" } },
|
||||
{ { L"Portuguese (Portugal)" }, { L"PT-PT" } },
|
||||
{ { L"Romanian" }, { L"RO" } },
|
||||
{ { L"Russian" }, { L"RU" } },
|
||||
{ { L"Slovak" }, { L"SK" } },
|
||||
{ { L"Slovenian" }, { L"SL" } },
|
||||
{ { L"Spanish" }, { L"ES" } },
|
||||
{ { L"Swedish" }, { L"SV" } },
|
||||
{ { L"?" }, { L"auto" } }
|
||||
};
|
||||
std::wstring autoDetectLanguage = L"auto";
|
||||
|
||||
bool translateSelectedOnly = true, rateLimitAll = true, rateLimitSelected = true, useCache = true, useFilter = true;
|
||||
int tokenCount = 10, tokenRestoreDelay = 60000, maxSentenceSize = 1000;
|
||||
|
||||
enum KeyType { CAT, REST };
|
||||
int keyType = REST;
|
||||
enum PlanLevel { FREE, PAID };
|
||||
int planLevel = PAID;
|
||||
|
||||
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
|
||||
{
|
||||
if (!authKey->empty())
|
||||
if (!tlp.authKey.empty())
|
||||
{
|
||||
std::string translateFromComponent = translateFrom.Copy() == autoDetectLanguage ? "" : "&source_lang=" + WideStringToString(translateFrom.Copy());
|
||||
std::string translateFromComponent = tlp.translateFrom == L"?" ? "" : "&source_lang=" + WideStringToString(codes.at(tlp.translateFrom));
|
||||
if (HttpRequest httpRequest{
|
||||
L"Mozilla/5.0 Textractor",
|
||||
planLevel == PAID ? L"api.deepl.com" : L"api-free.deepl.com",
|
||||
tlp.authKey.find(L":fx") == std::string::npos ? L"api.deepl.com" : L"api-free.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()) + translateFromComponent,
|
||||
FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), tlp.authKey, codes.at(tlp.translateTo)) + translateFromComponent,
|
||||
L"Content-Type: application/x-www-form-urlencoded"
|
||||
}; httpRequest && (!httpRequest.response.empty() || (httpRequest = HttpRequest{
|
||||
}; httpRequest && (httpRequest.response.find(L"translations") != std::string::npos || (httpRequest = HttpRequest{
|
||||
L"Mozilla/5.0 Textractor",
|
||||
planLevel == PAID ? L"api.deepl.com" : L"api-free.deepl.com",
|
||||
tlp.authKey.find(L":fx") == std::string::npos ? L"api.deepl.com" : L"api-free.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()) + translateFromComponent,
|
||||
L"Content-Type: application/x-www-form-urlencoded"
|
||||
})) && (httpRequest.response.find(L"Wrong endpoint. Use") == std::string::npos || (httpRequest = HttpRequest{
|
||||
L"Mozilla/5.0 Textractor",
|
||||
(planLevel = !planLevel) == PAID ? L"api.deepl.com" : L"api-free.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()) + translateFromComponent,
|
||||
FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), tlp.authKey, codes.at(tlp.translateTo)) + translateFromComponent,
|
||||
L"Content-Type: application/x-www-form-urlencoded"
|
||||
})))
|
||||
// Response formatted as JSON: translation starts with text":" and ends with "}]
|
||||
@ -92,7 +143,7 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||
"priority": -1,
|
||||
"timestamp": %lld,
|
||||
"lang": {
|
||||
"target_lang": "%S",
|
||||
"target_lang": "%.2S",
|
||||
"source_lang_user_selected": "%S"
|
||||
},
|
||||
"jobs": [{
|
||||
@ -105,7 +156,7 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||
}]
|
||||
}
|
||||
}
|
||||
)", id, r + (n - r % n), translateTo.Copy(), translateFrom.Copy(), JSON::Escape(WideStringToString(text)));
|
||||
)", id, r + (n - r % n), codes.at(tlp.translateTo), codes.at(tlp.translateFrom), JSON::Escape(WideStringToString(text)));
|
||||
// missing accept-encoding header since it fucks up HttpRequest
|
||||
if (HttpRequest httpRequest{
|
||||
L"Mozilla/5.0 Textractor",
|
||||
|
@ -1,45 +1,105 @@
|
||||
#include "qtcommon.h"
|
||||
#include "translatewrapper.h"
|
||||
#include "devtools.h"
|
||||
|
||||
extern const wchar_t* ERROR_START_CHROME;
|
||||
extern const wchar_t* TRANSLATION_ERROR;
|
||||
|
||||
extern Synchronized<std::wstring> translateTo, translateFrom;
|
||||
|
||||
const char* TRANSLATION_PROVIDER = "DevTools DeepL Translate";
|
||||
const char* GET_API_KEY_FROM = nullptr;
|
||||
|
||||
extern const QStringList languagesTo
|
||||
{
|
||||
"Bulgarian",
|
||||
"Chinese (Simplified)",
|
||||
"Czech",
|
||||
"Danish",
|
||||
"Dutch",
|
||||
"English (American)",
|
||||
"English (British)",
|
||||
"Estonian",
|
||||
"Finnish",
|
||||
"French",
|
||||
"German",
|
||||
"Greek",
|
||||
"Hungarian",
|
||||
"Italian",
|
||||
"Japanese",
|
||||
"Latvian",
|
||||
"Lithuanian",
|
||||
"Polish",
|
||||
"Portuguese",
|
||||
"Portuguese (Brazilian)",
|
||||
"Romanian",
|
||||
"Russian",
|
||||
"Slovak",
|
||||
"Slovenian",
|
||||
"Spanish",
|
||||
"Swedish"
|
||||
},
|
||||
languagesFrom =
|
||||
{
|
||||
"Bulgarian",
|
||||
"Chinese",
|
||||
"Czech",
|
||||
"Danish",
|
||||
"Dutch",
|
||||
"English",
|
||||
"Estonian",
|
||||
"Finnish",
|
||||
"French",
|
||||
"German",
|
||||
"Greek",
|
||||
"Hungarian",
|
||||
"Italian",
|
||||
"Japanese",
|
||||
"Latvian",
|
||||
"Lithuanian",
|
||||
"Polish",
|
||||
"Portuguese",
|
||||
"Romanian",
|
||||
"Russian",
|
||||
"Slovak",
|
||||
"Slovenian",
|
||||
"Spanish",
|
||||
"Swedish"
|
||||
};
|
||||
extern const std::unordered_map<std::wstring, std::wstring> codes
|
||||
{
|
||||
{ { L"Bulgarian" }, { L"Bulgarian" } },
|
||||
{ { L"Chinese" }, { L"Chinese" } },
|
||||
{ { L"Chinese (Simplified)" }, { L"Chinese (simplified)" } },
|
||||
{ { L"Czech" }, { L"Czech" } },
|
||||
{ { L"Danish" }, { L"Danish" } },
|
||||
{ { L"Dutch" }, { L"Dutch" } },
|
||||
{ { L"English" }, { L"English" } },
|
||||
{ { L"English (American)" }, { L"English (American)" } },
|
||||
{ { L"English (British)" }, { L"English (British)" } },
|
||||
{ { L"Estonian" }, { L"Estonian" } },
|
||||
{ { L"Finnish" }, { L"Finnish" } },
|
||||
{ { L"French" }, { L"French" } },
|
||||
{ { L"German" }, { L"German" } },
|
||||
{ { L"Greek" }, { L"Greek" } },
|
||||
{ { L"Hungarian" }, { L"Hungarian" } },
|
||||
{ { L"Italian" }, { L"Italian" } },
|
||||
{ { L"Japanese" }, { L"Japanese" } },
|
||||
{ { L"Latvian" }, { L"Latvian" } },
|
||||
{ { L"Lithuanian" }, { L"Lithuanian" } },
|
||||
{ { L"Polish" }, { L"Polish" } },
|
||||
{ { L"Portuguese" }, { L"Portuguese" } },
|
||||
{ { L"Portuguese (Brazilian)" }, { L"Portuguese (Brazilian)" } },
|
||||
{ { L"Romanian" }, { L"Romanian" } },
|
||||
{ { L"Russian" }, { L"Russian" } },
|
||||
{ { L"Slovak" }, { L"Slovak" } },
|
||||
{ { L"Slovenian" }, { L"Slovenian" } },
|
||||
{ { L"Spanish" }, { L"Spanish" } },
|
||||
{ { L"Swedish" }, { L"Swedish" } },
|
||||
{ { L"?" }, { L"Any language (detect)" } }
|
||||
};
|
||||
|
||||
bool translateSelectedOnly = true, rateLimitAll = false, rateLimitSelected = false, useCache = true, useFilter = true;
|
||||
int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 2500;
|
||||
|
||||
QStringList languages
|
||||
{
|
||||
"Bulgarian: BG",
|
||||
"Chinese: ZH",
|
||||
"Czech: CS",
|
||||
"Danish: DA",
|
||||
"Dutch: NL",
|
||||
"English: EN",
|
||||
"Estonian: ET",
|
||||
"Finnish: FI",
|
||||
"French: FR",
|
||||
"German: DE",
|
||||
"Greek: EL",
|
||||
"Hungarian: HU",
|
||||
"Italian: IT",
|
||||
"Japanese: JA",
|
||||
"Latvian: LV",
|
||||
"Lithuanian: LT",
|
||||
"Polish: PL",
|
||||
"Portuguese: PT",
|
||||
"Romanian: RO",
|
||||
"Russian: RU",
|
||||
"Slovak: SK",
|
||||
"Slovenian: SL",
|
||||
"Spanish: ES",
|
||||
"Swedish: SV"
|
||||
};
|
||||
std::wstring autoDetectLanguage = L"auto";
|
||||
|
||||
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
@ -58,19 +118,22 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
|
||||
{
|
||||
if (!DevTools::Connected()) return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, ERROR_START_CHROME) };
|
||||
// DevTools can't handle concurrent translations yet
|
||||
static std::mutex translationMutex;
|
||||
std::scoped_lock lock(translationMutex);
|
||||
DevTools::SendRequest("Page.navigate", FormatString(LR"({"url":"https://www.deepl.com/en/translator#%s/%s/%s"})", translateTo.Copy(), translateTo.Copy(), Escape(text)));
|
||||
DevTools::SendRequest("Page.navigate", FormatString(LR"({"url":"https://www.deepl.com/en/translator#en/en/%s"})", Escape(text)));
|
||||
for (int retry = 0; ++retry < 20; Sleep(100))
|
||||
if (Copy(DevTools::SendRequest("Runtime.evaluate", LR"({"expression":"document.readyState"})")[L"result"][L"value"].String()) == L"complete") break;
|
||||
|
||||
if (translateFrom.Copy() != autoDetectLanguage)
|
||||
DevTools::SendRequest("Runtime.evaluate", FormatString(LR"({"expression":"
|
||||
document.querySelector('.lmt__language_select--source').querySelector('button').click();
|
||||
document.evaluate(`//button[contains(text(),'%s')]`,document.querySelector('.lmt__language_select__menu'),null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue.click();
|
||||
"})", S(std::find_if(languages.begin(), languages.end(), [end = S(translateFrom.Copy())](const QString& language) { return language.endsWith(end); })->split(":")[0])));
|
||||
document.evaluate(`//button[text()='%s']`,document.querySelector('.lmt__language_select__menu'),null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue.click();
|
||||
document.querySelector('.lmt__language_select--target').querySelector('button').click();
|
||||
document.evaluate(`//button[text()='%s']`,document.querySelector('.lmt__language_select__menu'),null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue.click();
|
||||
"})", codes.at(tlp.translateFrom), codes.at(tlp.translateTo)));
|
||||
|
||||
for (int retry = 0; ++retry < 100; Sleep(100))
|
||||
if (auto translation = Copy(DevTools::SendRequest("Runtime.evaluate",
|
||||
|
@ -1,36 +1,54 @@
|
||||
#include "qtcommon.h"
|
||||
#include "translatewrapper.h"
|
||||
#include "devtools.h"
|
||||
|
||||
extern const wchar_t* ERROR_START_CHROME;
|
||||
extern const wchar_t* TRANSLATION_ERROR;
|
||||
|
||||
extern Synchronized<std::wstring> translateTo, translateFrom;
|
||||
|
||||
const char* TRANSLATION_PROVIDER = "DevTools Papago Translate";
|
||||
const char* GET_API_KEY_FROM = nullptr;
|
||||
|
||||
extern const QStringList languagesTo
|
||||
{
|
||||
"Chinese (Simplified)",
|
||||
"Chinese (Traditional)",
|
||||
"English",
|
||||
"French",
|
||||
"German",
|
||||
"Hindi",
|
||||
"Indonesian",
|
||||
"Italian",
|
||||
"Japanese",
|
||||
"Korean",
|
||||
"Portuguese",
|
||||
"Russian",
|
||||
"Spanish",
|
||||
"Thai",
|
||||
"Vietnamese",
|
||||
}, languagesFrom = languagesTo;
|
||||
extern const std::unordered_map<std::wstring, std::wstring> codes
|
||||
{
|
||||
{ { L"Chinese (Simplified)" }, { L"zh-CN" } },
|
||||
{ { L"Chinese (Traditional)" }, { L"zt-TW" } },
|
||||
{ { L"English" }, { L"en" } },
|
||||
{ { L"French" }, { L"fr" } },
|
||||
{ { L"German" }, { L"de" } },
|
||||
{ { L"Hindi" }, { L"hi" } },
|
||||
{ { L"Indonesian" }, { L"id" } },
|
||||
{ { L"Italian" }, { L"it" } },
|
||||
{ { L"Japanese" }, { L"ja" } },
|
||||
{ { L"Korean" }, { L"ko" } },
|
||||
{ { L"Portuguese" }, { L"pt" } },
|
||||
{ { L"Russian" }, { L"ru" } },
|
||||
{ { L"Spanish" }, { L"es" } },
|
||||
{ { L"Thai" }, { L"th" } },
|
||||
{ { L"Vietnamese" }, { L"vi" } },
|
||||
{ { L"?" }, { L"auto" } }
|
||||
};
|
||||
|
||||
bool translateSelectedOnly = true, rateLimitAll = false, rateLimitSelected = false, useCache = true, useFilter = true;
|
||||
int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 2500;
|
||||
|
||||
QStringList languages
|
||||
{
|
||||
"Chinese (simplified): zh-CN",
|
||||
"Chinese (traditional): zt-TW",
|
||||
"English: en",
|
||||
"French: fr",
|
||||
"German: de",
|
||||
"Hindi: hi",
|
||||
"Indonesian: id",
|
||||
"Italian: it",
|
||||
"Japanese: ja",
|
||||
"Korean: ko",
|
||||
"Portuguese: pt",
|
||||
"Russian: ru",
|
||||
"Spanish: es",
|
||||
"Thai: th",
|
||||
"Vietnamese: vi",
|
||||
};
|
||||
std::wstring autoDetectLanguage = L"auto";
|
||||
|
||||
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
@ -49,13 +67,13 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
|
||||
{
|
||||
if (!DevTools::Connected()) return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, ERROR_START_CHROME) };
|
||||
// DevTools can't handle concurrent translations yet
|
||||
static std::mutex translationMutex;
|
||||
std::scoped_lock lock(translationMutex);
|
||||
DevTools::SendRequest("Page.navigate", FormatString(LR"({"url":"https://papago.naver.com/?sk=%s&tk=%s&st=%s"})", translateFrom.Copy(), translateTo.Copy(), Escape(text)));
|
||||
DevTools::SendRequest("Page.navigate", FormatString(LR"({"url":"https://papago.naver.com/?sk=%s&tk=%s&st=%s"})", codes.at(tlp.translateFrom), codes.at(tlp.translateTo), Escape(text)));
|
||||
for (int retry = 0; ++retry < 100; Sleep(100))
|
||||
if (auto translation = Copy(DevTools::SendRequest("Runtime.evaluate",
|
||||
LR"({"expression":"document.querySelector('#txtTarget').textContent.trim() ","returnByValue":true})"
|
||||
|
@ -1,34 +1,120 @@
|
||||
#include "qtcommon.h"
|
||||
#include "translatewrapper.h"
|
||||
#include "devtools.h"
|
||||
|
||||
extern const wchar_t* ERROR_START_CHROME;
|
||||
extern const wchar_t* TRANSLATION_ERROR;
|
||||
|
||||
extern Synchronized<std::wstring> translateTo, translateFrom;
|
||||
extern QFormLayout* display;
|
||||
extern Settings settings;
|
||||
|
||||
const char* TRANSLATION_PROVIDER = "DevTools Systran Translate";
|
||||
const char* GET_API_KEY_FROM = nullptr;
|
||||
|
||||
extern const QStringList languagesTo
|
||||
{
|
||||
"Albanian",
|
||||
"Arabic",
|
||||
"Bengali",
|
||||
"Bulgarian",
|
||||
"Burmese",
|
||||
"Catalan",
|
||||
"Chinese (Simplified)",
|
||||
"Chinese (Traditional)",
|
||||
"Croatian",
|
||||
"Czech",
|
||||
"Danish",
|
||||
"Dutch",
|
||||
"English",
|
||||
"Estonian",
|
||||
"Finnish",
|
||||
"French",
|
||||
"German",
|
||||
"Greek",
|
||||
"Hebrew",
|
||||
"Hindi",
|
||||
"Hungarian",
|
||||
"Indonesian",
|
||||
"Italian",
|
||||
"Japanese",
|
||||
"Korean",
|
||||
"Latvian",
|
||||
"Lithuanian",
|
||||
"Malay",
|
||||
"Norwegian",
|
||||
"Pashto",
|
||||
"Persian",
|
||||
"Polish",
|
||||
"Portuguese",
|
||||
"Romanian",
|
||||
"Russian",
|
||||
"Serbian",
|
||||
"Slovak",
|
||||
"Slovenian",
|
||||
"Somali",
|
||||
"Spanish",
|
||||
"Swedish",
|
||||
"Tagalog",
|
||||
"Tamil",
|
||||
"Thai",
|
||||
"Turkish",
|
||||
"Ukrainian",
|
||||
"Urdu",
|
||||
"Vietnamese"
|
||||
}, languagesFrom = languagesTo;
|
||||
extern const std::unordered_map<std::wstring, std::wstring> codes
|
||||
{
|
||||
{ { L"Albanian" }, { L"sq" } },
|
||||
{ { L"Arabic" }, { L"ar" } },
|
||||
{ { L"Bengali" }, { L"bn" } },
|
||||
{ { L"Bulgarian" }, { L"bg" } },
|
||||
{ { L"Burmese" }, { L"my" } },
|
||||
{ { L"Catalan" }, { L"ca" } },
|
||||
{ { L"Chinese (Simplified)" }, { L"zh" } },
|
||||
{ { L"Chinese (Traditional)" }, { L"zt" } },
|
||||
{ { L"Croatian" }, { L"hr" } },
|
||||
{ { L"Czech" }, { L"cs" } },
|
||||
{ { L"Danish" }, { L"da" } },
|
||||
{ { L"Dutch" }, { L"nl" } },
|
||||
{ { L"English" }, { L"en" } },
|
||||
{ { L"Estonian" }, { L"et" } },
|
||||
{ { L"Finnish" }, { L"fi" } },
|
||||
{ { L"French" }, { L"fr" } },
|
||||
{ { L"German" }, { L"de" } },
|
||||
{ { L"Greek" }, { L"el" } },
|
||||
{ { L"Hebrew" }, { L"he" } },
|
||||
{ { L"Hindi" }, { L"hi" } },
|
||||
{ { L"Hungarian" }, { L"hu" } },
|
||||
{ { L"Indonesian" }, { L"id" } },
|
||||
{ { L"Italian" }, { L"it" } },
|
||||
{ { L"Japanese" }, { L"ja" } },
|
||||
{ { L"Korean" }, { L"ko" } },
|
||||
{ { L"Latvian" }, { L"lv" } },
|
||||
{ { L"Lithuanian" }, { L"lt" } },
|
||||
{ { L"Malay" }, { L"ms" } },
|
||||
{ { L"Norwegian" }, { L"no" } },
|
||||
{ { L"Pashto" }, { L"ps" } },
|
||||
{ { L"Persian" }, { L"fa" } },
|
||||
{ { L"Polish" }, { L"pl" } },
|
||||
{ { L"Portuguese" }, { L"pt" } },
|
||||
{ { L"Romanian" }, { L"ro" } },
|
||||
{ { L"Russian" }, { L"ru" } },
|
||||
{ { L"Serbian" }, { L"sr" } },
|
||||
{ { L"Slovak" }, { L"sk" } },
|
||||
{ { L"Slovenian" }, { L"sl" } },
|
||||
{ { L"Somali" }, { L"so" } },
|
||||
{ { L"Spanish" }, { L"es" } },
|
||||
{ { L"Swedish" }, { L"sv" } },
|
||||
{ { L"Tagalog" }, { L"tl" } },
|
||||
{ { L"Tamil" }, { L"ta" } },
|
||||
{ { L"Thai" }, { L"th" } },
|
||||
{ { L"Turkish" }, { L"tr" } },
|
||||
{ { L"Ukrainian" }, { L"uk" } },
|
||||
{ { L"Urdu" }, { L"ur" } },
|
||||
{ { L"Vietnamese" }, { L"vi" } },
|
||||
{ { L"?" }, { L"autodetect" } }
|
||||
};
|
||||
|
||||
bool translateSelectedOnly = true, rateLimitAll = false, rateLimitSelected = false, useCache = true, useFilter = true;
|
||||
int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 2500;
|
||||
|
||||
QStringList languages
|
||||
{
|
||||
"Chinese: zh",
|
||||
"Chinese (traditional): zt",
|
||||
"English: en",
|
||||
"French: fr",
|
||||
"German: de",
|
||||
"Italian: it",
|
||||
"Japanese: ja",
|
||||
"Korean: ko",
|
||||
"Portuguese: pt",
|
||||
"Spanish: es",
|
||||
"Thai: th",
|
||||
};
|
||||
std::wstring autoDetectLanguage = L"auto";
|
||||
|
||||
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
@ -47,14 +133,17 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
|
||||
{
|
||||
if (!DevTools::Connected()) return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, ERROR_START_CHROME) };
|
||||
// DevTools can't handle concurrent translations yet
|
||||
static std::mutex translationMutex;
|
||||
std::scoped_lock lock(translationMutex);
|
||||
|
||||
DevTools::SendRequest("Page.navigate", FormatString(LR"({"url":"https://translate.systran.net/?&source=%s&target=%s&input=%s"})", translateFrom.Copy(), translateTo.Copy(), Escape(text)));
|
||||
DevTools::SendRequest(
|
||||
"Page.navigate",
|
||||
FormatString(LR"({"url":"https://translate.systran.net/?source=%s&target=%s&input=%s"})", codes.at(tlp.translateFrom), codes.at(tlp.translateTo), Escape(text))
|
||||
);
|
||||
for (int retry = 0; ++retry < 100; Sleep(100))
|
||||
if (auto translation = Copy(DevTools::SendRequest("Runtime.evaluate",
|
||||
LR"({"expression":"document.querySelector('#outputEditor').textContent.trim() ","returnByValue":true})"
|
||||
|
@ -1,140 +1,250 @@
|
||||
#include "qtcommon.h"
|
||||
#include "translatewrapper.h"
|
||||
#include "network.h"
|
||||
#include <ctime>
|
||||
|
||||
extern const wchar_t* TRANSLATION_ERROR;
|
||||
|
||||
extern Synchronized<std::wstring> translateTo, translateFrom, authKey;
|
||||
|
||||
const char* TRANSLATION_PROVIDER = "Google Translate";
|
||||
const char* GET_API_KEY_FROM = "https://codelabs.developers.google.com/codelabs/cloud-translation-intro";
|
||||
QStringList languages
|
||||
extern const QStringList languagesTo
|
||||
{
|
||||
"Afrikaans: af",
|
||||
"Albanian: sq",
|
||||
"Amharic: am",
|
||||
"Arabic: ar",
|
||||
"Armenian: hy",
|
||||
"Azerbaijani: az",
|
||||
"Basque: eu",
|
||||
"Belarusian: be",
|
||||
"Bengali: bn",
|
||||
"Bosnian: bs",
|
||||
"Bulgarian: bg",
|
||||
"Catalan: ca",
|
||||
"Cebuano: ceb",
|
||||
"Chichewa: ny",
|
||||
"Chinese (simplified): zh",
|
||||
"Chinese (traditional): zh-TW",
|
||||
"Corsican: co",
|
||||
"Croatian: hr",
|
||||
"Czech: cs",
|
||||
"Danish: da",
|
||||
"Dutch: nl",
|
||||
"English: en",
|
||||
"Esperanto: eo",
|
||||
"Estonian: et",
|
||||
"Filipino: tl",
|
||||
"Finnish: fi",
|
||||
"French: fr",
|
||||
"Frisian: fy",
|
||||
"Galician: gl",
|
||||
"Georgian: ka",
|
||||
"German: de",
|
||||
"Greek: el",
|
||||
"Gujarati: gu",
|
||||
"Haitian Creole: ht",
|
||||
"Hausa: ha",
|
||||
"Hawaiian: haw",
|
||||
"Hebrew: iw",
|
||||
"Hindi: hi",
|
||||
"Hmong: hmn",
|
||||
"Hungarian: hu",
|
||||
"Icelandic: is",
|
||||
"Igbo: ig",
|
||||
"Indonesian: id",
|
||||
"Irish: ga",
|
||||
"Italian: it",
|
||||
"Japanese: ja",
|
||||
"Javanese: jw",
|
||||
"Kannada: kn",
|
||||
"Kazakh: kk",
|
||||
"Khmer: km",
|
||||
"Kinyarwanda: rw",
|
||||
"Korean: ko",
|
||||
"Kurdish (Kurmanji): ku",
|
||||
"Kyrgyz: ky",
|
||||
"Lao: lo",
|
||||
"Latin: la",
|
||||
"Latvian: lv",
|
||||
"Lithuanian: lt",
|
||||
"Luxembourgish: lb",
|
||||
"Macedonian: mk",
|
||||
"Malagasy: mg",
|
||||
"Malay: ms",
|
||||
"Malayalam: ml",
|
||||
"Maltese: mt",
|
||||
"Maori: mi",
|
||||
"Marathi: mr",
|
||||
"Mongolian: mn",
|
||||
"Myanmar (Burmese): my",
|
||||
"Nepali: ne",
|
||||
"Norwegian: no",
|
||||
"Odia (Oriya): or",
|
||||
"Pashto: ps",
|
||||
"Persian: fa",
|
||||
"Polish: pl",
|
||||
"Portuguese: pt",
|
||||
"Punjabi: pa",
|
||||
"Romanian: ro",
|
||||
"Russian: ru",
|
||||
"Samoan: sm",
|
||||
"Scots Gaelic: gd",
|
||||
"Serbian: sr",
|
||||
"Sesotho: st",
|
||||
"Shona: sn",
|
||||
"Sindhi: sd",
|
||||
"Sinhala: si",
|
||||
"Slovak: sk",
|
||||
"Slovenian: sl",
|
||||
"Somali: so",
|
||||
"Spanish: es",
|
||||
"Sundanese: su",
|
||||
"Swahili: sw",
|
||||
"Swedish: sv",
|
||||
"Tajik: tg",
|
||||
"Tamil: ta",
|
||||
"Tatar: tt",
|
||||
"Telugu: te",
|
||||
"Thai: th",
|
||||
"Turkish: tr",
|
||||
"Turkmen: tk",
|
||||
"Ukrainian: uk",
|
||||
"Urdu: ur",
|
||||
"Uyghur: ug",
|
||||
"Uzbek: uz",
|
||||
"Vietnamese: vi",
|
||||
"Welsh: cy",
|
||||
"Xhosa: xh",
|
||||
"Yiddish: yi",
|
||||
"Yoruba: yo",
|
||||
"Zulu: zu"
|
||||
"Afrikaans",
|
||||
"Albanian",
|
||||
"Amharic",
|
||||
"Arabic",
|
||||
"Armenian",
|
||||
"Azerbaijani",
|
||||
"Basque",
|
||||
"Belarusian",
|
||||
"Bengali",
|
||||
"Bosnian",
|
||||
"Bulgarian",
|
||||
"Catalan",
|
||||
"Cebuano",
|
||||
"Chichewa",
|
||||
"Chinese (Simplified)",
|
||||
"Chinese (Traditional)",
|
||||
"Corsican",
|
||||
"Croatian",
|
||||
"Czech",
|
||||
"Danish",
|
||||
"Dutch",
|
||||
"English",
|
||||
"Esperanto",
|
||||
"Estonian",
|
||||
"Filipino",
|
||||
"Finnish",
|
||||
"French",
|
||||
"Frisian",
|
||||
"Galician",
|
||||
"Georgian",
|
||||
"German",
|
||||
"Greek",
|
||||
"Gujarati",
|
||||
"Haitian Creole",
|
||||
"Hausa",
|
||||
"Hawaiian",
|
||||
"Hebrew",
|
||||
"Hindi",
|
||||
"Hmong",
|
||||
"Hungarian",
|
||||
"Icelandic",
|
||||
"Igbo",
|
||||
"Indonesian",
|
||||
"Irish",
|
||||
"Italian",
|
||||
"Japanese",
|
||||
"Javanese",
|
||||
"Kannada",
|
||||
"Kazakh",
|
||||
"Khmer",
|
||||
"Kinyarwanda",
|
||||
"Korean",
|
||||
"Kurdish (Kurmanji)",
|
||||
"Kyrgyz",
|
||||
"Lao",
|
||||
"Latin",
|
||||
"Latvian",
|
||||
"Lithuanian",
|
||||
"Luxembourgish",
|
||||
"Macedonian",
|
||||
"Malagasy",
|
||||
"Malay",
|
||||
"Malayalam",
|
||||
"Maltese",
|
||||
"Maori",
|
||||
"Marathi",
|
||||
"Mongolian",
|
||||
"Myanmar (Burmese)",
|
||||
"Nepali",
|
||||
"Norwegian",
|
||||
"Odia (Oriya)",
|
||||
"Pashto",
|
||||
"Persian",
|
||||
"Polish",
|
||||
"Portuguese",
|
||||
"Punjabi",
|
||||
"Romanian",
|
||||
"Russian",
|
||||
"Samoan",
|
||||
"Scots Gaelic",
|
||||
"Serbian",
|
||||
"Sesotho",
|
||||
"Shona",
|
||||
"Sindhi",
|
||||
"Sinhala",
|
||||
"Slovak",
|
||||
"Slovenian",
|
||||
"Somali",
|
||||
"Spanish",
|
||||
"Sundanese",
|
||||
"Swahili",
|
||||
"Swedish",
|
||||
"Tajik",
|
||||
"Tamil",
|
||||
"Tatar",
|
||||
"Telugu",
|
||||
"Thai",
|
||||
"Turkish",
|
||||
"Turkmen",
|
||||
"Ukrainian",
|
||||
"Urdu",
|
||||
"Uyghur",
|
||||
"Uzbek",
|
||||
"Vietnamese",
|
||||
"Welsh",
|
||||
"Xhosa",
|
||||
"Yiddish",
|
||||
"Yoruba",
|
||||
"Zulu",
|
||||
}, languagesFrom = languagesTo;
|
||||
extern const std::unordered_map<std::wstring, std::wstring> codes
|
||||
{
|
||||
{ { L"Afrikaans" }, { L"af" } },
|
||||
{ { L"Albanian" }, { L"sq" } },
|
||||
{ { L"Amharic" }, { L"am" } },
|
||||
{ { L"Arabic" }, { L"ar" } },
|
||||
{ { L"Armenian" }, { L"hy" } },
|
||||
{ { L"Azerbaijani" }, { L"az" } },
|
||||
{ { L"Basque" }, { L"eu" } },
|
||||
{ { L"Belarusian" }, { L"be" } },
|
||||
{ { L"Bengali" }, { L"bn" } },
|
||||
{ { L"Bosnian" }, { L"bs" } },
|
||||
{ { L"Bulgarian" }, { L"bg" } },
|
||||
{ { L"Catalan" }, { L"ca" } },
|
||||
{ { L"Cebuano" }, { L"ceb" } },
|
||||
{ { L"Chichewa" }, { L"ny" } },
|
||||
{ { L"Chinese (Simplified)" }, { L"zh-CN" } },
|
||||
{ { L"Chinese (Traditional)" }, { L"zh-TW" } },
|
||||
{ { L"Corsican" }, { L"co" } },
|
||||
{ { L"Croatian" }, { L"hr" } },
|
||||
{ { L"Czech" }, { L"cs" } },
|
||||
{ { L"Danish" }, { L"da" } },
|
||||
{ { L"Dutch" }, { L"nl" } },
|
||||
{ { L"English" }, { L"en" } },
|
||||
{ { L"Esperanto" }, { L"eo" } },
|
||||
{ { L"Estonian" }, { L"et" } },
|
||||
{ { L"Filipino" }, { L"tl" } },
|
||||
{ { L"Finnish" }, { L"fi" } },
|
||||
{ { L"French" }, { L"fr" } },
|
||||
{ { L"Frisian" }, { L"fy" } },
|
||||
{ { L"Galician" }, { L"gl" } },
|
||||
{ { L"Georgian" }, { L"ka" } },
|
||||
{ { L"German" }, { L"de" } },
|
||||
{ { L"Greek" }, { L"el" } },
|
||||
{ { L"Gujarati" }, { L"gu" } },
|
||||
{ { L"Haitian Creole" }, { L"ht" } },
|
||||
{ { L"Hausa" }, { L"ha" } },
|
||||
{ { L"Hawaiian" }, { L"haw" } },
|
||||
{ { L"Hebrew" }, { L"iw" } },
|
||||
{ { L"Hindi" }, { L"hi" } },
|
||||
{ { L"Hmong" }, { L"hmn" } },
|
||||
{ { L"Hungarian" }, { L"hu" } },
|
||||
{ { L"Icelandic" }, { L"is" } },
|
||||
{ { L"Igbo" }, { L"ig" } },
|
||||
{ { L"Indonesian" }, { L"id" } },
|
||||
{ { L"Irish" }, { L"ga" } },
|
||||
{ { L"Italian" }, { L"it" } },
|
||||
{ { L"Japanese" }, { L"ja" } },
|
||||
{ { L"Javanese" }, { L"jw" } },
|
||||
{ { L"Kannada" }, { L"kn" } },
|
||||
{ { L"Kazakh" }, { L"kk" } },
|
||||
{ { L"Khmer" }, { L"km" } },
|
||||
{ { L"Kinyarwanda" }, { L"rw" } },
|
||||
{ { L"Korean" }, { L"ko" } },
|
||||
{ { L"Kurdish (Kurmanji)" }, { L"ku" } },
|
||||
{ { L"Kyrgyz" }, { L"ky" } },
|
||||
{ { L"Lao" }, { L"lo" } },
|
||||
{ { L"Latin" }, { L"la" } },
|
||||
{ { L"Latvian" }, { L"lv" } },
|
||||
{ { L"Lithuanian" }, { L"lt" } },
|
||||
{ { L"Luxembourgish" }, { L"lb" } },
|
||||
{ { L"Macedonian" }, { L"mk" } },
|
||||
{ { L"Malagasy" }, { L"mg" } },
|
||||
{ { L"Malay" }, { L"ms" } },
|
||||
{ { L"Malayalam" }, { L"ml" } },
|
||||
{ { L"Maltese" }, { L"mt" } },
|
||||
{ { L"Maori" }, { L"mi" } },
|
||||
{ { L"Marathi" }, { L"mr" } },
|
||||
{ { L"Mongolian" }, { L"mn" } },
|
||||
{ { L"Myanmar (Burmese)" }, { L"my" } },
|
||||
{ { L"Nepali" }, { L"ne" } },
|
||||
{ { L"Norwegian" }, { L"no" } },
|
||||
{ { L"Odia (Oriya)" }, { L"or" } },
|
||||
{ { L"Pashto" }, { L"ps" } },
|
||||
{ { L"Persian" }, { L"fa" } },
|
||||
{ { L"Polish" }, { L"pl" } },
|
||||
{ { L"Portuguese" }, { L"pt" } },
|
||||
{ { L"Punjabi" }, { L"pa" } },
|
||||
{ { L"Romanian" }, { L"ro" } },
|
||||
{ { L"Russian" }, { L"ru" } },
|
||||
{ { L"Samoan" }, { L"sm" } },
|
||||
{ { L"Scots Gaelic" }, { L"gd" } },
|
||||
{ { L"Serbian" }, { L"sr" } },
|
||||
{ { L"Sesotho" }, { L"st" } },
|
||||
{ { L"Shona" }, { L"sn" } },
|
||||
{ { L"Sindhi" }, { L"sd" } },
|
||||
{ { L"Sinhala" }, { L"si" } },
|
||||
{ { L"Slovak" }, { L"sk" } },
|
||||
{ { L"Slovenian" }, { L"sl" } },
|
||||
{ { L"Somali" }, { L"so" } },
|
||||
{ { L"Spanish" }, { L"es" } },
|
||||
{ { L"Sundanese" }, { L"su" } },
|
||||
{ { L"Swahili" }, { L"sw" } },
|
||||
{ { L"Swedish" }, { L"sv" } },
|
||||
{ { L"Tajik" }, { L"tg" } },
|
||||
{ { L"Tamil" }, { L"ta" } },
|
||||
{ { L"Tatar" }, { L"tt" } },
|
||||
{ { L"Telugu" }, { L"te" } },
|
||||
{ { L"Thai" }, { L"th" } },
|
||||
{ { L"Turkish" }, { L"tr" } },
|
||||
{ { L"Turkmen" }, { L"tk" } },
|
||||
{ { L"Ukrainian" }, { L"uk" } },
|
||||
{ { L"Urdu" }, { L"ur" } },
|
||||
{ { L"Uyghur" }, { L"ug" } },
|
||||
{ { L"Uzbek" }, { L"uz" } },
|
||||
{ { L"Vietnamese" }, { L"vi" } },
|
||||
{ { L"Welsh" }, { L"cy" } },
|
||||
{ { L"Xhosa" }, { L"xh" } },
|
||||
{ { L"Yiddish" }, { L"yi" } },
|
||||
{ { L"Yoruba" }, { L"yo" } },
|
||||
{ { L"Zulu" }, { L"zu" } },
|
||||
{ { L"?" }, { L"auto" } }
|
||||
};
|
||||
std::wstring autoDetectLanguage = L"auto";
|
||||
|
||||
bool translateSelectedOnly = false, rateLimitAll = true, rateLimitSelected = false, useCache = true, useFilter = true;
|
||||
int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 1000;
|
||||
|
||||
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
|
||||
{
|
||||
if (!authKey->empty())
|
||||
if (!tlp.authKey.empty())
|
||||
{
|
||||
std::wstring translateFromComponent = translateFrom.Copy() == autoDetectLanguage ? L"" : L"&source=" + translateFrom.Copy();
|
||||
std::wstring translateFromComponent = tlp.translateFrom == L"?" ? L"" : L"&source=" + codes.at(tlp.translateFrom);
|
||||
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%s", translateTo.Copy(), authKey.Copy(), translateFromComponent).c_str(),
|
||||
FormatString(L"/language/translate/v2?format=text&target=%s&key=%s%s", codes.at(tlp.translateTo), tlp.authKey, 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() };
|
||||
@ -146,7 +256,7 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||
L"Mozilla/5.0 Textractor",
|
||||
L"translate.google.com",
|
||||
L"GET",
|
||||
FormatString(L"/m?sl=%s&tl=%s&q=%s", translateFrom.Copy(), translateTo.Copy(), Escape(text)).c_str()
|
||||
FormatString(L"/m?sl=%s&tl=%s&q=%s", codes.at(tlp.translateFrom), codes.at(tlp.translateTo), Escape(text)).c_str()
|
||||
})
|
||||
{
|
||||
auto start = httpRequest.response.find(L"result-container\">"), end = httpRequest.response.find(L'<', start);
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "qtcommon.h"
|
||||
#include "extension.h"
|
||||
#include "translatewrapper.h"
|
||||
#include "blockmarkup.h"
|
||||
#include "network.h"
|
||||
#include <map>
|
||||
@ -22,22 +23,19 @@ 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 const QStringList languagesTo, languagesFrom;
|
||||
extern bool translateSelectedOnly, rateLimitAll, rateLimitSelected, useCache, useFilter;
|
||||
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, TranslationParam tlp);
|
||||
|
||||
// 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<std::wstring> translateTo = L"en", translateFrom = L"auto", authKey;
|
||||
|
||||
namespace
|
||||
{
|
||||
Synchronized<TranslationParam> tlp;
|
||||
Synchronized<std::map<std::wstring, std::wstring>> translationCache;
|
||||
int savedSize;
|
||||
void SaveCache()
|
||||
@ -60,23 +58,22 @@ public:
|
||||
settings.beginGroup(TRANSLATION_PROVIDER);
|
||||
|
||||
auto translateToCombo = new QComboBox(this);
|
||||
translateToCombo->addItems(languages);
|
||||
int language = -1;
|
||||
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);
|
||||
translateToCombo->addItems(languagesTo);
|
||||
int i = -1;
|
||||
if (settings.contains(TRANSLATE_TO)) i = translateToCombo->findText(settings.value(TRANSLATE_TO).toString());
|
||||
if (i < 0) i = translateToCombo->findText(NATIVE_LANGUAGE, Qt::MatchStartsWith);
|
||||
if (i < 0) i = translateToCombo->findText("English", Qt::MatchStartsWith);
|
||||
translateToCombo->setCurrentIndex(i);
|
||||
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("?", Qt::MatchStartsWith);
|
||||
translateFromCombo->setCurrentIndex(language);
|
||||
translateFromCombo->addItem("?");
|
||||
translateFromCombo->addItems(languagesFrom);
|
||||
i = -1;
|
||||
if (settings.contains(TRANSLATE_FROM)) i = translateFromCombo->findText(settings.value(TRANSLATE_FROM).toString());
|
||||
if (i < 0) i = 0;
|
||||
translateFromCombo->setCurrentIndex(i);
|
||||
SaveTranslateFrom(translateFromCombo->currentText());
|
||||
display->addRow(TRANSLATE_FROM, translateFromCombo);
|
||||
connect(translateFromCombo, &QComboBox::currentTextChanged, this, &Window::SaveTranslateFrom);
|
||||
@ -110,8 +107,8 @@ public:
|
||||
if (GET_API_KEY_FROM)
|
||||
{
|
||||
auto keyEdit = new QLineEdit(settings.value(API_KEY).toString(), this);
|
||||
authKey->assign(S(keyEdit->text()));
|
||||
QObject::connect(keyEdit, &QLineEdit::textChanged, [](QString key) { settings.setValue(API_KEY, S(authKey->assign(S(key)))); });
|
||||
tlp->authKey = S(keyEdit->text());
|
||||
QObject::connect(keyEdit, &QLineEdit::textChanged, [](QString key) { settings.setValue(API_KEY, S(tlp->authKey = S(key))); });
|
||||
auto keyLabel = new QLabel(QString("<a href=\"%1\">%2</a>").arg(GET_API_KEY_FROM, API_KEY), this);
|
||||
keyLabel->setOpenExternalLinks(true);
|
||||
display->addRow(keyLabel, keyEdit);
|
||||
@ -139,11 +136,11 @@ public:
|
||||
private:
|
||||
void SaveTranslateTo(QString language)
|
||||
{
|
||||
settings.setValue(TRANSLATE_TO, S(translateTo->assign(S(language.split(": ")[1]))));
|
||||
settings.setValue(TRANSLATE_TO, S(tlp->translateTo = S(language)));
|
||||
}
|
||||
void SaveTranslateFrom(QString language)
|
||||
{
|
||||
settings.setValue(TRANSLATE_FROM, S(translateFrom->assign(S(language.split(": ")[1]))));
|
||||
settings.setValue(TRANSLATE_FROM, S(tlp->translateFrom = S(language)));
|
||||
}
|
||||
} window;
|
||||
|
||||
@ -186,7 +183,7 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
||||
if (auto it = translationCache->find(sentence); it != translationCache->end()) translation = it->second + L"\x200b"; // dumb hack to not try to translate if stored empty translation
|
||||
}
|
||||
if (translation.empty() && (!translateSelectedOnly || sentenceInfo["current select"]))
|
||||
if (rateLimiter.Request() || !rateLimitAll || (!rateLimitSelected && sentenceInfo["current select"])) std::tie(cache, translation) = Translate(sentence);
|
||||
if (rateLimiter.Request() || !rateLimitAll || (!rateLimitSelected && sentenceInfo["current select"])) std::tie(cache, translation) = Translate(sentence, tlp.Copy());
|
||||
else translation = TOO_MANY_TRANS_REQUESTS;
|
||||
if (useFilter) Trim(translation);
|
||||
if (cache) translationCache->try_emplace(sentence, translation);
|
||||
@ -197,4 +194,13 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST(assert(Translate(L"こんにちは").second.find(L"ello") != std::string::npos));
|
||||
extern const std::unordered_map<std::wstring, std::wstring> codes;
|
||||
TEST(
|
||||
{
|
||||
assert(Translate(L"こんにちは", { L"English", L"?", L"" }).second.find(L"ello") == 1 || strstr(TRANSLATION_PROVIDER, "DevTools"));
|
||||
|
||||
for (auto languages : { languagesFrom, languagesTo }) for (auto language : languages)
|
||||
assert(codes.count(S(language)));
|
||||
assert(codes.count(L"?"));
|
||||
}
|
||||
);
|
||||
|
6
extensions/translatewrapper.h
Normal file
6
extensions/translatewrapper.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
struct TranslationParam
|
||||
{
|
||||
std::wstring translateTo, translateFrom, authKey;
|
||||
};
|
2
text.cpp
2
text.cpp
@ -324,7 +324,7 @@ Clic y arrastra los bordes de la ventana para moverla, o en la esquina inferior
|
||||
#endif // SPANISH
|
||||
|
||||
#ifdef SIMPLIFIED_CHINESE
|
||||
NATIVE_LANGUAGE = "Chinese (simplified)";
|
||||
NATIVE_LANGUAGE = "Chinese (Simplified)";
|
||||
ATTACH = u8"附加到游戏";
|
||||
LAUNCH = u8"启动游戏";
|
||||
CONFIG = u8"配置游戏";
|
||||
|
Loading…
Reference in New Issue
Block a user