feat(ui): add reusable audio language selector for app and channel settings

This commit is contained in:
Dakota Gravitt 2026-03-02 00:11:02 -06:00
parent f818b341b0
commit a7639098c0
1 changed files with 107 additions and 0 deletions

View File

@ -0,0 +1,107 @@
import { useEffect, useMemo, useState } from 'react';
type AudioLanguageSelectorProps = {
name: string;
value: string | null;
oldValue: string | null;
updateCallback: (name: string, value: string | boolean | number | null) => void;
};
const COMMON_AUDIO_LANGUAGES = [
{ code: 'en', label: 'English' },
{ code: 'es', label: 'Spanish' },
{ code: 'fr', label: 'French' },
{ code: 'de', label: 'German' },
{ code: 'it', label: 'Italian' },
{ code: 'pt', label: 'Portuguese' },
{ code: 'ja', label: 'Japanese' },
{ code: 'ko', label: 'Korean' },
{ code: 'zh', label: 'Chinese' },
{ code: 'ru', label: 'Russian' },
{ code: 'ar', label: 'Arabic' },
{ code: 'hi', label: 'Hindi' },
];
const normalizeCsv = (value: string | null): string[] => {
if (!value) return [];
return value
.split(',')
.map(item => item.trim())
.filter(Boolean);
};
const AudioLanguageSelector = ({
name,
value,
oldValue,
updateCallback,
}: AudioLanguageSelectorProps) => {
const commonCodes = useMemo(() => new Set(COMMON_AUDIO_LANGUAGES.map(item => item.code)), []);
const [selectedCommon, setSelectedCommon] = useState<string[]>([]);
const [customLanguages, setCustomLanguages] = useState('');
useEffect(() => {
const parsed = normalizeCsv(value);
const known = parsed.filter(item => commonCodes.has(item));
const custom = parsed.filter(item => !commonCodes.has(item));
setSelectedCommon(known);
setCustomLanguages(custom.join(', '));
}, [value, commonCodes]);
const mergedValue = useMemo(() => {
const custom = normalizeCsv(customLanguages);
const deduped = [...new Set([...selectedCommon, ...custom])];
return deduped.length ? deduped.join(',') : null;
}, [selectedCommon, customLanguages]);
return (
<div>
<p className="settings-help-text">Select common languages and/or add custom tags. No selection will download all languages.</p>
<select
multiple
value={selectedCommon}
onChange={event => {
const selected = Array.from(event.target.selectedOptions).map(option => option.value);
setSelectedCommon(selected);
}}
>
{COMMON_AUDIO_LANGUAGES.map(language => (
<option key={language.code} value={language.code}>
{language.label} ({language.code})
</option>
))}
</select>
<input
type="text"
value={customLanguages}
placeholder="Custom language tags, e.g. zh-Hans, pt-BR, tlh"
onChange={event => setCustomLanguages(event.target.value)}
/>
<div className="button-box">
{mergedValue !== oldValue && (
<>
<button onClick={() => updateCallback(name, mergedValue)}>Update</button>
<button
onClick={() => {
const parsed = normalizeCsv(oldValue);
const known = parsed.filter(item => commonCodes.has(item));
const custom = parsed.filter(item => !commonCodes.has(item));
setSelectedCommon(known);
setCustomLanguages(custom.join(', '));
}}
>
Cancel
</button>
</>
)}
{oldValue !== null && <button onClick={() => updateCallback(name, null)}>reset</button>}
</div>
</div>
);
};
export default AudioLanguageSelector;