summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indra/newview/CMakeLists.txt9
-rw-r--r--indra/newview/app_settings/settings.xml33
-rwxr-xr-xindra/newview/llfloaterpreference.cpp9
-rw-r--r--indra/newview/llfloaterpreference.h1
-rw-r--r--indra/newview/llfloatertranslationsettings.cpp296
-rw-r--r--indra/newview/llfloatertranslationsettings.h76
-rw-r--r--indra/newview/lltranslate.cpp366
-rw-r--r--indra/newview/lltranslate.h265
-rw-r--r--indra/newview/llviewerfloaterreg.cpp2
-rw-r--r--indra/newview/llviewermessage.cpp13
-rw-r--r--indra/newview/skins/default/xui/en/floater_translation_settings.xml244
-rw-r--r--indra/newview/skins/default/xui/en/panel_preferences_chat.xml127
-rw-r--r--indra/newview/skins/default/xui/en/strings.xml4
-rw-r--r--indra/newview/tests/lltranslate_test.cpp345
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, "&#xD;", ""); // strip CR
+ return false;
+ }
+
+ // Sample response: <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">Hola</string>
+ size_t begin = body.find(">");
+ if (begin == std::string::npos || begin >= (body.size() - 1))
+ {
+ 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, "&#xD;", ""); // 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, "&lt;", "<");
LLStringUtil::replaceString(translation, "&gt;",">");
@@ -83,67 +257,66 @@ void LLTranslate::TranslationReceiver::completedRaw( U32 status,
LLStringUtil::replaceString(translation, "&amp;","&");
LLStringUtil::replaceString(translation, "&apos;","'");
- 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 &quot;Verify&quot;"
+ 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 &quot;Verify&quot;"
+ 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&#xD;\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; }