feat: add Russian localization and independent STT languages
This commit is contained in:
@@ -39,6 +39,7 @@ import {
|
||||
type GraphSettings,
|
||||
type UserSettings,
|
||||
} from "@/src/core";
|
||||
import { LanguageSelect, useI18n } from "@/components/i18n";
|
||||
import { useAppearance } from "./appearance.tsx";
|
||||
import { SettingsSection, SettingsRow, Toggle } from "./controls.tsx";
|
||||
import { AgentSection } from "./agent-section.tsx";
|
||||
@@ -144,6 +145,7 @@ export function SettingsDialogBody({
|
||||
* retargets follow it exactly like initialSection below. */
|
||||
initialRow?: string | null;
|
||||
}) {
|
||||
const { t, general, saveGeneral } = useI18n();
|
||||
const { onAppearanceChange } = useAppearance();
|
||||
const [record, setRecord] = useState<UserSettings | null | undefined>(
|
||||
undefined,
|
||||
@@ -232,6 +234,10 @@ export function SettingsDialogBody({
|
||||
body: object,
|
||||
) => {
|
||||
setError(null);
|
||||
if (section === "general") {
|
||||
try { await saveGeneral(body); } catch (error) { setError(error instanceof Error ? error.message : "Could not save language settings"); }
|
||||
return;
|
||||
}
|
||||
const res = await fetch(`/api/settings/${section}`, {
|
||||
method: "PUT",
|
||||
headers: { "content-type": "application/json" },
|
||||
@@ -260,30 +266,30 @@ export function SettingsDialogBody({
|
||||
if (section === "appearance")
|
||||
onAppearanceChange(body as AppearanceSettings);
|
||||
},
|
||||
[onAppearanceChange],
|
||||
[onAppearanceChange, saveGeneral, t],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Settings"
|
||||
aria-label={t("Settings")}
|
||||
data-testid="settings-dialog"
|
||||
className="settings-dialog-pop flex h-[620px] max-h-[85vh] w-[920px] max-w-[90vw] flex-col overflow-hidden rounded-md border border-border bg-card shadow-xl"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Header — the app's quiet CMU title language. */}
|
||||
<header className="flex h-15 shrink-0 items-center gap-3 border-b border-border px-5">
|
||||
<h2 className="font-title text-2xl tracking-tight">Settings</h2>
|
||||
<h2 className="font-title text-2xl tracking-tight">{t("Settings")}</h2>
|
||||
<Hint
|
||||
// Esc closes the dialog (its own listener) — surface it.
|
||||
label="Close"
|
||||
label={t("Close")}
|
||||
binding="escape"
|
||||
side="left"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Close settings"
|
||||
aria-label={t("Close settings")}
|
||||
data-testid="settings-close"
|
||||
className="ml-auto size-7 rounded-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={onClose}
|
||||
@@ -305,7 +311,7 @@ export function SettingsDialogBody({
|
||||
{/* Left nav — text-only, the session-list mono language,
|
||||
active = bg-accent. */}
|
||||
<nav
|
||||
aria-label="Settings sections"
|
||||
aria-label={t("Settings sections")}
|
||||
data-testid="settings-nav"
|
||||
className="flex w-44 shrink-0 flex-col gap-1 border-r border-border p-2"
|
||||
>
|
||||
@@ -322,7 +328,7 @@ export function SettingsDialogBody({
|
||||
}`}
|
||||
onClick={() => setActive(s.id)}
|
||||
>
|
||||
{s.label}
|
||||
{t(s.label)}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
@@ -344,32 +350,32 @@ export function SettingsDialogBody({
|
||||
className="m-4 rounded-sm border border-red-500/40 px-3 py-2 text-xs text-red-500"
|
||||
data-testid="settings-error"
|
||||
>
|
||||
{error}
|
||||
{t(error)}
|
||||
</p>
|
||||
)}
|
||||
{record === undefined ? (
|
||||
<p className="px-6 py-5 text-xs text-muted-foreground/70">
|
||||
Loading settings…
|
||||
</p>
|
||||
{t("Loading settings…")}</p>
|
||||
) : record === null ? (
|
||||
<p className="px-6 py-5 text-xs text-muted-foreground/70">
|
||||
Settings could not be loaded — refresh to retry.
|
||||
</p>
|
||||
{t("Settings could not be loaded — refresh to retry.")}</p>
|
||||
) : (
|
||||
<div>
|
||||
{active === "general" && (
|
||||
<SettingsSection
|
||||
title="General"
|
||||
title={t("General")}
|
||||
testid="section-general"
|
||||
>
|
||||
<SettingsRow label={t("Interface language")} testid="row-language"><LanguageSelect field="language" /></SettingsRow>
|
||||
<SettingsRow label={t("Speech recognition language")} testid="row-stt-language"><LanguageSelect field="sttLanguage" /></SettingsRow>
|
||||
<SettingsRow
|
||||
label="Auto-update"
|
||||
label={t("Auto-update")}
|
||||
hint={AUTO_UPDATE_HINT}
|
||||
testid="row-auto-update"
|
||||
>
|
||||
<Toggle
|
||||
checked={record.general?.autoUpdate ?? true}
|
||||
label="auto update"
|
||||
checked={general.autoUpdate ?? true}
|
||||
label={t("auto update")}
|
||||
onChange={(on) =>
|
||||
void write("general", { autoUpdate: on })
|
||||
}
|
||||
@@ -378,13 +384,13 @@ export function SettingsDialogBody({
|
||||
</SettingsSection>
|
||||
)}
|
||||
{active === "keys" && (
|
||||
<SettingsSection title="Providers" testid="section-keys">
|
||||
<SettingsSection title={t("Providers")} testid="section-keys">
|
||||
<KeysSection initialRow={initialRow ?? undefined} />
|
||||
</SettingsSection>
|
||||
)}
|
||||
{active === "agent" && (
|
||||
<SettingsSection
|
||||
title="Agent"
|
||||
title={t("Agent")}
|
||||
testid="section-agent"
|
||||
>
|
||||
<AgentSection
|
||||
@@ -406,7 +412,7 @@ export function SettingsDialogBody({
|
||||
)}
|
||||
{active === "appearance" && (
|
||||
<SettingsSection
|
||||
title="Appearance"
|
||||
title={t("Appearance")}
|
||||
testid="section-appearance"
|
||||
>
|
||||
<AppearanceSection
|
||||
@@ -419,7 +425,7 @@ export function SettingsDialogBody({
|
||||
)}
|
||||
{active === "graph" && (
|
||||
<SettingsSection
|
||||
title="Graph"
|
||||
title={t("Graph")}
|
||||
testid="section-graph"
|
||||
>
|
||||
<GraphSection
|
||||
@@ -432,8 +438,8 @@ export function SettingsDialogBody({
|
||||
)}
|
||||
{active === "keyboard" && (
|
||||
<SettingsSection
|
||||
title="Keyboard"
|
||||
hint="Stored on this device"
|
||||
title={t("Keyboard")}
|
||||
hint={t("Stored on this device")}
|
||||
testid="section-keyboard"
|
||||
>
|
||||
<KeyboardSection />
|
||||
|
||||
Reference in New Issue
Block a user