From e2552ec6737fe734ffd5b4768193c6a890d66f70 Mon Sep 17 00:00:00 2001
From: Vadim ProductEngine <vsavchuk@productengine.com>
Date: Tue, 6 Sep 2011 17:45:47 +0300
Subject: [PATCH] STORM-1577 WIP Implemented translation via Microsoft
 Translator and Google Translate v2 APIs.

---
 indra/newview/app_settings/settings.xml |  33 +++
 indra/newview/lltranslate.cpp           | 312 ++++++++++++++++++++----
 indra/newview/lltranslate.h             |  87 ++-----
 indra/newview/llviewermessage.cpp       |   7 +-
 4 files changed, 316 insertions(+), 123 deletions(-)

diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 0996f75fbba..2549538df27 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -10922,6 +10922,39 @@
       <key>Value</key>
       <integer>0</integer>
     </map>
+    <key>TranslationService</key>
+    <map>
+      <key>Comment</key>
+      <string>Translation API to use. (google_v1|google_v2|bing)</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>String</string>
+      <key>Value</key>
+      <string>google_v1</string>
+    </map>
+    <key>GoogleTranslateAPIv2Key</key>
+    <map>
+      <key>Comment</key>
+      <string>Google Translate API v2 key</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>String</string>
+      <key>Value</key>
+      <string></string>
+    </map>
+    <key>BingTranslateAPIKey</key>
+    <map>
+      <key>Comment</key>
+      <string>Bing AppID to use with the Microsoft Translator V2 API</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>String</string>
+      <key>Value</key>
+      <string></string>
+    </map>
     <key>TutorialURL</key>
     <map>
       <key>Comment</key>
diff --git a/indra/newview/lltranslate.cpp b/indra/newview/lltranslate.cpp
index 2f60b6b90b4..e29ea373ce3 100644
--- a/indra/newview/lltranslate.cpp
+++ b/indra/newview/lltranslate.cpp
@@ -37,74 +37,263 @@
 
 #include "reader.h"
 
-// These two are concatenated with the language specifiers to form a complete Google Translate URL
-const char* LLTranslate::m_GoogleURL = "http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q=";
-const char* LLTranslate::m_GoogleLangSpec = "&langpair=";
-float LLTranslate::m_GoogleTimeout = 5;
-
-LLSD LLTranslate::m_Header;
-// These constants are for the GET header.
-const char* LLTranslate::m_AcceptHeader = "Accept";
-const char* LLTranslate::m_AcceptType = "text/plain";
-const char* LLTranslate::m_AgentHeader = "User-Agent";
-
-// These constants are in the JSON returned from Google
-const char* LLTranslate::m_GoogleData = "responseData";
-const char* LLTranslate::m_GoogleTranslation = "translatedText";
-const char* LLTranslate::m_GoogleLanguage = "detectedSourceLanguage";
+class LLTranslationAPIHandler
+{
+public:
+	virtual void getTranslateURL(
+		std::string &url,
+		const std::string &from_lang,
+		const std::string &to_lang,
+		const std::string &text) const = 0;
 
-//static
-void LLTranslate::translateMessage(LLHTTPClient::ResponderPtr &result, const std::string &from_lang, const std::string &to_lang, const std::string &mesg)
+	virtual bool parseResponse(
+		int& status,
+		const std::string& body,
+		std::string& translation,
+		std::string& detected_lang,
+		std::string& err_msg) const = 0;
+
+	virtual ~LLTranslationAPIHandler() {}
+
+protected:
+	static const int STATUS_OK = 200;
+};
+
+class LLGoogleV1Handler : public LLTranslationAPIHandler
 {
-	std::string url;
-	getTranslateUrl(url, from_lang, to_lang, mesg);
+	LOG_CLASS(LLGoogleV1Handler);
+
+public:
+	/*virtual*/ void getTranslateURL(
+		std::string &url,
+		const std::string &from_lang,
+		const std::string &to_lang,
+		const std::string &text) const
+	{
+		url = "http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q="
+			+ LLURI::escape(text)
+			+ "&langpair=" + from_lang + "%7C" + to_lang;
+	}
+
+	/*virtual*/ bool parseResponse(
+		int& status,
+		const std::string& body,
+		std::string& translation,
+		std::string& detected_lang,
+		std::string& err_msg) const
+	{
+		Json::Value root;
+		Json::Reader reader;
+
+		if (!reader.parse(body, root))
+		{
+			err_msg = reader.getFormatedErrorMessages();
+			return false;
+		}
+
+		// This API doesn't return proper status in the HTTP response header,
+		// but it is in the body.
+		status = root["responseStatus"].asInt();
+		if (status != STATUS_OK)
+		{
+			err_msg = root["responseDetails"].asString();
+			return false;
+		}
+
+		const Json::Value& response_data = root["responseData"];
+		translation = response_data.get("translatedText", "").asString();
+		detected_lang = response_data.get("detectedSourceLanguage", "").asString();
+		return true;
+	}
+};
+
+class LLGoogleV2Handler : public LLTranslationAPIHandler
+{
+	LOG_CLASS(LLGoogleV2Handler);
+
+public:
+	/*virtual*/ void getTranslateURL(
+		std::string &url,
+		const std::string &from_lang,
+		const std::string &to_lang,
+		const std::string &text) const
+	{
+		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;
+		}
+	}
+
+	/*virtual*/ bool parseResponse(
+		int& status,
+		const std::string& body,
+		std::string& translation,
+		std::string& detected_lang,
+		std::string& err_msg) const
+	{
+		Json::Value root;
+		Json::Reader reader;
+
+		if (!reader.parse(body, root))
+		{
+			err_msg = reader.getFormatedErrorMessages();
+			return false;
+		}
+
+		if (status != STATUS_OK)
+		{
+			const Json::Value& error = root["error"];
+			err_msg = error["message"].asString();
+			status = error["code"].asInt();
+			return false;
+		}
+
+		const Json::Value& response_data = root["data"]["translations"][0U];
+		translation = response_data["translatedText"].asString();
+		detected_lang = response_data["detectedSourceLanguage"].asString();
+		return true;
+	}
+
+private:
+	static std::string getAPIKey()
+	{
+		return gSavedSettings.getString("GoogleTranslateAPIv2Key");
+	}
+};
+
+class LLBingHandler : public LLTranslationAPIHandler
+{
+	LOG_CLASS(LLBingHandler);
 
-    std::string user_agent = llformat("%s %d.%d.%d (%d)",
-		LLVersionInfo::getChannel().c_str(),
-		LLVersionInfo::getMajor(),
-		LLVersionInfo::getMinor(),
-		LLVersionInfo::getPatch(),
-		LLVersionInfo::getBuild());
+public:
+	/*virtual*/ void getTranslateURL(
+		std::string &url,
+		const std::string &from_lang,
+		const std::string &to_lang,
+		const std::string &text) const
+	{
+		url = std::string("http://api.microsofttranslator.com/v2/Http.svc/Translate?appId=")
+			+ getAPIKey() + "&text=" + LLURI::escape(text) + "&to=" + to_lang;
+		if (!from_lang.empty())
+		{
+			url += "&from=" + from_lang;
+		}
+	}
 
-	if (!m_Header.size())
+	/*virtual*/ bool parseResponse(
+		int& status,
+		const std::string& body,
+		std::string& translation,
+		std::string& detected_lang,
+		std::string& err_msg) const
 	{
-		m_Header.insert(m_AcceptHeader, LLSD(m_AcceptType));
-		m_Header.insert(m_AgentHeader, LLSD(user_agent));
+		if (status != STATUS_OK)
+		{
+			size_t begin = body.find("Message: ");
+			size_t end = body.find("</p>", begin);
+			err_msg = body.substr(begin, end-begin);
+			LLStringUtil::replaceString(err_msg, "&#xD;", ""); // strip CR
+			return false;
+		}
+
+		// Sample response: <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">Hola</string>
+		size_t begin = body.find(">");
+		if (begin == std::string::npos || begin >= (body.size() - 1))
+		{
+			return false;
+		}
+
+		size_t end = body.find("</string>", ++begin);
+		if (end == std::string::npos || end < begin)
+		{
+			return false;
+		}
+
+		detected_lang = ""; // unsupported by this API
+		translation = body.substr(begin, end-begin);
+		LLStringUtil::replaceString(translation, "&#xD;", ""); // strip CR
+		return true;
 	}
 
-	LLHTTPClient::get(url, result, m_Header, m_GoogleTimeout);
+private:
+	static std::string getAPIKey()
+	{
+		return gSavedSettings.getString("BingTranslateAPIKey");
+	}
+};
+
+LLTranslate::TranslationReceiver::TranslationReceiver(const std::string& from_lang, const std::string& to_lang)
+:	mFromLang(from_lang)
+,	mToLang(to_lang)
+,	mHandler(LLTranslate::getPreferredHandler())
+{
 }
 
-//static
-void LLTranslate::getTranslateUrl(std::string &translate_url, const std::string &from_lang, const std::string &to_lang, const std::string &mesg)
+// virtual
+void LLTranslate::TranslationReceiver::completedRaw(
+	U32 http_status,
+	const std::string& reason,
+	const LLChannelDescriptors& channels,
+	const LLIOPipe::buffer_ptr_t& buffer)
 {
-	char * curl_str = curl_escape(mesg.c_str(), mesg.size());
-	std::string const escaped_mesg(curl_str);
-	curl_free(curl_str);
-
-	translate_url = m_GoogleURL
-		+ escaped_mesg + m_GoogleLangSpec
-		+ from_lang // 'from' language; empty string for auto
-		+ "%7C" // |
-		+ to_lang; // 'to' language
+	LLBufferStream istr(channels, buffer.get());
+	std::stringstream strstrm;
+	strstrm << istr.rdbuf();
+
+	const std::string body = strstrm.str();
+	std::string translation, detected_lang, err_msg;
+	int status = http_status;
+	if (mHandler.parseResponse(status, body, translation, detected_lang, err_msg))
+	{
+		// Fix up the response
+		LLStringUtil::replaceString(translation, "&lt;", "<");
+		LLStringUtil::replaceString(translation, "&gt;",">");
+		LLStringUtil::replaceString(translation, "&quot;","\"");
+		LLStringUtil::replaceString(translation, "&#39;","'");
+		LLStringUtil::replaceString(translation, "&amp;","&");
+		LLStringUtil::replaceString(translation, "&apos;","'");
+
+		handleResponse(translation, detected_lang);
+	}
+	else
+	{
+		llwarns << "Translation request failed: " << err_msg << llendl;
+		LL_DEBUGS("Translate") << "HTTP status: " << status << " " << reason << LL_ENDL;
+		LL_DEBUGS("Translate") << "Error response body: " << body << LL_ENDL;
+		handleFailure(status, err_msg);
+	}
 }
 
 //static
-bool LLTranslate::parseGoogleTranslate(const std::string& body, std::string &translation, std::string &detected_language)
+void LLTranslate::translateMessage(
+	TranslationReceiverPtr &receiver,
+	const std::string &from_lang,
+	const std::string &to_lang,
+	const std::string &mesg)
 {
-	Json::Value root;
-	Json::Reader reader;
-	
-	bool success = reader.parse(body, root);
-	if (!success)
+	std::string url;
+	receiver->mHandler.getTranslateURL(url, from_lang, to_lang, mesg);
+
+	static const float REQUEST_TIMEOUT = 5;
+    static LLSD sHeader;
+
+	if (!sHeader.size())
 	{
-		LL_WARNS("Translate") << "Non valid response from Google Translate API: '" << reader.getFormatedErrorMessages() << "'" << LL_ENDL;
-		return false;
+	    std::string user_agent = llformat("%s %d.%d.%d (%d)",
+			LLVersionInfo::getChannel().c_str(),
+			LLVersionInfo::getMajor(),
+			LLVersionInfo::getMinor(),
+			LLVersionInfo::getPatch(),
+			LLVersionInfo::getBuild());
+
+		sHeader.insert("Accept", "text/plain");
+		sHeader.insert("User-Agent", user_agent);
 	}
-	
-	translation = 			root[m_GoogleData].get(m_GoogleTranslation, "").asString();
-	detected_language = 	root[m_GoogleData].get(m_GoogleLanguage, "").asString();
-	return true;
+
+	LL_DEBUGS("Translate") << "Sending translation request: " << url << LL_ENDL;
+	LLHTTPClient::get(url, receiver, sHeader, REQUEST_TIMEOUT);
 }
 
 //static
@@ -119,3 +308,22 @@ std::string LLTranslate::getTranslateLanguage()
 	return language;
 }
 
+// static
+const LLTranslationAPIHandler& LLTranslate::getPreferredHandler()
+{
+	static LLGoogleV1Handler	google_v1;
+	static LLGoogleV2Handler	google_v2;
+	static LLBingHandler		bing;
+
+	std::string service = gSavedSettings.getString("TranslationService");
+	if (service == "google_v2")
+	{
+		return google_v2;
+	}
+	else if (service == "google_v1")
+	{
+		return google_v1;
+	}
+
+	return bing;
+}
diff --git a/indra/newview/lltranslate.h b/indra/newview/lltranslate.h
index e85a42e878d..1dee792f7b7 100644
--- a/indra/newview/lltranslate.h
+++ b/indra/newview/lltranslate.h
@@ -30,89 +30,42 @@
 #include "llhttpclient.h"
 #include "llbufferstream.h"
 
+class LLTranslationAPIHandler;
+
 class LLTranslate
 {
 	LOG_CLASS(LLTranslate);
+
 public :
 	class TranslationReceiver: public LLHTTPClient::Responder
 	{
-	protected:
-		TranslationReceiver(const std::string &from_lang, const std::string &to_lang)
-			: m_fromLang(from_lang),
-			m_toLang(to_lang)
-		{
-		}
-
-		virtual void handleResponse(const std::string &translation, const std::string &recognized_lang) {};
-		virtual void handleFailure() {};
-
 	public:
-		~TranslationReceiver()
-		{
-		}
-
-		virtual void completedRaw(	U32 status,
-									const std::string& reason,
-									const LLChannelDescriptors& channels,
-									const LLIOPipe::buffer_ptr_t& buffer)
-		{
-			if (200 <= status && status < 300)
-			{
-				LLBufferStream istr(channels, buffer.get());
-				std::stringstream strstrm;
-				strstrm << istr.rdbuf();
+		/*virtual*/ void completedRaw(
+			U32 http_status,
+			const std::string& reason,
+			const LLChannelDescriptors& channels,
+			const LLIOPipe::buffer_ptr_t& buffer);
 
-				const std::string result = strstrm.str();
-				std::string translation;
-				std::string detected_language;
+	protected:
+		friend class LLTranslate;
 
-				if (!parseGoogleTranslate(result, translation, detected_language))
-				{
-					handleFailure();
-					return;
-				}
-				
-				// Fix up the response
-				LLStringUtil::replaceString(translation, "&lt;", "<");
-				LLStringUtil::replaceString(translation, "&gt;",">");
-				LLStringUtil::replaceString(translation, "&quot;","\"");
-				LLStringUtil::replaceString(translation, "&#39;","'");
-				LLStringUtil::replaceString(translation, "&amp;","&");
-				LLStringUtil::replaceString(translation, "&apos;","'");
+		TranslationReceiver(const std::string& from_lang, const std::string& to_lang);
 
-				handleResponse(translation, detected_language);
-			}
-			else
-			{
-				LL_WARNS("Translate") << "HTTP request for Google Translate failed with status " << status << ", reason: " << reason << LL_ENDL;
-				handleFailure();
-			}
-		}
+		virtual void handleResponse(const std::string &translation, const std::string &recognized_lang) = 0;
+		virtual void handleFailure(int status, const std::string& err_msg) = 0;
 
-	protected:
-		const std::string m_toLang;
-		const std::string m_fromLang;
+		std::string mFromLang;
+		std::string mToLang;
+		const LLTranslationAPIHandler& mHandler;
 	};
 
-	static void translateMessage(LLHTTPClient::ResponderPtr &result, const std::string &from_lang, const std::string &to_lang, const std::string &mesg);
-	static float m_GoogleTimeout;
+	typedef boost::intrusive_ptr<TranslationReceiver> TranslationReceiverPtr;
+
+	static void translateMessage(TranslationReceiverPtr &receiver, const std::string &from_lang, const std::string &to_lang, const std::string &mesg);
 	static std::string getTranslateLanguage();
 
 private:
-	static void getTranslateUrl(std::string &translate_url, const std::string &from_lang, const std::string &to_lang, const std::string &text);
-	static bool parseGoogleTranslate(const std::string& body, std::string &translation, std::string &detected_language);
-
-	static LLSD m_Header;
-	static const char* m_GoogleURL;
-	static const char* m_GoogleLangSpec;
-	static const char* m_AcceptHeader;
-	static const char* m_AcceptType;
-	static const char* m_AgentHeader;
-	static const char* m_UserAgent;
-
-	static const char* m_GoogleData;
-	static const char* m_GoogleTranslation;
-	static const char* m_GoogleLanguage;
+	static const LLTranslationAPIHandler& getPreferredHandler();
 };
 
 #endif
diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp
index 68745d5aeb9..ff022141945 100644
--- a/indra/newview/llviewermessage.cpp
+++ b/indra/newview/llviewermessage.cpp
@@ -3138,7 +3138,7 @@ public :
 	{
 		// filter out non-interesting responeses
 		if ( !translation.empty()
-			&& (m_toLang != detected_language)
+			&& (mToLang != detected_language)
 			&& (LLStringUtil::compareInsensitive(translation, m_origMesg) != 0) )
 		{
 			m_chat.mText += " (" + translation + ")";
@@ -3147,9 +3147,8 @@ public :
 		LLNotificationsUI::LLNotificationManager::instance().onChat(m_chat, m_toastArgs);
 	}
 
-	void handleFailure()
+	void handleFailure(int status, const std::string& err_msg)
 	{
-		LLTranslate::TranslationReceiver::handleFailure();
 		m_chat.mText += " (?)";
 
 		LLNotificationsUI::LLNotificationManager::instance().onChat(m_chat, m_toastArgs);
@@ -3388,7 +3387,7 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data)
 			const std::string from_lang = ""; // leave empty to trigger autodetect
 			const std::string to_lang = LLTranslate::getTranslateLanguage();
 
-			LLHTTPClient::ResponderPtr result = ChatTranslationReceiver::build(from_lang, to_lang, mesg, chat, args);
+			LLTranslate::TranslationReceiverPtr result = ChatTranslationReceiver::build(from_lang, to_lang, mesg, chat, args);
 			LLTranslate::translateMessage(result, from_lang, to_lang, mesg);
 		}
 		else
-- 
GitLab