diff options
author | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 21:25:21 +0200 |
---|---|---|
committer | Andrey Lihatskiy <alihatskiy@productengine.com> | 2024-05-22 22:40:26 +0300 |
commit | e2e37cced861b98de8c1a7c9c0d3a50d2d90e433 (patch) | |
tree | 1bb897489ce524986f6196201c10ac0d8861aa5f /indra/newview/lltranslate.cpp | |
parent | 069ea06848f766466f1a281144c82a0f2bd79f3a (diff) |
Fix line endlings
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 9659922333..ae3bcfdf79 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; +} |