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 "chrome/browser/translate/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 "chrome/browser/browser_process.h" 17 #include "chrome/browser/translate/translate_browser_metrics.h" 18 #include "chrome/browser/translate/translate_event_details.h" 19 #include "chrome/browser/translate/translate_manager.h" 20 #include "chrome/browser/translate/translate_url_fetcher.h" 21 #include "chrome/browser/translate/translate_url_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 kLanguageListFetchURL[] = 99 "https://translate.googleapis.com/translate_a/l?client=chrome&cb=sl"; 100 101 // Used in kTranslateScriptURL to request supporting languages list including 102 // "alpha languages". 103 const char kAlphaLanguageQueryName[] = "alpha"; 104 const char kAlphaLanguageQueryValue[] = "1"; 105 106 // Represent if the language list updater is disabled. 107 bool update_is_disabled = false; 108 109 // Retry parameter for fetching. 110 const int kMaxRetryOn5xx = 5; 111 112 // Show a message in chrome:://translate-internals Event Logs. 113 void NotifyEvent(int line, const std::string& message) { 114 TranslateManager* manager = TranslateManager::GetInstance(); 115 DCHECK(manager); 116 117 TranslateEventDetails details(__FILE__, line, message); 118 manager->NotifyTranslateEvent(details); 119 } 120 121 // Parses |language_list| containing the list of languages that the translate 122 // server can translate to and from, and fills |set| with them. 123 void SetSupportedLanguages(const std::string& language_list, 124 std::set<std::string>* target_language_set, 125 std::set<std::string>* alpha_language_set) { 126 DCHECK(target_language_set); 127 DCHECK(alpha_language_set); 128 129 // The format is: 130 // sl({ 131 // "sl": {"XX": "LanguageName", ...}, 132 // "tl": {"XX": "LanguageName", ...}, 133 // "al": {"XX": 1, ...} 134 // }) 135 // Where "sl(" is set in kLanguageListCallbackName, "tl" is 136 // kTargetLanguagesKey and "al" kAlphaLanguagesKey. 137 if (!StartsWithASCII(language_list, 138 TranslateLanguageList::kLanguageListCallbackName, 139 false) || 140 !EndsWith(language_list, ")", false)) { 141 // We don't have a NOTREACHED here since this can happen in ui_tests, even 142 // though the the BrowserMain function won't call us with parameters.ui_task 143 // is NULL some tests don't set it, so we must bail here. 144 return; 145 } 146 static const size_t kLanguageListCallbackNameLength = 147 strlen(TranslateLanguageList::kLanguageListCallbackName); 148 std::string languages_json = language_list.substr( 149 kLanguageListCallbackNameLength, 150 language_list.size() - kLanguageListCallbackNameLength - 1); 151 scoped_ptr<Value> json_value( 152 base::JSONReader::Read(languages_json, base::JSON_ALLOW_TRAILING_COMMAS)); 153 if (json_value == NULL || !json_value->IsType(Value::TYPE_DICTIONARY)) { 154 NOTREACHED(); 155 return; 156 } 157 // The first level dictionary contains three sub-dict, first for source 158 // languages and second for target languages, we want to use the target 159 // languages. The last is for alpha languages. 160 DictionaryValue* language_dict = 161 static_cast<DictionaryValue*>(json_value.get()); 162 DictionaryValue* target_languages = NULL; 163 if (!language_dict->GetDictionary(TranslateLanguageList::kTargetLanguagesKey, 164 &target_languages) || 165 target_languages == NULL) { 166 NOTREACHED(); 167 return; 168 } 169 170 const std::string& locale = g_browser_process->GetApplicationLocale(); 171 172 // Now we can clear language list. 173 target_language_set->clear(); 174 std::string message; 175 // ... and replace it with the values we just fetched from the server. 176 for (DictionaryValue::Iterator iter(*target_languages); 177 !iter.IsAtEnd(); 178 iter.Advance()) { 179 const std::string& lang = iter.key(); 180 if (!l10n_util::IsLocaleNameTranslated(lang.c_str(), locale)) { 181 TranslateBrowserMetrics::ReportUndisplayableLanguage(lang); 182 continue; 183 } 184 target_language_set->insert(lang); 185 if (message.empty()) 186 message += lang; 187 else 188 message += ", " + lang; 189 } 190 NotifyEvent(__LINE__, message); 191 192 // Get the alpha languages. The "al" parameter could be abandoned. 193 DictionaryValue* alpha_languages = NULL; 194 if (!language_dict->GetDictionary(TranslateLanguageList::kAlphaLanguagesKey, 195 &alpha_languages) || 196 alpha_languages == NULL) { 197 return; 198 } 199 200 // We assume that the alpha languages are included in the above target 201 // languages, and don't use UMA or NotifyEvent. 202 alpha_language_set->clear(); 203 for (DictionaryValue::Iterator iter(*alpha_languages); 204 !iter.IsAtEnd(); iter.Advance()) { 205 const std::string& lang = iter.key(); 206 if (!l10n_util::IsLocaleNameTranslated(lang.c_str(), locale)) 207 continue; 208 alpha_language_set->insert(lang); 209 } 210 } 211 212 } // namespace 213 214 // This must be kept in sync with the &cb= value in the kLanguageListFetchURL. 215 const char TranslateLanguageList::kLanguageListCallbackName[] = "sl("; 216 const char TranslateLanguageList::kTargetLanguagesKey[] = "tl"; 217 const char TranslateLanguageList::kAlphaLanguagesKey[] = "al"; 218 219 TranslateLanguageList::TranslateLanguageList() { 220 // We default to our hard coded list of languages in 221 // |kDefaultSupportedLanguages|. This list will be overriden by a server 222 // providing supported langauges list. 223 for (size_t i = 0; i < arraysize(kDefaultSupportedLanguages); ++i) 224 all_supported_languages_.insert(kDefaultSupportedLanguages[i]); 225 226 if (update_is_disabled) 227 return; 228 229 language_list_fetcher_.reset(new TranslateURLFetcher(kFetcherId)); 230 language_list_fetcher_->set_max_retry_on_5xx(kMaxRetryOn5xx); 231 } 232 233 TranslateLanguageList::~TranslateLanguageList() { 234 } 235 236 void TranslateLanguageList::GetSupportedLanguages( 237 std::vector<std::string>* languages) { 238 DCHECK(languages && languages->empty()); 239 std::set<std::string>::const_iterator iter = all_supported_languages_.begin(); 240 for (; iter != all_supported_languages_.end(); ++iter) 241 languages->push_back(*iter); 242 243 // Update language lists if they are not updated after Chrome was launched 244 // for later requests. 245 if (!update_is_disabled && language_list_fetcher_.get()) 246 RequestLanguageList(); 247 } 248 249 std::string TranslateLanguageList::GetLanguageCode( 250 const std::string& chrome_locale) { 251 // Only remove the country code for country specific languages we don't 252 // support specifically yet. 253 if (IsSupportedLanguage(chrome_locale)) 254 return chrome_locale; 255 256 size_t hypen_index = chrome_locale.find('-'); 257 if (hypen_index == std::string::npos) 258 return chrome_locale; 259 return chrome_locale.substr(0, hypen_index); 260 } 261 262 bool TranslateLanguageList::IsSupportedLanguage(const std::string& language) { 263 return all_supported_languages_.count(language) != 0; 264 } 265 266 bool TranslateLanguageList::IsAlphaLanguage(const std::string& language) { 267 return alpha_languages_.count(language) != 0; 268 } 269 270 void TranslateLanguageList::RequestLanguageList() { 271 // If resource requests are not allowed, we'll get a callback when they are. 272 if (resource_request_allowed_notifier_.ResourceRequestsAllowed()) 273 OnResourceRequestsAllowed(); 274 } 275 276 void TranslateLanguageList::OnResourceRequestsAllowed() { 277 if (language_list_fetcher_.get() && 278 (language_list_fetcher_->state() == TranslateURLFetcher::IDLE || 279 language_list_fetcher_->state() == TranslateURLFetcher::FAILED)) { 280 GURL url = GURL(kLanguageListFetchURL); 281 url = TranslateURLUtil::AddHostLocaleToUrl(url); 282 url = TranslateURLUtil::AddApiKeyToUrl(url); 283 url = net::AppendQueryParameter(url, 284 kAlphaLanguageQueryName, 285 kAlphaLanguageQueryValue); 286 287 std::string message = base::StringPrintf( 288 "Language list including alpha languages fetch starts (URL: %s)", 289 url.spec().c_str()); 290 NotifyEvent(__LINE__, message); 291 292 bool result = language_list_fetcher_->Request( 293 url, 294 base::Bind(&TranslateLanguageList::OnLanguageListFetchComplete, 295 base::Unretained(this))); 296 if (!result) 297 NotifyEvent(__LINE__, "Request is omitted due to retry limit"); 298 } 299 } 300 301 // static 302 void TranslateLanguageList::DisableUpdate() { 303 update_is_disabled = true; 304 } 305 306 void TranslateLanguageList::OnLanguageListFetchComplete( 307 int id, 308 bool success, 309 const std::string& data) { 310 if (!success) { 311 // Since it fails just now, omit to schedule resource requests if 312 // ResourceRequestAllowedNotifier think it's ready. Otherwise, a callback 313 // will be invoked later to request resources again. 314 // The TranslateURLFetcher has a limit for retried requests and aborts 315 // re-try not to invoke OnLanguageListFetchComplete anymore if it's asked to 316 // re-try too many times. 317 NotifyEvent(__LINE__, "Failed to fetch languages"); 318 return; 319 } 320 321 NotifyEvent(__LINE__, "Language list is updated"); 322 323 DCHECK_EQ(kFetcherId, id); 324 325 SetSupportedLanguages(data, &all_supported_languages_, &alpha_languages_); 326 language_list_fetcher_.reset(); 327 328 last_updated_ = base::Time::Now(); 329 } 330