// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/translate/core/browser/translate_infobar_delegate.h"
#include <algorithm>
#include <utility>
#include "base/i18n/string_compare.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "components/infobars/core/infobar.h"
#include "components/infobars/core/infobar_manager.h"
#include "components/language/core/browser/accept_languages_service.h"
#include "components/language/core/common/language_experiments.h"
#include "components/strings/grit/components_strings.h"
#include "components/translate/core/browser/language_state.h"
#include "components/translate/core/browser/translate_client.h"
#include "components/translate/core/browser/translate_download_manager.h"
#include "components/translate/core/browser/translate_driver.h"
#include "components/translate/core/browser/translate_manager.h"
#include "components/translate/core/common/translate_constants.h"
#include "components/translate/core/common/translate_util.h"
#include "ui/base/l10n/l10n_util.h"
namespace translate {
BASE_FEATURE(kTranslateCompactUI,
"TranslateCompactUI",
base::FEATURE_ENABLED_BY_DEFAULT);
const size_t TranslateInfoBarDelegate::kNoIndex =
TranslateUILanguagesManager::kNoIndex;
TranslateInfoBarDelegate::~TranslateInfoBarDelegate() {
for (auto& observer : observers_) {
observer.OnTranslateInfoBarDelegateDestroyed(this);
}
}
infobars::InfoBarDelegate::InfoBarIdentifier
TranslateInfoBarDelegate::GetIdentifier() const {
return TRANSLATE_INFOBAR_DELEGATE_NON_AURA;
}
// static
void TranslateInfoBarDelegate::Create(
bool replace_existing_infobar,
const base::WeakPtr<TranslateManager>& translate_manager,
infobars::InfoBarManager* infobar_manager,
translate::TranslateStep step,
const std::string& source_language,
const std::string& target_language,
TranslateErrors error_type,
bool triggered_from_menu) {
DCHECK(translate_manager);
DCHECK(infobar_manager);
// Check preconditions.
if (step != translate::TRANSLATE_STEP_TRANSLATE_ERROR) {
DCHECK(TranslateDownloadManager::IsSupportedLanguage(target_language));
if (!TranslateDownloadManager::IsSupportedLanguage(source_language)) {
// If the source language is unsupported than it must be the unknown
// language. On iOS, the source language can only be unknown for the
// "translating" infobar, which is the case when the user started a
// translation from the context menu.
#if BUILDFLAG(IS_IOS)
DCHECK(step == translate::TRANSLATE_STEP_TRANSLATING ||
step == translate::TRANSLATE_STEP_AFTER_TRANSLATE);
#endif
DCHECK_EQ(translate::kUnknownLanguageCode, source_language);
}
}
// Find any existing translate infobar delegate.
infobars::InfoBar* old_infobar = NULL;
TranslateInfoBarDelegate* old_delegate = NULL;
for (infobars::InfoBar* infobar : infobar_manager->infobars()) {
old_infobar = infobar;
old_delegate = old_infobar->delegate()->AsTranslateInfoBarDelegate();
if (old_delegate) {
if (!replace_existing_infobar) {
return;
}
break;
}
}
if (old_delegate) {
if (!triggered_from_menu) {
// Try to reuse existing translate infobar delegate.
old_delegate->step_ = step;
for (auto& observer : old_delegate->observers_) {
observer.OnTargetLanguageChanged(target_language);
observer.OnTranslateStepChanged(step, error_type);
}
return;
}
// The old infobar may still be visible, but a new translate flow started.
// Remove the previous infobar and add a new one.
infobar_manager->RemoveInfoBar(old_infobar);
}
// Add the new delegate.
TranslateClient* translate_client = translate_manager->translate_client();
std::unique_ptr<infobars::InfoBar> infobar(translate_client->CreateInfoBar(
base::WrapUnique(new TranslateInfoBarDelegate(
translate_manager, step, source_language, target_language, error_type,
triggered_from_menu))));
infobar_manager->AddInfoBar(std::move(infobar));
}
size_t TranslateInfoBarDelegate::num_languages() const {
return ui_languages_manager_->GetNumberOfLanguages();
}
std::string TranslateInfoBarDelegate::language_code_at(size_t index) const {
return ui_languages_manager_->GetLanguageCodeAt(index);
}
std::u16string TranslateInfoBarDelegate::language_name_at(size_t index) const {
return ui_languages_manager_->GetLanguageNameAt(index);
}
std::u16string TranslateInfoBarDelegate::source_language_name() const {
return language_name_at(ui_languages_manager_->GetSourceLanguageIndex());
}
std::u16string TranslateInfoBarDelegate::initial_source_language_name() const {
return language_name_at(
ui_languages_manager_->GetInitialSourceLanguageIndex());
}
std::u16string TranslateInfoBarDelegate::target_language_name() const {
return language_name_at(ui_languages_manager_->GetTargetLanguageIndex());
}
std::u16string TranslateInfoBarDelegate::unknown_language_name() const {
return ui_languages_manager_->GetUnknownLanguageDisplayName();
}
void TranslateInfoBarDelegate::GetLanguagesNames(
std::vector<std::u16string>* languages) const {
DCHECK(languages != nullptr);
languages->clear();
for (size_t i = 0; i < ui_languages_manager_->GetNumberOfLanguages(); ++i) {
languages->push_back(ui_languages_manager_->GetLanguageNameAt(i));
}
}
void TranslateInfoBarDelegate::GetLanguagesCodes(
std::vector<std::string>* languages_codes) const {
DCHECK(languages_codes != nullptr);
languages_codes->clear();
for (size_t i = 0; i < ui_languages_manager_->GetNumberOfLanguages(); ++i) {
languages_codes->push_back(ui_languages_manager_->GetLanguageCodeAt(i));
}
}
void TranslateInfoBarDelegate::UpdateSourceLanguage(
const std::string& language_code) {
ui_delegate_.UpdateAndRecordSourceLanguage(language_code);
}
void TranslateInfoBarDelegate::UpdateTargetLanguage(
const std::string& language_code) {
ui_delegate_.UpdateAndRecordTargetLanguage(language_code);
}
void TranslateInfoBarDelegate::OnErrorShown(TranslateErrors error_type) {
ui_delegate_.OnErrorShown(error_type);
}
void TranslateInfoBarDelegate::Translate() {
ui_delegate_.Translate();
}
void TranslateInfoBarDelegate::RevertTranslation() {
ui_delegate_.RevertTranslation();
infobar()->RemoveSelf();
}
void TranslateInfoBarDelegate::RevertWithoutClosingInfobar() {
ui_delegate_.RevertTranslation();
step_ = TRANSLATE_STEP_BEFORE_TRANSLATE;
}
void TranslateInfoBarDelegate::TranslationDeclined() {
ui_delegate_.TranslationDeclined(true);
}
bool TranslateInfoBarDelegate::IsTranslatableLanguageByPrefs() const {
TranslateClient* client = translate_manager_->translate_client();
std::unique_ptr<TranslatePrefs> translate_prefs(client->GetTranslatePrefs());
return translate_prefs->CanTranslateLanguage(source_language_code());
}
void TranslateInfoBarDelegate::ToggleTranslatableLanguageByPrefs() {
ui_delegate_.SetLanguageBlocked(!ui_delegate_.IsLanguageBlocked());
}
bool TranslateInfoBarDelegate::IsSiteOnNeverPromptList() const {
return ui_delegate_.IsSiteOnNeverPromptList();
}
void TranslateInfoBarDelegate::ToggleNeverPromptSite() {
ui_delegate_.SetNeverPromptSite(!ui_delegate_.IsSiteOnNeverPromptList());
}
bool TranslateInfoBarDelegate::ShouldNeverTranslateLanguage() const {
return ui_delegate_.IsLanguageBlocked();
}
bool TranslateInfoBarDelegate::ShouldAlwaysTranslate() const {
return ui_delegate_.ShouldAlwaysTranslate();
}
void TranslateInfoBarDelegate::ToggleAlwaysTranslate() {
ui_delegate_.SetAlwaysTranslate(!ui_delegate_.ShouldAlwaysTranslate());
}
void TranslateInfoBarDelegate::AlwaysTranslatePageLanguage() {
DCHECK(!ui_delegate_.ShouldAlwaysTranslate());
ui_delegate_.SetAlwaysTranslate(true);
Translate();
}
void TranslateInfoBarDelegate::NeverTranslatePageLanguage() {
DCHECK(!ui_delegate_.IsLanguageBlocked());
ui_delegate_.SetLanguageBlocked(true);
infobar()->RemoveSelf();
}
std::u16string TranslateInfoBarDelegate::GetMessageInfoBarButtonText() {
if (step_ != translate::TRANSLATE_STEP_TRANSLATE_ERROR) {
DCHECK_EQ(translate::TRANSLATE_STEP_TRANSLATING, step_);
} else if ((error_type_ != TranslateErrors::IDENTICAL_LANGUAGES) &&
(error_type_ != TranslateErrors::UNKNOWN_LANGUAGE)) {
return l10n_util::GetStringUTF16(
(error_type_ == TranslateErrors::UNSUPPORTED_LANGUAGE)
? IDS_TRANSLATE_INFOBAR_REVERT
: IDS_TRANSLATE_INFOBAR_RETRY);
}
return std::u16string();
}
void TranslateInfoBarDelegate::MessageInfoBarButtonPressed() {
DCHECK_EQ(translate::TRANSLATE_STEP_TRANSLATE_ERROR, step_);
if (error_type_ == TranslateErrors::UNSUPPORTED_LANGUAGE) {
RevertTranslation();
return;
}
// This is the "Try again..." case.
DCHECK(translate_manager_);
translate_manager_->TranslatePage(
source_language_code(), target_language_code(), false,
translate_manager_->GetActiveTranslateMetricsLogger()
->GetNextManualTranslationType(
/*is_context_menu_initiated_translation=*/false));
}
bool TranslateInfoBarDelegate::ShouldShowMessageInfoBarButton() {
return !GetMessageInfoBarButtonText().empty();
}
bool TranslateInfoBarDelegate::ShouldShowAlwaysTranslateShortcut() {
#if BUILDFLAG(IS_IOS)
// On mobile, the option to always translate is shown after the translation.
DCHECK_EQ(translate::TRANSLATE_STEP_AFTER_TRANSLATE, step_);
#else
// On desktop, the option to always translate is shown before the translation.
DCHECK_EQ(translate::TRANSLATE_STEP_BEFORE_TRANSLATE, step_);
#endif
return ui_delegate_.ShouldShowAlwaysTranslateShortcut();
}
bool TranslateInfoBarDelegate::ShouldShowNeverTranslateShortcut() {
DCHECK_EQ(translate::TRANSLATE_STEP_BEFORE_TRANSLATE, step_);
return ui_delegate_.ShouldShowNeverTranslateShortcut();
}
#if BUILDFLAG(IS_IOS)
void TranslateInfoBarDelegate::ShowNeverTranslateInfobar() {
// Return if the infobar is not owned.
if (!infobar()->owner())
return;
Create(true, translate_manager_, infobar()->owner(),
translate::TRANSLATE_STEP_NEVER_TRANSLATE, source_language_code(),
target_language_code(), TranslateErrors::NONE, false);
}
#endif
void TranslateInfoBarDelegate::GetContentLanguagesCodes(
std::vector<std::string>* content_codes) const {
ui_delegate_.GetContentLanguagesCodes(content_codes);
}
bool TranslateInfoBarDelegate::ShouldAutoAlwaysTranslate() {
return ui_delegate_.ShouldAutoAlwaysTranslate();
}
bool TranslateInfoBarDelegate::ShouldAutoNeverTranslate() {
return ui_delegate_.ShouldAutoNeverTranslate();
}
// static
void TranslateInfoBarDelegate::GetAfterTranslateStrings(
std::vector<std::u16string>* strings,
bool* swap_languages,
bool autodetermined_source_language) {
DCHECK(strings);
if (autodetermined_source_language) {
size_t offset;
std::u16string text = l10n_util::GetStringFUTF16(
IDS_TRANSLATE_INFOBAR_AFTER_MESSAGE_AUTODETERMINED_SOURCE_LANGUAGE,
std::u16string(), &offset);
strings->push_back(text.substr(0, offset));
strings->push_back(text.substr(offset));
return;
}
DCHECK(swap_languages);
std::vector<size_t> offsets;
std::u16string text =
l10n_util::GetStringFUTF16(IDS_TRANSLATE_INFOBAR_AFTER_MESSAGE,
std::u16string(), std::u16string(), &offsets);
DCHECK_EQ(2U, offsets.size());
*swap_languages = (offsets[0] > offsets[1]);
if (*swap_languages)
std::swap(offsets[0], offsets[1]);
strings->push_back(text.substr(0, offsets[0]));
strings->push_back(text.substr(offsets[0], offsets[1] - offsets[0]));
strings->push_back(text.substr(offsets[1]));
}
TranslateDriver* TranslateInfoBarDelegate::GetTranslateDriver() {
if (!translate_manager_)
return NULL;
return translate_manager_->translate_client()->GetTranslateDriver();
}
void TranslateInfoBarDelegate::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void TranslateInfoBarDelegate::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
TranslateInfoBarDelegate::TranslateInfoBarDelegate(
const base::WeakPtr<TranslateManager>& translate_manager,
translate::TranslateStep step,
const std::string& source_language,
const std::string& target_language,
TranslateErrors error_type,
bool triggered_from_menu)
: infobars::InfoBarDelegate(),
step_(step),
ui_delegate_(translate_manager, source_language, target_language),
translate_manager_(translate_manager),
ui_languages_manager_(ui_delegate_.translate_ui_languages_manager()),
error_type_(error_type),
prefs_(translate_manager->translate_client()->GetTranslatePrefs()),
triggered_from_menu_(triggered_from_menu) {
DCHECK_NE((step_ == translate::TRANSLATE_STEP_TRANSLATE_ERROR),
(error_type_ == TranslateErrors::NONE));
DCHECK(translate_manager_);
}
int TranslateInfoBarDelegate::GetIconId() const {
return translate_manager_->translate_client()->GetInfobarIconID();
}
void TranslateInfoBarDelegate::InfoBarDismissed() {
OnInfoBarClosedByUser();
ReportUIInteraction(UIInteraction::kCloseUIExplicitly);
bool declined = false;
bool has_observer = false;
for (auto& observer : observers_) {
has_observer = true;
if (observer.IsDeclinedByUser())
declined = true;
}
if (!has_observer)
declined = step_ == translate::TRANSLATE_STEP_BEFORE_TRANSLATE;
if (declined) {
// The user closed the infobar without clicking the translate button.
TranslationDeclined();
}
}
TranslateInfoBarDelegate*
TranslateInfoBarDelegate::AsTranslateInfoBarDelegate() {
return this;
}
void TranslateInfoBarDelegate::OnInfoBarClosedByUser() {
ui_delegate_.OnUIClosedByUser();
}
void TranslateInfoBarDelegate::ReportUIInteraction(
UIInteraction ui_interaction) {
ui_delegate_.ReportUIInteraction(ui_interaction);
}
} // namespace translate