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
18const char TranslatePrefs::kPrefTranslateLanguageBlacklist[] =
19    "translate_language_blacklist";
20const char TranslatePrefs::kPrefTranslateSiteBlacklist[] =
21    "translate_site_blacklist";
22const char TranslatePrefs::kPrefTranslateWhitelists[] =
23    "translate_whitelists";
24const char TranslatePrefs::kPrefTranslateDeniedCount[] =
25    "translate_denied_count";
26const char TranslatePrefs::kPrefTranslateAcceptedCount[] =
27    "translate_accepted_count";
28const char TranslatePrefs::kPrefTranslateBlockedLanguages[] =
29    "translate_blocked_languages";
30
31namespace {
32
33void 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.
51void 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
81TranslatePrefs::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
93void 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
108bool TranslatePrefs::IsBlockedLanguage(
109    const std::string& original_language) const {
110  return IsValueBlacklisted(kPrefTranslateBlockedLanguages,
111                            original_language);
112}
113
114void 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
131void TranslatePrefs::UnblockLanguage(const std::string& original_language) {
132  RemoveValueFromBlacklist(kPrefTranslateBlockedLanguages, original_language);
133}
134
135void TranslatePrefs::RemoveLanguageFromLegacyBlacklist(
136    const std::string& original_language) {
137  RemoveValueFromBlacklist(kPrefTranslateLanguageBlacklist, original_language);
138}
139
140bool TranslatePrefs::IsSiteBlacklisted(const std::string& site) const {
141  return IsValueBlacklisted(kPrefTranslateSiteBlacklist, site);
142}
143
144void TranslatePrefs::BlacklistSite(const std::string& site) {
145  BlacklistValue(kPrefTranslateSiteBlacklist, site);
146}
147
148void TranslatePrefs::RemoveSiteFromBlacklist(const std::string& site) {
149  RemoveValueFromBlacklist(kPrefTranslateSiteBlacklist, site);
150}
151
152bool 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
166void 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
177void 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
189bool TranslatePrefs::HasBlockedLanguages() const {
190  return !IsListEmpty(kPrefTranslateBlockedLanguages);
191}
192
193void TranslatePrefs::ClearBlockedLanguages() {
194  prefs_->ClearPref(kPrefTranslateBlockedLanguages);
195}
196
197bool TranslatePrefs::HasBlacklistedSites() const {
198  return !IsListEmpty(kPrefTranslateSiteBlacklist);
199}
200
201void TranslatePrefs::ClearBlacklistedSites() {
202  prefs_->ClearPref(kPrefTranslateSiteBlacklist);
203}
204
205bool TranslatePrefs::HasWhitelistedLanguagePairs() const {
206  return !IsDictionaryEmpty(kPrefTranslateWhitelists);
207}
208
209void TranslatePrefs::ClearWhitelistedLanguagePairs() {
210  prefs_->ClearPref(kPrefTranslateWhitelists);
211}
212
213int 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
221void 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
231void TranslatePrefs::ResetTranslationDeniedCount(const std::string& language) {
232  DictionaryPrefUpdate update(prefs_, kPrefTranslateDeniedCount);
233  update.Get()->SetInteger(language, 0);
234}
235
236int 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
243void 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
252void TranslatePrefs::ResetTranslationAcceptedCount(
253    const std::string& language) {
254  DictionaryPrefUpdate update(prefs_, kPrefTranslateAcceptedCount);
255  update.Get()->SetInteger(language, 0);
256}
257
258void 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
272void 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
288bool 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
309bool 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
321void 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
341void 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
436void 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
474std::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
491bool 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
501bool 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
507void 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
520void 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
532bool TranslatePrefs::IsListEmpty(const char* pref_id) const {
533  const base::ListValue* blacklist = prefs_->GetList(pref_id);
534  return (blacklist == NULL || blacklist->empty());
535}
536
537bool TranslatePrefs::IsDictionaryEmpty(const char* pref_id) const {
538  const base::DictionaryValue* dict = prefs_->GetDictionary(pref_id);
539  return (dict == NULL || dict->empty());
540}
541