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