Home | History | Annotate | Download | only in browser
      1 // Copyright 2013 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 "components/translate/core/browser/translate_language_list.h"
      6 
      7 #include <set>
      8 
      9 #include "base/bind.h"
     10 #include "base/json/json_reader.h"
     11 #include "base/lazy_instance.h"
     12 #include "base/logging.h"
     13 #include "base/strings/string_util.h"
     14 #include "base/strings/stringprintf.h"
     15 #include "base/values.h"
     16 #include "components/translate/core/browser/translate_browser_metrics.h"
     17 #include "components/translate/core/browser/translate_download_manager.h"
     18 #include "components/translate/core/browser/translate_event_details.h"
     19 #include "components/translate/core/browser/translate_url_fetcher.h"
     20 #include "components/translate/core/browser/translate_url_util.h"
     21 #include "components/translate/core/common/translate_util.h"
     22 #include "net/base/url_util.h"
     23 #include "ui/base/l10n/l10n_util.h"
     24 #include "url/gurl.h"
     25 
     26 namespace {
     27 
     28 // The default list of languages the Google translation server supports.
     29 // We use this list until we receive the list that the server exposes.
     30 // For information, here is the list of languages that Chrome can be run in
     31 // but that the translation server does not support:
     32 // am Amharic
     33 // bn Bengali
     34 // gu Gujarati
     35 // kn Kannada
     36 // ml Malayalam
     37 // mr Marathi
     38 // ta Tamil
     39 // te Telugu
     40 const char* const kDefaultSupportedLanguages[] = {
     41   "af",     // Afrikaans
     42   "sq",     // Albanian
     43   "ar",     // Arabic
     44   "be",     // Belarusian
     45   "bg",     // Bulgarian
     46   "ca",     // Catalan
     47   "zh-CN",  // Chinese (Simplified)
     48   "zh-TW",  // Chinese (Traditional)
     49   "hr",     // Croatian
     50   "cs",     // Czech
     51   "da",     // Danish
     52   "nl",     // Dutch
     53   "en",     // English
     54   "eo",     // Esperanto
     55   "et",     // Estonian
     56   "tl",     // Filipino
     57   "fi",     // Finnish
     58   "fr",     // French
     59   "gl",     // Galician
     60   "de",     // German
     61   "el",     // Greek
     62   "ht",     // Haitian Creole
     63   "iw",     // Hebrew
     64   "hi",     // Hindi
     65   "hu",     // Hungarian
     66   "is",     // Icelandic
     67   "id",     // Indonesian
     68   "ga",     // Irish
     69   "it",     // Italian
     70   "ja",     // Japanese
     71   "ko",     // Korean
     72   "lv",     // Latvian
     73   "lt",     // Lithuanian
     74   "mk",     // Macedonian
     75   "ms",     // Malay
     76   "mt",     // Maltese
     77   "no",     // Norwegian
     78   "fa",     // Persian
     79   "pl",     // Polish
     80   "pt",     // Portuguese
     81   "ro",     // Romanian
     82   "ru",     // Russian
     83   "sr",     // Serbian
     84   "sk",     // Slovak
     85   "sl",     // Slovenian
     86   "es",     // Spanish
     87   "sw",     // Swahili
     88   "sv",     // Swedish
     89   "th",     // Thai
     90   "tr",     // Turkish
     91   "uk",     // Ukrainian
     92   "vi",     // Vietnamese
     93   "cy",     // Welsh
     94   "yi",     // Yiddish
     95 };
     96 
     97 // Constant URL string to fetch server supporting language list.
     98 const char kLanguageListFetchPath[] = "translate_a/l?client=chrome&cb=sl";
     99 
    100 // Used in kTranslateScriptURL to request supporting languages list including
    101 // "alpha languages".
    102 const char kAlphaLanguageQueryName[] = "alpha";
    103 const char kAlphaLanguageQueryValue[] = "1";
    104 
    105 // Represent if the language list updater is disabled.
    106 bool update_is_disabled = false;
    107 
    108 // Retry parameter for fetching.
    109 const int kMaxRetryOn5xx = 5;
    110 
    111 }  // namespace
    112 
    113 // This must be kept in sync with the &cb= value in the kLanguageListFetchURL.
    114 const char TranslateLanguageList::kLanguageListCallbackName[] = "sl(";
    115 const char TranslateLanguageList::kTargetLanguagesKey[] = "tl";
    116 const char TranslateLanguageList::kAlphaLanguagesKey[] = "al";
    117 
    118 TranslateLanguageList::TranslateLanguageList()
    119     : resource_requests_allowed_(false), request_pending_(false) {
    120   // We default to our hard coded list of languages in
    121   // |kDefaultSupportedLanguages|. This list will be overriden by a server
    122   // providing supported langauges list.
    123   for (size_t i = 0; i < arraysize(kDefaultSupportedLanguages); ++i)
    124     all_supported_languages_.insert(kDefaultSupportedLanguages[i]);
    125 
    126   if (update_is_disabled)
    127     return;
    128 
    129   language_list_fetcher_.reset(new TranslateURLFetcher(kFetcherId));
    130   language_list_fetcher_->set_max_retry_on_5xx(kMaxRetryOn5xx);
    131 }
    132 
    133 TranslateLanguageList::~TranslateLanguageList() {}
    134 
    135 void TranslateLanguageList::GetSupportedLanguages(
    136     std::vector<std::string>* languages) {
    137   DCHECK(languages && languages->empty());
    138   std::set<std::string>::const_iterator iter = all_supported_languages_.begin();
    139   for (; iter != all_supported_languages_.end(); ++iter)
    140     languages->push_back(*iter);
    141 
    142   // Update language lists if they are not updated after Chrome was launched
    143   // for later requests.
    144   if (!update_is_disabled && language_list_fetcher_.get())
    145     RequestLanguageList();
    146 }
    147 
    148 std::string TranslateLanguageList::GetLanguageCode(
    149     const std::string& language) {
    150   // Only remove the country code for country specific languages we don't
    151   // support specifically yet.
    152   if (IsSupportedLanguage(language))
    153     return language;
    154 
    155   size_t hypen_index = language.find('-');
    156   if (hypen_index == std::string::npos)
    157     return language;
    158   return language.substr(0, hypen_index);
    159 }
    160 
    161 bool TranslateLanguageList::IsSupportedLanguage(const std::string& language) {
    162   return all_supported_languages_.count(language) != 0;
    163 }
    164 
    165 bool TranslateLanguageList::IsAlphaLanguage(const std::string& language) {
    166   return alpha_languages_.count(language) != 0;
    167 }
    168 
    169 GURL TranslateLanguageList::TranslateLanguageUrl() {
    170   std::string url = translate::GetTranslateSecurityOrigin().spec() +
    171       kLanguageListFetchPath;
    172   return GURL(url);
    173 }
    174 
    175 void TranslateLanguageList::RequestLanguageList() {
    176   // If resource requests are not allowed, we'll get a callback when they are.
    177   if (!resource_requests_allowed_) {
    178     request_pending_ = true;
    179     return;
    180   }
    181 
    182   request_pending_ = false;
    183 
    184   if (language_list_fetcher_.get() &&
    185       (language_list_fetcher_->state() == TranslateURLFetcher::IDLE ||
    186        language_list_fetcher_->state() == TranslateURLFetcher::FAILED)) {
    187     GURL url = TranslateLanguageUrl();
    188     url = TranslateURLUtil::AddHostLocaleToUrl(url);
    189     url = TranslateURLUtil::AddApiKeyToUrl(url);
    190     url = net::AppendQueryParameter(
    191         url, kAlphaLanguageQueryName, kAlphaLanguageQueryValue);
    192 
    193     std::string message = base::StringPrintf(
    194         "Language list including alpha languages fetch starts (URL: %s)",
    195         url.spec().c_str());
    196     NotifyEvent(__LINE__, message);
    197 
    198     bool result = language_list_fetcher_->Request(
    199         url,
    200         base::Bind(&TranslateLanguageList::OnLanguageListFetchComplete,
    201                    base::Unretained(this)));
    202     if (!result)
    203       NotifyEvent(__LINE__, "Request is omitted due to retry limit");
    204   }
    205 }
    206 
    207 void TranslateLanguageList::SetResourceRequestsAllowed(bool allowed) {
    208   resource_requests_allowed_ = allowed;
    209   if (resource_requests_allowed_ && request_pending_) {
    210     RequestLanguageList();
    211     DCHECK(!request_pending_);
    212   }
    213 }
    214 
    215 scoped_ptr<TranslateLanguageList::EventCallbackList::Subscription>
    216 TranslateLanguageList::RegisterEventCallback(const EventCallback& callback) {
    217   return callback_list_.Add(callback);
    218 }
    219 
    220 // static
    221 void TranslateLanguageList::DisableUpdate() {
    222   update_is_disabled = true;
    223 }
    224 
    225 void TranslateLanguageList::OnLanguageListFetchComplete(
    226     int id,
    227     bool success,
    228     const std::string& data) {
    229   if (!success) {
    230     // Since it fails just now, omit to schedule resource requests if
    231     // ResourceRequestAllowedNotifier think it's ready. Otherwise, a callback
    232     // will be invoked later to request resources again.
    233     // The TranslateURLFetcher has a limit for retried requests and aborts
    234     // re-try not to invoke OnLanguageListFetchComplete anymore if it's asked to
    235     // re-try too many times.
    236     NotifyEvent(__LINE__, "Failed to fetch languages");
    237     return;
    238   }
    239 
    240   NotifyEvent(__LINE__, "Language list is updated");
    241 
    242   DCHECK_EQ(kFetcherId, id);
    243 
    244   SetSupportedLanguages(data);
    245   language_list_fetcher_.reset();
    246 
    247   last_updated_ = base::Time::Now();
    248 }
    249 
    250 void TranslateLanguageList::NotifyEvent(int line, const std::string& message) {
    251   TranslateEventDetails details(__FILE__, line, message);
    252   callback_list_.Notify(details);
    253 }
    254 
    255 void TranslateLanguageList::SetSupportedLanguages(
    256     const std::string& language_list) {
    257   // The format is:
    258   // sl({
    259   //   "sl": {"XX": "LanguageName", ...},
    260   //   "tl": {"XX": "LanguageName", ...},
    261   //   "al": {"XX": 1, ...}
    262   // })
    263   // Where "sl(" is set in kLanguageListCallbackName, "tl" is
    264   // kTargetLanguagesKey and "al" kAlphaLanguagesKey.
    265   if (!StartsWithASCII(language_list,
    266                        TranslateLanguageList::kLanguageListCallbackName,
    267                        false) ||
    268       !EndsWith(language_list, ")", false)) {
    269     // We don't have a NOTREACHED here since this can happen in ui_tests, even
    270     // though the the BrowserMain function won't call us with parameters.ui_task
    271     // is NULL some tests don't set it, so we must bail here.
    272     return;
    273   }
    274   static const size_t kLanguageListCallbackNameLength =
    275       strlen(TranslateLanguageList::kLanguageListCallbackName);
    276   std::string languages_json = language_list.substr(
    277       kLanguageListCallbackNameLength,
    278       language_list.size() - kLanguageListCallbackNameLength - 1);
    279   scoped_ptr<base::Value> json_value(
    280       base::JSONReader::Read(languages_json, base::JSON_ALLOW_TRAILING_COMMAS));
    281   if (json_value == NULL || !json_value->IsType(base::Value::TYPE_DICTIONARY)) {
    282     NOTREACHED();
    283     return;
    284   }
    285   // The first level dictionary contains three sub-dict, first for source
    286   // languages and second for target languages, we want to use the target
    287   // languages. The last is for alpha languages.
    288   base::DictionaryValue* language_dict =
    289       static_cast<base::DictionaryValue*>(json_value.get());
    290   base::DictionaryValue* target_languages = NULL;
    291   if (!language_dict->GetDictionary(TranslateLanguageList::kTargetLanguagesKey,
    292                                     &target_languages) ||
    293       target_languages == NULL) {
    294     NOTREACHED();
    295     return;
    296   }
    297 
    298   const std::string& locale =
    299       TranslateDownloadManager::GetInstance()->application_locale();
    300 
    301   // Now we can clear language list.
    302   all_supported_languages_.clear();
    303   std::string message;
    304   // ... and replace it with the values we just fetched from the server.
    305   for (base::DictionaryValue::Iterator iter(*target_languages);
    306        !iter.IsAtEnd();
    307        iter.Advance()) {
    308     const std::string& lang = iter.key();
    309     if (!l10n_util::IsLocaleNameTranslated(lang.c_str(), locale)) {
    310       TranslateBrowserMetrics::ReportUndisplayableLanguage(lang);
    311       continue;
    312     }
    313     all_supported_languages_.insert(lang);
    314     if (message.empty())
    315       message += lang;
    316     else
    317       message += ", " + lang;
    318   }
    319   NotifyEvent(__LINE__, message);
    320 
    321   // Get the alpha languages. The "al" parameter could be abandoned.
    322   base::DictionaryValue* alpha_languages = NULL;
    323   if (!language_dict->GetDictionary(TranslateLanguageList::kAlphaLanguagesKey,
    324                                     &alpha_languages) ||
    325       alpha_languages == NULL) {
    326     return;
    327   }
    328 
    329   // We assume that the alpha languages are included in the above target
    330   // languages, and don't use UMA or NotifyEvent.
    331   alpha_languages_.clear();
    332   for (base::DictionaryValue::Iterator iter(*alpha_languages);
    333        !iter.IsAtEnd(); iter.Advance()) {
    334     const std::string& lang = iter.key();
    335     if (!l10n_util::IsLocaleNameTranslated(lang.c_str(), locale))
    336       continue;
    337     alpha_languages_.insert(lang);
    338   }
    339 }
    340