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