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