Merge pull request from ericrosenbaum/bugfix/localize-tts-languages

Localize the Text to Speech extension "set language" menu
This commit is contained in:
Eric Rosenbaum 2019-11-18 16:22:15 -05:00 committed by GitHub
commit 24116e5514
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 2507 additions and 2447 deletions
package-lock.jsonpackage.json
src/extensions/scratch3_text2speech
test/unit

4876
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -44,7 +44,7 @@
"nets": "3.2.0",
"scratch-parser": "5.0.0",
"scratch-sb1-converter": "0.2.7",
"scratch-translate-extension-languages": "0.0.20190416132834",
"scratch-translate-extension-languages": "0.0.20191118205314",
"socket.io-client": "2.0.4",
"text-encoding": "0.7.0",
"worker-loader": "^1.1.1"

View file

@ -1,5 +1,6 @@
const formatMessage = require('format-message');
const nets = require('nets');
const languageNames = require('scratch-translate-extension-languages');
const ArgumentType = require('../../extension-support/argument-type');
const BlockType = require('../../extension-support/block-type');
@ -267,7 +268,7 @@ class Scratch3Text2SpeechBlocks {
},
[JAPANESE_ID]: {
name: 'Japanese',
locales: ['ja', 'ja-Hira'],
locales: ['ja', 'ja-hira'],
speechSynthLocale: 'ja-JP'
},
[KOREAN_ID]: {
@ -487,8 +488,9 @@ class Scratch3Text2SpeechBlocks {
* @return {string} a Scratch locale code.
*/
getEditorLanguage () {
return formatMessage.setup().locale ||
const locale = formatMessage.setup().locale ||
navigator.language || navigator.userLanguage || this.DEFAULT_LANGUAGE;
return locale.toLowerCase();
}
/**
@ -518,6 +520,15 @@ class Scratch3Text2SpeechBlocks {
stage.textToSpeechLanguage = this._getExtensionLocaleForSupportedLocale(locale);
}
// Support language names dropped onto the menu via reporter block
// such as a variable containing a language name (in any language),
// or the translate extension's language reporter.
const localeForDroppedName = languageNames.nameMap[locale.toLowerCase()];
if (localeForDroppedName && this.isSupportedLanguage(localeForDroppedName)) {
stage.textToSpeechLanguage =
this._getExtensionLocaleForSupportedLocale(localeForDroppedName);
}
// 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.
@ -584,14 +595,49 @@ class Scratch3Text2SpeechBlocks {
}
/**
* Get the menu of languages for the "set language" block.
* Get the localized menu of languages for the "set language" block.
* For each language:
* if there is a custom translated spoken language name, use that;
* otherwise use the translation in the languageNames menuMap;
* otherwise fall back to the untranslated name in LANGUAGE_INFO.
* @return {array} the text and value for each menu item.
*/
getLanguageMenu () {
return Object.keys(this.LANGUAGE_INFO).map(key => ({
text: this.LANGUAGE_INFO[key].name,
value: key
}));
const editorLanguage = this.getEditorLanguage();
// Get the array of localized language names
const localizedNameMap = {};
let nameArray = languageNames.menuMap[editorLanguage];
if (nameArray) {
// Also get any localized names of spoken languages
let spokenNameArray = [];
if (languageNames.spokenLanguages) {
spokenNameArray = languageNames.spokenLanguages[editorLanguage];
nameArray = nameArray.concat(spokenNameArray);
}
// Create a map of language code to localized name
// The localized spoken language names have been concatenated onto
// the end of the name array, so the result of the forEach below is
// when there is both a written language name (e.g. 'Chinese
// (simplified)') and a spoken language name (e.g. 'Chinese
// (Mandarin)', we always use the spoken version.
nameArray.forEach(lang => {
localizedNameMap[lang.code] = lang.name;
});
}
return Object.keys(this.LANGUAGE_INFO).map(key => {
let name = this.LANGUAGE_INFO[key].name;
const localizedName = localizedNameMap[key];
if (localizedName) {
name = localizedName;
}
// Uppercase the first character of the name
name = name.charAt(0).toUpperCase() + name.slice(1);
return {
text: name,
value: key
};
});
}
/**

View file

@ -23,8 +23,22 @@ test('if an unsupported language is dropped onto the set language block, use def
t.end();
});
test('if a supported language name is dropped onto the set language block, use it', t => {
ext.setLanguage({LANGUAGE: 'español'});
t.strictEqual(ext.getCurrentLanguage(), 'es');
t.end();
});
test('get the extension locale for a supported locale that differs', t => {
ext.setLanguage({LANGUAGE: 'ja-Hira'});
ext.setLanguage({LANGUAGE: 'ja-hira'});
t.strictEqual(ext.getCurrentLanguage(), 'ja');
t.end();
});
test('use localized spoken language name in place of localized written language name', t => {
ext.getEditorLanguage = () => 'es';
const languageMenu = ext.getLanguageMenu();
const localizedNameForChineseInSpanish = languageMenu.find(el => el.value === 'zh-cn').text;
t.strictEqual(localizedNameForChineseInSpanish, 'Chino (Mandarín)'); // i.e. should not be 'Chino (simplificado)'
t.end();
});