summaryrefslogtreecommitdiffstats
path: root/chrome/browser/translate/translate_manager2.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/translate/translate_manager2.cc')
-rw-r--r--chrome/browser/translate/translate_manager2.cc549
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;
+}