// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/accessibility/dictation.h"
#include <string_view>
#include "base/containers/contains.h"
#include "base/containers/fixed_flat_map.h"
#include "base/containers/flat_map.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "components/language/core/browser/pref_names.h"
#include "components/language/core/common/locale_util.h"
#include "components/prefs/pref_service.h"
#include "components/soda/soda_installer.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/base/ime/ash/input_method_manager.h"
#include "ui/base/ime/ash/input_method_util.h"
namespace ash {
namespace {
const char kDefaultProfileLocale[] = "en-US";
// Determines the user's language or locale from the system, first trying
// the current IME language and falling back to the application locale.
std::string GetUserLangOrLocaleFromSystem(Profile* profile) {
// Convert from the ID used in the pref to a language identifier.
std::vector<std::string> input_method_ids;
input_method_ids.push_back(
profile->GetPrefs()->GetString(::prefs::kLanguageCurrentInputMethod));
std::vector<std::string> languages;
input_method::InputMethodManager::Get()
->GetInputMethodUtil()
->GetLanguageCodesFromInputMethodIds(input_method_ids, &languages);
std::string user_language;
if (!languages.empty())
user_language = languages[0];
// If we don't find an IME language, fall back to using the application
// locale.
if (user_language.empty())
user_language = g_browser_process->GetApplicationLocale();
return user_language.empty() ? kDefaultProfileLocale : user_language;
}
std::string GetSupportedLocale(const std::string& lang_or_locale) {
if (lang_or_locale.empty())
return std::string();
// Map of language code to supported locale for the open web API.
// Chrome OS does not support Chinese languages with "cmn", so this
// map also includes a map from Open Speech API "cmn" languages to
// their equivalent default locale.
static constexpr auto kLangsToDefaultLocales =
base::MakeFixedFlatMap<std::string_view, std::string_view>(
{{"af", "af-ZA"}, {"am", "am-ET"},
{"ar", "ar-001"}, {"az", "az-AZ"},
{"bg", "bg-BG"}, {"bn", "bn-IN"},
{"bs", "bs-BA"}, {"ca", "ca-ES"},
{"cs", "cs-CZ"}, {"da", "da-DK"},
{"de", "de-DE"}, {"el", "el-GR"},
{"en", "en-US"}, {"es", "es-ES"},
{"et", "et-EE"}, {"eu", "eu-ES"},
{"fa", "fa-IR"}, {"fi", "fi-FI"},
{"fil", "fil-PH"}, {"fr", "fr-FR"},
{"gl", "gl-ES"}, {"gu", "gu-IN"},
{"he", "iw-IL"}, {"hi", "hi-IN"},
{"hr", "hr-HR"}, {"hu", "hu-HU"},
{"hy", "hy-AM"}, {"id", "id-ID"},
{"is", "is-IS"}, {"it", "it-IT"},
{"iw", "iw-IL"}, {"ja", "ja-JP"},
{"jv", "jv-ID"}, {"ka", "ka-GE"},
{"kk", "kk-KZ"}, {"km", "km-KH"},
{"kn", "kn-IN"}, {"ko", "ko-KR"},
{"lo", "lo-LA"}, {"lt", "lt-LT"},
{"lv", "lv-LV"}, {"mk", "mk-MK"},
{"ml", "ml-IN"}, {"mn", "mn-MN"},
{"mo", "ro-RO"}, {"mr", "mr-IN"},
{"ms", "ms-MY"}, {"my", "my-MM"},
{"ne", "ne-NP"}, {"nl", "nl-NL"},
{"no", "no-NO"}, {"pa", "pa-Guru-IN"},
{"pl", "pl-PL"}, {"pt", "pt-BR"},
{"ro", "ro-RO"}, {"ru", "ru-RU"},
{"si", "si-LK"}, {"sk", "sk-SK"},
{"sl", "sl-SI"}, {"sq", "sq-AL"},
{"sr", "sr-RS"}, {"su", "su-ID"},
{"sv", "sv-SE"}, {"sw", "sw-TZ"},
{"ta", "ta-IN"}, {"te", "te-IN"},
{"tl", "fil-PH"}, {"th", "th-TH"},
{"tr", "tr-TR"}, {"uk", "uk-UA"},
{"ur", "ur-PK"}, {"uz", "uz-UZ"},
{"vi", "vi-VN"}, {"yue", "yue-Hant-HK"},
{"zh", "zh-CN"}, {"zu", "zu-ZA"},
{"zh-cmn-CN", "zh-CN"}, {"zh-cmn", "zh-CN"},
{"zh-cmn-Hans", "zh-CN"}, {"zh-cmn-Hans-CN", "zh-CN"},
{"cmn-CN", "zh-CN"}, {"cmn-Hans", "zh-CN"},
{"cmn-Hans-CN", "zh-CN"}, {"cmn-Hant-TW", "zh-TW"},
{"zh-cmn-TW", "zh-TW"}, {"zh-cmn-Hant-TW", "zh-TW"},
{"cmn-TW", "zh-TW"}});
// First check if this is a language code supported in the map above.
auto iter = kLangsToDefaultLocales.find(lang_or_locale);
if (iter != kLangsToDefaultLocales.end())
return std::string(iter->second);
// If it's only a language code, we can return early, because no other
// language-only codes are supported.
std::pair<std::string_view, std::string_view> lang_and_locale_pair =
language::SplitIntoMainAndTail(lang_or_locale);
if (lang_and_locale_pair.second.size() == 0)
return std::string();
// The code is a supported locale. Return itself.
// Note that it doesn't matter if the supported locale is online or offline.
if (base::Contains(Dictation::GetAllSupportedLocales(), lang_or_locale))
return lang_or_locale;
// Finally, get the language code from the locale and try to use it to map
// to a default locale. For example, "en-XX" should map to "en-US" if "en-XX"
// does not exist.
iter = kLangsToDefaultLocales.find(lang_and_locale_pair.first);
if (iter != kLangsToDefaultLocales.end())
return std::string(iter->second);
return std::string();
}
} // namespace
// static
const base::flat_map<std::string, Dictation::LocaleData>
Dictation::GetAllSupportedLocales() {
base::flat_map<std::string, LocaleData> supported_locales;
// If new RTL locales are added, ensure that
// accessibility_common/dictation/commands.js RTLLocales is updated
// appropriately.
static const char* kWebSpeechSupportedLocales[] = {
"af-ZA", "am-ET", "ar-AE", "ar-BH", "ar-DZ", "ar-EG", "ar-IL",
"ar-IQ", "ar-JO", "ar-KW", "ar-LB", "ar-MA", "ar-OM", "ar-PS",
"ar-QA", "ar-SA", "ar-TN", "ar-YE", "az-AZ", "bg-BG", "bn-BD",
"bn-IN", "bs-BA", "ca-ES", "cs-CZ", "da-DK", "de-AT", "de-CH",
"de-DE", "el-GR", "en-AU", "en-CA", "en-GB", "en-GH", "en-HK",
"en-IE", "en-IN", "en-KE", "en-NG", "en-NZ", "en-PH", "en-PK",
"en-SG", "en-TZ", "en-US", "en-ZA", "es-AR", "es-BO", "es-CL",
"es-CO", "es-CR", "es-DO", "es-EC", "es-ES", "es-GT", "es-HN",
"es-MX", "es-NI", "es-PA", "es-PE", "es-PR", "es-PY", "es-SV",
"es-US", "es-UY", "es-VE", "et-EE", "eu-ES", "fa-IR", "fi-FI",
"fil-PH", "fr-BE", "fr-CA", "fr-CH", "fr-FR", "gl-ES", "gu-IN",
"hi-IN", "hr-HR", "hu-HU", "hy-AM", "id-ID", "is-IS", "it-CH",
"it-IT", "iw-IL", "ja-JP", "jv-ID", "ka-GE", "kk-KZ", "km-KH",
"kn-IN", "ko-KR", "lo-LA", "lt-LT", "lv-LV", "mk-MK", "ml-IN",
"mn-MN", "mr-IN", "ms-MY", "my-MM", "ne-NP", "nl-BE", "nl-NL",
"no-NO", "pa-Guru-IN", "pl-PL", "pt-BR", "pt-PT", "ro-RO", "ru-RU",
"si-LK", "sk-SK", "sl-SI", "sq-AL", "sr-RS", "su-ID", "sv-SE",
"sw-KE", "sw-TZ", "ta-IN", "ta-LK", "ta-MY", "ta-SG", "te-IN",
"th-TH", "tr-TR", "uk-UA", "ur-IN", "ur-PK", "uz-UZ", "vi-VN",
"yue-Hant-HK", "zh-CN", "zh-TW", "zu-ZA", "ar-001"};
for (const char* locale : kWebSpeechSupportedLocales) {
// By default these languages are not supported offline.
supported_locales[locale] = LocaleData();
}
if (features::IsDictationOfflineAvailable()) {
speech::SodaInstaller* soda_installer =
speech::SodaInstaller::GetInstance();
std::vector<std::string> offline_locales =
soda_installer->GetAvailableLanguages();
for (auto locale : offline_locales) {
// These are supported offline.
supported_locales[locale] = LocaleData();
supported_locales[locale].works_offline = true;
supported_locales[locale].installed =
soda_installer->IsSodaInstalled(speech::GetLanguageCode(locale));
}
}
return supported_locales;
}
// static
std::string Dictation::DetermineDefaultSupportedLocale(Profile* profile,
bool new_user) {
std::string lang_or_locale;
if (new_user) {
// This is the first time this user has enabled Dictation. Pick the default
// language preference based on their application locale.
lang_or_locale = g_browser_process->GetApplicationLocale();
} else {
// This user has already had Dictation enabled, but now we need to map
// from the language they've previously used to a supported locale.
lang_or_locale = GetUserLangOrLocaleFromSystem(profile);
}
std::string supported_locale = GetSupportedLocale(lang_or_locale);
return supported_locale.empty() ? kDefaultProfileLocale : supported_locale;
}
} // namespace ash