feat: add Russian localization and independent STT languages
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
"use client";
|
||||
import { useI18n } from "@/components/i18n";
|
||||
|
||||
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { ArrowDownIcon, ChevronRightIcon, PlayIcon } from "lucide-react";
|
||||
@@ -105,6 +107,7 @@ export function QuestionDock({
|
||||
dictationClaim = true,
|
||||
compactionRunning = false,
|
||||
}: QuestionDockProps) {
|
||||
const { t } = useI18n();
|
||||
// The probe-phase names (owner, 2026-10-09) canonicalize onto their
|
||||
// teach twins for every dock concern — parse dispatch, labels, kinds:
|
||||
// the probe quiz ("probe") is quiz-shaped, the probe free-recall
|
||||
@@ -114,7 +117,7 @@ export function QuestionDock({
|
||||
// The chip's display name carries the tool's OWN name (owner
|
||||
// 2026-10-09): probe-phase questions label Probe/Recall in the
|
||||
// composer; teach tools keep the standing labels.
|
||||
const toolLabel = toolName === "probe" ? "Probe" : toolName === "recall" ? "Recall" : undefined;
|
||||
const toolLabel = toolName === "probe" ? t("Probe") : toolName === "recall" ? t("Recall") : undefined;
|
||||
// Parse, don't cast (src/core/tool-parts.ts): the model authored this
|
||||
// input, and only the schema the tool declared vouches for its shape.
|
||||
// The dock mounts on input-available parts (already validated at the
|
||||
@@ -126,8 +129,7 @@ export function QuestionDock({
|
||||
if (!parsed) {
|
||||
return (
|
||||
<div data-testid="malformed-question" className="rounded-xl border border-dashed bg-card/60 p-3 text-sm text-muted-foreground">
|
||||
This question arrived malformed — the transcript keeps the retry.
|
||||
</div>
|
||||
{t("This question arrived malformed — the transcript keeps the retry.")}</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
@@ -142,7 +144,7 @@ export function QuestionDock({
|
||||
label={
|
||||
(canonical === "quiz" || canonical === "explain") &&
|
||||
normalizeQuizType((parsed as { type?: unknown }).type) === "predict"
|
||||
? "Prediction"
|
||||
? t("Prediction")
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
@@ -201,6 +203,7 @@ export function QuestionDockBar({
|
||||
* label Probe/Recall — the phase boundary visible in the composer. */
|
||||
toolLabel?: string;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
if (variant === "reveal") {
|
||||
return (
|
||||
<button
|
||||
@@ -213,11 +216,11 @@ export function QuestionDockBar({
|
||||
<PlayIcon className="size-3.5" />
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block text-sm font-medium">Check your understanding</span>
|
||||
<span className="block text-sm font-medium">{t("Check your understanding")}</span>
|
||||
<span className="block truncate text-xs text-muted-foreground">{teaserText(question)}</span>
|
||||
</span>
|
||||
<span className="flex shrink-0 items-center gap-1 text-xs font-medium text-primary">
|
||||
Open <ChevronRightIcon className="size-3.5" />
|
||||
{t("Open")}{" "}<ChevronRightIcon className="size-3.5" />
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
@@ -232,11 +235,11 @@ export function QuestionDockBar({
|
||||
<span className="shrink-0 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
||||
{label ??
|
||||
toolLabel ??
|
||||
(toolName === "quiz" ? "Check" : toolName === "explain" ? "Explain" : "Question")}
|
||||
(toolName === "quiz" ? t("Check") : toolName === "explain" ? t("Explain") : t("Question"))}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate text-muted-foreground">{teaserText(question)}</span>
|
||||
<span className="flex shrink-0 items-center gap-1 text-xs font-medium">
|
||||
Answer <ArrowDownIcon className="size-3.5" />
|
||||
{t("Answer")}{" "}<ArrowDownIcon className="size-3.5" />
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
@@ -254,6 +257,7 @@ function QuestionDockForm({
|
||||
dictationClaim = true,
|
||||
compactionRunning = false,
|
||||
}: Omit<QuestionDockProps, "expanded" | "onExpand" | "reveal">) {
|
||||
const { t } = useI18n();
|
||||
const kind = toolQuestionKind(toolName);
|
||||
|
||||
// Canonical QuestionData, built once per question. Quiz options are
|
||||
@@ -547,17 +551,17 @@ function QuestionDockForm({
|
||||
<div data-session-find-root="" className="max-h-[var(--dock-max-h,55dvh)] overflow-y-auto px-4 pt-3">
|
||||
<div className="mb-1 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
||||
{toolName === "probe"
|
||||
? "Probe"
|
||||
? t("Probe")
|
||||
: toolName === "recall"
|
||||
? "Recall"
|
||||
? t("Recall")
|
||||
: kind === "quiz"
|
||||
? qd.questionType === "predict"
|
||||
? "Prediction"
|
||||
: "Check"
|
||||
? t("Prediction")
|
||||
: t("Check")
|
||||
: kind === "explain"
|
||||
? "Explain"
|
||||
: "Question"}
|
||||
{qd.mode === "multi-select" && " · select all that apply"}
|
||||
? t("Explain")
|
||||
: t("Question")}
|
||||
{qd.mode === "multi-select" && t(" · select all that apply")}
|
||||
</div>
|
||||
<div className="mb-3 font-medium">
|
||||
<MessageResponse>{qd.question}</MessageResponse>
|
||||
@@ -644,7 +648,7 @@ function QuestionDockForm({
|
||||
>
|
||||
{sel.dontKnow && <span className="size-1.5 rounded-full bg-current" />}
|
||||
</span>
|
||||
<span>I don't know</span>
|
||||
<span>{t("I don't know")}</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -710,7 +714,7 @@ function QuestionDockForm({
|
||||
submit();
|
||||
}
|
||||
}}
|
||||
placeholder="Type your own answer…"
|
||||
placeholder={t("Type your own answer…")}
|
||||
className="lh-input w-full min-w-0 flex-1 bg-transparent outline-none placeholder:text-muted-foreground"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
@@ -742,8 +746,7 @@ function QuestionDockForm({
|
||||
sel.dontKnow ? "text-muted-foreground/50" : "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
Confidence
|
||||
</span>
|
||||
{t("Confidence")}</span>
|
||||
<div
|
||||
className={cn("relative h-4 min-w-0 flex-1", sel.dontKnow && "opacity-40")}
|
||||
data-testid="confidence-track"
|
||||
@@ -771,9 +774,9 @@ function QuestionDockForm({
|
||||
step={1}
|
||||
value={CONFIDENCE_STOPS[confIdx]}
|
||||
disabled={sel.dontKnow}
|
||||
aria-label="Confidence — the probability your selected answer is correct"
|
||||
aria-valuetext={`${CONFIDENCE_STOPS[confIdx]} percent`}
|
||||
title="The probability your selected answer is correct — 50% is a coin flip. Adjust with ←/→."
|
||||
aria-label={t("Confidence — the probability your selected answer is correct")}
|
||||
aria-valuetext={t("{v0} percent", { v0: CONFIDENCE_STOPS[confIdx] })}
|
||||
title={t("The probability your selected answer is correct — 50% is a coin flip. Adjust with ←/→.")}
|
||||
data-testid="confidence-slider"
|
||||
onChange={(e) => {
|
||||
// Snap to the nearest stop; the rendered VALUE is the stop's own
|
||||
@@ -828,7 +831,7 @@ function QuestionDockForm({
|
||||
className="rounded-[10px] border border-border/70 bg-background/60 transition-colors focus-within:border-primary/50"
|
||||
>
|
||||
<div className="px-2.5 pt-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
||||
{qd.mode === "text" ? "Your answer" : "YOUR THINKING"}
|
||||
{qd.mode === "text" ? t("Your answer") : t("YOUR THINKING")}
|
||||
</div>
|
||||
{qd.mode === "text" ? (
|
||||
<textarea
|
||||
@@ -854,7 +857,7 @@ function QuestionDockForm({
|
||||
submit();
|
||||
}
|
||||
}}
|
||||
placeholder="Type your answer…"
|
||||
placeholder={t("Type your answer…")}
|
||||
rows={1}
|
||||
className="field-sizing-content lh-input max-h-48 min-h-10 w-full resize-none bg-transparent px-2.5 py-2 outline-none placeholder:text-muted-foreground"
|
||||
/>
|
||||
@@ -884,7 +887,7 @@ function QuestionDockForm({
|
||||
submit();
|
||||
}
|
||||
}}
|
||||
placeholder="Why this answer? Whatever comes to mind."
|
||||
placeholder={t("Why this answer? Whatever comes to mind.")}
|
||||
rows={1}
|
||||
className={cn(
|
||||
"field-sizing-content lh-input max-h-48 w-full resize-none bg-transparent px-2.5 py-2 outline-none placeholder:text-muted-foreground/70",
|
||||
@@ -896,15 +899,14 @@ function QuestionDockForm({
|
||||
)}
|
||||
<div className="flex items-center justify-between gap-2 p-1">
|
||||
<span aria-hidden className="pl-1.5 text-xs text-muted-foreground">
|
||||
{compactionRunning ? "compacting session…" : "↵ submits · ⇧↵ newline"}
|
||||
{compactionRunning ? t("compacting session…") : t("↵ submits · ⇧↵ newline")}
|
||||
</span>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={!canSubmit || compactionRunning}
|
||||
className="ml-auto h-9 rounded-[6px] px-3.5 text-sm font-medium"
|
||||
>
|
||||
Submit answer
|
||||
</Button>
|
||||
{t("Submit answer")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user