diff options
author | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 19:04:52 +0200 |
---|---|---|
committer | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 19:04:52 +0200 |
commit | 1b67dd855c41f5a0cda7ec2a68d98071986ca703 (patch) | |
tree | ab243607f74f78200787bba5b9b88f07ef1b966f /indra/newview/lltranslate.cpp | |
parent | 6d6eabca44d08d5b97bfe3e941d2b9687c2246ea (diff) | |
parent | e1623bb276f83a43ce7a197e388720c05bdefe61 (diff) |
Merge remote-tracking branch 'origin/main' into DRTVWR-600-maint-A
# Conflicts:
# autobuild.xml
# indra/cmake/CMakeLists.txt
# indra/cmake/GoogleMock.cmake
# indra/llaudio/llaudioengine_fmodstudio.cpp
# indra/llaudio/llaudioengine_fmodstudio.h
# indra/llaudio/lllistener_fmodstudio.cpp
# indra/llaudio/lllistener_fmodstudio.h
# indra/llaudio/llstreamingaudio_fmodstudio.cpp
# indra/llaudio/llstreamingaudio_fmodstudio.h
# indra/llcharacter/llmultigesture.cpp
# indra/llcharacter/llmultigesture.h
# indra/llimage/llimage.cpp
# indra/llimage/llimagepng.cpp
# indra/llimage/llimageworker.cpp
# indra/llimage/tests/llimageworker_test.cpp
# indra/llmessage/tests/llmockhttpclient.h
# indra/llprimitive/llgltfmaterial.h
# indra/llrender/llfontfreetype.cpp
# indra/llui/llcombobox.cpp
# indra/llui/llfolderview.cpp
# indra/llui/llfolderviewmodel.h
# indra/llui/lllineeditor.cpp
# indra/llui/lllineeditor.h
# indra/llui/lltextbase.cpp
# indra/llui/lltextbase.h
# indra/llui/lltexteditor.cpp
# indra/llui/lltextvalidate.cpp
# indra/llui/lltextvalidate.h
# indra/llui/lluictrl.h
# indra/llui/llview.cpp
# indra/llwindow/llwindowmacosx.cpp
# indra/newview/app_settings/settings.xml
# indra/newview/llappearancemgr.cpp
# indra/newview/llappearancemgr.h
# indra/newview/llavatarpropertiesprocessor.cpp
# indra/newview/llavatarpropertiesprocessor.h
# indra/newview/llbreadcrumbview.cpp
# indra/newview/llbreadcrumbview.h
# indra/newview/llbreastmotion.cpp
# indra/newview/llbreastmotion.h
# indra/newview/llconversationmodel.h
# indra/newview/lldensityctrl.cpp
# indra/newview/lldensityctrl.h
# indra/newview/llface.inl
# indra/newview/llfloatereditsky.cpp
# indra/newview/llfloatereditwater.cpp
# indra/newview/llfloateremojipicker.h
# indra/newview/llfloaterimsessiontab.cpp
# indra/newview/llfloaterprofiletexture.cpp
# indra/newview/llfloaterprofiletexture.h
# indra/newview/llgesturemgr.cpp
# indra/newview/llgesturemgr.h
# indra/newview/llimpanel.cpp
# indra/newview/llimpanel.h
# indra/newview/llinventorybridge.cpp
# indra/newview/llinventorybridge.h
# indra/newview/llinventoryclipboard.cpp
# indra/newview/llinventoryclipboard.h
# indra/newview/llinventoryfunctions.cpp
# indra/newview/llinventoryfunctions.h
# indra/newview/llinventorygallery.cpp
# indra/newview/lllistbrowser.cpp
# indra/newview/lllistbrowser.h
# indra/newview/llpanelobjectinventory.cpp
# indra/newview/llpanelprofile.cpp
# indra/newview/llpanelprofile.h
# indra/newview/llpreviewgesture.cpp
# indra/newview/llsavedsettingsglue.cpp
# indra/newview/llsavedsettingsglue.h
# indra/newview/lltooldraganddrop.cpp
# indra/newview/llurllineeditorctrl.cpp
# indra/newview/llvectorperfoptions.cpp
# indra/newview/llvectorperfoptions.h
# indra/newview/llviewerparceloverlay.cpp
# indra/newview/llviewertexlayer.cpp
# indra/newview/llviewertexturelist.cpp
# indra/newview/macmain.h
# indra/test/test.cpp
Diffstat (limited to 'indra/newview/lltranslate.cpp')
-rw-r--r-- | indra/newview/lltranslate.cpp | 2606 |
1 files changed, 1303 insertions, 1303 deletions
diff --git a/indra/newview/lltranslate.cpp b/indra/newview/lltranslate.cpp index e099fd8769..9659922333 100644 --- a/indra/newview/lltranslate.cpp +++ b/indra/newview/lltranslate.cpp @@ -1,1303 +1,1303 @@ -/** -* @file lltranslate.cpp -* @brief Functions for translating text via Google Translate. -* - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltranslate.h" - -#include <curl/curl.h> - -#include "llbufferstream.h" -#include "lltrans.h" -#include "llui.h" -#include "llversioninfo.h" -#include "llviewercontrol.h" -#include "llcoros.h" -#include "llcorehttputil.h" -#include "llurlregistry.h" -#include "stringize.h" - -#include <boost/json.hpp> - -static const std::string AZURE_NOTRANSLATE_OPENING_TAG("<div translate=\"no\">"); -static const std::string AZURE_NOTRANSLATE_CLOSING_TAG("</div>"); - -/** -* Handler of an HTTP machine translation service. -* -* Derived classes know the service URL -* and how to parse the translation result. -*/ -class LLTranslationAPIHandler -{ -public: - typedef std::pair<std::string, std::string> LanguagePair_t; - - /** - * Get URL for translation of the given string. - * - * Sending HTTP GET request to the URL will initiate translation. - * - * @param[out] url Place holder for the result. - * @param from_lang Source language. Leave empty for auto-detection. - * @param to_lang Target language. - * @param text Text to translate. - */ - virtual std::string getTranslateURL( - const std::string &from_lang, - const std::string &to_lang, - const std::string &text) const = 0; - - /** - * Get URL to verify the given API key. - * - * Sending request to the URL verifies the key. - * Positive HTTP response (code 200) means that the key is valid. - * - * @param[out] url Place holder for the URL. - * @param[in] key Key to verify. - */ - virtual std::string getKeyVerificationURL( - const LLSD &key) const = 0; - - /** - * Check API verification response. - * - * @param[out] bool true if valid. - * @param[in] response - * @param[in] status - */ - virtual bool checkVerificationResponse( - const LLSD &response, - int status) const = 0; - - /** - * Parse translation response. - * - * @param[in,out] status HTTP status. May be modified while parsing. - * @param body Response text. - * @param[out] translation Translated text. - * @param[out] detected_lang Detected source language. May be empty. - * @param[out] err_msg Error message (in case of error). - */ - virtual bool parseResponse( - const LLSD& http_response, - int& status, - const std::string& body, - std::string& translation, - std::string& detected_lang, - std::string& err_msg) const = 0; - - /** - * @return if the handler is configured to function properly - */ - virtual bool isConfigured() const = 0; - - virtual LLTranslate::EService getCurrentService() = 0; - - virtual void verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) = 0; - virtual void translateMessage(LanguagePair_t fromTo, std::string msg, LLTranslate::TranslationSuccess_fn success, LLTranslate::TranslationFailure_fn failure); - - - virtual ~LLTranslationAPIHandler() {} - - void verifyKeyCoro(LLTranslate::EService service, LLSD key, LLTranslate::KeyVerificationResult_fn fnc); - void translateMessageCoro(LanguagePair_t fromTo, std::string msg, LLTranslate::TranslationSuccess_fn success, LLTranslate::TranslationFailure_fn failure); - - virtual void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const = 0; - virtual void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent, const LLSD &key) const = 0; - virtual LLSD sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url, - const std::string & msg, - const std::string& from_lang, - const std::string& to_lang) const = 0; - virtual LLSD verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url) const = 0; -}; - -void LLTranslationAPIHandler::translateMessage(LanguagePair_t fromTo, std::string msg, LLTranslate::TranslationSuccess_fn success, LLTranslate::TranslationFailure_fn failure) -{ - LLCoros::instance().launch("Translation", boost::bind(&LLTranslationAPIHandler::translateMessageCoro, - this, fromTo, msg, success, failure)); - -} - -void LLTranslationAPIHandler::verifyKeyCoro(LLTranslate::EService service, LLSD key, LLTranslate::KeyVerificationResult_fn fnc) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - - - std::string user_agent = stringize( - LLVersionInfo::instance().getChannel(), ' ', - LLVersionInfo::instance().getMajor(), '.', - LLVersionInfo::instance().getMinor(), '.', - LLVersionInfo::instance().getPatch(), " (", - LLVersionInfo::instance().getBuild(), ')'); - - initHttpHeader(httpHeaders, user_agent, key); - - httpOpts->setFollowRedirects(true); - httpOpts->setSSLVerifyPeer(false); - - std::string url = this->getKeyVerificationURL(key); - if (url.empty()) - { - LL_INFOS("Translate") << "No translation URL" << LL_ENDL; - return; - } - - std::string::size_type delim_pos = url.find("://"); - if (delim_pos == std::string::npos) - { - LL_INFOS("Translate") << "URL is missing a scheme" << LL_ENDL; - return; - } - - LLSD result = verifyAndSuspend(httpAdapter, httpRequest, httpOpts, httpHeaders, url); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - bool bOk = true; - int parseResult = status.getType(); - if (!checkVerificationResponse(httpResults, parseResult)) - { - bOk = false; - } - - if (!fnc.empty()) - { - fnc(service, bOk, parseResult); - } -} - -void LLTranslationAPIHandler::translateMessageCoro(LanguagePair_t fromTo, std::string msg, - LLTranslate::TranslationSuccess_fn success, LLTranslate::TranslationFailure_fn failure) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - - - std::string user_agent = stringize( - LLVersionInfo::instance().getChannel(), ' ', - LLVersionInfo::instance().getMajor(), '.', - LLVersionInfo::instance().getMinor(), '.', - LLVersionInfo::instance().getPatch(), " (", - LLVersionInfo::instance().getBuild(), ')'); - - initHttpHeader(httpHeaders, user_agent); - httpOpts->setSSLVerifyPeer(false); - - std::string url = this->getTranslateURL(fromTo.first, fromTo.second, msg); - if (url.empty()) - { - LL_INFOS("Translate") << "No translation URL" << LL_ENDL; - return; - } - - LLSD result = sendMessageAndSuspend(httpAdapter, httpRequest, httpOpts, httpHeaders, url, msg, fromTo.first, fromTo.second); - - if (LLApp::isQuitting()) - { - return; - } - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - std::string translation, err_msg; - std::string detected_lang(fromTo.second); - - int parseResult = status.getType(); - const LLSD::Binary &rawBody = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].asBinary(); - std::string body(rawBody.begin(), rawBody.end()); - - bool res = false; - - try - { - res = parseResponse(httpResults, parseResult, body, translation, detected_lang, err_msg); - } - catch (std::out_of_range&) - { - LL_WARNS() << "Out of range exception on string " << body << LL_ENDL; - } - catch (...) - { - LOG_UNHANDLED_EXCEPTION( "Exception on string " + body ); - } - - if (res) - { - // Fix up the response - LLStringUtil::replaceString(translation, "<", "<"); - LLStringUtil::replaceString(translation, ">", ">"); - LLStringUtil::replaceString(translation, """, "\""); - LLStringUtil::replaceString(translation, "'", "'"); - LLStringUtil::replaceString(translation, "&", "&"); - LLStringUtil::replaceString(translation, "'", "'"); - - if (!success.empty()) - success(translation, detected_lang); - } - else - { - if (err_msg.empty() && httpResults.has("error_body")) - { - err_msg = httpResults["error_body"].asString(); - } - - if (err_msg.empty()) - { - err_msg = LLTrans::getString("TranslationResponseParseError"); - } - - LL_WARNS() << "Translation request failed: " << err_msg << LL_ENDL; - if (!failure.empty()) - failure(status, err_msg); - } -} - -//========================================================================= -/// Google Translate v2 API handler. -class LLGoogleTranslationHandler : public LLTranslationAPIHandler -{ - LOG_CLASS(LLGoogleTranslationHandler); - -public: - std::string getTranslateURL( - const std::string &from_lang, - const std::string &to_lang, - const std::string &text) const override; - std::string getKeyVerificationURL( - const LLSD &key) const override; - bool checkVerificationResponse( - const LLSD &response, - int status) const override; - bool parseResponse( - const LLSD& http_response, - int& status, - const std::string& body, - std::string& translation, - std::string& detected_lang, - std::string& err_msg) const override; - bool isConfigured() const override; - - LLTranslate::EService getCurrentService() override { return LLTranslate::EService::SERVICE_GOOGLE; } - - void verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) override; - - void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const override; - void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent, const LLSD &key) const override; - LLSD sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url, - const std::string & msg, - const std::string& from_lang, - const std::string& to_lang) const override; - - LLSD verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url) const override; - -private: - static void parseErrorResponse( - const boost::json::value& root, - int& status, - std::string& err_msg); - static bool parseTranslation( - const boost::json::value& root, - std::string& translation, - std::string& detected_lang); - static std::string getAPIKey(); -}; - -//------------------------------------------------------------------------- -// virtual -std::string LLGoogleTranslationHandler::getTranslateURL( - const std::string &from_lang, - const std::string &to_lang, - const std::string &text) const -{ - std::string url = std::string("https://www.googleapis.com/language/translate/v2?key=") - + getAPIKey() + "&q=" + LLURI::escape(text) + "&target=" + to_lang; - if (!from_lang.empty()) - { - url += "&source=" + from_lang; - } - return url; -} - -// virtual -std::string LLGoogleTranslationHandler::getKeyVerificationURL( - const LLSD& key) const -{ - std::string url = std::string("https://www.googleapis.com/language/translate/v2/languages?key=") - + key.asString() +"&target=en"; - return url; -} - -//virtual -bool LLGoogleTranslationHandler::checkVerificationResponse( - const LLSD &response, - int status) const -{ - return status == HTTP_OK; -} - -// virtual -bool LLGoogleTranslationHandler::parseResponse( - const LLSD& http_response, - int& status, - const std::string& body, - std::string& translation, - std::string& detected_lang, - std::string& err_msg) const -{ - const std::string& text = !body.empty() ? body : http_response["error_body"].asStringRef(); - - boost::json::error_code ec; - boost::json::value root = boost::json::parse(text, ec); - if (ec.failed()) - { - err_msg = ec.what(); - return false; - } - - if (root.is_object()) - { - // Request succeeded, extract translation from the XML body. - if (parseTranslation(root, translation, detected_lang)) - return true; - - // Request failed. Extract error message from the XML body. - parseErrorResponse(root, status, err_msg); - } - - return false; -} - -// virtual -bool LLGoogleTranslationHandler::isConfigured() const -{ - return !getAPIKey().empty(); -} - -// static -void LLGoogleTranslationHandler::parseErrorResponse( - const boost::json::value& root, - int& status, - std::string& err_msg) -{ - boost::json::error_code ec; - auto message = root.find_pointer("/data/message", ec); - auto code = root.find_pointer("/data/code", ec); - if (!message || !code) - { - return; - } - - auto message_val = boost::json::try_value_to<std::string>(*message); - auto code_val = boost::json::try_value_to<int>(*code); - if (!message_val || !code_val) - { - return; - } - - err_msg = message_val.value(); - status = code_val.value(); -} - -// static -bool LLGoogleTranslationHandler::parseTranslation( - const boost::json::value& root, - std::string& translation, - std::string& detected_lang) -{ - boost::json::error_code ec; - auto translated_text = root.find_pointer("/data/translations/0/translatedText", ec); - if (!translated_text) return false; - - auto text_val = boost::json::try_value_to<std::string>(*translated_text); - if (!text_val) - { - LL_WARNS() << "Failed to parse translation" << text_val.error() << LL_ENDL; - return false; - } - - translation = text_val.value(); - - auto language = root.find_pointer("/data/translations/0/detectedSourceLanguage", ec); - if (language) - { - auto lang_val = boost::json::try_value_to<std::string>(*language); - detected_lang = lang_val ? lang_val.value() : ""; - } - - return true; -} - -// static -std::string LLGoogleTranslationHandler::getAPIKey() -{ - static LLCachedControl<std::string> google_key(gSavedSettings, "GoogleTranslateAPIKey"); - return google_key; -} - -/*virtual*/ -void LLGoogleTranslationHandler::verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) -{ - LLCoros::instance().launch("Google /Verify Key", boost::bind(&LLTranslationAPIHandler::verifyKeyCoro, - this, LLTranslate::SERVICE_GOOGLE, key, fnc)); -} - -/*virtual*/ -void LLGoogleTranslationHandler::initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const -{ - headers->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_JSON); - headers->append(HTTP_OUT_HEADER_USER_AGENT, user_agent); -} - -/*virtual*/ -void LLGoogleTranslationHandler::initHttpHeader( - LLCore::HttpHeaders::ptr_t headers, - const std::string& user_agent, - const LLSD &key) const -{ - initHttpHeader(headers, user_agent); -} - -LLSD LLGoogleTranslationHandler::sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url, - const std::string & msg, - const std::string& from_lang, - const std::string& to_lang) const -{ - return adapter->getRawAndSuspend(request, url, options, headers); -} - -LLSD LLGoogleTranslationHandler::verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url) const -{ - return adapter->getAndSuspend(request, url, options, headers); -} - -//========================================================================= -/// Microsoft Translator v2 API handler. -class LLAzureTranslationHandler : public LLTranslationAPIHandler -{ - LOG_CLASS(LLAzureTranslationHandler); - -public: - std::string getTranslateURL( - const std::string &from_lang, - const std::string &to_lang, - const std::string &text) const override; - std::string getKeyVerificationURL( - const LLSD &key) const override; - bool checkVerificationResponse( - const LLSD &response, - int status) const override; - bool parseResponse( - const LLSD& http_response, - int& status, - const std::string& body, - std::string& translation, - std::string& detected_lang, - std::string& err_msg) const override; - bool isConfigured() const override; - - LLTranslate::EService getCurrentService() override { return LLTranslate::EService::SERVICE_AZURE; } - - void verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) override; - - void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const override; - void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent, const LLSD &key) const override; - LLSD sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url, - const std::string & msg, - const std::string& from_lang, - const std::string& to_lang) const override; - - LLSD verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url) const override; -private: - static std::string parseErrorResponse( - const std::string& body); - static LLSD getAPIKey(); - static std::string getAPILanguageCode(const std::string& lang); - -}; - -//------------------------------------------------------------------------- -// virtual -std::string LLAzureTranslationHandler::getTranslateURL( - const std::string &from_lang, - const std::string &to_lang, - const std::string &text) const -{ - std::string url; - LLSD key = getAPIKey(); - if (key.isMap()) - { - std::string endpoint = key["endpoint"].asString(); - - if (*endpoint.rbegin() != '/') - { - endpoint += "/"; - } - url = endpoint + std::string("translate?api-version=3.0&to=") - + getAPILanguageCode(to_lang); - } - return url; -} - - -// virtual -std::string LLAzureTranslationHandler::getKeyVerificationURL( - const LLSD& key) const -{ - std::string url; - if (key.isMap()) - { - std::string endpoint = key["endpoint"].asString(); - if (*endpoint.rbegin() != '/') - { - endpoint += "/"; - } - url = endpoint + std::string("translate?api-version=3.0&to=en"); - } - return url; -} - -//virtual -bool LLAzureTranslationHandler::checkVerificationResponse( - const LLSD &response, - int status) const -{ - if (status == HTTP_UNAUTHORIZED) - { - LL_DEBUGS("Translate") << "Key unathorised" << LL_ENDL; - return false; - } - - if (status == HTTP_NOT_FOUND) - { - LL_DEBUGS("Translate") << "Either endpoint doesn't have requested resource" << LL_ENDL; - return false; - } - - if (status != HTTP_BAD_REQUEST) - { - LL_DEBUGS("Translate") << "Unexpected error code" << LL_ENDL; - return false; - } - - if (!response.has("error_body")) - { - LL_DEBUGS("Translate") << "Unexpected response, no error returned" << LL_ENDL; - return false; - } - - // Expected: "{\"error\":{\"code\":400000,\"message\":\"One of the request inputs is not valid.\"}}" - // But for now just verify response is a valid json - - boost::json::error_code ec; - boost::json::value root = boost::json::parse(response["error_body"].asString(), ec); - if (ec.failed()) - { - LL_DEBUGS("Translate") << "Failed to parse error_body:" << ec.what() << LL_ENDL; - return false; - } - - return true; -} - -// virtual -bool LLAzureTranslationHandler::parseResponse( - const LLSD& http_response, - int& status, - const std::string& body, - std::string& translation, - std::string& detected_lang, - std::string& err_msg) const -{ - if (status != HTTP_OK) - { - if (http_response.has("error_body")) - err_msg = parseErrorResponse(http_response["error_body"].asString()); - return false; - } - - //Example: - // "[{\"detectedLanguage\":{\"language\":\"en\",\"score\":1.0},\"translations\":[{\"text\":\"Hello, what is your name?\",\"to\":\"en\"}]}]" - - boost::json::error_code ec; - boost::json::value root = boost::json::parse(body, ec); - if (ec.failed()) - { - err_msg = ec.what(); - return false; - } - auto language = root.find_pointer("/0/detectedLanguage/language", ec); - if (!language) return false; - - auto translated_text = root.find_pointer("/0/translations/0/text", ec); - if (!translated_text) return false; - - auto lang_val = boost::json::try_value_to<std::string>(*language); - auto text_val = boost::json::try_value_to<std::string>(*translated_text); - if (!lang_val || !text_val) - { - LL_WARNS() << "Failed to parse translation" << lang_val.error() << text_val.error() << LL_ENDL; - return false; - } - - detected_lang = lang_val.value(); - translation = text_val.value(); - - return true; -} - -// virtual -bool LLAzureTranslationHandler::isConfigured() const -{ - return getAPIKey().isMap(); -} - -//static -std::string LLAzureTranslationHandler::parseErrorResponse( - const std::string& body) -{ - // Expected: "{\"error\":{\"code\":400000,\"message\":\"One of the request inputs is not valid.\"}}" - // But for now just verify response is a valid json with an error - - boost::json::error_code ec; - boost::json::value root = boost::json::parse(body, ec); - if (ec.failed()) - { - return {}; - } - - auto err_msg = root.find_pointer("/error/message", ec); - if (!err_msg) - { - return {}; - } - - auto err_msg_val = boost::json::try_value_to<std::string>(*err_msg); - if (!err_msg_val) - { - return {}; - } - return err_msg_val.value(); -} - -// static -LLSD LLAzureTranslationHandler::getAPIKey() -{ - static LLCachedControl<LLSD> azure_key(gSavedSettings, "AzureTranslateAPIKey"); - return azure_key; -} - -// static -std::string LLAzureTranslationHandler::getAPILanguageCode(const std::string& lang) -{ - return lang == "zh" ? "zh-CHT" : lang; // treat Chinese as Traditional Chinese -} - -/*virtual*/ -void LLAzureTranslationHandler::verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) -{ - LLCoros::instance().launch("Azure /Verify Key", boost::bind(&LLTranslationAPIHandler::verifyKeyCoro, - this, LLTranslate::SERVICE_AZURE, key, fnc)); -} -/*virtual*/ -void LLAzureTranslationHandler::initHttpHeader( - LLCore::HttpHeaders::ptr_t headers, - const std::string& user_agent) const -{ - initHttpHeader(headers, user_agent, getAPIKey()); -} - -/*virtual*/ -void LLAzureTranslationHandler::initHttpHeader( - LLCore::HttpHeaders::ptr_t headers, - const std::string& user_agent, - const LLSD &key) const -{ - headers->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_JSON); - headers->append(HTTP_OUT_HEADER_USER_AGENT, user_agent); - - if (key.has("id")) - { - // Token based autorization - headers->append("Ocp-Apim-Subscription-Key", key["id"].asString()); - } - if (key.has("region")) - { - // ex: "westeurope" - headers->append("Ocp-Apim-Subscription-Region", key["region"].asString()); - } -} - -LLSD LLAzureTranslationHandler::sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url, - const std::string & msg, - const std::string& from_lang, - const std::string& to_lang) const -{ - LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray); - LLCore::BufferArrayStream outs(rawbody.get()); - - static const std::string allowed_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz " - "0123456789" - "-._~"; - - outs << "[{\"text\":\""; - outs << LLURI::escape(msg, allowed_chars); - outs << "\"}]"; - - return adapter->postRawAndSuspend(request, url, rawbody, options, headers); -} - -LLSD LLAzureTranslationHandler::verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url) const -{ - LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray); - LLCore::BufferArrayStream outs(rawbody.get()); - outs << "[{\"intentionally_invalid_400\"}]"; - - return adapter->postRawAndSuspend(request, url, rawbody, options, headers); -} - -//========================================================================= -/// DeepL Translator API handler. -class LLDeepLTranslationHandler: public LLTranslationAPIHandler -{ - LOG_CLASS(LLDeepLTranslationHandler); - -public: - std::string getTranslateURL( - const std::string& from_lang, - const std::string& to_lang, - const std::string& text) const override; - std::string getKeyVerificationURL( - const LLSD& key) const override; - bool checkVerificationResponse( - const LLSD& response, - int status) const override; - bool parseResponse( - const LLSD& http_response, - int& status, - const std::string& body, - std::string& translation, - std::string& detected_lang, - std::string& err_msg) const override; - bool isConfigured() const override; - - LLTranslate::EService getCurrentService() override - { - return LLTranslate::EService::SERVICE_DEEPL; - } - - void verifyKey(const LLSD& key, LLTranslate::KeyVerificationResult_fn fnc) override; - - void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const override; - void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent, const LLSD& key) const override; - LLSD sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string& url, - const std::string& msg, - const std::string& from_lang, - const std::string& to_lang) const override; - - LLSD verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string& url) const override; -private: - static std::string parseErrorResponse( - const std::string& body); - static LLSD getAPIKey(); - static std::string getAPILanguageCode(const std::string& lang); -}; - -//------------------------------------------------------------------------- -// virtual -std::string LLDeepLTranslationHandler::getTranslateURL( - const std::string& from_lang, - const std::string& to_lang, - const std::string& text) const -{ - std::string url; - LLSD key = getAPIKey(); - if (key.isMap()) - { - url = key["domain"].asString(); - - if (*url.rbegin() != '/') - { - url += "/"; - } - url += std::string("v2/translate"); - } - return url; -} - - -// virtual -std::string LLDeepLTranslationHandler::getKeyVerificationURL( - const LLSD& key) const -{ - std::string url; - if (key.isMap()) - { - url = key["domain"].asString(); - - if (*url.rbegin() != '/') - { - url += "/"; - } - url += std::string("v2/translate"); - } - return url; -} - -//virtual -bool LLDeepLTranslationHandler::checkVerificationResponse( - const LLSD& response, - int status) const -{ - // Might need to parse body to make sure we got - // a valid response and not a message - return status == HTTP_OK; -} - -// virtual -bool LLDeepLTranslationHandler::parseResponse( - const LLSD& http_response, - int& status, - const std::string& body, - std::string& translation, - std::string& detected_lang, - std::string& err_msg) const -{ - if (status != HTTP_OK) - { - if (http_response.has("error_body")) - err_msg = parseErrorResponse(http_response["error_body"].asString()); - return false; - } - - //Example: - // "{\"translations\":[{\"detected_source_language\":\"EN\",\"text\":\"test\"}]}" - - boost::json::error_code ec; - boost::json::value root = boost::json::parse(body, ec); - if (ec.failed()) - { - err_msg = ec.message(); - return false; - } - - auto detected_langp = root.find_pointer("/translations/0/detected_source_language", ec); - if (!detected_langp || ec.failed()) // empty response? should not happen - { - err_msg = ec.message(); - return false; - } - - // Request succeeded, extract translation from the response. - auto text_valp = root.find_pointer("/translations/0/text", ec); - if (!text_valp || ec.failed()) - { - err_msg = ec.message(); - return false; - } - - auto lang_result = boost::json::try_value_to<std::string>(*detected_langp); - auto text_result = boost::json::try_value_to<std::string>(*text_valp); - if (!lang_result || !text_result) - { - return false; - } - - detected_lang = lang_result.value(); - LLStringUtil::toLower(detected_lang); - translation = text_result.value(); - - return true; -} - -// virtual -bool LLDeepLTranslationHandler::isConfigured() const -{ - return getAPIKey().isMap(); -} - -//static -std::string LLDeepLTranslationHandler::parseErrorResponse( - const std::string& body) -{ - // Example: "{\"message\":\"One of the request inputs is not valid.\"}" - boost::json::error_code ec; - boost::json::value root = boost::json::parse(body, ec); - if (ec.failed()) - { - return {}; - } - - auto message_ptr = root.find_pointer("/message", ec); - if (!message_ptr || ec.failed()) - { - return {}; - } - - auto message_val = boost::json::try_value_to<std::string>(*message_ptr); - if (!message_val) - return {}; - - return message_val.value(); -} - -// static -LLSD LLDeepLTranslationHandler::getAPIKey() -{ - static LLCachedControl<LLSD> deepl_key(gSavedSettings, "DeepLTranslateAPIKey"); - return deepl_key; -} - -// static -std::string LLDeepLTranslationHandler::getAPILanguageCode(const std::string& lang) -{ - return lang == "zh" ? "zh-CHT" : lang; // treat Chinese as Traditional Chinese -} - -/*virtual*/ -void LLDeepLTranslationHandler::verifyKey(const LLSD& key, LLTranslate::KeyVerificationResult_fn fnc) -{ - LLCoros::instance().launch("DeepL /Verify Key", boost::bind(&LLTranslationAPIHandler::verifyKeyCoro, - this, LLTranslate::SERVICE_DEEPL, key, fnc)); -} -/*virtual*/ -void LLDeepLTranslationHandler::initHttpHeader( - LLCore::HttpHeaders::ptr_t headers, - const std::string& user_agent) const -{ - initHttpHeader(headers, user_agent, getAPIKey()); -} - -/*virtual*/ -void LLDeepLTranslationHandler::initHttpHeader( - LLCore::HttpHeaders::ptr_t headers, - const std::string& user_agent, - const LLSD& key) const -{ - headers->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/x-www-form-urlencoded"); - headers->append(HTTP_OUT_HEADER_USER_AGENT, user_agent); - - if (key.has("id")) - { - std::string authkey = "DeepL-Auth-Key " + key["id"].asString(); - headers->append(HTTP_OUT_HEADER_AUTHORIZATION, authkey); - } -} - -LLSD LLDeepLTranslationHandler::sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string& url, - const std::string& msg, - const std::string& from_lang, - const std::string& to_lang) const -{ - LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray); - LLCore::BufferArrayStream outs(rawbody.get()); - outs << "text="; - std::string escaped_string = LLURI::escape(msg); - outs << escaped_string; - outs << "&target_lang="; - std::string lang = to_lang; - LLStringUtil::toUpper(lang); - outs << lang; - - return adapter->postRawAndSuspend(request, url, rawbody, options, headers); -} - -LLSD LLDeepLTranslationHandler::verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string& url) const -{ - LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray); - LLCore::BufferArrayStream outs(rawbody.get()); - outs << "text=&target_lang=EN"; - - return adapter->postRawAndSuspend(request, url, rawbody, options, headers); -} - -//========================================================================= -LLTranslate::LLTranslate(): - mCharsSeen(0), - mCharsSent(0), - mFailureCount(0), - mSuccessCount(0) -{ -} - -LLTranslate::~LLTranslate() -{ -} - -/*static*/ -void LLTranslate::translateMessage(const std::string &from_lang, const std::string &to_lang, - const std::string &mesg, TranslationSuccess_fn success, TranslationFailure_fn failure) -{ - LLTranslationAPIHandler& handler = getPreferredHandler(); - - handler.translateMessage(LLTranslationAPIHandler::LanguagePair_t(from_lang, to_lang), addNoTranslateTags(mesg), success, failure); -} - -std::string LLTranslate::addNoTranslateTags(std::string mesg) -{ - if (getPreferredHandler().getCurrentService() == SERVICE_GOOGLE) - { - return mesg; - } - - if (getPreferredHandler().getCurrentService() == SERVICE_DEEPL) - { - return mesg; - } - - if (getPreferredHandler().getCurrentService() == SERVICE_AZURE) - { - // https://learn.microsoft.com/en-us/azure/cognitive-services/translator/prevent-translation - std::string upd_msg(mesg); - LLUrlMatch match; - S32 dif = 0; - //surround all links (including SLURLs) with 'no-translate' tags to prevent unnecessary translation - while (LLUrlRegistry::instance().findUrl(mesg, match)) - { - upd_msg.insert(dif + match.getStart(), AZURE_NOTRANSLATE_OPENING_TAG); - upd_msg.insert(dif + AZURE_NOTRANSLATE_OPENING_TAG.size() + match.getEnd() + 1, AZURE_NOTRANSLATE_CLOSING_TAG); - mesg.erase(match.getStart(), match.getEnd() - match.getStart()); - dif += match.getEnd() - match.getStart() + AZURE_NOTRANSLATE_OPENING_TAG.size() + AZURE_NOTRANSLATE_CLOSING_TAG.size(); - } - return upd_msg; - } - return mesg; -} - -std::string LLTranslate::removeNoTranslateTags(std::string mesg) -{ - if (getPreferredHandler().getCurrentService() == SERVICE_GOOGLE) - { - return mesg; - } - if (getPreferredHandler().getCurrentService() == SERVICE_DEEPL) - { - return mesg; - } - - if (getPreferredHandler().getCurrentService() == SERVICE_AZURE) - { - std::string upd_msg(mesg); - LLUrlMatch match; - S32 opening_tag_size = AZURE_NOTRANSLATE_OPENING_TAG.size(); - S32 closing_tag_size = AZURE_NOTRANSLATE_CLOSING_TAG.size(); - S32 dif = 0; - //remove 'no-translate' tags we added to the links before - while (LLUrlRegistry::instance().findUrl(mesg, match)) - { - if (upd_msg.substr(dif + match.getStart() - opening_tag_size, opening_tag_size) == AZURE_NOTRANSLATE_OPENING_TAG) - { - upd_msg.erase(dif + match.getStart() - opening_tag_size, opening_tag_size); - dif -= opening_tag_size; - - if (upd_msg.substr(dif + match.getEnd() + 1, closing_tag_size) == AZURE_NOTRANSLATE_CLOSING_TAG) - { - upd_msg.replace(dif + match.getEnd() + 1, closing_tag_size, " "); - dif -= closing_tag_size - 1; - } - } - mesg.erase(match.getStart(), match.getUrl().size()); - dif += match.getUrl().size(); - } - return upd_msg; - } - - return mesg; -} - -/*static*/ -void LLTranslate::verifyKey(EService service, const LLSD &key, KeyVerificationResult_fn fnc) -{ - LLTranslationAPIHandler& handler = getHandler(service); - - handler.verifyKey(key, fnc); -} - - -//static -std::string LLTranslate::getTranslateLanguage() -{ - std::string language = gSavedSettings.getString("TranslateLanguage"); - if (language.empty() || language == "default") - { - language = LLUI::getLanguage(); - } - language = language.substr(0,2); - return language; -} - -// static -bool LLTranslate::isTranslationConfigured() -{ - return getPreferredHandler().isConfigured(); -} - -void LLTranslate::logCharsSeen(size_t count) -{ - mCharsSeen += count; -} - -void LLTranslate::logCharsSent(size_t count) -{ - mCharsSent += count; -} - -void LLTranslate::logSuccess(S32 count) -{ - mSuccessCount += count; -} - -void LLTranslate::logFailure(S32 count) -{ - mFailureCount += count; -} - -LLSD LLTranslate::asLLSD() const -{ - LLSD res; - bool on = gSavedSettings.getBOOL("TranslateChat"); - res["on"] = on; - res["chars_seen"] = (S32) mCharsSeen; - if (on) - { - res["chars_sent"] = (S32) mCharsSent; - res["success_count"] = mSuccessCount; - res["failure_count"] = mFailureCount; - res["language"] = getTranslateLanguage(); - res["service"] = gSavedSettings.getString("TranslationService"); - } - return res; -} - -// static -LLTranslationAPIHandler& LLTranslate::getPreferredHandler() -{ - EService service = SERVICE_AZURE; - - std::string service_str = gSavedSettings.getString("TranslationService"); - if (service_str == "google") - { - service = SERVICE_GOOGLE; - } - if (service_str == "azure") - { - service = SERVICE_AZURE; - } - if (service_str == "deepl") - { - service = SERVICE_DEEPL; - } - - return getHandler(service); -} - -// static -LLTranslationAPIHandler& LLTranslate::getHandler(EService service) -{ - static LLGoogleTranslationHandler google; - static LLAzureTranslationHandler azure; - static LLDeepLTranslationHandler deepl; - - switch (service) - { - case SERVICE_AZURE: - return azure; - case SERVICE_GOOGLE: - return google; - case SERVICE_DEEPL: - return deepl; - } - - return azure; -} +/**
+* @file lltranslate.cpp
+* @brief Functions for translating text via Google Translate.
+*
+ * $LicenseInfo:firstyear=2009&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "lltranslate.h"
+
+#include <curl/curl.h>
+
+#include "llbufferstream.h"
+#include "lltrans.h"
+#include "llui.h"
+#include "llversioninfo.h"
+#include "llviewercontrol.h"
+#include "llcoros.h"
+#include "llcorehttputil.h"
+#include "llurlregistry.h"
+#include "stringize.h"
+
+#include <boost/json.hpp>
+
+static const std::string AZURE_NOTRANSLATE_OPENING_TAG("<div translate=\"no\">");
+static const std::string AZURE_NOTRANSLATE_CLOSING_TAG("</div>");
+
+/**
+* Handler of an HTTP machine translation service.
+*
+* Derived classes know the service URL
+* and how to parse the translation result.
+*/
+class LLTranslationAPIHandler
+{
+public:
+ typedef std::pair<std::string, std::string> LanguagePair_t;
+
+ /**
+ * Get URL for translation of the given string.
+ *
+ * Sending HTTP GET request to the URL will initiate translation.
+ *
+ * @param[out] url Place holder for the result.
+ * @param from_lang Source language. Leave empty for auto-detection.
+ * @param to_lang Target language.
+ * @param text Text to translate.
+ */
+ virtual std::string getTranslateURL(
+ const std::string &from_lang,
+ const std::string &to_lang,
+ const std::string &text) const = 0;
+
+ /**
+ * Get URL to verify the given API key.
+ *
+ * Sending request to the URL verifies the key.
+ * Positive HTTP response (code 200) means that the key is valid.
+ *
+ * @param[out] url Place holder for the URL.
+ * @param[in] key Key to verify.
+ */
+ virtual std::string getKeyVerificationURL(
+ const LLSD &key) const = 0;
+
+ /**
+ * Check API verification response.
+ *
+ * @param[out] bool true if valid.
+ * @param[in] response
+ * @param[in] status
+ */
+ virtual bool checkVerificationResponse(
+ const LLSD &response,
+ int status) const = 0;
+
+ /**
+ * Parse translation response.
+ *
+ * @param[in,out] status HTTP status. May be modified while parsing.
+ * @param body Response text.
+ * @param[out] translation Translated text.
+ * @param[out] detected_lang Detected source language. May be empty.
+ * @param[out] err_msg Error message (in case of error).
+ */
+ virtual bool parseResponse(
+ const LLSD& http_response,
+ int& status,
+ const std::string& body,
+ std::string& translation,
+ std::string& detected_lang,
+ std::string& err_msg) const = 0;
+
+ /**
+ * @return if the handler is configured to function properly
+ */
+ virtual bool isConfigured() const = 0;
+
+ virtual LLTranslate::EService getCurrentService() = 0;
+
+ virtual void verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) = 0;
+ virtual void translateMessage(LanguagePair_t fromTo, std::string msg, LLTranslate::TranslationSuccess_fn success, LLTranslate::TranslationFailure_fn failure);
+
+
+ virtual ~LLTranslationAPIHandler() {}
+
+ void verifyKeyCoro(LLTranslate::EService service, LLSD key, LLTranslate::KeyVerificationResult_fn fnc);
+ void translateMessageCoro(LanguagePair_t fromTo, std::string msg, LLTranslate::TranslationSuccess_fn success, LLTranslate::TranslationFailure_fn failure);
+
+ virtual void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const = 0;
+ virtual void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent, const LLSD &key) const = 0;
+ virtual LLSD sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter,
+ LLCore::HttpRequest::ptr_t request,
+ LLCore::HttpOptions::ptr_t options,
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string & url,
+ const std::string & msg,
+ const std::string& from_lang,
+ const std::string& to_lang) const = 0;
+ virtual LLSD verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter,
+ LLCore::HttpRequest::ptr_t request,
+ LLCore::HttpOptions::ptr_t options,
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string & url) const = 0;
+};
+
+void LLTranslationAPIHandler::translateMessage(LanguagePair_t fromTo, std::string msg, LLTranslate::TranslationSuccess_fn success, LLTranslate::TranslationFailure_fn failure)
+{
+ LLCoros::instance().launch("Translation", boost::bind(&LLTranslationAPIHandler::translateMessageCoro,
+ this, fromTo, msg, success, failure));
+
+}
+
+void LLTranslationAPIHandler::verifyKeyCoro(LLTranslate::EService service, LLSD key, LLTranslate::KeyVerificationResult_fn fnc)
+{
+ LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
+ LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
+ httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy));
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
+ LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions);
+ LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders);
+
+
+ std::string user_agent = stringize(
+ LLVersionInfo::instance().getChannel(), ' ',
+ LLVersionInfo::instance().getMajor(), '.',
+ LLVersionInfo::instance().getMinor(), '.',
+ LLVersionInfo::instance().getPatch(), " (",
+ LLVersionInfo::instance().getBuild(), ')');
+
+ initHttpHeader(httpHeaders, user_agent, key);
+
+ httpOpts->setFollowRedirects(true);
+ httpOpts->setSSLVerifyPeer(false);
+
+ std::string url = this->getKeyVerificationURL(key);
+ if (url.empty())
+ {
+ LL_INFOS("Translate") << "No translation URL" << LL_ENDL;
+ return;
+ }
+
+ std::string::size_type delim_pos = url.find("://");
+ if (delim_pos == std::string::npos)
+ {
+ LL_INFOS("Translate") << "URL is missing a scheme" << LL_ENDL;
+ return;
+ }
+
+ LLSD result = verifyAndSuspend(httpAdapter, httpRequest, httpOpts, httpHeaders, url);
+
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+
+ bool bOk = true;
+ int parseResult = status.getType();
+ if (!checkVerificationResponse(httpResults, parseResult))
+ {
+ bOk = false;
+ }
+
+ if (!fnc.empty())
+ {
+ fnc(service, bOk, parseResult);
+ }
+}
+
+void LLTranslationAPIHandler::translateMessageCoro(LanguagePair_t fromTo, std::string msg,
+ LLTranslate::TranslationSuccess_fn success, LLTranslate::TranslationFailure_fn failure)
+{
+ LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
+ LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
+ httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy));
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
+ LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions);
+ LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders);
+
+
+ std::string user_agent = stringize(
+ LLVersionInfo::instance().getChannel(), ' ',
+ LLVersionInfo::instance().getMajor(), '.',
+ LLVersionInfo::instance().getMinor(), '.',
+ LLVersionInfo::instance().getPatch(), " (",
+ LLVersionInfo::instance().getBuild(), ')');
+
+ initHttpHeader(httpHeaders, user_agent);
+ httpOpts->setSSLVerifyPeer(false);
+
+ std::string url = this->getTranslateURL(fromTo.first, fromTo.second, msg);
+ if (url.empty())
+ {
+ LL_INFOS("Translate") << "No translation URL" << LL_ENDL;
+ return;
+ }
+
+ LLSD result = sendMessageAndSuspend(httpAdapter, httpRequest, httpOpts, httpHeaders, url, msg, fromTo.first, fromTo.second);
+
+ if (LLApp::isQuitting())
+ {
+ return;
+ }
+
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+
+ std::string translation, err_msg;
+ std::string detected_lang(fromTo.second);
+
+ int parseResult = status.getType();
+ const LLSD::Binary &rawBody = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].asBinary();
+ std::string body(rawBody.begin(), rawBody.end());
+
+ bool res = false;
+
+ try
+ {
+ res = parseResponse(httpResults, parseResult, body, translation, detected_lang, err_msg);
+ }
+ catch (std::out_of_range&)
+ {
+ LL_WARNS() << "Out of range exception on string " << body << LL_ENDL;
+ }
+ catch (...)
+ {
+ LOG_UNHANDLED_EXCEPTION( "Exception on string " + body );
+ }
+
+ if (res)
+ {
+ // Fix up the response
+ LLStringUtil::replaceString(translation, "<", "<");
+ LLStringUtil::replaceString(translation, ">", ">");
+ LLStringUtil::replaceString(translation, """, "\"");
+ LLStringUtil::replaceString(translation, "'", "'");
+ LLStringUtil::replaceString(translation, "&", "&");
+ LLStringUtil::replaceString(translation, "'", "'");
+
+ if (!success.empty())
+ success(translation, detected_lang);
+ }
+ else
+ {
+ if (err_msg.empty() && httpResults.has("error_body"))
+ {
+ err_msg = httpResults["error_body"].asString();
+ }
+
+ if (err_msg.empty())
+ {
+ err_msg = LLTrans::getString("TranslationResponseParseError");
+ }
+
+ LL_WARNS() << "Translation request failed: " << err_msg << LL_ENDL;
+ if (!failure.empty())
+ failure(status, err_msg);
+ }
+}
+
+//=========================================================================
+/// Google Translate v2 API handler.
+class LLGoogleTranslationHandler : public LLTranslationAPIHandler
+{
+ LOG_CLASS(LLGoogleTranslationHandler);
+
+public:
+ std::string getTranslateURL(
+ const std::string &from_lang,
+ const std::string &to_lang,
+ const std::string &text) const override;
+ std::string getKeyVerificationURL(
+ const LLSD &key) const override;
+ bool checkVerificationResponse(
+ const LLSD &response,
+ int status) const override;
+ bool parseResponse(
+ const LLSD& http_response,
+ int& status,
+ const std::string& body,
+ std::string& translation,
+ std::string& detected_lang,
+ std::string& err_msg) const override;
+ bool isConfigured() const override;
+
+ LLTranslate::EService getCurrentService() override { return LLTranslate::EService::SERVICE_GOOGLE; }
+
+ void verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) override;
+
+ void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const override;
+ void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent, const LLSD &key) const override;
+ LLSD sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter,
+ LLCore::HttpRequest::ptr_t request,
+ LLCore::HttpOptions::ptr_t options,
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string & url,
+ const std::string & msg,
+ const std::string& from_lang,
+ const std::string& to_lang) const override;
+
+ LLSD verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter,
+ LLCore::HttpRequest::ptr_t request,
+ LLCore::HttpOptions::ptr_t options,
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string & url) const override;
+
+private:
+ static void parseErrorResponse(
+ const boost::json::value& root,
+ int& status,
+ std::string& err_msg);
+ static bool parseTranslation(
+ const boost::json::value& root,
+ std::string& translation,
+ std::string& detected_lang);
+ static std::string getAPIKey();
+};
+
+//-------------------------------------------------------------------------
+// virtual
+std::string LLGoogleTranslationHandler::getTranslateURL(
+ const std::string &from_lang,
+ const std::string &to_lang,
+ const std::string &text) const
+{
+ std::string url = std::string("https://www.googleapis.com/language/translate/v2?key=")
+ + getAPIKey() + "&q=" + LLURI::escape(text) + "&target=" + to_lang;
+ if (!from_lang.empty())
+ {
+ url += "&source=" + from_lang;
+ }
+ return url;
+}
+
+// virtual
+std::string LLGoogleTranslationHandler::getKeyVerificationURL(
+ const LLSD& key) const
+{
+ std::string url = std::string("https://www.googleapis.com/language/translate/v2/languages?key=")
+ + key.asString() +"&target=en";
+ return url;
+}
+
+//virtual
+bool LLGoogleTranslationHandler::checkVerificationResponse(
+ const LLSD &response,
+ int status) const
+{
+ return status == HTTP_OK;
+}
+
+// virtual
+bool LLGoogleTranslationHandler::parseResponse(
+ const LLSD& http_response,
+ int& status,
+ const std::string& body,
+ std::string& translation,
+ std::string& detected_lang,
+ std::string& err_msg) const
+{
+ const std::string& text = !body.empty() ? body : http_response["error_body"].asStringRef();
+
+ boost::json::error_code ec;
+ boost::json::value root = boost::json::parse(text, ec);
+ if (ec.failed())
+ {
+ err_msg = ec.what();
+ return false;
+ }
+
+ if (root.is_object())
+ {
+ // Request succeeded, extract translation from the XML body.
+ if (parseTranslation(root, translation, detected_lang))
+ return true;
+
+ // Request failed. Extract error message from the XML body.
+ parseErrorResponse(root, status, err_msg);
+ }
+
+ return false;
+}
+
+// virtual
+bool LLGoogleTranslationHandler::isConfigured() const
+{
+ return !getAPIKey().empty();
+}
+
+// static
+void LLGoogleTranslationHandler::parseErrorResponse(
+ const boost::json::value& root,
+ int& status,
+ std::string& err_msg)
+{
+ boost::json::error_code ec;
+ auto message = root.find_pointer("/data/message", ec);
+ auto code = root.find_pointer("/data/code", ec);
+ if (!message || !code)
+ {
+ return;
+ }
+
+ auto message_val = boost::json::try_value_to<std::string>(*message);
+ auto code_val = boost::json::try_value_to<int>(*code);
+ if (!message_val || !code_val)
+ {
+ return;
+ }
+
+ err_msg = message_val.value();
+ status = code_val.value();
+}
+
+// static
+bool LLGoogleTranslationHandler::parseTranslation(
+ const boost::json::value& root,
+ std::string& translation,
+ std::string& detected_lang)
+{
+ boost::json::error_code ec;
+ auto translated_text = root.find_pointer("/data/translations/0/translatedText", ec);
+ if (!translated_text) return false;
+
+ auto text_val = boost::json::try_value_to<std::string>(*translated_text);
+ if (!text_val)
+ {
+ LL_WARNS() << "Failed to parse translation" << text_val.error() << LL_ENDL;
+ return false;
+ }
+
+ translation = text_val.value();
+
+ auto language = root.find_pointer("/data/translations/0/detectedSourceLanguage", ec);
+ if (language)
+ {
+ auto lang_val = boost::json::try_value_to<std::string>(*language);
+ detected_lang = lang_val ? lang_val.value() : "";
+ }
+
+ return true;
+}
+
+// static
+std::string LLGoogleTranslationHandler::getAPIKey()
+{
+ static LLCachedControl<std::string> google_key(gSavedSettings, "GoogleTranslateAPIKey");
+ return google_key;
+}
+
+/*virtual*/
+void LLGoogleTranslationHandler::verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc)
+{
+ LLCoros::instance().launch("Google /Verify Key", boost::bind(&LLTranslationAPIHandler::verifyKeyCoro,
+ this, LLTranslate::SERVICE_GOOGLE, key, fnc));
+}
+
+/*virtual*/
+void LLGoogleTranslationHandler::initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const
+{
+ headers->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_JSON);
+ headers->append(HTTP_OUT_HEADER_USER_AGENT, user_agent);
+}
+
+/*virtual*/
+void LLGoogleTranslationHandler::initHttpHeader(
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string& user_agent,
+ const LLSD &key) const
+{
+ initHttpHeader(headers, user_agent);
+}
+
+LLSD LLGoogleTranslationHandler::sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter,
+ LLCore::HttpRequest::ptr_t request,
+ LLCore::HttpOptions::ptr_t options,
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string & url,
+ const std::string & msg,
+ const std::string& from_lang,
+ const std::string& to_lang) const
+{
+ return adapter->getRawAndSuspend(request, url, options, headers);
+}
+
+LLSD LLGoogleTranslationHandler::verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter,
+ LLCore::HttpRequest::ptr_t request,
+ LLCore::HttpOptions::ptr_t options,
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string & url) const
+{
+ return adapter->getAndSuspend(request, url, options, headers);
+}
+
+//=========================================================================
+/// Microsoft Translator v2 API handler.
+class LLAzureTranslationHandler : public LLTranslationAPIHandler
+{
+ LOG_CLASS(LLAzureTranslationHandler);
+
+public:
+ std::string getTranslateURL(
+ const std::string &from_lang,
+ const std::string &to_lang,
+ const std::string &text) const override;
+ std::string getKeyVerificationURL(
+ const LLSD &key) const override;
+ bool checkVerificationResponse(
+ const LLSD &response,
+ int status) const override;
+ bool parseResponse(
+ const LLSD& http_response,
+ int& status,
+ const std::string& body,
+ std::string& translation,
+ std::string& detected_lang,
+ std::string& err_msg) const override;
+ bool isConfigured() const override;
+
+ LLTranslate::EService getCurrentService() override { return LLTranslate::EService::SERVICE_AZURE; }
+
+ void verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) override;
+
+ void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const override;
+ void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent, const LLSD &key) const override;
+ LLSD sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter,
+ LLCore::HttpRequest::ptr_t request,
+ LLCore::HttpOptions::ptr_t options,
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string & url,
+ const std::string & msg,
+ const std::string& from_lang,
+ const std::string& to_lang) const override;
+
+ LLSD verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter,
+ LLCore::HttpRequest::ptr_t request,
+ LLCore::HttpOptions::ptr_t options,
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string & url) const override;
+private:
+ static std::string parseErrorResponse(
+ const std::string& body);
+ static LLSD getAPIKey();
+ static std::string getAPILanguageCode(const std::string& lang);
+
+};
+
+//-------------------------------------------------------------------------
+// virtual
+std::string LLAzureTranslationHandler::getTranslateURL(
+ const std::string &from_lang,
+ const std::string &to_lang,
+ const std::string &text) const
+{
+ std::string url;
+ LLSD key = getAPIKey();
+ if (key.isMap())
+ {
+ std::string endpoint = key["endpoint"].asString();
+
+ if (*endpoint.rbegin() != '/')
+ {
+ endpoint += "/";
+ }
+ url = endpoint + std::string("translate?api-version=3.0&to=")
+ + getAPILanguageCode(to_lang);
+ }
+ return url;
+}
+
+
+// virtual
+std::string LLAzureTranslationHandler::getKeyVerificationURL(
+ const LLSD& key) const
+{
+ std::string url;
+ if (key.isMap())
+ {
+ std::string endpoint = key["endpoint"].asString();
+ if (*endpoint.rbegin() != '/')
+ {
+ endpoint += "/";
+ }
+ url = endpoint + std::string("translate?api-version=3.0&to=en");
+ }
+ return url;
+}
+
+//virtual
+bool LLAzureTranslationHandler::checkVerificationResponse(
+ const LLSD &response,
+ int status) const
+{
+ if (status == HTTP_UNAUTHORIZED)
+ {
+ LL_DEBUGS("Translate") << "Key unathorised" << LL_ENDL;
+ return false;
+ }
+
+ if (status == HTTP_NOT_FOUND)
+ {
+ LL_DEBUGS("Translate") << "Either endpoint doesn't have requested resource" << LL_ENDL;
+ return false;
+ }
+
+ if (status != HTTP_BAD_REQUEST)
+ {
+ LL_DEBUGS("Translate") << "Unexpected error code" << LL_ENDL;
+ return false;
+ }
+
+ if (!response.has("error_body"))
+ {
+ LL_DEBUGS("Translate") << "Unexpected response, no error returned" << LL_ENDL;
+ return false;
+ }
+
+ // Expected: "{\"error\":{\"code\":400000,\"message\":\"One of the request inputs is not valid.\"}}"
+ // But for now just verify response is a valid json
+
+ boost::json::error_code ec;
+ boost::json::value root = boost::json::parse(response["error_body"].asString(), ec);
+ if (ec.failed())
+ {
+ LL_DEBUGS("Translate") << "Failed to parse error_body:" << ec.what() << LL_ENDL;
+ return false;
+ }
+
+ return true;
+}
+
+// virtual
+bool LLAzureTranslationHandler::parseResponse(
+ const LLSD& http_response,
+ int& status,
+ const std::string& body,
+ std::string& translation,
+ std::string& detected_lang,
+ std::string& err_msg) const
+{
+ if (status != HTTP_OK)
+ {
+ if (http_response.has("error_body"))
+ err_msg = parseErrorResponse(http_response["error_body"].asString());
+ return false;
+ }
+
+ //Example:
+ // "[{\"detectedLanguage\":{\"language\":\"en\",\"score\":1.0},\"translations\":[{\"text\":\"Hello, what is your name?\",\"to\":\"en\"}]}]"
+
+ boost::json::error_code ec;
+ boost::json::value root = boost::json::parse(body, ec);
+ if (ec.failed())
+ {
+ err_msg = ec.what();
+ return false;
+ }
+ auto language = root.find_pointer("/0/detectedLanguage/language", ec);
+ if (!language) return false;
+
+ auto translated_text = root.find_pointer("/0/translations/0/text", ec);
+ if (!translated_text) return false;
+
+ auto lang_val = boost::json::try_value_to<std::string>(*language);
+ auto text_val = boost::json::try_value_to<std::string>(*translated_text);
+ if (!lang_val || !text_val)
+ {
+ LL_WARNS() << "Failed to parse translation" << lang_val.error() << text_val.error() << LL_ENDL;
+ return false;
+ }
+
+ detected_lang = lang_val.value();
+ translation = text_val.value();
+
+ return true;
+}
+
+// virtual
+bool LLAzureTranslationHandler::isConfigured() const
+{
+ return getAPIKey().isMap();
+}
+
+//static
+std::string LLAzureTranslationHandler::parseErrorResponse(
+ const std::string& body)
+{
+ // Expected: "{\"error\":{\"code\":400000,\"message\":\"One of the request inputs is not valid.\"}}"
+ // But for now just verify response is a valid json with an error
+
+ boost::json::error_code ec;
+ boost::json::value root = boost::json::parse(body, ec);
+ if (ec.failed())
+ {
+ return {};
+ }
+
+ auto err_msg = root.find_pointer("/error/message", ec);
+ if (!err_msg)
+ {
+ return {};
+ }
+
+ auto err_msg_val = boost::json::try_value_to<std::string>(*err_msg);
+ if (!err_msg_val)
+ {
+ return {};
+ }
+ return err_msg_val.value();
+}
+
+// static
+LLSD LLAzureTranslationHandler::getAPIKey()
+{
+ static LLCachedControl<LLSD> azure_key(gSavedSettings, "AzureTranslateAPIKey");
+ return azure_key;
+}
+
+// static
+std::string LLAzureTranslationHandler::getAPILanguageCode(const std::string& lang)
+{
+ return lang == "zh" ? "zh-CHT" : lang; // treat Chinese as Traditional Chinese
+}
+
+/*virtual*/
+void LLAzureTranslationHandler::verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc)
+{
+ LLCoros::instance().launch("Azure /Verify Key", boost::bind(&LLTranslationAPIHandler::verifyKeyCoro,
+ this, LLTranslate::SERVICE_AZURE, key, fnc));
+}
+/*virtual*/
+void LLAzureTranslationHandler::initHttpHeader(
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string& user_agent) const
+{
+ initHttpHeader(headers, user_agent, getAPIKey());
+}
+
+/*virtual*/
+void LLAzureTranslationHandler::initHttpHeader(
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string& user_agent,
+ const LLSD &key) const
+{
+ headers->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_JSON);
+ headers->append(HTTP_OUT_HEADER_USER_AGENT, user_agent);
+
+ if (key.has("id"))
+ {
+ // Token based autorization
+ headers->append("Ocp-Apim-Subscription-Key", key["id"].asString());
+ }
+ if (key.has("region"))
+ {
+ // ex: "westeurope"
+ headers->append("Ocp-Apim-Subscription-Region", key["region"].asString());
+ }
+}
+
+LLSD LLAzureTranslationHandler::sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter,
+ LLCore::HttpRequest::ptr_t request,
+ LLCore::HttpOptions::ptr_t options,
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string & url,
+ const std::string & msg,
+ const std::string& from_lang,
+ const std::string& to_lang) const
+{
+ LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray);
+ LLCore::BufferArrayStream outs(rawbody.get());
+
+ static const std::string allowed_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz "
+ "0123456789"
+ "-._~";
+
+ outs << "[{\"text\":\"";
+ outs << LLURI::escape(msg, allowed_chars);
+ outs << "\"}]";
+
+ return adapter->postRawAndSuspend(request, url, rawbody, options, headers);
+}
+
+LLSD LLAzureTranslationHandler::verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter,
+ LLCore::HttpRequest::ptr_t request,
+ LLCore::HttpOptions::ptr_t options,
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string & url) const
+{
+ LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray);
+ LLCore::BufferArrayStream outs(rawbody.get());
+ outs << "[{\"intentionally_invalid_400\"}]";
+
+ return adapter->postRawAndSuspend(request, url, rawbody, options, headers);
+}
+
+//=========================================================================
+/// DeepL Translator API handler.
+class LLDeepLTranslationHandler: public LLTranslationAPIHandler
+{
+ LOG_CLASS(LLDeepLTranslationHandler);
+
+public:
+ std::string getTranslateURL(
+ const std::string& from_lang,
+ const std::string& to_lang,
+ const std::string& text) const override;
+ std::string getKeyVerificationURL(
+ const LLSD& key) const override;
+ bool checkVerificationResponse(
+ const LLSD& response,
+ int status) const override;
+ bool parseResponse(
+ const LLSD& http_response,
+ int& status,
+ const std::string& body,
+ std::string& translation,
+ std::string& detected_lang,
+ std::string& err_msg) const override;
+ bool isConfigured() const override;
+
+ LLTranslate::EService getCurrentService() override
+ {
+ return LLTranslate::EService::SERVICE_DEEPL;
+ }
+
+ void verifyKey(const LLSD& key, LLTranslate::KeyVerificationResult_fn fnc) override;
+
+ void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const override;
+ void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent, const LLSD& key) const override;
+ LLSD sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter,
+ LLCore::HttpRequest::ptr_t request,
+ LLCore::HttpOptions::ptr_t options,
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string& url,
+ const std::string& msg,
+ const std::string& from_lang,
+ const std::string& to_lang) const override;
+
+ LLSD verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter,
+ LLCore::HttpRequest::ptr_t request,
+ LLCore::HttpOptions::ptr_t options,
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string& url) const override;
+private:
+ static std::string parseErrorResponse(
+ const std::string& body);
+ static LLSD getAPIKey();
+ static std::string getAPILanguageCode(const std::string& lang);
+};
+
+//-------------------------------------------------------------------------
+// virtual
+std::string LLDeepLTranslationHandler::getTranslateURL(
+ const std::string& from_lang,
+ const std::string& to_lang,
+ const std::string& text) const
+{
+ std::string url;
+ LLSD key = getAPIKey();
+ if (key.isMap())
+ {
+ url = key["domain"].asString();
+
+ if (*url.rbegin() != '/')
+ {
+ url += "/";
+ }
+ url += std::string("v2/translate");
+ }
+ return url;
+}
+
+
+// virtual
+std::string LLDeepLTranslationHandler::getKeyVerificationURL(
+ const LLSD& key) const
+{
+ std::string url;
+ if (key.isMap())
+ {
+ url = key["domain"].asString();
+
+ if (*url.rbegin() != '/')
+ {
+ url += "/";
+ }
+ url += std::string("v2/translate");
+ }
+ return url;
+}
+
+//virtual
+bool LLDeepLTranslationHandler::checkVerificationResponse(
+ const LLSD& response,
+ int status) const
+{
+ // Might need to parse body to make sure we got
+ // a valid response and not a message
+ return status == HTTP_OK;
+}
+
+// virtual
+bool LLDeepLTranslationHandler::parseResponse(
+ const LLSD& http_response,
+ int& status,
+ const std::string& body,
+ std::string& translation,
+ std::string& detected_lang,
+ std::string& err_msg) const
+{
+ if (status != HTTP_OK)
+ {
+ if (http_response.has("error_body"))
+ err_msg = parseErrorResponse(http_response["error_body"].asString());
+ return false;
+ }
+
+ //Example:
+ // "{\"translations\":[{\"detected_source_language\":\"EN\",\"text\":\"test\"}]}"
+
+ boost::json::error_code ec;
+ boost::json::value root = boost::json::parse(body, ec);
+ if (ec.failed())
+ {
+ err_msg = ec.message();
+ return false;
+ }
+
+ auto detected_langp = root.find_pointer("/translations/0/detected_source_language", ec);
+ if (!detected_langp || ec.failed()) // empty response? should not happen
+ {
+ err_msg = ec.message();
+ return false;
+ }
+
+ // Request succeeded, extract translation from the response.
+ auto text_valp = root.find_pointer("/translations/0/text", ec);
+ if (!text_valp || ec.failed())
+ {
+ err_msg = ec.message();
+ return false;
+ }
+
+ auto lang_result = boost::json::try_value_to<std::string>(*detected_langp);
+ auto text_result = boost::json::try_value_to<std::string>(*text_valp);
+ if (!lang_result || !text_result)
+ {
+ return false;
+ }
+
+ detected_lang = lang_result.value();
+ LLStringUtil::toLower(detected_lang);
+ translation = text_result.value();
+
+ return true;
+}
+
+// virtual
+bool LLDeepLTranslationHandler::isConfigured() const
+{
+ return getAPIKey().isMap();
+}
+
+//static
+std::string LLDeepLTranslationHandler::parseErrorResponse(
+ const std::string& body)
+{
+ // Example: "{\"message\":\"One of the request inputs is not valid.\"}"
+ boost::json::error_code ec;
+ boost::json::value root = boost::json::parse(body, ec);
+ if (ec.failed())
+ {
+ return {};
+ }
+
+ auto message_ptr = root.find_pointer("/message", ec);
+ if (!message_ptr || ec.failed())
+ {
+ return {};
+ }
+
+ auto message_val = boost::json::try_value_to<std::string>(*message_ptr);
+ if (!message_val)
+ return {};
+
+ return message_val.value();
+}
+
+// static
+LLSD LLDeepLTranslationHandler::getAPIKey()
+{
+ static LLCachedControl<LLSD> deepl_key(gSavedSettings, "DeepLTranslateAPIKey");
+ return deepl_key;
+}
+
+// static
+std::string LLDeepLTranslationHandler::getAPILanguageCode(const std::string& lang)
+{
+ return lang == "zh" ? "zh-CHT" : lang; // treat Chinese as Traditional Chinese
+}
+
+/*virtual*/
+void LLDeepLTranslationHandler::verifyKey(const LLSD& key, LLTranslate::KeyVerificationResult_fn fnc)
+{
+ LLCoros::instance().launch("DeepL /Verify Key", boost::bind(&LLTranslationAPIHandler::verifyKeyCoro,
+ this, LLTranslate::SERVICE_DEEPL, key, fnc));
+}
+/*virtual*/
+void LLDeepLTranslationHandler::initHttpHeader(
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string& user_agent) const
+{
+ initHttpHeader(headers, user_agent, getAPIKey());
+}
+
+/*virtual*/
+void LLDeepLTranslationHandler::initHttpHeader(
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string& user_agent,
+ const LLSD& key) const
+{
+ headers->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/x-www-form-urlencoded");
+ headers->append(HTTP_OUT_HEADER_USER_AGENT, user_agent);
+
+ if (key.has("id"))
+ {
+ std::string authkey = "DeepL-Auth-Key " + key["id"].asString();
+ headers->append(HTTP_OUT_HEADER_AUTHORIZATION, authkey);
+ }
+}
+
+LLSD LLDeepLTranslationHandler::sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter,
+ LLCore::HttpRequest::ptr_t request,
+ LLCore::HttpOptions::ptr_t options,
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string& url,
+ const std::string& msg,
+ const std::string& from_lang,
+ const std::string& to_lang) const
+{
+ LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray);
+ LLCore::BufferArrayStream outs(rawbody.get());
+ outs << "text=";
+ std::string escaped_string = LLURI::escape(msg);
+ outs << escaped_string;
+ outs << "&target_lang=";
+ std::string lang = to_lang;
+ LLStringUtil::toUpper(lang);
+ outs << lang;
+
+ return adapter->postRawAndSuspend(request, url, rawbody, options, headers);
+}
+
+LLSD LLDeepLTranslationHandler::verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter,
+ LLCore::HttpRequest::ptr_t request,
+ LLCore::HttpOptions::ptr_t options,
+ LLCore::HttpHeaders::ptr_t headers,
+ const std::string& url) const
+{
+ LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray);
+ LLCore::BufferArrayStream outs(rawbody.get());
+ outs << "text=&target_lang=EN";
+
+ return adapter->postRawAndSuspend(request, url, rawbody, options, headers);
+}
+
+//=========================================================================
+LLTranslate::LLTranslate():
+ mCharsSeen(0),
+ mCharsSent(0),
+ mFailureCount(0),
+ mSuccessCount(0)
+{
+}
+
+LLTranslate::~LLTranslate()
+{
+}
+
+/*static*/
+void LLTranslate::translateMessage(const std::string &from_lang, const std::string &to_lang,
+ const std::string &mesg, TranslationSuccess_fn success, TranslationFailure_fn failure)
+{
+ LLTranslationAPIHandler& handler = getPreferredHandler();
+
+ handler.translateMessage(LLTranslationAPIHandler::LanguagePair_t(from_lang, to_lang), addNoTranslateTags(mesg), success, failure);
+}
+
+std::string LLTranslate::addNoTranslateTags(std::string mesg)
+{
+ if (getPreferredHandler().getCurrentService() == SERVICE_GOOGLE)
+ {
+ return mesg;
+ }
+
+ if (getPreferredHandler().getCurrentService() == SERVICE_DEEPL)
+ {
+ return mesg;
+ }
+
+ if (getPreferredHandler().getCurrentService() == SERVICE_AZURE)
+ {
+ // https://learn.microsoft.com/en-us/azure/cognitive-services/translator/prevent-translation
+ std::string upd_msg(mesg);
+ LLUrlMatch match;
+ S32 dif = 0;
+ //surround all links (including SLURLs) with 'no-translate' tags to prevent unnecessary translation
+ while (LLUrlRegistry::instance().findUrl(mesg, match))
+ {
+ upd_msg.insert(dif + match.getStart(), AZURE_NOTRANSLATE_OPENING_TAG);
+ upd_msg.insert(dif + AZURE_NOTRANSLATE_OPENING_TAG.size() + match.getEnd() + 1, AZURE_NOTRANSLATE_CLOSING_TAG);
+ mesg.erase(match.getStart(), match.getEnd() - match.getStart());
+ dif += match.getEnd() - match.getStart() + AZURE_NOTRANSLATE_OPENING_TAG.size() + AZURE_NOTRANSLATE_CLOSING_TAG.size();
+ }
+ return upd_msg;
+ }
+ return mesg;
+}
+
+std::string LLTranslate::removeNoTranslateTags(std::string mesg)
+{
+ if (getPreferredHandler().getCurrentService() == SERVICE_GOOGLE)
+ {
+ return mesg;
+ }
+ if (getPreferredHandler().getCurrentService() == SERVICE_DEEPL)
+ {
+ return mesg;
+ }
+
+ if (getPreferredHandler().getCurrentService() == SERVICE_AZURE)
+ {
+ std::string upd_msg(mesg);
+ LLUrlMatch match;
+ S32 opening_tag_size = AZURE_NOTRANSLATE_OPENING_TAG.size();
+ S32 closing_tag_size = AZURE_NOTRANSLATE_CLOSING_TAG.size();
+ S32 dif = 0;
+ //remove 'no-translate' tags we added to the links before
+ while (LLUrlRegistry::instance().findUrl(mesg, match))
+ {
+ if (upd_msg.substr(dif + match.getStart() - opening_tag_size, opening_tag_size) == AZURE_NOTRANSLATE_OPENING_TAG)
+ {
+ upd_msg.erase(dif + match.getStart() - opening_tag_size, opening_tag_size);
+ dif -= opening_tag_size;
+
+ if (upd_msg.substr(dif + match.getEnd() + 1, closing_tag_size) == AZURE_NOTRANSLATE_CLOSING_TAG)
+ {
+ upd_msg.replace(dif + match.getEnd() + 1, closing_tag_size, " ");
+ dif -= closing_tag_size - 1;
+ }
+ }
+ mesg.erase(match.getStart(), match.getUrl().size());
+ dif += match.getUrl().size();
+ }
+ return upd_msg;
+ }
+
+ return mesg;
+}
+
+/*static*/
+void LLTranslate::verifyKey(EService service, const LLSD &key, KeyVerificationResult_fn fnc)
+{
+ LLTranslationAPIHandler& handler = getHandler(service);
+
+ handler.verifyKey(key, fnc);
+}
+
+
+//static
+std::string LLTranslate::getTranslateLanguage()
+{
+ std::string language = gSavedSettings.getString("TranslateLanguage");
+ if (language.empty() || language == "default")
+ {
+ language = LLUI::getLanguage();
+ }
+ language = language.substr(0,2);
+ return language;
+}
+
+// static
+bool LLTranslate::isTranslationConfigured()
+{
+ return getPreferredHandler().isConfigured();
+}
+
+void LLTranslate::logCharsSeen(size_t count)
+{
+ mCharsSeen += count;
+}
+
+void LLTranslate::logCharsSent(size_t count)
+{
+ mCharsSent += count;
+}
+
+void LLTranslate::logSuccess(S32 count)
+{
+ mSuccessCount += count;
+}
+
+void LLTranslate::logFailure(S32 count)
+{
+ mFailureCount += count;
+}
+
+LLSD LLTranslate::asLLSD() const
+{
+ LLSD res;
+ bool on = gSavedSettings.getBOOL("TranslateChat");
+ res["on"] = on;
+ res["chars_seen"] = (S32) mCharsSeen;
+ if (on)
+ {
+ res["chars_sent"] = (S32) mCharsSent;
+ res["success_count"] = mSuccessCount;
+ res["failure_count"] = mFailureCount;
+ res["language"] = getTranslateLanguage();
+ res["service"] = gSavedSettings.getString("TranslationService");
+ }
+ return res;
+}
+
+// static
+LLTranslationAPIHandler& LLTranslate::getPreferredHandler()
+{
+ EService service = SERVICE_AZURE;
+
+ std::string service_str = gSavedSettings.getString("TranslationService");
+ if (service_str == "google")
+ {
+ service = SERVICE_GOOGLE;
+ }
+ if (service_str == "azure")
+ {
+ service = SERVICE_AZURE;
+ }
+ if (service_str == "deepl")
+ {
+ service = SERVICE_DEEPL;
+ }
+
+ return getHandler(service);
+}
+
+// static
+LLTranslationAPIHandler& LLTranslate::getHandler(EService service)
+{
+ static LLGoogleTranslationHandler google;
+ static LLAzureTranslationHandler azure;
+ static LLDeepLTranslationHandler deepl;
+
+ switch (service)
+ {
+ case SERVICE_AZURE:
+ return azure;
+ case SERVICE_GOOGLE:
+ return google;
+ case SERVICE_DEEPL:
+ return deepl;
+ }
+
+ return azure;
+}
|