blob: 251f0d2daec4747a65667ae996576a7587202420 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// 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_prefs.h"
#include <algorithm>
#include <limits>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "base/containers/contains.h"
#include "base/containers/cxx20_erase.h"
#include "base/feature_list.h"
#include "base/i18n/rtl.h"
#include "base/json/values_util.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "components/language/core/browser/language_prefs.h"
#include "components/language/core/browser/pref_names.h"
#include "components/language/core/common/language_experiments.h"
#include "components/language/core/common/language_util.h"
#include "components/language/core/common/locale_util.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/strings/grit/components_locale_settings.h"
#include "components/translate/core/browser/translate_accept_languages.h"
#include "components/translate/core/browser/translate_download_manager.h"
#include "components/translate/core/browser/translate_pref_names.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/l10n_util_collator.h"
namespace translate {
namespace {
// Returns whether or not the given list includes at least one language with
// the same base as the input language.
// For example: "en-US" and "en-UK" share the same base "en".
bool ContainsSameBaseLanguage(const std::vector<std::string>& list,
base::StringPiece language_code) {
base::StringPiece base_language =
language::ExtractBaseLanguage(language_code);
for (const auto& item : list) {
if (base_language == language::ExtractBaseLanguage(item))
return true;
}
return false;
}
// Removes from the language list any language that isn't supported as an
// Accept-Language (it's not in kAcceptLanguageList) if and only if there
// aren't any other languages from the same family in the list that are
// supported.
void PurgeUnsupportedLanguagesInLanguageFamily(base::StringPiece language,
std::vector<std::string>* list) {
base::StringPiece base_language = language::ExtractBaseLanguage(language);
for (const auto& lang : *list) {
// This method only operates on languages in the same family as |language|.
if (base_language != language::ExtractBaseLanguage(lang))
continue;
// If at least one of these same-family languages in |list| is supported by
// Accept-Languages, then that means that none of the languages in this
// family should be purged.
if (TranslateAcceptLanguages::CanBeAcceptLanguage(lang))
return;
}
// Purge all languages in the same family as |language|.
base::EraseIf(*list, [base_language](const std::string& lang) {
return base_language == language::ExtractBaseLanguage(lang);
});
}
} // namespace
const char TranslatePrefs::kPrefForceTriggerTranslateCount[] =
"translate_force_trigger_on_english_count_for_backoff_1";
const char TranslatePrefs::kPrefNeverPromptSitesDeprecated[] =
"translate_site_blacklist";
const char TranslatePrefs::kPrefNeverPromptSitesWithTime[] =
"translate_site_blacklist_with_time";
const char TranslatePrefs::kPrefTranslateDeniedCount[] =
"translate_denied_count_for_language";
const char TranslatePrefs::kPrefTranslateIgnoredCount[] =
"translate_ignored_count_for_language";
const char TranslatePrefs::kPrefTranslateAcceptedCount[] =
"translate_accepted_count";
// Deprecated 10/2021.
const char TranslatePrefs::kPrefAlwaysTranslateListDeprecated[] =
"translate_whitelists";
#if defined(OS_ANDROID) || defined(OS_IOS)
const char TranslatePrefs::kPrefTranslateAutoAlwaysCount[] =
"translate_auto_always_count";
const char TranslatePrefs::kPrefTranslateAutoNeverCount[] =
"translate_auto_never_count";
#endif
#if defined(OS_ANDROID)
const char TranslatePrefs::kPrefExplicitLanguageAskShown[] =
"translate_explicit_language_ask_shown";
#endif
// The below properties used to be used but now are deprecated. Don't use them
// since an old profile might have some values there.
//
// * translate_last_denied_time
// * translate_too_often_denied
// * translate_language_blacklist
const base::Feature kTranslateRecentTarget{"TranslateRecentTarget",
base::FEATURE_ENABLED_BY_DEFAULT};
const base::Feature kTranslate{"Translate", base::FEATURE_ENABLED_BY_DEFAULT};
TranslateLanguageInfo::TranslateLanguageInfo() = default;
TranslateLanguageInfo::TranslateLanguageInfo(const TranslateLanguageInfo&) =
default;
TranslateLanguageInfo::TranslateLanguageInfo(TranslateLanguageInfo&&) noexcept =
default;
TranslateLanguageInfo& TranslateLanguageInfo::operator=(
const TranslateLanguageInfo&) = default;
TranslateLanguageInfo& TranslateLanguageInfo::operator=(
TranslateLanguageInfo&&) noexcept = default;
TranslatePrefs::TranslatePrefs(PrefService* user_prefs)
: prefs_(user_prefs),
language_prefs_(std::make_unique<language::LanguagePrefs>(user_prefs)) {
MigrateNeverPromptSites();
ResetEmptyBlockedLanguagesToDefaults();
}
TranslatePrefs::~TranslatePrefs() = default;
// static
std::string TranslatePrefs::MapPreferenceName(const std::string& pref_name) {
if (pref_name == kPrefNeverPromptSitesDeprecated) {
return "translate_site_blocklist";
}
return pref_name;
}
bool TranslatePrefs::IsOfferTranslateEnabled() const {
return prefs_->GetBoolean(prefs::kOfferTranslateEnabled);
}
bool TranslatePrefs::IsTranslateAllowedByPolicy() const {
const PrefService::Preference* const pref =
prefs_->FindPreference(prefs::kOfferTranslateEnabled);
DCHECK(pref);
DCHECK(pref->GetValue()->is_bool());
return pref->GetValue()->GetBool() || !pref->IsManaged();
}
void TranslatePrefs::SetCountry(const std::string& country) {
country_ = country;
}
std::string TranslatePrefs::GetCountry() const {
return country_;
}
void TranslatePrefs::ResetToDefaults() {
ResetBlockedLanguagesToDefault();
ClearNeverPromptSiteList();
ClearAlwaysTranslateLanguagePairs();
prefs_->ClearPref(kPrefTranslateDeniedCount);
prefs_->ClearPref(kPrefTranslateIgnoredCount);
prefs_->ClearPref(kPrefTranslateAcceptedCount);
prefs_->ClearPref(prefs::kPrefTranslateRecentTarget);
#if defined(OS_ANDROID) || defined(OS_IOS)
prefs_->ClearPref(kPrefTranslateAutoAlwaysCount);
prefs_->ClearPref(kPrefTranslateAutoNeverCount);
#endif
prefs_->ClearPref(prefs::kOfferTranslateEnabled);
}
// static
base::Value TranslatePrefs::GetDefaultBlockedLanguages() {
typename base::Value::ListStorage languages;
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Preferred languages.
std::string language = language::kFallbackInputMethodLocale;
language::ToTranslateLanguageSynonym(&language);
languages.push_back(base::Value(std::move(language)));
#else
// Accept languages.
#pragma GCC diagnostic push
// See comment above the |break;| in the loop just below for why.
#pragma GCC diagnostic ignored "-Wunreachable-code"
for (std::string& language :
base::SplitString(l10n_util::GetStringUTF8(IDS_ACCEPT_LANGUAGES), ",",
base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
language::ToTranslateLanguageSynonym(&language);
languages.push_back(base::Value(std::move(language)));
// crbug.com/958348: The default value for Accept-Language *should* be the
// same as the one for Blocked Languages. However, Accept-Language contains
// English (and more) in addition to the local language in most locales due
// to historical reasons. Exiting early from this loop is a temporary fix
// that allows Blocked Languages to be at least populated with the UI
// language while still allowing Translate to trigger on other languages,
// most importantly English.
// Once the change to remove English from Accept-Language defaults lands,
// this break should be removed to enable the Blocked Language List and the
// Accept-Language list to be initialized to the same values.
break;
#pragma GCC diagnostic pop
}
#endif
std::sort(languages.begin(), languages.end());
languages.erase(std::unique(languages.begin(), languages.end()),
languages.end());
return base::Value(std::move(languages));
}
bool TranslatePrefs::IsBlockedLanguage(base::StringPiece input_language) const {
std::string canonical_lang(input_language);
language::ToTranslateLanguageSynonym(&canonical_lang);
const base::Value* blocked =
prefs_->GetList(translate::prefs::kBlockedLanguages);
return base::Contains(blocked->GetList(),
base::Value(std::move(canonical_lang)));
}
void TranslatePrefs::BlockLanguage(base::StringPiece input_language) {
DCHECK(!input_language.empty());
if (!IsBlockedLanguage(input_language)) {
std::string canonical_lang(input_language);
language::ToTranslateLanguageSynonym(&canonical_lang);
ListPrefUpdateDeprecated update(prefs_,
translate::prefs::kBlockedLanguages);
update->Append(std::move(canonical_lang));
}
// Remove the blocked language from the always translate list if present.
SetLanguageAlwaysTranslateState(input_language, false);
}
void TranslatePrefs::UnblockLanguage(base::StringPiece input_language) {
DCHECK(!input_language.empty());
// Never remove last fluent language.
if (GetNeverTranslateLanguages().size() <= 1) {
return;
}
std::string canonical_lang(input_language);
language::ToTranslateLanguageSynonym(&canonical_lang);
ListPrefUpdateDeprecated update(prefs_, translate::prefs::kBlockedLanguages);
update->EraseListValue(base::Value(std::move(canonical_lang)));
}
void TranslatePrefs::ResetEmptyBlockedLanguagesToDefaults() {
if (GetNeverTranslateLanguages().size() == 0) {
ResetBlockedLanguagesToDefault();
}
}
void TranslatePrefs::ResetBlockedLanguagesToDefault() {
prefs_->ClearPref(translate::prefs::kBlockedLanguages);
}
std::vector<std::string> TranslatePrefs::GetNeverTranslateLanguages() const {
const base::Value* fluent_languages_value =
prefs_->GetList(translate::prefs::kBlockedLanguages);
if (!fluent_languages_value) {
NOTREACHED() << "Fluent languages pref is unregistered";
}
std::vector<std::string> languages;
for (const auto& language : fluent_languages_value->GetList()) {
std::string chrome_language(language.GetString());
language::ToChromeLanguageSynonym(&chrome_language);
languages.push_back(chrome_language);
}
return languages;
}
// Note: the language codes used in the language settings list have the Chrome
// internal format and not the Translate server format.
// To convert from one to the other use util functions
// ToTranslateLanguageSynonym() and ToChromeLanguageSynonym().
void TranslatePrefs::AddToLanguageList(base::StringPiece input_language,
const bool force_blocked) {
DCHECK(!input_language.empty());
std::string chrome_language(input_language);
language::ToChromeLanguageSynonym(&chrome_language);
std::vector<std::string> languages;
std::vector<std::string> user_selected_languages;
GetLanguageList(&languages);
GetUserSelectedLanguageList(&user_selected_languages);
// We should block the language if the list does not already contain another
// language with the same base language. Policy-forced languages aren't
// counted as "blocking", so only user-selected languages are checked.
const bool should_block =
!ContainsSameBaseLanguage(user_selected_languages, chrome_language);
if (force_blocked || should_block) {
BlockLanguage(input_language);
}
// Add the language to the list.
if (!base::Contains(languages, chrome_language)) {
user_selected_languages.push_back(chrome_language);
language_prefs_->SetUserSelectedLanguagesList(user_selected_languages);
}
}
void TranslatePrefs::RemoveFromLanguageList(base::StringPiece input_language) {
DCHECK(!input_language.empty());
std::string chrome_language(input_language);
language::ToChromeLanguageSynonym(&chrome_language);
std::vector<std::string> languages;
std::vector<std::string> user_selected_languages;
GetUserSelectedLanguageList(&user_selected_languages);
// Remove the language from the list.
const auto& it = std::find(user_selected_languages.begin(),
user_selected_languages.end(), chrome_language);
if (it != user_selected_languages.end()) {
user_selected_languages.erase(it);
PurgeUnsupportedLanguagesInLanguageFamily(chrome_language,
&user_selected_languages);
language_prefs_->SetUserSelectedLanguagesList(user_selected_languages);
// We should unblock the language if this was the last one from the same
// language family.
GetLanguageList(&languages);
if (!ContainsSameBaseLanguage(languages, chrome_language)) {
UnblockLanguage(input_language);
// If the recent translate target matches the last language of a family
// being removed, reset the most recent target language so it will not be
// used the next time Translate is triggered.
std::string translate_language(input_language);
language::ToTranslateLanguageSynonym(&translate_language);
if (translate_language == GetRecentTargetLanguage()) {
ResetRecentTargetLanguage();
}
}
}
}
void TranslatePrefs::RearrangeLanguage(
base::StringPiece language,
const TranslatePrefs::RearrangeSpecifier where,
int offset,
const std::vector<std::string>& enabled_languages) {
// Negative offset is not supported.
DCHECK(!(offset < 1 && (where == kUp || where == kDown)));
std::vector<std::string> languages;
GetUserSelectedLanguageList(&languages);
auto pos = std::find(languages.begin(), languages.end(), language);
if (pos == languages.end())
return;
// Sort the vector of enabled languages for fast lookup.
std::vector<base::StringPiece> enabled(enabled_languages.begin(),
enabled_languages.end());
std::sort(enabled.begin(), enabled.end());
if (!std::binary_search(enabled.begin(), enabled.end(), language))
return;
switch (where) {
case kTop:
// To avoid code duplication, set |offset| to max int and re-use the logic
// to move |language| up in the list as far as possible.
offset = std::numeric_limits<int>::max();
[[fallthrough]];
case kUp:
if (pos == languages.begin())
return;
while (pos != languages.begin()) {
auto next_pos = pos - 1;
// Skip over non-enabled languages without decrementing |offset|.
// Also skip over languages hidden due to duplication between forced
// and user-selected languages.
if (std::binary_search(enabled.begin(), enabled.end(), *next_pos) &&
!language_prefs_->IsForcedLanguage(*next_pos)) {
// By only checking |offset| when an enabled, non-forced language is
// found, and decrementing |offset| after checking it (instead of
// before), this means that |language| will be moved up the list until
// it has either reached the next enabled language or the top of the
// list.
if (offset <= 0)
break;
--offset;
}
std::swap(*next_pos, *pos);
pos = next_pos;
}
break;
case kDown:
if (pos + 1 == languages.end())
return;
for (auto next_pos = pos + 1; next_pos != languages.end() && offset > 0;
pos = next_pos++) {
// Skip over non-enabled or forced languages without decrementing
// offset. Unlike moving languages up in the list, moving languages down
// in the list stops as soon as |offset| reaches zero, instead of
// continuing to skip non-enabled languages after |offset| has reached
// zero.
if (std::binary_search(enabled.begin(), enabled.end(), *next_pos) &&
!language_prefs_->IsForcedLanguage(*next_pos))
--offset;
std::swap(*next_pos, *pos);
}
break;
case kNone:
return;
default:
NOTREACHED();
return;
}
language_prefs_->SetUserSelectedLanguagesList(languages);
}
void TranslatePrefs::SetLanguageOrder(
const std::vector<std::string>& new_order) {
language_prefs_->SetUserSelectedLanguagesList(new_order);
}
// static
void TranslatePrefs::GetLanguageInfoList(
const std::string& app_locale,
bool translate_allowed,
std::vector<TranslateLanguageInfo>* language_list) {
DCHECK(language_list != nullptr);
if (app_locale.empty()) {
return;
}
language_list->clear();
// Collect the language codes from the supported accept-languages.
std::vector<std::string> language_codes;
l10n_util::GetAcceptLanguagesForLocale(app_locale, &language_codes);
// Collator used to sort display names in the given locale.
UErrorCode error = U_ZERO_ERROR;
std::unique_ptr<icu::Collator> collator(
icu::Collator::createInstance(icu::Locale(app_locale.c_str()), error));
if (U_FAILURE(error)) {
collator.reset();
}
// Map of [display name -> language code].
std::map<std::u16string, std::string,
l10n_util::StringComparator<std::u16string>>
language_map(l10n_util::StringComparator<std::u16string>(collator.get()));
// Build the list of display names and the language map.
for (std::string& code : language_codes) {
language_map[l10n_util::GetDisplayNameForLocale(code, app_locale, false)] =
std::move(code);
}
// Get the sorted list of translatable languages.
std::vector<std::string> translate_languages;
translate::TranslateDownloadManager::GetSupportedLanguages(
translate_allowed, &translate_languages);
// |translate_languages| should already be sorted alphabetically for fast
// searching.
DCHECK(
std::is_sorted(translate_languages.begin(), translate_languages.end()));
// Build the language list from the language map.
for (auto& entry : language_map) {
TranslateLanguageInfo language;
language.code = std::move(entry.second);
std::u16string adjusted_display_name = entry.first;
base::i18n::AdjustStringForLocaleDirection(&adjusted_display_name);
language.display_name = base::UTF16ToUTF8(adjusted_display_name);
std::u16string adjusted_native_display_name =
l10n_util::GetDisplayNameForLocale(language.code, language.code, false);
base::i18n::AdjustStringForLocaleDirection(&adjusted_native_display_name);
language.native_display_name =
base::UTF16ToUTF8(adjusted_native_display_name);
std::string supports_translate_code = language.code;
// Extract the base language: if the base language can be translated, then
// even the regional one should be marked as such.
language::ToTranslateLanguageSynonym(&supports_translate_code);
language.supports_translate =
std::binary_search(translate_languages.begin(),
translate_languages.end(), supports_translate_code);
language_list->push_back(std::move(language));
}
}
void TranslatePrefs::GetTranslatableContentLanguages(
const std::string& app_locale,
std::vector<std::string>* codes) {
DCHECK(codes != nullptr);
if (app_locale.empty()) {
return;
}
codes->clear();
// Get the language codes of user content languages.
// Returned in Chrome format.
std::vector<std::string> language_codes;
GetLanguageList(&language_codes);
std::set<std::string> unique_languages;
for (auto& entry : language_codes) {
std::string supports_translate_code = entry;
// Get the language in Translate format.
language::ToTranslateLanguageSynonym(&supports_translate_code);
// Extract the language code, for example for en-US it returns en.
std::string lang_code =
TranslateDownloadManager::GetLanguageCode(supports_translate_code);
// If the language code for a translatable language hasn't yet been added,
// add it to the result list.
if (TranslateDownloadManager::IsSupportedLanguage(lang_code)) {
if (unique_languages.count(lang_code) == 0) {
unique_languages.insert(lang_code);
codes->push_back(lang_code);
}
}
}
}
bool TranslatePrefs::IsSiteOnNeverPromptList(base::StringPiece site) const {
return prefs_->GetDictionary(kPrefNeverPromptSitesWithTime)->FindKey(site);
}
void TranslatePrefs::AddSiteToNeverPromptList(base::StringPiece site) {
DCHECK(!site.empty());
AddValueToNeverPromptList(kPrefNeverPromptSitesDeprecated, site);
DictionaryPrefUpdateDeprecated update(prefs_, kPrefNeverPromptSitesWithTime);
update.Get()->SetKey(site, base::TimeToValue(base::Time::Now()));
}
void TranslatePrefs::RemoveSiteFromNeverPromptList(base::StringPiece site) {
DCHECK(!site.empty());
RemoveValueFromNeverPromptList(kPrefNeverPromptSitesDeprecated, site);
DictionaryPrefUpdateDeprecated update(prefs_, kPrefNeverPromptSitesWithTime);
update.Get()->RemoveKey(site);
}
std::vector<std::string> TranslatePrefs::GetNeverPromptSitesBetween(
base::Time begin,
base::Time end) const {
std::vector<std::string> result;
auto* dict = prefs_->GetDictionary(kPrefNeverPromptSitesWithTime);
for (auto entry : dict->DictItems()) {
absl::optional<base::Time> time = base::ValueToTime(entry.second);
if (!time) {
NOTREACHED();
continue;
}
if (begin <= *time && *time < end)
result.push_back(entry.first);
}
return result;
}
void TranslatePrefs::DeleteNeverPromptSitesBetween(base::Time begin,
base::Time end) {
for (auto& site : GetNeverPromptSitesBetween(begin, end))
RemoveSiteFromNeverPromptList(site);
}
bool TranslatePrefs::IsLanguagePairOnAlwaysTranslateList(
base::StringPiece source_language,
base::StringPiece target_language) {
const base::Value* dict =
prefs_->GetDictionary(prefs::kPrefAlwaysTranslateList);
if (dict) {
const std::string* auto_target_lang = dict->FindStringKey(source_language);
if (auto_target_lang && *auto_target_lang == target_language)
return true;
}
return false;
}
void TranslatePrefs::AddLanguagePairToAlwaysTranslateList(
base::StringPiece source_language,
base::StringPiece target_language) {
DictionaryPrefUpdateDeprecated update(prefs_,
prefs::kPrefAlwaysTranslateList);
DCHECK(update.Get()) << "Always translated pref is unregistered";
// Get translate version of language codes.
std::string translate_source_language(source_language);
language::ToTranslateLanguageSynonym(&translate_source_language);
std::string translate_target_language(target_language);
language::ToTranslateLanguageSynonym(&translate_target_language);
update.Get()->SetStringKey(translate_source_language,
translate_target_language);
// Remove source language from block list if present.
UnblockLanguage(translate_source_language);
}
void TranslatePrefs::RemoveLanguagePairFromAlwaysTranslateList(
base::StringPiece source_language,
base::StringPiece target_language) {
DictionaryPrefUpdateDeprecated update(prefs_,
prefs::kPrefAlwaysTranslateList);
DCHECK(update.Get()) << "Always translate pref is unregistered";
// Get translate version of language codes.
std::string translate_source_language(source_language);
language::ToTranslateLanguageSynonym(&translate_source_language);
update.Get()->RemoveKey(translate_source_language);
}
void TranslatePrefs::SetLanguageAlwaysTranslateState(
base::StringPiece source_language,
bool always_translate) {
if (always_translate) {
AddLanguagePairToAlwaysTranslateList(source_language,
GetRecentTargetLanguage());
} else {
RemoveLanguagePairFromAlwaysTranslateList(source_language,
GetRecentTargetLanguage());
}
}
std::vector<std::string> TranslatePrefs::GetAlwaysTranslateLanguages() const {
const base::Value* dict =
prefs_->GetDictionary(prefs::kPrefAlwaysTranslateList);
DCHECK(dict) << "Always translate pref is unregistered";
std::vector<std::string> languages;
for (auto language_pair : dict->DictItems()) {
std::string chrome_language(language_pair.first);
language::ToChromeLanguageSynonym(&chrome_language);
languages.push_back(chrome_language);
}
return languages;
}
void TranslatePrefs::ClearNeverPromptSiteList() {
prefs_->ClearPref(kPrefNeverPromptSitesDeprecated);
prefs_->ClearPref(kPrefNeverPromptSitesWithTime);
}
bool TranslatePrefs::HasLanguagePairsToAlwaysTranslate() const {
return !IsDictionaryEmpty(prefs::kPrefAlwaysTranslateList);
}
void TranslatePrefs::ClearAlwaysTranslateLanguagePairs() {
prefs_->ClearPref(prefs::kPrefAlwaysTranslateList);
}
int TranslatePrefs::GetTranslationDeniedCount(
base::StringPiece language) const {
const base::Value* dict = prefs_->GetDictionary(kPrefTranslateDeniedCount);
return dict->FindIntKey(language).value_or(0);
}
void TranslatePrefs::IncrementTranslationDeniedCount(
base::StringPiece language) {
DictionaryPrefUpdateDeprecated update(prefs_, kPrefTranslateDeniedCount);
base::Value* dict = update.Get();
int count = dict->FindIntKey(language).value_or(0);
if (count < std::numeric_limits<int>::max())
dict->SetIntKey(language, count + 1);
}
void TranslatePrefs::ResetTranslationDeniedCount(base::StringPiece language) {
DictionaryPrefUpdateDeprecated update(prefs_, kPrefTranslateDeniedCount);
update.Get()->SetIntKey(language, 0);
}
int TranslatePrefs::GetTranslationIgnoredCount(
base::StringPiece language) const {
const base::Value* dict = prefs_->GetDictionary(kPrefTranslateIgnoredCount);
return dict->FindIntKey(language).value_or(0);
}
void TranslatePrefs::IncrementTranslationIgnoredCount(
base::StringPiece language) {
DictionaryPrefUpdateDeprecated update(prefs_, kPrefTranslateIgnoredCount);
base::Value* dict = update.Get();
int count = dict->FindIntKey(language).value_or(0);
if (count < std::numeric_limits<int>::max())
dict->SetIntKey(language, count + 1);
}
void TranslatePrefs::ResetTranslationIgnoredCount(base::StringPiece language) {
DictionaryPrefUpdateDeprecated update(prefs_, kPrefTranslateIgnoredCount);
update.Get()->SetIntKey(language, 0);
}
int TranslatePrefs::GetTranslationAcceptedCount(
base::StringPiece language) const {
const base::Value* dict = prefs_->GetDictionary(kPrefTranslateAcceptedCount);
return dict->FindIntKey(language).value_or(0);
}
void TranslatePrefs::IncrementTranslationAcceptedCount(
base::StringPiece language) {
DictionaryPrefUpdateDeprecated update(prefs_, kPrefTranslateAcceptedCount);
base::Value* dict = update.Get();
int count = dict->FindIntKey(language).value_or(0);
if (count < std::numeric_limits<int>::max())
dict->SetIntKey(language, count + 1);
}
void TranslatePrefs::ResetTranslationAcceptedCount(base::StringPiece language) {
DictionaryPrefUpdateDeprecated update(prefs_, kPrefTranslateAcceptedCount);
update.Get()->SetIntKey(language, 0);
}
#if defined(OS_ANDROID) || defined(OS_IOS)
int TranslatePrefs::GetTranslationAutoAlwaysCount(
base::StringPiece language) const {
const base::Value* dict =
prefs_->GetDictionary(kPrefTranslateAutoAlwaysCount);
return dict->FindIntKey(language).value_or(0);
}
void TranslatePrefs::IncrementTranslationAutoAlwaysCount(
base::StringPiece language) {
DictionaryPrefUpdateDeprecated update(prefs_, kPrefTranslateAutoAlwaysCount);
base::Value* dict = update.Get();
int count = dict->FindIntKey(language).value_or(0);
if (count < std::numeric_limits<int>::max())
dict->SetIntKey(language, count + 1);
}
void TranslatePrefs::ResetTranslationAutoAlwaysCount(
base::StringPiece language) {
DictionaryPrefUpdateDeprecated update(prefs_, kPrefTranslateAutoAlwaysCount);
update.Get()->SetIntKey(language, 0);
}
int TranslatePrefs::GetTranslationAutoNeverCount(
base::StringPiece language) const {
const base::Value* dict = prefs_->GetDictionary(kPrefTranslateAutoNeverCount);
return dict->FindIntKey(language).value_or(0);
}
void TranslatePrefs::IncrementTranslationAutoNeverCount(
base::StringPiece language) {
DictionaryPrefUpdateDeprecated update(prefs_, kPrefTranslateAutoNeverCount);
base::Value* dict = update.Get();
int count = dict->FindIntKey(language).value_or(0);
if (count < std::numeric_limits<int>::max())
dict->SetIntKey(language, count + 1);
}
void TranslatePrefs::ResetTranslationAutoNeverCount(
base::StringPiece language) {
DictionaryPrefUpdateDeprecated update(prefs_, kPrefTranslateAutoNeverCount);
update.Get()->SetIntKey(language, 0);
}
#endif // defined(OS_ANDROID) || defined(OS_IOS)
#if defined(OS_ANDROID)
bool TranslatePrefs::GetExplicitLanguageAskPromptShown() const {
return prefs_->GetBoolean(kPrefExplicitLanguageAskShown);
}
void TranslatePrefs::SetExplicitLanguageAskPromptShown(bool shown) {
prefs_->SetBoolean(kPrefExplicitLanguageAskShown, shown);
}
bool TranslatePrefs::GetAppLanguagePromptShown() const {
return prefs_->GetBoolean(language::prefs::kAppLanguagePromptShown);
}
void TranslatePrefs::SetAppLanguagePromptShown() {
prefs_->SetBoolean(language::prefs::kAppLanguagePromptShown, true);
}
#endif // defined(OS_ANDROID)
void TranslatePrefs::GetLanguageList(
std::vector<std::string>* const languages) const {
language_prefs_->GetAcceptLanguagesList(languages);
}
void TranslatePrefs::GetUserSelectedLanguageList(
std::vector<std::string>* const languages) const {
language_prefs_->GetUserSelectedLanguagesList(languages);
}
bool TranslatePrefs::CanTranslateLanguage(
TranslateAcceptLanguages* accept_languages,
base::StringPiece language) {
// Languages not on the blocklist can always be translated.
if (!IsBlockedLanguage(language))
return true;
// Languages not on the Accept-Language list should not be blocked unless the
// detailed language settings are showing or the language can not be on the
// Accept-Language list (this is true for languages that do not have a ICU
// localization for the current UI locale.
bool can_be_accept_language =
TranslateAcceptLanguages::CanBeAcceptLanguage(language);
bool is_accept_language = accept_languages->IsAcceptLanguage(language);
if (!is_accept_language && can_be_accept_language &&
!IsDetailedLanguageSettingsEnabled())
return true;
// Under this experiment, translate English page even though English may be
// blocked.
if (language == "en" && language::ShouldForceTriggerTranslateOnEnglishPages(
GetForceTriggerOnEnglishPagesCount()))
return true;
return false;
}
// static
bool TranslatePrefs::IsDetailedLanguageSettingsEnabled() {
#if defined(OS_ANDROID)
return base::FeatureList::IsEnabled(language::kDetailedLanguageSettings);
#elif defined(OS_WIN) || defined(OS_MAC) || defined(OS_LINUX)
return base::FeatureList::IsEnabled(
language::kDesktopDetailedLanguageSettings);
#else
return false;
#endif
}
bool TranslatePrefs::ShouldAutoTranslate(base::StringPiece source_language,
std::string* target_language) {
const base::Value* dict =
prefs_->GetDictionary(prefs::kPrefAlwaysTranslateList);
if (!dict)
return false;
const std::string* value = dict->FindStringKey(source_language);
if (!value)
return false;
DCHECK(!value->empty());
target_language->assign(*value);
return true;
}
void TranslatePrefs::SetRecentTargetLanguage(
const std::string& target_language) {
// Get translate version of language code.
std::string translate_target_language(target_language);
language::ToTranslateLanguageSynonym(&translate_target_language);
prefs_->SetString(prefs::kPrefTranslateRecentTarget,
translate_target_language);
}
void TranslatePrefs::ResetRecentTargetLanguage() {
SetRecentTargetLanguage("");
}
std::string TranslatePrefs::GetRecentTargetLanguage() const {
return prefs_->GetString(prefs::kPrefTranslateRecentTarget);
}
int TranslatePrefs::GetForceTriggerOnEnglishPagesCount() const {
return prefs_->GetInteger(kPrefForceTriggerTranslateCount);
}
void TranslatePrefs::ReportForceTriggerOnEnglishPages() {
int current_count = GetForceTriggerOnEnglishPagesCount();
if (current_count != -1 && current_count < std::numeric_limits<int>::max())
prefs_->SetInteger(kPrefForceTriggerTranslateCount, current_count + 1);
}
void TranslatePrefs::ReportAcceptedAfterForceTriggerOnEnglishPages() {
int current_count = GetForceTriggerOnEnglishPagesCount();
if (current_count != -1)
prefs_->SetInteger(kPrefForceTriggerTranslateCount, -1);
}
// static
void TranslatePrefs::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterListPref(kPrefNeverPromptSitesDeprecated,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterDictionaryPref(
kPrefNeverPromptSitesWithTime,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterDictionaryPref(
prefs::kPrefAlwaysTranslateList,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterDictionaryPref(
kPrefTranslateDeniedCount,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterDictionaryPref(
kPrefTranslateIgnoredCount,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterDictionaryPref(
kPrefTranslateAcceptedCount,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterStringPref(prefs::kPrefTranslateRecentTarget, "",
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterIntegerPref(
kPrefForceTriggerTranslateCount, 0,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterListPref(translate::prefs::kBlockedLanguages,
TranslatePrefs::GetDefaultBlockedLanguages(),
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
#if defined(OS_ANDROID) || defined(OS_IOS)
registry->RegisterDictionaryPref(
kPrefTranslateAutoAlwaysCount,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterDictionaryPref(
kPrefTranslateAutoNeverCount,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
#endif
#if defined(OS_ANDROID)
registry->RegisterBooleanPref(
kPrefExplicitLanguageAskShown, false,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
#endif
RegisterProfilePrefsForMigration(registry);
}
// static
void TranslatePrefs::RegisterProfilePrefsForMigration(
user_prefs::PrefRegistrySyncable* registry) {
// Deprecated 10/2021.
registry->RegisterDictionaryPref(kPrefAlwaysTranslateListDeprecated);
}
void TranslatePrefs::MigrateNeverPromptSites() {
// Migration copies any sites on the deprecated never prompt pref to
// the new version and clears all references to the old one. This will
// make subsequent calls to migrate no-ops.
DictionaryPrefUpdateDeprecated never_prompt_list_update(
prefs_, kPrefNeverPromptSitesWithTime);
base::Value* never_prompt_list = never_prompt_list_update.Get();
if (never_prompt_list) {
ListPrefUpdateDeprecated deprecated_prompt_list_update(
prefs_, kPrefNeverPromptSitesDeprecated);
base::Value* deprecated_list = deprecated_prompt_list_update.Get();
for (auto& site : deprecated_list->GetList()) {
if (!never_prompt_list->FindKey(site.GetString()) ||
!base::ValueToTime(never_prompt_list->FindKey(site.GetString()))) {
never_prompt_list->SetKey(site.GetString(),
base::TimeToValue(base::Time::Now()));
}
}
deprecated_list->ClearList();
}
}
// static
void TranslatePrefs::MigrateObsoleteProfilePrefs(PrefService* profile_prefs) {
const base::Value* deprecated_always_translate_list =
profile_prefs->GetUserPrefValue(kPrefAlwaysTranslateListDeprecated);
if (deprecated_always_translate_list &&
!profile_prefs->GetUserPrefValue(prefs::kPrefAlwaysTranslateList)) {
profile_prefs->Set(prefs::kPrefAlwaysTranslateList,
*deprecated_always_translate_list);
}
}
// static
void TranslatePrefs::ClearObsoleteProfilePrefs(PrefService* profile_prefs) {
profile_prefs->ClearPref(kPrefAlwaysTranslateListDeprecated);
}
bool TranslatePrefs::IsValueOnNeverPromptList(const char* pref_id,
base::StringPiece value) const {
const base::Value* never_prompt_list = prefs_->GetList(pref_id);
if (!never_prompt_list)
return false;
for (const base::Value& value_in_list : never_prompt_list->GetList()) {
if (value_in_list.is_string() && value_in_list.GetString() == value)
return true;
}
return false;
}
void TranslatePrefs::AddValueToNeverPromptList(const char* pref_id,
base::StringPiece value) {
ListPrefUpdateDeprecated update(prefs_, pref_id);
base::Value* never_prompt_list = update.Get();
if (!never_prompt_list) {
NOTREACHED() << "Unregistered never-translate pref";
return;
}
if (IsValueOnNeverPromptList(pref_id, value)) {
return;
}
never_prompt_list->Append(value);
}
void TranslatePrefs::RemoveValueFromNeverPromptList(const char* pref_id,
base::StringPiece value) {
ListPrefUpdateDeprecated update(prefs_, pref_id);
base::Value* never_prompt_list = update.Get();
if (!never_prompt_list) {
NOTREACHED() << "Unregistered never-translate pref";
return;
}
auto list_view = never_prompt_list->GetList();
never_prompt_list->EraseListIter(std::find_if(
list_view.begin(), list_view.end(),
[value](const base::Value& value_in_list) {
return value_in_list.is_string() && value_in_list.GetString() == value;
}));
}
size_t TranslatePrefs::GetListSize(const char* pref_id) const {
const base::Value* never_prompt_list = prefs_->GetList(pref_id);
return never_prompt_list == nullptr ? 0 : never_prompt_list->GetList().size();
}
bool TranslatePrefs::IsDictionaryEmpty(const char* pref_id) const {
const base::Value* dict = prefs_->GetDictionary(pref_id);
return (dict == nullptr || dict->DictEmpty());
}
} // namespace translate