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 <map>
8 #include <memory>
9 #include <tuple>
10 #include <utility>
11
12 #include "base/bind.h"
13 #include "base/command_line.h"
14 #include "base/metrics/field_trial.h"
15 #include "base/metrics/field_trial_params.h"
16 #include "base/metrics/histogram.h"
17 #include "base/metrics/histogram_macros.h"
18 #include "base/stl_util.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/string_piece.h"
21 #include "base/strings/string_split.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/time/time.h"
24 #include "build/build_config.h"
25 #include "components/language/core/browser/language_model.h"
26 #include "components/language/core/common/language_experiments.h"
27 #include "components/language/core/common/language_util.h"
28 #include "components/language/core/common/locale_util.h"
29 #include "components/prefs/pref_service.h"
30 #include "components/translate/core/browser/language_state.h"
31 #include "components/translate/core/browser/page_translated_details.h"
32 #include "components/translate/core/browser/translate_accept_languages.h"
33 #include "components/translate/core/browser/translate_browser_metrics.h"
34 #include "components/translate/core/browser/translate_client.h"
35 #include "components/translate/core/browser/translate_download_manager.h"
36 #include "components/translate/core/browser/translate_driver.h"
37 #include "components/translate/core/browser/translate_error_details.h"
38 #include "components/translate/core/browser/translate_init_details.h"
39 #include "components/translate/core/browser/translate_language_list.h"
40 #include "components/translate/core/browser/translate_metrics_logger.h"
41 #include "components/translate/core/browser/translate_metrics_logger_impl.h"
42 #include "components/translate/core/browser/translate_prefs.h"
43 #include "components/translate/core/browser/translate_ranker.h"
44 #include "components/translate/core/browser/translate_script.h"
45 #include "components/translate/core/browser/translate_trigger_decision.h"
46 #include "components/translate/core/browser/translate_url_util.h"
47 #include "components/translate/core/common/language_detection_details.h"
48 #include "components/translate/core/common/translate_constants.h"
49 #include "components/translate/core/common/translate_switches.h"
50 #include "components/variations/variations_associated_data.h"
51 #include "google_apis/google_api_keys.h"
52 #include "net/base/network_change_notifier.h"
53 #include "net/base/url_util.h"
54 #include "net/http/http_status_code.h"
55 #include "third_party/metrics_proto/translate_event.pb.h"
56
57 namespace translate {
58
59 namespace {
60
61 // Callbacks for translate errors.
62 TranslateManager::TranslateErrorCallbackList* g_error_callback_list_ = nullptr;
63
64 // Callbacks for translate initializations.
65 TranslateManager::TranslateInitCallbackList* g_init_callback_list_ = nullptr;
66
67 const char kReportLanguageDetectionErrorURL[] =
68 "https://translate.google.com/translate_error?client=cr&action=langidc";
69
70 // Used in kReportLanguageDetectionErrorURL to specify the original page
71 // language.
72 const char kSourceLanguageQueryName[] = "sl";
73
74 // Used in kReportLanguageDetectionErrorURL to specify the page URL.
75 const char kUrlQueryName[] = "u";
76
GetSkippedLanguagesForExperiments(std::string source_lang,translate::TranslatePrefs * translate_prefs)77 std::set<std::string> GetSkippedLanguagesForExperiments(
78 std::string source_lang,
79 translate::TranslatePrefs* translate_prefs) {
80 // Under this experiment, skip english as the target language if possible so
81 // that Translate triggers on English pages.
82 std::set<std::string> skipped_languages;
83 if (language::ShouldForceTriggerTranslateOnEnglishPages(
84 translate_prefs->GetForceTriggerOnEnglishPagesCount()) &&
85 source_lang == "en") {
86 skipped_languages.insert("en");
87 }
88 return skipped_languages;
89 }
90
91 // Moves any element in |languages| for which |lang_code| is found in
92 // |skipped_languages| to the end of |languages|. Otherwise preserves relative
93 // ordering of elements. Modifies |languages| in place.
MoveSkippedLanguagesToEndIfNecessary(std::vector<std::string> * languages,const std::set<std::string> & skipped_languages)94 void MoveSkippedLanguagesToEndIfNecessary(
95 std::vector<std::string>* languages,
96 const std::set<std::string>& skipped_languages) {
97 if (!skipped_languages.empty()) {
98 std::stable_partition(
99 languages->begin(), languages->end(), [&](const auto& lang) {
100 return skipped_languages.find(lang) == skipped_languages.end();
101 });
102 }
103 }
104
105 } // namespace
106
107 const base::Feature kOverrideLanguagePrefsForHrefTranslate{
108 "OverrideLanguagePrefsForHrefTranslate", base::FEATURE_ENABLED_BY_DEFAULT};
109
110 const base::Feature kOverrideSitePrefsForHrefTranslate{
111 "OverrideSitePrefsForHrefTranslate", base::FEATURE_DISABLED_BY_DEFAULT};
112
113 const char kForceAutoTranslateKey[] = "force-auto-translate";
114
115 TranslateManager::~TranslateManager() = default;
116
117 // static
118 std::unique_ptr<TranslateManager::TranslateErrorCallbackList::Subscription>
RegisterTranslateErrorCallback(const TranslateManager::TranslateErrorCallback & callback)119 TranslateManager::RegisterTranslateErrorCallback(
120 const TranslateManager::TranslateErrorCallback& callback) {
121 if (!g_error_callback_list_)
122 g_error_callback_list_ = new TranslateErrorCallbackList;
123 return g_error_callback_list_->Add(callback);
124 }
125
126 // static
127 std::unique_ptr<TranslateManager::TranslateInitCallbackList::Subscription>
RegisterTranslateInitCallback(const TranslateManager::TranslateInitCallback & callback)128 TranslateManager::RegisterTranslateInitCallback(
129 const TranslateManager::TranslateInitCallback& callback) {
130 if (!g_init_callback_list_)
131 g_init_callback_list_ = new TranslateInitCallbackList;
132 return g_init_callback_list_->Add(callback);
133 }
134
TranslateManager(TranslateClient * translate_client,TranslateRanker * translate_ranker,language::LanguageModel * language_model)135 TranslateManager::TranslateManager(TranslateClient* translate_client,
136 TranslateRanker* translate_ranker,
137 language::LanguageModel* language_model)
138 : page_seq_no_(0),
139 translate_client_(translate_client),
140 translate_driver_(translate_client_->GetTranslateDriver()),
141 translate_ranker_(translate_ranker),
142 language_model_(language_model),
143 null_translate_metrics_logger_(
144 std::make_unique<NullTranslateMetricsLogger>()),
145 language_state_(translate_driver_),
146 translate_event_(std::make_unique<metrics::TranslateEventProto>()) {}
147
GetWeakPtr()148 base::WeakPtr<TranslateManager> TranslateManager::GetWeakPtr() {
149 return weak_method_factory_.GetWeakPtr();
150 }
151
InitiateTranslation(const std::string & page_lang)152 void TranslateManager::InitiateTranslation(const std::string& page_lang) {
153 std::unique_ptr<TranslatePrefs> translate_prefs(
154 translate_client_->GetTranslatePrefs());
155 std::string page_language_code =
156 TranslateDownloadManager::GetLanguageCode(page_lang);
157 const std::set<std::string>& skipped_languages =
158 GetSkippedLanguagesForExperiments(page_language_code,
159 translate_prefs.get());
160 std::string target_lang = GetTargetLanguage(
161 translate_prefs.get(), language_model_, skipped_languages);
162
163 // TODO(crbug.com/924980): The ranker event shouldn't be a global on this
164 // object. It should instead be passed around to code that uses it.
165 InitTranslateEvent(page_language_code, target_lang, *translate_prefs);
166
167 const TranslateTriggerDecision& decision = ComputePossibleOutcomes(
168 translate_prefs.get(), page_language_code, target_lang);
169
170 MaybeShowOmniboxIcon(decision);
171 bool ui_shown = MaterializeDecision(decision, translate_prefs.get(),
172 page_language_code, target_lang);
173
174 NotifyTranslateInit(page_language_code, target_lang, decision, ui_shown);
175
176 RecordDecisionMetrics(decision, page_language_code, ui_shown);
177 RecordDecisionRankerEvent(decision, translate_prefs.get(), page_language_code,
178 target_lang);
179
180 // Mark the current state as the initial state now that we are done
181 // initializing Translate.
182 GetActiveTranslateMetricsLogger()->LogInitialState();
183 }
184
OnAutofillAssistantFinished()185 void TranslateManager::OnAutofillAssistantFinished() {
186 if (!page_language_code_.empty()) {
187 InitiateTranslation(page_language_code_);
188 }
189 }
190
191 // static
GetManualTargetLanguage(const std::string & source_code,const LanguageState & language_state,translate::TranslatePrefs * prefs,language::LanguageModel * language_model)192 std::string TranslateManager::GetManualTargetLanguage(
193 const std::string& source_code,
194 const LanguageState& language_state,
195 translate::TranslatePrefs* prefs,
196 language::LanguageModel* language_model) {
197 if (language_state.IsPageTranslated()) {
198 return language_state.current_language();
199 } else {
200 const std::set<std::string>& skipped_languages =
201 GetSkippedLanguagesForExperiments(source_code, prefs);
202 return GetTargetLanguage(prefs, language_model, skipped_languages);
203 }
204 }
205
CanManuallyTranslate(bool menuLogging)206 bool TranslateManager::CanManuallyTranslate(bool menuLogging) {
207 bool can_translate = true;
208
209 if (!base::FeatureList::IsEnabled(translate::kTranslate)) {
210 if (!menuLogging)
211 return false;
212 TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
213 TranslateBrowserMetrics::MenuTranslationUnavailableReason::
214 kTranslateDisabled);
215 can_translate = false;
216 }
217
218 if (net::NetworkChangeNotifier::IsOffline()) {
219 if (!menuLogging)
220 return false;
221 TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
222 TranslateBrowserMetrics::MenuTranslationUnavailableReason::
223 kNetworkOffline);
224 can_translate = false;
225 }
226
227 if (!ignore_missing_key_for_testing_ &&
228 !::google_apis::HasAPIKeyConfigured()) {
229 if (!menuLogging)
230 return false;
231 TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
232 TranslateBrowserMetrics::MenuTranslationUnavailableReason::
233 kApiKeysMissing);
234 can_translate = false;
235 }
236
237 // MHTML pages currently cannot be translated (crbug.com/217945).
238 if (translate_driver_->GetContentsMimeType() == "multipart/related") {
239 if (!menuLogging)
240 return false;
241 TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
242 TranslateBrowserMetrics::MenuTranslationUnavailableReason::kMHTMLPage);
243 can_translate = false;
244 }
245
246 if (!translate_client_->IsTranslatableURL(
247 translate_driver_->GetVisibleURL())) {
248 if (!menuLogging)
249 return false;
250 TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
251 TranslateBrowserMetrics::MenuTranslationUnavailableReason::
252 kURLNotTranslatable);
253 can_translate = false;
254 }
255
256 const std::string source_language = language_state_.original_language();
257 if (source_language.empty()) {
258 if (!menuLogging)
259 return false;
260 TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
261 TranslateBrowserMetrics::MenuTranslationUnavailableReason::
262 kSourceLangUnknown);
263 can_translate = false;
264 }
265 // Translation of unknown source language pages is supported on desktop
266 // platforms, but not mobile.
267 #if defined(OS_ANDROID) || defined(OS_IOS)
268 if (source_language == translate::kUnknownLanguageCode) {
269 if (!menuLogging)
270 return false;
271 TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
272 TranslateBrowserMetrics::MenuTranslationUnavailableReason::
273 kSourceLangUnknown);
274 can_translate = false;
275 }
276 #endif
277
278 std::unique_ptr<TranslatePrefs> translate_prefs(
279 translate_client_->GetTranslatePrefs());
280 if (!translate_prefs->IsTranslateAllowedByPolicy()) {
281 if (!menuLogging)
282 return false;
283 TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
284 TranslateBrowserMetrics::MenuTranslationUnavailableReason::
285 kNotAllowedByPolicy);
286 can_translate = false;
287 }
288
289 const std::string target_lang = GetManualTargetLanguage(
290 TranslateDownloadManager::GetLanguageCode(source_language),
291 language_state_, translate_prefs.get(), language_model_);
292 if (target_lang.empty()) {
293 if (!menuLogging)
294 return false;
295 TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
296 TranslateBrowserMetrics::MenuTranslationUnavailableReason::
297 kTargetLangUnknown);
298 can_translate = false;
299 }
300
301 if (menuLogging)
302 UMA_HISTOGRAM_BOOLEAN("Translate.MenuTranslation.IsAvailable",
303 can_translate);
304
305 return can_translate;
306 }
307
InitiateManualTranslation(bool auto_translate,bool triggered_from_menu)308 void TranslateManager::InitiateManualTranslation(bool auto_translate,
309 bool triggered_from_menu) {
310 // If a translation has already been triggered, do nothing.
311 if (language_state_.IsPageTranslated() ||
312 language_state_.translation_pending())
313 return;
314
315 std::unique_ptr<TranslatePrefs> translate_prefs(
316 translate_client_->GetTranslatePrefs());
317 const std::string source_code = TranslateDownloadManager::GetLanguageCode(
318 language_state_.original_language());
319 const std::string target_lang = GetManualTargetLanguage(
320 source_code, language_state_, translate_prefs.get(), language_model_);
321
322 language_state_.SetTranslateEnabled(true);
323
324 // Translate the page if it has not been translated and manual translate
325 // should trigger translation automatically. Otherwise, only show the infobar.
326 if (auto_translate) {
327 TranslatePage(source_code, target_lang, triggered_from_menu);
328 return;
329 }
330
331 const translate::TranslateStep step =
332 language_state_.IsPageTranslated()
333 ? translate::TRANSLATE_STEP_AFTER_TRANSLATE
334 : translate::TRANSLATE_STEP_BEFORE_TRANSLATE;
335 translate_client_->ShowTranslateUI(step, source_code, target_lang,
336 TranslateErrors::NONE,
337 true /* triggered_by_menu */);
338 }
339
TranslatePage(const std::string & original_source_lang,const std::string & target_lang,bool triggered_from_menu)340 void TranslateManager::TranslatePage(const std::string& original_source_lang,
341 const std::string& target_lang,
342 bool triggered_from_menu) {
343 if (!translate_driver_->HasCurrentPage()) {
344 NOTREACHED();
345 return;
346 }
347
348 // Log the source and target languages of the translate request.
349 TranslateBrowserMetrics::ReportTranslateSourceLanguage(original_source_lang);
350 TranslateBrowserMetrics::ReportTranslateTargetLanguage(target_lang);
351
352 // If the source language matches the UI language, it means the translation
353 // prompt is being forced by an experiment. Report this so the count of how
354 // often it happens can be decremented (meaning the user didn't decline or
355 // ignore the prompt).
356 if (original_source_lang ==
357 TranslateDownloadManager::GetLanguageCode(
358 TranslateDownloadManager::GetInstance()->application_locale())) {
359 translate_client_->GetTranslatePrefs()
360 ->ReportAcceptedAfterForceTriggerOnEnglishPages();
361 }
362
363 // If the target language isn't in the chrome://settings/languages list, add
364 // it there. This way, it's obvious to the user that Chrome is remembering
365 // their choice, they can remove it from the list, and they'll send that
366 // language in the Accept-Language header, giving servers a chance to serve
367 // them pages in that language.
368 AddTargetLanguageToAcceptLanguages(target_lang);
369
370 // Translation can be kicked by context menu against unsupported languages.
371 // Unsupported language strings should be replaced with
372 // kUnknownLanguageCode in order to send a translation request with enabling
373 // server side auto language detection.
374 std::string source_lang(original_source_lang);
375 if (!TranslateDownloadManager::IsSupportedLanguage(source_lang))
376 source_lang = std::string(translate::kUnknownLanguageCode);
377
378 // Capture the translate event if we were triggered from the menu.
379 if (triggered_from_menu) {
380 RecordTranslateEvent(
381 metrics::TranslateEventProto::USER_CONTEXT_MENU_TRANSLATE);
382 }
383
384 if (source_lang == target_lang) {
385 // If the languages are the same, try the translation using the unknown
386 // language code on Desktop. Android and iOS don't support unknown source
387 // language, so this silently falls back to 'auto' when making the
388 // translation request. The source and target languages should only be equal
389 // if the translation was manually triggered by the user. Rather than show
390 // them the error, we should attempt to send the page for translation. For
391 // page with multiple languages we often detect same language, but the
392 // Translation service is able to translate the various languages using it's
393 // own language detection.
394 #if !defined(OS_ANDROID) && !defined(OS_IOS)
395 source_lang = translate::kUnknownLanguageCode;
396 #endif
397 TranslateBrowserMetrics::ReportInitiationStatus(
398 TranslateBrowserMetrics::
399 INITIATION_STATUS_IDENTICAL_LANGUAGE_USE_SOURCE_LANGUAGE_UNKNOWN);
400 }
401
402 // Trigger the "translating now" UI.
403 translate_client_->ShowTranslateUI(
404 translate::TRANSLATE_STEP_TRANSLATING, source_lang, target_lang,
405 TranslateErrors::NONE, triggered_from_menu);
406
407 GetActiveTranslateMetricsLogger()->LogTranslationStarted();
408
409 TranslateScript* script = TranslateDownloadManager::GetInstance()->script();
410 DCHECK(script != nullptr);
411
412 const std::string& script_data = script->data();
413 if (!script_data.empty()) {
414 DoTranslatePage(script_data, source_lang, target_lang);
415 return;
416 }
417
418 // The script is not available yet. Queue that request and query for the
419 // script. Once it is downloaded we'll do the translate.
420 TranslateScript::RequestCallback callback =
421 base::BindOnce(&TranslateManager::OnTranslateScriptFetchComplete,
422 GetWeakPtr(), source_lang, target_lang);
423
424 script->Request(std::move(callback), translate_driver_->IsIncognito());
425 }
426
RevertTranslation()427 void TranslateManager::RevertTranslation() {
428 // Capture the revert event in the translate metrics
429 RecordTranslateEvent(metrics::TranslateEventProto::USER_REVERT);
430
431 // Revert the translation.
432 translate_driver_->RevertTranslation(page_seq_no_);
433 language_state_.SetCurrentLanguage(language_state_.original_language());
434
435 GetActiveTranslateMetricsLogger()->LogReversion();
436 }
437
ReportLanguageDetectionError()438 void TranslateManager::ReportLanguageDetectionError() {
439 TranslateBrowserMetrics::ReportLanguageDetectionError();
440
441 GURL report_error_url = GURL(kReportLanguageDetectionErrorURL);
442
443 report_error_url = net::AppendQueryParameter(
444 report_error_url, kUrlQueryName,
445 translate_driver_->GetLastCommittedURL().spec());
446
447 report_error_url =
448 net::AppendQueryParameter(report_error_url, kSourceLanguageQueryName,
449 language_state_.original_language());
450
451 report_error_url = translate::AddHostLocaleToUrl(report_error_url);
452 report_error_url = translate::AddApiKeyToUrl(report_error_url);
453
454 translate_client_->ShowReportLanguageDetectionErrorUI(report_error_url);
455 }
456
DoTranslatePage(const std::string & translate_script,const std::string & source_lang,const std::string & target_lang)457 void TranslateManager::DoTranslatePage(const std::string& translate_script,
458 const std::string& source_lang,
459 const std::string& target_lang) {
460 language_state_.set_translation_pending(true);
461 translate_driver_->TranslatePage(page_seq_no_, translate_script, source_lang,
462 target_lang);
463 }
464
465 // Notifies |g_error_callback_list_| of translate errors.
NotifyTranslateError(TranslateErrors::Type error_type)466 void TranslateManager::NotifyTranslateError(TranslateErrors::Type error_type) {
467 if (!g_error_callback_list_ || error_type == TranslateErrors::NONE ||
468 translate_driver_->IsIncognito()) {
469 return;
470 }
471
472 TranslateErrorDetails error_details;
473 error_details.time = base::Time::Now();
474 error_details.url = translate_driver_->GetLastCommittedURL();
475 error_details.error = error_type;
476 g_error_callback_list_->Notify(error_details);
477 }
478
NotifyTranslateInit(std::string page_language_code,std::string target_lang,TranslateTriggerDecision decision,bool ui_shown)479 void TranslateManager::NotifyTranslateInit(std::string page_language_code,
480 std::string target_lang,
481 TranslateTriggerDecision decision,
482 bool ui_shown) {
483 if (!g_init_callback_list_ || translate_driver_->IsIncognito())
484 return;
485
486 TranslateInitDetails details;
487 details.time = base::Time::Now();
488 details.url = translate_driver_->GetLastCommittedURL();
489 details.page_language_code = page_language_code;
490 details.target_lang = target_lang;
491 details.decision = decision;
492 details.ui_shown = ui_shown;
493
494 g_init_callback_list_->Notify(details);
495 }
496
PageTranslated(const std::string & source_lang,const std::string & target_lang,TranslateErrors::Type error_type)497 void TranslateManager::PageTranslated(const std::string& source_lang,
498 const std::string& target_lang,
499 TranslateErrors::Type error_type) {
500 if (error_type == TranslateErrors::NONE) {
501 // The user could have updated the source language before translating, so
502 // update the language state with both original and current.
503 language_state_.SetOriginalLanguage(source_lang);
504 language_state_.SetCurrentLanguage(target_lang);
505 }
506
507 language_state_.set_translation_pending(false);
508 language_state_.set_translation_error(error_type != TranslateErrors::NONE);
509
510 if ((error_type == TranslateErrors::NONE) &&
511 source_lang != translate::kUnknownLanguageCode &&
512 !TranslateDownloadManager::IsSupportedLanguage(source_lang)) {
513 error_type = TranslateErrors::UNSUPPORTED_LANGUAGE;
514 }
515
516 // Currently we only want to log any error happens during the translation
517 // script initialization phase such as translation script failed because of
518 // CSP issues (crbug.com/738277).
519 // Note: NotifyTranslateError and ShowTranslateUI will not log the errors.
520 if (error_type == TranslateErrors::INITIALIZATION_ERROR)
521 RecordTranslateEvent(metrics::TranslateEventProto::INITIALIZATION_ERROR);
522 translate_client_->ShowTranslateUI(translate::TRANSLATE_STEP_AFTER_TRANSLATE,
523 source_lang, target_lang, error_type,
524 false);
525 NotifyTranslateError(error_type);
526
527 GetActiveTranslateMetricsLogger()->LogTranslationFinished(
528 error_type == TranslateErrors::NONE);
529 }
530
OnTranslateScriptFetchComplete(const std::string & source_lang,const std::string & target_lang,bool success,const std::string & data)531 void TranslateManager::OnTranslateScriptFetchComplete(
532 const std::string& source_lang,
533 const std::string& target_lang,
534 bool success,
535 const std::string& data) {
536 if (!translate_driver_->HasCurrentPage())
537 return;
538
539 if (success) {
540 // Translate the page.
541 TranslateScript* translate_script =
542 TranslateDownloadManager::GetInstance()->script();
543 DCHECK(translate_script);
544 DoTranslatePage(translate_script->data(), source_lang, target_lang);
545 } else {
546 translate_client_->ShowTranslateUI(
547 translate::TRANSLATE_STEP_TRANSLATE_ERROR, source_lang, target_lang,
548 TranslateErrors::NETWORK, false);
549 NotifyTranslateError(TranslateErrors::NETWORK);
550 GetActiveTranslateMetricsLogger()->LogTranslationFinished(false);
551 }
552 }
553
554 // static
GetTargetLanguage(const TranslatePrefs * prefs,language::LanguageModel * language_model,const std::set<std::string> & skipped_languages)555 std::string TranslateManager::GetTargetLanguage(
556 const TranslatePrefs* prefs,
557 language::LanguageModel* language_model,
558 const std::set<std::string>& skipped_languages) {
559 DCHECK(prefs);
560 const std::string& recent_target = prefs->GetRecentTargetLanguage();
561
562 // If we've recorded the most recent target language, use that.
563 if (base::FeatureList::IsEnabled(kTranslateRecentTarget) &&
564 !recent_target.empty()) {
565 TranslateBrowserMetrics::ReportTranslateTargetLanguageOrigin(
566 TranslateBrowserMetrics::TargetLanguageOrigin::kRecentTarget);
567 return recent_target;
568 }
569
570 if (language_model) {
571 std::vector<std::string> language_codes;
572 for (const auto& lang : language_model->GetLanguages()) {
573 std::string lang_code =
574 TranslateDownloadManager::GetLanguageCode(lang.lang_code);
575 language::ToTranslateLanguageSynonym(&lang_code);
576 if (TranslateDownloadManager::IsSupportedLanguage(lang_code))
577 language_codes.push_back(lang_code);
578 }
579 // If some languages need to be skipped, move them to the end of the
580 // language vector so that any other eligible language takes priority.
581 MoveSkippedLanguagesToEndIfNecessary(&language_codes, skipped_languages);
582
583 // Use the first language from the model that translate supports.
584 if (!language_codes.empty()) {
585 TranslateBrowserMetrics::ReportTranslateTargetLanguageOrigin(
586 TranslateBrowserMetrics::TargetLanguageOrigin::kLanguageModel);
587 return language_codes[0];
588 }
589 }
590
591 // Get the browser's user interface language.
592 std::string language = TranslateDownloadManager::GetLanguageCode(
593 TranslateDownloadManager::GetInstance()->application_locale());
594 // Map 'he', 'nb', 'fil' back to 'iw', 'no', 'tl'
595 language::ToTranslateLanguageSynonym(&language);
596 if (TranslateDownloadManager::IsSupportedLanguage(language)) {
597 TranslateBrowserMetrics::ReportTranslateTargetLanguageOrigin(
598 TranslateBrowserMetrics::TargetLanguageOrigin::kApplicationUI);
599 return language;
600 }
601
602 // Will translate to the first supported language on the Accepted Language
603 // list or not at all if no such candidate exists.
604 std::vector<std::string> accept_languages_list;
605 prefs->GetLanguageList(&accept_languages_list);
606 for (const auto& lang : accept_languages_list) {
607 std::string lang_code = TranslateDownloadManager::GetLanguageCode(lang);
608 if (TranslateDownloadManager::IsSupportedLanguage(lang_code)) {
609 TranslateBrowserMetrics::ReportTranslateTargetLanguageOrigin(
610 TranslateBrowserMetrics::TargetLanguageOrigin::kAcceptLanguages);
611 return lang_code;
612 }
613 }
614
615 // If there isn't a target language determined by the above logic, default to
616 // English. Otherwise the user can get stuck not being able to translate. See
617 // https://crbug.com/1041387.
618 TranslateBrowserMetrics::ReportTranslateTargetLanguageOrigin(
619 TranslateBrowserMetrics::TargetLanguageOrigin::kDefaultEnglish);
620 return std::string("en");
621 }
622
623 // static
GetTargetLanguage(const TranslatePrefs * prefs,language::LanguageModel * language_model)624 std::string TranslateManager::GetTargetLanguage(
625 const TranslatePrefs* prefs,
626 language::LanguageModel* language_model) {
627 return GetTargetLanguage(prefs, language_model, {});
628 }
629
630 // static
GetAutoTargetLanguage(const std::string & original_language,TranslatePrefs * translate_prefs)631 std::string TranslateManager::GetAutoTargetLanguage(
632 const std::string& original_language,
633 TranslatePrefs* translate_prefs) {
634 std::string auto_target_lang;
635 if (translate_prefs->ShouldAutoTranslate(original_language,
636 &auto_target_lang)) {
637 // We need to confirm that the saved target language is still supported.
638 // Also, GetLanguageCode will take care of removing country code if any.
639 auto_target_lang =
640 TranslateDownloadManager::GetLanguageCode(auto_target_lang);
641 if (TranslateDownloadManager::IsSupportedLanguage(auto_target_lang))
642 return auto_target_lang;
643 }
644 return std::string();
645 }
646
GetLanguageState()647 LanguageState* TranslateManager::GetLanguageState() {
648 return &language_state_;
649 }
650
651 bool TranslateManager::ignore_missing_key_for_testing_ = false;
652
653 // static
SetIgnoreMissingKeyForTesting(bool ignore)654 void TranslateManager::SetIgnoreMissingKeyForTesting(bool ignore) {
655 ignore_missing_key_for_testing_ = ignore;
656 }
657
658 // static
IsAvailable(const TranslatePrefs * prefs)659 bool TranslateManager::IsAvailable(const TranslatePrefs* prefs) {
660 // These conditions mirror the conditions in InitiateTranslation.
661 return base::FeatureList::IsEnabled(translate::kTranslate) &&
662 (ignore_missing_key_for_testing_ ||
663 ::google_apis::HasAPIKeyConfigured()) &&
664 prefs->IsOfferTranslateEnabled();
665 }
666
InitTranslateEvent(const std::string & src_lang,const std::string & dst_lang,const TranslatePrefs & prefs)667 void TranslateManager::InitTranslateEvent(const std::string& src_lang,
668 const std::string& dst_lang,
669 const TranslatePrefs& prefs) {
670 translate_event_->Clear();
671 translate_event_->set_source_language(src_lang);
672 translate_event_->set_target_language(dst_lang);
673 translate_event_->set_country(prefs.GetCountry());
674 translate_event_->set_accept_count(
675 prefs.GetTranslationAcceptedCount(src_lang));
676 translate_event_->set_decline_count(
677 prefs.GetTranslationDeniedCount(src_lang));
678 translate_event_->set_ignore_count(
679 prefs.GetTranslationIgnoredCount(src_lang));
680 translate_event_->set_ranker_response(
681 metrics::TranslateEventProto::NOT_QUERIED);
682 translate_event_->set_event_type(metrics::TranslateEventProto::UNKNOWN);
683 // TODO(rogerm): Populate the language list.
684 }
685
RecordTranslateEvent(int event_type)686 void TranslateManager::RecordTranslateEvent(int event_type) {
687 translate_ranker_->RecordTranslateEvent(
688 event_type, translate_driver_->GetUkmSourceId(), translate_event_.get());
689 }
690
ShouldOverrideDecision(int event_type)691 bool TranslateManager::ShouldOverrideDecision(int event_type) {
692 return translate_ranker_->ShouldOverrideDecision(
693 event_type, translate_driver_->GetUkmSourceId(), translate_event_.get());
694 }
695
ShouldSuppressBubbleUI(bool triggered_from_menu,const std::string & source_language)696 bool TranslateManager::ShouldSuppressBubbleUI(
697 bool triggered_from_menu,
698 const std::string& source_language) {
699 // Suppress the UI if the user navigates to a page with
700 // the same language as the previous page. In the new UI,
701 // continue offering translation after the user navigates
702 // to another page.
703 if (!language_state_.HasLanguageChanged() &&
704 !ShouldOverrideDecision(
705 metrics::TranslateEventProto::MATCHES_PREVIOUS_LANGUAGE)) {
706 TranslateBrowserMetrics::ReportInitiationStatus(
707 TranslateBrowserMetrics::
708 INITIATION_STATUS_ABORTED_BY_MATCHES_PREVIOUS_LANGUAGE);
709 return true;
710 }
711
712 // Suppress the UI if the user denied translation for this language
713 // too often.
714 if (!triggered_from_menu &&
715 translate_client_->GetTranslatePrefs()->IsTooOftenDenied(
716 source_language) &&
717 !ShouldOverrideDecision(
718 metrics::TranslateEventProto::LANGUAGE_DISABLED_BY_AUTO_BLACKLIST)) {
719 TranslateBrowserMetrics::ReportInitiationStatus(
720 TranslateBrowserMetrics::INITIATION_STATUS_ABORTED_BY_TOO_OFTEN_DENIED);
721 return true;
722 }
723
724 return false;
725 }
726
AddTargetLanguageToAcceptLanguages(const std::string & target_language_code)727 void TranslateManager::AddTargetLanguageToAcceptLanguages(
728 const std::string& target_language_code) {
729 auto prefs = translate_client_->GetTranslatePrefs();
730 std::vector<std::string> languages;
731 prefs->GetLanguageList(&languages);
732
733 base::StringPiece target_language, tail;
734 // |target_language_code| should satisfy BCP47 and consist of a language code
735 // and an optional region code joined by an hyphen.
736 std::tie(target_language, tail) =
737 language::SplitIntoMainAndTail(target_language_code);
738
739 // Don't add the target language if it's redundant with another already in the
740 // list.
741 if (tail.empty()) {
742 for (const auto& language : languages) {
743 if (language::ExtractBaseLanguage(language) == target_language)
744 return;
745 }
746 } else {
747 for (const auto& language : languages) {
748 if (language == target_language_code)
749 return;
750 }
751 }
752
753 // Only add the target language if it's not an automatic target (such as when
754 // translation happens because of an hrefTranslate navigation).
755 if (language_state_.AutoTranslateTo() != target_language_code &&
756 language_state_.href_translate() != target_language_code) {
757 prefs->AddToLanguageList(target_language_code, /*force_blocked=*/false);
758 }
759 }
760
ComputePossibleOutcomes(TranslatePrefs * translate_prefs,const std::string & page_language_code,const std::string & target_lang)761 const TranslateTriggerDecision TranslateManager::ComputePossibleOutcomes(
762 TranslatePrefs* translate_prefs,
763 const std::string& page_language_code,
764 const std::string& target_lang) {
765 // This function looks at a bunch of signals and determines which of three
766 // outcomes should be selected:
767 // 1. Auto-translate the page
768 // 2. Show translate UI
769 // 3. Do nothing
770 // This is achieved by passing the |decision| object to the different Filter*
771 // functions, which will mark certain outcomes as undesirable. This |decision|
772 // object is then used to trigger the correct behavior, and finally record
773 // corresponding metrics in InitiateTranslation.
774 TranslateTriggerDecision decision;
775
776 FilterIsTranslatePossible(&decision, translate_prefs, page_language_code,
777 target_lang);
778
779 // Querying the ranker now, but not exiting immediately so that we may log
780 // other potential suppression reasons.
781 // Ignore Ranker's decision under triggering experiments since it wasn't
782 // trained appropriately under those scenarios.
783 if (!language::ShouldPreventRankerEnforcementInIndia(
784 translate_prefs->GetForceTriggerOnEnglishPagesCount()) &&
785 !translate_ranker_->ShouldOfferTranslation(
786 translate_event_.get(), GetActiveTranslateMetricsLogger())) {
787 decision.SuppressFromRanker();
788 }
789
790 FilterForUserPrefs(&decision, translate_prefs, page_language_code);
791
792 if (decision.should_suppress_from_ranker()) {
793 // Delay logging this until after FilterForUserPrefs because TriggerDecision
794 // values from FilterForUserPrefs have higher priority.
795 GetActiveTranslateMetricsLogger()->LogTriggerDecision(
796 TriggerDecision::kDisabledByRanker);
797 }
798
799 FilterAutoTranslate(&decision, translate_prefs, page_language_code);
800 FilterForHrefTranslate(&decision, translate_prefs, page_language_code);
801 FilterForPredefinedTarget(&decision, translate_prefs, page_language_code);
802
803 return decision;
804 }
805
FilterIsTranslatePossible(TranslateTriggerDecision * decision,TranslatePrefs * translate_prefs,const std::string & page_language_code,const std::string & target_lang)806 void TranslateManager::FilterIsTranslatePossible(
807 TranslateTriggerDecision* decision,
808 TranslatePrefs* translate_prefs,
809 const std::string& page_language_code,
810 const std::string& target_lang) {
811 // Short-circuit out if not in a state where initiating translation makes
812 // sense (this method may be called multiple times for a given page).
813 if (!language_state_.page_needs_translation() ||
814 language_state_.translation_pending() ||
815 language_state_.translation_declined() ||
816 language_state_.IsPageTranslated()) {
817 decision->PreventAllTriggering();
818 decision->initiation_statuses.push_back(
819 TranslateBrowserMetrics::INITIATION_STATUS_DOESNT_NEED_TRANSLATION);
820 GetActiveTranslateMetricsLogger()->LogTriggerDecision(
821 TriggerDecision::kDisabledDoesntNeedTranslation);
822 }
823
824 if (!base::FeatureList::IsEnabled(translate::kTranslate)) {
825 decision->PreventAllTriggering();
826 decision->initiation_statuses.push_back(
827 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_SWITCH);
828 GetActiveTranslateMetricsLogger()->LogTriggerDecision(
829 TriggerDecision::kDisabledTranslationFeatureDisabled);
830 }
831
832 // Also, skip if the connection is currently offline - initiation doesn't make
833 // sense there, either.
834 if (net::NetworkChangeNotifier::IsOffline()) {
835 decision->PreventAllTriggering();
836 decision->initiation_statuses.push_back(
837 TranslateBrowserMetrics::INITIATION_STATUS_NO_NETWORK);
838 GetActiveTranslateMetricsLogger()->LogTriggerDecision(
839 TriggerDecision::kDisabledOffline);
840 }
841
842 // Skip translation if autofill assistant is running.
843 if (translate_client_->IsAutofillAssistantRunning()) {
844 page_language_code_ = page_language_code;
845 decision->PreventAllTriggering();
846 decision->initiation_statuses.push_back(
847 TranslateBrowserMetrics::
848 INITIATION_STATUS_DISABLED_BY_AUTOFILL_ASSISTANT);
849 GetActiveTranslateMetricsLogger()
850 ->LogAutofillAssistantDeferredTriggerDecision();
851 }
852
853 if (!ignore_missing_key_for_testing_ &&
854 !::google_apis::HasAPIKeyConfigured()) {
855 // Without an API key, translate won't work, so don't offer to translate in
856 // the first place. Leave prefs::kOfferTranslateEnabled on, though, because
857 // that settings syncs and we don't want to turn off translate everywhere
858 // else.
859 decision->PreventAllTriggering();
860 decision->initiation_statuses.push_back(
861 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_KEY);
862 GetActiveTranslateMetricsLogger()->LogTriggerDecision(
863 TriggerDecision::kDisabledMissingAPIKey);
864 }
865
866 // MHTML pages currently cannot be translated.
867 // See bug: 217945.
868 if (translate_driver_->GetContentsMimeType() == "multipart/related") {
869 decision->PreventAllTriggering();
870 decision->initiation_statuses.push_back(
871 TranslateBrowserMetrics::INITIATION_STATUS_MIME_TYPE_IS_NOT_SUPPORTED);
872 GetActiveTranslateMetricsLogger()->LogTriggerDecision(
873 TriggerDecision::kDisabledMIMETypeNotSupported);
874 }
875
876 // Don't translate any Chrome specific page, e.g., New Tab Page, Download,
877 // History, and so on.
878 const GURL& page_url = translate_driver_->GetVisibleURL();
879 if (!translate_client_->IsTranslatableURL(page_url)) {
880 decision->PreventAllTriggering();
881 decision->initiation_statuses.push_back(
882 TranslateBrowserMetrics::INITIATION_STATUS_URL_IS_NOT_SUPPORTED);
883 GetActiveTranslateMetricsLogger()->LogTriggerDecision(
884 TriggerDecision::kDisabledURLNotSupported);
885 }
886
887 if (!translate_prefs->IsOfferTranslateEnabled()) {
888 decision->PreventAllTriggering();
889 decision->initiation_statuses.push_back(
890 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_PREFS);
891 decision->ranker_events.push_back(
892 metrics::TranslateEventProto::DISABLED_BY_PREF);
893 GetActiveTranslateMetricsLogger()->LogTriggerDecision(
894 TriggerDecision::kDisabledNeverOfferTranslations);
895 }
896
897 // Don't translate similar languages (ex: en-US to en).
898 if (page_language_code == target_lang) {
899 // This doesn't prevent *all* possible translate outcomes because some could
900 // use a different target language, making this condition only relevant to
901 // regular auto-translate/show UI.
902 decision->PreventAutoTranslate();
903 decision->PreventShowingUI();
904 decision->initiation_statuses.push_back(
905 TranslateBrowserMetrics::INITIATION_STATUS_SIMILAR_LANGUAGES);
906 GetActiveTranslateMetricsLogger()->LogTriggerDecision(
907 TriggerDecision::kDisabledSimilarLanguages);
908 }
909
910 // Nothing to do if either the language Chrome is in or the language of
911 // the page is not supported by the translation server.
912 if (target_lang.empty() ||
913 !TranslateDownloadManager::IsSupportedLanguage(page_language_code)) {
914 // This doesn't prevent *all* possible translate outcomes because some could
915 // use a different target language, making this condition only relevant to
916 // regular auto-translate/show UI.
917 decision->PreventAutoTranslate();
918 decision->PreventShowingUI();
919 decision->initiation_statuses.push_back(
920 TranslateBrowserMetrics::INITIATION_STATUS_LANGUAGE_IS_NOT_SUPPORTED);
921 decision->ranker_events.push_back(
922 metrics::TranslateEventProto::UNSUPPORTED_LANGUAGE);
923 GetActiveTranslateMetricsLogger()->LogTriggerDecision(
924 TriggerDecision::kDisabledUnsupportedLanguage);
925 }
926 }
927
FilterAutoTranslate(TranslateTriggerDecision * decision,TranslatePrefs * translate_prefs,const std::string & page_language_code)928 void TranslateManager::FilterAutoTranslate(
929 TranslateTriggerDecision* decision,
930 TranslatePrefs* translate_prefs,
931 const std::string& page_language_code) {
932 // Determine whether auto-translate is required, and if so for which target
933 // language.
934 std::string always_translate_target =
935 GetAutoTargetLanguage(page_language_code, translate_prefs);
936 std::string link_auto_translate_target = language_state_.AutoTranslateTo();
937 if (!translate_driver_->IsIncognito() && !always_translate_target.empty()) {
938 // If the user has previously selected "always translate" for this language
939 // we automatically translate. Note that in incognito mode we disable that
940 // feature; the user will get an infobar, so they can control whether the
941 // page's text is sent to the translate server.
942 decision->auto_translate_target = always_translate_target;
943 decision->initiation_statuses.push_back(
944 TranslateBrowserMetrics::INITIATION_STATUS_AUTO_BY_CONFIG);
945 decision->ranker_events.push_back(
946 metrics::TranslateEventProto::AUTO_TRANSLATION_BY_PREF);
947 GetActiveTranslateMetricsLogger()->LogTriggerDecision(
948 TriggerDecision::kAutomaticTranslationByPref);
949 } else if (!link_auto_translate_target.empty()) {
950 // This page was navigated through a click from a translated page.
951 decision->auto_translate_target = link_auto_translate_target;
952 decision->initiation_statuses.push_back(
953 TranslateBrowserMetrics::INITIATION_STATUS_AUTO_BY_LINK);
954 decision->ranker_events.push_back(
955 metrics::TranslateEventProto::AUTO_TRANSLATION_BY_LINK);
956 GetActiveTranslateMetricsLogger()->LogTriggerDecision(
957 TriggerDecision::kAutomaticTranslationByLink);
958 }
959
960 if (decision->auto_translate_target.empty()) {
961 decision->PreventAutoTranslate();
962 }
963 }
964
FilterForUserPrefs(TranslateTriggerDecision * decision,TranslatePrefs * translate_prefs,const std::string & page_language_code)965 void TranslateManager::FilterForUserPrefs(
966 TranslateTriggerDecision* decision,
967 TranslatePrefs* translate_prefs,
968 const std::string& page_language_code) {
969 TranslateAcceptLanguages* accept_languages =
970 translate_client_->GetTranslateAcceptLanguages();
971 // Don't translate any user black-listed languages.
972 if (!translate_prefs->CanTranslateLanguage(accept_languages,
973 page_language_code)) {
974 decision->SetIsInLanguageBlocklist();
975
976 decision->PreventAutoTranslate();
977 decision->PreventShowingUI();
978 decision->PreventShowingPredefinedLanguageTranslateUI();
979
980 // Disable showing the translate UI for hrefTranslate unless hrefTranslate
981 // is supposed to override the language blocklist.
982 if (!base::FeatureList::IsEnabled(kOverrideLanguagePrefsForHrefTranslate)) {
983 decision->PreventShowingHrefTranslateUI();
984 }
985 // Disable auto-translating the page for hrefTranslate unless hrefTranslate
986 // is supposed to override the language blocklist for auto-translation as
987 // well. This is enabled by default, but the below if-statement also
988 // explicitly checks if the underlying base::Feature is enabled as well so
989 // that disabling the underlying base::Feature will also turn off forcing
990 // auto translation.
991 if (!base::FeatureList::IsEnabled(kOverrideLanguagePrefsForHrefTranslate) ||
992 !base::GetFieldTrialParamByFeatureAsBool(
993 kOverrideLanguagePrefsForHrefTranslate, kForceAutoTranslateKey,
994 true)) {
995 decision->PreventAutoHrefTranslate();
996 }
997
998 decision->initiation_statuses.push_back(
999 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_CONFIG);
1000 decision->ranker_events.push_back(
1001 metrics::TranslateEventProto::LANGUAGE_DISABLED_BY_USER_CONFIG);
1002 GetActiveTranslateMetricsLogger()->LogTriggerDecision(
1003 TriggerDecision::kDisabledNeverTranslateLanguage);
1004 }
1005
1006 // Don't translate any user black-listed URLs.
1007 const GURL& page_url = translate_driver_->GetVisibleURL();
1008 if (translate_prefs->IsSiteBlacklisted(page_url.HostNoBrackets())) {
1009 decision->SetIsInSiteBlocklist();
1010
1011 decision->PreventAutoTranslate();
1012 decision->PreventShowingUI();
1013 decision->PreventShowingPredefinedLanguageTranslateUI();
1014
1015 // Disable showing the translate UI for hrefTranslate unless hrefTranslate
1016 // is supposed to override the site blocklist.
1017 if (!base::FeatureList::IsEnabled(kOverrideSitePrefsForHrefTranslate)) {
1018 decision->PreventShowingHrefTranslateUI();
1019 }
1020 // Disable auto-translating the page for hrefTranslate unless hrefTranslate
1021 // is supposed to override the site blocklist for auto-translation as well.
1022 if (!base::GetFieldTrialParamByFeatureAsBool(
1023 kOverrideSitePrefsForHrefTranslate, kForceAutoTranslateKey,
1024 false)) {
1025 decision->PreventAutoHrefTranslate();
1026 }
1027
1028 decision->initiation_statuses.push_back(
1029 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_CONFIG);
1030 decision->ranker_events.push_back(
1031 metrics::TranslateEventProto::URL_DISABLED_BY_USER_CONFIG);
1032 GetActiveTranslateMetricsLogger()->LogTriggerDecision(
1033 TriggerDecision::kDisabledNeverTranslateSite);
1034 }
1035 }
1036
FilterForHrefTranslate(TranslateTriggerDecision * decision,TranslatePrefs * translate_prefs,const std::string & page_language_code)1037 void TranslateManager::FilterForHrefTranslate(
1038 TranslateTriggerDecision* decision,
1039 TranslatePrefs* translate_prefs,
1040 const std::string& page_language_code) {
1041 if (!language_state_.navigation_from_google()) {
1042 decision->PreventAutoHrefTranslate();
1043 }
1044
1045 decision->href_translate_target = language_state_.href_translate();
1046 // Can't honor hrefTranslate if there's no specified target, the source or
1047 // the target aren't supported, or the source and target match.
1048 if (!IsTranslatableLanguagePair(page_language_code,
1049 decision->href_translate_target)) {
1050 decision->PreventAutoHrefTranslate();
1051 decision->PreventShowingHrefTranslateUI();
1052 }
1053 }
1054
FilterForPredefinedTarget(TranslateTriggerDecision * decision,TranslatePrefs * translate_prefs,const std::string & page_language_code)1055 void TranslateManager::FilterForPredefinedTarget(
1056 TranslateTriggerDecision* decision,
1057 TranslatePrefs* translate_prefs,
1058 const std::string& page_language_code) {
1059 decision->predefined_translate_target =
1060 language_state_.GetPredefinedTargetLanguage();
1061 if (!IsTranslatableLanguagePair(page_language_code,
1062 decision->predefined_translate_target)) {
1063 decision->PreventShowingPredefinedLanguageTranslateUI();
1064 }
1065 }
1066
IsTranslatableLanguagePair(const std::string & page_language_code,const std::string & target_language_code)1067 bool TranslateManager::IsTranslatableLanguagePair(
1068 const std::string& page_language_code,
1069 const std::string& target_language_code) {
1070 translate::TranslateLanguageList* language_list =
1071 translate::TranslateDownloadManager::GetInstance()->language_list();
1072
1073 return !target_language_code.empty() &&
1074 language_list->IsSupportedLanguage(target_language_code) &&
1075 TranslateDownloadManager::IsSupportedLanguage(page_language_code) &&
1076 page_language_code != target_language_code;
1077 }
1078
MaybeShowOmniboxIcon(const TranslateTriggerDecision & decision)1079 void TranslateManager::MaybeShowOmniboxIcon(
1080 const TranslateTriggerDecision& decision) {
1081 if (decision.IsTriggeringPossible()) {
1082 // Show the omnibox icon if any translate trigger is possible.
1083 language_state_.SetTranslateEnabled(true);
1084 TranslateBrowserMetrics::ReportInitiationStatus(
1085 TranslateBrowserMetrics::INITIATION_STATUS_SHOW_ICON);
1086 }
1087 }
1088
MaterializeDecision(const TranslateTriggerDecision & decision,TranslatePrefs * translate_prefs,const std::string & page_language_code,const std::string target_lang)1089 bool TranslateManager::MaterializeDecision(
1090 const TranslateTriggerDecision& decision,
1091 TranslatePrefs* translate_prefs,
1092 const std::string& page_language_code,
1093 const std::string target_lang) {
1094 // Auto-translating always happens if it's still possible here.
1095 if (decision.can_auto_translate()) {
1096 TranslatePage(page_language_code, decision.auto_translate_target, false);
1097 return false;
1098 }
1099
1100 if (decision.can_auto_href_translate()) {
1101 TranslatePage(page_language_code, decision.href_translate_target, false);
1102 return false;
1103 }
1104
1105 // Auto-translate didn't happen, so check if the UI should be shown. It must
1106 // not be suppressed by preference, system state, or the Ranker.
1107
1108 // Will be true if we've decided to show the infobar/bubble UI to the user.
1109 bool did_show_ui = false;
1110
1111 // Check whether the target language is predefined. If it is predefined
1112 // trigger Translate UI even if it would not otherwise be triggered
1113 // or would be triggered with another language.
1114 if (decision.can_show_predefined_language_translate_ui()) {
1115 did_show_ui = translate_client_->ShowTranslateUI(
1116 translate::TRANSLATE_STEP_BEFORE_TRANSLATE, page_language_code,
1117 decision.predefined_translate_target, TranslateErrors::NONE, false);
1118 }
1119
1120 if (!did_show_ui && decision.ShouldShowUI()) {
1121 // If the source language matches the UI language, it means the translation
1122 // prompt is being forced by an experiment. Report this so the count of how
1123 // often it happens can be tracked to suppress the experiment as necessary.
1124 if (page_language_code ==
1125 TranslateDownloadManager::GetLanguageCode(
1126 TranslateDownloadManager::GetInstance()->application_locale())) {
1127 translate_prefs->ReportForceTriggerOnEnglishPages();
1128 }
1129
1130 // Prompts the user if they want the page translated.
1131 did_show_ui = translate_client_->ShowTranslateUI(
1132 translate::TRANSLATE_STEP_BEFORE_TRANSLATE, page_language_code,
1133 target_lang, TranslateErrors::NONE, false);
1134 }
1135
1136 // Auto-translate didn't happen, and the UI wasn't shown so consider the
1137 // hrefTranslate attribute if it was present on the originating link.
1138 if (!did_show_ui && decision.can_show_href_translate_ui()) {
1139 did_show_ui = translate_client_->ShowTranslateUI(
1140 translate::TRANSLATE_STEP_BEFORE_TRANSLATE, page_language_code,
1141 decision.href_translate_target, TranslateErrors::NONE, false);
1142 }
1143
1144 if (did_show_ui)
1145 GetActiveTranslateMetricsLogger()->LogTriggerDecision(
1146 TriggerDecision::kShowUI);
1147
1148 return did_show_ui;
1149 }
1150
RecordDecisionMetrics(const TranslateTriggerDecision & decision,const std::string & page_language_code,bool ui_shown)1151 void TranslateManager::RecordDecisionMetrics(
1152 const TranslateTriggerDecision& decision,
1153 const std::string& page_language_code,
1154 bool ui_shown) {
1155 // For Google navigations, the hrefTranslate hint may trigger a translation
1156 // automatically. Record metrics if there is navigation from Google and a
1157 // |decision.href_translate_target|.
1158 if (language_state_.navigation_from_google() &&
1159 !decision.href_translate_target.empty()) {
1160 if (decision.can_auto_translate() || decision.can_auto_href_translate()) {
1161 if (decision.can_auto_translate() &&
1162 decision.auto_translate_target != decision.href_translate_target) {
1163 TranslateBrowserMetrics::ReportTranslateHrefHintStatus(
1164 TranslateBrowserMetrics::HrefTranslateStatus::
1165 kAutoTranslatedDifferentTargetLanguage);
1166 } else {
1167 TranslateBrowserMetrics::ReportTranslateHrefHintStatus(
1168 TranslateBrowserMetrics::HrefTranslateStatus::kAutoTranslated);
1169 }
1170 } else if (decision.can_show_href_translate_ui()) {
1171 TranslateBrowserMetrics::ReportTranslateHrefHintStatus(
1172 TranslateBrowserMetrics::HrefTranslateStatus::
1173 kUiShownNotAutoTranslated);
1174 } else {
1175 TranslateBrowserMetrics::ReportTranslateHrefHintStatus(
1176 TranslateBrowserMetrics::HrefTranslateStatus::
1177 kNoUiShownNotAutoTranslated);
1178 }
1179
1180 if (decision.is_in_language_blocklist()) {
1181 if (decision.is_in_site_blocklist()) {
1182 TranslateBrowserMetrics::ReportTranslateHrefHintPrefsFilterStatus(
1183 TranslateBrowserMetrics::HrefTranslatePrefsFilterStatus::
1184 kBothLanguageAndSiteInBlocklist);
1185 } else {
1186 TranslateBrowserMetrics::ReportTranslateHrefHintPrefsFilterStatus(
1187 TranslateBrowserMetrics::HrefTranslatePrefsFilterStatus::
1188 kLanguageInBlocklist);
1189 }
1190 } else if (decision.is_in_site_blocklist()) {
1191 TranslateBrowserMetrics::ReportTranslateHrefHintPrefsFilterStatus(
1192 TranslateBrowserMetrics::HrefTranslatePrefsFilterStatus::
1193 kSiteInBlocklist);
1194 } else {
1195 TranslateBrowserMetrics::ReportTranslateHrefHintPrefsFilterStatus(
1196 TranslateBrowserMetrics::HrefTranslatePrefsFilterStatus::
1197 kNotInBlocklists);
1198 }
1199 }
1200
1201 if (!decision.can_auto_translate() &&
1202 decision.can_show_predefined_language_translate_ui()) {
1203 TranslateBrowserMetrics::ReportInitiationStatus(
1204 TranslateBrowserMetrics::
1205 INITIATION_STATUS_SHOW_UI_PREDEFINED_TARGET_LANGUAGE);
1206
1207 return;
1208 }
1209
1210 // If the chosen outcome is to show the UI or let it be suppressed, log a few
1211 // explicit things.
1212 if (!decision.can_auto_translate() && decision.can_show_ui()) {
1213 // By getting here it's expected that nothing caused the translation to
1214 // be aborted or happen automatically. Because of that,
1215 // |decision.initiation_statuses| should be empty.
1216 DCHECK(decision.initiation_statuses.empty());
1217
1218 if (decision.should_suppress_from_ranker() || !ui_shown) {
1219 TranslateBrowserMetrics::ReportInitiationStatus(
1220 TranslateBrowserMetrics::INITIATION_STATUS_SUPPRESS_INFOBAR);
1221 }
1222
1223 // If the UI was suppressed, log the suppression source.
1224 if (decision.should_suppress_from_ranker()) {
1225 TranslateBrowserMetrics::ReportInitiationStatus(
1226 TranslateBrowserMetrics::INITIATION_STATUS_ABORTED_BY_RANKER);
1227 } else {
1228 // Always log INITIATION_STATUS_SHOW_INFOBAR regardless of whether it's
1229 // being subsequently suppressed or not. It's a measure of how often a
1230 // decision is taken to show it, and other metrics track *actual*
1231 // instances of it being shown.
1232 TranslateBrowserMetrics::ReportInitiationStatus(
1233 TranslateBrowserMetrics::INITIATION_STATUS_SHOW_INFOBAR);
1234 }
1235
1236 // There's nothing else to log if the UI was shown.
1237 return;
1238 }
1239
1240 // To match previous behavior, this function will log the first initiation
1241 // status that was recorded in the vector. This ensures that the histograms
1242 // reflect which conditions were met first to either trigger or prevent
1243 // translate triggering.
1244 if (!decision.initiation_statuses.empty()) {
1245 auto status = decision.initiation_statuses[0];
1246 if (status !=
1247 TranslateBrowserMetrics::INITIATION_STATUS_DOESNT_NEED_TRANSLATION) {
1248 // Don't record INITIATION_STATUS_DOESNT_NEED_TRANSLATION because it's
1249 // very frequent and not important to track.
1250 TranslateBrowserMetrics::ReportInitiationStatus(status);
1251 }
1252
1253 // The following metrics are logged alongside extra info.
1254 if (status ==
1255 TranslateBrowserMetrics::INITIATION_STATUS_LANGUAGE_IS_NOT_SUPPORTED) {
1256 TranslateBrowserMetrics::ReportUnsupportedLanguageAtInitiation(
1257 page_language_code);
1258 }
1259
1260 if (status ==
1261 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_PREFS) {
1262 const std::string& locale =
1263 TranslateDownloadManager::GetInstance()->application_locale();
1264 TranslateBrowserMetrics::ReportLocalesOnDisabledByPrefs(locale);
1265 }
1266 }
1267 }
1268
RecordDecisionRankerEvent(const TranslateTriggerDecision & decision,TranslatePrefs * translate_prefs,const std::string & page_language_code,const std::string & target_lang)1269 void TranslateManager::RecordDecisionRankerEvent(
1270 const TranslateTriggerDecision& decision,
1271 TranslatePrefs* translate_prefs,
1272 const std::string& page_language_code,
1273 const std::string& target_lang) {
1274 if (!decision.auto_translate_target.empty()) {
1275 translate_event_->set_modified_target_language(
1276 decision.auto_translate_target);
1277 }
1278
1279 if (!decision.ranker_events.empty()) {
1280 auto event = decision.ranker_events[0];
1281 RecordTranslateEvent(event);
1282 }
1283
1284 // Finally, if the decision was to show UI and ranker suppressed it, log that.
1285 if (!decision.can_auto_translate() && decision.can_show_ui() &&
1286 decision.should_suppress_from_ranker()) {
1287 RecordTranslateEvent(metrics::TranslateEventProto::DISABLED_BY_RANKER);
1288 }
1289 }
1290
SetPredefinedTargetLanguage(const std::string & language_code)1291 void TranslateManager::SetPredefinedTargetLanguage(
1292 const std::string& language_code) {
1293 language_state_.SetPredefinedTargetLanguage(language_code);
1294 }
1295
GetActiveTranslateMetricsLogger()1296 TranslateMetricsLogger* TranslateManager::GetActiveTranslateMetricsLogger() {
1297 // If |active_translate_metrics_logger_| is not null, return that. Otherwise
1298 // return |null_translate_metrics_logger_|. This way the callee doesn't have
1299 // to check if the returned value is null.
1300 return active_translate_metrics_logger_
1301 ? active_translate_metrics_logger_.get()
1302 : null_translate_metrics_logger_.get();
1303 }
1304
RegisterTranslateMetricsLogger(base::WeakPtr<TranslateMetricsLogger> translate_metrics_logger)1305 void TranslateManager::RegisterTranslateMetricsLogger(
1306 base::WeakPtr<TranslateMetricsLogger> translate_metrics_logger) {
1307 active_translate_metrics_logger_ = translate_metrics_logger;
1308 }
1309
1310 } // namespace translate
1311