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.
4
5 #include "components/translate/core/browser/translate_prefs.h"
6
7 #include <set>
8
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"
17
18 const char TranslatePrefs::kPrefTranslateLanguageBlacklist[] =
19 "translate_language_blacklist";
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";
26 const char TranslatePrefs::kPrefTranslateAcceptedCount[] =
27 "translate_accepted_count";
28 const char TranslatePrefs::kPrefTranslateBlockedLanguages[] =
29 "translate_blocked_languages";
30
31 namespace {
32
GetBlacklistedLanguages(const PrefService * prefs,std::vector<std::string> * languages)33 void GetBlacklistedLanguages(const PrefService* prefs,
34 std::vector<std::string>* languages) {
35 DCHECK(languages);
36 DCHECK(languages->empty());
37
38 const char* key = TranslatePrefs::kPrefTranslateLanguageBlacklist;
39 const base::ListValue* list = prefs->GetList(key);
40 for (base::ListValue::const_iterator it = list->begin();
41 it != list->end(); ++it) {
42 std::string lang;
43 (*it)->GetAsString(&lang);
44 languages->push_back(lang);
45 }
46 }
47
48 // Expands language codes to make these more suitable for Accept-Language.
49 // Example: ['en-US', 'ja', 'en-CA'] => ['en-US', 'en', 'ja', 'en-CA'].
50 // 'en' won't appear twice as this function eliminates duplicates.
ExpandLanguageCodes(const std::vector<std::string> & languages,std::vector<std::string> * expanded_languages)51 void ExpandLanguageCodes(const std::vector<std::string>& languages,
52 std::vector<std::string>* expanded_languages) {
53 DCHECK(expanded_languages);
54 DCHECK(expanded_languages->empty());
55
56 // used to eliminate duplicates.
57 std::set<std::string> seen;
58
59 for (std::vector<std::string>::const_iterator it = languages.begin();
60 it != languages.end(); ++it) {
61 const std::string& language = *it;
62 if (seen.find(language) == seen.end()) {
63 expanded_languages->push_back(language);
64 seen.insert(language);
65 }
66
67 std::vector<std::string> tokens;
68 base::SplitString(language, '-', &tokens);
69 if (tokens.size() == 0)
70 continue;
71 const std::string& main_part = tokens[0];
72 if (seen.find(main_part) == seen.end()) {
73 expanded_languages->push_back(main_part);
74 seen.insert(main_part);
75 }
76 }
77 }
78
79 } // namespace
80
TranslatePrefs(PrefService * user_prefs,const char * accept_languages_pref,const char * preferred_languages_pref)81 TranslatePrefs::TranslatePrefs(PrefService* user_prefs,
82 const char* accept_languages_pref,
83 const char* preferred_languages_pref)
84 : accept_languages_pref_(accept_languages_pref),
85 prefs_(user_prefs) {
86 #if defined(OS_CHROMEOS)
87 preferred_languages_pref_ = preferred_languages_pref;
88 #else
89 DCHECK(!preferred_languages_pref);
90 #endif
91 }
92
ResetToDefaults()93 void TranslatePrefs::ResetToDefaults() {
94 ClearBlockedLanguages();
95 ClearBlacklistedSites();
96 ClearWhitelistedLanguagePairs();
97
98 std::vector<std::string> languages;
99 GetLanguageList(&languages);
100 for (std::vector<std::string>::const_iterator it = languages.begin();
101 it != languages.end(); ++it) {
102 const std::string& language = *it;
103 ResetTranslationAcceptedCount(language);
104 ResetTranslationDeniedCount(language);
105 }
106 }
107
IsBlockedLanguage(const std::string & original_language) const108 bool TranslatePrefs::IsBlockedLanguage(
109 const std::string& original_language) const {
110 return IsValueBlacklisted(kPrefTranslateBlockedLanguages,
111 original_language);
112 }
113
BlockLanguage(const std::string & original_language)114 void TranslatePrefs::BlockLanguage(const std::string& original_language) {
115 BlacklistValue(kPrefTranslateBlockedLanguages, original_language);
116
117 // Add the language to the language list at chrome://settings/languages.
118 std::string language = original_language;
119 translate::ToChromeLanguageSynonym(&language);
120
121 std::vector<std::string> languages;
122 GetLanguageList(&languages);
123
124 if (std::find(languages.begin(), languages.end(), language) ==
125 languages.end()) {
126 languages.push_back(language);
127 UpdateLanguageList(languages);
128 }
129 }
130
UnblockLanguage(const std::string & original_language)131 void TranslatePrefs::UnblockLanguage(const std::string& original_language) {
132 RemoveValueFromBlacklist(kPrefTranslateBlockedLanguages, original_language);
133 }
134
RemoveLanguageFromLegacyBlacklist(const std::string & original_language)135 void TranslatePrefs::RemoveLanguageFromLegacyBlacklist(
136 const std::string& original_language) {
137 RemoveValueFromBlacklist(kPrefTranslateLanguageBlacklist, original_language);
138 }
139
IsSiteBlacklisted(const std::string & site) const140 bool TranslatePrefs::IsSiteBlacklisted(const std::string& site) const {
141 return IsValueBlacklisted(kPrefTranslateSiteBlacklist, site);
142 }
143
BlacklistSite(const std::string & site)144 void TranslatePrefs::BlacklistSite(const std::string& site) {
145 BlacklistValue(kPrefTranslateSiteBlacklist, site);
146 }
147
RemoveSiteFromBlacklist(const std::string & site)148 void TranslatePrefs::RemoveSiteFromBlacklist(const std::string& site) {
149 RemoveValueFromBlacklist(kPrefTranslateSiteBlacklist, site);
150 }
151
IsLanguagePairWhitelisted(const std::string & original_language,const std::string & target_language)152 bool TranslatePrefs::IsLanguagePairWhitelisted(
153 const std::string& original_language,
154 const std::string& target_language) {
155 const base::DictionaryValue* dict =
156 prefs_->GetDictionary(kPrefTranslateWhitelists);
157 if (dict && !dict->empty()) {
158 std::string auto_target_lang;
159 if (dict->GetString(original_language, &auto_target_lang) &&
160 auto_target_lang == target_language)
161 return true;
162 }
163 return false;
164 }
165
WhitelistLanguagePair(const std::string & original_language,const std::string & target_language)166 void TranslatePrefs::WhitelistLanguagePair(const std::string& original_language,
167 const std::string& target_language) {
168 DictionaryPrefUpdate update(prefs_, kPrefTranslateWhitelists);
169 base::DictionaryValue* dict = update.Get();
170 if (!dict) {
171 NOTREACHED() << "Unregistered translate whitelist pref";
172 return;
173 }
174 dict->SetString(original_language, target_language);
175 }
176
RemoveLanguagePairFromWhitelist(const std::string & original_language,const std::string & target_language)177 void TranslatePrefs::RemoveLanguagePairFromWhitelist(
178 const std::string& original_language,
179 const std::string& target_language) {
180 DictionaryPrefUpdate update(prefs_, kPrefTranslateWhitelists);
181 base::DictionaryValue* dict = update.Get();
182 if (!dict) {
183 NOTREACHED() << "Unregistered translate whitelist pref";
184 return;
185 }
186 dict->Remove(original_language, NULL);
187 }
188
HasBlockedLanguages() const189 bool TranslatePrefs::HasBlockedLanguages() const {
190 return !IsListEmpty(kPrefTranslateBlockedLanguages);
191 }
192
ClearBlockedLanguages()193 void TranslatePrefs::ClearBlockedLanguages() {
194 prefs_->ClearPref(kPrefTranslateBlockedLanguages);
195 }
196
HasBlacklistedSites() const197 bool TranslatePrefs::HasBlacklistedSites() const {
198 return !IsListEmpty(kPrefTranslateSiteBlacklist);
199 }
200
ClearBlacklistedSites()201 void TranslatePrefs::ClearBlacklistedSites() {
202 prefs_->ClearPref(kPrefTranslateSiteBlacklist);
203 }
204
HasWhitelistedLanguagePairs() const205 bool TranslatePrefs::HasWhitelistedLanguagePairs() const {
206 return !IsDictionaryEmpty(kPrefTranslateWhitelists);
207 }
208
ClearWhitelistedLanguagePairs()209 void TranslatePrefs::ClearWhitelistedLanguagePairs() {
210 prefs_->ClearPref(kPrefTranslateWhitelists);
211 }
212
GetTranslationDeniedCount(const std::string & language) const213 int TranslatePrefs::GetTranslationDeniedCount(
214 const std::string& language) const {
215 const base::DictionaryValue* dict =
216 prefs_->GetDictionary(kPrefTranslateDeniedCount);
217 int count = 0;
218 return dict->GetInteger(language, &count) ? count : 0;
219 }
220
IncrementTranslationDeniedCount(const std::string & language)221 void TranslatePrefs::IncrementTranslationDeniedCount(
222 const std::string& language) {
223 DictionaryPrefUpdate update(prefs_, kPrefTranslateDeniedCount);
224 base::DictionaryValue* dict = update.Get();
225
226 int count = 0;
227 dict->GetInteger(language, &count);
228 dict->SetInteger(language, count + 1);
229 }
230
ResetTranslationDeniedCount(const std::string & language)231 void TranslatePrefs::ResetTranslationDeniedCount(const std::string& language) {
232 DictionaryPrefUpdate update(prefs_, kPrefTranslateDeniedCount);
233 update.Get()->SetInteger(language, 0);
234 }
235
GetTranslationAcceptedCount(const std::string & language)236 int TranslatePrefs::GetTranslationAcceptedCount(const std::string& language) {
237 const base::DictionaryValue* dict =
238 prefs_->GetDictionary(kPrefTranslateAcceptedCount);
239 int count = 0;
240 return dict->GetInteger(language, &count) ? count : 0;
241 }
242
IncrementTranslationAcceptedCount(const std::string & language)243 void TranslatePrefs::IncrementTranslationAcceptedCount(
244 const std::string& language) {
245 DictionaryPrefUpdate update(prefs_, kPrefTranslateAcceptedCount);
246 base::DictionaryValue* dict = update.Get();
247 int count = 0;
248 dict->GetInteger(language, &count);
249 dict->SetInteger(language, count + 1);
250 }
251
ResetTranslationAcceptedCount(const std::string & language)252 void TranslatePrefs::ResetTranslationAcceptedCount(
253 const std::string& language) {
254 DictionaryPrefUpdate update(prefs_, kPrefTranslateAcceptedCount);
255 update.Get()->SetInteger(language, 0);
256 }
257
GetLanguageList(std::vector<std::string> * languages)258 void TranslatePrefs::GetLanguageList(std::vector<std::string>* languages) {
259 DCHECK(languages);
260 DCHECK(languages->empty());
261
262 #if defined(OS_CHROMEOS)
263 const char* key = preferred_languages_pref_.c_str();
264 #else
265 const char* key = accept_languages_pref_.c_str();
266 #endif
267
268 std::string languages_str = prefs_->GetString(key);
269 base::SplitString(languages_str, ',', languages);
270 }
271
UpdateLanguageList(const std::vector<std::string> & languages)272 void TranslatePrefs::UpdateLanguageList(
273 const std::vector<std::string>& languages) {
274 #if defined(OS_CHROMEOS)
275 std::string languages_str = JoinString(languages, ',');
276 prefs_->SetString(preferred_languages_pref_.c_str(), languages_str);
277 #endif
278
279 // Save the same language list as accept languages preference as well, but we
280 // need to expand the language list, to make it more acceptable. For instance,
281 // some web sites don't understand 'en-US' but 'en'. See crosbug.com/9884.
282 std::vector<std::string> accept_languages;
283 ExpandLanguageCodes(languages, &accept_languages);
284 std::string accept_languages_str = JoinString(accept_languages, ',');
285 prefs_->SetString(accept_languages_pref_.c_str(), accept_languages_str);
286 }
287
CanTranslateLanguage(TranslateAcceptLanguages * accept_languages,const std::string & language)288 bool TranslatePrefs::CanTranslateLanguage(
289 TranslateAcceptLanguages* accept_languages,
290 const std::string& language) {
291 bool can_be_accept_language =
292 TranslateAcceptLanguages::CanBeAcceptLanguage(language);
293 bool is_accept_language = accept_languages->IsAcceptLanguage(language);
294
295 // Don't translate any user black-listed languages. Checking
296 // |is_accept_language| is necessary because if the user eliminates the
297 // language from the preference, it is natural to forget whether or not
298 // the language should be translated. Checking |cannot_be_accept_language|
299 // is also necessary because some minor languages can't be selected in the
300 // language preference even though the language is available in Translate
301 // server.
302 if (IsBlockedLanguage(language) &&
303 (is_accept_language || !can_be_accept_language))
304 return false;
305
306 return true;
307 }
308
ShouldAutoTranslate(const std::string & original_language,std::string * target_language)309 bool TranslatePrefs::ShouldAutoTranslate(const std::string& original_language,
310 std::string* target_language) {
311 const base::DictionaryValue* dict =
312 prefs_->GetDictionary(kPrefTranslateWhitelists);
313 if (dict && dict->GetString(original_language, target_language)) {
314 DCHECK(!target_language->empty());
315 return !target_language->empty();
316 }
317 return false;
318 }
319
320 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)321 void TranslatePrefs::RegisterProfilePrefs(
322 user_prefs::PrefRegistrySyncable* registry) {
323 registry->RegisterListPref(kPrefTranslateLanguageBlacklist,
324 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
325 registry->RegisterListPref(kPrefTranslateSiteBlacklist,
326 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
327 registry->RegisterDictionaryPref(
328 kPrefTranslateWhitelists,
329 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
330 registry->RegisterDictionaryPref(
331 kPrefTranslateDeniedCount,
332 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
333 registry->RegisterDictionaryPref(
334 kPrefTranslateAcceptedCount,
335 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
336 registry->RegisterListPref(kPrefTranslateBlockedLanguages,
337 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
338 }
339
340 // static
MigrateUserPrefs(PrefService * user_prefs,const char * accept_languages_pref)341 void TranslatePrefs::MigrateUserPrefs(PrefService* user_prefs,
342 const char* accept_languages_pref) {
343 // Old format of kPrefTranslateWhitelists
344 // - original language -> list of target langs to auto-translate
345 // - list of langs is in order of being enabled i.e. last in list is the
346 // most recent language that user enabled via
347 // Always translate |source_lang| to |target_lang|"
348 // - this results in a one-to-n relationship between source lang and target
349 // langs.
350 // New format:
351 // - original language -> one target language to auto-translate
352 // - each time that the user enables the "Always translate..." option, that
353 // target lang overwrites the previous one.
354 // - this results in a one-to-one relationship between source lang and target
355 // lang
356 // - we replace old list of target langs with the last target lang in list,
357 // assuming the last (i.e. most recent) target lang is what user wants to
358 // keep auto-translated.
359 DictionaryPrefUpdate update(user_prefs, kPrefTranslateWhitelists);
360 base::DictionaryValue* dict = update.Get();
361 if (dict && !dict->empty()) {
362 base::DictionaryValue::Iterator iter(*dict);
363 while (!iter.IsAtEnd()) {
364 const base::ListValue* list = NULL;
365 if (!iter.value().GetAsList(&list) || !list)
366 break; // Dictionary has either been migrated or new format.
367 std::string key = iter.key();
368 // Advance the iterator before removing the current element.
369 iter.Advance();
370 std::string target_lang;
371 if (list->empty() ||
372 !list->GetString(list->GetSize() - 1, &target_lang) ||
373 target_lang.empty()) {
374 dict->Remove(key, NULL);
375 } else {
376 dict->SetString(key, target_lang);
377 }
378 }
379 }
380
381 // Get the union of the blacklist and the Accept languages, and set this to
382 // the new language set 'translate_blocked_languages'. This is used for the
383 // settings UI for Translate and configration to determine which langauage
384 // should be translated instead of the blacklist. The blacklist is no longer
385 // used after launching the settings UI.
386 // After that, Set 'translate_languages_not_translate' to Accept languages to
387 // enable settings for users.
388 bool merged = user_prefs->HasPrefPath(kPrefTranslateBlockedLanguages);
389
390 if (!merged) {
391 std::vector<std::string> blacklisted_languages;
392 GetBlacklistedLanguages(user_prefs, &blacklisted_languages);
393
394 std::string accept_languages_str =
395 user_prefs->GetString(accept_languages_pref);
396 std::vector<std::string> accept_languages;
397 base::SplitString(accept_languages_str, ',', &accept_languages);
398
399 std::vector<std::string> blocked_languages;
400 CreateBlockedLanguages(
401 &blocked_languages, blacklisted_languages, accept_languages);
402
403 // Create the new preference kPrefTranslateBlockedLanguages.
404 {
405 base::ListValue blocked_languages_list;
406 for (std::vector<std::string>::const_iterator it =
407 blocked_languages.begin();
408 it != blocked_languages.end(); ++it) {
409 blocked_languages_list.Append(new base::StringValue(*it));
410 }
411 ListPrefUpdate update(user_prefs, kPrefTranslateBlockedLanguages);
412 base::ListValue* list = update.Get();
413 DCHECK(list != NULL);
414 list->Swap(&blocked_languages_list);
415 }
416
417 // Update kAcceptLanguages
418 for (std::vector<std::string>::const_iterator it =
419 blocked_languages.begin();
420 it != blocked_languages.end(); ++it) {
421 std::string lang = *it;
422 translate::ToChromeLanguageSynonym(&lang);
423 bool not_found =
424 std::find(accept_languages.begin(), accept_languages.end(), lang) ==
425 accept_languages.end();
426 if (not_found)
427 accept_languages.push_back(lang);
428 }
429
430 std::string new_accept_languages_str = JoinString(accept_languages, ",");
431 user_prefs->SetString(accept_languages_pref, new_accept_languages_str);
432 }
433 }
434
435 // static
CreateBlockedLanguages(std::vector<std::string> * blocked_languages,const std::vector<std::string> & blacklisted_languages,const std::vector<std::string> & accept_languages)436 void TranslatePrefs::CreateBlockedLanguages(
437 std::vector<std::string>* blocked_languages,
438 const std::vector<std::string>& blacklisted_languages,
439 const std::vector<std::string>& accept_languages) {
440 DCHECK(blocked_languages);
441 DCHECK(blocked_languages->empty());
442
443 std::set<std::string> result;
444
445 for (std::vector<std::string>::const_iterator it =
446 blacklisted_languages.begin();
447 it != blacklisted_languages.end(); ++it) {
448 result.insert(*it);
449 }
450
451 const std::string& app_locale =
452 TranslateDownloadManager::GetInstance()->application_locale();
453 std::string ui_lang = TranslateDownloadManager::GetLanguageCode(app_locale);
454 bool is_ui_english = ui_lang == "en" ||
455 StartsWithASCII(ui_lang, "en-", false);
456
457 for (std::vector<std::string>::const_iterator it = accept_languages.begin();
458 it != accept_languages.end(); ++it) {
459 std::string converted_lang = ConvertLangCodeForTranslation(*it);
460
461 // Regarding http://crbug.com/36182, even though English exists in Accept
462 // language list, English could be translated on non-English locale.
463 if (converted_lang == "en" && !is_ui_english)
464 continue;
465
466 result.insert(converted_lang);
467 }
468
469 blocked_languages->insert(
470 blocked_languages->begin(), result.begin(), result.end());
471 }
472
473 // static
ConvertLangCodeForTranslation(const std::string & lang)474 std::string TranslatePrefs::ConvertLangCodeForTranslation(
475 const std::string& lang) {
476 std::vector<std::string> tokens;
477 base::SplitString(lang, '-', &tokens);
478 if (tokens.size() < 1)
479 return lang;
480
481 std::string main_part = tokens[0];
482
483 // Translate doesn't support General Chinese and the sub code is necessary.
484 if (main_part == "zh")
485 return lang;
486
487 translate::ToTranslateLanguageSynonym(&main_part);
488 return main_part;
489 }
490
IsValueInList(const base::ListValue * list,const std::string & in_value) const491 bool TranslatePrefs::IsValueInList(const base::ListValue* list,
492 const std::string& in_value) const {
493 for (size_t i = 0; i < list->GetSize(); ++i) {
494 std::string value;
495 if (list->GetString(i, &value) && value == in_value)
496 return true;
497 }
498 return false;
499 }
500
IsValueBlacklisted(const char * pref_id,const std::string & value) const501 bool TranslatePrefs::IsValueBlacklisted(const char* pref_id,
502 const std::string& value) const {
503 const base::ListValue* blacklist = prefs_->GetList(pref_id);
504 return (blacklist && !blacklist->empty() && IsValueInList(blacklist, value));
505 }
506
BlacklistValue(const char * pref_id,const std::string & value)507 void TranslatePrefs::BlacklistValue(const char* pref_id,
508 const std::string& value) {
509 {
510 ListPrefUpdate update(prefs_, pref_id);
511 base::ListValue* blacklist = update.Get();
512 if (!blacklist) {
513 NOTREACHED() << "Unregistered translate blacklist pref";
514 return;
515 }
516 blacklist->Append(new base::StringValue(value));
517 }
518 }
519
RemoveValueFromBlacklist(const char * pref_id,const std::string & value)520 void TranslatePrefs::RemoveValueFromBlacklist(const char* pref_id,
521 const std::string& value) {
522 ListPrefUpdate update(prefs_, pref_id);
523 base::ListValue* blacklist = update.Get();
524 if (!blacklist) {
525 NOTREACHED() << "Unregistered translate blacklist pref";
526 return;
527 }
528 base::StringValue string_value(value);
529 blacklist->Remove(string_value, NULL);
530 }
531
IsListEmpty(const char * pref_id) const532 bool TranslatePrefs::IsListEmpty(const char* pref_id) const {
533 const base::ListValue* blacklist = prefs_->GetList(pref_id);
534 return (blacklist == NULL || blacklist->empty());
535 }
536
IsDictionaryEmpty(const char * pref_id) const537 bool TranslatePrefs::IsDictionaryEmpty(const char* pref_id) const {
538 const base::DictionaryValue* dict = prefs_->GetDictionary(pref_id);
539 return (dict == NULL || dict->empty());
540 }
541