Commit fa2fae25 authored by Katie D's avatar Katie D Committed by Commit Bot

Set default Text-to-Speech voices on the TTS settings page.

Finds the voice that best matches the app locale so that the default voice
is as close as possible to the app locale in the user's preferred language.

Bug: 823359
Cq-Include-Trybots: master.tryserver.chromium.linux:closure_compilation
Change-Id: I3374e2d38051cd7dbc089da2a9483887befc9fe4
Reviewed-on: https://chromium-review.googlesource.com/1067540Reviewed-by: default avatarHector Carmona <hcarmona@chromium.org>
Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Commit-Queue: Katie Dektar <katie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#561705}
parent 5805cd33
......@@ -15,8 +15,11 @@
* is generated in tts_subpage.js and not passed from tts_handler.cc).
* |displayLanguage| is the user-facing display string, i.e. 'English'.
* |fullLanguageCode| is the code with locale, i.e. 'en-us' or 'en-gb'.
* |languageScore| is a relative measure of how closely the voice's language
* matches the app language, and can be used to set a default voice.
* @typedef {{languageCode: string, name: string, displayLanguage: string,
* extensionId: string, id: string, fullLanguageCode: string}}
* extensionId: string, id: string, fullLanguageCode: string,
* languageScore: number}}
*/
let TtsHandlerVoice;
......
......@@ -23,6 +23,7 @@ Polymer({
/**
* Available languages.
* @type {Array<{language: string, code: string, voice: TtsHandlerVoice}>}
*/
languagesToVoices: {
type: Array,
......@@ -156,9 +157,10 @@ Polymer({
result[voice.languageCode].voices.push(voice);
languageCodeMap[voice.fullLanguageCode] = voice.languageCode;
});
this.updateLangToVoicePrefs_(result);
this.set('languagesToVoices', Object.values(result));
this.set('allVoices', voices);
this.setDefaultPreviewVoiceForLocale_(languageCodeMap);
this.setDefaultPreviewVoiceForLocale_(voices, languageCodeMap);
},
/**
......@@ -204,33 +206,104 @@ Polymer({
});
},
/**
* Updates the preferences given the current list of voices.
* @param {Object<string, {language: string, code: string,
* voices: Array<TtsHandlerVoice>}>} langToVoices
* @private
*/
updateLangToVoicePrefs_: function(langToVoices) {
if (langToVoices.length == 0)
return;
let allCodes = new Set(
Object.keys(this.prefs.settings['tts']['lang_to_voice_name'].value));
for (let code in langToVoices) {
// Remove from allCodes, to track what we've found a default for.
allCodes.delete(code);
let voices = langToVoices[code].voices;
let defaultVoiceForLang =
this.prefs.settings['tts']['lang_to_voice_name'].value[code];
if (!defaultVoiceForLang || defaultVoiceForLang === '') {
// Initialize prefs that have no value
this.set(
'prefs.settings.tts.lang_to_voice_name.value.' + code,
this.getBestVoiceForLocale_(voices));
continue;
}
// See if the set voice ID is in the voices list, in which case we are
// done checking this language.
if (voices.some(voice => voice.id === defaultVoiceForLang))
continue;
// Change prefs that point to voices that no longer exist.
this.set(
'prefs.settings.tts.lang_to_voice_name.value.' + code,
this.getBestVoiceForLocale_(voices));
}
// If there are any items left in allCodes, they are for languages that are
// no longer covered by the UI. We could now delete them from the
// lang_to_voice_name pref.
for (let code of allCodes) {
this.set('prefs.settings.tts.lang_to_voice_name.value.' + code, '');
}
},
/**
* Sets the voice to show in the preview drop-down as default, based on the
* current locale and voice preferences.
* @param {Array<TtsHandlerVoice>} allVoices
* @param {Object<string, string>} languageCodeMap Mapping from language code
* to simple language code without locale.
* @private
*/
setDefaultPreviewVoiceForLocale_: function(languageCodeMap) {
if (!this.allVoices)
setDefaultPreviewVoiceForLocale_: function(allVoices, languageCodeMap) {
if (!allVoices || allVoices.length == 0)
return;
// Force a synchronous render so that we can set the default.
this.$.previewVoiceOptions.render();
// Set something if nothing exists. This useful for new users where
// sometimes browserProxy.getProspectiveUILanguage() does not complete the
// callback.
if (!this.defaultPreviewVoice)
this.set('defaultPreviewVoice', this.getBestVoiceForLocale_(allVoices));
let browserProxy = settings.LanguagesBrowserProxyImpl.getInstance();
browserProxy.getProspectiveUILanguage().then(prospectiveUILanguage => {
if (!prospectiveUILanguage)
return;
let code = languageCodeMap[prospectiveUILanguage];
if (!code)
return;
let result = this.prefs.settings['tts']['lang_to_voice_name'].value[code];
if (!result)
return;
// Force a synchronous render so that we can set the default.
this.$.previewVoiceOptions.render();
let result;
if (prospectiveUILanguage && prospectiveUILanguage != '' &&
languageCodeMap[prospectiveUILanguage]) {
let code = languageCodeMap[prospectiveUILanguage];
// First try the pref value.
result = this.prefs.settings['tts']['lang_to_voice_name'].value[code];
}
if (!result) {
// If it's not a pref value yet, or the prospectiveUILanguage was
// missing, try using the voice score.
result = this.getBestVoiceForLocale_(allVoices);
}
this.set('defaultPreviewVoice', result);
});
},
/**
* Gets the best voice for the app locale.
* @param {Array<TtsHandlerVoice>} voices Voices to search through.
* @return {string} The ID of the best matching voice in the array.
* @private
*/
getBestVoiceForLocale_: function(voices) {
let bestScore = -1;
let bestVoice = '';
voices.forEach((voice) => {
if (voice.languageScore > bestScore) {
bestScore = voice.languageScore;
bestVoice = voice.id;
}
});
return bestVoice;
},
/** @private */
onPreviewTtsClick_: function() {
chrome.send(
......
......@@ -67,13 +67,16 @@ void TtsHandler::OnVoicesChanged() {
TtsControllerImpl* impl = TtsControllerImpl::GetInstance();
std::vector<VoiceData> voices;
impl->GetVoices(Profile::FromWebUI(web_ui()), &voices);
const std::string& app_locale = g_browser_process->GetApplicationLocale();
base::ListValue responses;
for (const auto& voice : voices) {
base::DictionaryValue response;
int language_score = GetVoiceLangMatchScore(&voice, app_locale);
std::string language_code = l10n_util::GetLanguage(voice.lang);
response.SetPath({"name"}, base::Value(voice.name));
response.SetPath({"languageCode"}, base::Value(language_code));
response.SetPath({"fullLanguageCode"}, base::Value(voice.lang));
response.SetPath({"languageScore"}, base::Value(language_score));
response.SetPath({"extensionId"}, base::Value(voice.extension_id));
response.SetPath(
{"displayLanguage"},
......@@ -138,4 +141,16 @@ void TtsHandler::OnJavascriptDisallowed() {
TtsController::GetInstance()->RemoveVoicesChangedDelegate(this);
}
int TtsHandler::GetVoiceLangMatchScore(const VoiceData* voice,
const std::string& app_locale) {
if (voice->lang.empty() || app_locale.empty())
return 0;
if (voice->lang == app_locale)
return 2;
return l10n_util::GetLanguage(voice->lang) ==
l10n_util::GetLanguage(app_locale)
? 1
: 0;
}
} // namespace settings
......@@ -29,6 +29,9 @@ class TtsHandler : public SettingsPageUIHandler, public VoicesChangedDelegate {
void OnVoicesChanged() override;
private:
int GetVoiceLangMatchScore(const VoiceData* voice,
const std::string& app_locale);
DISALLOW_COPY_AND_ASSIGN(TtsHandler);
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment