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