blob: 83c7cc8e7ecc9253e3352199b870f05c4c1df94a [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 <set>
#include "base/memory/ptr_util.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "build/build_config.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/translate/core/browser/translate_accept_languages.h"
#include "components/translate/core/browser/translate_download_manager.h"
#include "components/translate/core/browser/translate_experiment.h"
#include "components/translate/core/common/translate_util.h"
namespace translate {
const char TranslatePrefs::kPrefTranslateSiteBlacklist[] =
"translate_site_blacklist";
const char TranslatePrefs::kPrefTranslateWhitelists[] = "translate_whitelists";
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";
const char TranslatePrefs::kPrefTranslateBlockedLanguages[] =
"translate_blocked_languages";
const char TranslatePrefs::kPrefTranslateLastDeniedTimeForLanguage[] =
"translate_last_denied_time_for_language";
const char TranslatePrefs::kPrefTranslateTooOftenDeniedForLanguage[] =
"translate_too_often_denied_for_language";
// 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
namespace {
// Expands language codes to make these more suitable for Accept-Language.
// Example: ['en-US', 'ja', 'en-CA'] => ['en-US', 'en', 'ja', 'en-CA'].
// 'en' won't appear twice as this function eliminates duplicates.
void ExpandLanguageCodes(const std::vector<std::string>& languages,
std::vector<std::string>* expanded_languages) {
DCHECK(expanded_languages);
DCHECK(expanded_languages->empty());
// used to eliminate duplicates.
std::set<std::string> seen;
for (std::vector<std::string>::const_iterator it = languages.begin();
it != languages.end(); ++it) {
const std::string& language = *it;
if (seen.find(language) == seen.end()) {
expanded_languages->push_back(language);
seen.insert(language);
}
std::vector<std::string> tokens = base::SplitString(
language, "-", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (tokens.size() == 0)
continue;
const std::string& main_part = tokens[0];
if (seen.find(main_part) == seen.end()) {
expanded_languages->push_back(main_part);
seen.insert(main_part);
}
}
}
} // namespace
const base::Feature kTranslateUI2016Q2{"TranslateUI2016Q2",
base::FEATURE_DISABLED_BY_DEFAULT};
DenialTimeUpdate::DenialTimeUpdate(PrefService* prefs,
const std::string& language,
size_t max_denial_count)
: denial_time_dict_update_(
prefs,
TranslatePrefs::kPrefTranslateLastDeniedTimeForLanguage),
language_(language),
max_denial_count_(max_denial_count),
time_list_(nullptr) {}
DenialTimeUpdate::~DenialTimeUpdate() {}
// Gets the list of timestamps when translation was denied.
base::ListValue* DenialTimeUpdate::GetDenialTimes() {
if (time_list_)
return time_list_;
// Any consumer of GetDenialTimes _will_ write to them, so let's get an
// update started.
base::DictionaryValue* denial_time_dict = denial_time_dict_update_.Get();
DCHECK(denial_time_dict);
base::Value* denial_value = nullptr;
bool has_value = denial_time_dict->Get(language_, &denial_value);
bool has_list = has_value && denial_value->GetAsList(&time_list_);
if (!has_list) {
time_list_ = new base::ListValue();
double oldest_denial_time = 0;
bool has_old_style =
has_value && denial_value->GetAsDouble(&oldest_denial_time);
if (has_old_style)
time_list_->AppendDouble(oldest_denial_time);
denial_time_dict->Set(language_, base::WrapUnique(time_list_));
}
return time_list_;
}
base::Time DenialTimeUpdate::GetOldestDenialTime() {
double oldest_time;
bool result = GetDenialTimes()->GetDouble(0, &oldest_time);
if (!result)
return base::Time();
return base::Time::FromJsTime(oldest_time);
}
void DenialTimeUpdate::AddDenialTime(base::Time denial_time) {
DCHECK(GetDenialTimes());
GetDenialTimes()->AppendDouble(denial_time.ToJsTime());
while (GetDenialTimes()->GetSize() >= max_denial_count_)
GetDenialTimes()->Remove(0, nullptr);
}
TranslatePrefs::TranslatePrefs(PrefService* user_prefs,
const char* accept_languages_pref,
const char* preferred_languages_pref)
: accept_languages_pref_(accept_languages_pref), prefs_(user_prefs) {
#if defined(OS_CHROMEOS)
preferred_languages_pref_ = preferred_languages_pref;
#else
DCHECK(!preferred_languages_pref);
#endif
}
void TranslatePrefs::SetCountry(const std::string& country) {
country_ = country;
}
std::string TranslatePrefs::GetCountry() const {
return country_;
}
void TranslatePrefs::ResetToDefaults() {
ClearBlockedLanguages();
ClearBlacklistedSites();
ClearWhitelistedLanguagePairs();
std::vector<std::string> languages;
GetLanguageList(&languages);
for (std::vector<std::string>::const_iterator it = languages.begin();
it != languages.end(); ++it) {
const std::string& language = *it;
ResetTranslationAcceptedCount(language);
ResetTranslationDeniedCount(language);
ResetTranslationIgnoredCount(language);
}
prefs_->ClearPref(kPrefTranslateLastDeniedTimeForLanguage);
prefs_->ClearPref(kPrefTranslateTooOftenDeniedForLanguage);
}
bool TranslatePrefs::IsBlockedLanguage(
const std::string& original_language) const {
return IsValueBlacklisted(kPrefTranslateBlockedLanguages, original_language);
}
void TranslatePrefs::BlockLanguage(const std::string& original_language) {
BlacklistValue(kPrefTranslateBlockedLanguages, original_language);
// Add the language to the language list at chrome://settings/languages.
std::string language = original_language;
translate::ToChromeLanguageSynonym(&language);
std::vector<std::string> languages;
GetLanguageList(&languages);
if (std::find(languages.begin(), languages.end(), language) ==
languages.end()) {
languages.push_back(language);
UpdateLanguageList(languages);
}
}
void TranslatePrefs::UnblockLanguage(const std::string& original_language) {
RemoveValueFromBlacklist(kPrefTranslateBlockedLanguages, original_language);
}
bool TranslatePrefs::IsSiteBlacklisted(const std::string& site) const {
return IsValueBlacklisted(kPrefTranslateSiteBlacklist, site);
}
void TranslatePrefs::BlacklistSite(const std::string& site) {
BlacklistValue(kPrefTranslateSiteBlacklist, site);
}
void TranslatePrefs::RemoveSiteFromBlacklist(const std::string& site) {
RemoveValueFromBlacklist(kPrefTranslateSiteBlacklist, site);
}
bool TranslatePrefs::IsLanguagePairWhitelisted(
const std::string& original_language,
const std::string& target_language) {
const base::DictionaryValue* dict =
prefs_->GetDictionary(kPrefTranslateWhitelists);
if (dict && !dict->empty()) {
std::string auto_target_lang;
if (dict->GetString(original_language, &auto_target_lang) &&
auto_target_lang == target_language)
return true;
}
return false;
}
void TranslatePrefs::WhitelistLanguagePair(const std::string& original_language,
const std::string& target_language) {
DictionaryPrefUpdate update(prefs_, kPrefTranslateWhitelists);
base::DictionaryValue* dict = update.Get();
if (!dict) {
NOTREACHED() << "Unregistered translate whitelist pref";
return;
}
dict->SetString(original_language, target_language);
}
void TranslatePrefs::RemoveLanguagePairFromWhitelist(
const std::string& original_language,
const std::string& target_language) {
DictionaryPrefUpdate update(prefs_, kPrefTranslateWhitelists);
base::DictionaryValue* dict = update.Get();
if (!dict) {
NOTREACHED() << "Unregistered translate whitelist pref";
return;
}
dict->Remove(original_language, NULL);
}
bool TranslatePrefs::HasBlockedLanguages() const {
return !IsListEmpty(kPrefTranslateBlockedLanguages);
}
void TranslatePrefs::ClearBlockedLanguages() {
prefs_->ClearPref(kPrefTranslateBlockedLanguages);
}
bool TranslatePrefs::HasBlacklistedSites() const {
return !IsListEmpty(kPrefTranslateSiteBlacklist);
}
void TranslatePrefs::ClearBlacklistedSites() {
prefs_->ClearPref(kPrefTranslateSiteBlacklist);
}
bool TranslatePrefs::HasWhitelistedLanguagePairs() const {
return !IsDictionaryEmpty(kPrefTranslateWhitelists);
}
void TranslatePrefs::ClearWhitelistedLanguagePairs() {
prefs_->ClearPref(kPrefTranslateWhitelists);
}
int TranslatePrefs::GetTranslationDeniedCount(
const std::string& language) const {
const base::DictionaryValue* dict =
prefs_->GetDictionary(kPrefTranslateDeniedCount);
int count = 0;
return dict->GetInteger(language, &count) ? count : 0;
}
void TranslatePrefs::IncrementTranslationDeniedCount(
const std::string& language) {
DictionaryPrefUpdate update(prefs_, kPrefTranslateDeniedCount);
base::DictionaryValue* dict = update.Get();
int count = 0;
dict->GetInteger(language, &count);
dict->SetInteger(language, count + 1);
}
void TranslatePrefs::ResetTranslationDeniedCount(const std::string& language) {
DictionaryPrefUpdate update(prefs_, kPrefTranslateDeniedCount);
update.Get()->SetInteger(language, 0);
}
int TranslatePrefs::GetTranslationIgnoredCount(
const std::string& language) const {
const base::DictionaryValue* dict =
prefs_->GetDictionary(kPrefTranslateIgnoredCount);
int count = 0;
return dict->GetInteger(language, &count) ? count : 0;
}
void TranslatePrefs::IncrementTranslationIgnoredCount(
const std::string& language) {
DictionaryPrefUpdate update(prefs_, kPrefTranslateIgnoredCount);
base::DictionaryValue* dict = update.Get();
int count = 0;
dict->GetInteger(language, &count);
dict->SetInteger(language, count + 1);
}
void TranslatePrefs::ResetTranslationIgnoredCount(const std::string& language) {
DictionaryPrefUpdate update(prefs_, kPrefTranslateIgnoredCount);
update.Get()->SetInteger(language, 0);
}
int TranslatePrefs::GetTranslationAcceptedCount(const std::string& language) {
const base::DictionaryValue* dict =
prefs_->GetDictionary(kPrefTranslateAcceptedCount);
int count = 0;
return dict->GetInteger(language, &count) ? count : 0;
}
void TranslatePrefs::IncrementTranslationAcceptedCount(
const std::string& language) {
DictionaryPrefUpdate update(prefs_, kPrefTranslateAcceptedCount);
base::DictionaryValue* dict = update.Get();
int count = 0;
dict->GetInteger(language, &count);
dict->SetInteger(language, count + 1);
}
void TranslatePrefs::ResetTranslationAcceptedCount(
const std::string& language) {
DictionaryPrefUpdate update(prefs_, kPrefTranslateAcceptedCount);
update.Get()->SetInteger(language, 0);
}
void TranslatePrefs::UpdateLastDeniedTime(const std::string& language) {
if (IsTooOftenDenied(language))
return;
DenialTimeUpdate update(prefs_, language, 2);
base::Time now = base::Time::Now();
base::Time oldest_denial_time = update.GetOldestDenialTime();
update.AddDenialTime(now);
if (oldest_denial_time.is_null())
return;
if (now - oldest_denial_time <= base::TimeDelta::FromDays(1)) {
DictionaryPrefUpdate update(prefs_,
kPrefTranslateTooOftenDeniedForLanguage);
update.Get()->SetBoolean(language, true);
}
}
bool TranslatePrefs::IsTooOftenDenied(const std::string& language) const {
if (base::FeatureList::IsEnabled(kTranslateUI2016Q2)) {
// In the new logic, we only hide the bubble if user denied it more than
// 3 times or the user ignored it more than 10 times.
return (GetTranslationDeniedCount(language) > 3) ||
(GetTranslationIgnoredCount(language) > 10);
} else {
const base::DictionaryValue* dict =
prefs_->GetDictionary(kPrefTranslateTooOftenDeniedForLanguage);
bool result = false;
return dict->GetBoolean(language, &result) ? result : false;
}
}
void TranslatePrefs::ResetDenialState() {
prefs_->ClearPref(kPrefTranslateLastDeniedTimeForLanguage);
prefs_->ClearPref(kPrefTranslateTooOftenDeniedForLanguage);
}
void TranslatePrefs::GetLanguageList(
std::vector<std::string>* languages) const {
DCHECK(languages);
DCHECK(languages->empty());
#if defined(OS_CHROMEOS)
const char* key = preferred_languages_pref_.c_str();
#else
const char* key = accept_languages_pref_.c_str();
#endif
*languages = base::SplitString(prefs_->GetString(key), ",",
base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
}
void TranslatePrefs::UpdateLanguageList(
const std::vector<std::string>& languages) {
#if defined(OS_CHROMEOS)
std::string languages_str = base::JoinString(languages, ",");
prefs_->SetString(preferred_languages_pref_.c_str(), languages_str);
#endif
// Save the same language list as accept languages preference as well, but we
// need to expand the language list, to make it more acceptable. For instance,
// some web sites don't understand 'en-US' but 'en'. See crosbug.com/9884.
std::vector<std::string> accept_languages;
ExpandLanguageCodes(languages, &accept_languages);
std::string accept_languages_str = base::JoinString(accept_languages, ",");
prefs_->SetString(accept_languages_pref_.c_str(), accept_languages_str);
}
bool TranslatePrefs::CanTranslateLanguage(
TranslateAcceptLanguages* accept_languages,
const std::string& language) {
bool can_be_accept_language =
TranslateAcceptLanguages::CanBeAcceptLanguage(language);
bool is_accept_language = accept_languages->IsAcceptLanguage(language);
// For the translate language experiment, blocklists can be overridden.
const std::string& app_locale =
TranslateDownloadManager::GetInstance()->application_locale();
std::string ui_lang = TranslateDownloadManager::GetLanguageCode(app_locale);
if (TranslateExperiment::ShouldOverrideBlocking(ui_lang, language))
return true;
// Don't translate any user black-listed languages. Checking
// |is_accept_language| is necessary because if the user eliminates the
// language from the preference, it is natural to forget whether or not
// the language should be translated. Checking |cannot_be_accept_language|
// is also necessary because some minor languages can't be selected in the
// language preference even though the language is available in Translate
// server.
if (IsBlockedLanguage(language) &&
(is_accept_language || !can_be_accept_language))
return false;
return true;
}
bool TranslatePrefs::ShouldAutoTranslate(const std::string& original_language,
std::string* target_language) {
const base::DictionaryValue* dict =
prefs_->GetDictionary(kPrefTranslateWhitelists);
if (dict && dict->GetString(original_language, target_language)) {
DCHECK(!target_language->empty());
return !target_language->empty();
}
return false;
}
// static
void TranslatePrefs::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterListPref(kPrefTranslateSiteBlacklist,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterDictionaryPref(
kPrefTranslateWhitelists,
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->RegisterListPref(kPrefTranslateBlockedLanguages,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterDictionaryPref(kPrefTranslateLastDeniedTimeForLanguage);
registry->RegisterDictionaryPref(
kPrefTranslateTooOftenDeniedForLanguage,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
}
// static
void TranslatePrefs::MigrateUserPrefs(PrefService* user_prefs,
const char* accept_languages_pref) {
// Old format of kPrefTranslateWhitelists
// - original language -> list of target langs to auto-translate
// - list of langs is in order of being enabled i.e. last in list is the
// most recent language that user enabled via
// Always translate |source_lang| to |target_lang|"
// - this results in a one-to-n relationship between source lang and target
// langs.
// New format:
// - original language -> one target language to auto-translate
// - each time that the user enables the "Always translate..." option, that
// target lang overwrites the previous one.
// - this results in a one-to-one relationship between source lang and target
// lang
// - we replace old list of target langs with the last target lang in list,
// assuming the last (i.e. most recent) target lang is what user wants to
// keep auto-translated.
DictionaryPrefUpdate update(user_prefs, kPrefTranslateWhitelists);
base::DictionaryValue* dict = update.Get();
if (dict && !dict->empty()) {
base::DictionaryValue::Iterator iter(*dict);
while (!iter.IsAtEnd()) {
const base::ListValue* list = NULL;
if (!iter.value().GetAsList(&list) || !list)
break; // Dictionary has either been migrated or new format.
std::string key = iter.key();
// Advance the iterator before removing the current element.
iter.Advance();
std::string target_lang;
if (list->empty() ||
!list->GetString(list->GetSize() - 1, &target_lang) ||
target_lang.empty()) {
dict->Remove(key, NULL);
} else {
dict->SetString(key, target_lang);
}
}
}
}
bool TranslatePrefs::IsValueInList(const base::ListValue* list,
const std::string& in_value) const {
for (size_t i = 0; i < list->GetSize(); ++i) {
std::string value;
if (list->GetString(i, &value) && value == in_value)
return true;
}
return false;
}
bool TranslatePrefs::IsValueBlacklisted(const char* pref_id,
const std::string& value) const {
const base::ListValue* blacklist = prefs_->GetList(pref_id);
return (blacklist && !blacklist->empty() && IsValueInList(blacklist, value));
}
void TranslatePrefs::BlacklistValue(const char* pref_id,
const std::string& value) {
{
ListPrefUpdate update(prefs_, pref_id);
base::ListValue* blacklist = update.Get();
if (!blacklist) {
NOTREACHED() << "Unregistered translate blacklist pref";
return;
}
blacklist->AppendString(value);
}
}
void TranslatePrefs::RemoveValueFromBlacklist(const char* pref_id,
const std::string& value) {
ListPrefUpdate update(prefs_, pref_id);
base::ListValue* blacklist = update.Get();
if (!blacklist) {
NOTREACHED() << "Unregistered translate blacklist pref";
return;
}
base::StringValue string_value(value);
blacklist->Remove(string_value, NULL);
}
bool TranslatePrefs::IsListEmpty(const char* pref_id) const {
const base::ListValue* blacklist = prefs_->GetList(pref_id);
return (blacklist == NULL || blacklist->empty());
}
bool TranslatePrefs::IsDictionaryEmpty(const char* pref_id) const {
const base::DictionaryValue* dict = prefs_->GetDictionary(pref_id);
return (dict == NULL || dict->empty());
}
} // namespace translate