From e2552ec6737fe734ffd5b4768193c6a890d66f70 Mon Sep 17 00:00:00 2001 From: Vadim ProductEngine Date: Tue, 6 Sep 2011 17:45:47 +0300 Subject: 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(-) (limited to 'indra/newview') diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 0996f75fbb..2549538df2 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -10922,6 +10922,39 @@ Value 0 + TranslationService + + Comment + Translation API to use. (google_v1|google_v2|bing) + Persist + 1 + Type + String + Value + google_v1 + + GoogleTranslateAPIv2Key + + Comment + Google Translate API v2 key + Persist + 1 + Type + String + Value + + + BingTranslateAPIKey + + Comment + Bing AppID to use with the Microsoft Translator V2 API + Persist + 1 + Type + String + Value + + TutorialURL Comment diff --git a/indra/newview/lltranslate.cpp b/indra/newview/lltranslate.cpp index 2f60b6b90b..e29ea373ce 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("

", 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)) + { + return false; + } + + size_t end = body.find("", ++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, " ", ""); // 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, "<", "<"); + LLStringUtil::replaceString(translation, ">",">"); + LLStringUtil::replaceString(translation, ""","\""); + LLStringUtil::replaceString(translation, "'","'"); + LLStringUtil::replaceString(translation, "&","&"); + LLStringUtil::replaceString(translation, "'","'"); + + 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 e85a42e878..1dee792f7b 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, "<", "<"); - LLStringUtil::replaceString(translation, ">",">"); - LLStringUtil::replaceString(translation, ""","\""); - LLStringUtil::replaceString(translation, "'","'"); - LLStringUtil::replaceString(translation, "&","&"); - LLStringUtil::replaceString(translation, "'","'"); + 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 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 68745d5aeb..ff02214194 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -3138,7 +3138,7 @@ protected: { // 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 @@ protected: 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 -- cgit v1.2.3