diff options
-rw-r--r-- | indra/newview/CMakeLists.txt | 9 | ||||
-rw-r--r-- | indra/newview/app_settings/settings.xml | 33 | ||||
-rwxr-xr-x | indra/newview/llfloaterpreference.cpp | 9 | ||||
-rw-r--r-- | indra/newview/llfloaterpreference.h | 1 | ||||
-rw-r--r-- | indra/newview/llfloatertranslationsettings.cpp | 296 | ||||
-rw-r--r-- | indra/newview/llfloatertranslationsettings.h | 76 | ||||
-rw-r--r-- | indra/newview/lltranslate.cpp | 366 | ||||
-rw-r--r-- | indra/newview/lltranslate.h | 265 | ||||
-rw-r--r-- | indra/newview/llviewerfloaterreg.cpp | 2 | ||||
-rw-r--r-- | indra/newview/llviewermessage.cpp | 13 | ||||
-rw-r--r-- | indra/newview/skins/default/xui/en/floater_translation_settings.xml | 244 | ||||
-rw-r--r-- | indra/newview/skins/default/xui/en/panel_preferences_chat.xml | 127 | ||||
-rw-r--r-- | indra/newview/skins/default/xui/en/strings.xml | 4 | ||||
-rw-r--r-- | indra/newview/tests/lltranslate_test.cpp | 345 |
14 files changed, 1566 insertions, 224 deletions
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 7288bf6933..bef775cdb8 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -241,6 +241,7 @@ set(viewer_SOURCE_FILES llfloatertopobjects.cpp llfloatertos.cpp llfloatertoybox.cpp + llfloatertranslationsettings.cpp llfloateruipreview.cpp llfloaterurlentry.cpp llfloatervoiceeffect.cpp @@ -807,6 +808,7 @@ set(viewer_HEADER_FILES llfloatertopobjects.h llfloatertos.h llfloatertoybox.h + llfloatertranslationsettings.h llfloateruipreview.h llfloaterurlentry.h llfloatervoiceeffect.h @@ -1987,12 +1989,19 @@ if (LL_TESTS) llmediadataclient.cpp lllogininstance.cpp llremoteparcelrequest.cpp + lltranslate.cpp llviewerhelputil.cpp llversioninfo.cpp llworldmap.cpp llworldmipmap.cpp ) + set_source_files_properties( + lltranslate.cpp + PROPERTIES + LL_TEST_ADDITIONAL_LIBRARIES "${JSONCPP_LIBRARIES}" + ) + ################################################## # DISABLING PRECOMPILED HEADERS USAGE FOR TESTS ################################################## diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 9f01674efe..7cfbba3160 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -11014,6 +11014,39 @@ <key>Value</key> <integer>0</integer> </map> + <key>TranslationService</key> + <map> + <key>Comment</key> + <string>Translation API to use. (google|bing)</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>String</string> + <key>Value</key> + <string>bing</string> + </map> + <key>GoogleTranslateAPIKey</key> + <map> + <key>Comment</key> + <string>Google Translate API 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 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/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index 5fdeb46daa..a333989e7e 100755 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -345,6 +345,7 @@ LLFloaterPreference::LLFloaterPreference(const LLSD& key) mCommitCallbackRegistrar.add("Pref.MaturitySettings", boost::bind(&LLFloaterPreference::onChangeMaturity, this)); mCommitCallbackRegistrar.add("Pref.BlockList", boost::bind(&LLFloaterPreference::onClickBlockList, this)); mCommitCallbackRegistrar.add("Pref.Proxy", boost::bind(&LLFloaterPreference::onClickProxySettings, this)); + mCommitCallbackRegistrar.add("Pref.TranslationSettings", boost::bind(&LLFloaterPreference::onClickTranslationSettings, this)); sSkin = gSavedSettings.getString("SkinCurrent"); @@ -599,6 +600,9 @@ void LLFloaterPreference::cancel() } // hide joystick pref floater LLFloaterReg::hideInstance("pref_joystick"); + + // hide translation settings floater + LLFloaterReg::hideInstance("prefs_translation"); // cancel hardware menu LLFloaterHardwareSettings* hardware_settings = LLFloaterReg::getTypedInstance<LLFloaterHardwareSettings>("prefs_hardware_settings"); @@ -1505,6 +1509,11 @@ void LLFloaterPreference::onClickProxySettings() LLFloaterReg::showInstance("prefs_proxy"); } +void LLFloaterPreference::onClickTranslationSettings() +{ + LLFloaterReg::showInstance("prefs_translation"); +} + void LLFloaterPreference::onClickActionChange() { mClickActionDirty = true; diff --git a/indra/newview/llfloaterpreference.h b/indra/newview/llfloaterpreference.h index 5c74e9f60c..7ee3294478 100644 --- a/indra/newview/llfloaterpreference.h +++ b/indra/newview/llfloaterpreference.h @@ -156,6 +156,7 @@ public: void onChangeMaturity(); void onClickBlockList(); void onClickProxySettings(); + void onClickTranslationSettings(); void applyUIColor(LLUICtrl* ctrl, const LLSD& param); void getUIColor(LLUICtrl* ctrl, const LLSD& param); diff --git a/indra/newview/llfloatertranslationsettings.cpp b/indra/newview/llfloatertranslationsettings.cpp new file mode 100644 index 0000000000..959edff713 --- /dev/null +++ b/indra/newview/llfloatertranslationsettings.cpp @@ -0,0 +1,296 @@ +/** + * @file llfloatertranslationsettings.cpp + * @brief Machine translation settings for chat + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, 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 "llfloatertranslationsettings.h" + +// Viewer includes +#include "lltranslate.h" +#include "llviewercontrol.h" // for gSavedSettings + +// Linden library includes +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llfloaterreg.h" +#include "lllineeditor.h" +#include "llnotificationsutil.h" +#include "llradiogroup.h" + +class EnteredKeyVerifier : public LLTranslate::KeyVerificationReceiver +{ +public: + EnteredKeyVerifier(LLTranslate::EService service, bool alert) + : LLTranslate::KeyVerificationReceiver(service) + , mAlert(alert) + { + } + +private: + /*virtual*/ void setVerificationStatus(bool ok) + { + LLFloaterTranslationSettings* floater = + LLFloaterReg::getTypedInstance<LLFloaterTranslationSettings>("prefs_translation"); + + if (!floater) + { + llwarns << "Cannot find translation settings floater" << llendl; + return; + } + + switch (getService()) + { + case LLTranslate::SERVICE_BING: + floater->setBingVerified(ok, mAlert); + break; + case LLTranslate::SERVICE_GOOGLE: + floater->setGoogleVerified(ok, mAlert); + break; + } + } + + bool mAlert; +}; + +LLFloaterTranslationSettings::LLFloaterTranslationSettings(const LLSD& key) +: LLFloater(key) +, mMachineTranslationCB(NULL) +, mLanguageCombo(NULL) +, mTranslationServiceRadioGroup(NULL) +, mBingAPIKeyEditor(NULL) +, mGoogleAPIKeyEditor(NULL) +, mBingVerifyBtn(NULL) +, mGoogleVerifyBtn(NULL) +, mOKBtn(NULL) +, mBingKeyVerified(false) +, mGoogleKeyVerified(false) +{ +} + +// virtual +BOOL LLFloaterTranslationSettings::postBuild() +{ + mMachineTranslationCB = getChild<LLCheckBoxCtrl>("translate_chat_checkbox"); + mLanguageCombo = getChild<LLComboBox>("translate_language_combo"); + mTranslationServiceRadioGroup = getChild<LLRadioGroup>("translation_service_rg"); + mBingAPIKeyEditor = getChild<LLLineEditor>("bing_api_key"); + mGoogleAPIKeyEditor = getChild<LLLineEditor>("google_api_key"); + mBingVerifyBtn = getChild<LLButton>("verify_bing_api_key_btn"); + mGoogleVerifyBtn = getChild<LLButton>("verify_google_api_key_btn"); + mOKBtn = getChild<LLButton>("ok_btn"); + + mMachineTranslationCB->setCommitCallback(boost::bind(&LLFloaterTranslationSettings::updateControlsEnabledState, this)); + mTranslationServiceRadioGroup->setCommitCallback(boost::bind(&LLFloaterTranslationSettings::updateControlsEnabledState, this)); + mOKBtn->setClickedCallback(boost::bind(&LLFloaterTranslationSettings::onBtnOK, this)); + getChild<LLButton>("cancel_btn")->setClickedCallback(boost::bind(&LLFloater::closeFloater, this, false)); + mBingVerifyBtn->setClickedCallback(boost::bind(&LLFloaterTranslationSettings::onBtnBingVerify, this)); + mGoogleVerifyBtn->setClickedCallback(boost::bind(&LLFloaterTranslationSettings::onBtnGoogleVerify, this)); + + mBingAPIKeyEditor->setFocusReceivedCallback(boost::bind(&LLFloaterTranslationSettings::onEditorFocused, this, _1)); + mBingAPIKeyEditor->setKeystrokeCallback(boost::bind(&LLFloaterTranslationSettings::onBingKeyEdited, this), NULL); + mGoogleAPIKeyEditor->setFocusReceivedCallback(boost::bind(&LLFloaterTranslationSettings::onEditorFocused, this, _1)); + mGoogleAPIKeyEditor->setKeystrokeCallback(boost::bind(&LLFloaterTranslationSettings::onGoogleKeyEdited, this), NULL); + + center(); + return TRUE; +} + +// virtual +void LLFloaterTranslationSettings::onOpen(const LLSD& key) +{ + mMachineTranslationCB->setValue(gSavedSettings.getBOOL("TranslateChat")); + mLanguageCombo->setSelectedByValue(gSavedSettings.getString("TranslateLanguage"), TRUE); + mTranslationServiceRadioGroup->setSelectedByValue(gSavedSettings.getString("TranslationService"), TRUE); + + std::string bing_key = gSavedSettings.getString("BingTranslateAPIKey"); + if (!bing_key.empty()) + { + mBingAPIKeyEditor->setText(bing_key); + mBingAPIKeyEditor->setTentative(FALSE); + verifyKey(LLTranslate::SERVICE_BING, bing_key, false); + } + else + { + mBingAPIKeyEditor->setTentative(TRUE); + mBingKeyVerified = FALSE; + } + + std::string google_key = gSavedSettings.getString("GoogleTranslateAPIKey"); + if (!google_key.empty()) + { + mGoogleAPIKeyEditor->setText(google_key); + mGoogleAPIKeyEditor->setTentative(FALSE); + verifyKey(LLTranslate::SERVICE_GOOGLE, google_key, false); + } + else + { + mGoogleAPIKeyEditor->setTentative(TRUE); + mGoogleKeyVerified = FALSE; + } + + updateControlsEnabledState(); +} + +void LLFloaterTranslationSettings::setBingVerified(bool ok, bool alert) +{ + if (alert) + { + showAlert(ok ? "bing_api_key_verified" : "bing_api_key_not_verified"); + } + + mBingKeyVerified = ok; + updateControlsEnabledState(); +} + +void LLFloaterTranslationSettings::setGoogleVerified(bool ok, bool alert) +{ + if (alert) + { + showAlert(ok ? "google_api_key_verified" : "google_api_key_not_verified"); + } + + mGoogleKeyVerified = ok; + updateControlsEnabledState(); +} + +std::string LLFloaterTranslationSettings::getSelectedService() const +{ + return mTranslationServiceRadioGroup->getSelectedValue().asString(); +} + +std::string LLFloaterTranslationSettings::getEnteredBingKey() const +{ + return mBingAPIKeyEditor->getTentative() ? LLStringUtil::null : mBingAPIKeyEditor->getText(); +} + +std::string LLFloaterTranslationSettings::getEnteredGoogleKey() const +{ + return mGoogleAPIKeyEditor->getTentative() ? LLStringUtil::null : mGoogleAPIKeyEditor->getText(); +} + +void LLFloaterTranslationSettings::showAlert(const std::string& msg_name) const +{ + LLSD args; + args["MESSAGE"] = getString(msg_name); + LLNotificationsUtil::add("GenericAlert", args); +} + +void LLFloaterTranslationSettings::updateControlsEnabledState() +{ + // Enable/disable controls based on the checkbox value. + bool on = mMachineTranslationCB->getValue().asBoolean(); + std::string service = getSelectedService(); + bool bing_selected = service == "bing"; + bool google_selected = service == "google"; + + mTranslationServiceRadioGroup->setEnabled(on); + mLanguageCombo->setEnabled(on); + + getChild<LLTextBox>("bing_api_key_label")->setEnabled(on); + mBingAPIKeyEditor->setEnabled(on); + + getChild<LLTextBox>("google_api_key_label")->setEnabled(on); + mGoogleAPIKeyEditor->setEnabled(on); + + mBingAPIKeyEditor->setEnabled(on && bing_selected); + mGoogleAPIKeyEditor->setEnabled(on && google_selected); + + mBingVerifyBtn->setEnabled(on && bing_selected && + !mBingKeyVerified && !getEnteredBingKey().empty()); + mGoogleVerifyBtn->setEnabled(on && google_selected && + !mGoogleKeyVerified && !getEnteredGoogleKey().empty()); + + mOKBtn->setEnabled( + !on || ( + (bing_selected && mBingKeyVerified) || + (google_selected && mGoogleKeyVerified) + )); +} + +void LLFloaterTranslationSettings::verifyKey(int service, const std::string& key, bool alert) +{ + LLTranslate::KeyVerificationReceiverPtr receiver = + new EnteredKeyVerifier((LLTranslate::EService) service, alert); + LLTranslate::verifyKey(receiver, key); +} + +void LLFloaterTranslationSettings::onEditorFocused(LLFocusableElement* control) +{ + LLLineEditor* editor = dynamic_cast<LLLineEditor*>(control); + if (editor && editor->hasTabStop()) // if enabled. getEnabled() doesn't work + { + if (editor->getTentative()) + { + editor->setText(LLStringUtil::null); + editor->setTentative(FALSE); + } + } +} + +void LLFloaterTranslationSettings::onBingKeyEdited() +{ + if (mBingAPIKeyEditor->isDirty()) + { + setBingVerified(false, false); + } +} + +void LLFloaterTranslationSettings::onGoogleKeyEdited() +{ + if (mGoogleAPIKeyEditor->isDirty()) + { + setGoogleVerified(false, false); + } +} + +void LLFloaterTranslationSettings::onBtnBingVerify() +{ + std::string key = getEnteredBingKey(); + if (!key.empty()) + { + verifyKey(LLTranslate::SERVICE_BING, key); + } +} + +void LLFloaterTranslationSettings::onBtnGoogleVerify() +{ + std::string key = getEnteredGoogleKey(); + if (!key.empty()) + { + verifyKey(LLTranslate::SERVICE_GOOGLE, key); + } +} + +void LLFloaterTranslationSettings::onBtnOK() +{ + gSavedSettings.setBOOL("TranslateChat", mMachineTranslationCB->getValue().asBoolean()); + gSavedSettings.setString("TranslateLanguage", mLanguageCombo->getSelectedValue().asString()); + gSavedSettings.setString("TranslationService", getSelectedService()); + gSavedSettings.setString("BingTranslateAPIKey", getEnteredBingKey()); + gSavedSettings.setString("GoogleTranslateAPIKey", getEnteredGoogleKey()); + closeFloater(false); +} diff --git a/indra/newview/llfloatertranslationsettings.h b/indra/newview/llfloatertranslationsettings.h new file mode 100644 index 0000000000..9b47ad72ed --- /dev/null +++ b/indra/newview/llfloatertranslationsettings.h @@ -0,0 +1,76 @@ +/** + * @file llfloatertranslationsettings.h + * @brief Machine translation settings for chat + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, 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_LLFLOATERTRANSLATIONSETTINGS_H +#define LL_LLFLOATERTRANSLATIONSETTINGS_H + +#include "llfloater.h" + +class LLButton; +class LLCheckBoxCtrl; +class LLComboBox; +class LLLineEditor; +class LLRadioGroup; + +class LLFloaterTranslationSettings : public LLFloater +{ +public: + LLFloaterTranslationSettings(const LLSD& key); + /*virtual*/ BOOL postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + + void setBingVerified(bool ok, bool alert); + void setGoogleVerified(bool ok, bool alert); + +private: + std::string getSelectedService() const; + std::string getEnteredBingKey() const; + std::string getEnteredGoogleKey() const; + void showAlert(const std::string& msg_name) const; + void updateControlsEnabledState(); + void verifyKey(int service, const std::string& key, bool alert = true); + + void onEditorFocused(LLFocusableElement* control); + void onBingKeyEdited(); + void onGoogleKeyEdited(); + void onBtnBingVerify(); + void onBtnGoogleVerify(); + void onBtnOK(); + + LLCheckBoxCtrl* mMachineTranslationCB; + LLComboBox* mLanguageCombo; + LLLineEditor* mBingAPIKeyEditor; + LLLineEditor* mGoogleAPIKeyEditor; + LLRadioGroup* mTranslationServiceRadioGroup; + LLButton* mBingVerifyBtn; + LLButton* mGoogleVerifyBtn; + LLButton* mOKBtn; + + bool mBingKeyVerified; + bool mGoogleKeyVerified; +}; + +#endif // LL_LLFLOATERTRANSLATIONSETTINGS_H diff --git a/indra/newview/lltranslate.cpp b/indra/newview/lltranslate.cpp index 3692f67a8d..7eb54271f4 100644 --- a/indra/newview/lltranslate.cpp +++ b/indra/newview/lltranslate.cpp @@ -31,50 +31,224 @@ #include <curl/curl.h> #include "llbufferstream.h" +#include "lltrans.h" #include "llui.h" #include "llversioninfo.h" #include "llviewercontrol.h" #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; +// virtual +void LLGoogleTranslationHandler::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 +void LLGoogleTranslationHandler::getKeyVerificationURL( + std::string& url, + const std::string& key) const +{ + url = std::string("https://www.googleapis.com/language/translate/v2/languages?key=") + + key + "&target=en"; +} + +// virtual +bool LLGoogleTranslationHandler::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 (!root.isObject()) // empty response? should not happen + { + return false; + } + + if (status != STATUS_OK) + { + // Request failed. Extract error message from the response. + parseErrorResponse(root, status, err_msg); + return false; + } + + // Request succeeded, extract translation from the response. + return parseTranslation(root, translation, detected_lang); +} + +// static +void LLGoogleTranslationHandler::parseErrorResponse( + const Json::Value& root, + int& status, + std::string& err_msg) +{ + const Json::Value& error = root.get("error", 0); + if (!error.isObject() || !error.isMember("message") || !error.isMember("code")) + { + return; + } -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"; + err_msg = error["message"].asString(); + status = error["code"].asInt(); +} + +// static +bool LLGoogleTranslationHandler::parseTranslation( + const Json::Value& root, + std::string& translation, + std::string& detected_lang) +{ + // JsonCpp is prone to aborting the program on failed assertions, + // so be super-careful and verify the response format. + const Json::Value& data = root.get("data", 0); + if (!data.isObject() || !data.isMember("translations")) + { + return false; + } + + const Json::Value& translations = data["translations"]; + if (!translations.isArray() || translations.size() == 0) + { + return false; + } + + const Json::Value& first = translations[0U]; + if (!first.isObject() || !first.isMember("translatedText")) + { + return false; + } + + translation = first["translatedText"].asString(); + detected_lang = first.get("detectedSourceLanguage", "").asString(); + return true; +} -// 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"; +// static +std::string LLGoogleTranslationHandler::getAPIKey() +{ + return gSavedSettings.getString("GoogleTranslateAPIKey"); +} // virtual -void LLTranslate::TranslationReceiver::completedRaw( U32 status, - const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer) +void LLBingTranslationHandler::getTranslateURL( + std::string &url, + const std::string &from_lang, + const std::string &to_lang, + const std::string &text) const { - if (200 <= status && status < 300) + url = std::string("http://api.microsofttranslator.com/v2/Http.svc/Translate?appId=") + + getAPIKey() + "&text=" + LLURI::escape(text) + "&to=" + to_lang; + if (!from_lang.empty()) { - LLBufferStream istr(channels, buffer.get()); - std::stringstream strstrm; - strstrm << istr.rdbuf(); + url += "&from=" + from_lang; + } +} - const std::string result = strstrm.str(); - std::string translation; - std::string detected_language; +// virtual +void LLBingTranslationHandler::getKeyVerificationURL( + std::string& url, + const std::string& key) const +{ + url = std::string("http://api.microsofttranslator.com/v2/Http.svc/GetLanguagesForTranslate?appId=") + + key; +} - if (!parseGoogleTranslate(result, translation, detected_language)) +// virtual +bool LLBingTranslationHandler::parseResponse( + int& status, + const std::string& body, + std::string& translation, + std::string& detected_lang, + std::string& err_msg) const +{ + if (status != STATUS_OK) + { + static const std::string MSG_BEGIN_MARKER = "Message: "; + size_t begin = body.find(MSG_BEGIN_MARKER); + if (begin != std::string::npos) + { + begin += MSG_BEGIN_MARKER.size(); + } + else { - handleFailure(); - return; + begin = 0; + err_msg.clear(); } - + size_t end = body.find("</p>", begin); + err_msg = body.substr(begin, end-begin); + LLStringUtil::replaceString(err_msg, "
", ""); // 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)) + { + begin = 0; + } + else + { + ++begin; + } + + size_t end = body.find("</string>", begin); + + detected_lang = ""; // unsupported by this API + translation = body.substr(begin, end-begin); + LLStringUtil::replaceString(translation, "
", ""); // strip CR + return true; +} + +// 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, ">",">"); @@ -83,67 +257,66 @@ void LLTranslate::TranslationReceiver::completedRaw( U32 status, LLStringUtil::replaceString(translation, "&","&"); LLStringUtil::replaceString(translation, "'","'"); - handleResponse(translation, detected_language); + handleResponse(translation, detected_lang); } else { - LL_WARNS("Translate") << "HTTP request for Google Translate failed with status " << status << ", reason: " << reason << LL_ENDL; - handleFailure(); + if (err_msg.empty()) + { + err_msg = LLTrans::getString("TranslationResponseParseError"); + } + + llwarns << "Translation request failed: " << err_msg << llendl; + handleFailure(status, err_msg); } } -//static -void LLTranslate::translateMessage(LLHTTPClient::ResponderPtr &result, const std::string &from_lang, const std::string &to_lang, const std::string &mesg) +LLTranslate::KeyVerificationReceiver::KeyVerificationReceiver(EService service) +: mService(service) { - std::string url; - getTranslateUrl(url, from_lang, to_lang, mesg); - - std::string user_agent = llformat("%s %d.%d.%d (%d)", - LLVersionInfo::getChannel().c_str(), - LLVersionInfo::getMajor(), - LLVersionInfo::getMinor(), - LLVersionInfo::getPatch(), - LLVersionInfo::getBuild()); +} - if (!m_Header.size()) - { - m_Header.insert(m_AcceptHeader, LLSD(m_AcceptType)); - m_Header.insert(m_AgentHeader, LLSD(user_agent)); - } +LLTranslate::EService LLTranslate::KeyVerificationReceiver::getService() const +{ + return mService; +} - LLHTTPClient::get(url, result, m_Header, m_GoogleTimeout); +// 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::getTranslateUrl(std::string &translate_url, const std::string &from_lang, const std::string &to_lang, const std::string &mesg) +void LLTranslate::translateMessage( + TranslationReceiverPtr &receiver, + const std::string &from_lang, + const std::string &to_lang, + const std::string &mesg) { - char * curl_str = curl_escape(mesg.c_str(), mesg.size()); - std::string const escaped_mesg(curl_str); - curl_free(curl_str); + std::string url; + receiver->mHandler.getTranslateURL(url, from_lang, to_lang, mesg); - translate_url = m_GoogleURL - + escaped_mesg + m_GoogleLangSpec - + from_lang // 'from' language; empty string for auto - + "%7C" // | - + to_lang; // 'to' language + LL_DEBUGS("Translate") << "Sending translation request: " << url << LL_ENDL; + sendRequest(url, receiver); } -//static -bool LLTranslate::parseGoogleTranslate(const std::string& body, std::string &translation, std::string &detected_language) +// static +void LLTranslate::verifyKey( + KeyVerificationReceiverPtr& receiver, + const std::string& key) { - Json::Value root; - Json::Reader reader; - - bool success = reader.parse(body, root); - if (!success) - { - LL_WARNS("Translate") << "Non valid response from Google Translate API: '" << reader.getFormatedErrorMessages() << "'" << LL_ENDL; - return false; - } - - translation = root[m_GoogleData].get(m_GoogleTranslation, "").asString(); - detected_language = root[m_GoogleData].get(m_GoogleLanguage, "").asString(); - return true; + 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 @@ -158,3 +331,52 @@ std::string LLTranslate::getTranslateLanguage() return language; } +// 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)", + LLVersionInfo::getChannel().c_str(), + LLVersionInfo::getMajor(), + LLVersionInfo::getMinor(), + LLVersionInfo::getPatch(), + LLVersionInfo::getBuild()); + + 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 c7d6eac33f..c2330daa81 100644 --- a/indra/newview/lltranslate.h +++ b/indra/newview/lltranslate.h @@ -30,56 +30,257 @@ #include "llhttpclient.h" #include "llbufferstream.h" +namespace Json +{ + class Value; +} + +/** + * 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; + + /** + * 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; + + /** + * 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 ~LLTranslationAPIHandler() {} + +protected: + static const int STATUS_OK = 200; +}; + +/// Google Translate v2 API handler. +class LLGoogleTranslationHandler : public LLTranslationAPIHandler +{ + LOG_CLASS(LLGoogleTranslationHandler); + +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; + +private: + 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(); +}; + +/// Microsoft Translator v2 API handler. +class LLBingTranslationHandler : public LLTranslationAPIHandler +{ + LOG_CLASS(LLBingTranslationHandler); + +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; +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: - TranslationReceiver(const std::string &from_lang, const std::string &to_lang) - : m_fromLang(from_lang), - m_toLang(to_lang) - { - } + 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; - virtual void handleFailure() = 0; - public: - ~TranslationReceiver() - { - } + /// 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; + }; - virtual void completedRaw( U32 status, - const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer); + /** + * Subclasses are supposed to handle API key verification result. + */ + class KeyVerificationReceiver: public LLHTTPClient::Responder + { + public: + EService getService() const; protected: - const std::string m_toLang; - const std::string m_fromLang; + /** + * 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; }; - 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; + typedef boost::intrusive_ptr<KeyVerificationReceiver> 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); 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(); + static const LLTranslationAPIHandler& getHandler(EService service); + static void sendRequest(const std::string& url, LLHTTPClient::ResponderPtr responder); }; #endif diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index ba53540374..c761969fcf 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -107,6 +107,7 @@ #include "llfloatertos.h" #include "llfloatertopobjects.h" #include "llfloatertoybox.h" +#include "llfloatertranslationsettings.h" #include "llfloateruipreview.h" #include "llfloatervoiceeffect.h" #include "llfloaterwhitelistentry.h" @@ -248,6 +249,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("preferences", "floater_preferences.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterPreference>); LLFloaterReg::add("prefs_proxy", "floater_preferences_proxy.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterPreferenceProxy>); LLFloaterReg::add("prefs_hardware_settings", "floater_hardware_settings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterHardwareSettings>); + LLFloaterReg::add("prefs_translation", "floater_translation_settings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterTranslationSettings>); LLFloaterReg::add("perm_prefs", "floater_perm_prefs.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterPerms>); LLFloaterReg::add("picks", "floater_picks.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSidePanelContainer>); LLFloaterReg::add("pref_joystick", "floater_joystick.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterJoystick>); diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 65b569a190..a9ca70fd26 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -3121,7 +3121,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 + ")"; @@ -3130,10 +3130,13 @@ protected: LLNotificationsUI::LLNotificationManager::instance().onChat(m_chat, m_toastArgs); } - void handleFailure() + void handleFailure(int status, const std::string& err_msg) { - llwarns << "translation failed for mesg " << m_origMesg << " toLang " << m_toLang << " fromLang " << m_fromLang << llendl; - m_chat.mText += " (?)"; + llwarns << "Translation failed for mesg " << m_origMesg << " toLang " << mToLang << " fromLang " << mFromLang << llendl; + + std::string msg = LLTrans::getString("TranslationFailed", LLSD().with("[REASON]", err_msg)); + LLStringUtil::replaceString(msg, "\n", " "); // we want one-line error messages + m_chat.mText += " (" + msg + ")"; LLNotificationsUI::LLNotificationManager::instance().onChat(m_chat, m_toastArgs); } @@ -3371,7 +3374,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 diff --git a/indra/newview/skins/default/xui/en/floater_translation_settings.xml b/indra/newview/skins/default/xui/en/floater_translation_settings.xml new file mode 100644 index 0000000000..c03f751265 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_translation_settings.xml @@ -0,0 +1,244 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + legacy_header_height="18" + height="310" + layout="topleft" + name="floater_translation_settings" + help_topic="environment_editor_floater" + save_rect="true" + title="CHAT TRANSLATION SETTINGS" + width="485"> + + <string name="bing_api_key_not_verified">Bing appID not verified. Please try again.</string> + <string name="google_api_key_not_verified">Google API key not verified. Please try again.</string> + + <string name="bing_api_key_verified">Bing appID verified.</string> + <string name="google_api_key_verified">Google API key verified.</string> + + <check_box + height="16" + label="Enable machine translation while chatting" + layout="topleft" + left="10" + name="translate_chat_checkbox" + top="30" + width="20" /> + <text + height="20" + follows="left|top" + layout="topleft" + left="40" + name="translate_language_label" + top_pad="20" + width="130"> + Translate chat into: + </text> + <combo_box + allow_text_entry="true" + follows="left|top" + height="23" + left_pad="10" + max_chars="135" + mouse_opaque="true" + name="translate_language_combo" + top_delta="-5" + width="190"> + <combo_box.item + label="System Default" + name="System Default Language" + value="default" /> + <combo_box.item + label="English" + name="English" + value="en" /> + <!-- After "System Default" and "English", please keep the rest of these combo_box.items in alphabetical order by the first character in the string. --> + <combo_box.item + label="Dansk (Danish)" + name="Danish" + value="da" /> + <combo_box.item + label="Deutsch (German)" + name="German" + value="de" /> + <combo_box.item + label="Español (Spanish)" + name="Spanish" + value="es" /> + <combo_box.item + label="Français (French)" + name="French" + value="fr" /> + <combo_box.item + label="Italiano (Italian)" + name="Italian" + value="it" /> + <combo_box.item + label="Magyar (Hungarian)" + name="Hungarian" + value="hu" /> + <combo_box.item + label="Nederlands (Dutch)" + name="Dutch" + value="nl" /> + <combo_box.item + label="Polski (Polish)" + name="Polish" + value="pl" /> + <combo_box.item + label="Português (Portuguese)" + name="Portugese" + value="pt" /> + <combo_box.item + label="Русский (Russian)" + name="Russian" + value="ru" /> + <combo_box.item + label="Türkçe (Turkish)" + name="Turkish" + value="tr" /> + <combo_box.item + label="Українська (Ukrainian)" + name="Ukrainian" + value="uk" /> + <combo_box.item + label="中文 (正體) (Chinese)" + name="Chinese" + value="zh" /> + <combo_box.item + label="日本語 (Japanese)" + name="Japanese" + value="ja" /> + <combo_box.item + label="한국어 (Korean)" + name="Korean" + value="ko" /> + </combo_box> + + <text + follows="top|left|right" + height="15" + layout="topleft" + left="40" + name="tip" + top_pad="20" + width="330" + wrap="true"> + Choose translation service: + </text> + + <radio_group + follows="top|left" + height="80" + layout="topleft" + left_delta="10" + name="translation_service_rg" + top_pad="20" + width="320"> + <radio_item + initial_value="bing" + label="Bing Translator" + layout="topleft" + name="bing" /> + <radio_item + initial_value="google" + label="Google Translate" + layout="topleft" + name="google" + top_pad="55" /> + </radio_group> + + <text + type="string" + length="1" + follows="top|right" + height="20" + layout="topleft" + left="70" + name="bing_api_key_label" + top_pad="-55" + width="85"> + Bing [http://www.bing.com/developers/createapp.aspx AppID]: + </text> + <line_editor + default_text="Enter Bing AppID and click "Verify"" + follows="top|left" + height="20" + layout="topleft" + left_pad="10" + max_length_chars="50" + top_delta="-4" + name="bing_api_key" + width="210" /> + <button + follows="left|top" + height="23" + label="Verify" + layout="topleft" + left_pad="10" + name="verify_bing_api_key_btn" + top_delta="-2" + width="90" /> + + <text + follows="top|right" + height="20" + layout="topleft" + left="70" + length="1" + name="google_api_key_label" + top_pad="50" + type="string" + width="85"> + Google [http://code.google.com/apis/language/translate/v2/getting_started.html#auth API key]: + </text> + <line_editor + default_text="Enter Google API key and click "Verify"" + follows="top|left" + height="20" + layout="topleft" + left_pad="10" + max_length_chars="50" + top_delta="-4" + name="google_api_key" + width="210" /> + <button + follows="left|top" + height="23" + label="Verify" + layout="topleft" + left_pad="10" + name="verify_google_api_key_btn" + top_delta="-2" + width="90" /> + + <text + follows="top|right" + height="20" + layout="topleft" + left="185" + length="1" + name="google_links_text" + top_delta="-23" + type="string" + width="100"> + [http://code.google.com/apis/language/translate/v2/pricing.html Pricing] | [https://code.google.com/apis/console Stats] + </text> + + <button + follows="left|top" + height="23" + label="OK" + layout="topleft" + right="-120" + name="ok_btn" + top="-30" + width="100" /> + <button + follows="left|top" + height="23" + label="Cancel" + layout="topleft" + left_pad="10" + name="cancel_btn" + width="100" /> +</floater> diff --git a/indra/newview/skins/default/xui/en/panel_preferences_chat.xml b/indra/newview/skins/default/xui/en/panel_preferences_chat.xml index 52be805260..caf7fc85f5 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_chat.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_chat.xml @@ -204,119 +204,16 @@ name="nearby_toasts_fadingtime" top_pad="3" width="325" /> - - <check_box - control_name="TranslateChat" - enabled="true" - height="16" - layout="topleft" - left="30" - name="translate_chat_checkbox" - top_pad="5" - width="400" /> - <!-- *HACK - After storm-1109 will be fixed: instead of using this text_box, word_wrap should be applied for "translate_chat_checkbox" check_box's label.--> - <text - follows="top|left" - height="15" - layout="topleft" - left="50" - name="translate_chb_label" - top_delta="1" - width="450" - wrap="true"> - Use machine translation while chatting (powered by Google) - </text> - <text - top_pad="20" - name="translate_language_text" - follows="left|top" - layout="topleft" - left_delta="20" - height="20" - width="110"> - Translate chat into: - </text> - <combo_box - allow_text_entry="true" - bottom_delta="3" - control_name="TranslateLanguage" - enabled="true" - follows="left|top" - height="23" - left_delta="110" - max_chars="135" - mouse_opaque="true" - name="translate_language_combobox" - width="146"> - <combo_box.item - label="System Default" - name="System Default Language" - value="default" /> - <combo_box.item - label="English" - name="English" - value="en" /> - <!-- After "System Default" and "English", please keep the rest of these combo_box.items in alphabetical order by the first character in the string. --> - <combo_box.item - label="Dansk (Danish)" - name="Danish" - value="da" /> - <combo_box.item - label="Deutsch (German)" - name="German" - value="de" /> - <combo_box.item - label="Español (Spanish)" - name="Spanish" - value="es" /> - <combo_box.item - label="Français (French)" - name="French" - value="fr" /> - <combo_box.item - label="Italiano (Italian)" - name="Italian" - value="it" /> - <combo_box.item - label="Magyar (Hungarian)" - name="Hungarian" - value="hu" /> - <combo_box.item - label="Nederlands (Dutch)" - name="Dutch" - value="nl" /> - <combo_box.item - label="Polski (Polish)" - name="Polish" - value="pl" /> - <combo_box.item - label="Português (Portuguese)" - name="Portugese" - value="pt" /> - <combo_box.item - label="Русский (Russian)" - name="Russian" - value="ru" /> - <combo_box.item - label="Türkçe (Turkish)" - name="Turkish" - value="tr" /> - <combo_box.item - label="Українська (Ukrainian)" - name="Ukrainian" - value="uk" /> - <combo_box.item - label="中文 (正體) (Chinese)" - name="Chinese" - value="zh" /> - <combo_box.item - label="日本語 (Japanese)" - name="Japanese" - value="ja" /> - <combo_box.item - label="한국어 (Korean)" - name="Korean" - value="ko" /> - </combo_box> + <button + follows="left|top" + height="23" + label="Chat Translation Settings" + layout="topleft" + left="30" + name="ok_btn" + top="-40" + width="170"> + <button.commit_callback + function="Pref.TranslationSettings" /> + </button> </panel>
\ No newline at end of file diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index de5ade7fd9..24cec13c4c 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -3531,6 +3531,10 @@ Try enclosing path to the editor with double quotes. <string name="ExternalEditorCommandParseError">Error parsing the external editor command.</string> <string name="ExternalEditorFailedToRun">External editor failed to run.</string> + <!-- Machine translation of chat messahes --> + <string name="TranslationFailed">Translation failed: [REASON]</string> + <string name="TranslationResponseParseError">Error parsing translation response.</string> + <!-- Key names begin --> <string name="Esc">Esc</string> <string name="Space">Space</string> diff --git a/indra/newview/tests/lltranslate_test.cpp b/indra/newview/tests/lltranslate_test.cpp new file mode 100644 index 0000000000..10e37fae97 --- /dev/null +++ b/indra/newview/tests/lltranslate_test.cpp @@ -0,0 +1,345 @@ +/** + * @file lltranslate_test.cpp + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, 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 "linden_common.h" + +#include "../test/lltut.h" +#include "../lltranslate.h" +#include "../llversioninfo.h" +#include "../llviewercontrol.h" + +#include "llbufferstream.h" +#include "lltrans.h" +#include "llui.h" + +static const std::string GOOGLE_VALID_RESPONSE1 = +"{\ + \"data\": {\ + \"translations\": [\ + {\ + \"translatedText\": \"привет\",\ + \"detectedSourceLanguage\": \"es\"\ + }\ + ]\ + }\ +}"; + +static const std::string GOOGLE_VALID_RESPONSE2 = +"{\ + \"data\": {\ + \"translations\": [\ + {\ + \"translatedText\": \"привет\"\ + }\ + ]\ + }\ +}\ +"; + +static const std::string GOOGLE_VALID_RESPONSE3 = +"{\ + \"error\": {\ + \"errors\": [\ + {\ + \"domain\": \"global\",\ + \"reason\": \"invalid\",\ + \"message\": \"Invalid Value\"\ + }\ + ],\ + \"code\": 400,\ + \"message\": \"Invalid Value\"\ + }\ +}"; + +static const std::string BING_VALID_RESPONSE1 = +"<string xmlns=\"http://schemas.microsoft.com/2003/10/Serialization/\">Привет</string>"; + +static const std::string BING_VALID_RESPONSE2 = +"<html><body><h1>Argument Exception</h1><p>Method: Translate()</p><p>Parameter: </p>\ +<p>Message: 'from' must be a valid language</p><code></code>\ +<p>message id=3743.V2_Rest.Translate.58E8454F</p></body></html>"; + +static const std::string BING_VALID_RESPONSE3 = +"<html><body><h1>Argument Exception</h1><p>Method: Translate()</p>\ +<p>Parameter: appId</p><p>Message: Invalid appId
\nParameter name: appId</p>\ +<code></code><p>message id=3737.V2_Rest.Translate.56016759</p></body></html>"; + +namespace tut +{ + class translate_test + { + protected: + void test_translation( + LLTranslationAPIHandler& handler, + int status, const std::string& resp, + const std::string& exp_trans, const std::string& exp_lang, const std::string& exp_err) + { + std::string translation, detected_lang, err_msg; + bool rc = handler.parseResponse(status, resp, translation, detected_lang, err_msg); + ensure_equals("rc", rc, (status == 200)); + ensure_equals("err_msg", err_msg, exp_err); + ensure_equals("translation", translation, exp_trans); + ensure_equals("detected_lang", detected_lang, exp_lang); + } + + LLGoogleTranslationHandler mGoogle; + LLBingTranslationHandler mBing; + }; + + typedef test_group<translate_test> translate_test_group_t; + typedef translate_test_group_t::object translate_test_object_t; + tut::translate_test_group_t tut_translate("LLTranslate"); + + template<> template<> + void translate_test_object_t::test<1>() + { + test_translation(mGoogle, 200, GOOGLE_VALID_RESPONSE1, "привет", "es", ""); + } + + template<> template<> + void translate_test_object_t::test<2>() + { + test_translation(mGoogle, 200, GOOGLE_VALID_RESPONSE2, "привет", "", ""); + } + + template<> template<> + void translate_test_object_t::test<3>() + { + test_translation(mGoogle, 400, GOOGLE_VALID_RESPONSE3, "", "", "Invalid Value"); + } + + template<> template<> + void translate_test_object_t::test<4>() + { + test_translation(mGoogle, 400, + "", + "", "", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n"); + } + + template<> template<> + void translate_test_object_t::test<5>() + { + test_translation(mGoogle, 400, + "[]", + "", "", ""); + } + + template<> template<> + void translate_test_object_t::test<6>() + { + test_translation(mGoogle, 400, + "{\"oops\": \"invalid\"}", + "", "", ""); + } + + template<> template<> + void translate_test_object_t::test<7>() + { + test_translation(mGoogle, 400, + "{\"data\": {}}", + "", "", ""); + } + + template<> template<> + void translate_test_object_t::test<8>() + { + test_translation(mGoogle, 400, + "{\"data\": { \"translations\": [ {} ] }}", + "", "", ""); + } + + template<> template<> + void translate_test_object_t::test<9>() + { + test_translation(mGoogle, 400, + "{\"data\": { \"translations\": [ { \"translatedTextZZZ\": \"привет\", \"detectedSourceLanguageZZZ\": \"es\" } ] }}", + "", "", ""); + } + + template<> template<> + void translate_test_object_t::test<10>() + { + test_translation(mBing, 200, BING_VALID_RESPONSE1, "Привет", "", ""); + } + + template<> template<> + void translate_test_object_t::test<11>() + { + test_translation(mBing, 400, BING_VALID_RESPONSE2, "", "", "'from' must be a valid language"); + } + + template<> template<> + void translate_test_object_t::test<12>() + { + test_translation(mBing, 400, BING_VALID_RESPONSE3, "", "", "Invalid appId\nParameter name: appId"); + } + + template<> template<> + void translate_test_object_t::test<13>() + { + test_translation(mBing, 200, + "Привет</string>", + "Привет", "", ""); + } + + template<> template<> + void translate_test_object_t::test<14>() + { + test_translation(mBing, 200, + "<string xmlns=\"http://schemas.microsoft.com/2003/10/Serialization/\">Привет", + "Привет", "", ""); + } + + template<> template<> + void translate_test_object_t::test<15>() + { + test_translation(mBing, 200, + "Привет", + "Привет", "", ""); + } + + template<> template<> + void translate_test_object_t::test<16>() + { + test_translation(mBing, 400, + "Message: some error</p>", + "", "", "some error"); + } + + template<> template<> + void translate_test_object_t::test<17>() + { + test_translation(mBing, 400, + "Message: some error", + "", "", "some error"); + } + + template<> template<> + void translate_test_object_t::test<18>() + { + test_translation(mBing, 400, + "some error</p>", + "", "", "some error"); + } + + template<> template<> + void translate_test_object_t::test<19>() + { + test_translation(mBing, 400, + "some error", + "", "", "some error"); + } + + template<> template<> + void translate_test_object_t::test<20>() + { + std::string url; + mBing.getTranslateURL(url, "en", "es", "hi"); + ensure_equals("bing URL", url, + "http://api.microsofttranslator.com/v2/Http.svc/Translate?appId=dummy&text=hi&to=es&from=en"); + } + + template<> template<> + void translate_test_object_t::test<21>() + { + std::string url; + mBing.getTranslateURL(url, "", "es", "hi"); + ensure_equals("bing URL", url, + "http://api.microsofttranslator.com/v2/Http.svc/Translate?appId=dummy&text=hi&to=es"); + } + + template<> template<> + void translate_test_object_t::test<22>() + { + std::string url; + mGoogle.getTranslateURL(url, "en", "es", "hi"); + ensure_equals("google URL", url, + "https://www.googleapis.com/language/translate/v2?key=dummy&q=hi&target=es&source=en"); + } + + template<> template<> + void translate_test_object_t::test<23>() + { + std::string url; + mGoogle.getTranslateURL(url, "", "es", "hi"); + ensure_equals("google URL", url, + "https://www.googleapis.com/language/translate/v2?key=dummy&q=hi&target=es"); + } +} + +//== Misc stubs =============================================================== +LLControlGroup gSavedSettings("test"); + +std::string LLUI::getLanguage() { return "en"; } +std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args) { return "dummy"; } + +LLControlGroup::LLControlGroup(const std::string& name) : LLInstanceTracker<LLControlGroup, std::string>(name) {} +std::string LLControlGroup::getString(const std::string& name) { return "dummy"; } +LLControlGroup::~LLControlGroup() {} + +namespace boost { + void intrusive_ptr_add_ref(LLCurl::Responder*) {} + void intrusive_ptr_release(LLCurl::Responder*) {} +} + +LLCurl::Responder::Responder() {} +void LLCurl::Responder::completedHeader(U32, std::string const&, LLSD const&) {} +void LLCurl::Responder::completedRaw(U32, const std::string&, const LLChannelDescriptors&, const LLIOPipe::buffer_ptr_t& buffer) {} +void LLCurl::Responder::completed(U32, std::string const&, LLSD const&) {} +void LLCurl::Responder::error(U32, std::string const&) {} +void LLCurl::Responder::errorWithContent(U32, std::string const&, LLSD const&) {} +void LLCurl::Responder::result(LLSD const&) {} +LLCurl::Responder::~Responder() {} + +void LLHTTPClient::get(const std::string&, const LLSD&, ResponderPtr, const LLSD&, const F32) {} +void LLHTTPClient::get(const std::string&, boost::intrusive_ptr<LLCurl::Responder>, const LLSD&, const F32) {} + +LLBufferStream::LLBufferStream(const LLChannelDescriptors& channels, LLBufferArray* buffer) +: std::iostream(&mStreamBuf), mStreamBuf(channels, buffer) {} +LLBufferStream::~LLBufferStream() {} + +LLBufferStreamBuf::LLBufferStreamBuf(const LLChannelDescriptors&, LLBufferArray*) {} +#if( LL_WINDOWS || __GNUC__ > 2) +LLBufferStreamBuf::pos_type LLBufferStreamBuf::seekoff( + off_type off, + std::ios::seekdir way, + std::ios::openmode which) +#else +streampos LLBufferStreamBuf::seekoff( + streamoff off, + std::ios::seekdir way, + std::ios::openmode which) +#endif +{ return 0; } +int LLBufferStreamBuf::sync() {return 0;} +int LLBufferStreamBuf::underflow() {return 0;} +int LLBufferStreamBuf::overflow(int) {return 0;} +LLBufferStreamBuf::~LLBufferStreamBuf() {} + +S32 LLVersionInfo::getBuild() { return 0; } +const std::string& LLVersionInfo::getChannel() {static std::string dummy; return dummy;} +S32 LLVersionInfo::getMajor() { return 0; } +S32 LLVersionInfo::getMinor() { return 0; } +S32 LLVersionInfo::getPatch() { return 0; } |