4b750206d659b572a3cb51eda5bd225027648b9d
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / webui / chromeos / login / l10n_util.cc
1 // Copyright 2014 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/ui/webui/chromeos/login/l10n_util.h"
6
7 #include <algorithm>
8 #include <iterator>
9 #include <map>
10 #include <set>
11 #include <utility>
12
13 #include "base/basictypes.h"
14 #include "base/bind.h"
15 #include "base/i18n/rtl.h"
16 #include "base/location.h"
17 #include "base/logging.h"
18 #include "base/memory/ref_counted.h"
19 #include "base/sequenced_task_runner.h"
20 #include "base/strings/string16.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "base/task_runner_util.h"
24 #include "base/threading/sequenced_worker_pool.h"
25 #include "base/values.h"
26 #include "chrome/browser/browser_process.h"
27 #include "chrome/browser/chromeos/customization_document.h"
28 #include "chrome/browser/chromeos/input_method/input_method_util.h"
29 #include "chromeos/ime/component_extension_ime_manager.h"
30 #include "chromeos/ime/input_method_descriptor.h"
31 #include "chromeos/ime/input_method_manager.h"
32 #include "content/public/browser/browser_thread.h"
33 #include "grit/generated_resources.h"
34 #include "ui/base/l10n/l10n_util.h"
35
36 namespace chromeos {
37
38 namespace {
39
40 const char kSequenceToken[] = "chromeos_login_l10n_util";
41
42 scoped_ptr<base::DictionaryValue> CreateInputMethodsEntry(
43     const input_method::InputMethodDescriptor& method,
44     const std::string selected) {
45   input_method::InputMethodUtil* util =
46       input_method::InputMethodManager::Get()->GetInputMethodUtil();
47   const std::string& ime_id = method.id();
48   scoped_ptr<base::DictionaryValue> input_method(new base::DictionaryValue);
49   input_method->SetString("value", ime_id);
50   input_method->SetString("title", util->GetInputMethodLongName(method));
51   input_method->SetBoolean("selected", ime_id == selected);
52   return input_method.Pass();
53 }
54
55 // Returns true if element was inserted.
56 bool InsertString(const std::string& str, std::set<std::string>& to) {
57   const std::pair<std::set<std::string>::iterator, bool> result =
58       to.insert(str);
59   return result.second;
60 }
61
62 void AddOptgroupOtherLayouts(base::ListValue* input_methods_list) {
63   scoped_ptr<base::DictionaryValue> optgroup(new base::DictionaryValue);
64   optgroup->SetString(
65       "optionGroupName",
66       l10n_util::GetStringUTF16(IDS_OOBE_OTHER_KEYBOARD_LAYOUTS));
67   input_methods_list->Append(optgroup.release());
68 }
69
70 // TODO(zork): Remove this blacklist when fonts are added to Chrome OS.
71 // see: crbug.com/240586
72 bool IsBlacklisted(const std::string& language_code) {
73   return language_code == "si";  // Sinhala
74 }
75
76 // Gets the list of languages with |descriptors| based on |base_language_codes|.
77 // The |most_relevant_language_codes| will be first in the list. If
78 // |insert_divider| is true, an entry with its "code" attribute set to
79 // kMostRelevantLanguagesDivider is placed between the most relevant languages
80 // and all others.
81 scoped_ptr<base::ListValue> GetLanguageList(
82     const input_method::InputMethodDescriptors& descriptors,
83     const std::vector<std::string>& base_language_codes,
84     const std::vector<std::string>& most_relevant_language_codes,
85     bool insert_divider) {
86   const std::string app_locale = g_browser_process->GetApplicationLocale();
87
88   std::set<std::string> language_codes;
89   // Collect the language codes from the supported input methods.
90   for (size_t i = 0; i < descriptors.size(); ++i) {
91     const input_method::InputMethodDescriptor& descriptor = descriptors[i];
92     const std::vector<std::string>& languages = descriptor.language_codes();
93     for (size_t i = 0; i < languages.size(); ++i)
94       language_codes.insert(languages[i]);
95   }
96
97   // Language sort order.
98   std::map<std::string, int /* index */> language_index;
99   for (size_t i = 0; i < most_relevant_language_codes.size(); ++i)
100     language_index[most_relevant_language_codes[i]] = i;
101
102   // Map of display name -> {language code, native_display_name}.
103   // In theory, we should be able to create a map that is sorted by
104   // display names using ICU comparator, but doing it is hard, thus we'll
105   // use an auxiliary vector to achieve the same result.
106   typedef std::pair<std::string, base::string16> LanguagePair;
107   typedef std::map<base::string16, LanguagePair> LanguageMap;
108   LanguageMap language_map;
109
110   // The auxiliary vector mentioned above (except the most relevant locales).
111   std::vector<base::string16> display_names;
112
113   // Separate vector of the most relevant locales.
114   std::vector<base::string16> most_relevant_locales_display_names(
115       most_relevant_language_codes.size());
116
117   size_t most_relevant_locales_count = 0;
118
119   // Build the list of display names, and build the language map.
120
121   // The list of configured locales might have entries not in
122   // base_language_codes. If there are unsupported language variants,
123   // but they resolve to backup locale within base_language_codes, also
124   // add them to the list.
125   for (std::map<std::string, int>::const_iterator it = language_index.begin();
126        it != language_index.end(); ++it) {
127     const std::string& language_id = it->first;
128
129     const size_t dash_pos = language_id.find_first_of('-');
130
131     // Ignore non-specific codes.
132     if (dash_pos == std::string::npos || dash_pos == 0)
133       continue;
134
135     if (std::find(base_language_codes.begin(),
136                   base_language_codes.end(),
137                   language_id) != base_language_codes.end()) {
138       // Language is supported. No need to replace
139       continue;
140     }
141     std::string resolved_locale;
142     if (!l10n_util::CheckAndResolveLocale(language_id, &resolved_locale))
143       continue;
144
145     if (std::find(base_language_codes.begin(),
146                   base_language_codes.end(),
147                   resolved_locale) == base_language_codes.end()) {
148       // Resolved locale is not supported.
149       continue;
150     }
151
152     const base::string16 display_name =
153         l10n_util::GetDisplayNameForLocale(language_id, app_locale, true);
154     const base::string16 native_display_name =
155         l10n_util::GetDisplayNameForLocale(
156             language_id, language_id, true);
157
158     language_map[display_name] =
159         std::make_pair(language_id, native_display_name);
160
161     most_relevant_locales_display_names[it->second] = display_name;
162     ++most_relevant_locales_count;
163   }
164
165   // Translate language codes, generated from input methods.
166   for (std::set<std::string>::const_iterator it = language_codes.begin();
167        it != language_codes.end(); ++it) {
168      // Exclude the language which is not in |base_langauge_codes| even it has
169      // input methods.
170     if (std::find(base_language_codes.begin(),
171                   base_language_codes.end(),
172                   *it) == base_language_codes.end()) {
173       continue;
174     }
175
176     const base::string16 display_name =
177         l10n_util::GetDisplayNameForLocale(*it, app_locale, true);
178     const base::string16 native_display_name =
179         l10n_util::GetDisplayNameForLocale(*it, *it, true);
180
181     language_map[display_name] =
182         std::make_pair(*it, native_display_name);
183
184     const std::map<std::string, int>::const_iterator index_pos =
185         language_index.find(*it);
186     if (index_pos != language_index.end()) {
187       base::string16& stored_display_name =
188           most_relevant_locales_display_names[index_pos->second];
189       if (stored_display_name.empty()) {
190         stored_display_name = display_name;
191         ++most_relevant_locales_count;
192       }
193     } else {
194       display_names.push_back(display_name);
195     }
196   }
197   DCHECK_EQ(display_names.size() + most_relevant_locales_count,
198             language_map.size());
199
200   // Build the list of display names, and build the language map.
201   for (size_t i = 0; i < base_language_codes.size(); ++i) {
202     // Skip this language if it was already added.
203     if (language_codes.find(base_language_codes[i]) != language_codes.end())
204       continue;
205
206     // TODO(zork): Remove this blacklist when fonts are added to Chrome OS.
207     // see: crbug.com/240586
208     if (IsBlacklisted(base_language_codes[i]))
209       continue;
210
211     base::string16 display_name =
212         l10n_util::GetDisplayNameForLocale(
213             base_language_codes[i], app_locale, false);
214     base::string16 native_display_name =
215         l10n_util::GetDisplayNameForLocale(
216             base_language_codes[i], base_language_codes[i], false);
217     language_map[display_name] =
218         std::make_pair(base_language_codes[i], native_display_name);
219
220     const std::map<std::string, int>::const_iterator index_pos =
221         language_index.find(base_language_codes[i]);
222     if (index_pos != language_index.end()) {
223       most_relevant_locales_display_names[index_pos->second] = display_name;
224       ++most_relevant_locales_count;
225     } else {
226       display_names.push_back(display_name);
227     }
228   }
229
230   // Sort display names using locale specific sorter.
231   l10n_util::SortStrings16(app_locale, &display_names);
232   // Concatenate most_relevant_locales_display_names and display_names.
233   // Insert special divider in between.
234   std::vector<base::string16> out_display_names;
235   for (size_t i = 0; i < most_relevant_locales_display_names.size(); ++i) {
236     if (most_relevant_locales_display_names[i].size() == 0)
237       continue;
238     out_display_names.push_back(most_relevant_locales_display_names[i]);
239   }
240
241   base::string16 divider16;
242   if (insert_divider && !out_display_names.empty()) {
243     // Insert a divider if requested, but only if
244     // |most_relevant_locales_display_names| is not empty.
245     divider16 = base::ASCIIToUTF16(kMostRelevantLanguagesDivider);
246     out_display_names.push_back(divider16);
247   }
248
249   std::copy(display_names.begin(),
250             display_names.end(),
251             std::back_inserter(out_display_names));
252
253   // Build the language list from the language map.
254   scoped_ptr<base::ListValue> language_list(new base::ListValue());
255   for (size_t i = 0; i < out_display_names.size(); ++i) {
256     // Sets the directionality of the display language name.
257     base::string16 display_name(out_display_names[i]);
258     if (insert_divider && display_name == divider16) {
259       // Insert divider.
260       base::DictionaryValue* dictionary = new base::DictionaryValue();
261       dictionary->SetString("code", kMostRelevantLanguagesDivider);
262       language_list->Append(dictionary);
263       continue;
264     }
265     const bool markup_removal =
266         base::i18n::UnadjustStringForLocaleDirection(&display_name);
267     DCHECK(markup_removal);
268     const bool has_rtl_chars =
269         base::i18n::StringContainsStrongRTLChars(display_name);
270     const std::string directionality = has_rtl_chars ? "rtl" : "ltr";
271
272     const LanguagePair& pair = language_map[out_display_names[i]];
273     base::DictionaryValue* dictionary = new base::DictionaryValue();
274     dictionary->SetString("code", pair.first);
275     dictionary->SetString("displayName", out_display_names[i]);
276     dictionary->SetString("textDirection", directionality);
277     dictionary->SetString("nativeDisplayName", pair.second);
278     language_list->Append(dictionary);
279   }
280
281   return language_list.Pass();
282 }
283
284 // Invokes |callback| with a list of keyboard layouts that can be used for
285 // |resolved_locale|.
286 void GetKeyboardLayoutsForResolvedLocale(
287     const GetKeyboardLayoutsForLocaleCallback& callback,
288     const std::string& resolved_locale) {
289   input_method::InputMethodUtil* util =
290       input_method::InputMethodManager::Get()->GetInputMethodUtil();
291   std::vector<std::string> layouts = util->GetHardwareInputMethodIds();
292   std::vector<std::string> layouts_from_locale;
293   util->GetInputMethodIdsFromLanguageCode(
294       resolved_locale,
295       input_method::kKeyboardLayoutsOnly,
296       &layouts_from_locale);
297   layouts.insert(layouts.end(), layouts_from_locale.begin(),
298                  layouts_from_locale.end());
299
300   std::string selected;
301   if (!layouts_from_locale.empty()) {
302     selected =
303         util->GetInputMethodDescriptorFromId(layouts_from_locale[0])->id();
304   }
305
306   scoped_ptr<base::ListValue> input_methods_list(new base::ListValue);
307   std::set<std::string> input_methods_added;
308   for (std::vector<std::string>::const_iterator it = layouts.begin();
309        it != layouts.end(); ++it) {
310     const input_method::InputMethodDescriptor* ime =
311         util->GetInputMethodDescriptorFromId(*it);
312     if (!InsertString(ime->id(), input_methods_added))
313       continue;
314     input_methods_list->Append(
315         CreateInputMethodsEntry(*ime, selected).release());
316   }
317
318   callback.Run(input_methods_list.Pass());
319 }
320
321 }  // namespace
322
323 const char kMostRelevantLanguagesDivider[] = "MOST_RELEVANT_LANGUAGES_DIVIDER";
324
325 scoped_ptr<base::ListValue> GetUILanguageList(
326     const std::vector<std::string>* most_relevant_language_codes,
327     const std::string& selected) {
328   ComponentExtensionIMEManager* manager =
329       input_method::InputMethodManager::Get()->
330           GetComponentExtensionIMEManager();
331   input_method::InputMethodDescriptors descriptors =
332       manager->GetXkbIMEAsInputMethodDescriptor();
333   scoped_ptr<base::ListValue> languages_list(GetLanguageList(
334       descriptors,
335       l10n_util::GetAvailableLocales(),
336       most_relevant_language_codes
337           ? *most_relevant_language_codes
338           : StartupCustomizationDocument::GetInstance()->configured_locales(),
339       true));
340
341   for (size_t i = 0; i < languages_list->GetSize(); ++i) {
342     base::DictionaryValue* language_info = NULL;
343     if (!languages_list->GetDictionary(i, &language_info))
344       NOTREACHED();
345
346     std::string value;
347     language_info->GetString("code", &value);
348     std::string display_name;
349     language_info->GetString("displayName", &display_name);
350     std::string native_name;
351     language_info->GetString("nativeDisplayName", &native_name);
352
353     // If it's an option group divider, add field name.
354     if (value == kMostRelevantLanguagesDivider) {
355       language_info->SetString(
356           "optionGroupName",
357           l10n_util::GetStringUTF16(IDS_OOBE_OTHER_LANGUAGES));
358     }
359     if (display_name != native_name) {
360       display_name = base::StringPrintf("%s - %s",
361                                         display_name.c_str(),
362                                         native_name.c_str());
363     }
364
365     language_info->SetString("value", value);
366     language_info->SetString("title", display_name);
367     if (value == selected)
368       language_info->SetBoolean("selected", true);
369   }
370   return languages_list.Pass();
371 }
372
373 std::string FindMostRelevantLocale(
374     const std::vector<std::string>& most_relevant_language_codes,
375     const base::ListValue& available_locales,
376     const std::string& fallback_locale) {
377   for (std::vector<std::string>::const_iterator most_relevant_it =
378           most_relevant_language_codes.begin();
379        most_relevant_it != most_relevant_language_codes.end();
380        ++most_relevant_it) {
381     for (base::ListValue::const_iterator available_it =
382              available_locales.begin();
383          available_it != available_locales.end(); ++available_it) {
384       base::DictionaryValue* dict;
385       std::string available_locale;
386       if (!(*available_it)->GetAsDictionary(&dict) ||
387           !dict->GetString("value", &available_locale)) {
388         NOTREACHED();
389         continue;
390       }
391       if (available_locale == *most_relevant_it)
392         return *most_relevant_it;
393     }
394   }
395
396   return fallback_locale;
397 }
398
399 scoped_ptr<base::ListValue> GetAcceptLanguageList() {
400   // Collect the language codes from the supported accept-languages.
401   const std::string app_locale = g_browser_process->GetApplicationLocale();
402   std::vector<std::string> accept_language_codes;
403   l10n_util::GetAcceptLanguagesForLocale(app_locale, &accept_language_codes);
404   return GetLanguageList(
405       *input_method::InputMethodManager::Get()->GetSupportedInputMethods(),
406       accept_language_codes,
407       StartupCustomizationDocument::GetInstance()->configured_locales(),
408       false);
409 }
410
411 scoped_ptr<base::ListValue> GetLoginKeyboardLayouts(
412     const std::string& locale,
413     const std::string& selected) {
414   scoped_ptr<base::ListValue> input_methods_list(new base::ListValue);
415   input_method::InputMethodManager* manager =
416       input_method::InputMethodManager::Get();
417   input_method::InputMethodUtil* util = manager->GetInputMethodUtil();
418
419   const std::vector<std::string>& hardware_login_input_methods =
420       util->GetHardwareLoginInputMethodIds();
421   manager->EnableLoginLayouts(locale, hardware_login_input_methods);
422
423   scoped_ptr<input_method::InputMethodDescriptors> input_methods(
424       manager->GetActiveInputMethods());
425   std::set<std::string> input_methods_added;
426
427   for (std::vector<std::string>::const_iterator i =
428            hardware_login_input_methods.begin();
429        i != hardware_login_input_methods.end();
430        ++i) {
431     const input_method::InputMethodDescriptor* ime =
432         util->GetInputMethodDescriptorFromId(*i);
433     // Do not crash in case of misconfiguration.
434     if (ime) {
435       input_methods_added.insert(*i);
436       input_methods_list->Append(
437           CreateInputMethodsEntry(*ime, selected).release());
438     } else {
439       NOTREACHED();
440     }
441   }
442
443   bool optgroup_added = false;
444   for (size_t i = 0; i < input_methods->size(); ++i) {
445     // Makes sure the id is in legacy xkb id format.
446     const std::string& ime_id = (*input_methods)[i].id();
447     if (!InsertString(ime_id, input_methods_added))
448       continue;
449     if (!optgroup_added) {
450       optgroup_added = true;
451       AddOptgroupOtherLayouts(input_methods_list.get());
452     }
453     input_methods_list->Append(CreateInputMethodsEntry((*input_methods)[i],
454                                                        selected).release());
455   }
456
457   // "xkb:us::eng" should always be in the list of available layouts.
458   const std::string us_keyboard_id =
459       util->GetFallbackInputMethodDescriptor().id();
460   if (input_methods_added.find(us_keyboard_id) == input_methods_added.end()) {
461     const input_method::InputMethodDescriptor* us_eng_descriptor =
462         util->GetInputMethodDescriptorFromId(us_keyboard_id);
463     DCHECK(us_eng_descriptor);
464     if (!optgroup_added) {
465       optgroup_added = true;
466       AddOptgroupOtherLayouts(input_methods_list.get());
467     }
468     input_methods_list->Append(CreateInputMethodsEntry(*us_eng_descriptor,
469                                                        selected).release());
470   }
471   return input_methods_list.Pass();
472 }
473
474 void GetKeyboardLayoutsForLocale(
475     const GetKeyboardLayoutsForLocaleCallback& callback,
476     const std::string& locale) {
477   base::SequencedWorkerPool* worker_pool =
478       content::BrowserThread::GetBlockingPool();
479   scoped_refptr<base::SequencedTaskRunner> background_task_runner =
480       worker_pool->GetSequencedTaskRunnerWithShutdownBehavior(
481           worker_pool->GetNamedSequenceToken(kSequenceToken),
482           base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
483
484   // Resolve |locale| on a background thread, then continue on the current
485   // thread.
486   std::string (*get_application_locale)(const std::string&, bool) =
487       &l10n_util::GetApplicationLocale;
488   base::PostTaskAndReplyWithResult(
489       background_task_runner,
490       FROM_HERE,
491       base::Bind(get_application_locale, locale, false /* set_icu_locale */),
492       base::Bind(&GetKeyboardLayoutsForResolvedLocale, callback));
493 }
494
495 scoped_ptr<base::DictionaryValue> GetCurrentKeyboardLayout() {
496   const input_method::InputMethodDescriptor current_input_method =
497       input_method::InputMethodManager::Get()->GetCurrentInputMethod();
498   return CreateInputMethodsEntry(current_input_method,
499                                  current_input_method.id());
500 }
501
502 }  // namespace chromeos