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.
5 #include "components/translate/core/browser/translate_language_list.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"
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:
42 const char* const kDefaultSupportedLanguages[] = {
49 "zh-CN", // Chinese (Simplified)
50 "zh-TW", // Chinese (Traditional)
64 "ht", // Haitian Creole
99 // Constant URL string to fetch server supporting language list.
100 const char kLanguageListFetchPath[] = "translate_a/l?client=chrome&cb=sl";
102 // Used in kTranslateScriptURL to request supporting languages list including
103 // "alpha languages".
104 const char kAlphaLanguageQueryName[] = "alpha";
105 const char kAlphaLanguageQueryValue[] = "1";
107 // Represent if the language list updater is disabled.
108 bool update_is_disabled = false;
110 // Retry parameter for fetching.
111 const int kMaxRetryOn5xx = 5;
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";
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]);
128 if (update_is_disabled)
131 language_list_fetcher_.reset(new TranslateURLFetcher(kFetcherId));
132 language_list_fetcher_->set_max_retry_on_5xx(kMaxRetryOn5xx);
135 TranslateLanguageList::~TranslateLanguageList() {}
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);
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();
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))
157 size_t hypen_index = language.find('-');
158 if (hypen_index == std::string::npos)
160 return language.substr(0, hypen_index);
163 bool TranslateLanguageList::IsSupportedLanguage(const std::string& language) {
164 return all_supported_languages_.count(language) != 0;
167 bool TranslateLanguageList::IsAlphaLanguage(const std::string& language) {
168 return alpha_languages_.count(language) != 0;
171 GURL TranslateLanguageList::TranslateLanguageUrl() {
172 std::string url = translate::GetTranslateSecurityOrigin().spec() +
173 kLanguageListFetchPath;
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;
184 request_pending_ = false;
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);
195 std::string message = base::StringPrintf(
196 "Language list including alpha languages fetch starts (URL: %s)",
198 NotifyEvent(__LINE__, message);
200 bool result = language_list_fetcher_->Request(
202 base::Bind(&TranslateLanguageList::OnLanguageListFetchComplete,
203 base::Unretained(this)));
205 NotifyEvent(__LINE__, "Request is omitted due to retry limit");
209 void TranslateLanguageList::SetResourceRequestsAllowed(bool allowed) {
210 resource_requests_allowed_ = allowed;
211 if (resource_requests_allowed_ && request_pending_) {
212 RequestLanguageList();
213 DCHECK(!request_pending_);
217 scoped_ptr<TranslateLanguageList::EventCallbackList::Subscription>
218 TranslateLanguageList::RegisterEventCallback(const EventCallback& callback) {
219 return callback_list_.Add(callback);
223 void TranslateLanguageList::DisableUpdate() {
224 update_is_disabled = true;
227 void TranslateLanguageList::OnLanguageListFetchComplete(
230 const std::string& data) {
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");
242 NotifyEvent(__LINE__, "Language list is updated");
244 DCHECK_EQ(kFetcherId, id);
246 SetSupportedLanguages(data);
247 language_list_fetcher_.reset();
249 last_updated_ = base::Time::Now();
252 void TranslateLanguageList::NotifyEvent(int line, const std::string& message) {
253 TranslateEventDetails details(__FILE__, line, message);
254 callback_list_.Notify(details);
257 void TranslateLanguageList::SetSupportedLanguages(
258 const std::string& language_list) {
261 // "sl": {"XX": "LanguageName", ...},
262 // "tl": {"XX": "LanguageName", ...},
263 // "al": {"XX": 1, ...}
265 // Where "sl(" is set in kLanguageListCallbackName, "tl" is
266 // kTargetLanguagesKey and "al" kAlphaLanguagesKey.
267 if (!StartsWithASCII(language_list,
268 TranslateLanguageList::kLanguageListCallbackName,
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.
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);
282 scoped_ptr<base::Value> json_value(
283 base::JSONReader::Read(languages_json, base::JSON_ALLOW_TRAILING_COMMAS));
285 if (json_value == NULL || !json_value->IsType(base::Value::TYPE_DICTIONARY)) {
289 // The first level dictionary contains three sub-dict, first for source
290 // languages and second for target languages, we want to use the target
291 // languages. The last is for alpha languages.
292 base::DictionaryValue* language_dict =
293 static_cast<base::DictionaryValue*>(json_value.get());
294 base::DictionaryValue* target_languages = NULL;
295 if (!language_dict->GetDictionary(TranslateLanguageList::kTargetLanguagesKey,
296 &target_languages) ||
297 target_languages == NULL) {
302 const std::string& locale =
303 TranslateDownloadManager::GetInstance()->application_locale();
305 // Now we can clear language list.
306 all_supported_languages_.clear();
308 // ... and replace it with the values we just fetched from the server.
309 for (base::DictionaryValue::Iterator iter(*target_languages);
312 const std::string& lang = iter.key();
313 if (!l10n_util::IsLocaleNameTranslated(lang.c_str(), locale)) {
314 TranslateBrowserMetrics::ReportUndisplayableLanguage(lang);
317 all_supported_languages_.insert(lang);
321 message += ", " + lang;
323 NotifyEvent(__LINE__, message);
325 // Get the alpha languages. The "al" parameter could be abandoned.
326 base::DictionaryValue* alpha_languages = NULL;
327 if (!language_dict->GetDictionary(TranslateLanguageList::kAlphaLanguagesKey,
329 alpha_languages == NULL) {
333 // We assume that the alpha languages are included in the above target
334 // languages, and don't use UMA or NotifyEvent.
335 alpha_languages_.clear();
336 for (base::DictionaryValue::Iterator iter(*alpha_languages);
337 !iter.IsAtEnd(); iter.Advance()) {
338 const std::string& lang = iter.key();
339 if (!l10n_util::IsLocaleNameTranslated(lang.c_str(), locale))
341 alpha_languages_.insert(lang);
345 } // namespace translate