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.
5 #include "chrome/browser/ui/webui/chromeos/login/l10n_util.h"
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"
40 const char kSequenceToken[] = "chromeos_login_l10n_util";
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();
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 =
62 void AddOptgroupOtherLayouts(base::ListValue* input_methods_list) {
63 scoped_ptr<base::DictionaryValue> optgroup(new base::DictionaryValue);
66 l10n_util::GetStringUTF16(IDS_OOBE_OTHER_KEYBOARD_LAYOUTS));
67 input_methods_list->Append(optgroup.release());
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
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
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();
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]);
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;
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;
110 // The auxiliary vector mentioned above (except the most relevant locales).
111 std::vector<base::string16> display_names;
113 // Separate vector of the most relevant locales.
114 std::vector<base::string16> most_relevant_locales_display_names(
115 most_relevant_language_codes.size());
117 size_t most_relevant_locales_count = 0;
119 // Build the list of display names, and build the language map.
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;
129 const size_t dash_pos = language_id.find_first_of('-');
131 // Ignore non-specific codes.
132 if (dash_pos == std::string::npos || dash_pos == 0)
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
141 std::string resolved_locale;
142 if (!l10n_util::CheckAndResolveLocale(language_id, &resolved_locale))
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.
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);
158 language_map[display_name] =
159 std::make_pair(language_id, native_display_name);
161 most_relevant_locales_display_names[it->second] = display_name;
162 ++most_relevant_locales_count;
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
170 if (std::find(base_language_codes.begin(),
171 base_language_codes.end(),
172 *it) == base_language_codes.end()) {
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);
181 language_map[display_name] =
182 std::make_pair(*it, native_display_name);
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;
194 display_names.push_back(display_name);
197 DCHECK_EQ(display_names.size() + most_relevant_locales_count,
198 language_map.size());
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())
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]))
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);
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;
226 display_names.push_back(display_name);
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)
238 out_display_names.push_back(most_relevant_locales_display_names[i]);
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);
249 std::copy(display_names.begin(),
251 std::back_inserter(out_display_names));
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) {
260 base::DictionaryValue* dictionary = new base::DictionaryValue();
261 dictionary->SetString("code", kMostRelevantLanguagesDivider);
262 language_list->Append(dictionary);
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";
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);
281 return language_list.Pass();
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(
295 input_method::kKeyboardLayoutsOnly,
296 &layouts_from_locale);
297 layouts.insert(layouts.end(), layouts_from_locale.begin(),
298 layouts_from_locale.end());
300 std::string selected;
301 if (!layouts_from_locale.empty()) {
303 util->GetInputMethodDescriptorFromId(layouts_from_locale[0])->id();
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))
314 input_methods_list->Append(
315 CreateInputMethodsEntry(*ime, selected).release());
318 callback.Run(input_methods_list.Pass());
323 const char kMostRelevantLanguagesDivider[] = "MOST_RELEVANT_LANGUAGES_DIVIDER";
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(
335 l10n_util::GetAvailableLocales(),
336 most_relevant_language_codes
337 ? *most_relevant_language_codes
338 : StartupCustomizationDocument::GetInstance()->configured_locales(),
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))
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);
353 // If it's an option group divider, add field name.
354 if (value == kMostRelevantLanguagesDivider) {
355 language_info->SetString(
357 l10n_util::GetStringUTF16(IDS_OOBE_OTHER_LANGUAGES));
359 if (display_name != native_name) {
360 display_name = base::StringPrintf("%s - %s",
361 display_name.c_str(),
362 native_name.c_str());
365 language_info->SetString("value", value);
366 language_info->SetString("title", display_name);
367 if (value == selected)
368 language_info->SetBoolean("selected", true);
370 return languages_list.Pass();
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)) {
391 if (available_locale == *most_relevant_it)
392 return *most_relevant_it;
396 return fallback_locale;
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(),
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();
419 const std::vector<std::string>& hardware_login_input_methods =
420 util->GetHardwareLoginInputMethodIds();
421 manager->EnableLoginLayouts(locale, hardware_login_input_methods);
423 scoped_ptr<input_method::InputMethodDescriptors> input_methods(
424 manager->GetActiveInputMethods());
425 std::set<std::string> input_methods_added;
427 for (std::vector<std::string>::const_iterator i =
428 hardware_login_input_methods.begin();
429 i != hardware_login_input_methods.end();
431 const input_method::InputMethodDescriptor* ime =
432 util->GetInputMethodDescriptorFromId(*i);
433 // Do not crash in case of misconfiguration.
435 input_methods_added.insert(*i);
436 input_methods_list->Append(
437 CreateInputMethodsEntry(*ime, selected).release());
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))
449 if (!optgroup_added) {
450 optgroup_added = true;
451 AddOptgroupOtherLayouts(input_methods_list.get());
453 input_methods_list->Append(CreateInputMethodsEntry((*input_methods)[i],
454 selected).release());
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());
468 input_methods_list->Append(CreateInputMethodsEntry(*us_eng_descriptor,
469 selected).release());
471 return input_methods_list.Pass();
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);
484 // Resolve |locale| on a background thread, then continue on the current
486 std::string (*get_application_locale)(const std::string&, bool) =
487 &l10n_util::GetApplicationLocale;
488 base::PostTaskAndReplyWithResult(
489 background_task_runner,
491 base::Bind(get_application_locale, locale, false /* set_icu_locale */),
492 base::Bind(&GetKeyboardLayoutsForResolvedLocale, callback));
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());
502 } // namespace chromeos