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