Skip to content

Commit

Permalink
Merge pull request #606 from nature-heart-software/feat/add-custom-tr…
Browse files Browse the repository at this point in the history
…anslation

Add custom translation API
  • Loading branch information
Wurielle committed Feb 22, 2023
2 parents e699ab8 + a39729d commit 1a76ff5
Show file tree
Hide file tree
Showing 13 changed files with 1,100 additions and 78 deletions.
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
<template>
<NvStack spacing="5">
<NvGroup justify="apart" no-wrap spacing="5">
<NvStack>
<NvText type="label">Enable speech-to-text-to-speech</NvText>
<NvText type="caption">This might prevent your device from going to sleep</NvText>
</NvStack>
<NvSwitch
:modelValue="settingsStore.enableSTTTS"
class="shrink-0"
@update:modelValue="(value) => settingsStore.$patch({ enableSTTTS: value })"
/>
</NvGroup>
<template v-if="settingsStore.enableSTTTS">
<NvGoogleCloudCredentialsFormPart>
Izabela uses Google Cloud Speech for speech recognition which requires Google Cloud
Credentials to be imported
</NvGoogleCloudCredentialsFormPart>
<template v-if="!!googleCloudSpeechCredentialsPath">
<NvStack spacing="5">
<NvDivider direction="horizontal" />

<NvGoogleCloudCredentialsFormPart>
Izabela uses Google Cloud Speech for speech recognition which requires Google Cloud
Credentials to be imported
</NvGoogleCloudCredentialsFormPart>
<template v-if="!!googleCloudSpeechCredentialsPath">
<NvGroup justify="apart" no-wrap spacing="5">
<NvStack>
<NvText type="label">Enable speech-to-text-to-speech</NvText>
<NvText type="caption">This might prevent your device from going to sleep</NvText>
</NvStack>
<NvSwitch
:modelValue="settingsStore.enableSTTTS"
class="shrink-0"
@update:modelValue="(value) => settingsStore.$patch({ enableSTTTS: value })"
/>
</NvGroup>
<template v-if="settingsStore.enableSTTTS">
<NvDivider direction="horizontal" />
<NvGroup justify="apart" no-wrap>
<NvText type="label"> Speech recognition language </NvText>
<NvText type="label"> Speech recognition language</NvText>
<NvSpeechInputLanguageSelect />
</NvGroup>
<NvDivider direction="horizontal" />
<NvGroup justify="apart" no-wrap>
<NvText type="label"> Speech recognition strategy </NvText>
<NvText type="label"> Speech recognition strategy</NvText>
<NvSpeechRecognitionStrategySelect />
</NvGroup>
<template
Expand Down
10 changes: 10 additions & 0 deletions apps/app/src/features/settings/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ export const useSettingsStore = defineStore(
const speechInputLanguage = ref('en-US')
const enableTranslation = ref(false)
const speechRecognitionStrategy = ref<'continuous-native' | 'continuous-web' | 'ptr'>('ptr')
const textTranslationStrategy = ref<'cloud-translation' | 'custom'>('cloud-translation')
const customTextTranslationEndpoint = ref('')
const customTextTranslationApiKey = ref('')
const customTextTranslationFrom = ref('')
const customTextTranslationTo = ref('')
const keybindings = ref<Record<string, Key[]>>({
recordAudio: [
{
Expand Down Expand Up @@ -140,6 +145,11 @@ export const useSettingsStore = defineStore(
textOutputLanguage,
speechInputLanguage,
enableTranslation,
textTranslationStrategy,
customTextTranslationEndpoint,
customTextTranslationApiKey,
customTextTranslationFrom,
customTextTranslationTo,
}
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<template>
<NvSelect
ref="select"
v-loading="isFetching"
:autocompleteWidth="width"
:modelValue="settingsStore.customTextTranslationFrom"
:options="options"
placeholder="Select a language"
@update:modelValue="(value) => settingsStore.$patch({ customTextTranslationFrom: value })"
/>
</template>
<script lang="ts" setup>
import { NvSelect } from '@packages/ui'
import { useElementSize } from '@vueuse/core'
import { computed, ref } from 'vue'
import { useSettingsStore } from '@/features/settings/store'
import { useGetCustomLanguagesQuery } from '@/features/translation/hooks/useGetCustomLanguagesQuery'
const settingsStore = useSettingsStore()
const select = ref()
const { width } = useElementSize(select)
const { data, isFetching } = useGetCustomLanguagesQuery()
const options = computed(
() =>
data.value?.from.map((language) => ({
label: language.name,
value: language.id,
})) || [],
)
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<template>
<NvSelect
ref="select"
v-loading="isFetching"
:autocompleteWidth="width"
:modelValue="settingsStore.customTextTranslationTo"
:options="options"
placeholder="Select a language"
@update:modelValue="(value) => settingsStore.$patch({ customTextTranslationTo: value })"
/>
</template>
<script lang="ts" setup>
import { NvSelect } from '@packages/ui'
import { useElementSize } from '@vueuse/core'
import { computed, ref } from 'vue'
import { useSettingsStore } from '@/features/settings/store'
import { useGetCustomLanguagesQuery } from '@/features/translation/hooks/useGetCustomLanguagesQuery'
const settingsStore = useSettingsStore()
const select = ref()
const { width } = useElementSize(select)
const { data, isFetching } = useGetCustomLanguagesQuery()
const options = computed(
() =>
data.value?.to.map((language) => ({
label: language.name,
value: language.id,
})) || [],
)
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<template>
<NvSelect
ref="select"
:autocompleteWidth="width"
:modelValue="settingsStore.textTranslationStrategy"
:options="[
{
label: 'Cloud Translation',
value: 'cloud-translation',
},
{
label: 'Custom',
value: 'custom',
},
]"
class="shrink-0"
@update:modelValue="(value) => settingsStore.$patch({ textTranslationStrategy: value })"
/>
</template>
<script lang="ts" setup>
import { NvSelect } from '@packages/ui'
import { useElementSize } from '@vueuse/core'
import { ref } from 'vue'
import { useSettingsStore } from '@/features/settings/store'
const settingsStore = useSettingsStore()
const select = ref()
const { width } = useElementSize(select)
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useQuery } from 'vue-query'
import axios from 'axios'
import { useSettingsStore } from '@/features/settings/store'
import { decrypt } from '@/utils/security'

export const useGetCustomLanguagesQueryKey = () => ['get-custom-languages']
export const useGetCustomLanguagesQuery = () =>
useQuery(useGetCustomLanguagesQueryKey(), async () => {
const settingsStore = useSettingsStore()
const endpoint = settingsStore.customTextTranslationEndpoint
const { data } = await axios.post<{
from: {
id: string
name: string
}[]
to: {
id: string
name: string
}[]
}>(`${endpoint.endsWith('/') ? endpoint.slice(0, -1) : endpoint}/languages`, {
credentials: {
apiKey: decrypt(settingsStore.customTextTranslationApiKey),
},
})
return data
})
34 changes: 31 additions & 3 deletions apps/app/src/modules/electron-translation/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { useSettingsStore } from '@/features/settings/store'
import { v2 } from '@google-cloud/translate'
import axios from 'axios'
import { decrypt } from '@/utils/security'

export const ElectronTranslation = () => ({
async translate(
Expand All @@ -10,9 +13,34 @@ export const ElectronTranslation = () => ({
format?: string
},
): Promise<string> {
const translate = new v2.Translate()
const [translations] = await translate.translate(text, options)
return translations
try {
const settingsStore = useSettingsStore()
if (settingsStore.textTranslationStrategy === 'cloud-translation') {
const translate = new v2.Translate()
const [translations] = await translate.translate(text, options)
return translations
}
if (settingsStore.textTranslationStrategy === 'custom') {
const endpoint = settingsStore.customTextTranslationEndpoint
const { data } = await axios.post<string>(
`${endpoint.endsWith('/') ? endpoint.slice(0, -1) : endpoint}/translate`,
{
credentials: {
apiKey: decrypt(settingsStore.customTextTranslationApiKey),
},
payload: {
text,
from: settingsStore.customTextTranslationFrom,
to: settingsStore.customTextTranslationTo,
},
},
)
return data
}
return text
} catch (error) {
return text
}
},
})

Expand Down
16 changes: 8 additions & 8 deletions apps/app/src/teams/messenger/components/NvHistoryMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
<NvStack class="!flex-1 min-h-0">
<NvStack>
<NvText>{{ message.originalMessage || id }}</NvText>
<NvGroup v-if="message.translatedMessage">
<NvIcon name="english-to-chinese" size="3"/>
<NvGroup v-if="message.translatedMessage" align="start" noWrap>
<NvIcon name="english-to-chinese" size="3" />
<NvText>{{ message.translatedMessage || id }}</NvText>
</NvGroup>
</NvStack>
Expand Down Expand Up @@ -53,7 +53,7 @@
},
]"
>
<NvButton class="shrink-0" icon-name="ellipsis-v" size="sm"/>
<NvButton class="shrink-0" icon-name="ellipsis-v" size="sm" />
</NvContextMenu>
</NvGroup>
<div v-if="isPlaying" class="h-2 relative bg-gray-10">
Expand Down Expand Up @@ -120,9 +120,9 @@ const downloadMessageLocally = async () => {
reader.onload = () => {
ElectronFilesystem.downloadMessagePrompt(
completeMessage,
`${ formatedCreatedAt.value } - ${ engine.value?.name } - ${ engine.value?.getVoiceName(
`${formatedCreatedAt.value} - ${engine.value?.name} - ${engine.value?.getVoiceName(
message.value?.voice,
) } - ${ message.value?.message }`.replace(/([^a-z0-9\s-]+)/gi, '_'),
)} - ${message.value?.message}`.replace(/([^a-z0-9\s-]+)/gi, '_'),
reader.result as string,
).finally(() => {
downloading.value = false
Expand All @@ -138,9 +138,9 @@ const downloadMessageLocally = async () => {
const playMessage = computed(() =>
message.value
? {
...message.value,
excludeFromHistory: true,
}
...message.value,
excludeFromHistory: true,
}
: undefined,
)
const { play, isPlaying, isLoading, progress } = usePlayMessage(playMessage)
Expand Down
39 changes: 32 additions & 7 deletions apps/app/src/teams/messenger/components/NvMessengerMessageBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
@update:modelValue="(value) => settingsStore.$patch({ enableTranslation: value })"
/>
</NvGroup>
<NvDivider direction="horizontal" />
<NvFormItem label="Translation strategy">
<NvTranslationStrategySelect />
</NvFormItem>
<NvAccessBlocker
:allowed="!!googleCloudSpeechCredentialsPath && settingsStore.enableTranslation"
:reason="
Expand All @@ -44,13 +48,31 @@
>
<NvStack spacing="4">
<NvDivider direction="horizontal" />
<NvFormItem label="From">
<NvTranslationFromSelect />
</NvFormItem>
<NvDivider direction="horizontal" />
<NvFormItem label="To">
<NvTranslationToSelect />
</NvFormItem>
<template v-if="settingsStore.textTranslationStrategy === 'cloud-translation'">
<NvFormItem label="From">
<NvTranslationFromSelect />
</NvFormItem>
<NvDivider direction="horizontal" />
<NvFormItem label="To">
<NvTranslationToSelect />
</NvFormItem>
</template>
<template v-if="settingsStore.textTranslationStrategy === 'custom'">
<NvAccessBlocker
:allowed="!!settingsStore.customTextTranslationEndpoint"
reason="Endpoint and/or credentials required"
>
<NvStack spacing="4">
<NvFormItem label="From">
<NvCustomTranslationFromSelect />
</NvFormItem>
<NvDivider direction="horizontal" />
<NvFormItem label="To">
<NvCustomTranslationToSelect />
</NvFormItem>
</NvStack>
</NvAccessBlocker>
</template>
</NvStack>
</NvAccessBlocker>
</NvStack>
Expand Down Expand Up @@ -118,7 +140,10 @@ import { inject } from 'vue'
import { useRoute } from 'vue-router'
import NvTranslationFromSelect from '@/features/translation/components/inputs/NvTranslationFromSelect.vue'
import NvTranslationToSelect from '@/features/translation/components/inputs/NvTranslationToSelect.vue'
import NvCustomTranslationFromSelect from '@/features/translation/components/inputs/NvCustomTranslationFromSelect.vue'
import NvCustomTranslationToSelect from '@/features/translation/components/inputs/NvCustomTranslationToSelect.vue'
import { useGetGoogleCloudSpeechCredentialsPath } from '@/features/settings/hooks'
import NvTranslationStrategySelect from '@/features/translation/components/inputs/NvTranslationStrategySelect.vue'
const settingsStore = useSettingsStore()
const messengerContext = inject('messenger')
Expand Down
Loading

0 comments on commit 1a76ff5

Please sign in to comment.