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
18namespace translate {
19
20const char TranslatePrefs::kPrefTranslateLanguageBlacklist[] =
21    "translate_language_blacklist";
22const char TranslatePrefs::kPrefTranslateSiteBlacklist[] =
23    "translate_site_blacklist";
24const char TranslatePrefs::kPrefTranslateWhitelists[] =
25    "translate_whitelists";
26const char TranslatePrefs::kPrefTranslateDeniedCount[] =
27    "translate_denied_count";
28const char TranslatePrefs::kPrefTranslateAcceptedCount[] =
29    "translate_accepted_count";
30const char TranslatePrefs::kPrefTranslateBlockedLanguages[] =
31    "translate_blocked_languages";
32const char TranslatePrefs::kPrefTranslateLastDeniedTime[] =
33    "translate_last_denied_time";
34const char TranslatePrefs::kPrefTranslateTooOftenDenied[] =
35    "translate_too_often_denied";
36
37namespace {
38
39void GetBlacklistedLanguages(const PrefService* prefs,
40                             std::vector<std::string>* languages) {
41  DCHECK(languages);
42  DCHECK(languages->empty());
43
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) {
48    std::string lang;
49    (*it)->GetAsString(&lang);
50    languages->push_back(lang);
51  }
52}
53
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.
57void ExpandLanguageCodes(const std::vector<std::string>& languages,
58                         std::vector<std::string>* expanded_languages) {
59  DCHECK(expanded_languages);
60  DCHECK(expanded_languages->empty());
61
62  // used to eliminate duplicates.
63  std::set<std::string> seen;
64
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);
71    }
72
73    std::vector<std::string> tokens;
74    base::SplitString(language, '-', &tokens);
75    if (tokens.size() == 0)
76      continue;
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);
81    }
82  }
83}
84
85}  // namespace
86
87TranslatePrefs::TranslatePrefs(PrefService* user_prefs,
88                               const char* accept_languages_pref,
89                               const char* preferred_languages_pref)
90    : accept_languages_pref_(accept_languages_pref),
91      prefs_(user_prefs) {
92#if defined(OS_CHROMEOS)
93  preferred_languages_pref_ = preferred_languages_pref;
94#else
95  DCHECK(!preferred_languages_pref);
96#endif
97}
98
99void TranslatePrefs::ResetToDefaults() {
100  ClearBlockedLanguages();
101  ClearBlacklistedSites();
102  ClearWhitelistedLanguagePairs();
103
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);
111  }
112
113  prefs_->ClearPref(kPrefTranslateLastDeniedTime);
114  prefs_->ClearPref(kPrefTranslateTooOftenDenied);
115}
116
117bool TranslatePrefs::IsBlockedLanguage(
118    const std::string& original_language) const {
119  return IsValueBlacklisted(kPrefTranslateBlockedLanguages,
120                            original_language);
121}
122
123void TranslatePrefs::BlockLanguage(const std::string& original_language) {
124  BlacklistValue(kPrefTranslateBlockedLanguages, original_language);
125
126  // Add the language to the language list at chrome://settings/languages.
127  std::string language = original_language;
128  translate::ToChromeLanguageSynonym(&language);
129
130  std::vector<std::string> languages;
131  GetLanguageList(&languages);
132
133  if (std::find(languages.begin(), languages.end(), language) ==
134      languages.end()) {
135    languages.push_back(language);
136    UpdateLanguageList(languages);
137  }
138}
139
140void TranslatePrefs::UnblockLanguage(const std::string& original_language) {
141  RemoveValueFromBlacklist(kPrefTranslateBlockedLanguages, original_language);
142}
143
144void TranslatePrefs::RemoveLanguageFromLegacyBlacklist(
145    const std::string& original_language) {
146  RemoveValueFromBlacklist(kPrefTranslateLanguageBlacklist, original_language);
147}
148
149bool TranslatePrefs::IsSiteBlacklisted(const std::string& site) const {
150  return IsValueBlacklisted(kPrefTranslateSiteBlacklist, site);
151}
152
153void TranslatePrefs::BlacklistSite(const std::string& site) {
154  BlacklistValue(kPrefTranslateSiteBlacklist, site);
155}
156
157void TranslatePrefs::RemoveSiteFromBlacklist(const std::string& site) {
158  RemoveValueFromBlacklist(kPrefTranslateSiteBlacklist, site);
159}
160
161bool 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)
170      return true;
171  }
172  return false;
173}
174
175void TranslatePrefs::WhitelistLanguagePair(const std::string& original_language,
176                                           const std::string& target_language) {
177  DictionaryPrefUpdate update(prefs_, kPrefTranslateWhitelists);
178  base::DictionaryValue* dict = update.Get();
179  if (!dict) {
180    NOTREACHED() << "Unregistered translate whitelist pref";
181    return;
182  }
183  dict->SetString(original_language, target_language);
184}
185
186void 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();
191  if (!dict) {
192    NOTREACHED() << "Unregistered translate whitelist pref";
193    return;
194  }
195  dict->Remove(original_language, NULL);
196}
197
198bool TranslatePrefs::HasBlockedLanguages() const {
199  return !IsListEmpty(kPrefTranslateBlockedLanguages);
200}
201
202void TranslatePrefs::ClearBlockedLanguages() {
203  prefs_->ClearPref(kPrefTranslateBlockedLanguages);
204}
205
206bool TranslatePrefs::HasBlacklistedSites() const {
207  return !IsListEmpty(kPrefTranslateSiteBlacklist);
208}
209
210void TranslatePrefs::ClearBlacklistedSites() {
211  prefs_->ClearPref(kPrefTranslateSiteBlacklist);
212}
213
214bool TranslatePrefs::HasWhitelistedLanguagePairs() const {
215  return !IsDictionaryEmpty(kPrefTranslateWhitelists);
216}
217
218void TranslatePrefs::ClearWhitelistedLanguagePairs() {
219  prefs_->ClearPref(kPrefTranslateWhitelists);
220}
221
222int TranslatePrefs::GetTranslationDeniedCount(
223    const std::string& language) const {
224  const base::DictionaryValue* dict =
225      prefs_->GetDictionary(kPrefTranslateDeniedCount);
226  int count = 0;
227  return dict->GetInteger(language, &count) ? count : 0;
228}
229
230void TranslatePrefs::IncrementTranslationDeniedCount(
231    const std::string& language) {
232  DictionaryPrefUpdate update(prefs_, kPrefTranslateDeniedCount);
233  base::DictionaryValue* dict = update.Get();
234
235  int count = 0;
236  dict->GetInteger(language, &count);
237  dict->SetInteger(language, count + 1);
238}
239
240void TranslatePrefs::ResetTranslationDeniedCount(const std::string& language) {
241  DictionaryPrefUpdate update(prefs_, kPrefTranslateDeniedCount);
242  update.Get()->SetInteger(language, 0);
243}
244
245int TranslatePrefs::GetTranslationAcceptedCount(const std::string& language) {
246  const base::DictionaryValue* dict =
247      prefs_->GetDictionary(kPrefTranslateAcceptedCount);
248  int count = 0;
249  return dict->GetInteger(language, &count) ? count : 0;
250}
251
252void TranslatePrefs::IncrementTranslationAcceptedCount(
253    const std::string& language) {
254  DictionaryPrefUpdate update(prefs_, kPrefTranslateAcceptedCount);
255  base::DictionaryValue* dict = update.Get();
256  int count = 0;
257  dict->GetInteger(language, &count);
258  dict->SetInteger(language, count + 1);
259}
260
261void TranslatePrefs::ResetTranslationAcceptedCount(
262    const std::string& language) {
263  DictionaryPrefUpdate update(prefs_, kPrefTranslateAcceptedCount);
264  update.Get()->SetInteger(language, 0);
265}
266
267void TranslatePrefs::UpdateLastDeniedTime() {
268  if (IsTooOftenDenied())
269    return;
270
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);
277}
278
279bool TranslatePrefs::IsTooOftenDenied() const {
280  return prefs_->GetBoolean(kPrefTranslateTooOftenDenied);
281}
282
283void TranslatePrefs::ResetDenialState() {
284  prefs_->SetDouble(kPrefTranslateLastDeniedTime, 0);
285  prefs_->SetBoolean(kPrefTranslateTooOftenDenied, false);
286}
287
288void TranslatePrefs::GetLanguageList(std::vector<std::string>* languages) {
289  DCHECK(languages);
290  DCHECK(languages->empty());
291
292#if defined(OS_CHROMEOS)
293  const char* key = preferred_languages_pref_.c_str();
294#else
295  const char* key = accept_languages_pref_.c_str();
296#endif
297
298  std::string languages_str = prefs_->GetString(key);
299  base::SplitString(languages_str, ',', languages);
300}
301
302void 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);
307#endif
308
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);
316}
317
318bool 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);
324
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
331  // server.
332  if (IsBlockedLanguage(language) &&
333      (is_accept_language || !can_be_accept_language))
334    return false;
335
336  return true;
337}
338
339bool 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();
346  }
347  return false;
348}
349
350// static
351void 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);
374}
375
376// static
377void 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
385  //   langs.
386  // New format:
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
391  //   lang
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.
405      iter.Advance();
406      std::string target_lang;
407      if (list->empty() ||
408          !list->GetString(list->GetSize() - 1, &target_lang) ||
409          target_lang.empty()) {
410        dict->Remove(key, NULL);
411      } else {
412        dict->SetString(key, target_lang);
413      }
414    }
415  }
416
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);
425
426  if (!merged) {
427    std::vector<std::string> blacklisted_languages;
428    GetBlacklistedLanguages(user_prefs, &blacklisted_languages);
429
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);
434
435    std::vector<std::string> blocked_languages;
436    CreateBlockedLanguages(
437        &blocked_languages, blacklisted_languages, accept_languages);
438
439    // Create the new preference kPrefTranslateBlockedLanguages.
440    {
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));
446      }
447      ListPrefUpdate update(user_prefs, kPrefTranslateBlockedLanguages);
448      base::ListValue* list = update.Get();
449      DCHECK(list != NULL);
450      list->Swap(&blocked_languages_list);
451    }
452
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);
459      bool not_found =
460          std::find(accept_languages.begin(), accept_languages.end(), lang) ==
461          accept_languages.end();
462      if (not_found)
463        accept_languages.push_back(lang);
464    }
465
466    std::string new_accept_languages_str = JoinString(accept_languages, ",");
467    user_prefs->SetString(accept_languages_pref, new_accept_languages_str);
468  }
469}
470
471// static
472void 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());
478
479  std::set<std::string> result;
480
481  for (std::vector<std::string>::const_iterator it =
482           blacklisted_languages.begin();
483       it != blacklisted_languages.end(); ++it) {
484    result.insert(*it);
485  }
486
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);
492
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);
496
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)
500      continue;
501
502    result.insert(converted_lang);
503  }
504
505  blocked_languages->insert(
506      blocked_languages->begin(), result.begin(), result.end());
507}
508
509// static
510std::string TranslatePrefs::ConvertLangCodeForTranslation(
511    const std::string& lang) {
512  std::vector<std::string> tokens;
513  base::SplitString(lang, '-', &tokens);
514  if (tokens.size() < 1)
515    return lang;
516
517  std::string main_part = tokens[0];
518
519  // Translate doesn't support General Chinese and the sub code is necessary.
520  if (main_part == "zh")
521    return lang;
522
523  translate::ToTranslateLanguageSynonym(&main_part);
524  return main_part;
525}
526
527bool TranslatePrefs::IsValueInList(const base::ListValue* list,
528                                   const std::string& in_value) const {
529  for (size_t i = 0; i < list->GetSize(); ++i) {
530    std::string value;
531    if (list->GetString(i, &value) && value == in_value)
532      return true;
533  }
534  return false;
535}
536
537bool 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));
541}
542
543void TranslatePrefs::BlacklistValue(const char* pref_id,
544                                    const std::string& value) {
545  {
546    ListPrefUpdate update(prefs_, pref_id);
547    base::ListValue* blacklist = update.Get();
548    if (!blacklist) {
549      NOTREACHED() << "Unregistered translate blacklist pref";
550      return;
551    }
552    blacklist->Append(new base::StringValue(value));
553  }
554}
555
556void TranslatePrefs::RemoveValueFromBlacklist(const char* pref_id,
557                                              const std::string& value) {
558  ListPrefUpdate update(prefs_, pref_id);
559  base::ListValue* blacklist = update.Get();
560  if (!blacklist) {
561    NOTREACHED() << "Unregistered translate blacklist pref";
562    return;
563  }
564  base::StringValue string_value(value);
565  blacklist->Remove(string_value, NULL);
566}
567
568bool TranslatePrefs::IsListEmpty(const char* pref_id) const {
569  const base::ListValue* blacklist = prefs_->GetList(pref_id);
570  return (blacklist == NULL || blacklist->empty());
571}
572
573bool TranslatePrefs::IsDictionaryEmpty(const char* pref_id) const {
574  const base::DictionaryValue* dict = prefs_->GetDictionary(pref_id);
575  return (dict == NULL || dict->empty());
576}
577
578}  // namespace translate
579