feat: add Russian localization and independent STT languages
CI / Typecheck, tests, lint (push) Canceled after 0s
CI / Standalone (mac-arm64) (push) Canceled after 0s
CI / Standalone (linux-x64) (push) Canceled after 0s
CI / Standalone (windows-x64) (push) Canceled after 0s

This commit is contained in:
2026-10-25 17:18:44 +03:00
parent b277ee3e0f
commit a50ca4b867
93 changed files with 3261 additions and 904 deletions
+13 -10
View File
@@ -1,4 +1,6 @@
"use client";
import { useI18n } from "@/components/i18n";
import { useCallback, useEffect, useRef, useState } from "react";
import type { ChatSettings, ModelCatalogEntry, ModelCatalogResponse, ModelsSettings, UserSettings } from "@/src/core";
@@ -82,6 +84,7 @@ export function catalogLimits(catalog: Pick<ModelCatalogResponse, "models">): Ma
* no-factory-fallback language: the UI never presents a default model).
*/
export function ModelControl({ chatId, onModelChange }: { chatId?: string; onModelChange?: (model: string | undefined) => void }) {
const { t } = useI18n();
const { catalog, refresh } = useModelCatalog();
const [models, setModels] = useState<ModelsSettings | undefined>();
const [session, setSession] = useState<ChatSettings | undefined>();
@@ -318,7 +321,7 @@ export function ModelControl({ chatId, onModelChange }: { chatId?: string; onMod
// Trimmed (owner, subtlety round 2026-09-25): "Model selector"
// — the long sentence was way too much. ⌘K opens this same
// menu (registry, override-aware).
label="Model selector"
label={t("Model selector")}
action="open-model-picker"
side="bottom"
>
@@ -341,7 +344,7 @@ export function ModelControl({ chatId, onModelChange }: { chatId?: string; onMod
// "Thinking" + its real shortcut via the registry (owner interlude
// 2026-09-25): ⇧⇥ cycles the level, so "Thinking (⇧⇥)" —
// rebind-live like every Hint suffix.
label="Thinking"
label={t("Thinking")}
action="cycle-thinking"
side="bottom"
>
@@ -364,7 +367,7 @@ export function ModelControl({ chatId, onModelChange }: { chatId?: string; onMod
{/* Just the level — the button reads like the model button reads
the model name (owner, stats-control round 1). */}
<span className="text-card-foreground">
{effectiveLevel === undefined ? "—" : (THINKING_LABEL[effectiveLevel] ?? effectiveLevel)}
{effectiveLevel === undefined ? "—" : t(THINKING_LABEL[effectiveLevel] ?? effectiveLevel)}
</span>
<span aria-hidden className="text-xs text-muted-foreground">▾</span>
</button>
@@ -380,7 +383,7 @@ export function ModelControl({ chatId, onModelChange }: { chatId?: string; onMod
preferredSide="up"
menuClass="flex w-[22rem] flex-col rounded-xl border bg-card p-2 text-xs shadow-xl"
>
{error && <div className="mb-1.5 text-destructive">{error}</div>}
{error && <div className="mb-1.5 text-destructive">{t(error)}</div>}
<ModelMenuList
models={catalog?.models}
effectiveId={effectiveId}
@@ -388,10 +391,10 @@ export function ModelControl({ chatId, onModelChange }: { chatId?: string; onMod
onPick={pickModel}
footer={
catalog && catalog.models.length === 0
? "No models — add a provider key in Keys"
? t("No models — add a provider key in Keys")
: chatId
? "This session's model"
: "Strong tier — the default for new sessions"
? t("This session's model")
: t("Strong tier — the default for new sessions")
}
/>
</MenuShell>
@@ -408,10 +411,10 @@ export function ModelControl({ chatId, onModelChange }: { chatId?: string; onMod
levels={catalog ? levels : []}
effectiveLevel={effectiveLevel}
onPick={(level) => apply(effectiveProvider ?? "", effectiveId ?? "", level as ThinkingLevel)}
empty={catalog ? (effectiveId ? `${prettyModel(effectiveId)} has no thinking levels.` : "Pick a model first.") : "Loading…"}
footer="Levels this model actually supports"
empty={catalog ? (effectiveId ? t("{v0} has no thinking levels.", { v0: prettyModel(effectiveId) }) : t("Pick a model first.")) : t("Loading…")}
footer={t("Levels this model actually supports")}
/>
{error && <div className="mt-1 text-destructive">{error}</div>}
{error && <div className="mt-1 text-destructive">{t(error)}</div>}
</MenuShell>
)}
</div>