From a8395543883b285d0a749384431624c157717584 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 16 Apr 2023 19:03:35 +0900 Subject: [PATCH 01/32] Update updateTranscript.js --- .../server/methods/updateTranscript.js | 92 ++++++++++++++++++- 1 file changed, 87 insertions(+), 5 deletions(-) diff --git a/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js b/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js index 3b1b7bf68cf9..c9e3b70136ed 100644 --- a/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js +++ b/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js @@ -2,13 +2,63 @@ import { check } from 'meteor/check'; import RedisPubSub from '/imports/startup/server/redis'; import { extractCredentials } from '/imports/api/common/server/helpers'; import Logger from '/imports/startup/server/logger'; +import axios from 'axios'; +import Users from '/imports/api/users'; + +const CAPTIONS_CONFIG = Meteor.settings.public.captions; + +function translateText (meetingId, userId, payload, dst) { + const REDIS_CONFIG = Meteor.settings.private.redis; + const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const EVENT_NAME = 'UpdateTranscriptPubMsg'; + + const { locale: src, transcript: textOri } = payload; + + if ( !CAPTIONS_CONFIG.enableAutomaticTranslation || textOri === "" || !dst || dst === "" || dst === src || dst.replace(/-.*$/,'') === src || dst === src.replace(/-.*$/,'') ) { + RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, payload); + } else { + let url = ''; + if (CAPTIONS_CONFIG.googleTranslateUrl) { + url = CAPTIONS_CONFIG.googleTranslateUrl + '/exec?' + + 'text=' + encodeURIComponent(textOri) + '&source=' + src + '&target=' + dst; + } else if (CAPTIONS_CONFIG.deeplTranslateUrl) { + url = CAPTIONS_CONFIG.deeplTranslateUrl + + '&text=' + encodeURIComponent(textOri) + '&source_lang=' + src.replace(/-.*$/,'').toUpperCase() + + '&target_lang=' + dst.toUpperCase(); + } else { + Logger.error('Could not get a translation service.'); + return; + } + + axios({ + method: 'get', + url, + responseType: 'json', + }).then((response) => { + if (CAPTIONS_CONFIG.googleTranslateUrl) { + const { code, text } = response.data; + if (code === 200) { + const newPayload = Object.assign({}, payload, {transcript: text}); + RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, newPayload); + } else { + Logger.error(`Failed to get Google translation for "${textOri}"`); + } + } else if (CAPTIONS_CONFIG.deeplTranslateUrl) { + const { translations } = response.data; + if (translations.length > 0 && translations[0].text) { + //sendToPad(padId, translations[0].text); + const newPayload = Object.assign({}, payload, {transcript: translations[0].text}); + RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, newPayload); + } else { + Logger.error(`Failed to get DeepL translation for "${textOri}"`); + } + } + }).catch((error) => Logger.error(`Could not get translation for ${textOri.trim()} on the locale ${dst}: ${error}`)); + } +} export default function updateTranscript(transcriptId, start, end, text, transcript, locale) { try { - const REDIS_CONFIG = Meteor.settings.private.redis; - const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; - const EVENT_NAME = 'UpdateTranscriptPubMsg'; - const { meetingId, requesterUserId } = extractCredentials(this.userId); check(meetingId, String); @@ -31,7 +81,39 @@ export default function updateTranscript(transcriptId, start, end, text, transcr locale, }; - RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); + const selector = { + meetingId, + userId: requesterUserId, + }; + + const fields = { + fields: { + translationLocale: 1, + prevTextOri: 1, + }, + }; + + const { translationLocale: dstLocale, prevTextOri = '' } = Users.findOne(selector, fields); + + if (payload.transcript != prevTextOri) { + const modifier = { + $set: { + prevTextOri: payload.transcript, + }, + }; + + try { + const numberAffected = Users.update(selector, modifier); + if (numberAffected) { + Logger.info(`Assigned user prevTextOri ${prevTextOri} id=${requesterUserId} meeting=${meetingId}`); + } + } catch (err) { + Logger.error(`Assigning user prevTextOri: ${err}`); + } + translateText(meetingId, requesterUserId, payload, dstLocale); + } else { + Logger.info(`Text ignored for caption: ${payload.transcript}`); + } } } catch (err) { Logger.error(`Exception while invoking method upadteTranscript ${err.stack}`); From d23696641d70ff6655882b768cb39fcc058495e8 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 16 Apr 2023 19:08:25 +0900 Subject: [PATCH 02/32] Create setTranslationLocale.js --- .../server/methods/setTranslationLocale.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 bigbluebutton-html5/imports/api/users/server/methods/setTranslationLocale.js diff --git a/bigbluebutton-html5/imports/api/users/server/methods/setTranslationLocale.js b/bigbluebutton-html5/imports/api/users/server/methods/setTranslationLocale.js new file mode 100644 index 000000000000..4d1530326e45 --- /dev/null +++ b/bigbluebutton-html5/imports/api/users/server/methods/setTranslationLocale.js @@ -0,0 +1,22 @@ +import { check } from 'meteor/check'; +import Logger from '/imports/startup/server/logger'; +import updateTranslationLocale from '../modifiers/updateTranslationLocale'; +import { extractCredentials } from '/imports/api/common/server/helpers'; + +const LANGUAGES = Meteor.settings.public.app.audioCaptions.language.available; + +export default function setTranslationLocale(locale) { + try { + const { meetingId, requesterUserId } = extractCredentials(this.userId); + + check(meetingId, String); + check(requesterUserId, String); + check(locale, String); + + if (LANGUAGES.includes(locale) || locale === '') { + updateTranslationLocale(meetingId, requesterUserId, locale); + } + } catch (err) { + Logger.error(`Exception while invoking method setTranslationLocale ${err.stack}`); + } +} From 28b7a68d019c7100b17e8342245557a2019367ee Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 16 Apr 2023 19:09:16 +0900 Subject: [PATCH 03/32] Update methods.js --- bigbluebutton-html5/imports/api/users/server/methods.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bigbluebutton-html5/imports/api/users/server/methods.js b/bigbluebutton-html5/imports/api/users/server/methods.js index 3d64c05d56f7..4f2344903136 100644 --- a/bigbluebutton-html5/imports/api/users/server/methods.js +++ b/bigbluebutton-html5/imports/api/users/server/methods.js @@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor'; import validateAuthToken from './methods/validateAuthToken'; import setEmojiStatus from './methods/setEmojiStatus'; import setSpeechLocale from './methods/setSpeechLocale'; +import setTranslationLocale from './methods/setTranslationLocale'; import setMobileUser from './methods/setMobileUser'; import assignPresenter from './methods/assignPresenter'; import changeRole from './methods/changeRole'; @@ -17,6 +18,7 @@ import setExitReason from './methods/setExitReason'; Meteor.methods({ setEmojiStatus, setSpeechLocale, + setTranslationLocale, setMobileUser, assignPresenter, changeRole, From 9b73c99e635c476485cfb5a84130835ec8b6b5c4 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 16 Apr 2023 19:45:48 +0900 Subject: [PATCH 04/32] Update addUser.js --- .../imports/api/users/server/modifiers/addUser.js | 1 + 1 file changed, 1 insertion(+) diff --git a/bigbluebutton-html5/imports/api/users/server/modifiers/addUser.js b/bigbluebutton-html5/imports/api/users/server/modifiers/addUser.js index b659e56d256f..f1b51fec7cc2 100755 --- a/bigbluebutton-html5/imports/api/users/server/modifiers/addUser.js +++ b/bigbluebutton-html5/imports/api/users/server/modifiers/addUser.js @@ -44,6 +44,7 @@ export default async function addUser(meetingId, userData) { meetingId, sortName: lowercaseTrim(user.name), speechLocale: '', + translationLocale: '', mobile: false, breakoutProps: { isBreakoutUser: Meeting.meetingProp.isBreakout, From 33c5e4127cac5fe3341514d36877c210a63c4018 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 16 Apr 2023 19:47:34 +0900 Subject: [PATCH 05/32] Create updateTranslationLocale.js --- .../modifiers/updateTranslationLocale.js | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 bigbluebutton-html5/imports/api/users/server/modifiers/updateTranslationLocale.js diff --git a/bigbluebutton-html5/imports/api/users/server/modifiers/updateTranslationLocale.js b/bigbluebutton-html5/imports/api/users/server/modifiers/updateTranslationLocale.js new file mode 100644 index 000000000000..f5bc7cab18a7 --- /dev/null +++ b/bigbluebutton-html5/imports/api/users/server/modifiers/updateTranslationLocale.js @@ -0,0 +1,25 @@ +import Logger from '/imports/startup/server/logger'; +import Users from '/imports/api/users'; + +export default function updateTranslationLocale(meetingId, userId, locale) { + const selector = { + meetingId, + userId, + }; + + const modifier = { + $set: { + translationLocale: locale, + }, + }; + + try { + const numberAffected = Users.update(selector, modifier); + + if (numberAffected) { + Logger.info(`Updated translation locale=${locale} userId=${userId} meetingId=${meetingId}`); + } + } catch (err) { + Logger.error(`Updating translation locale: ${err}`); + } +} From 46dcb475849e66675beb635e10e9e63b21c00c43 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 16 Apr 2023 19:49:00 +0900 Subject: [PATCH 06/32] Update addUserPersistentData.js --- .../server/modifiers/addUserPersistentData.js | 1 + 1 file changed, 1 insertion(+) diff --git a/bigbluebutton-html5/imports/api/users-persistent-data/server/modifiers/addUserPersistentData.js b/bigbluebutton-html5/imports/api/users-persistent-data/server/modifiers/addUserPersistentData.js index 71288d6ae551..e404a6fe7800 100644 --- a/bigbluebutton-html5/imports/api/users-persistent-data/server/modifiers/addUserPersistentData.js +++ b/bigbluebutton-html5/imports/api/users-persistent-data/server/modifiers/addUserPersistentData.js @@ -8,6 +8,7 @@ export default async function addUserPersistentData(user) { sortName: String, color: String, speechLocale: String, + translationLocale: String, mobile: Boolean, breakoutProps: Object, inactivityCheck: Boolean, From 5fe5a7728a7dfac6d78390855ab472bbdd611e23 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 16 Apr 2023 19:52:26 +0900 Subject: [PATCH 07/32] Show caption button for mobile users (need revise) --- .../imports/ui/components/actions-bar/component.jsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx index d0e1d262ff8a..89a134fd2f72 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx @@ -74,11 +74,7 @@ class ActionsBar extends PureComponent { ) : null} - { !deviceInfo.isMobile - ? ( - - ) - : null } + From 167744b682bb7104f7fc997c097c5d90e027496e Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 16 Apr 2023 19:59:16 +0900 Subject: [PATCH 08/32] Update component.jsx --- .../audio/captions/button/component.jsx | 118 +++++++++++++----- 1 file changed, 88 insertions(+), 30 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/audio/captions/button/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/captions/button/component.jsx index d87503540c56..1eeb35d0e332 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/captions/button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/captions/button/component.jsx @@ -24,12 +24,22 @@ const intlMessages = defineMessages({ id: 'app.audio.captions.button.transcription', description: 'Audio speech transcription label', }, + translation: { + id: 'app.audio.captions.button.translation', + description: 'Audio speech translation label', + }, transcriptionOn: { id: 'app.switch.onLabel', }, transcriptionOff: { id: 'app.switch.offLabel', }, + translationOn: { + id: 'app.switch.onLabel', + }, + translationOff: { + id: 'app.switch.offLabel', + }, language: { id: 'app.audio.captions.button.language', description: 'Audio speech recognition language label', @@ -85,7 +95,9 @@ const CaptionsButton = ({ isRTL, enabled, currentSpeechLocale, + currentTranslationLocale, availableVoices, + availableTranslations, isSupported, isVoiceUser, }) => { @@ -97,12 +109,18 @@ const CaptionsButton = ({ ? navigator.language : DEFAULT_LOCALE; const getSelectedLocaleValue = (isTranscriptionDisabled() ? fallbackLocale : currentSpeechLocale); + const getSelectedTranslationLocaleValue = (isTranslationDisabled() ? fallbackLocale : currentTranslationLocale); const selectedLocale = useRef(getSelectedLocaleValue); + const selectedTranslationLocale = useRef(getSelectedTranslationLocaleValue); useEffect(() => { if (!isTranscriptionDisabled()) selectedLocale.current = getSelectedLocaleValue; }, [currentSpeechLocale]); + + useEffect(() => { + if (!isTranslationDisabled()) selectedTranslationLocale.current = getSelectedTranslationLocaleValue; + }, [currentTranslationLocale]); if (!enabled) return null; @@ -115,9 +133,8 @@ const CaptionsButton = ({ label: intl.formatMessage(intlMessages[availableVoice]), key: availableVoice, iconRight: selectedLocale.current === availableVoice ? 'check' : null, - customStyles: (selectedLocale.current === availableVoice) && Styled.SelectedLabel, + customStyles: (selectedLocale.current === availableVoice) ? Styled.SelectedLabel : Styled.NormalLabel, disabled: isTranscriptionDisabled(), - dividerTop: availableVoice === availableVoices[0], onClick: () => { selectedLocale.current = availableVoice; SpeechService.setSpeechLocale(selectedLocale.current); @@ -125,39 +142,79 @@ const CaptionsButton = ({ } )) ); + + const getAvailableTranslationLocales = () => ( + availableTranslations.map((availableTranslation) => ( + { + icon: '', + label: intl.formatMessage(intlMessages[availableTranslation]), + key: availableTranslation, + iconRight: selectedTranslationLocale.current === availableTranslation ? 'check' : null, + customStyles: (selectedTranslationLocale.current === availableTranslation) ? Styled.SelectedLabel : Styled.NormalLabel, + disabled: isTranscriptionDisabled() || isTranslationDisabled(), + onClick: () => { + selectedTranslationLocale.current = availableTranslation; + SpeechService.setTranslationLocale(selectedTranslationLocale.current); + }, + } + )) + ); const toggleTranscription = () => { SpeechService.setSpeechLocale(isTranscriptionDisabled() ? selectedLocale.current : DISABLED); }; - const getAvailableLocalesList = () => ( - [{ - key: 'availableLocalesList', - label: intl.formatMessage(intlMessages.language), - customStyles: Styled.TitleLabel, - disabled: true, - dividerTop: false, - }, - ...getAvailableLocales(), - { - key: 'divider', - label: intl.formatMessage(intlMessages.transcription), - customStyles: Styled.TitleLabel, - disabled: true, - }, { - key: 'transcriptionStatus', - label: intl.formatMessage( - isTranscriptionDisabled() - ? intlMessages.transcriptionOn - : intlMessages.transcriptionOff, - ), - customStyles: isTranscriptionDisabled() - ? Styled.EnableTrascription : Styled.DisableTrascription, - disabled: false, - dividerTop: true, - onClick: toggleTranscription, - }] - ); + const toggleTranslation = () => { + SpeechService.setTranslationLocale(isTranslationDisabled() ? selectedTranslationLocale.current : DISABLED); + }; + + const getAvailableLocalesList = () => { + let items = [{ + key: 'divider', + label: intl.formatMessage(intlMessages.transcription), + customStyles: Styled.TitleLabel, + disabled: true, + }, + ...getAvailableLocales(), + { + key: 'transcriptionStatus', + label: intl.formatMessage( + isTranscriptionDisabled() + ? intlMessages.transcriptionOn + : intlMessages.transcriptionOff, + ), + customStyles: isTranscriptionDisabled() + ? Styled.EnableTrascription : Styled.DisableTrascription, + disabled: false, + onClick: toggleTranscription, + }]; + + if (isTranslationActivated()) { + items = items.concat([ + { + key: 'divider', + label: intl.formatMessage(intlMessages.translation), + customStyles: Styled.TitleLabel, + disabled: true, + dividerTop: true, + }, + ...getAvailableTranslationLocales(), + { + key: 'translationStatus', + label: intl.formatMessage( + isTranslationDisabled() + ? intlMessages.translationOn + : intlMessages.translationOff, + ), + customStyles: isTranslationDisabled() + ? Styled.EnableTraslation : Styled.DisableTraslation, + disabled: isTranscriptionDisabled(), + onClick: toggleTranslation, + } + ]); + } + return items; + }; const onToggleClick = (e) => { e.stopPropagation(); @@ -219,6 +276,7 @@ CaptionsButton.propTypes = { isRTL: PropTypes.bool.isRequired, enabled: PropTypes.bool.isRequired, currentSpeechLocale: PropTypes.string.isRequired, + currentTranslationLocale: PropTypes.string.isRequired, availableVoices: PropTypes.arrayOf(PropTypes.string).isRequired, isSupported: PropTypes.bool.isRequired, isVoiceUser: PropTypes.bool.isRequired, From 78925b6083e9b35a9b0cd8ab4905f2211b5fb7b4 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 16 Apr 2023 20:00:23 +0900 Subject: [PATCH 09/32] Update container.jsx --- .../imports/ui/components/audio/captions/button/container.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bigbluebutton-html5/imports/ui/components/audio/captions/button/container.jsx b/bigbluebutton-html5/imports/ui/components/audio/captions/button/container.jsx index 44a08b88c9a7..f9eb1caa14b8 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/captions/button/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/captions/button/container.jsx @@ -10,7 +10,9 @@ const Container = (props) =>