From c2bf0f4a52be114756c5f05468da8c2e1c7ed7a5 Mon Sep 17 00:00:00 2001 From: Quang Lam Date: Sun, 19 Dec 2021 18:48:24 -0500 Subject: [PATCH] Add option to set voice (TTS) speed --- main-src/libs/locales/de/ui.json | 8 +- main-src/libs/locales/en/ui.json | 6 +- main-src/libs/locales/es/ui.json | 8 +- main-src/libs/locales/fr/ui.json | 8 +- main-src/libs/locales/it/ui.json | 6 +- main-src/libs/locales/ja/ui.json | 8 +- main-src/libs/locales/ko/ui.json | 8 +- main-src/libs/locales/pt-PT/ui.json | 8 +- main-src/libs/locales/ru/ui.json | 8 +- main-src/libs/locales/vi/ui.json | 6 +- main-src/libs/locales/zh-CN/ui.json | 8 +- main-src/libs/preferences.js | 2 + src/components/pages/preferences/index.js | 73 ++++++------ .../pages/preferences/list-item-sliders.js | 112 ++++++++++++++++++ .../pages/home/text-to-speech/actions.js | 8 +- 15 files changed, 221 insertions(+), 56 deletions(-) create mode 100644 src/components/pages/preferences/list-item-sliders.js diff --git a/main-src/libs/locales/de/ui.json b/main-src/libs/locales/de/ui.json index f8738eea..04664eda 100644 --- a/main-src/libs/locales/de/ui.json +++ b/main-src/libs/locales/de/ui.json @@ -137,6 +137,10 @@ "allowTelemetry": "Erlaube der App anonyme Nutzungsdaten zu senden", "share": "Teilen", "extensions": "Erweiterungen", - "extensionDesc": "Select text with your mouse and translate with just one click.", - "privacyPolicy": "Privacy Policy" + "extensionDesc": "Wählen Sie den Text mit der Maus aus und übersetzen Sie ihn mit nur einem Klick.", + "privacyPolicy": "Datenschutzrichtlinien", + "voiceSpeed": "Sprachgeschwindigkeit", + "slowest": "Langsamste", + "normal": "Normal", + "fastest": "Schnellste" } diff --git a/main-src/libs/locales/en/ui.json b/main-src/libs/locales/en/ui.json index 0a1636ff..b968ea3e 100644 --- a/main-src/libs/locales/en/ui.json +++ b/main-src/libs/locales/en/ui.json @@ -138,5 +138,9 @@ "share": "Share", "extensions": "Extensions", "extensionDesc": "Select text with your mouse and translate with just one click.", - "privacyPolicy": "Privacy Policy" + "privacyPolicy": "Privacy Policy", + "voiceSpeed": "Voice speed", + "slowest": "Slowest", + "normal": "Normal", + "fastest": "Fastest" } diff --git a/main-src/libs/locales/es/ui.json b/main-src/libs/locales/es/ui.json index aff54b5d..0748f3e5 100644 --- a/main-src/libs/locales/es/ui.json +++ b/main-src/libs/locales/es/ui.json @@ -137,6 +137,10 @@ "allowTelemetry": "Permitir que la aplicación envíe datos de uso anónimos", "share": "Compartir", "extensions": "Extensiones", - "extensionDesc": "Select text with your mouse and translate with just one click.", - "privacyPolicy": "Privacy Policy" + "extensionDesc": "Seleccione el texto con el ratón y traduzca con un solo clic.", + "privacyPolicy": "Política de Privacidad", + "voiceSpeed": "Velocidad de voz", + "slowest": "Muy lento", + "normal": "Normal", + "fastest": "Lo más rápido" } diff --git a/main-src/libs/locales/fr/ui.json b/main-src/libs/locales/fr/ui.json index 3b7783f1..872ca321 100644 --- a/main-src/libs/locales/fr/ui.json +++ b/main-src/libs/locales/fr/ui.json @@ -137,6 +137,10 @@ "allowTelemetry": "Autoriser l'application à envoyer des données d'utilisation anonymes", "share": "Partager", "extensions": "Extensions", - "extensionDesc": "Select text with your mouse and translate with just one click.", - "privacyPolicy": "Privacy Policy" + "extensionDesc": "Sélectionnez le texte avec votre souris et traduisez en un seul clic.", + "privacyPolicy": "Politique de Confidentialité", + "voiceSpeed": "Vitesse de la voix", + "slowest": "Le plus lent", + "normal": "Normale", + "fastest": "Le plus rapide" } diff --git a/main-src/libs/locales/it/ui.json b/main-src/libs/locales/it/ui.json index 6bcf8fcd..9fbf2c6a 100644 --- a/main-src/libs/locales/it/ui.json +++ b/main-src/libs/locales/it/ui.json @@ -138,5 +138,9 @@ "share": "Condividi", "extensions": "Estensioni", "extensionDesc": "Seleziona il testo con il mouse e traduci con un solo clic.", - "privacyPolicy": "Privacy Policy" + "privacyPolicy": "Politica sulla Privacy", + "voiceSpeed": "Velocità vocale", + "slowest": "Più lenta", + "normal": "Normali", + "fastest": "Più Veloce" } diff --git a/main-src/libs/locales/ja/ui.json b/main-src/libs/locales/ja/ui.json index 8bdfe403..11cac1a7 100644 --- a/main-src/libs/locales/ja/ui.json +++ b/main-src/libs/locales/ja/ui.json @@ -137,6 +137,10 @@ "allowTelemetry": "匿名の利用状況データの送信をアプリに許可する", "share": "シェアする", "extensions": "拡張機能", - "extensionDesc": "Select text with your mouse and translate with just one click.", - "privacyPolicy": "Privacy Policy" + "extensionDesc": "マウスでテキストを選択し、ワンクリックで翻訳します。", + "privacyPolicy": "プライバシーポリシー", + "voiceSpeed": "音声スピード", + "slowest": "最も低速", + "normal": "普通", + "fastest": "最速" } diff --git a/main-src/libs/locales/ko/ui.json b/main-src/libs/locales/ko/ui.json index f599afe4..937eee94 100644 --- a/main-src/libs/locales/ko/ui.json +++ b/main-src/libs/locales/ko/ui.json @@ -137,6 +137,10 @@ "allowTelemetry": "앱이 익명의 사용 데이터를 보내도록 허용", "share": "공유", "extensions": "확장 프로그램", - "extensionDesc": "Select text with your mouse and translate with just one click.", - "privacyPolicy": "Privacy Policy" + "extensionDesc": "마우스로 텍스트를 선택하고 클릭 한 번으로 번역하세요.", + "privacyPolicy": "개인정보 취급방침", + "voiceSpeed": "음성 속도", + "slowest": "가장 느리게", + "normal": "보통", + "fastest": "가장 빠르게" } diff --git a/main-src/libs/locales/pt-PT/ui.json b/main-src/libs/locales/pt-PT/ui.json index 4383a138..ef7f6370 100644 --- a/main-src/libs/locales/pt-PT/ui.json +++ b/main-src/libs/locales/pt-PT/ui.json @@ -137,6 +137,10 @@ "allowTelemetry": "Permitir que a aplicação envie dados de utilização anónimos", "share": "Compartilhar", "extensions": "Extensões", - "extensionDesc": "Select text with your mouse and translate with just one click.", - "privacyPolicy": "Privacy Policy" + "extensionDesc": "Selecione texto com o seu mouse e traduza com apenas um clique.", + "privacyPolicy": "Política de Privacidade", + "voiceSpeed": "Velocidade de voz", + "slowest": "Mais lento", + "normal": "Normal", + "fastest": "Mais rápida" } diff --git a/main-src/libs/locales/ru/ui.json b/main-src/libs/locales/ru/ui.json index 584506d7..54d6e6e9 100644 --- a/main-src/libs/locales/ru/ui.json +++ b/main-src/libs/locales/ru/ui.json @@ -137,6 +137,10 @@ "allowTelemetry": "Разрешить приложению отправлять анонимные данные об использовании", "share": "Поделиться", "extensions": "Расширения", - "extensionDesc": "Select text with your mouse and translate with just one click.", - "privacyPolicy": "Privacy Policy" + "extensionDesc": "Выбирайте текст мышкой и переводите одним кликом.", + "privacyPolicy": "Политика приватности", + "voiceSpeed": "Скорость голоса", + "slowest": "Медленнейш", + "normal": "Норм", + "fastest": "Минимум" } diff --git a/main-src/libs/locales/vi/ui.json b/main-src/libs/locales/vi/ui.json index 068afc74..d56d4e1a 100644 --- a/main-src/libs/locales/vi/ui.json +++ b/main-src/libs/locales/vi/ui.json @@ -138,5 +138,9 @@ "share": "Chia sẻ", "extensions": "Tiện ích mở rộng", "extensionDesc": "Chọn văn bản bằng chuột và dịch chỉ với một cú nhấp chuột.", - "privacyPolicy": "Chính sách bảo mật" + "privacyPolicy": "Chính sách bảo mật", + "voiceSpeed": "Tốc độ giọng nói", + "slowest": "Chậm nhất", + "normal": "Bình thường", + "fastest": "Nhanh nhất" } diff --git a/main-src/libs/locales/zh-CN/ui.json b/main-src/libs/locales/zh-CN/ui.json index be491e8d..f226ea2a 100644 --- a/main-src/libs/locales/zh-CN/ui.json +++ b/main-src/libs/locales/zh-CN/ui.json @@ -137,6 +137,10 @@ "allowTelemetry": "允许该应用程序发送匿名使用情况数据", "share": "分享", "extensions": "扩展程序", - "extensionDesc": "Select text with your mouse and translate with just one click.", - "privacyPolicy": "Privacy Policy" + "extensionDesc": "用鼠标选择文本并只需点击一下即可翻译。", + "privacyPolicy": "隐私政策", + "voiceSpeed": "语音速度", + "slowest": "最慢", + "normal": "正常", + "fastest": "最快" } diff --git a/main-src/libs/preferences.js b/main-src/libs/preferences.js index cee083e9..357dd974 100644 --- a/main-src/libs/preferences.js +++ b/main-src/libs/preferences.js @@ -54,6 +54,8 @@ const defaultPreferences = { translateClipboardOnShortcut: false, translateWhenPressingEnter: true, useHardwareAcceleration: true, + // 10 => 1x (speed goes from 0.1x to 10x, we multiple by 10 to avoid using decimals) + voiceSpeed: 10, }; const initCachedPreferences = () => { diff --git a/src/components/pages/preferences/index.js b/src/components/pages/preferences/index.js index f68b209e..a89f381a 100644 --- a/src/components/pages/preferences/index.js +++ b/src/components/pages/preferences/index.js @@ -28,6 +28,7 @@ import { open as openDialogShortcut } from '../../../state/pages/preferences/sho import { open as openDialogOpenSourceNotices } from '../../../state/root/dialog-open-source-notices/actions'; import DialogShortcut from './dialog-shortcut'; +import ListItemSliders from './list-item-sliders'; import { requestOpenInBrowser, @@ -274,44 +275,11 @@ const Preferences = (props) => { /> + + - {/* extensions are not supported on Linux because it doesn't support protocol translatium:// */} - {window.process.platform !== 'linux' && ( - <> - - {getLocale('extensions')} - - - - window.remote.shell.openExternal('https://webcatalog.io/translatium/extensions/')} - > -
-
- PopClip - Google Chrome - Microsoft Edge - Safari - Firefox - Opera - Brave - Vivaldi -
- -
- -
-
-
- - )} - {getLocale('menubar')} @@ -384,6 +352,41 @@ const Preferences = (props) => { + {/* extensions are not supported on Linux because it doesn't support protocol translatium:// */} + {window.process.platform !== 'linux' && ( + <> + + {getLocale('extensions')} + + + + window.remote.shell.openExternal('https://webcatalog.io/translatium/extensions/')} + > +
+
+ PopClip + Google Chrome + Microsoft Edge + Safari + Firefox + Opera + Brave + Vivaldi +
+ +
+ +
+
+
+ + )} + {getLocale('advanced')} diff --git a/src/components/pages/preferences/list-item-sliders.js b/src/components/pages/preferences/list-item-sliders.js new file mode 100644 index 00000000..f7c0f386 --- /dev/null +++ b/src/components/pages/preferences/list-item-sliders.js @@ -0,0 +1,112 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import React from 'react'; +import PropTypes from 'prop-types'; + +import Grid from '@material-ui/core/Grid'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemText from '@material-ui/core/ListItemText'; +import Slider from '@material-ui/core/Slider'; +import Typography from '@material-ui/core/Typography'; + +import connectComponent from '../../../helpers/connect-component'; +import getLocale from '../../../helpers/get-locale'; + +import { + requestSetPreference, +} from '../../../senders'; + +const styles = (theme) => ({ + sliderContainer: { + paddingTop: theme.spacing(2), + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(5), + }, + sliderTitleContainer: { + paddingTop: `${theme.spacing(1.5)}px !important`, + width: 160, + }, + sliderMarkLabel: { + fontSize: '0.75rem', + }, +}); + +// Well, math +// we use different scaling system for the slider to make it look nice +const convertSliderValToPreferenceVal = (val) => { + if (val >= 0) return val + 10; // 0 -> 10, 30 -> 40 + return 10 + (val * 2) / 10; // -25 => 5 +}; + +const convertPreferenceValToSliderVal = (val) => { + if (val >= 10) return val - 10; // 10 -> 0, 40 -> 30 + return (val - 10) * 5; // 5 -> -25 +}; + +const ListItemSliders = ({ + classes, + voiceSpeed, +}) => ( + + + + + + {getLocale('voiceSpeed')} + + + + { + if (val < 0) return `${1 + (val * 2) / 100}x`; + return `${(val + 10) / 10}x`; + }} + marks={[ + { + value: -25, + label: getLocale('slowest'), + }, + { + value: 0, + label: getLocale('normal'), + }, + { + value: 40, + label: getLocale('fastest'), + }, + ]} + min={-25} + max={40} + onChange={(e, value) => { + requestSetPreference('voiceSpeed', convertSliderValToPreferenceVal(value)); + }} + /> + + + + +); + +ListItemSliders.propTypes = { + classes: PropTypes.object.isRequired, + voiceSpeed: PropTypes.number.isRequired, +}; + +const mapStateToProps = (state) => ({ + voiceSpeed: state.preferences.voiceSpeed, +}); + +export default connectComponent( + ListItemSliders, + mapStateToProps, + null, + styles, +); diff --git a/src/state/pages/home/text-to-speech/actions.js b/src/state/pages/home/text-to-speech/actions.js index c3123d1b..4d425d1b 100644 --- a/src/state/pages/home/text-to-speech/actions.js +++ b/src/state/pages/home/text-to-speech/actions.js @@ -35,7 +35,7 @@ const preloadGoogleTTSAsync = (lang, text, idx, total) => () => { }; // max length 100 characters -const playGoogleTTSAsync = (lang, text, idx, total) => () => { +const playGoogleTTSAsync = (lang, text, idx, total) => (_, getState) => { const uri = encodeURI(`https://translate.google.com/translate_tts?ie=UTF-8&q=${text}&tl=${lang}&total=${total}&idx=${idx}&textlen=8&client=dict-chrome-ex&prev=input`); const opts = { @@ -56,6 +56,8 @@ const playGoogleTTSAsync = (lang, text, idx, total) => () => { const blob = new window.Blob([buffer], { type: 'audio/mp3' }); const url = window.URL.createObjectURL(blob); player = new window.Audio(url); + const { voiceSpeed } = getState().preferences; + player.playbackRate = voiceSpeed / 10; return new Promise((resolve, reject) => { player.play(); player.onended = () => resolve(); @@ -67,7 +69,7 @@ const playGoogleTTSAsync = (lang, text, idx, total) => () => { }); }; -export const startTextToSpeech = (textToSpeechLang, textToSpeechText) => ((dispatch) => { +export const startTextToSpeech = (textToSpeechLang, textToSpeechText) => ((dispatch, getState) => { if (textToSpeechText.length < 1) return; currentTaskId = Date.now(); @@ -89,6 +91,8 @@ export const startTextToSpeech = (textToSpeechLang, textToSpeechText) => ((dispa const utterThis = new window.SpeechSynthesisUtterance(textToSpeechText); utterThis.voice = voice; + const { voiceSpeed } = getState().preferences; + utterThis.rate = voiceSpeed / 10; utterThis.onend = () => { dispatch({ type: END_TEXT_TO_SPEECH }); };