Index: chrome/browser/translate/translate_manager2.cc |
diff --git a/chrome/browser/translate/translate_manager2.cc b/chrome/browser/translate/translate_manager2.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..24cc2a6cbd05ceab137e1d7e03785b1978f28829 |
--- /dev/null |
+++ b/chrome/browser/translate/translate_manager2.cc |
@@ -0,0 +1,549 @@ |
+// Copyright (c) 2010 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "chrome/browser/translate/translate_manager2.h" |
+ |
+#include "app/resource_bundle.h" |
+#include "base/compiler_specific.h" |
+#include "base/string_util.h" |
+#include "chrome/browser/browser_process.h" |
+#include "chrome/browser/pref_service.h" |
+#include "chrome/browser/profile.h" |
+#include "chrome/browser/renderer_host/render_process_host.h" |
+#include "chrome/browser/renderer_host/render_view_host.h" |
+#include "chrome/browser/tab_contents/language_state.h" |
+#include "chrome/browser/tab_contents/navigation_controller.h" |
+#include "chrome/browser/tab_contents/navigation_entry.h" |
+#include "chrome/browser/tab_contents/tab_contents.h" |
+#include "chrome/browser/tab_contents/tab_util.h" |
+#include "chrome/browser/translate/page_translated_details.h" |
+#include "chrome/browser/translate/translate_infobar_delegate2.h" |
+#include "chrome/browser/translate/translate_prefs.h" |
+#include "chrome/common/notification_details.h" |
+#include "chrome/common/notification_service.h" |
+#include "chrome/common/notification_source.h" |
+#include "chrome/common/notification_type.h" |
+#include "chrome/common/pref_names.h" |
+#include "chrome/common/translate_errors.h" |
+#include "grit/browser_resources.h" |
+#include "net/url_request/url_request_status.h" |
+ |
+namespace { |
+ |
+// Mapping from a locale name to a language code name. |
+// Locale names not included are translated as is. |
+struct LocaleToCLDLanguage { |
+ const char* locale_language; // Language Chrome locale is in. |
+ const char* cld_language; // Language the CLD reports. |
+}; |
+LocaleToCLDLanguage kLocaleToCLDLanguages[] = { |
+ { "en-GB", "en" }, |
+ { "en-US", "en" }, |
+ { "es-419", "es" }, |
+ { "pt-BR", "pt" }, |
+ { "pt-PT", "pt" }, |
+}; |
+ |
+// The list of languages the Google translation server supports. |
+// For information, here is the list of languages that Chrome can be run in |
+// but that the translation server does not support: |
+// am Amharic |
+// bn Bengali |
+// gu Gujarati |
+// kn Kannada |
+// ml Malayalam |
+// mr Marathi |
+// ta Tamil |
+// te Telugu |
+const char* kSupportedLanguages[] = { |
+ "af", // Afrikaans |
+ "sq", // Albanian |
+ "ar", // Arabic |
+ "be", // Belarusian |
+ "bg", // Bulgarian |
+ "ca", // Catalan |
+ "zh-CN", // Chinese (Simplified) |
+ "zh-TW", // Chinese (Traditional) |
+ "hr", // Croatian |
+ "cs", // Czech |
+ "da", // Danish |
+ "nl", // Dutch |
+ "en", // English |
+ "et", // Estonian |
+ "fi", // Finnish |
+ "fil", // Filipino |
+ "fr", // French |
+ "gl", // Galician |
+ "de", // German |
+ "el", // Greek |
+ "he", // Hebrew |
+ "hi", // Hindi |
+ "hu", // Hungarian |
+ "is", // Icelandic |
+ "id", // Indonesian |
+ "it", // Italian |
+ "ga", // Irish |
+ "ja", // Japanese |
+ "ko", // Korean |
+ "lv", // Latvian |
+ "lt", // Lithuanian |
+ "mk", // Macedonian |
+ "ms", // Malay |
+ "mt", // Maltese |
+ "nb", // Norwegian |
+ "fa", // Persian |
+ "pl", // Polish |
+ "pt", // Portuguese |
+ "ro", // Romanian |
+ "ru", // Russian |
+ "sr", // Serbian |
+ "sk", // Slovak |
+ "sl", // Slovenian |
+ "es", // Spanish |
+ "sw", // Swahili |
+ "sv", // Swedish |
+ "th", // Thai |
+ "tr", // Turkish |
+ "uk", // Ukrainian |
+ "vi", // Vietnamese |
+ "cy", // Welsh |
+ "yi", // Yiddish |
+}; |
+ |
+const char* const kTranslateScriptURL = |
+ "http://translate.google.com/translate_a/element.js?" |
+ "cb=cr.googleTranslate.onTranslateElementLoad"; |
+const char* const kTranslateScriptHeader = |
+ "Google-Translate-Element-Mode: library"; |
+ |
+} // namespace |
+ |
+// static |
+base::LazyInstance<std::set<std::string> > |
+ TranslateManager2::supported_languages_(base::LINKER_INITIALIZED); |
+ |
+TranslateManager2::~TranslateManager2() { |
+} |
+ |
+// static |
+bool TranslateManager2::IsTranslatableURL(const GURL& url) { |
+ return !url.SchemeIs("chrome"); |
+} |
+ |
+// static |
+void TranslateManager2::GetSupportedLanguages( |
+ std::vector<std::string>* languages) { |
+ DCHECK(languages && languages->empty()); |
+ for (size_t i = 0; i < arraysize(kSupportedLanguages); ++i) |
+ languages->push_back(kSupportedLanguages[i]); |
+} |
+ |
+// static |
+std::string TranslateManager2::GetLanguageCode( |
+ const std::string& chrome_locale) { |
+ for (size_t i = 0; i < arraysize(kLocaleToCLDLanguages); ++i) { |
+ if (chrome_locale == kLocaleToCLDLanguages[i].locale_language) |
+ return kLocaleToCLDLanguages[i].cld_language; |
+ } |
+ return chrome_locale; |
+} |
+ |
+// static |
+bool TranslateManager2::IsSupportedLanguage(const std::string& page_language) { |
+ if (supported_languages_.Pointer()->empty()) { |
+ for (size_t i = 0; i < arraysize(kSupportedLanguages); ++i) |
+ supported_languages_.Pointer()->insert(kSupportedLanguages[i]); |
+ } |
+ return supported_languages_.Pointer()->find(page_language) != |
+ supported_languages_.Pointer()->end(); |
+} |
+ |
+void TranslateManager2::Observe(NotificationType type, |
+ const NotificationSource& source, |
+ const NotificationDetails& details) { |
+ switch (type.value) { |
+ case NotificationType::NAV_ENTRY_COMMITTED: { |
+ NavigationController* controller = |
+ Source<NavigationController>(source).ptr(); |
+ NavigationController::LoadCommittedDetails* load_details = |
+ Details<NavigationController::LoadCommittedDetails>(details).ptr(); |
+ NavigationEntry* entry = controller->GetActiveEntry(); |
+ if (!entry) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ if (entry->transition_type() != PageTransition::RELOAD && |
+ load_details->type != NavigationType::SAME_PAGE) { |
+ return; |
+ } |
+ // When doing a page reload, we don't get a TAB_LANGUAGE_DETERMINED |
+ // notification. So we need to explictly initiate the translation. |
+ // Note that we delay it as the TranslateManager2 gets this notification |
+ // before the TabContents and the TabContents processing might remove the |
+ // current infobars. Since InitTranslation might add an infobar, it must |
+ // be done after that. |
+ MessageLoop::current()->PostTask(FROM_HERE, |
+ method_factory_.NewRunnableMethod( |
+ &TranslateManager2::InitiateTranslationPosted, |
+ controller->tab_contents()->render_view_host()->process()->id(), |
+ controller->tab_contents()->render_view_host()->routing_id(), |
+ controller->tab_contents()->language_state(). |
+ original_language())); |
+ break; |
+ } |
+ case NotificationType::TAB_LANGUAGE_DETERMINED: { |
+ TabContents* tab = Source<TabContents>(source).ptr(); |
+ // We may get this notifications multiple times. Make sure to translate |
+ // only once. |
+ LanguageState& language_state = tab->language_state(); |
+ if (!language_state.translation_pending() && |
+ !language_state.translation_declined() && |
+ !language_state.IsPageTranslated()) { |
+ std::string language = *(Details<std::string>(details).ptr()); |
+ InitiateTranslation(tab, language); |
+ } |
+ break; |
+ } |
+ case NotificationType::PAGE_TRANSLATED: { |
+ // Only add translate infobar if it doesn't exist; if it already exists, |
+ // just update the state, the actual infobar would have received the same |
+ // notification and update the visual display accordingly. |
+ TabContents* tab = Source<TabContents>(source).ptr(); |
+ PageTranslatedDetails* page_translated_details = |
+ Details<PageTranslatedDetails>(details).ptr(); |
+ PageTranslated(tab, page_translated_details); |
+ break; |
+ } |
+ case NotificationType::PROFILE_DESTROYED: { |
+ Profile* profile = Source<Profile>(source).ptr(); |
+ notification_registrar_.Remove(this, NotificationType::PROFILE_DESTROYED, |
+ source); |
+ size_t count = accept_languages_.erase(profile->GetPrefs()); |
+ // We should know about this profile since we are listening for |
+ // notifications on it. |
+ DCHECK(count > 0); |
+ profile->GetPrefs()->RemovePrefObserver(prefs::kAcceptLanguages, this); |
+ break; |
+ } |
+ case NotificationType::PREF_CHANGED: { |
+ DCHECK(*Details<std::wstring>(details).ptr() == prefs::kAcceptLanguages); |
+ PrefService* prefs = Source<PrefService>(source).ptr(); |
+ InitAcceptLanguages(prefs); |
+ break; |
+ } |
+ default: |
+ NOTREACHED(); |
+ } |
+} |
+ |
+void TranslateManager2::OnURLFetchComplete(const URLFetcher* source, |
+ const GURL& url, |
+ const URLRequestStatus& status, |
+ int response_code, |
+ const ResponseCookies& cookies, |
+ const std::string& data) { |
+ scoped_ptr<const URLFetcher> delete_ptr(source); |
+ DCHECK(translate_script_request_pending_); |
+ translate_script_request_pending_ = false; |
+ bool error = |
+ (status.status() != URLRequestStatus::SUCCESS || response_code != 200); |
+ |
+ if (!error) { |
+ base::StringPiece str = ResourceBundle::GetSharedInstance(). |
+ GetRawDataResource(IDR_TRANSLATE_JS); |
+ DCHECK(translate_script_.empty()); |
+ str.CopyToString(&translate_script_); |
+ translate_script_ += "\n" + data; |
+ } |
+ |
+ // Process any pending requests. |
+ std::vector<PendingRequest>::const_iterator iter; |
+ for (iter = pending_requests_.begin(); iter != pending_requests_.end(); |
+ ++iter) { |
+ const PendingRequest& request = *iter; |
+ TabContents* tab = tab_util::GetTabContentsByID(request.render_process_id, |
+ request.render_view_id); |
+ if (!tab) { |
+ // The tab went away while we were retrieving the script. |
+ continue; |
+ } |
+ NavigationEntry* entry = tab->controller().GetActiveEntry(); |
+ if (!entry || entry->page_id() != request.page_id) { |
+ // We navigated away from the page the translation was triggered on. |
+ continue; |
+ } |
+ |
+ if (error) { |
+ ShowInfoBar(tab, |
+ TranslateInfoBarDelegate2::CreateInstance( |
+ TranslateInfoBarDelegate2::TRANSLATION_ERROR, |
+ TranslateErrors::NETWORK, |
+ tab, request.source_lang, request.target_lang)); |
+ } else { |
+ // Translate the page. |
+ DoTranslatePage(tab, translate_script_, |
+ request.source_lang, request.target_lang); |
+ } |
+ } |
+ pending_requests_.clear(); |
+} |
+ |
+// static |
+bool TranslateManager2::IsShowingTranslateInfobar(TabContents* tab) { |
+ return GetTranslateInfoBarDelegate2(tab) != NULL; |
+} |
+ |
+TranslateManager2::TranslateManager2() |
+ : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), |
+ translate_script_request_pending_(false) { |
+ notification_registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, |
+ NotificationService::AllSources()); |
+ notification_registrar_.Add(this, NotificationType::TAB_LANGUAGE_DETERMINED, |
+ NotificationService::AllSources()); |
+ notification_registrar_.Add(this, NotificationType::PAGE_TRANSLATED, |
+ NotificationService::AllSources()); |
+} |
+ |
+void TranslateManager2::InitiateTranslation(TabContents* tab, |
+ const std::string& page_lang) { |
+ PrefService* prefs = tab->profile()->GetPrefs(); |
+ if (!prefs->GetBoolean(prefs::kEnableTranslate)) |
+ return; |
+ |
+ NavigationEntry* entry = tab->controller().GetActiveEntry(); |
+ if (!entry) { |
+ // This can happen for popups created with window.open(""). |
+ return; |
+ } |
+ |
+ // If there is already a translate infobar showing, don't show another one. |
+ if (GetTranslateInfoBarDelegate2(tab)) |
+ return; |
+ |
+ std::string target_lang = GetTargetLanguage(); |
+ // Nothing to do if either the language Chrome is in or the language of the |
+ // page is not supported by the translation server. |
+ if (target_lang.empty() || !IsSupportedLanguage(page_lang)) { |
+ return; |
+ } |
+ |
+ // We don't want to translate: |
+ // - any Chrome specific page (New Tab Page, Download, History... pages). |
+ // - similar languages (ex: en-US to en). |
+ // - any user black-listed URLs or user selected language combination. |
+ // - any language the user configured as accepted languages. |
+ if (!IsTranslatableURL(entry->url()) || page_lang == target_lang || |
+ !TranslatePrefs::CanTranslate(prefs, page_lang, entry->url()) || |
+ IsAcceptLanguage(tab, page_lang)) { |
+ return; |
+ } |
+ |
+ // If the user has previously selected "always translate" for this language we |
+ // automatically translate. Note that in incognito mode we disable that |
+ // feature; the user will get an infobar, so they can control whether the |
+ // page's text is sent to the translate server. |
+ std::string auto_target_lang; |
+ if (!tab->profile()->IsOffTheRecord() && |
+ TranslatePrefs::ShouldAutoTranslate(prefs, page_lang, |
+ &auto_target_lang)) { |
+ TranslatePage(tab, page_lang, auto_target_lang); |
+ return; |
+ } |
+ |
+ std::string auto_translate_to = tab->language_state().AutoTranslateTo(); |
+ if (!auto_translate_to.empty()) { |
+ // This page was navigated through a click from a translated page. |
+ TranslatePage(tab, page_lang, auto_translate_to); |
+ return; |
+ } |
+ |
+ // Prompts the user if he/she wants the page translated. |
+ tab->AddInfoBar(TranslateInfoBarDelegate2::CreateInstance( |
+ TranslateInfoBarDelegate2::BEFORE_TRANSLATE, |
+ TranslateErrors::NONE, tab, page_lang, target_lang)); |
+} |
+ |
+void TranslateManager2::InitiateTranslationPosted( |
+ int process_id, int render_id, const std::string& page_lang) { |
+ // The tab might have been closed. |
+ TabContents* tab = tab_util::GetTabContentsByID(process_id, render_id); |
+ if (!tab || tab->language_state().translation_pending()) |
+ return; |
+ |
+ InitiateTranslation(tab, page_lang); |
+} |
+ |
+void TranslateManager2::TranslatePage(TabContents* tab_contents, |
+ const std::string& source_lang, |
+ const std::string& target_lang) { |
+ NavigationEntry* entry = tab_contents->controller().GetActiveEntry(); |
+ if (!entry) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ if (!translate_script_.empty()) { |
+ DoTranslatePage(tab_contents, translate_script_, source_lang, target_lang); |
+ return; |
+ } |
+ |
+ // The script is not available yet. Queue that request and query for the |
+ // script. Once it is downloaded we'll do the translate. |
+ RenderViewHost* rvh = tab_contents->render_view_host(); |
+ PendingRequest request; |
+ request.render_process_id = rvh->process()->id(); |
+ request.render_view_id = rvh->routing_id(); |
+ request.page_id = entry->page_id(); |
+ request.source_lang = source_lang; |
+ request.target_lang = target_lang; |
+ pending_requests_.push_back(request); |
+ RequestTranslateScript(); |
+} |
+ |
+void TranslateManager2::RevertTranslation(TabContents* tab_contents) { |
+ NavigationEntry* entry = tab_contents->controller().GetActiveEntry(); |
+ if (!entry) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ tab_contents->render_view_host()->RevertTranslation(entry->page_id()); |
+ tab_contents->language_state().set_current_language( |
+ tab_contents->language_state().original_language()); |
+} |
+ |
+void TranslateManager2::DoTranslatePage(TabContents* tab, |
+ const std::string& translate_script, |
+ const std::string& source_lang, |
+ const std::string& target_lang) { |
+ NavigationEntry* entry = tab->controller().GetActiveEntry(); |
+ if (!entry) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ |
+ TranslateInfoBarDelegate2* infobar = GetTranslateInfoBarDelegate2(tab); |
+ if (infobar) { |
+ // We don't show the translating infobar if no translate infobar is already |
+ // showing (that is the case when the translation was triggered by the |
+ // "always translate" for example). |
+ infobar = TranslateInfoBarDelegate2::CreateInstance( |
+ TranslateInfoBarDelegate2::TRANSLATING, TranslateErrors::NONE, |
+ tab, source_lang, target_lang); |
+ ShowInfoBar(tab, infobar); |
+ } |
+ tab->language_state().set_translation_pending(true); |
+ tab->render_view_host()->TranslatePage(entry->page_id(), translate_script, |
+ source_lang, target_lang); |
+} |
+ |
+void TranslateManager2::PageTranslated(TabContents* tab, |
+ PageTranslatedDetails* details) { |
+ // Create the new infobar to display. |
+ TranslateInfoBarDelegate2* infobar; |
+ if (details->error_type != TranslateErrors::NONE) { |
+ infobar = TranslateInfoBarDelegate2::CreateInstance( |
+ TranslateInfoBarDelegate2::TRANSLATION_ERROR, details->error_type, |
+ tab, details->source_language, details->target_language); |
+ } else { |
+ infobar = TranslateInfoBarDelegate2::CreateInstance( |
+ TranslateInfoBarDelegate2::AFTER_TRANSLATE, TranslateErrors::NONE, |
+ tab, details->source_language, details->target_language); |
+ } |
+ ShowInfoBar(tab, infobar); |
+} |
+ |
+bool TranslateManager2::IsAcceptLanguage(TabContents* tab, |
+ const std::string& language) { |
+ PrefService* pref_service = tab->profile()->GetPrefs(); |
+ PrefServiceLanguagesMap::const_iterator iter = |
+ accept_languages_.find(pref_service); |
+ if (iter == accept_languages_.end()) { |
+ InitAcceptLanguages(pref_service); |
+ // Listen for this profile going away, in which case we would need to clear |
+ // the accepted languages for the profile. |
+ notification_registrar_.Add(this, NotificationType::PROFILE_DESTROYED, |
+ Source<Profile>(tab->profile())); |
+ // Also start listening for changes in the accept languages. |
+ tab->profile()->GetPrefs()->AddPrefObserver(prefs::kAcceptLanguages, this); |
+ |
+ iter = accept_languages_.find(pref_service); |
+ } |
+ |
+ return iter->second.count(language) != 0; |
+} |
+ |
+void TranslateManager2::InitAcceptLanguages(PrefService* prefs) { |
+ // We have been asked for this profile, build the languages. |
+ std::wstring accept_langs_str = prefs->GetString(prefs::kAcceptLanguages); |
+ std::vector<std::string> accept_langs_list; |
+ LanguageSet accept_langs_set; |
+ SplitString(WideToASCII(accept_langs_str), ',', &accept_langs_list); |
+ std::vector<std::string>::const_iterator iter; |
+ std::string ui_lang = |
+ GetLanguageCode(g_browser_process->GetApplicationLocale()); |
+ bool is_ui_english = StartsWithASCII(ui_lang, "en-", false); |
+ for (iter = accept_langs_list.begin(); |
+ iter != accept_langs_list.end(); ++iter) { |
+ // Get rid of the locale extension if any (ex: en-US -> en), but for Chinese |
+ // for which the CLD reports zh-CN and zh-TW. |
+ std::string accept_lang(*iter); |
+ size_t index = iter->find("-"); |
+ if (index != std::string::npos && *iter != "zh-CN" && *iter != "zh-TW") |
+ accept_lang = iter->substr(0, index); |
+ // Special-case English until we resolve bug 36182 properly. |
+ // Add English only if the UI language is not English. This will annoy |
+ // users of non-English Chrome who can comprehend English until English is |
+ // black-listed. |
+ // TODO(jungshik): Once we determine that it's safe to remove English from |
+ // the default Accept-Language values for most locales, remove this |
+ // special-casing. |
+ if (accept_lang != "en" || is_ui_english) |
+ accept_langs_set.insert(accept_lang); |
+ } |
+ accept_languages_[prefs] = accept_langs_set; |
+} |
+ |
+void TranslateManager2::RequestTranslateScript() { |
+ if (translate_script_request_pending_) |
+ return; |
+ |
+ translate_script_request_pending_ = true; |
+ URLFetcher* fetcher = URLFetcher::Create(0, GURL(kTranslateScriptURL), |
+ URLFetcher::GET, this); |
+ fetcher->set_request_context(Profile::GetDefaultRequestContext()); |
+ fetcher->set_extra_request_headers(kTranslateScriptHeader); |
+ fetcher->Start(); |
+} |
+ |
+void TranslateManager2::ShowInfoBar(TabContents* tab, |
+ TranslateInfoBarDelegate2* infobar) { |
+ TranslateInfoBarDelegate2* old_infobar = GetTranslateInfoBarDelegate2(tab); |
+ infobar->UpdateBackgroundAnimation(old_infobar); |
+ if (old_infobar) { |
+ // There already is a translate infobar, simply replace it. |
+ tab->ReplaceInfoBar(old_infobar, infobar); |
+ } else { |
+ tab->AddInfoBar(infobar); |
+ } |
+} |
+ |
+// static |
+std::string TranslateManager2::GetTargetLanguage() { |
+ std::string target_lang = |
+ GetLanguageCode(g_browser_process->GetApplicationLocale()); |
+ if (IsSupportedLanguage(target_lang)) |
+ return target_lang; |
+ return std::string(); |
+} |
+ |
+// static |
+TranslateInfoBarDelegate2* TranslateManager2::GetTranslateInfoBarDelegate2( |
+ TabContents* tab) { |
+ for (int i = 0; i < tab->infobar_delegate_count(); ++i) { |
+ TranslateInfoBarDelegate2* delegate = |
+ tab->GetInfoBarDelegateAt(i)->AsTranslateInfoBarDelegate2(); |
+ if (delegate) |
+ return delegate; |
+ } |
+ return NULL; |
+} |