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 "chrome/browser/translate/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 "chrome/browser/browser_process.h"
17 #include "chrome/browser/translate/chrome_translate_delegate.h"
18 #include "chrome/browser/translate/translate_manager.h"
19 #include "chrome/browser/translate/translate_url_util.h"
20 #include "components/translate/core/browser/translate_browser_metrics.h"
21 #include "components/translate/core/browser/translate_event_details.h"
22 #include "components/translate/core/browser/translate_url_fetcher.h"
23 #include "net/base/url_util.h"
24 #include "ui/base/l10n/l10n_util.h"
29 // The default list of languages the Google translation server supports.
30 // We use this list until we receive the list that the server exposes.
31 // For information, here is the list of languages that Chrome can be run in
32 // but that the translation server does not support:
41 const char* const kDefaultSupportedLanguages
[] = {
48 "zh-CN", // Chinese (Simplified)
49 "zh-TW", // Chinese (Traditional)
63 "ht", // Haitian Creole
98 // Constant URL string to fetch server supporting language list.
99 const char kLanguageListFetchURL
[] =
100 "https://translate.googleapis.com/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;
113 // Show a message in chrome:://translate-internals Event Logs.
114 void NotifyEvent(int line
, const std::string
& message
) {
115 TranslateManager
* manager
= TranslateManager::GetInstance();
118 TranslateEventDetails
details(__FILE__
, line
, message
);
119 manager
->NotifyTranslateEvent(details
);
122 // Parses |language_list| containing the list of languages that the translate
123 // server can translate to and from, and fills |set| with them.
124 void SetSupportedLanguages(const std::string
& language_list
,
125 std::set
<std::string
>* target_language_set
,
126 std::set
<std::string
>* alpha_language_set
) {
127 DCHECK(target_language_set
);
128 DCHECK(alpha_language_set
);
132 // "sl": {"XX": "LanguageName", ...},
133 // "tl": {"XX": "LanguageName", ...},
134 // "al": {"XX": 1, ...}
136 // Where "sl(" is set in kLanguageListCallbackName, "tl" is
137 // kTargetLanguagesKey and "al" kAlphaLanguagesKey.
138 if (!StartsWithASCII(language_list
,
139 TranslateLanguageList::kLanguageListCallbackName
,
141 !EndsWith(language_list
, ")", false)) {
142 // We don't have a NOTREACHED here since this can happen in ui_tests, even
143 // though the the BrowserMain function won't call us with parameters.ui_task
144 // is NULL some tests don't set it, so we must bail here.
147 static const size_t kLanguageListCallbackNameLength
=
148 strlen(TranslateLanguageList::kLanguageListCallbackName
);
149 std::string languages_json
= language_list
.substr(
150 kLanguageListCallbackNameLength
,
151 language_list
.size() - kLanguageListCallbackNameLength
- 1);
152 scoped_ptr
<base::Value
> json_value(
153 base::JSONReader::Read(languages_json
, base::JSON_ALLOW_TRAILING_COMMAS
));
154 if (json_value
== NULL
|| !json_value
->IsType(base::Value::TYPE_DICTIONARY
)) {
158 // The first level dictionary contains three sub-dict, first for source
159 // languages and second for target languages, we want to use the target
160 // languages. The last is for alpha languages.
161 base::DictionaryValue
* language_dict
=
162 static_cast<base::DictionaryValue
*>(json_value
.get());
163 base::DictionaryValue
* target_languages
= NULL
;
164 if (!language_dict
->GetDictionary(TranslateLanguageList::kTargetLanguagesKey
,
165 &target_languages
) ||
166 target_languages
== NULL
) {
171 const std::string
& locale
= g_browser_process
->GetApplicationLocale();
173 // Now we can clear language list.
174 target_language_set
->clear();
176 // ... and replace it with the values we just fetched from the server.
177 for (base::DictionaryValue::Iterator
iter(*target_languages
);
180 const std::string
& lang
= iter
.key();
181 if (!l10n_util::IsLocaleNameTranslated(lang
.c_str(), locale
)) {
182 TranslateBrowserMetrics::ReportUndisplayableLanguage(lang
);
185 target_language_set
->insert(lang
);
189 message
+= ", " + lang
;
191 NotifyEvent(__LINE__
, message
);
193 // Get the alpha languages. The "al" parameter could be abandoned.
194 base::DictionaryValue
* alpha_languages
= NULL
;
195 if (!language_dict
->GetDictionary(TranslateLanguageList::kAlphaLanguagesKey
,
197 alpha_languages
== NULL
) {
201 // We assume that the alpha languages are included in the above target
202 // languages, and don't use UMA or NotifyEvent.
203 alpha_language_set
->clear();
204 for (base::DictionaryValue::Iterator
iter(*alpha_languages
);
205 !iter
.IsAtEnd(); iter
.Advance()) {
206 const std::string
& lang
= iter
.key();
207 if (!l10n_util::IsLocaleNameTranslated(lang
.c_str(), locale
))
209 alpha_language_set
->insert(lang
);
215 // This must be kept in sync with the &cb= value in the kLanguageListFetchURL.
216 const char TranslateLanguageList::kLanguageListCallbackName
[] = "sl(";
217 const char TranslateLanguageList::kTargetLanguagesKey
[] = "tl";
218 const char TranslateLanguageList::kAlphaLanguagesKey
[] = "al";
220 TranslateLanguageList::TranslateLanguageList() {
221 // We default to our hard coded list of languages in
222 // |kDefaultSupportedLanguages|. This list will be overriden by a server
223 // providing supported langauges list.
224 for (size_t i
= 0; i
< arraysize(kDefaultSupportedLanguages
); ++i
)
225 all_supported_languages_
.insert(kDefaultSupportedLanguages
[i
]);
227 if (update_is_disabled
)
230 language_list_fetcher_
.reset(new TranslateURLFetcher(
231 kFetcherId
, ChromeTranslateDelegate::GetInstance()));
232 language_list_fetcher_
->set_max_retry_on_5xx(kMaxRetryOn5xx
);
235 TranslateLanguageList::~TranslateLanguageList() {
238 void TranslateLanguageList::GetSupportedLanguages(
239 std::vector
<std::string
>* languages
) {
240 DCHECK(languages
&& languages
->empty());
241 std::set
<std::string
>::const_iterator iter
= all_supported_languages_
.begin();
242 for (; iter
!= all_supported_languages_
.end(); ++iter
)
243 languages
->push_back(*iter
);
245 // Update language lists if they are not updated after Chrome was launched
246 // for later requests.
247 if (!update_is_disabled
&& language_list_fetcher_
.get())
248 RequestLanguageList();
251 std::string
TranslateLanguageList::GetLanguageCode(
252 const std::string
& chrome_locale
) {
253 // Only remove the country code for country specific languages we don't
254 // support specifically yet.
255 if (IsSupportedLanguage(chrome_locale
))
256 return chrome_locale
;
258 size_t hypen_index
= chrome_locale
.find('-');
259 if (hypen_index
== std::string::npos
)
260 return chrome_locale
;
261 return chrome_locale
.substr(0, hypen_index
);
264 bool TranslateLanguageList::IsSupportedLanguage(const std::string
& language
) {
265 return all_supported_languages_
.count(language
) != 0;
268 bool TranslateLanguageList::IsAlphaLanguage(const std::string
& language
) {
269 return alpha_languages_
.count(language
) != 0;
272 void TranslateLanguageList::RequestLanguageList() {
273 // If resource requests are not allowed, we'll get a callback when they are.
274 if (resource_request_allowed_notifier_
.ResourceRequestsAllowed())
275 OnResourceRequestsAllowed();
278 void TranslateLanguageList::OnResourceRequestsAllowed() {
279 if (language_list_fetcher_
.get() &&
280 (language_list_fetcher_
->state() == TranslateURLFetcher::IDLE
||
281 language_list_fetcher_
->state() == TranslateURLFetcher::FAILED
)) {
282 GURL url
= GURL(kLanguageListFetchURL
);
283 url
= TranslateURLUtil::AddHostLocaleToUrl(url
);
284 url
= TranslateURLUtil::AddApiKeyToUrl(url
);
285 url
= net::AppendQueryParameter(url
,
286 kAlphaLanguageQueryName
,
287 kAlphaLanguageQueryValue
);
289 std::string message
= base::StringPrintf(
290 "Language list including alpha languages fetch starts (URL: %s)",
292 NotifyEvent(__LINE__
, message
);
294 bool result
= language_list_fetcher_
->Request(
296 base::Bind(&TranslateLanguageList::OnLanguageListFetchComplete
,
297 base::Unretained(this)));
299 NotifyEvent(__LINE__
, "Request is omitted due to retry limit");
304 void TranslateLanguageList::DisableUpdate() {
305 update_is_disabled
= true;
308 void TranslateLanguageList::OnLanguageListFetchComplete(
311 const std::string
& data
) {
313 // Since it fails just now, omit to schedule resource requests if
314 // ResourceRequestAllowedNotifier think it's ready. Otherwise, a callback
315 // will be invoked later to request resources again.
316 // The TranslateURLFetcher has a limit for retried requests and aborts
317 // re-try not to invoke OnLanguageListFetchComplete anymore if it's asked to
318 // re-try too many times.
319 NotifyEvent(__LINE__
, "Failed to fetch languages");
323 NotifyEvent(__LINE__
, "Language list is updated");
325 DCHECK_EQ(kFetcherId
, id
);
327 SetSupportedLanguages(data
, &all_supported_languages_
, &alpha_languages_
);
328 language_list_fetcher_
.reset();
330 last_updated_
= base::Time::Now();