1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "components/translate/core/browser/translate_manager.h" 6 7 #include "base/bind.h" 8 #include "base/command_line.h" 9 #include "base/metrics/field_trial.h" 10 #include "base/metrics/histogram.h" 11 #include "base/prefs/pref_service.h" 12 #include "base/strings/string_split.h" 13 #include "base/strings/stringprintf.h" 14 #include "base/time/time.h" 15 #include "components/translate/core/browser/language_state.h" 16 #include "components/translate/core/browser/page_translated_details.h" 17 #include "components/translate/core/browser/translate_accept_languages.h" 18 #include "components/translate/core/browser/translate_browser_metrics.h" 19 #include "components/translate/core/browser/translate_client.h" 20 #include "components/translate/core/browser/translate_download_manager.h" 21 #include "components/translate/core/browser/translate_driver.h" 22 #include "components/translate/core/browser/translate_error_details.h" 23 #include "components/translate/core/browser/translate_language_list.h" 24 #include "components/translate/core/browser/translate_prefs.h" 25 #include "components/translate/core/browser/translate_script.h" 26 #include "components/translate/core/browser/translate_url_util.h" 27 #include "components/translate/core/common/language_detection_details.h" 28 #include "components/translate/core/common/translate_constants.h" 29 #include "components/translate/core/common/translate_pref_names.h" 30 #include "components/translate/core/common/translate_switches.h" 31 #include "net/base/url_util.h" 32 #include "net/http/http_status_code.h" 33 34 namespace { 35 36 // Callbacks for translate errors. 37 TranslateManager::TranslateErrorCallbackList* g_callback_list_ = NULL; 38 39 const char kReportLanguageDetectionErrorURL[] = 40 "https://translate.google.com/translate_error?client=cr&action=langidc"; 41 42 // Used in kReportLanguageDetectionErrorURL to specify the original page 43 // language. 44 const char kSourceLanguageQueryName[] = "sl"; 45 46 // Used in kReportLanguageDetectionErrorURL to specify the page URL. 47 const char kUrlQueryName[] = "u"; 48 49 // Notifies |g_callback_list_| of translate errors. 50 void NotifyTranslateError(const TranslateErrorDetails& details) { 51 if (!g_callback_list_) 52 return; 53 54 g_callback_list_->Notify(details); 55 } 56 57 } // namespace 58 59 TranslateManager::~TranslateManager() {} 60 61 // static 62 scoped_ptr<TranslateManager::TranslateErrorCallbackList::Subscription> 63 TranslateManager::RegisterTranslateErrorCallback( 64 const TranslateManager::TranslateErrorCallback& callback) { 65 if (!g_callback_list_) 66 g_callback_list_ = new TranslateErrorCallbackList; 67 return g_callback_list_->Add(callback); 68 } 69 70 TranslateManager::TranslateManager( 71 TranslateClient* translate_client, 72 const std::string& accept_languages_pref_name) 73 : accept_languages_pref_name_(accept_languages_pref_name), 74 translate_client_(translate_client), 75 translate_driver_(translate_client_->GetTranslateDriver()), 76 language_state_(translate_driver_), 77 weak_method_factory_(this) { 78 } 79 80 base::WeakPtr<TranslateManager> TranslateManager::GetWeakPtr() { 81 return weak_method_factory_.GetWeakPtr(); 82 } 83 84 void TranslateManager::InitiateTranslation(const std::string& page_lang) { 85 // Short-circuit out if not in a state where initiating translation makes 86 // sense (this method may be called muhtiple times for a given page). 87 if (!language_state_.page_needs_translation() || 88 language_state_.translation_pending() || 89 language_state_.translation_declined() || 90 language_state_.IsPageTranslated()) { 91 return; 92 } 93 94 PrefService* prefs = translate_client_->GetPrefs(); 95 if (!prefs->GetBoolean(prefs::kEnableTranslate)) { 96 TranslateBrowserMetrics::ReportInitiationStatus( 97 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_PREFS); 98 const std::string& locale = 99 TranslateDownloadManager::GetInstance()->application_locale(); 100 TranslateBrowserMetrics::ReportLocalesOnDisabledByPrefs(locale); 101 return; 102 } 103 104 // Allow disabling of translate from the command line to assist with 105 // automated browser testing. 106 if (CommandLine::ForCurrentProcess()->HasSwitch( 107 translate::switches::kDisableTranslate)) { 108 TranslateBrowserMetrics::ReportInitiationStatus( 109 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_SWITCH); 110 return; 111 } 112 113 // MHTML pages currently cannot be translated. 114 // See bug: 217945. 115 if (translate_driver_->GetContentsMimeType() == "multipart/related") { 116 TranslateBrowserMetrics::ReportInitiationStatus( 117 TranslateBrowserMetrics::INITIATION_STATUS_MIME_TYPE_IS_NOT_SUPPORTED); 118 return; 119 } 120 121 // Don't translate any Chrome specific page, e.g., New Tab Page, Download, 122 // History, and so on. 123 const GURL& page_url = translate_driver_->GetVisibleURL(); 124 if (!translate_client_->IsTranslatableURL(page_url)) { 125 TranslateBrowserMetrics::ReportInitiationStatus( 126 TranslateBrowserMetrics::INITIATION_STATUS_URL_IS_NOT_SUPPORTED); 127 return; 128 } 129 130 // Get the accepted languages list. 131 std::vector<std::string> accept_languages_list; 132 base::SplitString(prefs->GetString(accept_languages_pref_name_.c_str()), ',', 133 &accept_languages_list); 134 135 std::string target_lang = GetTargetLanguage(accept_languages_list); 136 std::string language_code = 137 TranslateDownloadManager::GetLanguageCode(page_lang); 138 139 // Don't translate similar languages (ex: en-US to en). 140 if (language_code == target_lang) { 141 TranslateBrowserMetrics::ReportInitiationStatus( 142 TranslateBrowserMetrics::INITIATION_STATUS_SIMILAR_LANGUAGES); 143 return; 144 } 145 146 // Nothing to do if either the language Chrome is in or the language of the 147 // page is not supported by the translation server. 148 if (target_lang.empty() || 149 !TranslateDownloadManager::IsSupportedLanguage(language_code)) { 150 TranslateBrowserMetrics::ReportInitiationStatus( 151 TranslateBrowserMetrics::INITIATION_STATUS_LANGUAGE_IS_NOT_SUPPORTED); 152 TranslateBrowserMetrics::ReportUnsupportedLanguageAtInitiation( 153 language_code); 154 return; 155 } 156 157 scoped_ptr<TranslatePrefs> translate_prefs( 158 translate_client_->GetTranslatePrefs()); 159 160 TranslateAcceptLanguages* accept_languages = 161 translate_client_->GetTranslateAcceptLanguages(); 162 // Don't translate any user black-listed languages. 163 if (!translate_prefs->CanTranslateLanguage(accept_languages, 164 language_code)) { 165 TranslateBrowserMetrics::ReportInitiationStatus( 166 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_CONFIG); 167 return; 168 } 169 170 // Don't translate any user black-listed URLs. 171 if (translate_prefs->IsSiteBlacklisted(page_url.HostNoBrackets())) { 172 TranslateBrowserMetrics::ReportInitiationStatus( 173 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_CONFIG); 174 return; 175 } 176 177 // If the user has previously selected "always translate" for this language we 178 // automatically translate. Note that in incognito mode we disable that 179 // feature; the user will get an infobar, so they can control whether the 180 // page's text is sent to the translate server. 181 if (!translate_driver_->IsOffTheRecord()) { 182 scoped_ptr<TranslatePrefs> translate_prefs = 183 translate_client_->GetTranslatePrefs(); 184 std::string auto_target_lang = 185 GetAutoTargetLanguage(language_code, translate_prefs.get()); 186 if (!auto_target_lang.empty()) { 187 TranslateBrowserMetrics::ReportInitiationStatus( 188 TranslateBrowserMetrics::INITIATION_STATUS_AUTO_BY_CONFIG); 189 TranslatePage(language_code, auto_target_lang, false); 190 return; 191 } 192 } 193 194 std::string auto_translate_to = language_state_.AutoTranslateTo(); 195 if (!auto_translate_to.empty()) { 196 // This page was navigated through a click from a translated page. 197 TranslateBrowserMetrics::ReportInitiationStatus( 198 TranslateBrowserMetrics::INITIATION_STATUS_AUTO_BY_LINK); 199 TranslatePage(language_code, auto_translate_to, false); 200 return; 201 } 202 203 TranslateBrowserMetrics::ReportInitiationStatus( 204 TranslateBrowserMetrics::INITIATION_STATUS_SHOW_INFOBAR); 205 206 // Prompts the user if he/she wants the page translated. 207 translate_client_->ShowTranslateUI(translate::TRANSLATE_STEP_BEFORE_TRANSLATE, 208 language_code, 209 target_lang, 210 TranslateErrors::NONE, 211 false); 212 } 213 214 void TranslateManager::TranslatePage(const std::string& original_source_lang, 215 const std::string& target_lang, 216 bool triggered_from_menu) { 217 if (!translate_driver_->HasCurrentPage()) { 218 NOTREACHED(); 219 return; 220 } 221 222 // Translation can be kicked by context menu against unsupported languages. 223 // Unsupported language strings should be replaced with 224 // kUnknownLanguageCode in order to send a translation request with enabling 225 // server side auto language detection. 226 std::string source_lang(original_source_lang); 227 if (!TranslateDownloadManager::IsSupportedLanguage(source_lang)) 228 source_lang = std::string(translate::kUnknownLanguageCode); 229 230 translate_client_->ShowTranslateUI(translate::TRANSLATE_STEP_TRANSLATING, 231 source_lang, 232 target_lang, 233 TranslateErrors::NONE, 234 triggered_from_menu); 235 236 TranslateScript* script = TranslateDownloadManager::GetInstance()->script(); 237 DCHECK(script != NULL); 238 239 const std::string& script_data = script->data(); 240 if (!script_data.empty()) { 241 DoTranslatePage(script_data, source_lang, target_lang); 242 return; 243 } 244 245 // The script is not available yet. Queue that request and query for the 246 // script. Once it is downloaded we'll do the translate. 247 TranslateScript::RequestCallback callback = base::Bind( 248 &TranslateManager::OnTranslateScriptFetchComplete, GetWeakPtr(), 249 translate_driver_->GetCurrentPageID(), source_lang, target_lang); 250 251 script->Request(callback); 252 } 253 254 void TranslateManager::RevertTranslation() { 255 translate_driver_->RevertTranslation(); 256 language_state_.SetCurrentLanguage(language_state_.original_language()); 257 } 258 259 void TranslateManager::ReportLanguageDetectionError() { 260 TranslateBrowserMetrics::ReportLanguageDetectionError(); 261 262 GURL report_error_url = GURL(kReportLanguageDetectionErrorURL); 263 264 report_error_url = 265 net::AppendQueryParameter(report_error_url, 266 kUrlQueryName, 267 translate_driver_->GetActiveURL().spec()); 268 269 report_error_url = 270 net::AppendQueryParameter(report_error_url, 271 kSourceLanguageQueryName, 272 language_state_.original_language()); 273 274 report_error_url = TranslateURLUtil::AddHostLocaleToUrl(report_error_url); 275 report_error_url = TranslateURLUtil::AddApiKeyToUrl(report_error_url); 276 277 translate_client_->ShowReportLanguageDetectionErrorUI(report_error_url); 278 } 279 280 void TranslateManager::DoTranslatePage(const std::string& translate_script, 281 const std::string& source_lang, 282 const std::string& target_lang) { 283 language_state_.set_translation_pending(true); 284 translate_driver_->TranslatePage(translate_script, source_lang, target_lang); 285 } 286 287 void TranslateManager::PageTranslated(const std::string& source_lang, 288 const std::string& target_lang, 289 TranslateErrors::Type error_type) { 290 language_state_.SetCurrentLanguage(target_lang); 291 language_state_.set_translation_pending(false); 292 293 if ((error_type == TranslateErrors::NONE) && 294 source_lang != translate::kUnknownLanguageCode && 295 !TranslateDownloadManager::IsSupportedLanguage(source_lang)) { 296 error_type = TranslateErrors::UNSUPPORTED_LANGUAGE; 297 } 298 299 translate_client_->ShowTranslateUI(translate::TRANSLATE_STEP_AFTER_TRANSLATE, 300 source_lang, 301 target_lang, 302 error_type, 303 false); 304 305 if (error_type != TranslateErrors::NONE && 306 !translate_driver_->IsOffTheRecord()) { 307 TranslateErrorDetails error_details; 308 error_details.time = base::Time::Now(); 309 error_details.url = translate_driver_->GetLastCommittedURL(); 310 error_details.error = error_type; 311 NotifyTranslateError(error_details); 312 } 313 } 314 315 void TranslateManager::OnTranslateScriptFetchComplete( 316 int page_id, 317 const std::string& source_lang, 318 const std::string& target_lang, 319 bool success, 320 const std::string& data) { 321 if (!translate_driver_->HasCurrentPage() || 322 translate_driver_->GetCurrentPageID() != page_id) { 323 // We navigated away from the page the translation was triggered on. 324 return; 325 } 326 327 if (success) { 328 // Translate the page. 329 TranslateScript* translate_script = 330 TranslateDownloadManager::GetInstance()->script(); 331 DCHECK(translate_script); 332 DoTranslatePage(translate_script->data(), source_lang, target_lang); 333 } else { 334 translate_client_->ShowTranslateUI( 335 translate::TRANSLATE_STEP_TRANSLATE_ERROR, 336 source_lang, 337 target_lang, 338 TranslateErrors::NETWORK, 339 false); 340 if (!translate_driver_->IsOffTheRecord()) { 341 TranslateErrorDetails error_details; 342 error_details.time = base::Time::Now(); 343 error_details.url = translate_driver_->GetActiveURL(); 344 error_details.error = TranslateErrors::NETWORK; 345 NotifyTranslateError(error_details); 346 } 347 } 348 } 349 350 // static 351 std::string TranslateManager::GetTargetLanguage( 352 const std::vector<std::string>& accept_languages_list) { 353 std::string ui_lang = TranslatePrefs::ConvertLangCodeForTranslation( 354 TranslateDownloadManager::GetLanguageCode( 355 TranslateDownloadManager::GetInstance()->application_locale())); 356 357 if (TranslateDownloadManager::IsSupportedLanguage(ui_lang)) 358 return ui_lang; 359 360 // Will translate to the first supported language on the Accepted Language 361 // list or not at all if no such candidate exists 362 std::vector<std::string>::const_iterator iter; 363 for (iter = accept_languages_list.begin(); 364 iter != accept_languages_list.end(); ++iter) { 365 std::string lang_code = TranslateDownloadManager::GetLanguageCode(*iter); 366 if (TranslateDownloadManager::IsSupportedLanguage(lang_code)) 367 return lang_code; 368 } 369 return std::string(); 370 } 371 372 // static 373 std::string TranslateManager::GetAutoTargetLanguage( 374 const std::string& original_language, 375 TranslatePrefs* translate_prefs) { 376 std::string auto_target_lang; 377 if (translate_prefs->ShouldAutoTranslate(original_language, 378 &auto_target_lang)) { 379 // We need to confirm that the saved target language is still supported. 380 // Also, GetLanguageCode will take care of removing country code if any. 381 auto_target_lang = 382 TranslateDownloadManager::GetLanguageCode(auto_target_lang); 383 if (TranslateDownloadManager::IsSupportedLanguage(auto_target_lang)) 384 return auto_target_lang; 385 } 386 return std::string(); 387 } 388 389 LanguageState& TranslateManager::GetLanguageState() { 390 return language_state_; 391 } 392