diff options
Diffstat (limited to 'chrome/browser/translate/translate_manager2.cc')
-rw-r--r-- | chrome/browser/translate/translate_manager2.cc | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/chrome/browser/translate/translate_manager2.cc b/chrome/browser/translate/translate_manager2.cc new file mode 100644 index 0000000..24cc2a6c --- /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; +} |