From c6d6eed7d33121ac2bbd466ac2e49cccd6f6d921 Mon Sep 17 00:00:00 2001
From: Drake Arconis
Date: Sun, 19 Feb 2012 19:43:53 -0500
Subject: [PATCH] This works for now. Need to do more testing.
---
indra/newview/app_settings/settings.xml | 54 ++-
indra/newview/lltranslate.cpp | 469 ++++++++++++++++++------
indra/newview/lltranslate.h | 372 ++++++++++++++-----
indra/newview/llviewermessage.cpp | 26 +-
4 files changed, 696 insertions(+), 225 deletions(-)
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 2b521fed4..7553e8816 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -1,16 +1,15 @@
", begin);
+ err_msg = body.substr(begin, end-begin);
+ LLStringUtil::replaceString(err_msg, "
", ""); // strip CR
+ return false;
+ }
+
+ // Sample response: Hola
+ size_t begin = body.find(">");
+ if (begin == std::string::npos || begin >= (body.size() - 1))
+ {
+ begin = 0;
+ }
+ else
+ {
+ ++begin;
+ }
+
+ size_t end = body.find("", begin);
+
+ detected_lang = ""; // unsupported by this API
+ translation = body.substr(begin, end-begin);
+ LLStringUtil::replaceString(translation, "
", ""); // strip CR
+ return true;
+}
+
+// virtual
+bool LLBingTranslationHandler::isConfigured() const
+{
+ return !getAPIKey().empty();
+}
+
+// static
+std::string LLBingTranslationHandler::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())
+{
+}
+
+// virtual
+void LLTranslate::TranslationReceiver::completedRaw(
+ U32 http_status,
+ const std::string& reason,
+ const LLChannelDescriptors& channels,
+ const LLIOPipe::buffer_ptr_t& buffer)
+{
+ 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;
+ LL_DEBUGS("Translate") << "HTTP status: " << status << " " << reason << LL_ENDL;
+ LL_DEBUGS("Translate") << "Response body: " << body << LL_ENDL;
+ if (mHandler.parseResponse(status, body, translation, detected_lang, err_msg))
+ {
+ // Fix up the response
+ LLStringUtil::replaceString(translation, "<", "<");
+ LLStringUtil::replaceString(translation, ">",">");
+ LLStringUtil::replaceString(translation, ""","\"");
+ LLStringUtil::replaceString(translation, "'","'");
+ LLStringUtil::replaceString(translation, "&","&");
+ LLStringUtil::replaceString(translation, "'","'");
+
+ handleResponse(translation, detected_lang);
+ }
+ else
+ {
+ if (err_msg.empty())
+ {
+ err_msg = LLTrans::getString("TranslationResponseParseError");
+ }
+
+ llwarns << "Translation request failed: " << err_msg << llendl;
+ handleFailure(status, err_msg);
+ }
+}
+
+LLTranslate::KeyVerificationReceiver::KeyVerificationReceiver(EService service)
+: mService(service)
+{
+}
+
+LLTranslate::EService LLTranslate::KeyVerificationReceiver::getService() const
+{
+ return mService;
+}
+
+// virtual
+void LLTranslate::KeyVerificationReceiver::completedRaw(
+ U32 http_status,
+ const std::string& reason,
+ const LLChannelDescriptors& channels,
+ const LLIOPipe::buffer_ptr_t& buffer)
+{
+ bool ok = (http_status == 200);
+ setVerificationStatus(ok);
+}
+
+//static
+void LLTranslate::translateMessage(
+ TranslationReceiverPtr &receiver,
+ const std::string &from_lang,
+ const std::string &to_lang,
+ const std::string &mesg)
+{
+ std::string url;
+ receiver->mHandler.getTranslateURL(url, from_lang, to_lang, mesg);
+
+ LL_DEBUGS("Translate") << "Sending translation request: " << url << LL_ENDL;
+ sendRequest(url, receiver);
+}
+
+// static
+void LLTranslate::verifyKey(
+ KeyVerificationReceiverPtr& receiver,
+ const std::string& key)
+{
+ std::string url;
+ const LLTranslationAPIHandler& handler = getHandler(receiver->getService());
+ handler.getKeyVerificationURL(url, key);
+
+ LL_DEBUGS("Translate") << "Sending key verification request: " << url << LL_ENDL;
+ sendRequest(url, receiver);
}
//static
std::string LLTranslate::getTranslateLanguage()
{
- std::string language = "en";
- if (LLUI::sConfigGroup)
+ std::string language = gSavedSettings.getString("TranslateLanguage");
+ if (language.empty() || language == "default")
{
- language = LLUI::sConfigGroup->getString("TranslateLanguage");
- if (language.empty() || language == "default")
- {
- language = LLUI::getLanguage();
- }
+ language = LLUI::getLanguage();
}
language = language.substr(0,2);
return language;
}
+// static
+bool LLTranslate::isTranslationConfigured()
+{
+ return getPreferredHandler().isConfigured();
+}
+
+// static
+const LLTranslationAPIHandler& LLTranslate::getPreferredHandler()
+{
+ EService service = SERVICE_BING;
+
+ std::string service_str = gSavedSettings.getString("TranslationService");
+ if (service_str == "google")
+ {
+ service = SERVICE_GOOGLE;
+ }
+
+ return getHandler(service);
+}
+
+// static
+const LLTranslationAPIHandler& LLTranslate::getHandler(EService service)
+{
+ static LLGoogleTranslationHandler google;
+ static LLBingTranslationHandler bing;
+
+ if (service == SERVICE_GOOGLE)
+ {
+ return google;
+ }
+
+ return bing;
+}
+
+// static
+void LLTranslate::sendRequest(const std::string& url, LLHTTPClient::ResponderPtr responder)
+{
+ static const float REQUEST_TIMEOUT = 5;
+ static LLSD sHeader;
+
+ if (!sHeader.size())
+ {
+ std::string user_agent = llformat("%s %d.%d.%d (%d)",
+ gVersionChannel,
+ gVersionMajor,
+ gVersionMinor,
+ gVersionPatch,
+ gVersionBuild);
+
+ sHeader.insert("Accept", "text/plain");
+ sHeader.insert("User-Agent", user_agent);
+ }
+
+ LLHTTPClient::get(url, responder, sHeader, REQUEST_TIMEOUT);
+}
diff --git a/indra/newview/lltranslate.h b/indra/newview/lltranslate.h
index 2723c43ff..870cc1fae 100644
--- a/indra/newview/lltranslate.h
+++ b/indra/newview/lltranslate.h
@@ -2,33 +2,27 @@
* @file lltranslate.h
* @brief Human language translation class and JSON response receiver.
*
-* $LicenseInfo:firstyear=2009&license=viewergpl$
-*
-* Copyright (c) 2009, Linden Research, Inc.
-*
-* Second Life Viewer Source Code
-* The source code in this file ("Source Code") is provided by Linden Lab
-* to you under the terms of the GNU General Public License, version 2.0
-* ("GPL"), unless you have obtained a separate licensing agreement
-* ("Other License"), formally executed by you and Linden Lab. Terms of
-* the GPL can be found in doc/GPL-license.txt in this distribution, or
-* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
-*
-* There are special exceptions to the terms and conditions of the GPL as
-* it is applied to this Source Code. View the full text of the exception
-* in the file doc/FLOSS-exception.txt in this software distribution, or
-* online at
-* http://secondlifegrid.net/programs/open_source/licensing/flossexception
-*
-* By copying, modifying or distributing this software, you acknowledge
-* that you have read and understood your obligations described above,
-* and agree to abide by those obligations.
-*
-* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
-* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
-* COMPLETENESS OR PERFORMANCE.
-* $/LicenseInfo$
-*/
+ * $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$
+ */
#ifndef LL_LLTRANSLATE_H
#define LL_LLTRANSLATE_H
@@ -37,89 +31,273 @@
#include "llbufferstream.h"
#include "jsoncpp/reader.h"
-class LLTranslate
+namespace Json
{
-public :
- class TranslationReceiver: public LLHTTPClient::Responder
- {
- protected:
- TranslationReceiver(const std::string &fromLang, const std::string &toLang)
- : m_fromLang(fromLang),
- m_toLang(toLang)
- {
- }
+ class Value;
+}
- virtual void handleResponse(const std::string &translation, const std::string &recognizedLang) {}
- virtual void handleFailure() {};
+/**
+ * Handler of an HTTP machine translation service.
+ *
+ * Derived classes know the service URL
+ * and how to parse the translation result.
+ */
+class LLTranslationAPIHandler
+{
+public:
+ /**
+ * 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 void getTranslateURL(
+ std::string &url,
+ const std::string &from_lang,
+ const std::string &to_lang,
+ const std::string &text) const = 0;
- public:
- ~TranslationReceiver()
- {
- }
+ /**
+ * 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 void getKeyVerificationURL(
+ std::string &url,
+ const std::string &key) const = 0;
- virtual void error(U32 status, const std::string& reason)
- {
- LL_WARNS("Translate") << "URL Request error: " << reason << LL_ENDL;
- handleFailure();
- }
+ /**
+ * 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(
+ int& status,
+ const std::string& body,
+ std::string& translation,
+ std::string& detected_lang,
+ std::string& err_msg) const = 0;
- virtual void completedRaw(
- U32 status,
- const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer)
- {
- LLBufferStream istr(channels, buffer.get());
+ /**
+ * @return if the handler is configured to function properly
+ */
+ virtual bool isConfigured() const = 0;
- std::stringstream strstrm;
- strstrm << istr.rdbuf();
- const std::string result = strstrm.str();
+ virtual ~LLTranslationAPIHandler() {}
- std::string translation;
- std::string detectedLanguage;
+protected:
+ static const int STATUS_OK = 200;
+};
- if (!parseGoogleTranslate(result, translation, detectedLanguage))
- {
- handleFailure();
- return;
- }
+/// Google Translate v2 API handler.
+class LLGoogleTranslationHandler : public LLTranslationAPIHandler
+{
+ LOG_CLASS(LLGoogleTranslationHandler);
- // Fix up the response
- stringReplaceAll( translation, "<","<");
- stringReplaceAll( translation, ">",">");
- stringReplaceAll( translation, ""","\"");
- stringReplaceAll( translation, "'","'");
- stringReplaceAll( translation, "&","&");
- stringReplaceAll( translation, "'","'");
-
- handleResponse(translation, detectedLanguage);
- }
-
- protected:
- const std::string m_toLang;
- const std::string m_fromLang;
- };
-
- static void translateMessage(LLHTTPClient::ResponderPtr &result, const std::string &fromLang, const std::string &toLang, const std::string &mesg);
- static float m_GoogleTimeout;
- static std::string getTranslateLanguage();
+public:
+ /*virtual*/ void getTranslateURL(
+ std::string &url,
+ const std::string &from_lang,
+ const std::string &to_lang,
+ const std::string &text) const;
+ /*virtual*/ void getKeyVerificationURL(
+ std::string &url,
+ const std::string &key) const;
+ /*virtual*/ bool parseResponse(
+ int& status,
+ const std::string& body,
+ std::string& translation,
+ std::string& detected_lang,
+ std::string& err_msg) const;
+ /*virtual*/ bool isConfigured() const;
private:
- static void getTranslateUrl(std::string &translateUrl, const std::string &fromLang, const std::string &toLang, const std::string &text);
- static void stringReplaceAll(std::string& context, const std::string& from, const std::string& to);
- static BOOL parseGoogleTranslate(const std::string result, std::string &translation, std::string &detectedLanguage);
+ static void parseErrorResponse(
+ const Json::Value& root,
+ int& status,
+ std::string& err_msg);
+ static bool parseTranslation(
+ const Json::Value& root,
+ std::string& translation,
+ std::string& detected_lang);
+ static std::string getAPIKey();
+};
- 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;
+/// Microsoft Translator v2 API handler.
+class LLBingTranslationHandler : public LLTranslationAPIHandler
+{
+ LOG_CLASS(LLBingTranslationHandler);
- static const char* m_GoogleData;
- static const char* m_GoogleTranslation;
- static const char* m_GoogleLanguage;
+public:
+ /*virtual*/ void getTranslateURL(
+ std::string &url,
+ const std::string &from_lang,
+ const std::string &to_lang,
+ const std::string &text) const;
+ /*virtual*/ void getKeyVerificationURL(
+ std::string &url,
+ const std::string &key) const;
+ /*virtual*/ bool parseResponse(
+ int& status,
+ const std::string& body,
+ std::string& translation,
+ std::string& detected_lang,
+ std::string& err_msg) const;
+ /*virtual*/ bool isConfigured() const;
+private:
+ static std::string getAPIKey();
+};
+
+/**
+ * Entry point for machine translation services.
+ *
+ * Basically, to translate a string, we need to know the URL
+ * of a translation service, have a valid API for the service
+ * and be given the target language.
+ *
+ * Callers specify the string to translate and the target language,
+ * LLTranslate takes care of the rest.
+ *
+ * API keys for translation are taken from saved settings.
+ */
+class LLTranslate
+{
+ LOG_CLASS(LLTranslate);
+
+public :
+
+ typedef enum e_service {
+ SERVICE_BING,
+ SERVICE_GOOGLE,
+ } EService;
+
+ /**
+ * Subclasses are supposed to handle translation results (e.g. show them in chat)
+ */
+ class TranslationReceiver: public LLHTTPClient::Responder
+ {
+ public:
+
+ /**
+ * Using mHandler, parse incoming response.
+ *
+ * Calls either handleResponse() or handleFailure()
+ * depending on the HTTP status code and parsing success.
+ *
+ * @see handleResponse()
+ * @see handleFailure()
+ * @see mHandler
+ */
+ /*virtual*/ void completedRaw(
+ U32 http_status,
+ const std::string& reason,
+ const LLChannelDescriptors& channels,
+ const LLIOPipe::buffer_ptr_t& buffer);
+
+ protected:
+ friend class LLTranslate;
+
+ /// Remember source and target languages for subclasses to be able to filter inappropriate results.
+ TranslationReceiver(const std::string& from_lang, const std::string& to_lang);
+
+ /// Override point to handle successful translation.
+ virtual void handleResponse(const std::string &translation, const std::string &recognized_lang) = 0;
+
+ /// Override point to handle unsuccessful translation.
+ virtual void handleFailure(int status, const std::string& err_msg) = 0;
+
+ std::string mFromLang;
+ std::string mToLang;
+ const LLTranslationAPIHandler& mHandler;
+ };
+
+ /**
+ * Subclasses are supposed to handle API key verification result.
+ */
+ class KeyVerificationReceiver: public LLHTTPClient::Responder
+ {
+ public:
+ EService getService() const;
+
+ protected:
+ /**
+ * Save the translation service the key belongs to.
+ *
+ * Subclasses need to know it.
+ *
+ * @see getService()
+ */
+ KeyVerificationReceiver(EService service);
+
+ /**
+ * Parse verification response.
+ *
+ * Calls setVerificationStatus() with the verification status,
+ * which is true if HTTP status code is 200.
+ *
+ * @see setVerificationStatus()
+ */
+ /*virtual*/ void completedRaw(
+ U32 http_status,
+ const std::string& reason,
+ const LLChannelDescriptors& channels,
+ const LLIOPipe::buffer_ptr_t& buffer);
+
+ /**
+ * Override point for subclasses to handle key verification status.
+ */
+ virtual void setVerificationStatus(bool ok) = 0;
+
+ EService mService;
+ };
+
+ typedef boost::intrusive_ptr TranslationReceiverPtr;
+ typedef boost::intrusive_ptr KeyVerificationReceiverPtr;
+
+ /**
+ * Translate given text.
+ *
+ * @param receiver Object to pass translation result to.
+ * @param from_lang Source language. Leave empty for auto-detection.
+ * @param to_lang Target language.
+ * @param mesg Text to translate.
+ */
+ static void translateMessage(TranslationReceiverPtr &receiver, const std::string &from_lang, const std::string &to_lang, const std::string &mesg);
+
+ /**
+ * Verify given API key of a translation service.
+ *
+ * @param receiver Object to pass verification result to.
+ * @param key Key to verify.
+ */
+ static void verifyKey(KeyVerificationReceiverPtr& receiver, const std::string& key);
+
+ /**
+ * @return translation target language
+ */
+ static std::string getTranslateLanguage();
+
+ /**
+ * @return true if translation is configured properly.
+ */
+ static bool isTranslationConfigured();
+
+private:
+ static const LLTranslationAPIHandler& getPreferredHandler();
+ static const LLTranslationAPIHandler& getHandler(EService service);
+ static void sendRequest(const std::string& url, LLHTTPClient::ResponderPtr responder);
};
#endif
diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp
index 97ac3eb05..a060efd09 100644
--- a/indra/newview/llviewermessage.cpp
+++ b/indra/newview/llviewermessage.cpp
@@ -2957,23 +2957,23 @@ void process_decline_callingcard(LLMessageSystem* msg, void**)
class ChatTranslationReceiver : public LLTranslate::TranslationReceiver
{
public :
- ChatTranslationReceiver(const std::string &fromLang, const std::string &toLang, LLChat *chat,
+ ChatTranslationReceiver(const std::string &from_lang, const std::string &to_lang, LLChat *chat,
const BOOL history)
- : LLTranslate::TranslationReceiver(fromLang, toLang),
+ : LLTranslate::TranslationReceiver(from_lang, to_lang),
m_chat(chat),
m_history(history)
{
}
- static boost::intrusive_ptr build(const std::string &fromLang, const std::string &toLang, LLChat *chat, const BOOL history)
+ static boost::intrusive_ptr build(const std::string &from_lang, const std::string &to_lang, LLChat *chat, const BOOL history)
{
- return boost::intrusive_ptr(new ChatTranslationReceiver(fromLang, toLang, chat, history));
+ return boost::intrusive_ptr(new ChatTranslationReceiver(from_lang, to_lang, chat, history));
}
protected:
void handleResponse(const std::string &translation, const std::string &detectedLanguage)
{
- if (m_toLang != detectedLanguage)
+ if (mToLang != detectedLanguage)
m_chat->mText += " (" + translation + ")";
add_floater_chat(*m_chat, m_history);
@@ -2981,14 +2981,12 @@ protected:
delete m_chat;
}
- void handleFailure()
+ void handleFailure(int status, const std::string& err_msg)
{
- LLTranslate::TranslationReceiver::handleFailure();
+ llwarns << "Translation failed for mesg " << m_chat << " toLang " << mToLang << " fromLang " << mFromLang << llendl;
m_chat->mText += " (?)";
-
add_floater_chat(*m_chat, m_history);
-
delete m_chat;
}
@@ -3017,14 +3015,12 @@ void check_translate_chat(const std::string &mesg, LLChat &chat, const BOOL hist
if (translate && chat.mSourceType != CHAT_SOURCE_SYSTEM)
{
- // fromLang hardcoded to "" (autodetection) pending implementation of
- // SVC-4879
- const std::string &fromLang = "";
- const std::string &toLang = LLTranslate::getTranslateLanguage();
+ const std::string &from_lang = "";
+ const std::string &to_lang = LLTranslate::getTranslateLanguage();
LLChat *newChat = new LLChat(chat);
- LLHTTPClient::ResponderPtr result = ChatTranslationReceiver::build(fromLang, toLang, newChat, history);
- LLTranslate::translateMessage(result, fromLang, toLang, mesg);
+ LLTranslate::TranslationReceiverPtr result = ChatTranslationReceiver::build(from_lang, to_lang, newChat, history);
+ LLTranslate::translateMessage(result, from_lang, to_lang, mesg);
}
else
{