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 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 
     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.
     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 
     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 
     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 
    108 bool TranslatePrefs::IsBlockedLanguage(
    109     const std::string& original_language) const {
    110   return IsValueBlacklisted(kPrefTranslateBlockedLanguages,
    111                             original_language);
    112 }
    113 
    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 
    131 void TranslatePrefs::UnblockLanguage(const std::string& original_language) {
    132   RemoveValueFromBlacklist(kPrefTranslateBlockedLanguages, original_language);
    133 }
    134 
    135 void TranslatePrefs::RemoveLanguageFromLegacyBlacklist(
    136     const std::string& original_language) {
    137   RemoveValueFromBlacklist(kPrefTranslateLanguageBlacklist, original_language);
    138 }
    139 
    140 bool TranslatePrefs::IsSiteBlacklisted(const std::string& site) const {
    141   return IsValueBlacklisted(kPrefTranslateSiteBlacklist, site);
    142 }
    143 
    144 void TranslatePrefs::BlacklistSite(const std::string& site) {
    145   BlacklistValue(kPrefTranslateSiteBlacklist, site);
    146 }
    147 
    148 void TranslatePrefs::RemoveSiteFromBlacklist(const std::string& site) {
    149   RemoveValueFromBlacklist(kPrefTranslateSiteBlacklist, site);
    150 }
    151 
    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 
    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 
    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 
    189 bool TranslatePrefs::HasBlockedLanguages() const {
    190   return !IsListEmpty(kPrefTranslateBlockedLanguages);
    191 }
    192 
    193 void TranslatePrefs::ClearBlockedLanguages() {
    194   prefs_->ClearPref(kPrefTranslateBlockedLanguages);
    195 }
    196 
    197 bool TranslatePrefs::HasBlacklistedSites() const {
    198   return !IsListEmpty(kPrefTranslateSiteBlacklist);
    199 }
    200 
    201 void TranslatePrefs::ClearBlacklistedSites() {
    202   prefs_->ClearPref(kPrefTranslateSiteBlacklist);
    203 }
    204 
    205 bool TranslatePrefs::HasWhitelistedLanguagePairs() const {
    206   return !IsDictionaryEmpty(kPrefTranslateWhitelists);
    207 }
    208 
    209 void TranslatePrefs::ClearWhitelistedLanguagePairs() {
    210   prefs_->ClearPref(kPrefTranslateWhitelists);
    211 }
    212 
    213 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 
    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 
    231 void TranslatePrefs::ResetTranslationDeniedCount(const std::string& language) {
    232   DictionaryPrefUpdate update(prefs_, kPrefTranslateDeniedCount);
    233   update.Get()->SetInteger(language, 0);
    234 }
    235 
    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 
    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 
    252 void TranslatePrefs::ResetTranslationAcceptedCount(
    253     const std::string& language) {
    254   DictionaryPrefUpdate update(prefs_, kPrefTranslateAcceptedCount);
    255   update.Get()->SetInteger(language, 0);
    256 }
    257 
    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 
    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 
    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 
    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
    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
    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
    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
    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 
    491 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 
    501 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 
    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 
    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 
    532 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 
    537 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