feat: add Russian localization and independent STT languages
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
"use client";
|
||||
import { useI18n } from "@/components/i18n";
|
||||
|
||||
/**
|
||||
* The Appearance + Graph sections (thin settings wave).
|
||||
*
|
||||
@@ -97,6 +99,7 @@ export function AppearanceSection({
|
||||
/** PUT the whole appearance section; the page re-applies on success. */
|
||||
onSectionChange: (next: AppearanceSettings) => void;
|
||||
}) {
|
||||
const { t, language } = useI18n();
|
||||
const { appearance: resolved } = useAppearance();
|
||||
const md = appearance?.mdRendering;
|
||||
|
||||
@@ -186,27 +189,27 @@ export function AppearanceSection({
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsRow label="Mode" hint="Coming soon" testid="row-mode" dimmed>
|
||||
<SettingsRow label={t("Mode")} hint={t("Coming soon")} testid="row-mode" dimmed>
|
||||
<ThinSelect value="dark" ariaLabel="mode" onChange={() => {}} testid="select-mode" disabled>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="dark">{t("Dark")}</option>
|
||||
</ThinSelect>
|
||||
</SettingsRow>
|
||||
<SettingsRow
|
||||
label="Background"
|
||||
hint={`Empty = ${resolved.background ?? "#141414"}`}
|
||||
label={t("Background")}
|
||||
hint={t("Empty = {v0}", { v0: resolved.background ?? "#141414" })}
|
||||
testid="row-background"
|
||||
>
|
||||
<CommitInput
|
||||
value={appearance?.background ?? ""}
|
||||
placeholder="#141414"
|
||||
ariaLabel="background hex"
|
||||
ariaLabel={t("background hex")}
|
||||
testid="input-background"
|
||||
onCommit={(v) => onSectionChange({ ...appearance, background: v === "" ? null : v })}
|
||||
/>
|
||||
</SettingsRow>
|
||||
<SettingsRow
|
||||
label="Reading font"
|
||||
hint={`Empty = ${DEFAULT_READING_FONT}`}
|
||||
label={t("Reading font")}
|
||||
hint={t("Empty = {v0}", { v0: language === "ru" ? "Noto Sans" : DEFAULT_READING_FONT })}
|
||||
testid="row-font"
|
||||
>
|
||||
<span ref={fontMenuOpen ? menuAnchorRef : undefined} className="relative flex items-center gap-2">
|
||||
@@ -222,13 +225,13 @@ export function AppearanceSection({
|
||||
const triggerButton = (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Reading font"
|
||||
aria-label={t("Reading font")}
|
||||
aria-expanded={fontMenuOpen}
|
||||
data-testid="font-trigger"
|
||||
onClick={openFontMenu}
|
||||
className="flex h-7 min-w-20 items-center gap-1 rounded-sm border border-border bg-background px-1.5 font-ui text-xs text-foreground transition-colors hover:border-muted-foreground/50"
|
||||
>
|
||||
<span className="min-w-0 flex-1 truncate">{appearance?.fontFamily || DEFAULT_READING_FONT}</span>
|
||||
<span className="min-w-0 flex-1 truncate">{appearance?.fontFamily || (language === "ru" ? "Noto Sans" : DEFAULT_READING_FONT)}</span>
|
||||
<span aria-hidden className="text-muted-foreground">
|
||||
▾
|
||||
</span>
|
||||
@@ -237,7 +240,7 @@ export function AppearanceSection({
|
||||
return fontMenuOpen ? (
|
||||
triggerButton
|
||||
) : (
|
||||
<Hint label="Choose from your installed fonts" side="left">
|
||||
<Hint label={t("Choose from your installed fonts")} side="left">
|
||||
{triggerButton}
|
||||
</Hint>
|
||||
);
|
||||
@@ -258,21 +261,21 @@ export function AppearanceSection({
|
||||
? undefined
|
||||
: localFonts === null
|
||||
? enumEmpty
|
||||
? "Common families — installed fonts could not be read"
|
||||
: "Common families — your installed list needs the desktop app"
|
||||
: "Your installed fonts"
|
||||
? t("Common families — installed fonts could not be read")
|
||||
: t("Common families — your installed list needs the desktop app")
|
||||
: t("Your installed fonts")
|
||||
}
|
||||
/>
|
||||
</MenuShell>
|
||||
)}
|
||||
</span>
|
||||
</SettingsRow>
|
||||
<SettingsRow label="Reading font size" hint={`${resolved.proseFontSize}px`} testid="row-prose-size">
|
||||
<SettingsRow label={t("Reading font size")} hint={`${resolved.proseFontSize}px`} testid="row-prose-size">
|
||||
<CommitInput
|
||||
type="number"
|
||||
value={String(appearance?.proseFontSize ?? "")}
|
||||
placeholder="16"
|
||||
ariaLabel="prose font size"
|
||||
ariaLabel={t("prose font size")}
|
||||
testid="input-prose-size"
|
||||
onCommit={(v) => {
|
||||
// Empty = factory default (owner, fix wave): omit the key so the record
|
||||
@@ -288,19 +291,19 @@ export function AppearanceSection({
|
||||
}}
|
||||
/>
|
||||
</SettingsRow>
|
||||
<SettingsRow label="Session names in serif" testid="row-session-serif">
|
||||
<SettingsRow label={t("Session names in serif")} testid="row-session-serif">
|
||||
<Toggle
|
||||
checked={appearance?.sessionNameSerif ?? true}
|
||||
label="session names serif"
|
||||
label={t("session names serif")}
|
||||
onChange={(on) => onSectionChange({ ...appearance, sessionNameSerif: on })}
|
||||
/>
|
||||
</SettingsRow>
|
||||
<SettingsRow label="Heading weight" hint={`${resolved.mdRendering.headingWeight}`} testid="row-heading-weight">
|
||||
<SettingsRow label={t("Heading weight")} hint={`${resolved.mdRendering.headingWeight}`} testid="row-heading-weight">
|
||||
<CommitInput
|
||||
type="number"
|
||||
value={String(md?.headingWeight ?? "")}
|
||||
placeholder="900"
|
||||
ariaLabel="heading weight"
|
||||
ariaLabel={t("heading weight")}
|
||||
testid="input-heading-weight"
|
||||
onCommit={(v) => {
|
||||
// Empty = factory default (owner, fix wave): Number("") is 0, so
|
||||
@@ -321,20 +324,20 @@ export function AppearanceSection({
|
||||
{/* The emphasis inks default to the REGULAR TEXT COLOR now
|
||||
(appearance wave, 2026-09-26) — the empty-state labels say
|
||||
so; explicit hex overrides ride unchanged. */}
|
||||
<SettingsRow label="Bold color" hint="Empty = same as text" testid="row-bold-color">
|
||||
<SettingsRow label={t("Bold color")} hint={t("Empty = same as text")} testid="row-bold-color">
|
||||
<CommitInput
|
||||
value={md?.boldColor ?? ""}
|
||||
placeholder="same as text"
|
||||
ariaLabel="bold color"
|
||||
placeholder={t("same as text")}
|
||||
ariaLabel={t("bold color")}
|
||||
testid="input-bold-color"
|
||||
onCommit={(v) => onSectionChange({ ...appearance, mdRendering: { ...md, boldColor: v === "" ? null : v } })}
|
||||
/>
|
||||
</SettingsRow>
|
||||
<SettingsRow label="Italic color" hint="Empty = same as text" testid="row-italic-color">
|
||||
<SettingsRow label={t("Italic color")} hint={t("Empty = same as text")} testid="row-italic-color">
|
||||
<CommitInput
|
||||
value={md?.italicColor ?? ""}
|
||||
placeholder="same as text"
|
||||
ariaLabel="italic color"
|
||||
placeholder={t("same as text")}
|
||||
ariaLabel={t("italic color")}
|
||||
testid="input-italic-color"
|
||||
onCommit={(v) => onSectionChange({ ...appearance, mdRendering: { ...md, italicColor: v === "" ? null : v } })}
|
||||
/>
|
||||
@@ -350,6 +353,7 @@ export function GraphSection({
|
||||
graph: GraphSettings | undefined;
|
||||
onSectionChange: (next: GraphSettings) => void;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
// Wave 7 collapse (owner ruling 2026-09-23): the section is the MASTER
|
||||
// SWITCH — the embedding/clustering/labeling rungs are gone; clustering
|
||||
// is the free birth-session partition derived on read, labels ARE session
|
||||
@@ -370,10 +374,10 @@ export function GraphSection({
|
||||
switch below arrived: off ≠ no graph, off = the graph stops
|
||||
growing. The testid keeps the old name's shape (row-enabled) so
|
||||
this stays one rename, not a test re-pin. */}
|
||||
<SettingsRow label="Extraction" hint="Off = the graph stops growing" testid="graph-row-enabled">
|
||||
<SettingsRow label={t("Extraction")} hint={t("Off = the graph stops growing")} testid="graph-row-enabled">
|
||||
<Toggle
|
||||
checked={graph?.enabled ?? true}
|
||||
label="extraction enabled"
|
||||
label={t("extraction enabled")}
|
||||
onChange={(on) =>
|
||||
// Compose, never replace-whole (the section PUT is, but the
|
||||
// row must not drop its sibling field — fixed with the budget
|
||||
@@ -386,23 +390,23 @@ export function GraphSection({
|
||||
round): display-only. Off = the home shows the chains view
|
||||
instead of the canvas. The ledgers, the pipeline, everything
|
||||
data keeps working — that is the extraction switch above. */}
|
||||
<SettingsRow label="Graph rendering" hint="Off = home shows the chains view" testid="graph-row-rendering">
|
||||
<SettingsRow label={t("Graph rendering")} hint={t("Off = home shows the chains view")} testid="graph-row-rendering">
|
||||
<Toggle
|
||||
checked={graph?.rendering ?? true}
|
||||
label="graph rendering enabled"
|
||||
label={t("graph rendering enabled")}
|
||||
onChange={(on) => onSectionChange({ ...graph, rendering: on })}
|
||||
/>
|
||||
</SettingsRow>
|
||||
<SettingsRow
|
||||
label="Extraction chunk budget"
|
||||
hint={`${resolved.extractionChunkTokens} tok`}
|
||||
label={t("Extraction chunk budget")}
|
||||
hint={t("{v0} tok", { v0: resolved.extractionChunkTokens })}
|
||||
testid="graph-row-chunk-tokens"
|
||||
>
|
||||
<CommitInput
|
||||
type="number"
|
||||
value={String(graph?.extractionChunkTokens ?? "")}
|
||||
placeholder={String(resolved.extractionChunkTokens)}
|
||||
ariaLabel="extraction chunk budget"
|
||||
ariaLabel={t("extraction chunk budget")}
|
||||
testid="input-chunk-tokens"
|
||||
onCommit={(v) => {
|
||||
// Empty = factory default (the fix-wave discipline): absence is
|
||||
|
||||
Reference in New Issue
Block a user