| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/translate/partial_translate_bubble_model_impl.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/metrics_hashes.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/translate/chrome_translate_client.h" |
| #include "chrome/browser/ui/translate/partial_translate_bubble_model.h" |
| #include "components/language_detection/core/constants.h" |
| #include "components/translate/content/browser/partial_translate_manager.h" |
| #include "components/translate/core/browser/translate_manager.h" |
| #include "components/translate/core/browser/translate_ui_languages_manager.h" |
| #include "components/translate/core/common/translate_errors.h" |
| |
| namespace { |
| |
| const char kTranslatePartialTranslationSourceLanguage[] = |
| "Translate.PartialTranslation.SourceLanguage"; |
| const char kTranslatePartialTranslationTargetLanguage[] = |
| "Translate.PartialTranslation.TargetLanguage"; |
| const char kTranslatePartialTranslationResponseTime[] = |
| "Translate.PartialTranslation.ResponseTime"; |
| const char kTranslatePartialTranslationTranslationStatus[] = |
| "Translate.PartialTranslation.TranslationStatus"; |
| const char kTranslatePartialTranslationTranslatedCharacterCount[] = |
| "Translate.PartialTranslation.Translated.CharacterCount"; |
| |
| } // namespace |
| |
| PartialTranslateBubbleModelImpl::PartialTranslateBubbleModelImpl( |
| ViewState view_state, |
| translate::TranslateErrors error_type, |
| const std::u16string& source_text, |
| const std::u16string& target_text, |
| std::unique_ptr<PartialTranslateManager> partial_translate_manager, |
| std::unique_ptr<translate::TranslateUILanguagesManager> |
| ui_languages_manager) |
| : current_view_state_(view_state), |
| error_type_(error_type), |
| source_text_(source_text), |
| target_text_(target_text), |
| partial_translate_manager_(std::move(partial_translate_manager)), |
| ui_languages_manager_(std::move(ui_languages_manager)) { |
| DCHECK_NE(VIEW_STATE_SOURCE_LANGUAGE, view_state); |
| DCHECK_NE(VIEW_STATE_TARGET_LANGUAGE, view_state); |
| } |
| |
| PartialTranslateBubbleModelImpl::~PartialTranslateBubbleModelImpl() = default; |
| |
| void PartialTranslateBubbleModelImpl::AddObserver( |
| PartialTranslateBubbleModel::Observer* obs) { |
| observers_.AddObserver(obs); |
| } |
| void PartialTranslateBubbleModelImpl::RemoveObserver( |
| PartialTranslateBubbleModel::Observer* obs) { |
| observers_.RemoveObserver(obs); |
| } |
| |
| PartialTranslateBubbleModelImpl::ViewState |
| PartialTranslateBubbleModelImpl::GetViewState() const { |
| return current_view_state_; |
| } |
| |
| void PartialTranslateBubbleModelImpl::SetViewState( |
| PartialTranslateBubbleModelImpl::ViewState view_state) { |
| current_view_state_ = view_state; |
| } |
| |
| void PartialTranslateBubbleModelImpl::SetSourceLanguage( |
| const std::string& language_code) { |
| ui_languages_manager_->UpdateSourceLanguage(language_code); |
| } |
| |
| void PartialTranslateBubbleModelImpl::SetTargetLanguage( |
| const std::string& language_code) { |
| ui_languages_manager_->UpdateTargetLanguage(language_code); |
| } |
| |
| void PartialTranslateBubbleModelImpl::SetSourceText( |
| const std::u16string& text) { |
| source_text_ = text; |
| } |
| |
| std::u16string PartialTranslateBubbleModelImpl::GetSourceText() const { |
| return source_text_; |
| } |
| |
| void PartialTranslateBubbleModelImpl::SetTargetText( |
| const std::u16string& text) { |
| // Note: Some languages have syntactic differences in use of ellipses. |
| // Luxembourgish uses a leading space and is the only one of these languages |
| // supported by Translate in Chrome. Given this, specific localization is not |
| // handled, but could be in the future if more languages are included. |
| if (source_text_truncated_) { |
| target_text_ = text + u"…"; |
| } else { |
| target_text_ = text; |
| } |
| } |
| |
| std::u16string PartialTranslateBubbleModelImpl::GetTargetText() const { |
| return target_text_; |
| } |
| |
| void PartialTranslateBubbleModelImpl::SetError( |
| translate::TranslateErrors error_type) { |
| error_type_ = error_type; |
| } |
| |
| translate::TranslateErrors PartialTranslateBubbleModelImpl::GetError() const { |
| return error_type_; |
| } |
| |
| int PartialTranslateBubbleModelImpl::GetNumberOfSourceLanguages() const { |
| return ui_languages_manager_->GetNumberOfLanguages(); |
| } |
| |
| int PartialTranslateBubbleModelImpl::GetNumberOfTargetLanguages() const { |
| // Subtract 1 to account for unknown language option being omitted. |
| return ui_languages_manager_->GetNumberOfLanguages() - 1; |
| } |
| |
| std::u16string PartialTranslateBubbleModelImpl::GetSourceLanguageNameAt( |
| int index) const { |
| return ui_languages_manager_->GetLanguageNameAt(index); |
| } |
| |
| std::u16string PartialTranslateBubbleModelImpl::GetTargetLanguageNameAt( |
| int index) const { |
| // Add 1 to account for unknown language option at index 0 in |
| // TranslateUILanguagesManager language list. |
| return ui_languages_manager_->GetLanguageNameAt(index + 1); |
| } |
| |
| int PartialTranslateBubbleModelImpl::GetSourceLanguageIndex() const { |
| return ui_languages_manager_->GetSourceLanguageIndex(); |
| } |
| |
| void PartialTranslateBubbleModelImpl::UpdateSourceLanguageIndex(int index) { |
| ui_languages_manager_->UpdateSourceLanguageIndex(index); |
| } |
| |
| int PartialTranslateBubbleModelImpl::GetTargetLanguageIndex() const { |
| // Subtract 1 to account for unknown language option being omitted from the |
| // bubble target language list. |
| return ui_languages_manager_->GetTargetLanguageIndex() - 1; |
| } |
| |
| void PartialTranslateBubbleModelImpl::UpdateTargetLanguageIndex(int index) { |
| // Add 1 to account for unknown language option at index 0 in |
| // TranslateUILanguagesManager language list. |
| ui_languages_manager_->UpdateTargetLanguageIndex(index + 1); |
| } |
| |
| std::string PartialTranslateBubbleModelImpl::GetSourceLanguageCode() const { |
| return ui_languages_manager_->GetSourceLanguageCode(); |
| } |
| |
| std::string PartialTranslateBubbleModelImpl::GetTargetLanguageCode() const { |
| return ui_languages_manager_->GetTargetLanguageCode(); |
| } |
| |
| void PartialTranslateBubbleModelImpl::Translate( |
| content::WebContents* web_contents) { |
| PartialTranslateRequest request; |
| // If the selected text was truncated, strip the trailing ellipses before |
| // sending for translation. |
| std::u16string source_text = GetSourceText(); |
| if (source_text_truncated_) { |
| request.selection_text = source_text.substr(0, source_text.size() - 1); |
| } else { |
| request.selection_text = source_text; |
| } |
| |
| request.selection_encoding = web_contents->GetEncoding(); |
| std::string source_language_code = GetSourceLanguageCode(); |
| if (source_language_code != language_detection::kUnknownLanguageCode) { |
| // |source_language_code| will be kUnknownLanguageCode if it was initially |
| // returned by page language detection, or if the user explicitly selects |
| // "Detected Language" in the language list. In such cases, |
| // |request.source_language| is left as an "empty" value. |
| request.source_language = source_language_code; |
| } |
| request.target_language = GetTargetLanguageCode(); |
| |
| // If this is the initial Partial Translate request, then the source language |
| // should be used as a hint for backend language detection. |
| request.apply_lang_hint = !initial_request_completed_; |
| |
| translate_request_started_time_ = base::TimeTicks::Now(); |
| |
| RecordHistogramsOnPartialTranslateStart(); |
| |
| translate::TranslateManager* translate_manager = |
| ChromeTranslateClient::GetManagerFromWebContents(web_contents); |
| translate_manager->translate_client() |
| ->GetTranslatePrefs() |
| ->SetRecentTargetLanguage(GetTargetLanguageCode()); |
| |
| // Cancels any ongoing requests. |
| partial_translate_manager_->StartPartialTranslate( |
| web_contents, request, |
| base::BindOnce( |
| &PartialTranslateBubbleModelImpl::OnPartialTranslateResponse, |
| // |partial_translate_manager_| is owned by Model and will be |
| // destructed (cancelling the Callback) if Model is. |
| base::Unretained(this), request)); |
| } |
| |
| void PartialTranslateBubbleModelImpl::TranslateFullPage( |
| content::WebContents* web_contents) { |
| translate::TranslateManager* translate_manager = |
| ChromeTranslateClient::GetManagerFromWebContents(web_contents); |
| translate_manager->ShowTranslateUI(/* source_code */ std::nullopt, |
| GetTargetLanguageCode(), true); |
| } |
| |
| void PartialTranslateBubbleModelImpl::SetSourceTextTruncated( |
| bool is_truncated) { |
| source_text_truncated_ = is_truncated; |
| } |
| |
| void PartialTranslateBubbleModelImpl::OnPartialTranslateResponse( |
| const PartialTranslateRequest& request, |
| const PartialTranslateResponse& response) { |
| translate_response_received_time_ = base::TimeTicks::Now(); |
| |
| bool status_error = (response.status != PartialTranslateStatus::kSuccess); |
| if (status_error) { |
| error_type_ = translate::TranslateErrors::TRANSLATION_ERROR; |
| SetViewState(PartialTranslateBubbleModel::VIEW_STATE_ERROR); |
| } else { |
| SetSourceLanguage(response.source_language); |
| SetTargetLanguage(response.target_language); |
| SetTargetText(response.translated_text); |
| SetViewState(PartialTranslateBubbleModel::VIEW_STATE_AFTER_TRANSLATE); |
| error_type_ = translate::TranslateErrors::NONE; |
| initial_request_completed_ = true; |
| } |
| |
| RecordHistogramsOnPartialTranslateComplete(status_error); |
| |
| for (PartialTranslateBubbleModel::Observer& obs : observers_) { |
| obs.OnPartialTranslateComplete(); |
| } |
| } |
| |
| void PartialTranslateBubbleModelImpl:: |
| RecordHistogramsOnPartialTranslateStart() { |
| base::UmaHistogramSparse(kTranslatePartialTranslationSourceLanguage, |
| base::HashMetricName(GetSourceLanguageCode())); |
| base::UmaHistogramSparse(kTranslatePartialTranslationTargetLanguage, |
| base::HashMetricName(GetTargetLanguageCode())); |
| } |
| |
| void PartialTranslateBubbleModelImpl:: |
| RecordHistogramsOnPartialTranslateComplete(bool status_error) { |
| base::UmaHistogramMediumTimes( |
| kTranslatePartialTranslationResponseTime, |
| translate_response_received_time_ - translate_request_started_time_); |
| // All PartialTranslateTranslationStatus enum values >0 represent error |
| // states. Currently there is only one error value, but this can be split into |
| // specific error types in the future. |
| base::UmaHistogramBoolean(kTranslatePartialTranslationTranslationStatus, |
| status_error); |
| |
| if (!status_error) { |
| base::UmaHistogramCounts100000( |
| kTranslatePartialTranslationTranslatedCharacterCount, |
| target_text_.length()); |
| } |
| } |