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"
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"
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
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
48 void GetBlacklistedLanguages(const PrefService
* prefs
,
49 std::vector
<std::string
>* 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
) {
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)
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
);
96 DenialTimeUpdate::DenialTimeUpdate(PrefService
* prefs
,
97 const std::string
& language
,
98 size_t max_denial_count
)
99 : denial_time_dict_update_(
101 TranslatePrefs::kPrefTranslateLastDeniedTimeForLanguage
),
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() {
113 // Any consumer of GetDenialTimes _will_ write to them, so let's get an
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_
);
123 time_list_
= new base::ListValue();
124 double oldest_denial_time
= 0;
126 has_value
&& denial_value
->GetAsDouble(&oldest_denial_time
);
128 time_list_
->AppendDouble(oldest_denial_time
);
129 denial_time_dict
->Set(language_
, make_scoped_ptr(time_list_
));
134 base::Time
DenialTimeUpdate::GetOldestDenialTime() {
136 bool result
= GetDenialTimes()->GetDouble(0, &oldest_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
),
155 #if defined(OS_CHROMEOS)
156 preferred_languages_pref_
= preferred_languages_pref
;
158 DCHECK(!preferred_languages_pref
);
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
,
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
) ==
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
)
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();
243 NOTREACHED() << "Unregistered translate whitelist pref";
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();
255 NOTREACHED() << "Unregistered translate whitelist pref";
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
);
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();
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
);
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();
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
))
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())
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
);
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
) {
363 DCHECK(languages
->empty());
365 #if defined(OS_CHROMEOS)
366 const char* key
= preferred_languages_pref_
.c_str();
368 const char* key
= accept_languages_pref_
.c_str();
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
);
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
405 if (IsBlockedLanguage(language
) &&
406 (is_accept_language
|| !can_be_accept_language
))
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();
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
);
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
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
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.
477 std::string target_lang
;
479 !list
->GetString(list
->GetSize() - 1, &target_lang
) ||
480 target_lang
.empty()) {
481 dict
->Remove(key
, NULL
);
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
);
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
);
530 std::find(accept_languages
.begin(), accept_languages
.end(), lang
) ==
531 accept_languages
.end();
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
);
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
) {
558 const std::string
& app_locale
=
559 TranslateDownloadManager::GetInstance()->application_locale();
560 std::string ui_lang
= TranslateDownloadManager::GetLanguageCode(app_locale
);
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
)
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
) {
586 if (list
->GetString(i
, &value
) && value
== in_value
)
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();
604 NOTREACHED() << "Unregistered translate blacklist pref";
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();
616 NOTREACHED() << "Unregistered translate blacklist pref";
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