mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-22 03:57:47 -04:00
feat(profile): configurable voice for pronunciation (defaulting to the first voice of the locale or gb)
This commit is contained in:
parent
85c6e38d1b
commit
60ce203a11
@ -4,7 +4,6 @@ import type { Opinion } from '#shared/opinions.ts';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
word: string;
|
||||
pronunciation?: string | null;
|
||||
opinion: string;
|
||||
link?: unknown;
|
||||
escape?: boolean;
|
||||
@ -42,7 +41,7 @@ const op = computed((): (Opinion & { description: string }) | null => {
|
||||
</Tooltip>
|
||||
<nuxt-link v-if="link" :to="link" :class="`colour-${op.colour || 'default'}`"><Spelling :escape="escape" :text="word" /></nuxt-link>
|
||||
<Spelling v-else :escape="escape" :markdown="markdown" :text="word" class="d-inline-block" />
|
||||
<Pronunciation v-if="pronunciation" :pronunciation="pronunciation" text />
|
||||
<slot></slot>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
@ -186,10 +186,16 @@ const usedOpinions = computed(() => {
|
||||
:opinion="s.el.opinion"
|
||||
:escape="false"
|
||||
:markdown="profile.markdown"
|
||||
:pronunciation="s.el.pronunciation"
|
||||
:link="config.locale === 'tok' && config.pronouns.enabled ? `${config.pronouns.prefix}/${s.el.value}` : null"
|
||||
:custom-opinions="profile.opinions"
|
||||
/>
|
||||
>
|
||||
<Pronunciation
|
||||
v-if="s.el.pronunciation"
|
||||
:pronunciation="s.el.pronunciation"
|
||||
text
|
||||
:voices="s.el.voice !== null ? [s.el.voice] : []"
|
||||
/>
|
||||
</Opinion>
|
||||
</template>
|
||||
</ExpandableList>
|
||||
</div>
|
||||
|
@ -2,18 +2,19 @@
|
||||
import type { VoiceKey } from '#shared/pronunciation/voices.ts';
|
||||
import useConfig from '~/composables/useConfig.ts';
|
||||
|
||||
defineProps<{
|
||||
withDefaults(defineProps<{
|
||||
pronunciation: string;
|
||||
text?: boolean;
|
||||
}>();
|
||||
voices?: VoiceKey[];
|
||||
}>(), {
|
||||
voices: () => {
|
||||
const config = useConfig();
|
||||
|
||||
const config = useConfig();
|
||||
|
||||
const voices = computed((): VoiceKey[] => {
|
||||
if (!config.pronunciation?.enabled) {
|
||||
return [];
|
||||
}
|
||||
return config.pronunciation.voices;
|
||||
if (!config.pronunciation?.enabled) {
|
||||
return [];
|
||||
}
|
||||
return config.pronunciation.voices;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -1,16 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { escapePronunciationString, unescapePronunciationString } from '#shared/helpers.ts';
|
||||
import { voices } from '#shared/pronunciation/voices.ts';
|
||||
import type { VoiceKey } from '#shared/pronunciation/voices.ts';
|
||||
import useConfig from '~/composables/useConfig.ts';
|
||||
|
||||
const modelValue = defineModel<string | null>({ required: true });
|
||||
const pronounciationModelValue = defineModel<string | null>('pronounciation', { required: true });
|
||||
const voiceModelValue = defineModel<VoiceKey | null>('voice', { required: true });
|
||||
|
||||
const config = useConfig();
|
||||
|
||||
const rawPronunciation = computed({
|
||||
get(): string {
|
||||
if (modelValue.value) {
|
||||
const phonemes = modelValue.value.substring(1, modelValue.value.length - 1);
|
||||
if (pronounciationModelValue.value) {
|
||||
const phonemes = pronounciationModelValue.value.substring(1, pronounciationModelValue.value.length - 1);
|
||||
return unescapePronunciationString(phonemes);
|
||||
} else {
|
||||
return '';
|
||||
@ -24,20 +26,33 @@ const rawPronunciation = computed({
|
||||
pronunciation = null;
|
||||
}
|
||||
|
||||
modelValue.value = pronunciation;
|
||||
pronounciationModelValue.value = pronunciation;
|
||||
},
|
||||
});
|
||||
|
||||
const voices = computed((): VoiceKey[] => {
|
||||
if (!config.pronunciation?.enabled) {
|
||||
return [];
|
||||
watch(pronounciationModelValue, (value, oldValue) => {
|
||||
if (oldValue !== null) {
|
||||
return;
|
||||
}
|
||||
return config.pronunciation.voices;
|
||||
// reset voice to default voice
|
||||
if (!config.pronunciation?.enabled) {
|
||||
voiceModelValue.value = 'gb';
|
||||
} else {
|
||||
voiceModelValue.value = config.pronunciation.voices[0];
|
||||
}
|
||||
});
|
||||
|
||||
const sortedVoices = computed(() => {
|
||||
const voicesOfLocale = config.pronunciation?.enabled ? config.pronunciation.voices : [];
|
||||
|
||||
return (Object.keys(voices) as VoiceKey[]).toSorted((a, b) => {
|
||||
return (voicesOfLocale.includes(b) ? 1 : 0) - (voicesOfLocale.includes(a) ? 1 : 0);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="input-group input-group-sm w-auto">
|
||||
<div class="input-group input-group-sm w-50">
|
||||
<span class="input-group-text">/</span>
|
||||
<input
|
||||
v-model="rawPronunciation"
|
||||
@ -46,12 +61,28 @@ const voices = computed((): VoiceKey[] => {
|
||||
maxlength="255"
|
||||
>
|
||||
<span class="input-group-text">/</span>
|
||||
<PronunciationSpeaker
|
||||
v-for="voice in voices"
|
||||
:key="voice"
|
||||
class="btn btn-sm rounded-start-0 btn-outline-secondary"
|
||||
:pronunciation="modelValue"
|
||||
:voice="voice"
|
||||
/>
|
||||
<template v-if="pronounciationModelValue !== null">
|
||||
<select
|
||||
v-model="voiceModelValue"
|
||||
class="form-control"
|
||||
:class="voiceModelValue === null ? 'text-muted' : ''"
|
||||
>
|
||||
<option :value="null">
|
||||
<T>profile.pronunciation.voice.without</T>
|
||||
</option>
|
||||
<option v-for="voice of sortedVoices" :key="voice" :value="voice">
|
||||
{{ voice.toUpperCase() }}
|
||||
</option>
|
||||
</select>
|
||||
<PronunciationSpeaker
|
||||
v-if="voiceModelValue !== null"
|
||||
class="btn btn-sm rounded-start-0 btn-outline-secondary"
|
||||
:pronunciation="pronounciationModelValue"
|
||||
:voice="voiceModelValue"
|
||||
/>
|
||||
<button v-else class="btn btn-sm rounded-start-0 btn-outline-secondary" type="button" disabled>
|
||||
<Icon v="volume-slash" />
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -465,13 +465,16 @@ const propagateChanged = (field: string, checked: boolean): void => {
|
||||
</p>
|
||||
<OpinionListInput
|
||||
v-model="formData.names"
|
||||
:prototype="{ value: '', opinion: 'meh', pronunciation: null }"
|
||||
:prototype="{ value: '', opinion: 'meh', pronunciation: null, voice: null }"
|
||||
:custom-opinions="formData.opinions"
|
||||
:maxitems="128"
|
||||
:maxlength="config.profile.longNames ? 255 : 32"
|
||||
>
|
||||
<template #additional="s">
|
||||
<PronunciationInput v-model="s.val.pronunciation" />
|
||||
<PronunciationInput
|
||||
v-model:pronounciation="s.val.pronunciation"
|
||||
v-model:voice="s.val.voice"
|
||||
/>
|
||||
</template>
|
||||
</OpinionListInput>
|
||||
<InlineMarkdownInstructions v-model="formData.markdown" />
|
||||
|
@ -728,6 +728,8 @@ profile:
|
||||
names: 'Names'
|
||||
pronunciation:
|
||||
ipa: 'Pronunciation using IPA'
|
||||
voice:
|
||||
without: '(without voice)'
|
||||
pronouns: 'Pronouns'
|
||||
pronounsInfo: >
|
||||
You can enter a <strong>pronoun</strong> (eg. “they” or “she/her”)
|
||||
|
@ -488,11 +488,7 @@ pronouns:
|
||||
|
||||
pronunciation:
|
||||
enabled: true
|
||||
voices:
|
||||
DE:
|
||||
language: 'de-DE'
|
||||
voice: 'Vicki'
|
||||
engine: 'standard'
|
||||
voices: ['de']
|
||||
|
||||
sources:
|
||||
enabled: true
|
||||
|
@ -844,6 +844,8 @@ profile:
|
||||
names: 'Namen'
|
||||
pronunciation:
|
||||
ipa: 'Aussprache in IPA'
|
||||
voice:
|
||||
without: '(ohne Sprachausgabe)'
|
||||
pronouns: 'Pronomen'
|
||||
pronounsInfo: >
|
||||
Du kannst entweder ein <strong>Pronomen</strong> (z.B. „sier“ oder „sie/ihr“) oder einen <strong>Link</strong> (z.B. „https://pronomen.net/dey“)
|
||||
|
@ -944,6 +944,8 @@ profile:
|
||||
names: 'Names'
|
||||
pronunciation:
|
||||
ipa: 'Pronunciation using IPA'
|
||||
voice:
|
||||
without: '(without voice)'
|
||||
pronouns: 'Pronouns'
|
||||
pronounsInfo: >
|
||||
You can enter a <strong>pronoun</strong> (eg. “they” or “she/her”)
|
||||
|
20
migrations/095-profile-pronunciation-voice.sql
Normal file
20
migrations/095-profile-pronunciation-voice.sql
Normal file
@ -0,0 +1,20 @@
|
||||
-- Up
|
||||
|
||||
update profiles
|
||||
set names = (select json_group_array(case
|
||||
when json_type(names.value, '$.pronunciation') is 'text'
|
||||
then json_set(names.value, '$.voice',
|
||||
case
|
||||
when locale in ('en', 'eo', 'fo', 'hu', 'tok', 'ua', 'vi')
|
||||
then 'gb'
|
||||
when locale = 'et' then 'fi'
|
||||
when locale = 'lad' then 'es'
|
||||
when locale = 'nn' then 'nb'
|
||||
when locale = 'sv' then 'se'
|
||||
when locale = 'zh' then 'cn'
|
||||
else locale end)
|
||||
else json(names.value) end)
|
||||
from json_each(profiles.names) as names)
|
||||
where 1 = 1;
|
||||
|
||||
-- Down
|
@ -348,7 +348,7 @@ const fetchProfiles = async (
|
||||
opinions: propv('opinions', () => JSON.parse(profile.opinions)),
|
||||
names: propv('names', () => {
|
||||
return JSON.parse(profile.names).map((name: ValueOpinion) => {
|
||||
return { pronunciation: null, ...name };
|
||||
return { pronunciation: null, voice: null, ...name };
|
||||
});
|
||||
}),
|
||||
pronouns: propv('pronouns', () => JSON.parse(profile.pronouns)),
|
||||
|
@ -19,7 +19,6 @@ import grantOverridesRoute from './express/grantOverrides.ts';
|
||||
import imagesRoute from './express/images.ts';
|
||||
import mfaRoute from './express/mfa.ts';
|
||||
import profileRoute from './express/profile.ts';
|
||||
import pronounceRoute from './express/pronounce.ts';
|
||||
import sentryRoute from './express/sentry.ts';
|
||||
import subscriptionRoute from './express/subscription.ts';
|
||||
import translationsRoute from './express/translations.ts';
|
||||
@ -148,7 +147,6 @@ router.use(userRoute);
|
||||
router.use(profileRoute);
|
||||
router.use(adminRoute);
|
||||
router.use(mfaRoute);
|
||||
router.use(pronounceRoute);
|
||||
router.use(censusRoute);
|
||||
router.use(imagesRoute);
|
||||
router.use(calendarRoute);
|
||||
|
@ -2,6 +2,7 @@ import type { CustomEvent } from './calendar/helpers.ts';
|
||||
import type { Opinion } from './opinions.ts';
|
||||
import type { User } from './user.ts';
|
||||
|
||||
import type { VoiceKey } from '#shared/pronunciation/voices.ts';
|
||||
import type { LocaleCode } from '~~/locale/locales.ts';
|
||||
|
||||
export interface UserWithProfiles {
|
||||
@ -63,6 +64,7 @@ export interface ValueOpinion {
|
||||
|
||||
export interface NameOpinion extends ValueOpinion {
|
||||
pronunciation: string;
|
||||
voice: VoiceKey | null;
|
||||
}
|
||||
|
||||
export interface Timezone {
|
||||
|
Loading…
x
Reference in New Issue
Block a user