✨(frontend) chose transcription’s language in settings
Add a key feature allowing users to choose the language of their transcription via a setting. The default value is set to French, the most commonly used language across our user base. Users can still select English or “Automatic,” which re-enables automatic language detection if no default is configured on the microservice.
This commit is contained in:
committed by
aleb_the_flash
parent
19f8c96e9d
commit
049a9079c4
@@ -13,7 +13,11 @@ import {
|
|||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { ConnectionState, RoomEvent } from 'livekit-client'
|
import { ConnectionState, RoomEvent } from 'livekit-client'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { RecordingStatus, recordingStore } from '@/stores/recording'
|
import {
|
||||||
|
RecordingLanguage,
|
||||||
|
RecordingStatus,
|
||||||
|
recordingStore,
|
||||||
|
} from '@/stores/recording'
|
||||||
import { FeatureFlags } from '@/features/analytics/enums'
|
import { FeatureFlags } from '@/features/analytics/enums'
|
||||||
import {
|
import {
|
||||||
NotificationType,
|
NotificationType,
|
||||||
@@ -31,6 +35,12 @@ import { LoginButton } from '@/components/LoginButton'
|
|||||||
import { HStack, VStack } from '@/styled-system/jsx'
|
import { HStack, VStack } from '@/styled-system/jsx'
|
||||||
import { Checkbox } from '@/primitives/Checkbox.tsx'
|
import { Checkbox } from '@/primitives/Checkbox.tsx'
|
||||||
|
|
||||||
|
import {
|
||||||
|
useSettingsDialog,
|
||||||
|
SettingsDialogExtendedKey,
|
||||||
|
useTranscriptionLanguageOptions,
|
||||||
|
} from '@/features/settings'
|
||||||
|
|
||||||
export const TranscriptSidePanel = () => {
|
export const TranscriptSidePanel = () => {
|
||||||
const { data } = useConfig()
|
const { data } = useConfig()
|
||||||
|
|
||||||
@@ -45,6 +55,9 @@ export const TranscriptSidePanel = () => {
|
|||||||
const recordingSnap = useSnapshot(recordingStore)
|
const recordingSnap = useSnapshot(recordingStore)
|
||||||
|
|
||||||
const { notifyParticipants } = useNotifyParticipants()
|
const { notifyParticipants } = useNotifyParticipants()
|
||||||
|
const languageOptions = useTranscriptionLanguageOptions()
|
||||||
|
|
||||||
|
const { openSettingsDialog } = useSettingsDialog()
|
||||||
|
|
||||||
const hasTranscriptAccess = useHasRecordingAccess(
|
const hasTranscriptAccess = useHasRecordingAccess(
|
||||||
RecordingMode.Transcript,
|
RecordingMode.Transcript,
|
||||||
@@ -115,7 +128,9 @@ export const TranscriptSidePanel = () => {
|
|||||||
: RecordingMode.Transcript
|
: RecordingMode.Transcript
|
||||||
|
|
||||||
const recordingOptions = {
|
const recordingOptions = {
|
||||||
language: 'fr', // fix hardcoded language
|
...(recordingSnap.language != RecordingLanguage.AUTOMATIC && {
|
||||||
|
language: recordingSnap.language,
|
||||||
|
}),
|
||||||
...(includeScreenRecording && { transcribe: true }),
|
...(includeScreenRecording && { transcribe: true }),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,9 +474,27 @@ export const TranscriptSidePanel = () => {
|
|||||||
<div
|
<div
|
||||||
className={css({
|
className={css({
|
||||||
flex: 5,
|
flex: 5,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '0.25rem',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Text variant="sm">{t('details.language')}</Text>
|
<Text variant="sm">{t('details.language')}</Text>
|
||||||
|
<Text variant="sm">
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
size="xs"
|
||||||
|
onPress={() =>
|
||||||
|
openSettingsDialog(SettingsDialogExtendedKey.TRANSCRIPTION)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
languageOptions.find(
|
||||||
|
(option) => option.key == recordingSnap.language
|
||||||
|
)?.label
|
||||||
|
}
|
||||||
|
</Button>
|
||||||
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { NotificationsTab } from './tabs/NotificationsTab'
|
|||||||
import { GeneralTab } from './tabs/GeneralTab'
|
import { GeneralTab } from './tabs/GeneralTab'
|
||||||
import { AudioTab } from './tabs/AudioTab'
|
import { AudioTab } from './tabs/AudioTab'
|
||||||
import { VideoTab } from './tabs/VideoTab'
|
import { VideoTab } from './tabs/VideoTab'
|
||||||
|
import { TranscriptionTab } from './tabs/TranscriptionTab'
|
||||||
import { useRef } from 'react'
|
import { useRef } from 'react'
|
||||||
import { useMediaQuery } from '@/features/rooms/livekit/hooks/useMediaQuery'
|
import { useMediaQuery } from '@/features/rooms/livekit/hooks/useMediaQuery'
|
||||||
import { SettingsDialogExtendedKey } from '@/features/settings/type'
|
import { SettingsDialogExtendedKey } from '@/features/settings/type'
|
||||||
@@ -100,6 +101,11 @@ export const SettingsDialogExtended = (props: SettingsDialogExtended) => {
|
|||||||
{isWideScreen &&
|
{isWideScreen &&
|
||||||
t(`tabs.${SettingsDialogExtendedKey.NOTIFICATIONS}`)}
|
t(`tabs.${SettingsDialogExtendedKey.NOTIFICATIONS}`)}
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab icon highlight id={SettingsDialogExtendedKey.TRANSCRIPTION}>
|
||||||
|
<span className="material-symbols">speech_to_text</span>
|
||||||
|
{isWideScreen &&
|
||||||
|
t(`tabs.${SettingsDialogExtendedKey.TRANSCRIPTION}`)}
|
||||||
|
</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
</div>
|
</div>
|
||||||
<div className={tabPanelContainerStyle}>
|
<div className={tabPanelContainerStyle}>
|
||||||
@@ -111,6 +117,8 @@ export const SettingsDialogExtended = (props: SettingsDialogExtended) => {
|
|||||||
<VideoTab id={SettingsDialogExtendedKey.VIDEO} />
|
<VideoTab id={SettingsDialogExtendedKey.VIDEO} />
|
||||||
<GeneralTab id={SettingsDialogExtendedKey.GENERAL} />
|
<GeneralTab id={SettingsDialogExtendedKey.GENERAL} />
|
||||||
<NotificationsTab id={SettingsDialogExtendedKey.NOTIFICATIONS} />
|
<NotificationsTab id={SettingsDialogExtendedKey.NOTIFICATIONS} />
|
||||||
|
{/* Transcription tab won't be accessible if the tab is not active in the tab list */}
|
||||||
|
<TranscriptionTab id={SettingsDialogExtendedKey.TRANSCRIPTION} />
|
||||||
</div>
|
</div>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { TabPanel, TabPanelProps } from '@/primitives/Tabs'
|
||||||
|
import { Field, H } from '@/primitives'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { RecordingLanguage, recordingStore } from '@/stores/recording'
|
||||||
|
import { useSnapshot } from 'valtio'
|
||||||
|
import { useTranscriptionLanguageOptions } from '../../hook/useTranscriptionLanguageOptions'
|
||||||
|
|
||||||
|
export type TranscriptionTabProps = Pick<TabPanelProps, 'id'>
|
||||||
|
|
||||||
|
export const TranscriptionTab = ({ id }: TranscriptionTabProps) => {
|
||||||
|
const { t } = useTranslation('settings', { keyPrefix: 'transcription' })
|
||||||
|
const recordingSnap = useSnapshot(recordingStore)
|
||||||
|
|
||||||
|
const languageOptions = useTranscriptionLanguageOptions()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TabPanel padding={'md'} flex id={id}>
|
||||||
|
<H lvl={2}>{t('heading')}</H>
|
||||||
|
<Field
|
||||||
|
type="select"
|
||||||
|
label={t('language.label')}
|
||||||
|
items={languageOptions}
|
||||||
|
selectedKey={recordingSnap.language}
|
||||||
|
onSelectionChange={(lang) => {
|
||||||
|
recordingStore.language = lang as RecordingLanguage
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
import { RecordingLanguage } from '@/stores/recording'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
export const useTranscriptionLanguageOptions = () => {
|
||||||
|
const { t } = useTranslation('settings', { keyPrefix: 'transcription' })
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
key: RecordingLanguage.FRENCH,
|
||||||
|
value: RecordingLanguage.FRENCH,
|
||||||
|
label: t('language.options.french'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: RecordingLanguage.ENGLISH,
|
||||||
|
value: RecordingLanguage.ENGLISH,
|
||||||
|
label: t('language.options.english'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: RecordingLanguage.AUTOMATIC,
|
||||||
|
value: RecordingLanguage.AUTOMATIC,
|
||||||
|
label: t('language.options.auto'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[t]
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,2 +1,7 @@
|
|||||||
export { SettingsButton } from './components/SettingsButton'
|
export { SettingsButton } from './components/SettingsButton'
|
||||||
export { SettingsDialog } from './components/SettingsDialog'
|
export { SettingsDialog } from './components/SettingsDialog'
|
||||||
|
|
||||||
|
export { useTranscriptionLanguageOptions } from './hook/useTranscriptionLanguageOptions'
|
||||||
|
export { useSettingsDialog } from './hook/useSettingsDialog'
|
||||||
|
|
||||||
|
export { SettingsDialogExtendedKey } from './type.ts'
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ export enum SettingsDialogExtendedKey {
|
|||||||
VIDEO = 'video',
|
VIDEO = 'video',
|
||||||
GENERAL = 'general',
|
GENERAL = 'general',
|
||||||
NOTIFICATIONS = 'notifications',
|
NOTIFICATIONS = 'notifications',
|
||||||
|
TRANSCRIPTION = 'transcription',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -317,7 +317,7 @@
|
|||||||
"receiver": "Das Transkript wird an den Organisator und die Mitorganisatoren gesendet.",
|
"receiver": "Das Transkript wird an den Organisator und die Mitorganisatoren gesendet.",
|
||||||
"destination": "Ein neues Dokument wird erstellt auf",
|
"destination": "Ein neues Dokument wird erstellt auf",
|
||||||
"destinationUnknown": "Ein neues Dokument wird erstellt",
|
"destinationUnknown": "Ein neues Dokument wird erstellt",
|
||||||
"language": "Meeting-Sprachen: Französisch (fr)",
|
"language": "Meeting-Sprache:",
|
||||||
"recording": "Auch eine Aufzeichnung starten"
|
"recording": "Auch eine Aufzeichnung starten"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
|
|||||||
@@ -68,6 +68,17 @@
|
|||||||
},
|
},
|
||||||
"permissionsRequired": "Berechtigungen erforderlich"
|
"permissionsRequired": "Berechtigungen erforderlich"
|
||||||
},
|
},
|
||||||
|
"transcription": {
|
||||||
|
"heading": "Transkription",
|
||||||
|
"language": {
|
||||||
|
"label": "Besprechungssprache",
|
||||||
|
"options": {
|
||||||
|
"french": "Französisch (fr)",
|
||||||
|
"english": "Englisch (en)",
|
||||||
|
"auto": "Automatisch"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"heading": "Tonbenachrichtigungen",
|
"heading": "Tonbenachrichtigungen",
|
||||||
"label": "Tonbenachrichtigungen für",
|
"label": "Tonbenachrichtigungen für",
|
||||||
@@ -100,6 +111,7 @@
|
|||||||
"audio": "Audio",
|
"audio": "Audio",
|
||||||
"video": "Video",
|
"video": "Video",
|
||||||
"general": "Allgemein",
|
"general": "Allgemein",
|
||||||
"notifications": "Benachrichtigungen"
|
"notifications": "Benachrichtigungen",
|
||||||
|
"transcription": "Transkription"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -317,7 +317,7 @@
|
|||||||
"receiver": "The transcript will be sent to the host and co-hosts.",
|
"receiver": "The transcript will be sent to the host and co-hosts.",
|
||||||
"destination": "A new document will be created on",
|
"destination": "A new document will be created on",
|
||||||
"destinationUnknown": "A new document will be created",
|
"destinationUnknown": "A new document will be created",
|
||||||
"language": "Meeting language: French (fr)",
|
"language": "Meeting language:",
|
||||||
"recording": "Also start a recording"
|
"recording": "Also start a recording"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
|
|||||||
@@ -68,6 +68,17 @@
|
|||||||
},
|
},
|
||||||
"permissionsRequired": "Permissions required"
|
"permissionsRequired": "Permissions required"
|
||||||
},
|
},
|
||||||
|
"transcription": {
|
||||||
|
"heading": "Transcription",
|
||||||
|
"language": {
|
||||||
|
"label": "Meeting language",
|
||||||
|
"options": {
|
||||||
|
"french": "French (fr)",
|
||||||
|
"english": "English (en)",
|
||||||
|
"auto": "Automatic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"heading": "Sound notifications",
|
"heading": "Sound notifications",
|
||||||
"label": "sound notifications for",
|
"label": "sound notifications for",
|
||||||
@@ -100,6 +111,7 @@
|
|||||||
"audio": "Audio",
|
"audio": "Audio",
|
||||||
"video": "Video",
|
"video": "Video",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"notifications": "Notifications"
|
"notifications": "Notifications",
|
||||||
|
"transcription": "Transcription"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -317,7 +317,7 @@
|
|||||||
"receiver": "La transcription sera envoyée à l'organisateur et aux coorganisateurs.",
|
"receiver": "La transcription sera envoyée à l'organisateur et aux coorganisateurs.",
|
||||||
"destination": "Un nouveau document sera créé sur",
|
"destination": "Un nouveau document sera créé sur",
|
||||||
"destinationUnknown": "Un nouveau document sera créé",
|
"destinationUnknown": "Un nouveau document sera créé",
|
||||||
"language": "Langues de la réunion : Français (fr)",
|
"language": "Langue de la réunion :",
|
||||||
"recording": "Démarrer aussi un enregistrement"
|
"recording": "Démarrer aussi un enregistrement"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
|
|||||||
@@ -68,6 +68,17 @@
|
|||||||
},
|
},
|
||||||
"permissionsRequired": "Autorisations nécessaires"
|
"permissionsRequired": "Autorisations nécessaires"
|
||||||
},
|
},
|
||||||
|
"transcription": {
|
||||||
|
"heading": "Transcription",
|
||||||
|
"language": {
|
||||||
|
"label": "Langue de la réunion",
|
||||||
|
"options": {
|
||||||
|
"french": "Français (fr)",
|
||||||
|
"english": "Anglais (en)",
|
||||||
|
"auto": "Automatique"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"heading": "Notifications sonores",
|
"heading": "Notifications sonores",
|
||||||
"label": "la notification sonore pour",
|
"label": "la notification sonore pour",
|
||||||
@@ -100,6 +111,7 @@
|
|||||||
"audio": "Audio",
|
"audio": "Audio",
|
||||||
"video": "Vidéo",
|
"video": "Vidéo",
|
||||||
"general": "Général",
|
"general": "Général",
|
||||||
"notifications": "Notifications"
|
"notifications": "Notifications",
|
||||||
|
"transcription": "Transcription"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -317,7 +317,7 @@
|
|||||||
"receiver": "Het transcript wordt verzonden naar de organisator en medeorganisatoren.",
|
"receiver": "Het transcript wordt verzonden naar de organisator en medeorganisatoren.",
|
||||||
"destination": "Er wordt een nieuw document aangemaakt op",
|
"destination": "Er wordt een nieuw document aangemaakt op",
|
||||||
"destinationUnknown": "Een nieuw document wordt aangemaakt",
|
"destinationUnknown": "Een nieuw document wordt aangemaakt",
|
||||||
"language": "Vergadertalen: Frans (fr)",
|
"language": "Vergadertalen:",
|
||||||
"recording": "Start ook een opname"
|
"recording": "Start ook een opname"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
|
|||||||
@@ -68,6 +68,17 @@
|
|||||||
},
|
},
|
||||||
"permissionsRequired": "Machtigingen vereist"
|
"permissionsRequired": "Machtigingen vereist"
|
||||||
},
|
},
|
||||||
|
"transcription": {
|
||||||
|
"heading": "Transcriptie",
|
||||||
|
"language": {
|
||||||
|
"label": "Vergadertaal",
|
||||||
|
"options": {
|
||||||
|
"french": "Frans (fr)",
|
||||||
|
"english": "Engels (en)",
|
||||||
|
"auto": "Automatisch"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"heading": "Geluidsmeldingen",
|
"heading": "Geluidsmeldingen",
|
||||||
"label": "Geluidsmeldingen voor",
|
"label": "Geluidsmeldingen voor",
|
||||||
@@ -100,6 +111,7 @@
|
|||||||
"audio": "Audio",
|
"audio": "Audio",
|
||||||
"video": "Video",
|
"video": "Video",
|
||||||
"general": "Algemeen",
|
"general": "Algemeen",
|
||||||
"notifications": "Meldingen"
|
"notifications": "Meldingen",
|
||||||
|
"transcription": "Transcriptie"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { proxy } from 'valtio'
|
import { proxy } from 'valtio'
|
||||||
|
|
||||||
|
export enum RecordingLanguage {
|
||||||
|
ENGLISH = 'en',
|
||||||
|
FRENCH = 'fr',
|
||||||
|
AUTOMATIC = 'auto',
|
||||||
|
}
|
||||||
|
|
||||||
export enum RecordingStatus {
|
export enum RecordingStatus {
|
||||||
TRANSCRIPT_STARTING,
|
TRANSCRIPT_STARTING,
|
||||||
TRANSCRIPT_STARTED,
|
TRANSCRIPT_STARTED,
|
||||||
@@ -13,8 +19,10 @@ export enum RecordingStatus {
|
|||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
status: RecordingStatus
|
status: RecordingStatus
|
||||||
|
language: RecordingLanguage
|
||||||
}
|
}
|
||||||
|
|
||||||
export const recordingStore = proxy<State>({
|
export const recordingStore = proxy<State>({
|
||||||
status: RecordingStatus.STOPPED,
|
status: RecordingStatus.STOPPED,
|
||||||
|
language: RecordingLanguage.FRENCH,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user