feat: add preferred STT languages and fix model settings layout
This commit is contained in:
+30
-1
@@ -4,6 +4,7 @@ import { createContext, useCallback, useContext, useEffect, useId, useMemo, useR
|
||||
import { resolveLanguage, resolveSttLanguage, translator, type GeneralSettings, type Language, type SttLanguage, type Translator } from "@/src/core";
|
||||
import { notifySettingsSaved, useSettingsSaved } from "./settings-signal";
|
||||
import { MenuShell } from "./menu-shell";
|
||||
import { CheckIcon } from "lucide-react";
|
||||
|
||||
type LanguageField = "language" | "sttLanguage";
|
||||
type I18nValue = {
|
||||
@@ -57,7 +58,7 @@ export function I18nProvider({ children, initialGeneral = {}, system = "en" }: {
|
||||
if (!current.ok) throw new Error("Could not load language settings");
|
||||
const body = await current.json() as { settings?: { general?: GeneralSettings } };
|
||||
const next = { ...(body.settings?.general ?? latest.current) };
|
||||
for (const field of ["language", "sttLanguage", "autoUpdate"] as const) {
|
||||
for (const field of ["language", "sttLanguage", "sttPreferredLanguages", "autoUpdate"] as const) {
|
||||
if (!Object.hasOwn(patch, field)) continue;
|
||||
if (patch[field] === undefined) delete next[field];
|
||||
else Object.assign(next, { [field]: patch[field] });
|
||||
@@ -148,3 +149,31 @@ export function LanguageSelect({ field, disabled = false, compact = false }: { f
|
||||
{error && <span role="alert" className="max-w-56 text-xs text-destructive">{t(error)}</span>}
|
||||
</span>;
|
||||
}
|
||||
|
||||
/** Preferred languages guide multilingual recognition; they are saved independently of its mode. */
|
||||
export function SttPreferredLanguages() {
|
||||
const { general, sttLanguage, dictating, saveGeneral, t } = useI18n();
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const preferred = general.sttPreferredLanguages ?? [];
|
||||
const locked = sttLanguage !== "multi" || dictating || saving;
|
||||
const toggle = (language: Language) => {
|
||||
if (locked) return;
|
||||
const next = (["en", "ru"] as const).filter((code) => code === language ? !preferred.includes(code) : preferred.includes(code));
|
||||
setSaving(true); setError(null);
|
||||
void saveGeneral({ sttPreferredLanguages: next })
|
||||
.catch(() => setError("Could not save preferred languages"))
|
||||
.finally(() => setSaving(false));
|
||||
};
|
||||
return <span className="flex max-w-full flex-col items-start gap-1">
|
||||
<span role="group" aria-label={t("Preferred languages")} className="flex max-w-full flex-wrap gap-2">
|
||||
{(["en", "ru"] as const).map((language) => <button key={language} type="button" role="checkbox" aria-checked={preferred.includes(language)}
|
||||
data-testid={`stt-preferred-${language}`} disabled={locked} onClick={() => toggle(language)}
|
||||
className={`flex h-7 items-center gap-1.5 rounded-sm border border-border px-1.5 font-ui text-xs transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${preferred.includes(language) ? "bg-muted text-foreground" : "bg-background text-muted-foreground hover:border-muted-foreground/50"}`}>
|
||||
<span aria-hidden className="flex size-3.5 shrink-0 items-center justify-center rounded-xs border border-border">{preferred.includes(language) && <CheckIcon className="size-3" />}</span>
|
||||
{language === "en" ? "English" : "Русский"}
|
||||
</button>)}
|
||||
</span>
|
||||
{error && <span role="alert" className="max-w-56 text-xs text-destructive">{t(error)}</span>}
|
||||
</span>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user