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 21 const char TranslatePrefs::kPrefTranslateLanguageBlacklist[] = 22 "translate_language_blacklist"; 23 const char TranslatePrefs::kPrefTranslateSiteBlacklist[] = 24 "translate_site_blacklist"; 25 const char TranslatePrefs::kPrefTranslateWhitelists[] = 26 "translate_whitelists"; 27 const char TranslatePrefs::kPrefTranslateDeniedCount[] = 28 "translate_denied_count"; 29 const char TranslatePrefs::kPrefTranslateAcceptedCount[] = 30 "translate_accepted_count"; 31 const char TranslatePrefs::kPrefTranslateBlockedLanguages[] = 32 "translate_blocked_languages"; 33 34 namespace { 35 36 void 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. 53 void 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 83 TranslatePrefs::TranslatePrefs(PrefService* user_prefs) 84 : prefs_(user_prefs) { 85 } 86 87 void 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 102 bool TranslatePrefs::IsBlockedLanguage( 103 const std::string& original_language) const { 104 return IsValueBlacklisted(kPrefTranslateBlockedLanguages, 105 original_language); 106 } 107 108 void 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 126 void TranslatePrefs::UnblockLanguage( 127 const std::string& original_language) { 128 RemoveValueFromBlacklist(kPrefTranslateBlockedLanguages, 129 original_language); 130 } 131 132 void TranslatePrefs::RemoveLanguageFromLegacyBlacklist( 133 const std::string& original_language) { 134 RemoveValueFromBlacklist(kPrefTranslateLanguageBlacklist, 135 original_language); 136 } 137 138 bool TranslatePrefs::IsSiteBlacklisted(const std::string& site) const { 139 return IsValueBlacklisted(kPrefTranslateSiteBlacklist, site); 140 } 141 142 void TranslatePrefs::BlacklistSite(const std::string& site) { 143 BlacklistValue(kPrefTranslateSiteBlacklist, site); 144 } 145 146 void TranslatePrefs::RemoveSiteFromBlacklist(const std::string& site) { 147 RemoveValueFromBlacklist(kPrefTranslateSiteBlacklist, site); 148 } 149 150 bool 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 163 void 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 175 void 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 187 bool TranslatePrefs::HasBlockedLanguages() const { 188 return !IsListEmpty(kPrefTranslateBlockedLanguages); 189 } 190 191 void TranslatePrefs::ClearBlockedLanguages() { 192 prefs_->ClearPref(kPrefTranslateBlockedLanguages); 193 } 194 195 bool TranslatePrefs::HasBlacklistedSites() const { 196 return !IsListEmpty(kPrefTranslateSiteBlacklist); 197 } 198 199 void TranslatePrefs::ClearBlacklistedSites() { 200 prefs_->ClearPref(kPrefTranslateSiteBlacklist); 201 } 202 203 bool TranslatePrefs::HasWhitelistedLanguagePairs() const { 204 return !IsDictionaryEmpty(kPrefTranslateWhitelists); 205 } 206 207 void TranslatePrefs::ClearWhitelistedLanguagePairs() { 208 prefs_->ClearPref(kPrefTranslateWhitelists); 209 } 210 211 int 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 219 void 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 229 void TranslatePrefs::ResetTranslationDeniedCount(const std::string& language) { 230 DictionaryPrefUpdate update(prefs_, kPrefTranslateDeniedCount); 231 update.Get()->SetInteger(language, 0); 232 } 233 234 int 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 241 void 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 250 void TranslatePrefs::ResetTranslationAcceptedCount( 251 const std::string& language) { 252 DictionaryPrefUpdate update(prefs_, kPrefTranslateAcceptedCount); 253 update.Get()->SetInteger(language, 0); 254 } 255 256 void 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 270 void 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 287 bool 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 311 bool 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 318 void 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 338 void 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 433 void 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 470 std::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 487 bool 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 497 bool 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 503 void 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 516 void 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 528 bool 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 538 bool TranslatePrefs::IsListEmpty(const char* pref_id) const { 539 const ListValue* blacklist = prefs_->GetList(pref_id); 540 return (blacklist == NULL || blacklist->empty()); 541 } 542 543 bool TranslatePrefs::IsDictionaryEmpty(const char* pref_id) const { 544 const DictionaryValue* dict = prefs_->GetDictionary(pref_id); 545 return (dict == NULL || dict->empty()); 546 } 547