translate_manager.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
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_manager.h" 6 7#include "base/command_line.h" 8#include "base/compiler_specific.h" 9#include "base/metrics/histogram.h" 10#include "base/singleton.h" 11#include "base/string_split.h" 12#include "base/string_util.h" 13#include "chrome/browser/autofill/autofill_manager.h" 14#include "chrome/browser/browser_list.h" 15#include "chrome/browser/browser_process.h" 16#include "chrome/browser/prefs/pref_service.h" 17#include "chrome/browser/profiles/profile.h" 18#include "chrome/browser/renderer_host/render_process_host.h" 19#include "chrome/browser/renderer_host/render_view_host.h" 20#include "chrome/browser/tab_contents/language_state.h" 21#include "chrome/browser/tab_contents/navigation_controller.h" 22#include "chrome/browser/tab_contents/navigation_entry.h" 23#include "chrome/browser/tab_contents/tab_contents.h" 24#include "chrome/browser/tab_contents/tab_util.h" 25#include "chrome/browser/tabs/tab_strip_model.h" 26#include "chrome/browser/translate/page_translated_details.h" 27#include "chrome/browser/translate/translate_infobar_delegate.h" 28#include "chrome/browser/translate/translate_prefs.h" 29#include "chrome/browser/ui/browser.h" 30#include "chrome/common/chrome_switches.h" 31#include "chrome/common/notification_details.h" 32#include "chrome/common/notification_service.h" 33#include "chrome/common/notification_source.h" 34#include "chrome/common/notification_type.h" 35#include "chrome/common/pref_names.h" 36#include "chrome/common/render_messages.h" 37#include "chrome/common/translate_errors.h" 38#include "chrome/common/url_constants.h" 39#include "grit/browser_resources.h" 40#include "net/base/escape.h" 41#include "net/url_request/url_request_status.h" 42#include "ui/base/resource/resource_bundle.h" 43 44namespace { 45 46// Mapping from a locale name to a language code name. 47// Locale names not included are translated as is. 48struct LocaleToCLDLanguage { 49 const char* locale_language; // Language Chrome locale is in. 50 const char* cld_language; // Language the CLD reports. 51}; 52LocaleToCLDLanguage kLocaleToCLDLanguages[] = { 53 { "en-GB", "en" }, 54 { "en-US", "en" }, 55 { "es-419", "es" }, 56 { "pt-BR", "pt" }, 57 { "pt-PT", "pt" }, 58}; 59 60// The list of languages the Google translation server supports. 61// For information, here is the list of languages that Chrome can be run in 62// but that the translation server does not support: 63// am Amharic 64// bn Bengali 65// gu Gujarati 66// kn Kannada 67// ml Malayalam 68// mr Marathi 69// ta Tamil 70// te Telugu 71const char* kSupportedLanguages[] = { 72 "af", // Afrikaans 73 "az", // Azerbaijani 74 "sq", // Albanian 75 "ar", // Arabic 76 "hy", // Armenian 77 "eu", // Basque 78 "be", // Belarusian 79 "bg", // Bulgarian 80 "ca", // Catalan 81 "zh-CN", // Chinese (Simplified) 82 "zh-TW", // Chinese (Traditional) 83 "hr", // Croatian 84 "cs", // Czech 85 "da", // Danish 86 "nl", // Dutch 87 "en", // English 88 "et", // Estonian 89 "fi", // Finnish 90 "fil", // Filipino 91 "fr", // French 92 "gl", // Galician 93 "de", // German 94 "el", // Greek 95 "ht", // Haitian Creole 96 "he", // Hebrew 97 "hi", // Hindi 98 "hu", // Hungarian 99 "is", // Icelandic 100 "id", // Indonesian 101 "it", // Italian 102 "ga", // Irish 103 "ja", // Japanese 104 "ka", // Georgian 105 "ko", // Korean 106 "lv", // Latvian 107 "lt", // Lithuanian 108 "mk", // Macedonian 109 "ms", // Malay 110 "mt", // Maltese 111 "nb", // Norwegian 112 "fa", // Persian 113 "pl", // Polish 114 "pt", // Portuguese 115 "ro", // Romanian 116 "ru", // Russian 117 "sr", // Serbian 118 "sk", // Slovak 119 "sl", // Slovenian 120 "es", // Spanish 121 "sw", // Swahili 122 "sv", // Swedish 123 "th", // Thai 124 "tr", // Turkish 125 "uk", // Ukrainian 126 "ur", // Urdu 127 "vi", // Vietnamese 128 "cy", // Welsh 129 "yi", // Yiddish 130}; 131 132const char* const kTranslateScriptURL = 133 "http://translate.google.com/translate_a/element.js?" 134 "cb=cr.googleTranslate.onTranslateElementLoad"; 135const char* const kTranslateScriptHeader = 136 "Google-Translate-Element-Mode: library"; 137const char* const kReportLanguageDetectionErrorURL = 138 "http://translate.google.com/translate_error"; 139 140const int kTranslateScriptExpirationDelayMS = 24 * 60 * 60 * 1000; // 1 day. 141 142} // namespace 143 144// static 145base::LazyInstance<std::set<std::string> > 146 TranslateManager::supported_languages_(base::LINKER_INITIALIZED); 147 148TranslateManager::~TranslateManager() { 149} 150 151// static 152TranslateManager* TranslateManager::GetInstance() { 153 return Singleton<TranslateManager>::get(); 154} 155 156// static 157bool TranslateManager::IsTranslatableURL(const GURL& url) { 158 // A URLs is translatable unless it is one of the following: 159 // - an internal URL (chrome:// and others) 160 // - the devtools (which is considered UI) 161 // - an FTP page (as FTP pages tend to have long lists of filenames that may 162 // confuse the CLD) 163 return !url.SchemeIs(chrome::kChromeUIScheme) && 164 !url.SchemeIs(chrome::kChromeDevToolsScheme) && 165 !url.SchemeIs(chrome::kFtpScheme); 166} 167 168// static 169void TranslateManager::GetSupportedLanguages( 170 std::vector<std::string>* languages) { 171 DCHECK(languages && languages->empty()); 172 for (size_t i = 0; i < arraysize(kSupportedLanguages); ++i) 173 languages->push_back(kSupportedLanguages[i]); 174} 175 176// static 177std::string TranslateManager::GetLanguageCode( 178 const std::string& chrome_locale) { 179 for (size_t i = 0; i < arraysize(kLocaleToCLDLanguages); ++i) { 180 if (chrome_locale == kLocaleToCLDLanguages[i].locale_language) 181 return kLocaleToCLDLanguages[i].cld_language; 182 } 183 return chrome_locale; 184} 185 186// static 187bool TranslateManager::IsSupportedLanguage(const std::string& page_language) { 188 if (supported_languages_.Pointer()->empty()) { 189 for (size_t i = 0; i < arraysize(kSupportedLanguages); ++i) 190 supported_languages_.Pointer()->insert(kSupportedLanguages[i]); 191 } 192 return supported_languages_.Pointer()->find(page_language) != 193 supported_languages_.Pointer()->end(); 194} 195 196void TranslateManager::Observe(NotificationType type, 197 const NotificationSource& source, 198 const NotificationDetails& details) { 199 switch (type.value) { 200 case NotificationType::NAV_ENTRY_COMMITTED: { 201 NavigationController* controller = 202 Source<NavigationController>(source).ptr(); 203 NavigationController::LoadCommittedDetails* load_details = 204 Details<NavigationController::LoadCommittedDetails>(details).ptr(); 205 NavigationEntry* entry = controller->GetActiveEntry(); 206 if (!entry) { 207 NOTREACHED(); 208 return; 209 } 210 if (!load_details->is_main_frame && 211 controller->tab_contents()->language_state().translation_declined()) { 212 // Some sites (such as Google map) may trigger sub-frame navigations 213 // when the user interacts with the page. We don't want to show a new 214 // infobar if the user already dismissed one in that case. 215 return; 216 } 217 if (entry->transition_type() != PageTransition::RELOAD && 218 load_details->type != NavigationType::SAME_PAGE) { 219 return; 220 } 221 // When doing a page reload, we don't get a TAB_LANGUAGE_DETERMINED 222 // notification. So we need to explictly initiate the translation. 223 // Note that we delay it as the TranslateManager gets this notification 224 // before the TabContents and the TabContents processing might remove the 225 // current infobars. Since InitTranslation might add an infobar, it must 226 // be done after that. 227 MessageLoop::current()->PostTask(FROM_HERE, 228 method_factory_.NewRunnableMethod( 229 &TranslateManager::InitiateTranslationPosted, 230 controller->tab_contents()->render_view_host()->process()->id(), 231 controller->tab_contents()->render_view_host()->routing_id(), 232 controller->tab_contents()->language_state(). 233 original_language())); 234 break; 235 } 236 case NotificationType::TAB_LANGUAGE_DETERMINED: { 237 TabContents* tab = Source<TabContents>(source).ptr(); 238 // We may get this notifications multiple times. Make sure to translate 239 // only once. 240 LanguageState& language_state = tab->language_state(); 241 if (language_state.page_translatable() && 242 !language_state.translation_pending() && 243 !language_state.translation_declined() && 244 !language_state.IsPageTranslated()) { 245 std::string language = *(Details<std::string>(details).ptr()); 246 InitiateTranslation(tab, language); 247 } 248 break; 249 } 250 case NotificationType::PAGE_TRANSLATED: { 251 // Only add translate infobar if it doesn't exist; if it already exists, 252 // just update the state, the actual infobar would have received the same 253 // notification and update the visual display accordingly. 254 TabContents* tab = Source<TabContents>(source).ptr(); 255 PageTranslatedDetails* page_translated_details = 256 Details<PageTranslatedDetails>(details).ptr(); 257 PageTranslated(tab, page_translated_details); 258 break; 259 } 260 case NotificationType::PROFILE_DESTROYED: { 261 Profile* profile = Source<Profile>(source).ptr(); 262 notification_registrar_.Remove(this, NotificationType::PROFILE_DESTROYED, 263 source); 264 size_t count = accept_languages_.erase(profile->GetPrefs()); 265 // We should know about this profile since we are listening for 266 // notifications on it. 267 DCHECK(count > 0); 268 pref_change_registrar_.Remove(prefs::kAcceptLanguages, this); 269 break; 270 } 271 case NotificationType::PREF_CHANGED: { 272 DCHECK(*Details<std::string>(details).ptr() == prefs::kAcceptLanguages); 273 PrefService* prefs = Source<PrefService>(source).ptr(); 274 InitAcceptLanguages(prefs); 275 break; 276 } 277 default: 278 NOTREACHED(); 279 } 280} 281 282void TranslateManager::OnURLFetchComplete(const URLFetcher* source, 283 const GURL& url, 284 const net::URLRequestStatus& status, 285 int response_code, 286 const ResponseCookies& cookies, 287 const std::string& data) { 288 scoped_ptr<const URLFetcher> delete_ptr(source); 289 DCHECK(translate_script_request_pending_); 290 translate_script_request_pending_ = false; 291 bool error = 292 (status.status() != net::URLRequestStatus::SUCCESS || 293 response_code != 200); 294 295 if (!error) { 296 base::StringPiece str = ResourceBundle::GetSharedInstance(). 297 GetRawDataResource(IDR_TRANSLATE_JS); 298 DCHECK(translate_script_.empty()); 299 str.CopyToString(&translate_script_); 300 translate_script_ += "\n" + data; 301 // We'll expire the cached script after some time, to make sure long running 302 // browsers still get fixes that might get pushed with newer scripts. 303 MessageLoop::current()->PostDelayedTask(FROM_HERE, 304 method_factory_.NewRunnableMethod( 305 &TranslateManager::ClearTranslateScript), 306 translate_script_expiration_delay_); 307 } 308 309 // Process any pending requests. 310 std::vector<PendingRequest>::const_iterator iter; 311 for (iter = pending_requests_.begin(); iter != pending_requests_.end(); 312 ++iter) { 313 const PendingRequest& request = *iter; 314 TabContents* tab = tab_util::GetTabContentsByID(request.render_process_id, 315 request.render_view_id); 316 if (!tab) { 317 // The tab went away while we were retrieving the script. 318 continue; 319 } 320 NavigationEntry* entry = tab->controller().GetActiveEntry(); 321 if (!entry || entry->page_id() != request.page_id) { 322 // We navigated away from the page the translation was triggered on. 323 continue; 324 } 325 326 if (error) { 327 ShowInfoBar(tab, TranslateInfoBarDelegate::CreateErrorDelegate( 328 TranslateErrors::NETWORK, tab, 329 request.source_lang, request.target_lang)); 330 } else { 331 // Translate the page. 332 DoTranslatePage(tab, translate_script_, 333 request.source_lang, request.target_lang); 334 } 335 } 336 pending_requests_.clear(); 337} 338 339// static 340bool TranslateManager::IsShowingTranslateInfobar(TabContents* tab) { 341 return GetTranslateInfoBarDelegate(tab) != NULL; 342} 343 344TranslateManager::TranslateManager() 345 : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), 346 translate_script_expiration_delay_(kTranslateScriptExpirationDelayMS), 347 translate_script_request_pending_(false) { 348 notification_registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, 349 NotificationService::AllSources()); 350 notification_registrar_.Add(this, NotificationType::TAB_LANGUAGE_DETERMINED, 351 NotificationService::AllSources()); 352 notification_registrar_.Add(this, NotificationType::PAGE_TRANSLATED, 353 NotificationService::AllSources()); 354} 355 356void TranslateManager::InitiateTranslation(TabContents* tab, 357 const std::string& page_lang) { 358 PrefService* prefs = tab->profile()->GetOriginalProfile()->GetPrefs(); 359 if (!prefs->GetBoolean(prefs::kEnableTranslate)) 360 return; 361 362 pref_change_registrar_.Init(prefs); 363 364 // Allow disabling of translate from the command line to assist with 365 // automated browser testing. 366 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableTranslate)) 367 return; 368 369 NavigationEntry* entry = tab->controller().GetActiveEntry(); 370 if (!entry) { 371 // This can happen for popups created with window.open(""). 372 return; 373 } 374 375 // If there is already a translate infobar showing, don't show another one. 376 if (GetTranslateInfoBarDelegate(tab)) 377 return; 378 379 std::string target_lang = GetTargetLanguage(); 380 // Nothing to do if either the language Chrome is in or the language of the 381 // page is not supported by the translation server. 382 if (target_lang.empty() || !IsSupportedLanguage(page_lang)) { 383 return; 384 } 385 386 // We don't want to translate: 387 // - any Chrome specific page (New Tab Page, Download, History... pages). 388 // - similar languages (ex: en-US to en). 389 // - any user black-listed URLs or user selected language combination. 390 // - any language the user configured as accepted languages. 391 if (!IsTranslatableURL(entry->url()) || page_lang == target_lang || 392 !TranslatePrefs::CanTranslate(prefs, page_lang, entry->url()) || 393 IsAcceptLanguage(tab, page_lang)) { 394 return; 395 } 396 397 // If the user has previously selected "always translate" for this language we 398 // automatically translate. Note that in incognito mode we disable that 399 // feature; the user will get an infobar, so they can control whether the 400 // page's text is sent to the translate server. 401 std::string auto_target_lang; 402 if (!tab->profile()->IsOffTheRecord() && 403 TranslatePrefs::ShouldAutoTranslate(prefs, page_lang, 404 &auto_target_lang)) { 405 TranslatePage(tab, page_lang, auto_target_lang); 406 return; 407 } 408 409 std::string auto_translate_to = tab->language_state().AutoTranslateTo(); 410 if (!auto_translate_to.empty()) { 411 // This page was navigated through a click from a translated page. 412 TranslatePage(tab, page_lang, auto_translate_to); 413 return; 414 } 415 416 // Prompts the user if he/she wants the page translated. 417 tab->AddInfoBar(TranslateInfoBarDelegate::CreateDelegate( 418 TranslateInfoBarDelegate::BEFORE_TRANSLATE, tab, page_lang, target_lang)); 419} 420 421void TranslateManager::InitiateTranslationPosted( 422 int process_id, int render_id, const std::string& page_lang) { 423 // The tab might have been closed. 424 TabContents* tab = tab_util::GetTabContentsByID(process_id, render_id); 425 if (!tab || tab->language_state().translation_pending()) 426 return; 427 428 InitiateTranslation(tab, page_lang); 429} 430 431void TranslateManager::TranslatePage(TabContents* tab_contents, 432 const std::string& source_lang, 433 const std::string& target_lang) { 434 NavigationEntry* entry = tab_contents->controller().GetActiveEntry(); 435 if (!entry) { 436 NOTREACHED(); 437 return; 438 } 439 440 TranslateInfoBarDelegate* infobar = TranslateInfoBarDelegate::CreateDelegate( 441 TranslateInfoBarDelegate::TRANSLATING, tab_contents, 442 source_lang, target_lang); 443 if (!infobar) { 444 // This means the source or target languages are not supported, which should 445 // not happen as we won't show a translate infobar or have the translate 446 // context menu activated in such cases. 447 NOTREACHED(); 448 return; 449 } 450 ShowInfoBar(tab_contents, infobar); 451 452 if (!translate_script_.empty()) { 453 DoTranslatePage(tab_contents, translate_script_, source_lang, target_lang); 454 return; 455 } 456 457 // The script is not available yet. Queue that request and query for the 458 // script. Once it is downloaded we'll do the translate. 459 RenderViewHost* rvh = tab_contents->render_view_host(); 460 PendingRequest request; 461 request.render_process_id = rvh->process()->id(); 462 request.render_view_id = rvh->routing_id(); 463 request.page_id = entry->page_id(); 464 request.source_lang = source_lang; 465 request.target_lang = target_lang; 466 pending_requests_.push_back(request); 467 RequestTranslateScript(); 468} 469 470void TranslateManager::RevertTranslation(TabContents* tab_contents) { 471 NavigationEntry* entry = tab_contents->controller().GetActiveEntry(); 472 if (!entry) { 473 NOTREACHED(); 474 return; 475 } 476 tab_contents->render_view_host()->Send(new ViewMsg_RevertTranslation( 477 tab_contents->render_view_host()->routing_id(), entry->page_id())); 478 tab_contents->language_state().set_current_language( 479 tab_contents->language_state().original_language()); 480} 481 482void TranslateManager::ReportLanguageDetectionError(TabContents* tab_contents) { 483 UMA_HISTOGRAM_COUNTS("Translate.ReportLanguageDetectionError", 1); 484 GURL page_url = tab_contents->controller().GetActiveEntry()->url(); 485 std::string report_error_url(kReportLanguageDetectionErrorURL); 486 report_error_url += "?client=cr&action=langidc&u="; 487 report_error_url += EscapeUrlEncodedData(page_url.spec()); 488 report_error_url += "&sl="; 489 report_error_url += tab_contents->language_state().original_language(); 490 report_error_url += "&hl="; 491 report_error_url += 492 GetLanguageCode(g_browser_process->GetApplicationLocale()); 493 // Open that URL in a new tab so that the user can tell us more. 494 Browser* browser = BrowserList::GetLastActive(); 495 if (!browser) { 496 NOTREACHED(); 497 return; 498 } 499 browser->AddSelectedTabWithURL(GURL(report_error_url), 500 PageTransition::AUTO_BOOKMARK); 501} 502 503void TranslateManager::DoTranslatePage(TabContents* tab, 504 const std::string& translate_script, 505 const std::string& source_lang, 506 const std::string& target_lang) { 507 NavigationEntry* entry = tab->controller().GetActiveEntry(); 508 if (!entry) { 509 NOTREACHED(); 510 return; 511 } 512 513 tab->language_state().set_translation_pending(true); 514 515 tab->render_view_host()->Send(new ViewMsg_TranslatePage( 516 tab->render_view_host()->routing_id(), entry->page_id(), translate_script, 517 source_lang, target_lang)); 518 519 // Ideally we'd have a better way to uniquely identify form control elements, 520 // but we don't have that yet. So before start translation, we clear the 521 // current form and re-parse it in AutoFillManager first to get the new 522 // labels. 523 tab->autofill_manager()->Reset(); 524} 525 526void TranslateManager::PageTranslated(TabContents* tab, 527 PageTranslatedDetails* details) { 528 // Create the new infobar to display. 529 TranslateInfoBarDelegate* infobar; 530 if (details->error_type != TranslateErrors::NONE) { 531 infobar = TranslateInfoBarDelegate::CreateErrorDelegate(details->error_type, 532 tab, details->source_language, details->target_language); 533 } else if (!IsSupportedLanguage(details->source_language)) { 534 // TODO(jcivelli): http://crbug.com/9390 We should change the "after 535 // translate" infobar to support unknown as the original 536 // language. 537 UMA_HISTOGRAM_COUNTS("Translate.ServerReportedUnsupportedLanguage", 1); 538 infobar = TranslateInfoBarDelegate::CreateErrorDelegate( 539 TranslateErrors::UNSUPPORTED_LANGUAGE, tab, 540 details->source_language, details->target_language); 541 } else { 542 infobar = TranslateInfoBarDelegate::CreateDelegate( 543 TranslateInfoBarDelegate::AFTER_TRANSLATE, tab, 544 details->source_language, details->target_language); 545 } 546 ShowInfoBar(tab, infobar); 547} 548 549bool TranslateManager::IsAcceptLanguage(TabContents* tab, 550 const std::string& language) { 551 PrefService* pref_service = tab->profile()->GetOriginalProfile()->GetPrefs(); 552 PrefServiceLanguagesMap::const_iterator iter = 553 accept_languages_.find(pref_service); 554 if (iter == accept_languages_.end()) { 555 InitAcceptLanguages(pref_service); 556 // Listen for this profile going away, in which case we would need to clear 557 // the accepted languages for the profile. 558 notification_registrar_.Add(this, NotificationType::PROFILE_DESTROYED, 559 Source<Profile>(tab->profile())); 560 // Also start listening for changes in the accept languages. 561 pref_change_registrar_.Add(prefs::kAcceptLanguages, this); 562 563 iter = accept_languages_.find(pref_service); 564 } 565 566 return iter->second.count(language) != 0; 567} 568 569void TranslateManager::InitAcceptLanguages(PrefService* prefs) { 570 // We have been asked for this profile, build the languages. 571 std::string accept_langs_str = prefs->GetString(prefs::kAcceptLanguages); 572 std::vector<std::string> accept_langs_list; 573 LanguageSet accept_langs_set; 574 base::SplitString(accept_langs_str, ',', &accept_langs_list); 575 std::vector<std::string>::const_iterator iter; 576 std::string ui_lang = 577 GetLanguageCode(g_browser_process->GetApplicationLocale()); 578 bool is_ui_english = StartsWithASCII(ui_lang, "en-", false); 579 for (iter = accept_langs_list.begin(); 580 iter != accept_langs_list.end(); ++iter) { 581 // Get rid of the locale extension if any (ex: en-US -> en), but for Chinese 582 // for which the CLD reports zh-CN and zh-TW. 583 std::string accept_lang(*iter); 584 size_t index = iter->find("-"); 585 if (index != std::string::npos && *iter != "zh-CN" && *iter != "zh-TW") 586 accept_lang = iter->substr(0, index); 587 // Special-case English until we resolve bug 36182 properly. 588 // Add English only if the UI language is not English. This will annoy 589 // users of non-English Chrome who can comprehend English until English is 590 // black-listed. 591 // TODO(jungshik): Once we determine that it's safe to remove English from 592 // the default Accept-Language values for most locales, remove this 593 // special-casing. 594 if (accept_lang != "en" || is_ui_english) 595 accept_langs_set.insert(accept_lang); 596 } 597 accept_languages_[prefs] = accept_langs_set; 598} 599 600void TranslateManager::RequestTranslateScript() { 601 if (translate_script_request_pending_) 602 return; 603 604 translate_script_request_pending_ = true; 605 URLFetcher* fetcher = URLFetcher::Create(0, GURL(kTranslateScriptURL), 606 URLFetcher::GET, this); 607 fetcher->set_request_context(Profile::GetDefaultRequestContext()); 608 fetcher->set_extra_request_headers(kTranslateScriptHeader); 609 fetcher->Start(); 610} 611 612void TranslateManager::ShowInfoBar(TabContents* tab, 613 TranslateInfoBarDelegate* infobar) { 614 TranslateInfoBarDelegate* old_infobar = GetTranslateInfoBarDelegate(tab); 615 infobar->UpdateBackgroundAnimation(old_infobar); 616 if (old_infobar) { 617 // There already is a translate infobar, simply replace it. 618 tab->ReplaceInfoBar(old_infobar, infobar); 619 } else { 620 tab->AddInfoBar(infobar); 621 } 622} 623 624// static 625std::string TranslateManager::GetTargetLanguage() { 626 std::string target_lang = 627 GetLanguageCode(g_browser_process->GetApplicationLocale()); 628 return IsSupportedLanguage(target_lang) ? target_lang : std::string(); 629} 630 631// static 632TranslateInfoBarDelegate* TranslateManager::GetTranslateInfoBarDelegate( 633 TabContents* tab) { 634 for (size_t i = 0; i < tab->infobar_count(); ++i) { 635 TranslateInfoBarDelegate* delegate = 636 tab->GetInfoBarDelegateAt(i)->AsTranslateInfoBarDelegate(); 637 if (delegate) 638 return delegate; 639 } 640 return NULL; 641} 642