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
+19 -17
View File
@@ -1,4 +1,6 @@
"use client";
import { useI18n } from "@/components/i18n";
import { useState } from "react";
import { ConfidenceRail } from "@/components/confidence-rail";
@@ -20,12 +22,12 @@ import { parseAskInput, parseQuizInput } from "@/src/core";
* the explanation, with NO verdict chips (owner ruling 2026-09-27).
*/
export function AnsweredCard({ answer, input }: { answer: QuestionAnswerData; input?: unknown }) {
const { t } = useI18n();
const [whyOpen, setWhyOpen] = useState(false); // the tucked explanation (predictions, owner 2026-10-03)
if (answer.dismissed) {
return (
<div className="rounded-xl border bg-card p-4 text-sm text-muted-foreground">
<span className="font-medium">{answer.questionText}</span> — skipped
</div>
<span className="font-medium">{answer.questionText}</span> {" "}{t("— skipped")}</div>
);
}
@@ -51,7 +53,7 @@ export function AnsweredCard({ answer, input }: { answer: QuestionAnswerData; in
ThinkingBlock, instead of an inset inside the answer. Same muted
rail as before; the label is now plain "Your thinking". */}
{answer.readingThinking && (
<ThinkingBlock testId="reading-thinking-block" label="YOUR THINKING" text={answer.readingThinking} defaultOpen />
<ThinkingBlock testId="reading-thinking-block" label={t("YOUR THINKING")} text={answer.readingThinking} defaultOpen />
)}
{/* [&>*:last-child]:mb-0 (owner round, 2026-10-07): on ask cards the
thinking row is the last child — its my-3 stacked with the card's
@@ -66,23 +68,23 @@ export function AnsweredCard({ answer, input }: { answer: QuestionAnswerData; in
cards label Probe/Recall — the phase boundary visible in the
transcript. No `tool` on old records: kind labels as before. */}
{answer.tool === "probe"
? "Probe"
? t("Probe")
: answer.tool === "recall"
? "Recall"
? t("Recall")
: isQuiz
? answer.questionType === "predict"
? "Prediction"
: "Check"
? t("Prediction")
: t("Check")
: isExplain
? "Explain"
: "Question"}
? t("Explain")
: t("Question")}
{isQuiz &&
(answer.dontKnow ? (
<span className="rounded bg-muted px-1.5 py-0.5">didn't know</span>
<span className="rounded bg-muted px-1.5 py-0.5">{t("didn't know")}</span>
) : answer.correct ? (
<span className="rounded bg-green-500/15 px-1.5 py-0.5 text-green-500">correct</span>
<span className="rounded bg-green-500/15 px-1.5 py-0.5 text-green-500">{t("correct")}</span>
) : (
<span className="rounded bg-red-500/15 px-1.5 py-0.5 text-red-500">incorrect</span>
<span className="rounded bg-red-500/15 px-1.5 py-0.5 text-red-500">{t("incorrect")}</span>
))}
</div>
<div className="mb-3 font-medium">
@@ -176,7 +178,7 @@ export function AnsweredCard({ answer, input }: { answer: QuestionAnswerData; in
</div>
)}
{answer.dontKnow && isQuiz && (
<div className="mt-2 text-sm text-muted-foreground">Answered “I don't know”.</div>
<div className="mt-2 text-sm text-muted-foreground">{t("Answered “I don't know”.")}</div>
)}
{(answer.thinking ?? answer.note) && (
@@ -186,7 +188,7 @@ export function AnsweredCard({ answer, input }: { answer: QuestionAnswerData; in
the accent lift read too light); rail only, heights identical. */
<ThinkingBlock
testId="your-thinking"
label="YOUR THINKING"
label={t("YOUR THINKING")}
text={answer.thinking ?? answer.note ?? ""}
inCard
/>
@@ -206,11 +208,11 @@ export function AnsweredCard({ answer, input }: { answer: QuestionAnswerData; in
onClick={() => setWhyOpen((v) => !v)}
className="text-xs font-medium text-muted-foreground transition-colors hover:text-foreground"
>
{whyOpen ? "Hide why" : "Why"}
{whyOpen ? t("Hide why") : t("Why")}
</button>
{whyOpen && (
<div className="mt-1.5 rounded-lg bg-muted py-2 text-sm text-foreground/50">
<span className="font-medium text-muted-foreground">Why: </span>
<span className="font-medium text-muted-foreground">{t("Why:")}{" "}</span>
{/* The markdown wrapper must go truly inline — a block <p> after the
"Why:" label created the oversized leading gap (owner's fix list). */}
<MessageResponse className="inline [&_p]:inline [&_p]:m-0">{answer.explanation}</MessageResponse>
@@ -224,7 +226,7 @@ export function AnsweredCard({ answer, input }: { answer: QuestionAnswerData; in
the model's next reply IS the grading. */}
{isExplain && answer.committed && (
<div className="mt-3 rounded-lg bg-muted py-2 text-sm text-muted-foreground">
<span className="font-medium text-foreground">The answer: </span>
<span className="font-medium text-foreground">{t("The answer:")}{" "}</span>
<MessageResponse className="inline [&_p]:inline [&_p]:m-0">{answer.committed}</MessageResponse>
</div>
)}