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::kPrefTranslateLanguageBlacklist
[] =
21 "translate_language_blacklist";
22 const char TranslatePrefs::kPrefTranslateSiteBlacklist
[] =
23 "translate_site_blacklist";
24 const char TranslatePrefs::kPrefTranslateWhitelists
[] =
25 "translate_whitelists";
26 const char TranslatePrefs::kPrefTranslateDeniedCount
[] =
27 "translate_denied_count";
28 const char TranslatePrefs::kPrefTranslateAcceptedCount
[] =
29 "translate_accepted_count";
30 const char TranslatePrefs::kPrefTranslateBlockedLanguages
[] =
31 "translate_blocked_languages";
32 const char TranslatePrefs::kPrefTranslateLastDeniedTime
[] =
33 "translate_last_denied_time";
34 const char TranslatePrefs::kPrefTranslateTooOftenDenied
[] =
35 "translate_too_often_denied";
39 void GetBlacklistedLanguages(const PrefService
* prefs
,
40 std::vector
<std::string
>* languages
) {
42 DCHECK(languages
->empty());
44 const char* key
= TranslatePrefs::kPrefTranslateLanguageBlacklist
;
45 const base::ListValue
* list
= prefs
->GetList(key
);
46 for (base::ListValue::const_iterator it
= list
->begin();
47 it
!= list
->end(); ++it
) {
49 (*it
)->GetAsString(&lang
);
50 languages
->push_back(lang
);
54 // Expands language codes to make these more suitable for Accept-Language.
55 // Example: ['en-US', 'ja', 'en-CA'] => ['en-US', 'en', 'ja', 'en-CA'].
56 // 'en' won't appear twice as this function eliminates duplicates.
57 void ExpandLanguageCodes(const std::vector
<std::string
>& languages
,
58 std::vector
<std::string
>* expanded_languages
) {
59 DCHECK(expanded_languages
);
60 DCHECK(expanded_languages
->empty());
62 // used to eliminate duplicates.
63 std::set
<std::string
> seen
;
65 for (std::vector
<std::string
>::const_iterator it
= languages
.begin();
66 it
!= languages
.end(); ++it
) {
67 const std::string
& language
= *it
;
68 if (seen
.find(language
) == seen
.end()) {
69 expanded_languages
->push_back(language
);
70 seen
.insert(language
);
73 std::vector
<std::string
> tokens
;
74 base::SplitString(language
, '-', &tokens
);
75 if (tokens
.size() == 0)
77 const std::string
& main_part
= tokens
[0];
78 if (seen
.find(main_part
) == seen
.end()) {
79 expanded_languages
->push_back(main_part
);
80 seen
.insert(main_part
);
87 TranslatePrefs::TranslatePrefs(PrefService
* user_prefs
,
88 const char* accept_languages_pref
,
89 const char* preferred_languages_pref
)
90 : accept_languages_pref_(accept_languages_pref
),
92 #if defined(OS_CHROMEOS)
93 preferred_languages_pref_
= preferred_languages_pref
;
95 DCHECK(!preferred_languages_pref
);
99 void TranslatePrefs::ResetToDefaults() {
100 ClearBlockedLanguages();
101 ClearBlacklistedSites();
102 ClearWhitelistedLanguagePairs();
104 std::vector
<std::string
> languages
;
105 GetLanguageList(&languages
);
106 for (std::vector
<std::string
>::const_iterator it
= languages
.begin();
107 it
!= languages
.end(); ++it
) {
108 const std::string
& language
= *it
;
109 ResetTranslationAcceptedCount(language
);
110 ResetTranslationDeniedCount(language
);
113 prefs_
->ClearPref(kPrefTranslateLastDeniedTime
);
114 prefs_
->ClearPref(kPrefTranslateTooOftenDenied
);
117 bool TranslatePrefs::IsBlockedLanguage(
118 const std::string
& original_language
) const {
119 return IsValueBlacklisted(kPrefTranslateBlockedLanguages
,
123 void TranslatePrefs::BlockLanguage(const std::string
& original_language
) {
124 BlacklistValue(kPrefTranslateBlockedLanguages
, original_language
);
126 // Add the language to the language list at chrome://settings/languages.
127 std::string language
= original_language
;
128 translate::ToChromeLanguageSynonym(&language
);
130 std::vector
<std::string
> languages
;
131 GetLanguageList(&languages
);
133 if (std::find(languages
.begin(), languages
.end(), language
) ==
135 languages
.push_back(language
);
136 UpdateLanguageList(languages
);
140 void TranslatePrefs::UnblockLanguage(const std::string
& original_language
) {
141 RemoveValueFromBlacklist(kPrefTranslateBlockedLanguages
, original_language
);
144 void TranslatePrefs::RemoveLanguageFromLegacyBlacklist(
145 const std::string
& original_language
) {
146 RemoveValueFromBlacklist(kPrefTranslateLanguageBlacklist
, original_language
);
149 bool TranslatePrefs::IsSiteBlacklisted(const std::string
& site
) const {
150 return IsValueBlacklisted(kPrefTranslateSiteBlacklist
, site
);
153 void TranslatePrefs::BlacklistSite(const std::string
& site
) {
154 BlacklistValue(kPrefTranslateSiteBlacklist
, site
);
157 void TranslatePrefs::RemoveSiteFromBlacklist(const std::string
& site
) {
158 RemoveValueFromBlacklist(kPrefTranslateSiteBlacklist
, site
);
161 bool TranslatePrefs::IsLanguagePairWhitelisted(
162 const std::string
& original_language
,
163 const std::string
& target_language
) {
164 const base::DictionaryValue
* dict
=
165 prefs_
->GetDictionary(kPrefTranslateWhitelists
);
166 if (dict
&& !dict
->empty()) {
167 std::string auto_target_lang
;
168 if (dict
->GetString(original_language
, &auto_target_lang
) &&
169 auto_target_lang
== target_language
)
175 void TranslatePrefs::WhitelistLanguagePair(const std::string
& original_language
,
176 const std::string
& target_language
) {
177 DictionaryPrefUpdate
update(prefs_
, kPrefTranslateWhitelists
);
178 base::DictionaryValue
* dict
= update
.Get();
180 NOTREACHED() << "Unregistered translate whitelist pref";
183 dict
->SetString(original_language
, target_language
);
186 void TranslatePrefs::RemoveLanguagePairFromWhitelist(
187 const std::string
& original_language
,
188 const std::string
& target_language
) {
189 DictionaryPrefUpdate
update(prefs_
, kPrefTranslateWhitelists
);
190 base::DictionaryValue
* dict
= update
.Get();
192 NOTREACHED() << "Unregistered translate whitelist pref";
195 dict
->Remove(original_language
, NULL
);
198 bool TranslatePrefs::HasBlockedLanguages() const {
199 return !IsListEmpty(kPrefTranslateBlockedLanguages
);
202 void TranslatePrefs::ClearBlockedLanguages() {
203 prefs_
->ClearPref(kPrefTranslateBlockedLanguages
);
206 bool TranslatePrefs::HasBlacklistedSites() const {
207 return !IsListEmpty(kPrefTranslateSiteBlacklist
);
210 void TranslatePrefs::ClearBlacklistedSites() {
211 prefs_
->ClearPref(kPrefTranslateSiteBlacklist
);
214 bool TranslatePrefs::HasWhitelistedLanguagePairs() const {
215 return !IsDictionaryEmpty(kPrefTranslateWhitelists
);
218 void TranslatePrefs::ClearWhitelistedLanguagePairs() {
219 prefs_
->ClearPref(kPrefTranslateWhitelists
);
222 int TranslatePrefs::GetTranslationDeniedCount(
223 const std::string
& language
) const {
224 const base::DictionaryValue
* dict
=
225 prefs_
->GetDictionary(kPrefTranslateDeniedCount
);
227 return dict
->GetInteger(language
, &count
) ? count
: 0;
230 void TranslatePrefs::IncrementTranslationDeniedCount(
231 const std::string
& language
) {
232 DictionaryPrefUpdate
update(prefs_
, kPrefTranslateDeniedCount
);
233 base::DictionaryValue
* dict
= update
.Get();
236 dict
->GetInteger(language
, &count
);
237 dict
->SetInteger(language
, count
+ 1);
240 void TranslatePrefs::ResetTranslationDeniedCount(const std::string
& language
) {
241 DictionaryPrefUpdate
update(prefs_
, kPrefTranslateDeniedCount
);
242 update
.Get()->SetInteger(language
, 0);
245 int TranslatePrefs::GetTranslationAcceptedCount(const std::string
& language
) {
246 const base::DictionaryValue
* dict
=
247 prefs_
->GetDictionary(kPrefTranslateAcceptedCount
);
249 return dict
->GetInteger(language
, &count
) ? count
: 0;
252 void TranslatePrefs::IncrementTranslationAcceptedCount(
253 const std::string
& language
) {
254 DictionaryPrefUpdate
update(prefs_
, kPrefTranslateAcceptedCount
);
255 base::DictionaryValue
* dict
= update
.Get();
257 dict
->GetInteger(language
, &count
);
258 dict
->SetInteger(language
, count
+ 1);
261 void TranslatePrefs::ResetTranslationAcceptedCount(
262 const std::string
& language
) {
263 DictionaryPrefUpdate
update(prefs_
, kPrefTranslateAcceptedCount
);
264 update
.Get()->SetInteger(language
, 0);
267 void TranslatePrefs::UpdateLastDeniedTime() {
268 if (IsTooOftenDenied())
271 double time
= prefs_
->GetDouble(kPrefTranslateLastDeniedTime
);
272 base::Time last_closed_time
= base::Time::FromJsTime(time
);
273 base::Time now
= base::Time::Now();
274 prefs_
->SetDouble(kPrefTranslateLastDeniedTime
, now
.ToJsTime());
275 if (now
- last_closed_time
<= base::TimeDelta::FromDays(1))
276 prefs_
->SetBoolean(kPrefTranslateTooOftenDenied
, true);
279 bool TranslatePrefs::IsTooOftenDenied() const {
280 return prefs_
->GetBoolean(kPrefTranslateTooOftenDenied
);
283 void TranslatePrefs::ResetDenialState() {
284 prefs_
->SetDouble(kPrefTranslateLastDeniedTime
, 0);
285 prefs_
->SetBoolean(kPrefTranslateTooOftenDenied
, false);
288 void TranslatePrefs::GetLanguageList(std::vector
<std::string
>* languages
) {
290 DCHECK(languages
->empty());
292 #if defined(OS_CHROMEOS)
293 const char* key
= preferred_languages_pref_
.c_str();
295 const char* key
= accept_languages_pref_
.c_str();
298 std::string languages_str
= prefs_
->GetString(key
);
299 base::SplitString(languages_str
, ',', languages
);
302 void TranslatePrefs::UpdateLanguageList(
303 const std::vector
<std::string
>& languages
) {
304 #if defined(OS_CHROMEOS)
305 std::string languages_str
= JoinString(languages
, ',');
306 prefs_
->SetString(preferred_languages_pref_
.c_str(), languages_str
);
309 // Save the same language list as accept languages preference as well, but we
310 // need to expand the language list, to make it more acceptable. For instance,
311 // some web sites don't understand 'en-US' but 'en'. See crosbug.com/9884.
312 std::vector
<std::string
> accept_languages
;
313 ExpandLanguageCodes(languages
, &accept_languages
);
314 std::string accept_languages_str
= JoinString(accept_languages
, ',');
315 prefs_
->SetString(accept_languages_pref_
.c_str(), accept_languages_str
);
318 bool TranslatePrefs::CanTranslateLanguage(
319 TranslateAcceptLanguages
* accept_languages
,
320 const std::string
& language
) {
321 bool can_be_accept_language
=
322 TranslateAcceptLanguages::CanBeAcceptLanguage(language
);
323 bool is_accept_language
= accept_languages
->IsAcceptLanguage(language
);
325 // Don't translate any user black-listed languages. Checking
326 // |is_accept_language| is necessary because if the user eliminates the
327 // language from the preference, it is natural to forget whether or not
328 // the language should be translated. Checking |cannot_be_accept_language|
329 // is also necessary because some minor languages can't be selected in the
330 // language preference even though the language is available in Translate
332 if (IsBlockedLanguage(language
) &&
333 (is_accept_language
|| !can_be_accept_language
))
339 bool TranslatePrefs::ShouldAutoTranslate(const std::string
& original_language
,
340 std::string
* target_language
) {
341 const base::DictionaryValue
* dict
=
342 prefs_
->GetDictionary(kPrefTranslateWhitelists
);
343 if (dict
&& dict
->GetString(original_language
, target_language
)) {
344 DCHECK(!target_language
->empty());
345 return !target_language
->empty();
351 void TranslatePrefs::RegisterProfilePrefs(
352 user_prefs::PrefRegistrySyncable
* registry
) {
353 registry
->RegisterListPref(kPrefTranslateLanguageBlacklist
,
354 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF
);
355 registry
->RegisterListPref(kPrefTranslateSiteBlacklist
,
356 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF
);
357 registry
->RegisterDictionaryPref(
358 kPrefTranslateWhitelists
,
359 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF
);
360 registry
->RegisterDictionaryPref(
361 kPrefTranslateDeniedCount
,
362 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF
);
363 registry
->RegisterDictionaryPref(
364 kPrefTranslateAcceptedCount
,
365 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF
);
366 registry
->RegisterListPref(kPrefTranslateBlockedLanguages
,
367 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF
);
368 registry
->RegisterDoublePref(
369 kPrefTranslateLastDeniedTime
, 0,
370 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
371 registry
->RegisterBooleanPref(
372 kPrefTranslateTooOftenDenied
, false,
373 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF
);
377 void TranslatePrefs::MigrateUserPrefs(PrefService
* user_prefs
,
378 const char* accept_languages_pref
) {
379 // Old format of kPrefTranslateWhitelists
380 // - original language -> list of target langs to auto-translate
381 // - list of langs is in order of being enabled i.e. last in list is the
382 // most recent language that user enabled via
383 // Always translate |source_lang| to |target_lang|"
384 // - this results in a one-to-n relationship between source lang and target
387 // - original language -> one target language to auto-translate
388 // - each time that the user enables the "Always translate..." option, that
389 // target lang overwrites the previous one.
390 // - this results in a one-to-one relationship between source lang and target
392 // - we replace old list of target langs with the last target lang in list,
393 // assuming the last (i.e. most recent) target lang is what user wants to
394 // keep auto-translated.
395 DictionaryPrefUpdate
update(user_prefs
, kPrefTranslateWhitelists
);
396 base::DictionaryValue
* dict
= update
.Get();
397 if (dict
&& !dict
->empty()) {
398 base::DictionaryValue::Iterator
iter(*dict
);
399 while (!iter
.IsAtEnd()) {
400 const base::ListValue
* list
= NULL
;
401 if (!iter
.value().GetAsList(&list
) || !list
)
402 break; // Dictionary has either been migrated or new format.
403 std::string key
= iter
.key();
404 // Advance the iterator before removing the current element.
406 std::string target_lang
;
408 !list
->GetString(list
->GetSize() - 1, &target_lang
) ||
409 target_lang
.empty()) {
410 dict
->Remove(key
, NULL
);
412 dict
->SetString(key
, target_lang
);
417 // Get the union of the blacklist and the Accept languages, and set this to
418 // the new language set 'translate_blocked_languages'. This is used for the
419 // settings UI for Translate and configration to determine which langauage
420 // should be translated instead of the blacklist. The blacklist is no longer
421 // used after launching the settings UI.
422 // After that, Set 'translate_languages_not_translate' to Accept languages to
423 // enable settings for users.
424 bool merged
= user_prefs
->HasPrefPath(kPrefTranslateBlockedLanguages
);
427 std::vector
<std::string
> blacklisted_languages
;
428 GetBlacklistedLanguages(user_prefs
, &blacklisted_languages
);
430 std::string accept_languages_str
=
431 user_prefs
->GetString(accept_languages_pref
);
432 std::vector
<std::string
> accept_languages
;
433 base::SplitString(accept_languages_str
, ',', &accept_languages
);
435 std::vector
<std::string
> blocked_languages
;
436 CreateBlockedLanguages(
437 &blocked_languages
, blacklisted_languages
, accept_languages
);
439 // Create the new preference kPrefTranslateBlockedLanguages.
441 base::ListValue blocked_languages_list
;
442 for (std::vector
<std::string
>::const_iterator it
=
443 blocked_languages
.begin();
444 it
!= blocked_languages
.end(); ++it
) {
445 blocked_languages_list
.Append(new base::StringValue(*it
));
447 ListPrefUpdate
update(user_prefs
, kPrefTranslateBlockedLanguages
);
448 base::ListValue
* list
= update
.Get();
449 DCHECK(list
!= NULL
);
450 list
->Swap(&blocked_languages_list
);
453 // Update kAcceptLanguages
454 for (std::vector
<std::string
>::const_iterator it
=
455 blocked_languages
.begin();
456 it
!= blocked_languages
.end(); ++it
) {
457 std::string lang
= *it
;
458 translate::ToChromeLanguageSynonym(&lang
);
460 std::find(accept_languages
.begin(), accept_languages
.end(), lang
) ==
461 accept_languages
.end();
463 accept_languages
.push_back(lang
);
466 std::string new_accept_languages_str
= JoinString(accept_languages
, ",");
467 user_prefs
->SetString(accept_languages_pref
, new_accept_languages_str
);
472 void TranslatePrefs::CreateBlockedLanguages(
473 std::vector
<std::string
>* blocked_languages
,
474 const std::vector
<std::string
>& blacklisted_languages
,
475 const std::vector
<std::string
>& accept_languages
) {
476 DCHECK(blocked_languages
);
477 DCHECK(blocked_languages
->empty());
479 std::set
<std::string
> result
;
481 for (std::vector
<std::string
>::const_iterator it
=
482 blacklisted_languages
.begin();
483 it
!= blacklisted_languages
.end(); ++it
) {
487 const std::string
& app_locale
=
488 TranslateDownloadManager::GetInstance()->application_locale();
489 std::string ui_lang
= TranslateDownloadManager::GetLanguageCode(app_locale
);
490 bool is_ui_english
= ui_lang
== "en" ||
491 StartsWithASCII(ui_lang
, "en-", false);
493 for (std::vector
<std::string
>::const_iterator it
= accept_languages
.begin();
494 it
!= accept_languages
.end(); ++it
) {
495 std::string converted_lang
= ConvertLangCodeForTranslation(*it
);
497 // Regarding http://crbug.com/36182, even though English exists in Accept
498 // language list, English could be translated on non-English locale.
499 if (converted_lang
== "en" && !is_ui_english
)
502 result
.insert(converted_lang
);
505 blocked_languages
->insert(
506 blocked_languages
->begin(), result
.begin(), result
.end());
510 std::string
TranslatePrefs::ConvertLangCodeForTranslation(
511 const std::string
& lang
) {
512 std::vector
<std::string
> tokens
;
513 base::SplitString(lang
, '-', &tokens
);
514 if (tokens
.size() < 1)
517 std::string main_part
= tokens
[0];
519 // Translate doesn't support General Chinese and the sub code is necessary.
520 if (main_part
== "zh")
523 translate::ToTranslateLanguageSynonym(&main_part
);
527 bool TranslatePrefs::IsValueInList(const base::ListValue
* list
,
528 const std::string
& in_value
) const {
529 for (size_t i
= 0; i
< list
->GetSize(); ++i
) {
531 if (list
->GetString(i
, &value
) && value
== in_value
)
537 bool TranslatePrefs::IsValueBlacklisted(const char* pref_id
,
538 const std::string
& value
) const {
539 const base::ListValue
* blacklist
= prefs_
->GetList(pref_id
);
540 return (blacklist
&& !blacklist
->empty() && IsValueInList(blacklist
, value
));
543 void TranslatePrefs::BlacklistValue(const char* pref_id
,
544 const std::string
& value
) {
546 ListPrefUpdate
update(prefs_
, pref_id
);
547 base::ListValue
* blacklist
= update
.Get();
549 NOTREACHED() << "Unregistered translate blacklist pref";
552 blacklist
->Append(new base::StringValue(value
));
556 void TranslatePrefs::RemoveValueFromBlacklist(const char* pref_id
,
557 const std::string
& value
) {
558 ListPrefUpdate
update(prefs_
, pref_id
);
559 base::ListValue
* blacklist
= update
.Get();
561 NOTREACHED() << "Unregistered translate blacklist pref";
564 base::StringValue
string_value(value
);
565 blacklist
->Remove(string_value
, NULL
);
568 bool TranslatePrefs::IsListEmpty(const char* pref_id
) const {
569 const base::ListValue
* blacklist
= prefs_
->GetList(pref_id
);
570 return (blacklist
== NULL
|| blacklist
->empty());
573 bool TranslatePrefs::IsDictionaryEmpty(const char* pref_id
) const {
574 const base::DictionaryValue
* dict
= prefs_
->GetDictionary(pref_id
);
575 return (dict
== NULL
|| dict
->empty());
578 } // namespace translate