Home | History | Annotate | Download | only in browser
      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 namespace translate {
     19 
     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";
     36 
     37 namespace {
     38 
     39 void 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.
     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());
     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 
     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),
     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 
     99 void 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 
    117 bool TranslatePrefs::IsBlockedLanguage(
    118     const std::string& original_language) const {
    119   return IsValueBlacklisted(kPrefTranslateBlockedLanguages,
    120                             original_language);
    121 }
    122 
    123 void 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 
    140 void TranslatePrefs::UnblockLanguage(const std::string& original_language) {
    141   RemoveValueFromBlacklist(kPrefTranslateBlockedLanguages, original_language);
    142 }
    143 
    144 void TranslatePrefs::RemoveLanguageFromLegacyBlacklist(
    145     const std::string& original_language) {
    146   RemoveValueFromBlacklist(kPrefTranslateLanguageBlacklist, original_language);
    147 }
    148 
    149 bool TranslatePrefs::IsSiteBlacklisted(const std::string& site) const {
    150   return IsValueBlacklisted(kPrefTranslateSiteBlacklist, site);
    151 }
    152 
    153 void TranslatePrefs::BlacklistSite(const std::string& site) {
    154   BlacklistValue(kPrefTranslateSiteBlacklist, site);
    155 }
    156 
    157 void TranslatePrefs::RemoveSiteFromBlacklist(const std::string& site) {
    158   RemoveValueFromBlacklist(kPrefTranslateSiteBlacklist, site);
    159 }
    160 
    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)
    170       return true;
    171   }
    172   return false;
    173 }
    174 
    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();
    179   if (!dict) {
    180     NOTREACHED() << "Unregistered translate whitelist pref";
    181     return;
    182   }
    183   dict->SetString(original_language, target_language);
    184 }
    185 
    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();
    191   if (!dict) {
    192     NOTREACHED() << "Unregistered translate whitelist pref";
    193     return;
    194   }
    195   dict->Remove(original_language, NULL);
    196 }
    197 
    198 bool TranslatePrefs::HasBlockedLanguages() const {
    199   return !IsListEmpty(kPrefTranslateBlockedLanguages);
    200 }
    201 
    202 void TranslatePrefs::ClearBlockedLanguages() {
    203   prefs_->ClearPref(kPrefTranslateBlockedLanguages);
    204 }
    205 
    206 bool TranslatePrefs::HasBlacklistedSites() const {
    207   return !IsListEmpty(kPrefTranslateSiteBlacklist);
    208 }
    209 
    210 void TranslatePrefs::ClearBlacklistedSites() {
    211   prefs_->ClearPref(kPrefTranslateSiteBlacklist);
    212 }
    213 
    214 bool TranslatePrefs::HasWhitelistedLanguagePairs() const {
    215   return !IsDictionaryEmpty(kPrefTranslateWhitelists);
    216 }
    217 
    218 void TranslatePrefs::ClearWhitelistedLanguagePairs() {
    219   prefs_->ClearPref(kPrefTranslateWhitelists);
    220 }
    221 
    222 int 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 
    230 void 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 
    240 void TranslatePrefs::ResetTranslationDeniedCount(const std::string& language) {
    241   DictionaryPrefUpdate update(prefs_, kPrefTranslateDeniedCount);
    242   update.Get()->SetInteger(language, 0);
    243 }
    244 
    245 int 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 
    252 void 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 
    261 void TranslatePrefs::ResetTranslationAcceptedCount(
    262     const std::string& language) {
    263   DictionaryPrefUpdate update(prefs_, kPrefTranslateAcceptedCount);
    264   update.Get()->SetInteger(language, 0);
    265 }
    266 
    267 void 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 
    279 bool TranslatePrefs::IsTooOftenDenied() const {
    280   return prefs_->GetBoolean(kPrefTranslateTooOftenDenied);
    281 }
    282 
    283 void TranslatePrefs::ResetDenialState() {
    284   prefs_->SetDouble(kPrefTranslateLastDeniedTime, 0);
    285   prefs_->SetBoolean(kPrefTranslateTooOftenDenied, false);
    286 }
    287 
    288 void 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 
    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);
    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 
    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);
    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 
    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();
    346   }
    347   return false;
    348 }
    349 
    350 // static
    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);
    374 }
    375 
    376 // static
    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
    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
    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());
    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
    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)
    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 
    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) {
    530     std::string value;
    531     if (list->GetString(i, &value) && value == in_value)
    532       return true;
    533   }
    534   return false;
    535 }
    536 
    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));
    541 }
    542 
    543 void 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 
    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();
    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 
    568 bool TranslatePrefs::IsListEmpty(const char* pref_id) const {
    569   const base::ListValue* blacklist = prefs_->GetList(pref_id);
    570   return (blacklist == NULL || blacklist->empty());
    571 }
    572 
    573 bool 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