Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / translate / core / browser / translate_prefs.cc
blobd02dddebff5796430a65b45d0747232107a4666a
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.
5 #include "components/translate/core/browser/translate_prefs.h"
7 #include <set>
9 #include "base/prefs/pref_service.h"
10 #include "base/prefs/scoped_user_pref_update.h"
11 #include "base/strings/string_split.h"
12 #include "base/strings/string_util.h"
13 #include "components/pref_registry/pref_registry_syncable.h"
14 #include "components/translate/core/browser/translate_accept_languages.h"
15 #include "components/translate/core/browser/translate_download_manager.h"
16 #include "components/translate/core/common/translate_util.h"
18 namespace translate {
20 const char TranslatePrefs::kPrefTranslateSiteBlacklist[] =
21 "translate_site_blacklist";
22 const char TranslatePrefs::kPrefTranslateWhitelists[] =
23 "translate_whitelists";
24 const char TranslatePrefs::kPrefTranslateDeniedCount[] =
25 "translate_denied_count_for_language";
26 const char TranslatePrefs::kPrefTranslateAcceptedCount[] =
27 "translate_accepted_count";
28 const char TranslatePrefs::kPrefTranslateBlockedLanguages[] =
29 "translate_blocked_languages";
30 const char TranslatePrefs::kPrefTranslateLastDeniedTimeForLanguage[] =
31 "translate_last_denied_time_for_language";
32 const char TranslatePrefs::kPrefTranslateTooOftenDeniedForLanguage[] =
33 "translate_too_often_denied_for_language";
35 // This property is deprecated but there is still some usages. Don't use this
36 // for new code.
37 static const char kPrefTranslateLanguageBlacklist[] =
38 "translate_language_blacklist";
40 // The below properties used to be used but now are deprecated. Don't use them
41 // since an old profile might have some values there.
43 // * translate_last_denied_time
44 // * translate_too_often_denied
46 namespace {
48 void GetBlacklistedLanguages(const PrefService* prefs,
49 std::vector<std::string>* languages) {
50 DCHECK(languages);
51 DCHECK(languages->empty());
53 const char* key = kPrefTranslateLanguageBlacklist;
54 const base::ListValue* list = prefs->GetList(key);
55 for (base::ListValue::const_iterator it = list->begin();
56 it != list->end(); ++it) {
57 std::string lang;
58 (*it)->GetAsString(&lang);
59 languages->push_back(lang);
63 // Expands language codes to make these more suitable for Accept-Language.
64 // Example: ['en-US', 'ja', 'en-CA'] => ['en-US', 'en', 'ja', 'en-CA'].
65 // 'en' won't appear twice as this function eliminates duplicates.
66 void ExpandLanguageCodes(const std::vector<std::string>& languages,
67 std::vector<std::string>* expanded_languages) {
68 DCHECK(expanded_languages);
69 DCHECK(expanded_languages->empty());
71 // used to eliminate duplicates.
72 std::set<std::string> seen;
74 for (std::vector<std::string>::const_iterator it = languages.begin();
75 it != languages.end(); ++it) {
76 const std::string& language = *it;
77 if (seen.find(language) == seen.end()) {
78 expanded_languages->push_back(language);
79 seen.insert(language);
82 std::vector<std::string> tokens = base::SplitString(
83 language, "-", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
84 if (tokens.size() == 0)
85 continue;
86 const std::string& main_part = tokens[0];
87 if (seen.find(main_part) == seen.end()) {
88 expanded_languages->push_back(main_part);
89 seen.insert(main_part);
94 } // namespace
96 DenialTimeUpdate::DenialTimeUpdate(PrefService* prefs,
97 const std::string& language,
98 size_t max_denial_count)
99 : denial_time_dict_update_(
100 prefs,
101 TranslatePrefs::kPrefTranslateLastDeniedTimeForLanguage),
102 language_(language),
103 max_denial_count_(max_denial_count),
104 time_list_(nullptr) {}
106 DenialTimeUpdate::~DenialTimeUpdate() {}
108 // Gets the list of timestamps when translation was denied.
109 base::ListValue* DenialTimeUpdate::GetDenialTimes() {
110 if (time_list_)
111 return time_list_;
113 // Any consumer of GetDenialTimes _will_ write to them, so let's get an
114 // update started.
115 base::DictionaryValue* denial_time_dict = denial_time_dict_update_.Get();
116 DCHECK(denial_time_dict);
118 base::Value* denial_value = nullptr;
119 bool has_value = denial_time_dict->Get(language_, &denial_value);
120 bool has_list = has_value && denial_value->GetAsList(&time_list_);
122 if (!has_list) {
123 time_list_ = new base::ListValue();
124 double oldest_denial_time = 0;
125 bool has_old_style =
126 has_value && denial_value->GetAsDouble(&oldest_denial_time);
127 if (has_old_style)
128 time_list_->AppendDouble(oldest_denial_time);
129 denial_time_dict->Set(language_, make_scoped_ptr(time_list_));
131 return time_list_;
134 base::Time DenialTimeUpdate::GetOldestDenialTime() {
135 double oldest_time;
136 bool result = GetDenialTimes()->GetDouble(0, &oldest_time);
137 if (!result)
138 return base::Time();
139 return base::Time::FromJsTime(oldest_time);
142 void DenialTimeUpdate::AddDenialTime(base::Time denial_time) {
143 DCHECK(GetDenialTimes());
144 GetDenialTimes()->AppendDouble(denial_time.ToJsTime());
146 while (GetDenialTimes()->GetSize() >= max_denial_count_)
147 GetDenialTimes()->Remove(0, nullptr);
150 TranslatePrefs::TranslatePrefs(PrefService* user_prefs,
151 const char* accept_languages_pref,
152 const char* preferred_languages_pref)
153 : accept_languages_pref_(accept_languages_pref),
154 prefs_(user_prefs) {
155 #if defined(OS_CHROMEOS)
156 preferred_languages_pref_ = preferred_languages_pref;
157 #else
158 DCHECK(!preferred_languages_pref);
159 #endif
162 void TranslatePrefs::ResetToDefaults() {
163 ClearBlockedLanguages();
164 ClearBlacklistedSites();
165 ClearWhitelistedLanguagePairs();
167 std::vector<std::string> languages;
168 GetLanguageList(&languages);
169 for (std::vector<std::string>::const_iterator it = languages.begin();
170 it != languages.end(); ++it) {
171 const std::string& language = *it;
172 ResetTranslationAcceptedCount(language);
173 ResetTranslationDeniedCount(language);
176 prefs_->ClearPref(kPrefTranslateLastDeniedTimeForLanguage);
177 prefs_->ClearPref(kPrefTranslateTooOftenDeniedForLanguage);
180 bool TranslatePrefs::IsBlockedLanguage(
181 const std::string& original_language) const {
182 return IsValueBlacklisted(kPrefTranslateBlockedLanguages,
183 original_language);
186 void TranslatePrefs::BlockLanguage(const std::string& original_language) {
187 BlacklistValue(kPrefTranslateBlockedLanguages, original_language);
189 // Add the language to the language list at chrome://settings/languages.
190 std::string language = original_language;
191 translate::ToChromeLanguageSynonym(&language);
193 std::vector<std::string> languages;
194 GetLanguageList(&languages);
196 if (std::find(languages.begin(), languages.end(), language) ==
197 languages.end()) {
198 languages.push_back(language);
199 UpdateLanguageList(languages);
203 void TranslatePrefs::UnblockLanguage(const std::string& original_language) {
204 RemoveValueFromBlacklist(kPrefTranslateBlockedLanguages, original_language);
207 void TranslatePrefs::RemoveLanguageFromLegacyBlacklist(
208 const std::string& original_language) {
209 RemoveValueFromBlacklist(kPrefTranslateLanguageBlacklist, original_language);
212 bool TranslatePrefs::IsSiteBlacklisted(const std::string& site) const {
213 return IsValueBlacklisted(kPrefTranslateSiteBlacklist, site);
216 void TranslatePrefs::BlacklistSite(const std::string& site) {
217 BlacklistValue(kPrefTranslateSiteBlacklist, site);
220 void TranslatePrefs::RemoveSiteFromBlacklist(const std::string& site) {
221 RemoveValueFromBlacklist(kPrefTranslateSiteBlacklist, site);
224 bool TranslatePrefs::IsLanguagePairWhitelisted(
225 const std::string& original_language,
226 const std::string& target_language) {
227 const base::DictionaryValue* dict =
228 prefs_->GetDictionary(kPrefTranslateWhitelists);
229 if (dict && !dict->empty()) {
230 std::string auto_target_lang;
231 if (dict->GetString(original_language, &auto_target_lang) &&
232 auto_target_lang == target_language)
233 return true;
235 return false;
238 void TranslatePrefs::WhitelistLanguagePair(const std::string& original_language,
239 const std::string& target_language) {
240 DictionaryPrefUpdate update(prefs_, kPrefTranslateWhitelists);
241 base::DictionaryValue* dict = update.Get();
242 if (!dict) {
243 NOTREACHED() << "Unregistered translate whitelist pref";
244 return;
246 dict->SetString(original_language, target_language);
249 void TranslatePrefs::RemoveLanguagePairFromWhitelist(
250 const std::string& original_language,
251 const std::string& target_language) {
252 DictionaryPrefUpdate update(prefs_, kPrefTranslateWhitelists);
253 base::DictionaryValue* dict = update.Get();
254 if (!dict) {
255 NOTREACHED() << "Unregistered translate whitelist pref";
256 return;
258 dict->Remove(original_language, NULL);
261 bool TranslatePrefs::HasBlockedLanguages() const {
262 return !IsListEmpty(kPrefTranslateBlockedLanguages);
265 void TranslatePrefs::ClearBlockedLanguages() {
266 prefs_->ClearPref(kPrefTranslateBlockedLanguages);
269 bool TranslatePrefs::HasBlacklistedSites() const {
270 return !IsListEmpty(kPrefTranslateSiteBlacklist);
273 void TranslatePrefs::ClearBlacklistedSites() {
274 prefs_->ClearPref(kPrefTranslateSiteBlacklist);
277 bool TranslatePrefs::HasWhitelistedLanguagePairs() const {
278 return !IsDictionaryEmpty(kPrefTranslateWhitelists);
281 void TranslatePrefs::ClearWhitelistedLanguagePairs() {
282 prefs_->ClearPref(kPrefTranslateWhitelists);
285 int TranslatePrefs::GetTranslationDeniedCount(
286 const std::string& language) const {
287 const base::DictionaryValue* dict =
288 prefs_->GetDictionary(kPrefTranslateDeniedCount);
289 int count = 0;
290 return dict->GetInteger(language, &count) ? count : 0;
293 void TranslatePrefs::IncrementTranslationDeniedCount(
294 const std::string& language) {
295 DictionaryPrefUpdate update(prefs_, kPrefTranslateDeniedCount);
296 base::DictionaryValue* dict = update.Get();
298 int count = 0;
299 dict->GetInteger(language, &count);
300 dict->SetInteger(language, count + 1);
303 void TranslatePrefs::ResetTranslationDeniedCount(const std::string& language) {
304 DictionaryPrefUpdate update(prefs_, kPrefTranslateDeniedCount);
305 update.Get()->SetInteger(language, 0);
308 int TranslatePrefs::GetTranslationAcceptedCount(const std::string& language) {
309 const base::DictionaryValue* dict =
310 prefs_->GetDictionary(kPrefTranslateAcceptedCount);
311 int count = 0;
312 return dict->GetInteger(language, &count) ? count : 0;
315 void TranslatePrefs::IncrementTranslationAcceptedCount(
316 const std::string& language) {
317 DictionaryPrefUpdate update(prefs_, kPrefTranslateAcceptedCount);
318 base::DictionaryValue* dict = update.Get();
319 int count = 0;
320 dict->GetInteger(language, &count);
321 dict->SetInteger(language, count + 1);
324 void TranslatePrefs::ResetTranslationAcceptedCount(
325 const std::string& language) {
326 DictionaryPrefUpdate update(prefs_, kPrefTranslateAcceptedCount);
327 update.Get()->SetInteger(language, 0);
330 void TranslatePrefs::UpdateLastDeniedTime(const std::string& language) {
331 if (IsTooOftenDenied(language))
332 return;
334 DenialTimeUpdate update(prefs_, language, 2);
335 base::Time now = base::Time::Now();
336 base::Time oldest_denial_time = update.GetOldestDenialTime();
337 update.AddDenialTime(now);
339 if (oldest_denial_time.is_null())
340 return;
342 if (now - oldest_denial_time <= base::TimeDelta::FromDays(1)) {
343 DictionaryPrefUpdate update(prefs_,
344 kPrefTranslateTooOftenDeniedForLanguage);
345 update.Get()->SetBoolean(language, true);
349 bool TranslatePrefs::IsTooOftenDenied(const std::string& language) const {
350 const base::DictionaryValue* dict =
351 prefs_->GetDictionary(kPrefTranslateTooOftenDeniedForLanguage);
352 bool result = false;
353 return dict->GetBoolean(language, &result) ? result : false;
356 void TranslatePrefs::ResetDenialState() {
357 prefs_->ClearPref(kPrefTranslateLastDeniedTimeForLanguage);
358 prefs_->ClearPref(kPrefTranslateTooOftenDeniedForLanguage);
361 void TranslatePrefs::GetLanguageList(std::vector<std::string>* languages) {
362 DCHECK(languages);
363 DCHECK(languages->empty());
365 #if defined(OS_CHROMEOS)
366 const char* key = preferred_languages_pref_.c_str();
367 #else
368 const char* key = accept_languages_pref_.c_str();
369 #endif
371 *languages = base::SplitString(prefs_->GetString(key), ",",
372 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
375 void TranslatePrefs::UpdateLanguageList(
376 const std::vector<std::string>& languages) {
377 #if defined(OS_CHROMEOS)
378 std::string languages_str = base::JoinString(languages, ",");
379 prefs_->SetString(preferred_languages_pref_.c_str(), languages_str);
380 #endif
382 // Save the same language list as accept languages preference as well, but we
383 // need to expand the language list, to make it more acceptable. For instance,
384 // some web sites don't understand 'en-US' but 'en'. See crosbug.com/9884.
385 std::vector<std::string> accept_languages;
386 ExpandLanguageCodes(languages, &accept_languages);
387 std::string accept_languages_str = base::JoinString(accept_languages, ",");
388 prefs_->SetString(accept_languages_pref_.c_str(), accept_languages_str);
391 bool TranslatePrefs::CanTranslateLanguage(
392 TranslateAcceptLanguages* accept_languages,
393 const std::string& language) {
394 bool can_be_accept_language =
395 TranslateAcceptLanguages::CanBeAcceptLanguage(language);
396 bool is_accept_language = accept_languages->IsAcceptLanguage(language);
398 // Don't translate any user black-listed languages. Checking
399 // |is_accept_language| is necessary because if the user eliminates the
400 // language from the preference, it is natural to forget whether or not
401 // the language should be translated. Checking |cannot_be_accept_language|
402 // is also necessary because some minor languages can't be selected in the
403 // language preference even though the language is available in Translate
404 // server.
405 if (IsBlockedLanguage(language) &&
406 (is_accept_language || !can_be_accept_language))
407 return false;
409 return true;
412 bool TranslatePrefs::ShouldAutoTranslate(const std::string& original_language,
413 std::string* target_language) {
414 const base::DictionaryValue* dict =
415 prefs_->GetDictionary(kPrefTranslateWhitelists);
416 if (dict && dict->GetString(original_language, target_language)) {
417 DCHECK(!target_language->empty());
418 return !target_language->empty();
420 return false;
423 // static
424 void TranslatePrefs::RegisterProfilePrefs(
425 user_prefs::PrefRegistrySyncable* registry) {
426 registry->RegisterListPref(kPrefTranslateLanguageBlacklist,
427 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
428 registry->RegisterListPref(kPrefTranslateSiteBlacklist,
429 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
430 registry->RegisterDictionaryPref(
431 kPrefTranslateWhitelists,
432 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
433 registry->RegisterDictionaryPref(
434 kPrefTranslateDeniedCount,
435 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
436 registry->RegisterDictionaryPref(
437 kPrefTranslateAcceptedCount,
438 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
439 registry->RegisterListPref(kPrefTranslateBlockedLanguages,
440 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
441 registry->RegisterDictionaryPref(kPrefTranslateLastDeniedTimeForLanguage);
442 registry->RegisterDictionaryPref(
443 kPrefTranslateTooOftenDeniedForLanguage,
444 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
447 // static
448 void TranslatePrefs::MigrateUserPrefs(PrefService* user_prefs,
449 const char* accept_languages_pref) {
450 // Old format of kPrefTranslateWhitelists
451 // - original language -> list of target langs to auto-translate
452 // - list of langs is in order of being enabled i.e. last in list is the
453 // most recent language that user enabled via
454 // Always translate |source_lang| to |target_lang|"
455 // - this results in a one-to-n relationship between source lang and target
456 // langs.
457 // New format:
458 // - original language -> one target language to auto-translate
459 // - each time that the user enables the "Always translate..." option, that
460 // target lang overwrites the previous one.
461 // - this results in a one-to-one relationship between source lang and target
462 // lang
463 // - we replace old list of target langs with the last target lang in list,
464 // assuming the last (i.e. most recent) target lang is what user wants to
465 // keep auto-translated.
466 DictionaryPrefUpdate update(user_prefs, kPrefTranslateWhitelists);
467 base::DictionaryValue* dict = update.Get();
468 if (dict && !dict->empty()) {
469 base::DictionaryValue::Iterator iter(*dict);
470 while (!iter.IsAtEnd()) {
471 const base::ListValue* list = NULL;
472 if (!iter.value().GetAsList(&list) || !list)
473 break; // Dictionary has either been migrated or new format.
474 std::string key = iter.key();
475 // Advance the iterator before removing the current element.
476 iter.Advance();
477 std::string target_lang;
478 if (list->empty() ||
479 !list->GetString(list->GetSize() - 1, &target_lang) ||
480 target_lang.empty()) {
481 dict->Remove(key, NULL);
482 } else {
483 dict->SetString(key, target_lang);
488 // Get the union of the blacklist and the Accept languages, and set this to
489 // the new language set 'translate_blocked_languages'. This is used for the
490 // settings UI for Translate and configration to determine which langauage
491 // should be translated instead of the blacklist. The blacklist is no longer
492 // used after launching the settings UI.
493 // After that, Set 'translate_languages_not_translate' to Accept languages to
494 // enable settings for users.
495 bool merged = user_prefs->HasPrefPath(kPrefTranslateBlockedLanguages);
497 if (!merged) {
498 std::vector<std::string> blacklisted_languages;
499 GetBlacklistedLanguages(user_prefs, &blacklisted_languages);
501 std::vector<std::string> accept_languages = base::SplitString(
502 user_prefs->GetString(accept_languages_pref), ",",
503 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
505 std::vector<std::string> blocked_languages;
506 CreateBlockedLanguages(
507 &blocked_languages, blacklisted_languages, accept_languages);
509 // Create the new preference kPrefTranslateBlockedLanguages.
511 base::ListValue blocked_languages_list;
512 for (std::vector<std::string>::const_iterator it =
513 blocked_languages.begin();
514 it != blocked_languages.end(); ++it) {
515 blocked_languages_list.Append(new base::StringValue(*it));
517 ListPrefUpdate update(user_prefs, kPrefTranslateBlockedLanguages);
518 base::ListValue* list = update.Get();
519 DCHECK(list != NULL);
520 list->Swap(&blocked_languages_list);
523 // Update kAcceptLanguages
524 for (std::vector<std::string>::const_iterator it =
525 blocked_languages.begin();
526 it != blocked_languages.end(); ++it) {
527 std::string lang = *it;
528 translate::ToChromeLanguageSynonym(&lang);
529 bool not_found =
530 std::find(accept_languages.begin(), accept_languages.end(), lang) ==
531 accept_languages.end();
532 if (not_found)
533 accept_languages.push_back(lang);
536 std::string new_accept_languages_str =
537 base::JoinString(accept_languages, ",");
538 user_prefs->SetString(accept_languages_pref, new_accept_languages_str);
542 // static
543 void TranslatePrefs::CreateBlockedLanguages(
544 std::vector<std::string>* blocked_languages,
545 const std::vector<std::string>& blacklisted_languages,
546 const std::vector<std::string>& accept_languages) {
547 DCHECK(blocked_languages);
548 DCHECK(blocked_languages->empty());
550 std::set<std::string> result;
552 for (std::vector<std::string>::const_iterator it =
553 blacklisted_languages.begin();
554 it != blacklisted_languages.end(); ++it) {
555 result.insert(*it);
558 const std::string& app_locale =
559 TranslateDownloadManager::GetInstance()->application_locale();
560 std::string ui_lang = TranslateDownloadManager::GetLanguageCode(app_locale);
561 bool is_ui_english =
562 ui_lang == "en" ||
563 base::StartsWith(ui_lang, "en-", base::CompareCase::INSENSITIVE_ASCII);
565 for (std::vector<std::string>::const_iterator it = accept_languages.begin();
566 it != accept_languages.end(); ++it) {
567 std::string lang = *it;
568 translate::ToTranslateLanguageSynonym(&lang);
570 // Regarding http://crbug.com/36182, even though English exists in Accept
571 // language list, English could be translated on non-English locale.
572 if (lang == "en" && !is_ui_english)
573 continue;
575 result.insert(lang);
578 blocked_languages->insert(
579 blocked_languages->begin(), result.begin(), result.end());
582 bool TranslatePrefs::IsValueInList(const base::ListValue* list,
583 const std::string& in_value) const {
584 for (size_t i = 0; i < list->GetSize(); ++i) {
585 std::string value;
586 if (list->GetString(i, &value) && value == in_value)
587 return true;
589 return false;
592 bool TranslatePrefs::IsValueBlacklisted(const char* pref_id,
593 const std::string& value) const {
594 const base::ListValue* blacklist = prefs_->GetList(pref_id);
595 return (blacklist && !blacklist->empty() && IsValueInList(blacklist, value));
598 void TranslatePrefs::BlacklistValue(const char* pref_id,
599 const std::string& value) {
601 ListPrefUpdate update(prefs_, pref_id);
602 base::ListValue* blacklist = update.Get();
603 if (!blacklist) {
604 NOTREACHED() << "Unregistered translate blacklist pref";
605 return;
607 blacklist->Append(new base::StringValue(value));
611 void TranslatePrefs::RemoveValueFromBlacklist(const char* pref_id,
612 const std::string& value) {
613 ListPrefUpdate update(prefs_, pref_id);
614 base::ListValue* blacklist = update.Get();
615 if (!blacklist) {
616 NOTREACHED() << "Unregistered translate blacklist pref";
617 return;
619 base::StringValue string_value(value);
620 blacklist->Remove(string_value, NULL);
623 bool TranslatePrefs::IsListEmpty(const char* pref_id) const {
624 const base::ListValue* blacklist = prefs_->GetList(pref_id);
625 return (blacklist == NULL || blacklist->empty());
628 bool TranslatePrefs::IsDictionaryEmpty(const char* pref_id) const {
629 const base::DictionaryValue* dict = prefs_->GetDictionary(pref_id);
630 return (dict == NULL || dict->empty());
633 } // namespace translate