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/customization_document.h"
28 #include "chrome/browser/chromeos/input_method/input_method_util.h"
29 #include "chrome/browser/chromeos/profiles/profile_helper.h"
30 #include "chrome/browser/profiles/profile_manager.h"
31 #include "chrome/grit/generated_resources.h"
32 #include "content/public/browser/browser_thread.h"
33 #include "ui/base/ime/chromeos/component_extension_ime_manager.h"
34 #include "ui/base/ime/chromeos/input_method_descriptor.h"
35 #include "ui/base/ime/chromeos/input_method_manager.h"
36 #include "ui/base/l10n/l10n_util.h"
40 const char kMostRelevantLanguagesDivider
[] = "MOST_RELEVANT_LANGUAGES_DIVIDER";
44 const char kSequenceToken
[] = "chromeos_login_l10n_util";
46 scoped_ptr
<base::DictionaryValue
> CreateInputMethodsEntry(
47 const input_method::InputMethodDescriptor
& method
,
48 const std::string selected
) {
49 input_method::InputMethodUtil
* util
=
50 input_method::InputMethodManager::Get()->GetInputMethodUtil();
51 const std::string
& ime_id
= method
.id();
52 scoped_ptr
<base::DictionaryValue
> input_method(new base::DictionaryValue
);
53 input_method
->SetString("value", ime_id
);
54 input_method
->SetString(
55 "title", util
->GetInputMethodLongNameStripped(method
));
56 input_method
->SetBoolean("selected", ime_id
== selected
);
57 return input_method
.Pass();
60 // Returns true if element was inserted.
61 bool InsertString(const std::string
& str
, std::set
<std::string
>* to
) {
62 const std::pair
<std::set
<std::string
>::iterator
, bool> result
=
67 void AddOptgroupOtherLayouts(base::ListValue
* input_methods_list
) {
68 scoped_ptr
<base::DictionaryValue
> optgroup(new base::DictionaryValue
);
71 l10n_util::GetStringUTF16(IDS_OOBE_OTHER_KEYBOARD_LAYOUTS
));
72 input_methods_list
->Append(optgroup
.release());
75 base::DictionaryValue
* CreateLanguageEntry(
76 const std::string
& language_code
,
77 const base::string16
& language_display_name
,
78 const base::string16
& language_native_display_name
) {
79 base::string16 display_name
= language_display_name
;
80 const bool markup_removal
=
81 base::i18n::UnadjustStringForLocaleDirection(&display_name
);
82 DCHECK(markup_removal
);
84 const bool has_rtl_chars
=
85 base::i18n::StringContainsStrongRTLChars(display_name
);
86 const std::string directionality
= has_rtl_chars
? "rtl" : "ltr";
88 scoped_ptr
<base::DictionaryValue
> dictionary(new base::DictionaryValue());
89 dictionary
->SetString("code", language_code
);
90 dictionary
->SetString("displayName", language_display_name
);
91 dictionary
->SetString("textDirection", directionality
);
92 dictionary
->SetString("nativeDisplayName", language_native_display_name
);
93 return dictionary
.release();
96 // Gets the list of languages with |descriptors| based on |base_language_codes|.
97 // The |most_relevant_language_codes| will be first in the list. If
98 // |insert_divider| is true, an entry with its "code" attribute set to
99 // kMostRelevantLanguagesDivider is placed between the most relevant languages
101 scoped_ptr
<base::ListValue
> GetLanguageList(
102 const input_method::InputMethodDescriptors
& descriptors
,
103 const std::vector
<std::string
>& base_language_codes
,
104 const std::vector
<std::string
>& most_relevant_language_codes
,
105 bool insert_divider
) {
106 const std::string app_locale
= g_browser_process
->GetApplicationLocale();
108 std::set
<std::string
> language_codes
;
109 // Collect the language codes from the supported input methods.
110 for (size_t i
= 0; i
< descriptors
.size(); ++i
) {
111 const input_method::InputMethodDescriptor
& descriptor
= descriptors
[i
];
112 const std::vector
<std::string
>& languages
= descriptor
.language_codes();
113 for (size_t i
= 0; i
< languages
.size(); ++i
)
114 language_codes
.insert(languages
[i
]);
117 // Language sort order.
118 std::map
<std::string
, int /* index */> language_index
;
119 for (size_t i
= 0; i
< most_relevant_language_codes
.size(); ++i
)
120 language_index
[most_relevant_language_codes
[i
]] = i
;
122 // Map of display name -> {language code, native_display_name}.
123 // In theory, we should be able to create a map that is sorted by
124 // display names using ICU comparator, but doing it is hard, thus we'll
125 // use an auxiliary vector to achieve the same result.
126 typedef std::pair
<std::string
, base::string16
> LanguagePair
;
127 typedef std::map
<base::string16
, LanguagePair
> LanguageMap
;
128 LanguageMap language_map
;
130 // The auxiliary vector mentioned above (except the most relevant locales).
131 std::vector
<base::string16
> display_names
;
133 // Separate vector of the most relevant locales.
134 std::vector
<base::string16
> most_relevant_locales_display_names(
135 most_relevant_language_codes
.size());
137 size_t most_relevant_locales_count
= 0;
139 // Build the list of display names, and build the language map.
141 // The list of configured locales might have entries not in
142 // base_language_codes. If there are unsupported language variants,
143 // but they resolve to backup locale within base_language_codes, also
144 // add them to the list.
145 for (std::map
<std::string
, int>::const_iterator it
= language_index
.begin();
146 it
!= language_index
.end(); ++it
) {
147 const std::string
& language_id
= it
->first
;
149 const std::string lang
= l10n_util::GetLanguage(language_id
);
151 // Ignore non-specific codes.
152 if (lang
.empty() || lang
== language_id
)
155 if (std::find(base_language_codes
.begin(),
156 base_language_codes
.end(),
157 language_id
) != base_language_codes
.end()) {
158 // Language is supported. No need to replace
161 std::string resolved_locale
;
162 if (!l10n_util::CheckAndResolveLocale(language_id
, &resolved_locale
))
165 if (std::find(base_language_codes
.begin(),
166 base_language_codes
.end(),
167 resolved_locale
) == base_language_codes
.end()) {
168 // Resolved locale is not supported.
172 const base::string16 display_name
=
173 l10n_util::GetDisplayNameForLocale(language_id
, app_locale
, true);
174 const base::string16 native_display_name
=
175 l10n_util::GetDisplayNameForLocale(
176 language_id
, language_id
, true);
178 language_map
[display_name
] =
179 std::make_pair(language_id
, native_display_name
);
181 most_relevant_locales_display_names
[it
->second
] = display_name
;
182 ++most_relevant_locales_count
;
185 // Translate language codes, generated from input methods.
186 for (std::set
<std::string
>::const_iterator it
= language_codes
.begin();
187 it
!= language_codes
.end(); ++it
) {
188 // Exclude the language which is not in |base_langauge_codes| even it has
190 if (std::find(base_language_codes
.begin(),
191 base_language_codes
.end(),
192 *it
) == base_language_codes
.end()) {
196 const base::string16 display_name
=
197 l10n_util::GetDisplayNameForLocale(*it
, app_locale
, true);
198 const base::string16 native_display_name
=
199 l10n_util::GetDisplayNameForLocale(*it
, *it
, true);
201 language_map
[display_name
] =
202 std::make_pair(*it
, native_display_name
);
204 const std::map
<std::string
, int>::const_iterator index_pos
=
205 language_index
.find(*it
);
206 if (index_pos
!= language_index
.end()) {
207 base::string16
& stored_display_name
=
208 most_relevant_locales_display_names
[index_pos
->second
];
209 if (stored_display_name
.empty()) {
210 stored_display_name
= display_name
;
211 ++most_relevant_locales_count
;
214 display_names
.push_back(display_name
);
217 DCHECK_EQ(display_names
.size() + most_relevant_locales_count
,
218 language_map
.size());
220 // Build the list of display names, and build the language map.
221 for (size_t i
= 0; i
< base_language_codes
.size(); ++i
) {
222 // Skip this language if it was already added.
223 if (language_codes
.find(base_language_codes
[i
]) != language_codes
.end())
226 base::string16 display_name
=
227 l10n_util::GetDisplayNameForLocale(
228 base_language_codes
[i
], app_locale
, false);
229 base::string16 native_display_name
=
230 l10n_util::GetDisplayNameForLocale(
231 base_language_codes
[i
], base_language_codes
[i
], false);
232 language_map
[display_name
] =
233 std::make_pair(base_language_codes
[i
], native_display_name
);
235 const std::map
<std::string
, int>::const_iterator index_pos
=
236 language_index
.find(base_language_codes
[i
]);
237 if (index_pos
!= language_index
.end()) {
238 most_relevant_locales_display_names
[index_pos
->second
] = display_name
;
239 ++most_relevant_locales_count
;
241 display_names
.push_back(display_name
);
245 // Sort display names using locale specific sorter.
246 l10n_util::SortStrings16(app_locale
, &display_names
);
247 // Concatenate most_relevant_locales_display_names and display_names.
248 // Insert special divider in between.
249 std::vector
<base::string16
> out_display_names
;
250 for (size_t i
= 0; i
< most_relevant_locales_display_names
.size(); ++i
) {
251 if (most_relevant_locales_display_names
[i
].size() == 0)
253 out_display_names
.push_back(most_relevant_locales_display_names
[i
]);
256 base::string16 divider16
;
257 if (insert_divider
&& !out_display_names
.empty()) {
258 // Insert a divider if requested, but only if
259 // |most_relevant_locales_display_names| is not empty.
260 divider16
= base::ASCIIToUTF16(kMostRelevantLanguagesDivider
);
261 out_display_names
.push_back(divider16
);
264 std::copy(display_names
.begin(),
266 std::back_inserter(out_display_names
));
268 // Build the language list from the language map.
269 scoped_ptr
<base::ListValue
> language_list(new base::ListValue());
270 for (size_t i
= 0; i
< out_display_names
.size(); ++i
) {
271 // Sets the directionality of the display language name.
272 base::string16
display_name(out_display_names
[i
]);
273 if (insert_divider
&& display_name
== divider16
) {
275 base::DictionaryValue
* dictionary
= new base::DictionaryValue();
276 dictionary
->SetString("code", kMostRelevantLanguagesDivider
);
277 language_list
->Append(dictionary
);
281 const LanguagePair
& pair
= language_map
[out_display_names
[i
]];
282 language_list
->Append(
283 CreateLanguageEntry(pair
.first
, out_display_names
[i
], pair
.second
));
286 return language_list
.Pass();
289 // Invokes |callback| with a list of keyboard layouts that can be used for
290 // |resolved_locale|.
291 void GetKeyboardLayoutsForResolvedLocale(
292 const GetKeyboardLayoutsForLocaleCallback
& callback
,
293 const std::string
& resolved_locale
) {
294 input_method::InputMethodUtil
* util
=
295 input_method::InputMethodManager::Get()->GetInputMethodUtil();
296 std::vector
<std::string
> layouts
= util
->GetHardwareInputMethodIds();
297 std::vector
<std::string
> layouts_from_locale
;
298 util
->GetInputMethodIdsFromLanguageCode(
300 input_method::kKeyboardLayoutsOnly
,
301 &layouts_from_locale
);
302 layouts
.insert(layouts
.end(), layouts_from_locale
.begin(),
303 layouts_from_locale
.end());
305 std::string selected
;
306 if (!layouts_from_locale
.empty()) {
308 util
->GetInputMethodDescriptorFromId(layouts_from_locale
[0])->id();
311 scoped_ptr
<base::ListValue
> input_methods_list(new base::ListValue
);
312 std::set
<std::string
> input_methods_added
;
313 for (std::vector
<std::string
>::const_iterator it
= layouts
.begin();
314 it
!= layouts
.end(); ++it
) {
315 const input_method::InputMethodDescriptor
* ime
=
316 util
->GetInputMethodDescriptorFromId(*it
);
317 if (!InsertString(ime
->id(), &input_methods_added
))
319 input_methods_list
->Append(
320 CreateInputMethodsEntry(*ime
, selected
).release());
323 callback
.Run(input_methods_list
.Pass());
326 // For "UI Language" drop-down menu at OOBE screen we need to decide which
327 // entry to mark "selected". If user has just selected "requested_locale",
328 // but "loaded_locale" was actually loaded, we mark original user choice
329 // "selected" only if loaded_locale is a backup for "requested_locale".
330 std::string
CalculateSelectedLanguage(const std::string
& requested_locale
,
331 const std::string
& loaded_locale
) {
332 std::string resolved_locale
;
333 if (!l10n_util::CheckAndResolveLocale(requested_locale
, &resolved_locale
))
334 return loaded_locale
;
336 if (resolved_locale
== loaded_locale
)
337 return requested_locale
;
339 return loaded_locale
;
342 void ResolveLanguageListOnBlockingPool(
343 const chromeos::locale_util::LanguageSwitchResult
* language_switch_result
,
344 scoped_ptr
<base::ListValue
>* list
,
345 std::string
* list_locale
,
346 std::string
* selected_language
) {
347 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
349 if (!language_switch_result
) {
351 StartupCustomizationDocument::GetInstance()->initial_locale_default();
353 if (language_switch_result
->success
) {
354 if (language_switch_result
->requested_locale
==
355 language_switch_result
->loaded_locale
) {
356 *selected_language
= language_switch_result
->requested_locale
;
359 CalculateSelectedLanguage(language_switch_result
->requested_locale
,
360 language_switch_result
->loaded_locale
);
363 *selected_language
= language_switch_result
->loaded_locale
;
366 const std::string selected_code
=
367 selected_language
->empty() ? g_browser_process
->GetApplicationLocale()
368 : *selected_language
;
370 *list_locale
= language_switch_result
371 ? language_switch_result
->loaded_locale
372 : g_browser_process
->GetApplicationLocale();
373 list
->reset(chromeos::GetUILanguageList(NULL
, selected_code
).release());
376 void OnLanguageListResolved(
377 UILanguageListResolvedCallback callback
,
378 scoped_ptr
<scoped_ptr
<base::ListValue
>> new_language_list
,
379 scoped_ptr
<std::string
> new_language_list_locale
,
380 scoped_ptr
<std::string
> new_selected_language
) {
381 callback
.Run(new_language_list
->Pass(),
382 *new_language_list_locale
,
383 *new_selected_language
);
386 void AdjustUILanguageList(const std::string
& selected
,
387 base::ListValue
* languages_list
) {
388 for (size_t i
= 0; i
< languages_list
->GetSize(); ++i
) {
389 base::DictionaryValue
* language_info
= NULL
;
390 if (!languages_list
->GetDictionary(i
, &language_info
))
394 language_info
->GetString("code", &value
);
395 std::string display_name
;
396 language_info
->GetString("displayName", &display_name
);
397 std::string native_name
;
398 language_info
->GetString("nativeDisplayName", &native_name
);
400 // If it's an option group divider, add field name.
401 if (value
== kMostRelevantLanguagesDivider
) {
402 language_info
->SetString(
404 l10n_util::GetStringUTF16(IDS_OOBE_OTHER_LANGUAGES
));
406 if (display_name
!= native_name
) {
407 display_name
= base::StringPrintf("%s - %s",
408 display_name
.c_str(),
409 native_name
.c_str());
412 language_info
->SetString("value", value
);
413 language_info
->SetString("title", display_name
);
414 if (value
== selected
)
415 language_info
->SetBoolean("selected", true);
421 void ResolveUILanguageList(
422 scoped_ptr
<chromeos::locale_util::LanguageSwitchResult
>
423 language_switch_result
,
424 UILanguageListResolvedCallback callback
) {
425 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
427 scoped_ptr
<scoped_ptr
<base::ListValue
>> new_language_list(
428 new scoped_ptr
<base::ListValue
>());
429 scoped_ptr
<std::string
> new_language_list_locale(new std::string
);
430 scoped_ptr
<std::string
> new_selected_language(new std::string
);
432 base::Closure resolve_on_pool
=
433 base::Bind(&ResolveLanguageListOnBlockingPool
,
434 base::Owned(language_switch_result
.release()),
435 base::Unretained(new_language_list
.get()),
436 base::Unretained(new_language_list_locale
.get()),
437 base::Unretained(new_selected_language
.get()));
439 base::Closure on_language_list_resolved
=
440 base::Bind(&OnLanguageListResolved
,
442 base::Passed(new_language_list
.Pass()),
443 base::Passed(new_language_list_locale
.Pass()),
444 base::Passed(new_selected_language
.Pass()));
446 content::BrowserThread::GetBlockingPool()->PostTaskAndReply(
447 FROM_HERE
, resolve_on_pool
, on_language_list_resolved
);
450 scoped_ptr
<base::ListValue
> GetMinimalUILanguageList() {
451 const std::string application_locale
=
452 g_browser_process
->GetApplicationLocale();
453 base::string16 language_native_display_name
=
454 l10n_util::GetDisplayNameForLocale(
455 application_locale
, application_locale
, true);
457 scoped_ptr
<base::ListValue
> language_list(new base::ListValue());
458 language_list
->Append(CreateLanguageEntry(application_locale
,
459 language_native_display_name
,
460 language_native_display_name
));
461 AdjustUILanguageList(std::string(), language_list
.get());
462 return language_list
.Pass();
465 scoped_ptr
<base::ListValue
> GetUILanguageList(
466 const std::vector
<std::string
>* most_relevant_language_codes
,
467 const std::string
& selected
) {
468 ComponentExtensionIMEManager
* manager
=
469 input_method::InputMethodManager::Get()
470 ->GetComponentExtensionIMEManager();
471 input_method::InputMethodDescriptors descriptors
=
472 manager
->GetXkbIMEAsInputMethodDescriptor();
473 scoped_ptr
<base::ListValue
> languages_list(GetLanguageList(
475 l10n_util::GetAvailableLocales(),
476 most_relevant_language_codes
477 ? *most_relevant_language_codes
478 : StartupCustomizationDocument::GetInstance()->configured_locales(),
480 AdjustUILanguageList(selected
, languages_list
.get());
481 return languages_list
.Pass();
484 std::string
FindMostRelevantLocale(
485 const std::vector
<std::string
>& most_relevant_language_codes
,
486 const base::ListValue
& available_locales
,
487 const std::string
& fallback_locale
) {
488 for (std::vector
<std::string
>::const_iterator most_relevant_it
=
489 most_relevant_language_codes
.begin();
490 most_relevant_it
!= most_relevant_language_codes
.end();
491 ++most_relevant_it
) {
492 for (base::ListValue::const_iterator available_it
=
493 available_locales
.begin();
494 available_it
!= available_locales
.end(); ++available_it
) {
495 base::DictionaryValue
* dict
;
496 std::string available_locale
;
497 if (!(*available_it
)->GetAsDictionary(&dict
) ||
498 !dict
->GetString("value", &available_locale
)) {
502 if (available_locale
== *most_relevant_it
)
503 return *most_relevant_it
;
507 return fallback_locale
;
510 scoped_ptr
<base::ListValue
> GetAcceptLanguageList() {
511 // Collect the language codes from the supported accept-languages.
512 const std::string app_locale
= g_browser_process
->GetApplicationLocale();
513 std::vector
<std::string
> accept_language_codes
;
514 l10n_util::GetAcceptLanguagesForLocale(app_locale
, &accept_language_codes
);
515 return GetLanguageList(
516 *input_method::InputMethodManager::Get()->GetSupportedInputMethods(),
517 accept_language_codes
,
518 StartupCustomizationDocument::GetInstance()->configured_locales(),
522 scoped_ptr
<base::ListValue
> GetAndActivateLoginKeyboardLayouts(
523 const std::string
& locale
,
524 const std::string
& selected
,
525 bool activate_keyboards
) {
526 scoped_ptr
<base::ListValue
> input_methods_list(new base::ListValue
);
527 input_method::InputMethodManager
* manager
=
528 input_method::InputMethodManager::Get();
529 input_method::InputMethodUtil
* util
= manager
->GetInputMethodUtil();
531 const std::vector
<std::string
>& hardware_login_input_methods
=
532 util
->GetHardwareLoginInputMethodIds();
534 if (activate_keyboards
) {
536 ProfileHelper::IsSigninProfile(ProfileManager::GetActiveUserProfile()));
537 manager
->GetActiveIMEState()->EnableLoginLayouts(
538 locale
, hardware_login_input_methods
);
541 scoped_ptr
<input_method::InputMethodDescriptors
> input_methods(
542 manager
->GetActiveIMEState()->GetActiveInputMethods());
543 std::set
<std::string
> input_methods_added
;
545 for (std::vector
<std::string
>::const_iterator i
=
546 hardware_login_input_methods
.begin();
547 i
!= hardware_login_input_methods
.end();
549 const input_method::InputMethodDescriptor
* ime
=
550 util
->GetInputMethodDescriptorFromId(*i
);
551 // Do not crash in case of misconfiguration.
553 input_methods_added
.insert(*i
);
554 input_methods_list
->Append(
555 CreateInputMethodsEntry(*ime
, selected
).release());
561 bool optgroup_added
= false;
562 for (size_t i
= 0; i
< input_methods
->size(); ++i
) {
563 // Makes sure the id is in legacy xkb id format.
564 const std::string
& ime_id
= (*input_methods
)[i
].id();
565 if (!InsertString(ime_id
, &input_methods_added
))
567 if (!optgroup_added
) {
568 optgroup_added
= true;
569 AddOptgroupOtherLayouts(input_methods_list
.get());
571 input_methods_list
->Append(CreateInputMethodsEntry((*input_methods
)[i
],
572 selected
).release());
575 // "xkb:us::eng" should always be in the list of available layouts.
576 const std::string us_keyboard_id
=
577 util
->GetFallbackInputMethodDescriptor().id();
578 if (input_methods_added
.find(us_keyboard_id
) == input_methods_added
.end()) {
579 const input_method::InputMethodDescriptor
* us_eng_descriptor
=
580 util
->GetInputMethodDescriptorFromId(us_keyboard_id
);
581 DCHECK(us_eng_descriptor
);
582 if (!optgroup_added
) {
583 optgroup_added
= true;
584 AddOptgroupOtherLayouts(input_methods_list
.get());
586 input_methods_list
->Append(CreateInputMethodsEntry(*us_eng_descriptor
,
587 selected
).release());
588 manager
->GetActiveIMEState()->EnableInputMethod(us_keyboard_id
);
590 return input_methods_list
.Pass();
593 void GetKeyboardLayoutsForLocale(
594 const GetKeyboardLayoutsForLocaleCallback
& callback
,
595 const std::string
& locale
) {
596 base::SequencedWorkerPool
* worker_pool
=
597 content::BrowserThread::GetBlockingPool();
598 scoped_refptr
<base::SequencedTaskRunner
> background_task_runner
=
599 worker_pool
->GetSequencedTaskRunnerWithShutdownBehavior(
600 worker_pool
->GetNamedSequenceToken(kSequenceToken
),
601 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
603 // Resolve |locale| on a background thread, then continue on the current
605 std::string (*get_application_locale
)(const std::string
&, bool) =
606 &l10n_util::GetApplicationLocale
;
607 base::PostTaskAndReplyWithResult(
608 background_task_runner
.get(),
610 base::Bind(get_application_locale
, locale
, false /* set_icu_locale */),
611 base::Bind(&GetKeyboardLayoutsForResolvedLocale
, callback
));
614 scoped_ptr
<base::DictionaryValue
> GetCurrentKeyboardLayout() {
615 const input_method::InputMethodDescriptor current_input_method
=
616 input_method::InputMethodManager::Get()
617 ->GetActiveIMEState()
618 ->GetCurrentInputMethod();
619 return CreateInputMethodsEntry(current_input_method
,
620 current_input_method
.id());
623 } // namespace chromeos