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) => ;
export default withTracker(() => {
const isRTL = document.documentElement.getAttribute('dir') === 'rtl';
const availableVoices = SpeechService.getSpeechVoices();
+ const availableTranslations = SpeechService.getTranslations();
const currentSpeechLocale = SpeechService.getSpeechLocale();
+ const currentTranslationLocale = SpeechService.getTranslationLocale();
const isSupported = availableVoices.length > 0;
const isVoiceUser = AudioService.isVoiceUser();
return {
@@ -18,7 +20,9 @@ export default withTracker(() => {
enabled: Service.hasAudioCaptions(),
active: Service.getAudioCaptions(),
currentSpeechLocale,
+ currentTranslationLocale,
availableVoices,
+ availableTranslations,
isSupported,
isVoiceUser,
};
From 2ad9dbfea92cc05faeab176ca93a83bf7598ee12 Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sun, 16 Apr 2023 20:02:55 +0900
Subject: [PATCH 10/32] Update styles.js
---
.../audio/captions/button/styles.js | 27 +++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/bigbluebutton-html5/imports/ui/components/audio/captions/button/styles.js b/bigbluebutton-html5/imports/ui/components/audio/captions/button/styles.js
index d82aaf7f2c24..b1c5fc35cb82 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/captions/button/styles.js
+++ b/bigbluebutton-html5/imports/ui/components/audio/captions/button/styles.js
@@ -32,6 +32,12 @@ const TranscriptionToggle = styled(Toggle)`
padding-left: 1em;
`;
+const TranslationToggle = styled(Toggle)`
+ display: flex;
+ justify-content: flex-start;
+ padding-left: 1em;
+`;
+
const TitleLabel = {
fontWeight: 'bold',
opacity: 1,
@@ -39,23 +45,44 @@ const TitleLabel = {
const EnableTrascription = {
color: colorSuccess,
+ textIndent: '1em',
};
const DisableTrascription = {
color: colorDangerDark,
+ textIndent: '1em',
+};
+
+const EnableTraslation = {
+ color: colorSuccess,
+ textIndent: '1em',
+};
+
+const DisableTraslation = {
+ color: colorDangerDark,
+ textIndent: '1em',
+};
+
+const NormalLabel = {
+ textIndent: '1em',
};
const SelectedLabel = {
color: colorPrimary,
backgroundColor: colorOffWhite,
+ textIndent: '1em',
};
export default {
ClosedCaptionToggleButton,
SpanButtonWrapper,
TranscriptionToggle,
+ TranslationToggle,
TitleLabel,
EnableTrascription,
DisableTrascription,
+ EnableTraslation,
+ DisableTraslation,
+ NormalLabel,
SelectedLabel,
};
From 5bb08f5e68ef3229f302caeae5a2d80af6c97a5a Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sun, 16 Apr 2023 20:09:33 +0900
Subject: [PATCH 11/32] Update service.js
---
.../audio/captions/speech/service.js | 35 +++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js b/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js
index 93d62e387011..7eac27c7db53 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js
+++ b/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js
@@ -27,6 +27,12 @@ const setSpeechVoices = () => {
Session.set('speechVoices', _.uniq(window.speechSynthesis.getVoices().map((v) => v.lang)));
};
+const setTranslations = () => {
+ if (!hasSpeechRecognitionSupport()) return;
+ //For now the same items as transcription, but they can be different and are dependent on translation services.
+ Session.set('translations', _.uniq(window.speechSynthesis.getVoices().map((v) => v.lang)));
+};
+
// Trigger getVoices
setSpeechVoices();
@@ -36,6 +42,12 @@ const getSpeechVoices = () => {
return voices.filter((v) => LANGUAGES.includes(v));
};
+const getTranslations = () => {
+ const voices = Session.get('translations') || [];
+
+ return voices.filter((v) => LANGUAGES.includes(v));
+};
+
const setSpeechLocale = (value) => {
const voices = getSpeechVoices();
if (voices.includes(value) || value === '') {
@@ -47,6 +59,17 @@ const setSpeechLocale = (value) => {
}
};
+const setTranslationLocale = (value) => {
+ const voices = getTranslations();
+ if (voices.includes(value) || value === '') {
+ makeCall('setTranslationLocale', value);
+ } else {
+ logger.error({
+ logCode: 'captions_translation_locale',
+ }, 'Captions translation set locale error');
+ }
+};
+
const useFixedLocale = () => isEnabled() && CONFIG.language.forceLocale;
const initSpeechRecognition = () => {
@@ -54,6 +77,7 @@ const initSpeechRecognition = () => {
if (hasSpeechRecognitionSupport()) {
// Effectivate getVoices
setSpeechVoices();
+ setTranslations();
const speechRecognition = new SpeechRecognitionAPI();
speechRecognition.continuous = true;
speechRecognition.interimResults = true;
@@ -122,6 +146,14 @@ const getSpeechLocale = (userId = Auth.userID) => {
return '';
};
+const getTranslationLocale = (userId = Auth.userID) => {
+ const user = Users.findOne({ userId }, { fields: { translationLocale: 1 } });
+
+ if (user) return user.translationLocale;
+
+ return '';
+};
+
const hasSpeechLocale = (userId = Auth.userID) => getSpeechLocale(userId) !== '';
const isLocaleValid = (locale) => LANGUAGES.includes(locale);
@@ -162,8 +194,11 @@ export default {
updateInterimTranscript,
updateFinalTranscript,
getSpeechVoices,
+ getTranslations,
getSpeechLocale,
+ getTranslationLocale,
setSpeechLocale,
+ setTranslationLocale,
hasSpeechLocale,
isLocaleValid,
isEnabled,
From bcb36aedf1579a4b171924bd487e16f7267e5787 Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sun, 16 Apr 2023 20:10:25 +0900
Subject: [PATCH 12/32] Update en.json
---
bigbluebutton-html5/public/locales/en.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/bigbluebutton-html5/public/locales/en.json b/bigbluebutton-html5/public/locales/en.json
index 2df3b436488a..e65ad99e4eea 100755
--- a/bigbluebutton-html5/public/locales/en.json
+++ b/bigbluebutton-html5/public/locales/en.json
@@ -713,6 +713,7 @@
"app.audio.captions.button.stop": "Stop closed captions",
"app.audio.captions.button.language": "Language",
"app.audio.captions.button.transcription": "Transcription",
+ "app.audio.captions.button.translation": "Translation",
"app.audio.captions.button.transcriptionSettings": "Transcription settings",
"app.audio.captions.speech.title": "Automatic transcription",
"app.audio.captions.speech.disabled": "Disabled",
From 940a3c3a1227b750cf86551162cd8dea0955adcb Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sun, 16 Apr 2023 20:12:22 +0900
Subject: [PATCH 13/32] Update settings.yml
---
bigbluebutton-html5/private/config/settings.yml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml
index cf674fc44948..71f29187b683 100755
--- a/bigbluebutton-html5/private/config/settings.yml
+++ b/bigbluebutton-html5/private/config/settings.yml
@@ -504,6 +504,9 @@ public:
enabled: true
id: captions
dictation: false
+ enableAutomaticTranslation: false
+ googleTranslateUrl: https://script.google.com/macros/s/YOUR_ID
+ #deeplTranslateUrl: https://api-free.deepl.com/v2/translate?auth_key=YOUR_KEY
background: '#000000'
font:
color: '#ffffff'
From 0fadd50dd0877c511a29d1e63ce982babcd47389 Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sun, 16 Apr 2023 20:18:15 +0900
Subject: [PATCH 14/32] Update updateTranscript.js
---
.../api/audio-captions/server/methods/updateTranscript.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 c9e3b70136ed..c879b2e36633 100644
--- a/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
+++ b/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
@@ -108,7 +108,7 @@ export default function updateTranscript(transcriptId, start, end, text, transcr
Logger.info(`Assigned user prevTextOri ${prevTextOri} id=${requesterUserId} meeting=${meetingId}`);
}
} catch (err) {
- Logger.error(`Assigning user prevTextOri: ${err}`);
+ Logger.error(`Assigning user prevTextOri: ${err}`);
}
translateText(meetingId, requesterUserId, payload, dstLocale);
} else {
From cd68a205142c2270f4ef517d724ee6f5bbca9d4c Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sun, 16 Apr 2023 20:23:12 +0900
Subject: [PATCH 15/32] Update component.jsx
---
.../ui/components/audio/captions/button/component.jsx | 7 +++++++
1 file changed, 7 insertions(+)
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 1eeb35d0e332..72ad3b94dbb3 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/captions/button/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/captions/button/component.jsx
@@ -104,6 +104,13 @@ const CaptionsButton = ({
const isTranscriptionDisabled = () => (
currentSpeechLocale === DISABLED
);
+ const isTranslationDisabled = () => (
+ currentTranslationLocale === DISABLED
+ );
+
+ const isTranslationActivated = () => (
+ Meteor.settings.public.captions.enableAutomaticTranslation
+ );
const fallbackLocale = availableVoices.includes(navigator.language)
? navigator.language : DEFAULT_LOCALE;
From 5d92310cdc787f16f262e230e937d0765c47003c Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sun, 16 Apr 2023 20:31:45 +0900
Subject: [PATCH 16/32] fix regexp
---
.../api/audio-captions/server/methods/updateTranscript.js | 4 ++--
1 file changed, 2 insertions(+), 2 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 c879b2e36633..a6a24332dca1 100644
--- a/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
+++ b/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
@@ -14,7 +14,7 @@ function translateText (meetingId, userId, payload, dst) {
const { locale: src, transcript: textOri } = payload;
- if ( !CAPTIONS_CONFIG.enableAutomaticTranslation || textOri === "" || !dst || dst === "" || dst === src || dst.replace(/-.*$/,'') === src || dst === src.replace(/-.*$/,'') ) {
+ 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 = '';
@@ -23,7 +23,7 @@ function translateText (meetingId, userId, payload, dst) {
'text=' + encodeURIComponent(textOri) + '&source=' + src + '&target=' + dst;
} else if (CAPTIONS_CONFIG.deeplTranslateUrl) {
url = CAPTIONS_CONFIG.deeplTranslateUrl +
- '&text=' + encodeURIComponent(textOri) + '&source_lang=' + src.replace(/-.*$/,'').toUpperCase() +
+ '&text=' + encodeURIComponent(textOri) + '&source_lang=' + src.replace(/-..$/,'').toUpperCase() +
'&target_lang=' + dst.toUpperCase();
} else {
Logger.error('Could not get a translation service.');
From 207bca99d26d3df19311e32349e44eb51e040acb Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sun, 16 Apr 2023 20:35:41 +0900
Subject: [PATCH 17/32] Update updateTranscript.js
---
.../api/audio-captions/server/methods/updateTranscript.js | 1 -
1 file changed, 1 deletion(-)
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 a6a24332dca1..1e3850d78088 100644
--- a/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
+++ b/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
@@ -46,7 +46,6 @@ function translateText (meetingId, userId, payload, dst) {
} 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 {
From ebf448bd2354ad98a42345e6d6ba4fad167717de Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Tue, 18 Apr 2023 12:42:31 +0900
Subject: [PATCH 18/32] version2, creating translation db
---
.../server/methods/updateTranscript.js | 132 ++++++++++--------
1 file changed, 76 insertions(+), 56 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 1e3850d78088..3d3fda4df23a 100644
--- a/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
+++ b/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
@@ -4,55 +4,94 @@ 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';
+import Meetings from '/imports/api/meetings';
const CAPTIONS_CONFIG = Meteor.settings.public.captions;
+function updateDbAndPublish(channel, eventName, meetingId, userId, payload, translatedTranscript, translatedText, newDbEntry) {
+ const selector = { meetingId };
+ const modifier = {
+ $set: {
+ //[`translationDb.${newDbEntry[2]}.${newDbEntry[0]}`]: newDbEntry[1], // this works as well
+ ['translationDb.'+newDbEntry[2]+'.'+newDbEntry[0]]: newDbEntry[1],
+ },
+ };
+
+ try {
+ const numberAffected = Meetings.update(selector, modifier);
+ if (numberAffected) {
+ Logger.info(`Assigned meeting translationDb ${newDbEntry} meeting=${meetingId}`);
+ }
+ } catch (err) {
+ Logger.error(`Assigning meeting translationDb: ${err}`);
+ }
+
+ const newPayload = Object.assign({}, payload, {transcript: translatedTranscript, text: translatedText});
+ RedisPubSub.publishUserMessage(channel, eventName, meetingId, userId, newPayload);
+}
+
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;
+ const { locale: src, transcript: transcriptOri, text: textOri } = payload;
- if ( !CAPTIONS_CONFIG.enableAutomaticTranslation || textOri === "" || !dst || dst === "" || dst === src || dst.replace(/-..$/,'') === src || dst === src.replace(/-..$/,'') ) {
+ if ( !CAPTIONS_CONFIG.enableAutomaticTranslation || transcriptOri === "" || !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;
- }
+ const { translationDb : transDb = {} } = Meetings.findOne({ meetingId }, { fields: { translationDb: 1 }});
+ const { [src+'-'+dst]: tDb = {} } = transDb;
+ const transcriptOriNoBlank = transcriptOri.replace(/^\s*/, '');
+ const transcriptOriHeader = (transcriptOri === transcriptOriNoBlank ? '' : ' ');
- axios({
- method: 'get',
- url,
- responseType: 'json',
- }).then((response) => {
+ if (tDb[transcriptOriNoBlank]) {
+ // The 'text' item, which seems kept for backward compatibility, is not always the same as 'transcript';
+ // It can be either a blank string, same string as 'transcript', or trancated 'transcript'
+ // To reduce the access to translation servers, it is simplified: same as 'transcript' or a blank.
+ const newText = textOri.match(/\S/g) ? tDb[transcriptOriNoBlank] : '';
+ const newPayload = Object.assign({}, payload, {transcript: transcriptOriHeader + tDb[transcriptOriNoBlank], text: transcriptOriHeader + newText});
+ RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, newPayload);
+ } else {
+ let url = '';
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}"`);
- }
+ url = CAPTIONS_CONFIG.googleTranslateUrl + '/exec?' +
+ 'text=' + encodeURIComponent(transcriptOriNoBlank) + '&source=' + src + '&target=' + dst;
} else if (CAPTIONS_CONFIG.deeplTranslateUrl) {
- const { translations } = response.data;
- if (translations.length > 0 && 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}"`);
- }
+ url = CAPTIONS_CONFIG.deeplTranslateUrl +
+ '&text=' + encodeURIComponent(transcriptOriNoBlank) + '&source_lang=' + src.replace(/-.*$/,'').toUpperCase() +
+ '&target_lang=' + dst.toUpperCase();
+ } else {
+ Logger.error('Could not get a translation service.');
+ return;
}
- }).catch((error) => Logger.error(`Could not get translation for ${textOri.trim()} on the locale ${dst}: ${error}`));
+
+ axios({
+ method: 'get',
+ url,
+ responseType: 'json',
+ }).then((response) => {
+ if (CAPTIONS_CONFIG.googleTranslateUrl) {
+ const { code, text } = response.data;
+ if (code === 200) {
+ const newTranscript = transcriptOriHeader + text;
+ const newText = textOri.match(/\S/g) ? newTranscript : '';
+ updateDbAndPublish(CHANNEL, EVENT_NAME, meetingId, userId, payload, newTranscript, newText, [ transcriptOriNoBlank, text, src+'-'+dst ]);
+ } else {
+ Logger.error(`Failed to get Google translation for "${transcriptOri}"`);
+ }
+ } else if (CAPTIONS_CONFIG.deeplTranslateUrl) {
+ const { translations } = response.data;
+ if (translations.length > 0 && translations[0].text) {
+ const newTranscript = transcriptOriHeader + translations[0].text;
+ const newText = textOri.match(/\S/g) ? newTranscript : '';
+ updateDbAndPublish(CHANNEL, EVENT_NAME, meetingId, userId, payload, newTranscript, newText, [ transcriptOriNoBlank, text, src+'-'+dst ]);
+ } else {
+ Logger.error(`Failed to get DeepL translation for "${transcriptOri}"`);
+ }
+ }
+ }).catch((error) => Logger.error(`Could not get translation for ${transcriptOri.trim()} on the locale ${dst}: ${error}`));
+ }
}
}
@@ -88,31 +127,12 @@ export default function updateTranscript(transcriptId, start, end, text, transcr
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}`);
- }
+ const { translationLocale: dstLocale } = Users.findOne(selector, fields);
+
+ translateText(meetingId, requesterUserId, payload, dstLocale);
}
} catch (err) {
Logger.error(`Exception while invoking method upadteTranscript ${err.stack}`);
From 3f7c81dcaa2d043ec82f3d5326161bdd1556e2c0 Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Tue, 18 Apr 2023 12:48:13 +0900
Subject: [PATCH 19/32] fix regexp (again)
---
.../api/audio-captions/server/methods/updateTranscript.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 3d3fda4df23a..5a77cb5dc8dd 100644
--- a/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
+++ b/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
@@ -37,7 +37,7 @@ function translateText (meetingId, userId, payload, dst) {
const { locale: src, transcript: transcriptOri, text: textOri } = payload;
- if ( !CAPTIONS_CONFIG.enableAutomaticTranslation || transcriptOri === "" || !dst || dst === "" || dst === src || dst.replace(/-.*$/,'') === src || dst === src.replace(/-.*$/,'') ) {
+ if ( !CAPTIONS_CONFIG.enableAutomaticTranslation || transcriptOri === "" || !dst || dst === "" || dst === src || dst.replace(/-..$/,'') === src || dst === src.replace(/-..$/,'') ) {
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, payload);
} else {
const { translationDb : transDb = {} } = Meetings.findOne({ meetingId }, { fields: { translationDb: 1 }});
From 56df9d8fa2bffff12074f109478a3c68b5b8df2b Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Tue, 18 Apr 2023 12:52:40 +0900
Subject: [PATCH 20/32] fix regexp (one more)
---
.../api/audio-captions/server/methods/updateTranscript.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 5a77cb5dc8dd..a95add3f5652 100644
--- a/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
+++ b/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
@@ -59,7 +59,7 @@ function translateText (meetingId, userId, payload, dst) {
'text=' + encodeURIComponent(transcriptOriNoBlank) + '&source=' + src + '&target=' + dst;
} else if (CAPTIONS_CONFIG.deeplTranslateUrl) {
url = CAPTIONS_CONFIG.deeplTranslateUrl +
- '&text=' + encodeURIComponent(transcriptOriNoBlank) + '&source_lang=' + src.replace(/-.*$/,'').toUpperCase() +
+ '&text=' + encodeURIComponent(transcriptOriNoBlank) + '&source_lang=' + src.replace(/-..$/,'').toUpperCase() +
'&target_lang=' + dst.toUpperCase();
} else {
Logger.error('Could not get a translation service.');
From fe8bd997b67f691670912ae0ca705f9b56a8ad39 Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sun, 7 May 2023 10:00:52 +0900
Subject: [PATCH 21/32] Fix DeepL translation
---
.../api/audio-captions/server/methods/updateTranscript.js | 4 ++--
1 file changed, 2 insertions(+), 2 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 a95add3f5652..9e68dc422f8f 100644
--- a/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
+++ b/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
@@ -60,7 +60,7 @@ function translateText (meetingId, userId, payload, dst) {
} else if (CAPTIONS_CONFIG.deeplTranslateUrl) {
url = CAPTIONS_CONFIG.deeplTranslateUrl +
'&text=' + encodeURIComponent(transcriptOriNoBlank) + '&source_lang=' + src.replace(/-..$/,'').toUpperCase() +
- '&target_lang=' + dst.toUpperCase();
+ '&target_lang=' + dst.replace(/-..$/,'').toUpperCase();
} else {
Logger.error('Could not get a translation service.');
return;
@@ -85,7 +85,7 @@ function translateText (meetingId, userId, payload, dst) {
if (translations.length > 0 && translations[0].text) {
const newTranscript = transcriptOriHeader + translations[0].text;
const newText = textOri.match(/\S/g) ? newTranscript : '';
- updateDbAndPublish(CHANNEL, EVENT_NAME, meetingId, userId, payload, newTranscript, newText, [ transcriptOriNoBlank, text, src+'-'+dst ]);
+ updateDbAndPublish(CHANNEL, EVENT_NAME, meetingId, userId, payload, newTranscript, newText, [ transcriptOriNoBlank, translations[0].text, src+'-'+dst ]);
} else {
Logger.error(`Failed to get DeepL translation for "${transcriptOri}"`);
}
From f5819c916b3200c2a7001ddf4cb2ec6e22908d2f Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sun, 7 May 2023 18:37:52 +0900
Subject: [PATCH 22/32] change to translation locale
---
.../server/methods/updateTranscript.js | 15 +++++++++------
1 file changed, 9 insertions(+), 6 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 9e68dc422f8f..bb10fdf45ce4 100644
--- a/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
+++ b/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
@@ -10,10 +10,12 @@ const CAPTIONS_CONFIG = Meteor.settings.public.captions;
function updateDbAndPublish(channel, eventName, meetingId, userId, payload, translatedTranscript, translatedText, newDbEntry) {
const selector = { meetingId };
+ const dbname = newDbEntry.srcLocale + '-' + newDbEntry.dstLocale;
const modifier = {
$set: {
//[`translationDb.${newDbEntry[2]}.${newDbEntry[0]}`]: newDbEntry[1], // this works as well
- ['translationDb.'+newDbEntry[2]+'.'+newDbEntry[0]]: newDbEntry[1],
+ //['translationDb.'+newDbEntry[2]+'.'+newDbEntry[0]]: newDbEntry[1],
+ ['translationDb.'+dbname+'.'+newDbEntry.origText]: newDbEntry.translatedText,
},
};
@@ -26,7 +28,7 @@ function updateDbAndPublish(channel, eventName, meetingId, userId, payload, tran
Logger.error(`Assigning meeting translationDb: ${err}`);
}
- const newPayload = Object.assign({}, payload, {transcript: translatedTranscript, text: translatedText});
+ const newPayload = Object.assign({}, payload, {transcript: translatedTranscript, text: translatedText, locale: newDbEntry.dstLocale});
RedisPubSub.publishUserMessage(channel, eventName, meetingId, userId, newPayload);
}
@@ -38,7 +40,8 @@ function translateText (meetingId, userId, payload, dst) {
const { locale: src, transcript: transcriptOri, text: textOri } = payload;
if ( !CAPTIONS_CONFIG.enableAutomaticTranslation || transcriptOri === "" || !dst || dst === "" || dst === src || dst.replace(/-..$/,'') === src || dst === src.replace(/-..$/,'') ) {
- RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, payload);
+ const newPayload = Object.assign({}, payload, {locale: dst});
+ RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, newPayload);
} else {
const { translationDb : transDb = {} } = Meetings.findOne({ meetingId }, { fields: { translationDb: 1 }});
const { [src+'-'+dst]: tDb = {} } = transDb;
@@ -50,7 +53,7 @@ function translateText (meetingId, userId, payload, dst) {
// It can be either a blank string, same string as 'transcript', or trancated 'transcript'
// To reduce the access to translation servers, it is simplified: same as 'transcript' or a blank.
const newText = textOri.match(/\S/g) ? tDb[transcriptOriNoBlank] : '';
- const newPayload = Object.assign({}, payload, {transcript: transcriptOriHeader + tDb[transcriptOriNoBlank], text: transcriptOriHeader + newText});
+ const newPayload = Object.assign({}, payload, {transcript: transcriptOriHeader + tDb[transcriptOriNoBlank], text: transcriptOriHeader + newText, locale: dst});
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, newPayload);
} else {
let url = '';
@@ -76,7 +79,7 @@ function translateText (meetingId, userId, payload, dst) {
if (code === 200) {
const newTranscript = transcriptOriHeader + text;
const newText = textOri.match(/\S/g) ? newTranscript : '';
- updateDbAndPublish(CHANNEL, EVENT_NAME, meetingId, userId, payload, newTranscript, newText, [ transcriptOriNoBlank, text, src+'-'+dst ]);
+ updateDbAndPublish(CHANNEL, EVENT_NAME, meetingId, userId, payload, newTranscript, newText, { origText: transcriptOriNoBlank, translatedText: text, srcLocale: src, dstLocale: dst });
} else {
Logger.error(`Failed to get Google translation for "${transcriptOri}"`);
}
@@ -85,7 +88,7 @@ function translateText (meetingId, userId, payload, dst) {
if (translations.length > 0 && translations[0].text) {
const newTranscript = transcriptOriHeader + translations[0].text;
const newText = textOri.match(/\S/g) ? newTranscript : '';
- updateDbAndPublish(CHANNEL, EVENT_NAME, meetingId, userId, payload, newTranscript, newText, [ transcriptOriNoBlank, translations[0].text, src+'-'+dst ]);
+ updateDbAndPublish(CHANNEL, EVENT_NAME, meetingId, userId, payload, newTranscript, newText, { origText: transcriptOriNoBlank, translatedText: translations[0].text, srcLocale: src, dstLocale: dst });
} else {
Logger.error(`Failed to get DeepL translation for "${transcriptOri}"`);
}
From f698f5a5967385ab79c8c4181060a850056547a8 Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sun, 7 May 2023 18:45:44 +0900
Subject: [PATCH 23/32] remove space
---
.../api/audio-captions/server/methods/updateTranscript.js | 4 ++--
1 file changed, 2 insertions(+), 2 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 bb10fdf45ce4..c96cd2da8d50 100644
--- a/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
+++ b/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
@@ -40,8 +40,8 @@ function translateText (meetingId, userId, payload, dst) {
const { locale: src, transcript: transcriptOri, text: textOri } = payload;
if ( !CAPTIONS_CONFIG.enableAutomaticTranslation || transcriptOri === "" || !dst || dst === "" || dst === src || dst.replace(/-..$/,'') === src || dst === src.replace(/-..$/,'') ) {
- const newPayload = Object.assign({}, payload, {locale: dst});
- RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, newPayload);
+ const newPayload = Object.assign({}, payload, {locale: dst});
+ RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, newPayload);
} else {
const { translationDb : transDb = {} } = Meetings.findOne({ meetingId }, { fields: { translationDb: 1 }});
const { [src+'-'+dst]: tDb = {} } = transDb;
From 3f55e0a8a3b50759e96d0f538b21d1444a604115 Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sun, 7 May 2023 19:44:34 +0900
Subject: [PATCH 24/32] publish untranslated caption as well
---
.../api/audio-captions/server/methods/updateTranscript.js | 3 +++
1 file changed, 3 insertions(+)
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 c96cd2da8d50..4357b4388338 100644
--- a/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
+++ b/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
@@ -48,6 +48,9 @@ function translateText (meetingId, userId, payload, dst) {
const transcriptOriNoBlank = transcriptOri.replace(/^\s*/, '');
const transcriptOriHeader = (transcriptOri === transcriptOriNoBlank ? '' : ' ');
+ //we are going to translate but publish untranslated captions anyway
+ RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, payload);
+
if (tDb[transcriptOriNoBlank]) {
// The 'text' item, which seems kept for backward compatibility, is not always the same as 'transcript';
// It can be either a blank string, same string as 'transcript', or trancated 'transcript'
From 0796611e83386c68c4e7b3e4862a95c78e025aed Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sun, 7 May 2023 20:46:09 +0900
Subject: [PATCH 25/32] Delete orphan translations
This will not be necessary once the bug that locale is not set in event.xml is resolved.
---
record-and-playback/core/scripts/utils/gen_webvtt | 1 +
1 file changed, 1 insertion(+)
diff --git a/record-and-playback/core/scripts/utils/gen_webvtt b/record-and-playback/core/scripts/utils/gen_webvtt
index d9ab086ffa18..c58d32b83cb2 100755
--- a/record-and-playback/core/scripts/utils/gen_webvtt
+++ b/record-and-playback/core/scripts/utils/gen_webvtt
@@ -429,6 +429,7 @@ if __name__ == "__main__":
logger.info("Generating caption data from recording events")
captions = Caption.from_events(events)
+ del captions[None]
for locale, caption in captions.items():
filename = os.path.join(outputdir, "caption_{}.vtt".format(locale))
logger.info("Writing captions for locale %s to %s", locale, filename)
From a095ee447df6df2fb9c76bfdcb2419e8f2d70c1c Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sat, 13 May 2023 19:18:58 +0900
Subject: [PATCH 26/32] Revert updateTranscript.js
---
.../server/methods/updateTranscript.js | 117 +-----------------
1 file changed, 5 insertions(+), 112 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 4357b4388338..3b1b7bf68cf9 100644
--- a/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
+++ b/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js
@@ -2,107 +2,13 @@ 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';
-import Meetings from '/imports/api/meetings';
-
-const CAPTIONS_CONFIG = Meteor.settings.public.captions;
-
-function updateDbAndPublish(channel, eventName, meetingId, userId, payload, translatedTranscript, translatedText, newDbEntry) {
- const selector = { meetingId };
- const dbname = newDbEntry.srcLocale + '-' + newDbEntry.dstLocale;
- const modifier = {
- $set: {
- //[`translationDb.${newDbEntry[2]}.${newDbEntry[0]}`]: newDbEntry[1], // this works as well
- //['translationDb.'+newDbEntry[2]+'.'+newDbEntry[0]]: newDbEntry[1],
- ['translationDb.'+dbname+'.'+newDbEntry.origText]: newDbEntry.translatedText,
- },
- };
-
- try {
- const numberAffected = Meetings.update(selector, modifier);
- if (numberAffected) {
- Logger.info(`Assigned meeting translationDb ${newDbEntry} meeting=${meetingId}`);
- }
- } catch (err) {
- Logger.error(`Assigning meeting translationDb: ${err}`);
- }
-
- const newPayload = Object.assign({}, payload, {transcript: translatedTranscript, text: translatedText, locale: newDbEntry.dstLocale});
- RedisPubSub.publishUserMessage(channel, eventName, meetingId, userId, newPayload);
-}
-
-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: transcriptOri, text: textOri } = payload;
-
- if ( !CAPTIONS_CONFIG.enableAutomaticTranslation || transcriptOri === "" || !dst || dst === "" || dst === src || dst.replace(/-..$/,'') === src || dst === src.replace(/-..$/,'') ) {
- const newPayload = Object.assign({}, payload, {locale: dst});
- RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, newPayload);
- } else {
- const { translationDb : transDb = {} } = Meetings.findOne({ meetingId }, { fields: { translationDb: 1 }});
- const { [src+'-'+dst]: tDb = {} } = transDb;
- const transcriptOriNoBlank = transcriptOri.replace(/^\s*/, '');
- const transcriptOriHeader = (transcriptOri === transcriptOriNoBlank ? '' : ' ');
-
- //we are going to translate but publish untranslated captions anyway
- RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, payload);
-
- if (tDb[transcriptOriNoBlank]) {
- // The 'text' item, which seems kept for backward compatibility, is not always the same as 'transcript';
- // It can be either a blank string, same string as 'transcript', or trancated 'transcript'
- // To reduce the access to translation servers, it is simplified: same as 'transcript' or a blank.
- const newText = textOri.match(/\S/g) ? tDb[transcriptOriNoBlank] : '';
- const newPayload = Object.assign({}, payload, {transcript: transcriptOriHeader + tDb[transcriptOriNoBlank], text: transcriptOriHeader + newText, locale: dst});
- RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, newPayload);
- } else {
- let url = '';
- if (CAPTIONS_CONFIG.googleTranslateUrl) {
- url = CAPTIONS_CONFIG.googleTranslateUrl + '/exec?' +
- 'text=' + encodeURIComponent(transcriptOriNoBlank) + '&source=' + src + '&target=' + dst;
- } else if (CAPTIONS_CONFIG.deeplTranslateUrl) {
- url = CAPTIONS_CONFIG.deeplTranslateUrl +
- '&text=' + encodeURIComponent(transcriptOriNoBlank) + '&source_lang=' + src.replace(/-..$/,'').toUpperCase() +
- '&target_lang=' + dst.replace(/-..$/,'').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 newTranscript = transcriptOriHeader + text;
- const newText = textOri.match(/\S/g) ? newTranscript : '';
- updateDbAndPublish(CHANNEL, EVENT_NAME, meetingId, userId, payload, newTranscript, newText, { origText: transcriptOriNoBlank, translatedText: text, srcLocale: src, dstLocale: dst });
- } else {
- Logger.error(`Failed to get Google translation for "${transcriptOri}"`);
- }
- } else if (CAPTIONS_CONFIG.deeplTranslateUrl) {
- const { translations } = response.data;
- if (translations.length > 0 && translations[0].text) {
- const newTranscript = transcriptOriHeader + translations[0].text;
- const newText = textOri.match(/\S/g) ? newTranscript : '';
- updateDbAndPublish(CHANNEL, EVENT_NAME, meetingId, userId, payload, newTranscript, newText, { origText: transcriptOriNoBlank, translatedText: translations[0].text, srcLocale: src, dstLocale: dst });
- } else {
- Logger.error(`Failed to get DeepL translation for "${transcriptOri}"`);
- }
- }
- }).catch((error) => Logger.error(`Could not get translation for ${transcriptOri.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);
@@ -125,20 +31,7 @@ export default function updateTranscript(transcriptId, start, end, text, transcr
locale,
};
- const selector = {
- meetingId,
- userId: requesterUserId,
- };
-
- const fields = {
- fields: {
- translationLocale: 1,
- },
- };
-
- const { translationLocale: dstLocale } = Users.findOne(selector, fields);
-
- translateText(meetingId, requesterUserId, payload, dstLocale);
+ RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
}
} catch (err) {
Logger.error(`Exception while invoking method upadteTranscript ${err.stack}`);
From 8a733e635094efbb0ce786facc9e61acf06e6877 Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sat, 13 May 2023 19:28:16 +0900
Subject: [PATCH 27/32] Translation on cilent
---
.../audio/captions/speech/service.js | 70 +++++++++++++++++--
1 file changed, 65 insertions(+), 5 deletions(-)
diff --git a/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js b/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js
index 7eac27c7db53..3be6e78c785e 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js
+++ b/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js
@@ -8,6 +8,7 @@ import Users from '/imports/api/users';
import AudioService from '/imports/ui/components/audio/service';
import deviceInfo from '/imports/utils/deviceInfo';
import { isLiveTranscriptionEnabled } from '/imports/ui/services/features';
+import axios from 'axios';
const THROTTLE_TIMEOUT = 1000;
@@ -15,6 +16,8 @@ const CONFIG = Meteor.settings.public.app.audioCaptions;
const LANGUAGES = CONFIG.language.available;
const VALID_ENVIRONMENT = !deviceInfo.isMobile || CONFIG.mobile;
+const CAPTIONS_CONFIG = Meteor.settings.public.captions;
+
const SpeechRecognitionAPI = window.SpeechRecognition || window.webkitSpeechRecognition;
const hasSpeechRecognitionSupport = () => typeof SpeechRecognitionAPI !== 'undefined'
@@ -99,15 +102,72 @@ const initSpeechRecognition = () => {
};
let prevId = '';
-let prevTranscript = '';
+const prevTranscripts = {};
const updateTranscript = (id, transcript, locale) => {
+ const translationLocale = getTranslationLocale();
// If it's a new sentence
if (id !== prevId) {
prevId = id;
- prevTranscript = '';
+ prevTranscripts[locale] = '';
+ prevTranscripts[translationLocale] = '';
+ }
+
+ if ( CAPTIONS_CONFIG.enableAutomaticTranslation && translationLocale && translationLocale !== "" &&
+ locale !== translationLocale && locale.replace(/-..$/,'') !== translationLocale && locale !== translationLocale.replace(/-..$/,'') ) {
+ translateTranscript(id, transcript, locale, translationLocale);
}
- const transcriptDiff = diff(prevTranscript, transcript);
+ sendDiffTranscript(id, transcript, locale);
+};
+
+const translateTranscript = (id, text, src, dst) => {
+ if (text.match(/\S/)) {
+ const textDecap = text.replace(/^\s*/, '');
+ const preSpace = text === textDecap ? '' : ' ';
+ //console.log("translateTranscript", "|"+text+"|,", "|"+preSpace+"|");
+ let url = '';
+ if (CAPTIONS_CONFIG.googleTranslateUrl) {
+ url = CAPTIONS_CONFIG.googleTranslateUrl + '/exec?' +
+ 'text=' + encodeURIComponent(textDecap) + '&source=' + src + '&target=' + dst;
+ } else if (CAPTIONS_CONFIG.deeplTranslateUrl) {
+ url = CAPTIONS_CONFIG.deeplTranslateUrl +
+ '&text=' + encodeURIComponent(textDecap) + '&source_lang=' + src.replace(/-..$/,'').toUpperCase() +
+ '&target_lang=' + dst.replace(/-..$/,'').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) {
+ sendDiffTranscript(id, preSpace + text, dst);
+ } else {
+ logger.error(`Failed to get Google translation for "${transcriptOri}"`);
+ }
+ } else if (CAPTIONS_CONFIG.deeplTranslateUrl) {
+ const { translations } = response.data;
+ if (translations.length > 0 && translations[0].text) {
+ sendDiffTranscript(id, preSpace + translations[0].text, dst);
+ } else {
+ logger.error(`Failed to get DeepL translation for "${transcriptOri}"`);
+ }
+ }
+ }).catch((error) => logger.error(`Could not get translation for ${transcriptOri.trim()} on the locale ${dst}: ${error}`));
+
+ } else {
+ sendDiffTranscript(id, text, dst);
+ }
+}
+
+const sendDiffTranscript = (id, tsc, loc) => {
+ const transcriptDiff = diff(prevTranscripts[loc], tsc);
+ //console.log("sendDiffTranscript", loc, "\np:", prevTranscripts[loc], "\nc:", tsc, "\n", transcriptDiff);
let start = 0;
let end = 0;
@@ -119,9 +179,9 @@ const updateTranscript = (id, transcript, locale) => {
}
// Stores current transcript as previous
- prevTranscript = transcript;
+ prevTranscripts[loc] = tsc;
- makeCall('updateTranscript', id, start, end, text, transcript, locale);
+ makeCall('updateTranscript', id, start, end, text, tsc, loc);
};
const throttledTranscriptUpdate = _.throttle(updateTranscript, THROTTLE_TIMEOUT, {
From fdce6df745431859ecf001aaedfcb421e8d22758 Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sat, 13 May 2023 19:31:43 +0900
Subject: [PATCH 28/32] revert del Null gen_webvtt
---
record-and-playback/core/scripts/utils/gen_webvtt | 1 -
1 file changed, 1 deletion(-)
diff --git a/record-and-playback/core/scripts/utils/gen_webvtt b/record-and-playback/core/scripts/utils/gen_webvtt
index c58d32b83cb2..d9ab086ffa18 100755
--- a/record-and-playback/core/scripts/utils/gen_webvtt
+++ b/record-and-playback/core/scripts/utils/gen_webvtt
@@ -429,7 +429,6 @@ if __name__ == "__main__":
logger.info("Generating caption data from recording events")
captions = Caption.from_events(events)
- del captions[None]
for locale, caption in captions.items():
filename = os.path.join(outputdir, "caption_{}.vtt".format(locale))
logger.info("Writing captions for locale %s to %s", locale, filename)
From cd1e8d31c7ba64ce9cc083d0f3debf224c0a43e6 Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sun, 21 May 2023 13:50:14 +0900
Subject: [PATCH 29/32] add ffi-icu
---
record-and-playback/core/Gemfile | 1 +
1 file changed, 1 insertion(+)
diff --git a/record-and-playback/core/Gemfile b/record-and-playback/core/Gemfile
index b902f7d32fb8..91f471f0fb47 100644
--- a/record-and-playback/core/Gemfile
+++ b/record-and-playback/core/Gemfile
@@ -36,6 +36,7 @@ gem 'resque', '~> 2.4'
gem 'bbbevents', '~> 1.2'
gem 'rake', '>= 12.3', '<14'
gem 'tzinfo', '>= 1.2.10'
+gem 'ffi-icu'
group :test, optional: true do
gem 'rubocop', '~> 1.31.1'
From 8c36611cfd1e94eb6a1f676f0056dad77bb700b2 Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sun, 21 May 2023 13:53:27 +0900
Subject: [PATCH 30/32] add ffi-icu
---
record-and-playback/core/Gemfile.lock | 3 +++
1 file changed, 3 insertions(+)
diff --git a/record-and-playback/core/Gemfile.lock b/record-and-playback/core/Gemfile.lock
index e16775b1e0f1..a6c277aeceaf 100644
--- a/record-and-playback/core/Gemfile.lock
+++ b/record-and-playback/core/Gemfile.lock
@@ -15,6 +15,8 @@ GEM
crass (1.0.6)
fastimage (2.2.6)
ffi (1.15.5)
+ ffi-icu (0.5.0)
+ ffi (~> 1.0, >= 1.0.9)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
java_properties (0.0.4)
@@ -93,6 +95,7 @@ DEPENDENCIES
bbbevents (~> 1.2)
builder (~> 3.2)
fastimage (~> 2.1)
+ ffi-icu
java_properties
journald-logger (~> 3.0)
jwt (~> 2.2)
From 7e52943837dcbc6492bee5606a7985da1043aec8 Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Sat, 3 Jun 2023 20:57:24 +0900
Subject: [PATCH 31/32] =?UTF-8?q?=E7=BF=BB=E8=A8=B3=E3=82=92=E8=BF=BD?=
=?UTF-8?q?=E5=8A=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
bigbluebutton-html5/public/locales/ja.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/bigbluebutton-html5/public/locales/ja.json b/bigbluebutton-html5/public/locales/ja.json
index f924e2abc364..a2894c3108a0 100644
--- a/bigbluebutton-html5/public/locales/ja.json
+++ b/bigbluebutton-html5/public/locales/ja.json
@@ -714,6 +714,7 @@
"app.audio.captions.button.stop": "字幕を停止",
"app.audio.captions.button.language": "言語",
"app.audio.captions.button.transcription": "文字起こし",
+ "app.audio.captions.button.translation": "翻訳",
"app.audio.captions.button.transcriptionSettings": "文字起こし設定",
"app.audio.captions.speech.title": "自動文字起こし",
"app.audio.captions.speech.disabled": "無効",
From cdc19da0e87d23ee36eca3f444f602681e728c1b Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Thu, 29 Jun 2023 12:29:17 +0900
Subject: [PATCH 32/32] Google Translation locale without -XX
---
.../imports/ui/components/audio/captions/speech/service.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js b/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js
index 3be6e78c785e..8f462b7a813a 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js
+++ b/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js
@@ -128,7 +128,7 @@ const translateTranscript = (id, text, src, dst) => {
let url = '';
if (CAPTIONS_CONFIG.googleTranslateUrl) {
url = CAPTIONS_CONFIG.googleTranslateUrl + '/exec?' +
- 'text=' + encodeURIComponent(textDecap) + '&source=' + src + '&target=' + dst;
+ 'text=' + encodeURIComponent(textDecap) + '&source=' + src + '&target=' + dst.replace(/-..$/,'');
} else if (CAPTIONS_CONFIG.deeplTranslateUrl) {
url = CAPTIONS_CONFIG.deeplTranslateUrl +
'&text=' + encodeURIComponent(textDecap) + '&source_lang=' + src.replace(/-..$/,'').toUpperCase() +