From 0d12cead31216fb430063a14899eadc605250e2f Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Wed, 30 Jan 2019 17:00:42 -0500 Subject: [PATCH 01/19] key language info by locale id, and add chinese --- src/extensions/scratch3_text2speech/index.js | 76 +++++++++++++++----- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index 9a80e9468..da733a69c 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -152,20 +152,56 @@ class Scratch3Text2SpeechBlocks { */ get LANGUAGE_INFO () { return { - 'Danish': 'da', - 'Dutch': 'nl', - 'English': 'en', - 'French': 'fr', - 'German': 'de', - 'Icelandic': 'is', - 'Italian': 'it', - 'Japanese': 'ja', - 'Polish': 'pl', - 'Portuguese (Brazilian)': 'pt-br', - 'Portuguese (European)': 'pt', - 'Russian': 'ru', - 'Spanish (European)': 'es', - 'Spanish (Latin American)': 'es-419' + 'da': { + name: 'Danish' + }, + 'nl': { + name: 'Dutch' + }, + 'en': { + name: 'English' + }, + 'fr': { + name: 'French' + }, + 'de': { + name: 'German' + }, + 'is': { + name: 'Icelandic' + }, + 'it': { + name: 'Italian' + }, + 'ja': { + name: 'Japanese' + }, + 'pl': { + name: 'Polish' + }, + 'pt-br': { + name: 'Portuguese (Brazilian)' + }, + 'pt': { + name: 'Portuguese (European)' + }, + 'ru': { + name: 'Russian' + }, + 'es': { + name: 'Spanish (European)' + }, + 'es-419': { + name: 'Spanish (Latin American)' + }, + 'zh-cn': { + name: 'Chinese (Simplified)', + singleGender: true + }, + 'zh-tw': { + name: 'Chinese (Traditional)', + singleGender: true + } }; } @@ -190,7 +226,9 @@ class Scratch3Text2SpeechBlocks { 'pt': 'pt-PT', // Portuguese (European) 'ru': 'ru-RU', // Russian 'es': 'es-ES', // Spanish (European) - 'es-419': 'es-US' // Spanish (Latin American) + 'es-419': 'es-US', // Spanish (Latin American) + 'zh-cn': 'cmn-CN', // Chinese (simplified) -> Mandarin + 'zh-tw': 'cmn-CN' // Chinese (traditional) -> Mandarin }; let converted = 'en-US'; if (pollyLocales[locale]) { @@ -382,7 +420,7 @@ class Scratch3Text2SpeechBlocks { * @returns {boolean} true if the language code is supported. */ isSupportedLanguage (languageCode) { - return Object.values(this.LANGUAGE_INFO).includes(languageCode); + return Object.keys(this.LANGUAGE_INFO).includes(languageCode); } /** @@ -401,9 +439,9 @@ class Scratch3Text2SpeechBlocks { * @return {array} the text and value for each menu item. */ getLanguageMenu () { - return Object.keys(this.LANGUAGE_INFO).map(languageName => ({ - text: languageName, - value: this.LANGUAGE_INFO[languageName] + return Object.keys(this.LANGUAGE_INFO).map(key => ({ + text: this.LANGUAGE_INFO[key].name, + value: key })); } From 5b5c0d80ebf7a751d9f3c6faceb273ff9a75518a Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Wed, 30 Jan 2019 17:00:58 -0500 Subject: [PATCH 02/19] Set tenor playback rate for single gender languages --- src/extensions/scratch3_text2speech/index.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index da733a69c..673b05d5d 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -65,6 +65,11 @@ const GIANT_ID = 'GIANT'; */ const KITTEN_ID = 'KITTEN'; +/** + * Playback rate for the tenor voice, for cases where we have only a female gender voice. + */ +const TENOR_RATE = 0.9; + /** * Class for the text2speech blocks. * @constructor @@ -499,8 +504,15 @@ class Scratch3Text2SpeechBlocks { const state = this._getState(util.target); - const gender = this.VOICE_INFO[state.voiceId].gender; - const playbackRate = this.VOICE_INFO[state.voiceId].playbackRate; + let gender = this.VOICE_INFO[state.voiceId].gender; + let playbackRate = this.VOICE_INFO[state.voiceId].playbackRate; + + if (this.LANGUAGE_INFO[this.getCurrentLanguage()].singleGender) { + gender = 'female'; + if (state.voiceId === TENOR_ID) { + playbackRate = TENOR_RATE; + } + } if (state.voiceId === KITTEN_ID) { words = words.replace(/\S+/g, 'meow'); From f33e07132ebcc316e8607143619380738d9da66a Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Mon, 4 Feb 2019 17:35:27 -0500 Subject: [PATCH 03/19] Add hindi, korean, norwegian, and adjust rates --- src/extensions/scratch3_text2speech/index.js | 27 ++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index 673b05d5d..d779867bb 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -68,7 +68,12 @@ const KITTEN_ID = 'KITTEN'; /** * Playback rate for the tenor voice, for cases where we have only a female gender voice. */ -const TENOR_RATE = 0.9; +const FEMALE_TENOR_RATE = 0.89; // -2 semitones + +/** + * Playback rate for the giant voice, for cases where we have only a female gender voice. + */ +const FEMALE_GIANT_RATE = 0.79; // -4 semitones /** * Class for the text2speech blocks. @@ -172,6 +177,10 @@ class Scratch3Text2SpeechBlocks { 'de': { name: 'German' }, + 'hi': { + name: 'Hindi', + singleGender: true + }, 'is': { name: 'Icelandic' }, @@ -181,6 +190,14 @@ class Scratch3Text2SpeechBlocks { 'ja': { name: 'Japanese' }, + 'ko': { + name: 'Korean', + singleGender: true + }, + 'no': { + name: 'Norwegian', + singleGender: true + }, 'pl': { name: 'Polish' }, @@ -223,9 +240,12 @@ class Scratch3Text2SpeechBlocks { 'en': 'en-US', // English 'fr': 'fr-FR', // French 'de': 'de-DE', // German + 'hi': 'en-IN', // Hindi 'is': 'is-IS', // Icelandic 'it': 'it-IT', // Italian 'ja': 'ja-JP', // Japanese + 'ko': 'ko-KR', // Korean + 'no': 'nb-NO', // Norwegian 'pl': 'pl-PL', // Polish 'pt-br': 'pt-BR', // Portuguese (Brazilian) 'pt': 'pt-PT', // Portuguese (European) @@ -510,7 +530,10 @@ class Scratch3Text2SpeechBlocks { if (this.LANGUAGE_INFO[this.getCurrentLanguage()].singleGender) { gender = 'female'; if (state.voiceId === TENOR_ID) { - playbackRate = TENOR_RATE; + playbackRate = FEMALE_TENOR_RATE; + } + if (state.voiceId === GIANT_ID) { + playbackRate = FEMALE_GIANT_RATE; } } From 3996cc0c6b5aea3295daf19f984674abe30c9b9f Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Thu, 7 Feb 2019 17:16:49 -0500 Subject: [PATCH 04/19] Add Welsh, Swedish and Turkish --- src/extensions/scratch3_text2speech/index.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index d779867bb..2b04d06eb 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -162,6 +162,10 @@ class Scratch3Text2SpeechBlocks { */ get LANGUAGE_INFO () { return { + 'cy': { + name: 'Welsh', + singleGender: true + }, 'da': { name: 'Danish' }, @@ -213,6 +217,14 @@ class Scratch3Text2SpeechBlocks { 'es': { name: 'Spanish (European)' }, + 'sv': { + name: 'Swedish', + singleGender: true + }, + 'tr': { + name: 'Turkish', + singleGender: true + }, 'es-419': { name: 'Spanish (Latin American)' }, @@ -235,6 +247,7 @@ class Scratch3Text2SpeechBlocks { */ localeToPolly (locale) { const pollyLocales = { + 'cy': 'cy-GB', // Welsh 'da': 'da-DK', // Danish 'nl': 'nl-NL', // Dutch 'en': 'en-US', // English @@ -252,6 +265,8 @@ class Scratch3Text2SpeechBlocks { 'ru': 'ru-RU', // Russian 'es': 'es-ES', // Spanish (European) 'es-419': 'es-US', // Spanish (Latin American) + 'sv': 'sv-SE', // Swedish + 'tr': 'tr-TR', // Turkish 'zh-cn': 'cmn-CN', // Chinese (simplified) -> Mandarin 'zh-tw': 'cmn-CN' // Chinese (traditional) -> Mandarin }; From 920096d0617ea38052f73620c5916302243ea93e Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Fri, 8 Feb 2019 16:55:05 -0500 Subject: [PATCH 05/19] Add romanian and fix ordering --- src/extensions/scratch3_text2speech/index.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index 2b04d06eb..6a1860a47 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -211,12 +211,19 @@ class Scratch3Text2SpeechBlocks { 'pt': { name: 'Portuguese (European)' }, + 'ro': { + name: 'Romanian', + singleGender: true + }, 'ru': { name: 'Russian' }, 'es': { name: 'Spanish (European)' }, + 'es-419': { + name: 'Spanish (Latin American)' + }, 'sv': { name: 'Swedish', singleGender: true @@ -225,9 +232,6 @@ class Scratch3Text2SpeechBlocks { name: 'Turkish', singleGender: true }, - 'es-419': { - name: 'Spanish (Latin American)' - }, 'zh-cn': { name: 'Chinese (Simplified)', singleGender: true @@ -262,6 +266,7 @@ class Scratch3Text2SpeechBlocks { 'pl': 'pl-PL', // Polish 'pt-br': 'pt-BR', // Portuguese (Brazilian) 'pt': 'pt-PT', // Portuguese (European) + 'ro': 'ro-RO', // Romanian 'ru': 'ru-RU', // Russian 'es': 'es-ES', // Spanish (European) 'es-419': 'es-US', // Spanish (Latin American) From da212bcf06b75f8a49e920e1cfe56303eaad2fdb Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Thu, 14 Feb 2019 14:01:04 -0500 Subject: [PATCH 06/19] wip switching from using locales to language IDs internally --- src/extensions/scratch3_text2speech/index.js | 98 +++++++++++++------- 1 file changed, 66 insertions(+), 32 deletions(-) diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index 6a1860a47..5c7406fff 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -65,6 +65,15 @@ const GIANT_ID = 'GIANT'; */ const KITTEN_ID = 'KITTEN'; +/** + * Language ids. + */ +const ENGLISH_ID = 'ENGLISH'; +const CHINESE_ID = 'CHINESE'; +const TURKISH_ID = 'TURKISH'; +const JAPANESE_ID = 'JAPANESE'; + + /** * Playback rate for the tenor voice, for cases where we have only a female gender voice. */ @@ -162,6 +171,30 @@ class Scratch3Text2SpeechBlocks { */ get LANGUAGE_INFO () { return { + [CHINESE_ID]: { + name: 'Chinese (Mandarin)', + singleGender: true, + locales: ['zh-cn', 'zh-tw'], + pollyLocale: 'cmn-CN' + }, + [ENGLISH_ID]: { + name: 'English', + locales: ['en'], + pollyLocale: 'en-US' + }, + [JAPANESE_ID]: { + name: 'Japanese', + locales: ['ja', 'ja-Hira'], + pollyLocale: 'ja-JP' + }, + [TURKISH_ID]: { + name: 'Turkish', + singleGender: true, + locales: ['tr'], + pollyLocale: 'tr-TR' + } + + /* 'cy': { name: 'Welsh', singleGender: true @@ -240,6 +273,7 @@ class Scratch3Text2SpeechBlocks { name: 'Chinese (Traditional)', singleGender: true } + */ }; } @@ -249,38 +283,38 @@ class Scratch3Text2SpeechBlocks { * @param {string} locale the Scratch locale to convert. * @return {string} the Amazon polly locale. */ - localeToPolly (locale) { - const pollyLocales = { - 'cy': 'cy-GB', // Welsh - 'da': 'da-DK', // Danish - 'nl': 'nl-NL', // Dutch - 'en': 'en-US', // English - 'fr': 'fr-FR', // French - 'de': 'de-DE', // German - 'hi': 'en-IN', // Hindi - 'is': 'is-IS', // Icelandic - 'it': 'it-IT', // Italian - 'ja': 'ja-JP', // Japanese - 'ko': 'ko-KR', // Korean - 'no': 'nb-NO', // Norwegian - 'pl': 'pl-PL', // Polish - 'pt-br': 'pt-BR', // Portuguese (Brazilian) - 'pt': 'pt-PT', // Portuguese (European) - 'ro': 'ro-RO', // Romanian - 'ru': 'ru-RU', // Russian - 'es': 'es-ES', // Spanish (European) - 'es-419': 'es-US', // Spanish (Latin American) - 'sv': 'sv-SE', // Swedish - 'tr': 'tr-TR', // Turkish - 'zh-cn': 'cmn-CN', // Chinese (simplified) -> Mandarin - 'zh-tw': 'cmn-CN' // Chinese (traditional) -> Mandarin - }; - let converted = 'en-US'; - if (pollyLocales[locale]) { - converted = pollyLocales[locale]; - } - return converted; - } + // localeToPolly (locale) { + // const pollyLocales = { + // 'cy': 'cy-GB', // Welsh + // 'da': 'da-DK', // Danish + // 'nl': 'nl-NL', // Dutch + // 'en': 'en-US', // English + // 'fr': 'fr-FR', // French + // 'de': 'de-DE', // German + // 'hi': 'en-IN', // Hindi + // 'is': 'is-IS', // Icelandic + // 'it': 'it-IT', // Italian + // 'ja': 'ja-JP', // Japanese + // 'ko': 'ko-KR', // Korean + // 'no': 'nb-NO', // Norwegian + // 'pl': 'pl-PL', // Polish + // 'pt-br': 'pt-BR', // Portuguese (Brazilian) + // 'pt': 'pt-PT', // Portuguese (European) + // 'ro': 'ro-RO', // Romanian + // 'ru': 'ru-RU', // Russian + // 'es': 'es-ES', // Spanish (European) + // 'es-419': 'es-US', // Spanish (Latin American) + // 'sv': 'sv-SE', // Swedish + // 'tr': 'tr-TR', // Turkish + // 'zh-cn': 'cmn-CN', // Chinese (simplified) -> Mandarin + // 'zh-tw': 'cmn-CN' // Chinese (traditional) -> Mandarin + // }; + // let converted = 'en-US'; + // if (pollyLocales[locale]) { + // converted = pollyLocales[locale]; + // } + // return converted; + // } /** * The key to load & store a target's text2speech state. From 04c6bc189a39f30a40c1b8a56bb4c1ed28c9dc1c Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Tue, 5 Mar 2019 15:10:14 -0500 Subject: [PATCH 07/19] Add language ids --- src/extensions/scratch3_text2speech/index.js | 35 +++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index 5c7406fff..074a7718f 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -65,15 +65,6 @@ const GIANT_ID = 'GIANT'; */ const KITTEN_ID = 'KITTEN'; -/** - * Language ids. - */ -const ENGLISH_ID = 'ENGLISH'; -const CHINESE_ID = 'CHINESE'; -const TURKISH_ID = 'TURKISH'; -const JAPANESE_ID = 'JAPANESE'; - - /** * Playback rate for the tenor voice, for cases where we have only a female gender voice. */ @@ -84,6 +75,32 @@ const FEMALE_TENOR_RATE = 0.89; // -2 semitones */ const FEMALE_GIANT_RATE = 0.79; // -4 semitones +/** + * Language ids. The value for each language id must be a valid Scratch locale. + */ +const CHINESE_ID = 'zh-cn'; +const DANISH_ID = 'da'; +const DUTCH_ID = 'nl'; +const ENGLISH_ID = 'en'; +const FRENCH_ID = 'fr'; +const GERMAN_ID = 'de'; +const HINDI_ID = 'hi'; +const ICELANDIC_ID = 'is'; +const ITALIAN_ID = 'it'; +const JAPANESE_ID = 'ja'; +const KOREAN_ID = 'ko'; +const NORWEGIAN_ID = 'no'; +const POLISH_ID = 'pl'; +const PORTUGUESE_BR_ID = 'pt-br'; +const PORTUGUESE_ID = 'pt'; +const ROMANIAN_ID = 'ro'; +const RUSSIAN_ID = 'ru'; +const SPANISH_ID = 'es'; +const SPANISH_419_ID = 'es-419'; +const SWEDISH_ID = 'sv'; +const TURKISH_ID = 'tr'; +const WELSH_ID = 'cy'; + /** * Class for the text2speech blocks. * @constructor From bc9e215ee83c0c6ac42240f5d58961cc7b90fba3 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Tue, 5 Mar 2019 15:10:54 -0500 Subject: [PATCH 08/19] Fill in language info data --- src/extensions/scratch3_text2speech/index.js | 225 +++++++++---------- 1 file changed, 101 insertions(+), 124 deletions(-) diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index 074a7718f..f326b3236 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -186,153 +186,130 @@ class Scratch3Text2SpeechBlocks { /** * An object with language names mapped to their language codes. */ + get LANGUAGE_INFO () { return { [CHINESE_ID]: { name: 'Chinese (Mandarin)', - singleGender: true, locales: ['zh-cn', 'zh-tw'], - pollyLocale: 'cmn-CN' + speechSynthLocale: 'cmn-CN', + singleGender: true + }, + [DANISH_ID]: { + name: 'Danish', + locales: ['da'], + speechSynthLocale: 'da-DK' + }, + [DUTCH_ID]: { + name: 'Dutch', + locales: ['nl'], + speechSynthLocale: 'nl-NL' }, [ENGLISH_ID]: { name: 'English', locales: ['en'], - pollyLocale: 'en-US' + speechSynthLocale: 'en-US' + }, + [FRENCH_ID]: { + name: 'French', + locales: ['fr'], + speechSynthLocale: 'fr-FR' + }, + [GERMAN_ID]: { + name: 'German', + locales: ['de'], + speechSynthLocale: 'de-DE' + }, + [HINDI_ID]: { + name: 'Hindi', + locales: ['hi'], + speechSynthLocale: 'en-IN', // should be hi-IN, but this one is not working? + singleGender: true + }, + [ICELANDIC_ID]: { + name: 'Icelandic', + locales: ['is'], + speechSynthLocale: 'is-IS' + }, + [ITALIAN_ID]: { + name: 'Italian', + locales: ['it'], + speechSynthLocale: 'it-IT' }, [JAPANESE_ID]: { name: 'Japanese', locales: ['ja', 'ja-Hira'], - pollyLocale: 'ja-JP' + speechSynthLocale: 'ja-JP' + }, + [KOREAN_ID]: { + name: 'Korean', + locales: ['ko'], + speechSynthLocale: 'ko-KR', + singleGender: true + }, + [NORWEGIAN_ID]: { + name: 'Norwegian', + locales: ['nb', 'nn'], + speechSynthLocale: 'nb-NO', + singleGender: true + }, + [POLISH_ID]: { + name: 'Polish', + locales: ['pl'], + speechSynthLocale: 'pl-PL' + }, + [PORTUGUESE_BR_ID]: { + name: 'Portuguese (Brazilian)', + locales: ['pt-br'], + speechSynthLocale: 'pt-BR' + }, + [PORTUGUESE_ID]: { + name: 'Portuguese (European)', + locales: ['pt'], + speechSynthLocale: 'pt-PT' + }, + [ROMANIAN_ID]: { + name: 'Romanian', + locales: ['ro'], + speechSynthLocale: 'ro-RO', + singleGender: true + }, + [RUSSIAN_ID]: { + name: 'Russian', + locales: ['ru'], + speechSynthLocale: 'ru-RU' + }, + [SPANISH_ID]: { + name: 'Spanish (European)', + locales: ['es'], + speechSynthLocale: 'es-ES' + }, + [SPANISH_419_ID]: { + name: 'Spanish (Latin American)', + locales: ['es-419'], + speechSynthLocale: 'es-US' + }, + [SWEDISH_ID]: { + name: 'Swedish', + locales: ['sv'], + speechSynthLocale: 'sv-SE', + singleGender: true }, [TURKISH_ID]: { name: 'Turkish', - singleGender: true, locales: ['tr'], - pollyLocale: 'tr-TR' - } - - /* - 'cy': { + speechSynthLocale: 'tr-TR', + singleGender: true + }, + [WELSH_ID]: { name: 'Welsh', - singleGender: true - }, - 'da': { - name: 'Danish' - }, - 'nl': { - name: 'Dutch' - }, - 'en': { - name: 'English' - }, - 'fr': { - name: 'French' - }, - 'de': { - name: 'German' - }, - 'hi': { - name: 'Hindi', - singleGender: true - }, - 'is': { - name: 'Icelandic' - }, - 'it': { - name: 'Italian' - }, - 'ja': { - name: 'Japanese' - }, - 'ko': { - name: 'Korean', - singleGender: true - }, - 'no': { - name: 'Norwegian', - singleGender: true - }, - 'pl': { - name: 'Polish' - }, - 'pt-br': { - name: 'Portuguese (Brazilian)' - }, - 'pt': { - name: 'Portuguese (European)' - }, - 'ro': { - name: 'Romanian', - singleGender: true - }, - 'ru': { - name: 'Russian' - }, - 'es': { - name: 'Spanish (European)' - }, - 'es-419': { - name: 'Spanish (Latin American)' - }, - 'sv': { - name: 'Swedish', - singleGender: true - }, - 'tr': { - name: 'Turkish', - singleGender: true - }, - 'zh-cn': { - name: 'Chinese (Simplified)', - singleGender: true - }, - 'zh-tw': { - name: 'Chinese (Traditional)', + locales: ['cy'], + speechSynthLocale: 'cy-GB', singleGender: true } - */ }; } - /** - * This is a temporary adapter to convert Scratch locale codes to Amazon polly's locale codes. - * @todo remove this once the speech synthesis server can perform this conversion - * @param {string} locale the Scratch locale to convert. - * @return {string} the Amazon polly locale. - */ - // localeToPolly (locale) { - // const pollyLocales = { - // 'cy': 'cy-GB', // Welsh - // 'da': 'da-DK', // Danish - // 'nl': 'nl-NL', // Dutch - // 'en': 'en-US', // English - // 'fr': 'fr-FR', // French - // 'de': 'de-DE', // German - // 'hi': 'en-IN', // Hindi - // 'is': 'is-IS', // Icelandic - // 'it': 'it-IT', // Italian - // 'ja': 'ja-JP', // Japanese - // 'ko': 'ko-KR', // Korean - // 'no': 'nb-NO', // Norwegian - // 'pl': 'pl-PL', // Polish - // 'pt-br': 'pt-BR', // Portuguese (Brazilian) - // 'pt': 'pt-PT', // Portuguese (European) - // 'ro': 'ro-RO', // Romanian - // 'ru': 'ru-RU', // Russian - // 'es': 'es-ES', // Spanish (European) - // 'es-419': 'es-US', // Spanish (Latin American) - // 'sv': 'sv-SE', // Swedish - // 'tr': 'tr-TR', // Turkish - // 'zh-cn': 'cmn-CN', // Chinese (simplified) -> Mandarin - // 'zh-tw': 'cmn-CN' // Chinese (traditional) -> Mandarin - // }; - // let converted = 'en-US'; - // if (pollyLocales[locale]) { - // converted = pollyLocales[locale]; - // } - // return converted; - // } - /** * The key to load & store a target's text2speech state. * @return {string} The key. From ab633d34481e41b258119a90a7cd2738bcae1c59 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Tue, 5 Mar 2019 15:11:14 -0500 Subject: [PATCH 09/19] WIP updating to use new language info data --- src/extensions/scratch3_text2speech/index.js | 28 +++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index f326b3236..b0b6b522b 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -453,8 +453,8 @@ class Scratch3Text2SpeechBlocks { } /** - * Get the language for speech synthesis. - * @returns {string} the language code. + * Get the language code currently set for the extension. + * @returns {string} a Scratch locale code. */ getCurrentLanguage () { const stage = this.runtime.getTargetForStage(); @@ -467,9 +467,9 @@ class Scratch3Text2SpeechBlocks { } /** - * Set the language for speech synthesis. + * Set the language code for the extension. * It is stored in the stage so it can be saved and loaded with the project. - * @param {string} languageCode a locale code to set. + * @param {string} languageCode a Scratch locale code. */ setCurrentLanguage (languageCode) { const stage = this.runtime.getTargetForStage(); @@ -486,6 +486,19 @@ class Scratch3Text2SpeechBlocks { } } + /** + * Get the locale code used by the speech synthesis server corresponding to + * the current language code set for the extension. + * @returns {string} the speech synthesis locale. + */ + _getSpeechSynthLocale () { + let speechSynthLocale = this.LANGUAGE_INFO[this.DEFAULT_LANGUAGE].speechSynthLocale; + if (this.LANGUAGE_INFO[this.getCurrentLanguage()]) { + speechSynthLocale = this.LANGUAGE_INFO[this.getCurrentLanguage()].speechSynthLocale; + } + return speechSynthLocale; + } + /** * Check if a language code is in the list of supported languages for the * speech synthesis service. @@ -568,13 +581,16 @@ class Scratch3Text2SpeechBlocks { speakAndWait (args, util) { // Cast input to string let words = Cast.toString(args.WORDS); - let locale = this.localeToPolly(this.getCurrentLanguage()); + let locale = this._getSpeechSynthLocale(); const state = this._getState(util.target); let gender = this.VOICE_INFO[state.voiceId].gender; let playbackRate = this.VOICE_INFO[state.voiceId].playbackRate; + // Special case for voices where the synthesis service only provides a + // single gender voice. In that case, always request the female voice, + // and set special playback rates for the tenor and giant voices. if (this.LANGUAGE_INFO[this.getCurrentLanguage()].singleGender) { gender = 'female'; if (state.voiceId === TENOR_ID) { @@ -587,7 +603,7 @@ class Scratch3Text2SpeechBlocks { if (state.voiceId === KITTEN_ID) { words = words.replace(/\S+/g, 'meow'); - locale = 'en-US'; + locale = this.LANGUAGE_INFO[this.DEFAULT_LANGUAGE].speechSynthLocale; } // Build up URL From d059f8baa6c4ae10bc18d45adb1f585af3e4dbef Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Tue, 5 Mar 2019 17:38:54 -0500 Subject: [PATCH 10/19] Check and set language, handling many-to-one mapping --- src/extensions/scratch3_text2speech/index.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index b0b6b522b..c9cbdf1a2 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -476,7 +476,14 @@ class Scratch3Text2SpeechBlocks { if (!stage) return; // Only set the language if it is in the list. if (this.isSupportedLanguage(languageCode)) { - stage.textToSpeechLanguage = languageCode; + // Set the language code used by the extension. There are a few + // languages where we map multiple written languages to one spoken + // language. + for (const lang in this.LANGUAGE_INFO) { + if (this.LANGUAGE_INFO[lang].locales.includes(languageCode)) { + stage.textToSpeechLanguage = lang; + } + } } // If the language is null, set it to the default language. // This can occur e.g. if the extension was loaded with the editor @@ -500,13 +507,18 @@ class Scratch3Text2SpeechBlocks { } /** - * Check if a language code is in the list of supported languages for the + * Check if a Scratch language code is in the list of supported languages for the * speech synthesis service. * @param {string} languageCode the language code to check. * @returns {boolean} true if the language code is supported. */ isSupportedLanguage (languageCode) { - return Object.keys(this.LANGUAGE_INFO).includes(languageCode); + for (const lang in this.LANGUAGE_INFO) { + if (this.LANGUAGE_INFO[lang].locales.includes(languageCode)) { + return true; + } + } + return false; } /** From 65d0a3aa1131afe55b6015cd6e2b7a0348319a41 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Wed, 6 Mar 2019 10:36:28 -0500 Subject: [PATCH 11/19] cleanup --- src/extensions/scratch3_text2speech/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index c9cbdf1a2..b5f0c4ac3 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -186,7 +186,6 @@ class Scratch3Text2SpeechBlocks { /** * An object with language names mapped to their language codes. */ - get LANGUAGE_INFO () { return { [CHINESE_ID]: { @@ -223,7 +222,7 @@ class Scratch3Text2SpeechBlocks { [HINDI_ID]: { name: 'Hindi', locales: ['hi'], - speechSynthLocale: 'en-IN', // should be hi-IN, but this one is not working? + speechSynthLocale: 'en-IN', singleGender: true }, [ICELANDIC_ID]: { From 8fc3111b215e49f2d9f4dfe56cb23912a15a69b6 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Wed, 6 Mar 2019 10:51:08 -0500 Subject: [PATCH 12/19] use id for default language --- src/extensions/scratch3_text2speech/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index b5f0c4ac3..00916b6d8 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -332,7 +332,7 @@ class Scratch3Text2SpeechBlocks { * @type {string} */ get DEFAULT_LANGUAGE () { - return 'en'; + return ENGLISH_ID; } /** From ee0d395b9c36c51edebf70f093630bcf4383ae38 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Wed, 6 Mar 2019 12:03:46 -0500 Subject: [PATCH 13/19] Cleanup check supported and get extension locale --- src/extensions/scratch3_text2speech/index.js | 56 +++++++++++++------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index 00916b6d8..066549938 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -128,6 +128,12 @@ class Scratch3Text2SpeechBlocks { if (this.runtime) { runtime.on('targetWasCreated', this._onTargetCreated); } + + /** + * A list of all Scratch locales that are supported by the extension. + * @type {Array} + */ + this._supportedLocales = this._getSupportedLocales(); } /** @@ -468,22 +474,16 @@ class Scratch3Text2SpeechBlocks { /** * Set the language code for the extension. * It is stored in the stage so it can be saved and loaded with the project. - * @param {string} languageCode a Scratch locale code. + * @param {string} locale a locale code. */ - setCurrentLanguage (languageCode) { + setCurrentLanguage (locale) { const stage = this.runtime.getTargetForStage(); if (!stage) return; - // Only set the language if it is in the list. - if (this.isSupportedLanguage(languageCode)) { - // Set the language code used by the extension. There are a few - // languages where we map multiple written languages to one spoken - // language. - for (const lang in this.LANGUAGE_INFO) { - if (this.LANGUAGE_INFO[lang].locales.includes(languageCode)) { - stage.textToSpeechLanguage = lang; - } - } + + if (this.isSupportedLanguage(locale)) { + stage.textToSpeechLanguage = this._getExtensionLocaleForSupportedLocale(locale); } + // If the language is null, set it to the default language. // This can occur e.g. if the extension was loaded with the editor // set to a language that is not in the list. @@ -492,10 +492,24 @@ class Scratch3Text2SpeechBlocks { } } + /** + * Get the extension locale for a supported locale, or null. + * @param {string} locale a locale code. + * @returns {?string} a locale supported by the extension, or null. + */ + _getExtensionLocaleForSupportedLocale (locale) { + for (const lang in this.LANGUAGE_INFO) { + if (this.LANGUAGE_INFO[lang].locales.includes(locale)) { + return lang; + } + } + return null; + } + /** * Get the locale code used by the speech synthesis server corresponding to * the current language code set for the extension. - * @returns {string} the speech synthesis locale. + * @returns {string} a speech synthesis locale. */ _getSpeechSynthLocale () { let speechSynthLocale = this.LANGUAGE_INFO[this.DEFAULT_LANGUAGE].speechSynthLocale; @@ -505,6 +519,15 @@ class Scratch3Text2SpeechBlocks { return speechSynthLocale; } + /** + * Get an array of the locales supported by this extension. + * @returns {Array} An array of locale strings. + */ + _getSupportedLocales () { + return Object.keys(this.LANGUAGE_INFO).reduce((acc, cur) => + acc.concat(this.LANGUAGE_INFO[cur].locales), []); + } + /** * Check if a Scratch language code is in the list of supported languages for the * speech synthesis service. @@ -512,12 +535,7 @@ class Scratch3Text2SpeechBlocks { * @returns {boolean} true if the language code is supported. */ isSupportedLanguage (languageCode) { - for (const lang in this.LANGUAGE_INFO) { - if (this.LANGUAGE_INFO[lang].locales.includes(languageCode)) { - return true; - } - } - return false; + return this._supportedLocales.includes(languageCode); } /** From 59586291d72c45c66c6cacb3f4a8602c65cc118f Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Wed, 6 Mar 2019 12:03:54 -0500 Subject: [PATCH 14/19] Comments --- src/extensions/scratch3_text2speech/index.js | 23 +++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index 066549938..ef3374114 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -76,7 +76,7 @@ const FEMALE_TENOR_RATE = 0.89; // -2 semitones const FEMALE_GIANT_RATE = 0.79; // -4 semitones /** - * Language ids. The value for each language id must be a valid Scratch locale. + * Language ids. The value for each language id is a valid Scratch locale. */ const CHINESE_ID = 'zh-cn'; const DANISH_ID = 'da'; @@ -190,7 +190,24 @@ class Scratch3Text2SpeechBlocks { } /** - * An object with language names mapped to their language codes. + * An object with information for each language. + * + * A note on the different sets of locales referred to in this extension: + * + * SCRATCH LOCALE + * Set by the editor, and used to store the language state in the project. + * Listed in l10n: https://github.com/LLK/scratch-l10n/blob/master/src/supported-locales.js + * SUPPORTED LOCALE + * A Scratch locale that has a corresponding extension locale. + * EXTENSION LOCALE + * A locale corresponding to one of the available spoken languages + * in the extension. There can be multiple supported locales for a single + * extension locale. For example, for both written versions of chinese, + * zh-cn and zh-tw, we use a single spoken language (Mandarin). So there + * are two supported locales, with a single extension locale. + * SPEECH SYNTH LOCALE + * A different locale code system, used by our speech synthesis service. + * Each extension locale has a speech synth locale. */ get LANGUAGE_INFO () { return { @@ -450,7 +467,7 @@ class Scratch3Text2SpeechBlocks { /** * Get the language code currently set in the editor, or fall back to the * browser locale. - * @return {string} the language code. + * @return {string} a Scratch locale code. */ getEditorLanguage () { return formatMessage.setup().locale || From 423622daaae0fb6e2bb9a21ea2280640ab2b00a4 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Thu, 7 Mar 2019 16:33:20 -0500 Subject: [PATCH 15/19] Add unit tests --- test/unit/extension_text_to_speech.js | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 test/unit/extension_text_to_speech.js diff --git a/test/unit/extension_text_to_speech.js b/test/unit/extension_text_to_speech.js new file mode 100644 index 000000000..64061844f --- /dev/null +++ b/test/unit/extension_text_to_speech.js @@ -0,0 +1,30 @@ +const test = require('tap').test; +const TextToSpeech = require('../../src/extensions/scratch3_text2speech/index.js'); + +const fakeStage = { + textToSpeechLanguage: null +}; + +const fakeRuntime = { + getTargetForStage: () => fakeStage, + on: () => {} // Stub out listener methods used in constructor. +}; + +const ext = new TextToSpeech(fakeRuntime); + +test('if no language is saved in the project, use default', t => { + t.strictEqual(ext.getCurrentLanguage(), 'en'); + t.end(); +}); + +test('if an unsupported language is dropped onto the set language block, use default', t => { + ext.setLanguage({LANGUAGE: 'nope'}); + t.strictEqual(ext.getCurrentLanguage(), 'en'); + t.end(); +}); + +test('get the extension locale for a supported locale that differs', t => { + ext.setCurrentLanguage('ja-Hira'); + t.strictEqual(ext.getCurrentLanguage(), 'ja'); + t.end(); +}); From 9455112d7469a9e7ae4850a21904adfaefdbfee4 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Thu, 7 Mar 2019 17:00:40 -0500 Subject: [PATCH 16/19] improve variable name --- src/extensions/scratch3_text2speech/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index ef3374114..896e304bd 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -541,8 +541,8 @@ class Scratch3Text2SpeechBlocks { * @returns {Array} An array of locale strings. */ _getSupportedLocales () { - return Object.keys(this.LANGUAGE_INFO).reduce((acc, cur) => - acc.concat(this.LANGUAGE_INFO[cur].locales), []); + return Object.keys(this.LANGUAGE_INFO).reduce((acc, lang) => + acc.concat(this.LANGUAGE_INFO[lang].locales), []); } /** From 580e93d15ad300d76820c65032bd9dee0aa850fb Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Thu, 7 Mar 2019 17:00:48 -0500 Subject: [PATCH 17/19] fix norwegian id --- src/extensions/scratch3_text2speech/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index 896e304bd..db83d9505 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -89,7 +89,7 @@ const ICELANDIC_ID = 'is'; const ITALIAN_ID = 'it'; const JAPANESE_ID = 'ja'; const KOREAN_ID = 'ko'; -const NORWEGIAN_ID = 'no'; +const NORWEGIAN_ID = 'nb'; const POLISH_ID = 'pl'; const PORTUGUESE_BR_ID = 'pt-br'; const PORTUGUESE_ID = 'pt'; From a968dadb7a8b181a9a85e63441985318c5f7d00a Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Tue, 12 Mar 2019 10:54:30 -0400 Subject: [PATCH 18/19] Use opcode function in test --- test/unit/extension_text_to_speech.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/extension_text_to_speech.js b/test/unit/extension_text_to_speech.js index 64061844f..8c96ab802 100644 --- a/test/unit/extension_text_to_speech.js +++ b/test/unit/extension_text_to_speech.js @@ -24,7 +24,7 @@ test('if an unsupported language is dropped onto the set language block, use def }); test('get the extension locale for a supported locale that differs', t => { - ext.setCurrentLanguage('ja-Hira'); + ext.setLanguage({LANGUAGE: 'ja-Hira'}); t.strictEqual(ext.getCurrentLanguage(), 'ja'); t.end(); }); From bed0b05bc98e1e71f47e0197f2b9bb72bfa657c1 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Tue, 12 Mar 2019 14:29:39 -0400 Subject: [PATCH 19/19] Log error instead of returning null --- src/extensions/scratch3_text2speech/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index db83d9505..7bedcf580 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -512,7 +512,7 @@ class Scratch3Text2SpeechBlocks { /** * Get the extension locale for a supported locale, or null. * @param {string} locale a locale code. - * @returns {?string} a locale supported by the extension, or null. + * @returns {?string} a locale supported by the extension. */ _getExtensionLocaleForSupportedLocale (locale) { for (const lang in this.LANGUAGE_INFO) { @@ -520,7 +520,7 @@ class Scratch3Text2SpeechBlocks { return lang; } } - return null; + log.error(`cannot find extension locale for locale ${locale}`); } /**