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.
5 #include "components/translate/core/browser/translate_manager.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 "components/translate/core/common/translate_util.h"
32 #include "google_apis/google_api_keys.h"
33 #include "net/base/url_util.h"
34 #include "net/http/http_status_code.h"
40 // Callbacks for translate errors.
41 TranslateManager::TranslateErrorCallbackList
* g_callback_list_
= NULL
;
43 const char kReportLanguageDetectionErrorURL
[] =
44 "https://translate.google.com/translate_error?client=cr&action=langidc";
46 // Used in kReportLanguageDetectionErrorURL to specify the original page
48 const char kSourceLanguageQueryName
[] = "sl";
50 // Used in kReportLanguageDetectionErrorURL to specify the page URL.
51 const char kUrlQueryName
[] = "u";
53 // Notifies |g_callback_list_| of translate errors.
54 void NotifyTranslateError(const TranslateErrorDetails
& details
) {
55 if (!g_callback_list_
)
58 g_callback_list_
->Notify(details
);
63 TranslateManager::~TranslateManager() {}
66 scoped_ptr
<TranslateManager::TranslateErrorCallbackList::Subscription
>
67 TranslateManager::RegisterTranslateErrorCallback(
68 const TranslateManager::TranslateErrorCallback
& callback
) {
69 if (!g_callback_list_
)
70 g_callback_list_
= new TranslateErrorCallbackList
;
71 return g_callback_list_
->Add(callback
);
74 TranslateManager::TranslateManager(
75 TranslateClient
* translate_client
,
76 const std::string
& accept_languages_pref_name
)
78 accept_languages_pref_name_(accept_languages_pref_name
),
79 translate_client_(translate_client
),
80 translate_driver_(translate_client_
->GetTranslateDriver()),
81 language_state_(translate_driver_
),
82 weak_method_factory_(this) {
85 base::WeakPtr
<TranslateManager
> TranslateManager::GetWeakPtr() {
86 return weak_method_factory_
.GetWeakPtr();
89 void TranslateManager::InitiateTranslation(const std::string
& page_lang
) {
90 // Short-circuit out if not in a state where initiating translation makes
91 // sense (this method may be called muhtiple times for a given page).
92 if (!language_state_
.page_needs_translation() ||
93 language_state_
.translation_pending() ||
94 language_state_
.translation_declined() ||
95 language_state_
.IsPageTranslated()) {
99 if (!ignore_missing_key_for_testing_
&&
100 !::google_apis::HasKeysConfigured()) {
101 // Without an API key, translate won't work, so don't offer to translate
102 // in the first place. Leave prefs::kEnableTranslate on, though, because
103 // that settings syncs and we don't want to turn off translate everywhere
105 TranslateBrowserMetrics::ReportInitiationStatus(
106 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_KEY
);
110 PrefService
* prefs
= translate_client_
->GetPrefs();
111 if (!prefs
->GetBoolean(prefs::kEnableTranslate
)) {
112 TranslateBrowserMetrics::ReportInitiationStatus(
113 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_PREFS
);
114 const std::string
& locale
=
115 TranslateDownloadManager::GetInstance()->application_locale();
116 TranslateBrowserMetrics::ReportLocalesOnDisabledByPrefs(locale
);
120 // Allow disabling of translate from the command line to assist with
121 // automated browser testing.
122 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
123 translate::switches::kDisableTranslate
)) {
124 TranslateBrowserMetrics::ReportInitiationStatus(
125 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_SWITCH
);
129 // MHTML pages currently cannot be translated.
131 if (translate_driver_
->GetContentsMimeType() == "multipart/related") {
132 TranslateBrowserMetrics::ReportInitiationStatus(
133 TranslateBrowserMetrics::INITIATION_STATUS_MIME_TYPE_IS_NOT_SUPPORTED
);
137 // Don't translate any Chrome specific page, e.g., New Tab Page, Download,
138 // History, and so on.
139 const GURL
& page_url
= translate_driver_
->GetVisibleURL();
140 if (!translate_client_
->IsTranslatableURL(page_url
)) {
141 TranslateBrowserMetrics::ReportInitiationStatus(
142 TranslateBrowserMetrics::INITIATION_STATUS_URL_IS_NOT_SUPPORTED
);
146 // Get the accepted languages list.
147 std::vector
<std::string
> accept_languages_list
= base::SplitString(
148 prefs
->GetString(accept_languages_pref_name_
), ",",
149 base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
151 std::string target_lang
= GetTargetLanguage(accept_languages_list
);
152 std::string language_code
=
153 TranslateDownloadManager::GetLanguageCode(page_lang
);
155 // Don't translate similar languages (ex: en-US to en).
156 if (language_code
== target_lang
) {
157 TranslateBrowserMetrics::ReportInitiationStatus(
158 TranslateBrowserMetrics::INITIATION_STATUS_SIMILAR_LANGUAGES
);
162 // Nothing to do if either the language Chrome is in or the language of the
163 // page is not supported by the translation server.
164 if (target_lang
.empty() ||
165 !TranslateDownloadManager::IsSupportedLanguage(language_code
)) {
166 TranslateBrowserMetrics::ReportInitiationStatus(
167 TranslateBrowserMetrics::INITIATION_STATUS_LANGUAGE_IS_NOT_SUPPORTED
);
168 TranslateBrowserMetrics::ReportUnsupportedLanguageAtInitiation(
173 scoped_ptr
<TranslatePrefs
> translate_prefs(
174 translate_client_
->GetTranslatePrefs());
176 TranslateAcceptLanguages
* accept_languages
=
177 translate_client_
->GetTranslateAcceptLanguages();
178 // Don't translate any user black-listed languages.
179 if (!translate_prefs
->CanTranslateLanguage(accept_languages
,
181 TranslateBrowserMetrics::ReportInitiationStatus(
182 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_CONFIG
);
186 // Don't translate any user black-listed URLs.
187 if (translate_prefs
->IsSiteBlacklisted(page_url
.HostNoBrackets())) {
188 TranslateBrowserMetrics::ReportInitiationStatus(
189 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_CONFIG
);
193 // If the user has previously selected "always translate" for this language we
194 // automatically translate. Note that in incognito mode we disable that
195 // feature; the user will get an infobar, so they can control whether the
196 // page's text is sent to the translate server.
197 if (!translate_driver_
->IsOffTheRecord()) {
198 scoped_ptr
<TranslatePrefs
> translate_prefs
=
199 translate_client_
->GetTranslatePrefs();
200 std::string auto_target_lang
=
201 GetAutoTargetLanguage(language_code
, translate_prefs
.get());
202 if (!auto_target_lang
.empty()) {
203 TranslateBrowserMetrics::ReportInitiationStatus(
204 TranslateBrowserMetrics::INITIATION_STATUS_AUTO_BY_CONFIG
);
205 TranslatePage(language_code
, auto_target_lang
, false);
210 std::string auto_translate_to
= language_state_
.AutoTranslateTo();
211 if (!auto_translate_to
.empty()) {
212 // This page was navigated through a click from a translated page.
213 TranslateBrowserMetrics::ReportInitiationStatus(
214 TranslateBrowserMetrics::INITIATION_STATUS_AUTO_BY_LINK
);
215 TranslatePage(language_code
, auto_translate_to
, false);
219 TranslateBrowserMetrics::ReportInitiationStatus(
220 TranslateBrowserMetrics::INITIATION_STATUS_SHOW_INFOBAR
);
222 // Prompts the user if they want the page translated.
223 translate_client_
->ShowTranslateUI(translate::TRANSLATE_STEP_BEFORE_TRANSLATE
,
226 TranslateErrors::NONE
,
230 void TranslateManager::TranslatePage(const std::string
& original_source_lang
,
231 const std::string
& target_lang
,
232 bool triggered_from_menu
) {
233 if (!translate_driver_
->HasCurrentPage()) {
238 // Translation can be kicked by context menu against unsupported languages.
239 // Unsupported language strings should be replaced with
240 // kUnknownLanguageCode in order to send a translation request with enabling
241 // server side auto language detection.
242 std::string
source_lang(original_source_lang
);
243 if (!TranslateDownloadManager::IsSupportedLanguage(source_lang
))
244 source_lang
= std::string(translate::kUnknownLanguageCode
);
246 translate_client_
->ShowTranslateUI(translate::TRANSLATE_STEP_TRANSLATING
,
249 TranslateErrors::NONE
,
250 triggered_from_menu
);
252 TranslateScript
* script
= TranslateDownloadManager::GetInstance()->script();
253 DCHECK(script
!= NULL
);
255 const std::string
& script_data
= script
->data();
256 if (!script_data
.empty()) {
257 DoTranslatePage(script_data
, source_lang
, target_lang
);
261 // The script is not available yet. Queue that request and query for the
262 // script. Once it is downloaded we'll do the translate.
263 TranslateScript::RequestCallback callback
= base::Bind(
264 &TranslateManager::OnTranslateScriptFetchComplete
, GetWeakPtr(),
265 source_lang
, target_lang
);
267 script
->Request(callback
);
270 void TranslateManager::RevertTranslation() {
271 translate_driver_
->RevertTranslation(page_seq_no_
);
272 language_state_
.SetCurrentLanguage(language_state_
.original_language());
275 void TranslateManager::ReportLanguageDetectionError() {
276 TranslateBrowserMetrics::ReportLanguageDetectionError();
278 GURL report_error_url
= GURL(kReportLanguageDetectionErrorURL
);
280 report_error_url
= net::AppendQueryParameter(
281 report_error_url
, kUrlQueryName
,
282 translate_driver_
->GetLastCommittedURL().spec());
285 net::AppendQueryParameter(report_error_url
,
286 kSourceLanguageQueryName
,
287 language_state_
.original_language());
289 report_error_url
= translate::AddHostLocaleToUrl(report_error_url
);
290 report_error_url
= translate::AddApiKeyToUrl(report_error_url
);
292 translate_client_
->ShowReportLanguageDetectionErrorUI(report_error_url
);
295 void TranslateManager::DoTranslatePage(const std::string
& translate_script
,
296 const std::string
& source_lang
,
297 const std::string
& target_lang
) {
298 language_state_
.set_translation_pending(true);
299 translate_driver_
->TranslatePage(
300 page_seq_no_
, translate_script
, source_lang
, target_lang
);
303 void TranslateManager::PageTranslated(const std::string
& source_lang
,
304 const std::string
& target_lang
,
305 TranslateErrors::Type error_type
) {
306 language_state_
.SetCurrentLanguage(target_lang
);
307 language_state_
.set_translation_pending(false);
309 if ((error_type
== TranslateErrors::NONE
) &&
310 source_lang
!= translate::kUnknownLanguageCode
&&
311 !TranslateDownloadManager::IsSupportedLanguage(source_lang
)) {
312 error_type
= TranslateErrors::UNSUPPORTED_LANGUAGE
;
315 translate_client_
->ShowTranslateUI(translate::TRANSLATE_STEP_AFTER_TRANSLATE
,
321 if (error_type
!= TranslateErrors::NONE
&&
322 !translate_driver_
->IsOffTheRecord()) {
323 TranslateErrorDetails error_details
;
324 error_details
.time
= base::Time::Now();
325 error_details
.url
= translate_driver_
->GetLastCommittedURL();
326 error_details
.error
= error_type
;
327 NotifyTranslateError(error_details
);
331 void TranslateManager::OnTranslateScriptFetchComplete(
332 const std::string
& source_lang
,
333 const std::string
& target_lang
,
335 const std::string
& data
) {
336 if (!translate_driver_
->HasCurrentPage())
340 // Translate the page.
341 TranslateScript
* translate_script
=
342 TranslateDownloadManager::GetInstance()->script();
343 DCHECK(translate_script
);
344 DoTranslatePage(translate_script
->data(), source_lang
, target_lang
);
346 translate_client_
->ShowTranslateUI(
347 translate::TRANSLATE_STEP_TRANSLATE_ERROR
,
350 TranslateErrors::NETWORK
,
352 if (!translate_driver_
->IsOffTheRecord()) {
353 TranslateErrorDetails error_details
;
354 error_details
.time
= base::Time::Now();
355 error_details
.url
= translate_driver_
->GetLastCommittedURL();
356 error_details
.error
= TranslateErrors::NETWORK
;
357 NotifyTranslateError(error_details
);
363 std::string
TranslateManager::GetTargetLanguage(
364 const std::vector
<std::string
>& accept_languages_list
) {
365 std::string ui_lang
= TranslateDownloadManager::GetLanguageCode(
366 TranslateDownloadManager::GetInstance()->application_locale());
367 translate::ToTranslateLanguageSynonym(&ui_lang
);
369 if (TranslateDownloadManager::IsSupportedLanguage(ui_lang
))
372 // Will translate to the first supported language on the Accepted Language
373 // list or not at all if no such candidate exists
374 std::vector
<std::string
>::const_iterator iter
;
375 for (iter
= accept_languages_list
.begin();
376 iter
!= accept_languages_list
.end(); ++iter
) {
377 std::string lang_code
= TranslateDownloadManager::GetLanguageCode(*iter
);
378 if (TranslateDownloadManager::IsSupportedLanguage(lang_code
))
381 return std::string();
385 std::string
TranslateManager::GetAutoTargetLanguage(
386 const std::string
& original_language
,
387 TranslatePrefs
* translate_prefs
) {
388 std::string auto_target_lang
;
389 if (translate_prefs
->ShouldAutoTranslate(original_language
,
390 &auto_target_lang
)) {
391 // We need to confirm that the saved target language is still supported.
392 // Also, GetLanguageCode will take care of removing country code if any.
394 TranslateDownloadManager::GetLanguageCode(auto_target_lang
);
395 if (TranslateDownloadManager::IsSupportedLanguage(auto_target_lang
))
396 return auto_target_lang
;
398 return std::string();
401 LanguageState
& TranslateManager::GetLanguageState() {
402 return language_state_
;
405 bool TranslateManager::ignore_missing_key_for_testing_
= false;
408 void TranslateManager::SetIgnoreMissingKeyForTesting(bool ignore
) {
409 ignore_missing_key_for_testing_
= ignore
;
412 } // namespace translate