diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/caption/CaptionApp2x.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/caption/CaptionApp2x.scala index 90dd3555e1d5..0712be230598 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/caption/CaptionApp2x.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/caption/CaptionApp2x.scala @@ -46,7 +46,7 @@ class CaptionApp2x(implicit val context: ActorContext) extends RightsManagementT } if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId) - && isUserCaptionOwner(liveMeeting, msg.header.userId, msg.body.name)) { + /*&& isUserCaptionOwner(liveMeeting, msg.header.userId, msg.body.name)*/) { val meetingId = liveMeeting.props.meetingProp.intId val reason = "No permission to edit caption history in meeting." PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/pads/PadUpdatePubMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/pads/PadUpdatePubMsgHdlr.scala index 31f18df1fbba..93c07c13cfad 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/pads/PadUpdatePubMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/pads/PadUpdatePubMsgHdlr.scala @@ -21,11 +21,11 @@ trait PadUpdatePubMsgHdlr { bus.outGW.send(msgEvent) } - if (Pads.hasAccess(liveMeeting, msg.body.externalId, msg.header.userId)) { + //if (Pads.hasAccess(liveMeeting, msg.body.externalId, msg.header.userId)) { Pads.getGroup(liveMeeting.pads, msg.body.externalId) match { case Some(group) => broadcastEvent(group.groupId, msg.body.externalId, msg.body.text) case _ => } - } + //} } } diff --git a/bigbluebutton-html5/imports/api/captions/server/methods.js b/bigbluebutton-html5/imports/api/captions/server/methods.js index c1cb17675e72..c789cbec7838 100644 --- a/bigbluebutton-html5/imports/api/captions/server/methods.js +++ b/bigbluebutton-html5/imports/api/captions/server/methods.js @@ -1,12 +1,16 @@ import { Meteor } from 'meteor/meteor'; import updateCaptionsOwner from '/imports/api/captions/server/methods/updateCaptionsOwner'; -import startDictation from '/imports/api/captions/server/methods/startDictation'; -import stopDictation from '/imports/api/captions/server/methods/stopDictation'; +import provideSpeech from '/imports/api/captions/server/methods/provideSpeech'; +import retractSpeech from '/imports/api/captions/server/methods/retractSpeech'; import pushSpeechTranscript from '/imports/api/captions/server/methods/pushSpeechTranscript'; +import addTranslation from '/imports/api/captions/server/methods/addTranslation'; +import removeTranslation from '/imports/api/captions/server/methods/removeTranslation'; Meteor.methods({ updateCaptionsOwner, - startDictation, - stopDictation, + provideSpeech, + retractSpeech, pushSpeechTranscript, + addTranslation, + removeTranslation, }); diff --git a/bigbluebutton-html5/imports/api/captions/server/methods/addTranslation.js b/bigbluebutton-html5/imports/api/captions/server/methods/addTranslation.js new file mode 100644 index 000000000000..01a15774c096 --- /dev/null +++ b/bigbluebutton-html5/imports/api/captions/server/methods/addTranslation.js @@ -0,0 +1,19 @@ +import { check } from 'meteor/check'; +import { extractCredentials } from '/imports/api/common/server/helpers'; +import Logger from '/imports/startup/server/logger'; +import setAutoTranslation from '/imports/api/captions/server/modifiers/setAutoTranslation'; + +export default function addTranslation(locale) { + try { + const { meetingId, requesterUserId } = extractCredentials(this.userId); + + check(locale, String); + check(meetingId, String); + check(requesterUserId, String); + + //const caption = Captions.findOne({ meetingId, locale }, { fields: { translating:1 } }); + setAutoTranslation(meetingId, locale, requesterUserId, true); + } catch (err) { + Logger.error(`Exception while invoking method enableAutoTranslation ${err.stack}`); + } +} diff --git a/bigbluebutton-html5/imports/api/captions/server/methods/stopDictation.js b/bigbluebutton-html5/imports/api/captions/server/methods/provideSpeech.js similarity index 74% rename from bigbluebutton-html5/imports/api/captions/server/methods/stopDictation.js rename to bigbluebutton-html5/imports/api/captions/server/methods/provideSpeech.js index 44e469348ad1..51fd64802824 100644 --- a/bigbluebutton-html5/imports/api/captions/server/methods/stopDictation.js +++ b/bigbluebutton-html5/imports/api/captions/server/methods/provideSpeech.js @@ -4,7 +4,7 @@ import { extractCredentials } from '/imports/api/common/server/helpers'; import Logger from '/imports/startup/server/logger'; import setDictation from '/imports/api/captions/server/modifiers/setDictation'; -export default function stopDictation(locale) { +export default function provideSpeech(locale) { try { const { meetingId, requesterUserId } = extractCredentials(this.userId); @@ -14,12 +14,11 @@ export default function stopDictation(locale) { const captions = Captions.findOne({ meetingId, - ownerId: requesterUserId, locale, }); - if (captions) setDictation(meetingId, locale, false); + if (captions) setDictation(meetingId, locale, requesterUserId, true); } catch (err) { - Logger.error(`Exception while invoking method stopDictation ${err.stack}`); + Logger.error(`Exception while invoking method provideSpeech ${err.stack}`); } } diff --git a/bigbluebutton-html5/imports/api/captions/server/methods/pushSpeechTranscript.js b/bigbluebutton-html5/imports/api/captions/server/methods/pushSpeechTranscript.js index 7e81127fe6f2..34baa389a98f 100644 --- a/bigbluebutton-html5/imports/api/captions/server/methods/pushSpeechTranscript.js +++ b/bigbluebutton-html5/imports/api/captions/server/methods/pushSpeechTranscript.js @@ -1,11 +1,68 @@ import { check } from 'meteor/check'; import Captions from '/imports/api/captions'; +import Users from '/imports/api/users'; import { extractCredentials } from '/imports/api/common/server/helpers'; import Logger from '/imports/startup/server/logger'; import setTranscript from '/imports/api/captions/server/modifiers/setTranscript'; import updatePad from '/imports/api/pads/server/methods/updatePad'; +import axios from 'axios'; -export default function pushSpeechTranscript(locale, transcript, type) { +const CAPTIONS_CONFIG = Meteor.settings.public.captions; + +function sendTranscript(meetingId, requesterUserId, type, dst, text) { + const user = Users.findOne({meetingId, userId: requesterUserId}, { fields: {name: 1}}); + //console.log("sendTranscript", text); + //const textWithName = `${user.name}: ${text}`; + if (type === 'final') { + //const textLf = `\n${textWithName}`; + const textLf = `\n${text}`; + updatePad(meetingId, requesterUserId, dst, textLf); // Pad and recording + } + setTranscript(meetingId, dst, text, requesterUserId); // Live +} + +function translateText(meetingId, requesterUserId, textOri, type, src, dst) { + if ( !CAPTIONS_CONFIG.enableAutomaticTranslation || textOri === "" || !dst || dst === "" || dst === src || dst.replace(/-..$/,'') === src || dst === src.replace(/-..$/,'') ) { + sendTranscript(meetingId, requesterUserId, type, dst, textOri); + } else { + let url = ''; + if (CAPTIONS_CONFIG.googleTranslateUrl) { + url = CAPTIONS_CONFIG.googleTranslateUrl + '/exec?' + + 'text=' + encodeURIComponent(textOri) + '&source=' + src + '&target=' + dst.replace(/-..$/,''); + } 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) { + sendTranscript(meetingId, requesterUserId, type, dst, text); + } 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) { + sendTranscript(meetingId, requesterUserId, type, dst, translations[0].text); + } 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 pushSpeechTranscript(locale, transcript, type, locales) { try { const { meetingId, requesterUserId } = extractCredentials(this.userId); @@ -14,22 +71,23 @@ export default function pushSpeechTranscript(locale, transcript, type) { check(locale, String); check(transcript, String); check(type, String); + check(locales, Array); - const captions = Captions.findOne({ - meetingId, - ownerId: requesterUserId, - locale, - dictating: true, - }); + locales.forEach(function(dstLocale, index) { + const caption = Captions.findOne({ + meetingId, + locale: dstLocale, + }); - if (captions) { - if (type === 'final') { - const text = `\n${transcript}`; - updatePad(meetingId, requesterUserId, locale, text); + if (!caption) { + Logger.error(`Could not find the caption's pad for meetingId=${meetingId} locale=${locale}`); + } else { + if (type === 'final' || ( type === 'interim' && locale === dstLocale ) ) { + Logger.debug(`Transcription being translated from ${locale} into ${dstLocale}`); + translateText(meetingId, requesterUserId, transcript, type, locale, dstLocale); + } } - - setTranscript(meetingId, locale, transcript); - } + }); } catch (err) { Logger.error(`Exception while invoking method pushSpeechTranscript ${err.stack}`); } diff --git a/bigbluebutton-html5/imports/api/captions/server/methods/removeTranslation.js b/bigbluebutton-html5/imports/api/captions/server/methods/removeTranslation.js new file mode 100644 index 000000000000..7ee8977a4983 --- /dev/null +++ b/bigbluebutton-html5/imports/api/captions/server/methods/removeTranslation.js @@ -0,0 +1,19 @@ +import { check } from 'meteor/check'; +import { extractCredentials } from '/imports/api/common/server/helpers'; +import Logger from '/imports/startup/server/logger'; +import setAutoTranslation from '/imports/api/captions/server/modifiers/setAutoTranslation'; + +export default function removeTranslation(locale) { + try { + const { meetingId, requesterUserId } = extractCredentials(this.userId); + + check(locale, String); + check(meetingId, String); + check(requesterUserId, String); + + //const caption = Captions.findOne({ meetingId, locale }, { fields: { translating:1 } }); + setAutoTranslation(meetingId, locale, requesterUserId, false); + } catch (err) { + Logger.error(`Exception while invoking method disbleAutoTranslation ${err.stack}`); + } +} diff --git a/bigbluebutton-html5/imports/api/captions/server/methods/startDictation.js b/bigbluebutton-html5/imports/api/captions/server/methods/retractSpeech.js similarity index 72% rename from bigbluebutton-html5/imports/api/captions/server/methods/startDictation.js rename to bigbluebutton-html5/imports/api/captions/server/methods/retractSpeech.js index 7d0baa2a9df9..c46eeefd8361 100644 --- a/bigbluebutton-html5/imports/api/captions/server/methods/startDictation.js +++ b/bigbluebutton-html5/imports/api/captions/server/methods/retractSpeech.js @@ -4,7 +4,7 @@ import { extractCredentials } from '/imports/api/common/server/helpers'; import Logger from '/imports/startup/server/logger'; import setDictation from '/imports/api/captions/server/modifiers/setDictation'; -export default function startDictation(locale) { +export default function retractSpeech(locale) { try { const { meetingId, requesterUserId } = extractCredentials(this.userId); @@ -14,12 +14,11 @@ export default function startDictation(locale) { const captions = Captions.findOne({ meetingId, - ownerId: requesterUserId, locale, }); - if (captions) setDictation(meetingId, locale, true); + if (captions) setDictation(meetingId, locale, requesterUserId, false); } catch (err) { - Logger.error(`Exception while invoking method startDictation ${err.stack}`); + Logger.error(`Exception while invoking method retractSpeech ${err.stack}`); } } diff --git a/bigbluebutton-html5/imports/api/captions/server/modifiers/createCaptions.js b/bigbluebutton-html5/imports/api/captions/server/modifiers/createCaptions.js index 382be4726615..f92b3ea8fd58 100644 --- a/bigbluebutton-html5/imports/api/captions/server/modifiers/createCaptions.js +++ b/bigbluebutton-html5/imports/api/captions/server/modifiers/createCaptions.js @@ -20,6 +20,9 @@ export default function createCaptions(meetingId, locale, name) { ownerId: '', dictating: false, transcript: '', + translating: false, + translationDoner: {}, + speechDoner: {}, }; const numberAffected = Captions.upsert(selector, modifier); diff --git a/bigbluebutton-html5/imports/api/captions/server/modifiers/setAutoTranslation.js b/bigbluebutton-html5/imports/api/captions/server/modifiers/setAutoTranslation.js new file mode 100644 index 000000000000..ba82f60639b1 --- /dev/null +++ b/bigbluebutton-html5/imports/api/captions/server/modifiers/setAutoTranslation.js @@ -0,0 +1,62 @@ +//Do we need this? It just sets "translating" -> seems yes +import Captions from '/imports/api/captions'; +import Logger from '/imports/startup/server/logger'; +import { check } from 'meteor/check'; + +export default function setAutoTranslation(meetingId, locale, userId, selecting) { + check(meetingId, String); + check(locale, String); + check(userId, String); + check(selecting, Boolean); + + const selector = { + meetingId, + locale, + }; + + let modifier; + if (selecting) { + modifier = { + $set: { + ['translationDoner.'+userId]: true, + translating: true, + } + }; + } else { + modifier = { + $set: { + ['translationDoner.'+userId]: false, + } + }; + } + + try { + const numberAffected = Captions.update(selector, modifier); + Logger.info(`Adding/Removing (${selecting}) translationDoner ${locale} from ${userId} meeting=${meetingId}`); + if (numberAffected) { + Logger.verbose('Captions: updated pad autoTranslation', { locale }); + } + } catch (err) { + Logger.error(`Updating captions pad autoTranslation: ${err}`); + } + + if (!selecting) { + const newcap = Captions.findOne({ meetingId, locale }, { fields: { translationDoner: 1 } }); + const td = newcap.translationDoner; + if (!Object.values(td).includes(true)) { + const modifier2 = { + $set: { + translating: false, + }, + }; + try { + const numberAffected = Captions.upsert({ meetingId, locale }, modifier2); + if (numberAffected) { + Logger.info(`Updating translating to false; ${locale}, meeting=${meetingId} in Captions`); + } + } catch (err) { + Logger.error(`Updating translating: ${err} in Captions`); + } + } + } +} diff --git a/bigbluebutton-html5/imports/api/captions/server/modifiers/setDictation.js b/bigbluebutton-html5/imports/api/captions/server/modifiers/setDictation.js index d8bbfa246629..3c18254967d9 100644 --- a/bigbluebutton-html5/imports/api/captions/server/modifiers/setDictation.js +++ b/bigbluebutton-html5/imports/api/captions/server/modifiers/setDictation.js @@ -2,30 +2,56 @@ import { check } from 'meteor/check'; import Captions from '/imports/api/captions'; import Logger from '/imports/startup/server/logger'; -export default function setDictation(meetingId, locale, dictating) { +export default function setDictation(meetingId, locale, userId, providing) { try { check(meetingId, String); check(locale, String); - check(dictating, Boolean); + check(userId, String); + check(providing, Boolean); const selector = { meetingId, locale, }; - const modifier = { - $set: { - dictating, - transcript: '', - }, - }; + let modifier; + if (providing) { + modifier = { + $set: { + ['speechDoner.'+userId]: true, + dictating: true, + }, + }; + } else { + modifier = { + $set: { + ['speechDoner.'+userId]: false, + }, + }; + } const numberAffected = Captions.upsert(selector, modifier); if (numberAffected) { - Logger.info(`Set captions=${locale} dictating=${dictating} meeting=${meetingId}`); + Logger.info(`Set captions=${locale} provide?=${providing} meeting=${meetingId}, userId=${userId}`); } else { - Logger.info(`Upserted captions=${locale} dictating=${dictating} meeting=${meetingId}`); + Logger.info(`Upserted captions=${locale} provide?=${providing} meeting=${meetingId}, userId=${userId}`); + } + + if (!providing) { + const newcap = Captions.findOne({ meetingId, locale }, { fields: { speechDoner: 1 } }); + const sd = newcap.speechDoner; + if (!Object.values(sd).includes(true)) { + const modifier2 = { + $set: { + dictating: false, + }, + }; + const numberAffected = Captions.upsert({ meetingId, locale }, modifier2); + if (numberAffected) { + Logger.info(`Updating dicatating to false; ${locale}, meeting=${meetingId} in Captions`); + } + } } } catch (err) { Logger.error(`Setting captions dictation to the collection: ${err}`); diff --git a/bigbluebutton-html5/imports/api/captions/server/modifiers/setTranscript.js b/bigbluebutton-html5/imports/api/captions/server/modifiers/setTranscript.js index 2d7e1cc11c4e..a0d8335ab5ae 100644 --- a/bigbluebutton-html5/imports/api/captions/server/modifiers/setTranscript.js +++ b/bigbluebutton-html5/imports/api/captions/server/modifiers/setTranscript.js @@ -2,11 +2,12 @@ import { check } from 'meteor/check'; import Captions from '/imports/api/captions'; import Logger from '/imports/startup/server/logger'; -export default function setTranscript(meetingId, locale, transcript) { +export default function setTranscript(meetingId, locale, transcript, whosText) { try { check(meetingId, String); check(locale, String); check(transcript, String); + check(whosText, String); const selector = { meetingId, @@ -16,6 +17,7 @@ export default function setTranscript(meetingId, locale, transcript) { const modifier = { $set: { transcript, + whosText, }, }; diff --git a/bigbluebutton-html5/imports/api/captions/server/modifiers/updateCaptionsOwner.js b/bigbluebutton-html5/imports/api/captions/server/modifiers/updateCaptionsOwner.js index 61595c886f9f..e4a10524920f 100644 --- a/bigbluebutton-html5/imports/api/captions/server/modifiers/updateCaptionsOwner.js +++ b/bigbluebutton-html5/imports/api/captions/server/modifiers/updateCaptionsOwner.js @@ -16,7 +16,6 @@ export default function updateCaptionsOwner(meetingId, locale, ownerId) { const modifier = { $set: { ownerId, - dictating: false, // Refresh dictation mode }, }; diff --git a/bigbluebutton-html5/imports/ui/components/captions/button/component.jsx b/bigbluebutton-html5/imports/ui/components/captions/button/component.jsx index 19de6750f15a..a46f2080535f 100644 --- a/bigbluebutton-html5/imports/ui/components/captions/button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/captions/button/component.jsx @@ -2,6 +2,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; import Styled from './styles'; +import ButtonEmoji from '/imports/ui/components/common/button/button-emoji/ButtonEmoji'; +import _ from 'lodash'; +import Dropdown from '/imports/ui/components/dropdown/component'; +import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component'; +import DropdownContent from '/imports/ui/components/dropdown/content/component'; +import DropdownList from '/imports/ui/components/dropdown/list/component'; +import DropdownListItem from '/imports/ui/components/dropdown/list/item/component'; +import Service from '/imports/ui/components/captions/service'; const propTypes = { intl: PropTypes.shape({ @@ -20,20 +28,61 @@ const intlMessages = defineMessages({ id: 'app.actionsBar.captions.stop', description: 'Stop closed captions option', }, + changeCaption: { + id: 'app.actionsBar.changeCaption', + description: 'Open change caption label', + }, }); -const CaptionsButton = ({ intl, isActive, handleOnClick }) => ( - +const handleClickCaption = (locale, selectedLocale) => { + if (selectedLocale != locale) { + Service.setCaptionsActive(locale); + } +} + +const getAvailableCaptions = (captions, selected) => { + return( + captions.map(locale => ( + handleClickCaption(locale.locale, selected)} + iconRight={selected == locale.locale ? 'check' : null} + /> + )) + ); +}; + +const CaptionsButton = ({ intl, isActive, handleOnClick, translatedLocales, selectedLocale }) => ( +
+ + {isActive && + + + + + + + {getAvailableCaptions(translatedLocales, selectedLocale)} + + + + } +
); CaptionsButton.propTypes = propTypes; diff --git a/bigbluebutton-html5/imports/ui/components/captions/button/container.jsx b/bigbluebutton-html5/imports/ui/components/captions/button/container.jsx index add5eb0a6673..064a8b410f03 100644 --- a/bigbluebutton-html5/imports/ui/components/captions/button/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/captions/button/container.jsx @@ -12,4 +12,6 @@ export default withModalMounter(withTracker(({ mountModal }) => ({ handleOnClick: () => (Service.isCaptionsActive() ? Service.deactivateCaptions() : mountModal()), + translatedLocales: Service.getLocalesAutoTranslated(), + selectedLocale: Service.getCaptionsActive(), }))(Container)); diff --git a/bigbluebutton-html5/imports/ui/components/captions/component.jsx b/bigbluebutton-html5/imports/ui/components/captions/component.jsx index 3b5f4a5a82b5..a85692705a53 100644 --- a/bigbluebutton-html5/imports/ui/components/captions/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/captions/component.jsx @@ -9,6 +9,8 @@ import Styled from './styles'; import { PANELS, ACTIONS } from '/imports/ui/components/layout/enums'; import browserInfo from '/imports/utils/browserInfo'; import Header from '/imports/ui/components/common/control-header/component'; +import StyledHeader from '/imports/ui/components/common/control-header//styles'; +import { components } from 'react-select'; const intlMessages = defineMessages({ hide: { @@ -39,37 +41,83 @@ const intlMessages = defineMessages({ id: 'app.captions.dictationOffDesc', description: 'Aria description for button that turns off speech recognition', }, + autoTranslation: { + id: 'app.captions.pad.autoTranslation', + description: 'Label for auto translation of closed captions pad', + }, + autoTranslationDesc: { + id: 'app.captions.pad.autoTranslationDesc', + description: 'Descriotion for auto translation of closed captions pad', + }, }); const propTypes = { locale: PropTypes.string.isRequired, name: PropTypes.string.isRequired, - ownerId: PropTypes.string.isRequired, intl: PropTypes.shape({ formatMessage: PropTypes.func.isRequired, }).isRequired, - dictation: PropTypes.bool.isRequired, - dictating: PropTypes.bool.isRequired, isRTL: PropTypes.bool.isRequired, hasPermission: PropTypes.bool.isRequired, layoutContextDispatch: PropTypes.func.isRequired, isResizing: PropTypes.bool.isRequired, }; +const MultiValueRemove = (props) => { + if (props.data.isFixed) { + return null; + } + return ; +}; + const Captions = ({ locale, intl, - ownerId, name, - dictation, - dictating, + amISpeaker, isRTL, hasPermission, layoutContextDispatch, isResizing, + isAutoTranslated, + //toggleAutoTranslation, }) => { const { isChrome } = browserInfo; + const localeOptions = []; + const selectedLocales = []; + Service.getAvailableLocales().forEach((loc) => { + //The current locale not included + localeOptions.push({value: loc.locale, label: loc.name}); + if (loc.locale === locale) { + selectedLocales.push({value: loc.locale, label: loc.name, isFixed: true}); + } + }); + Service.getMyLocalesAutoTranslated().forEach((loc) => { + if (loc.locale !== locale) { + selectedLocales.push({value: loc.locale, label: loc.name}); + } + }); + + const onTranslationLocaleChanged = ( + newValue, + actionMeta + ) => { + switch (actionMeta.action) { + case 'remove-value': + case 'pop-value': + Service.removeTranslation(actionMeta.removedValue.value); + break; + case 'select-option': + Service.addTranslation(actionMeta.option.value); + break; + case 'clear': + //This would't happen.. + Service.clearTranslation(); + break; + } + }; + return (