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