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