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