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
+17 -12
View File
@@ -1,4 +1,6 @@
"use client";
import { useI18n } from "@/components/i18n";
import { CheckIcon, CircleX } from "lucide-react";
import type { PdfResult, ResearchResult, SubagentArtifactData } from "@/src/core";
@@ -26,6 +28,7 @@ import { VisualOutputCard } from "@/components/artifacts/visual-card";
* render; only a failure note does.
*/
export function SubagentSteerCard({ message }: { message: LhViewMessage }) {
const { t } = useI18n();
const artifactPart = message.parts.find((p) => p.type === "data-subagent-artifact");
const data = artifactPart?.data as SubagentArtifactData | undefined;
// The abort steer (2026-09 dock semantics): its message-type part is
@@ -38,8 +41,7 @@ export function SubagentSteerCard({ message }: { message: LhViewMessage }) {
if (aborted?.data) return <AbortedBadge kind={aborted.data.kind ?? "job"} />;
return (
<div className="rounded-xl border bg-card p-4 text-sm text-muted-foreground">
A sub-agent finished, but its result could not be displayed.
</div>
{t("A sub-agent finished, but its result could not be displayed.")}</div>
);
}
switch (data.kind) {
@@ -66,11 +68,12 @@ export function SubagentSteerCard({ message }: { message: LhViewMessage }) {
* teacher's wake turn acknowledges the abort in the lesson itself,
* this just marks what happened. */
function AbortedBadge({ kind }: { kind: string }) {
const { t } = useI18n();
return (
<div data-testid="job-aborted-badge" className="flex items-center gap-2 text-xs text-muted-foreground">
<CircleX className="size-3.5" />
<span className="font-medium uppercase tracking-wide">{kind}</span>
<span>aborted by the learner</span>
<span>{t("aborted by the learner")}</span>
</div>
);
}
@@ -80,6 +83,7 @@ function AbortedBadge({ kind }: { kind: string }) {
* the badge marks delivery, the collapsed details carries the full text
* + consulted pages. */
function PdfCompleteBadge({ result }: { result: PdfResult }) {
const { t } = useI18n();
// The honest count is READ pages (extract/render — sources v1 split);
// the ?? is runtime tolerance for pre-split persisted steers.
const readPages = result.pagesRead ?? result.pagesConsulted;
@@ -88,14 +92,14 @@ function PdfCompleteBadge({ result }: { result: PdfResult }) {
<summary className="flex cursor-pointer select-none list-none items-center gap-2 [&::-webkit-details-marker]:hidden">
<CheckIcon className="size-3.5" />
<span className="font-medium uppercase tracking-wide">PDF</span>
<span>reading complete</span>
<span>{t("reading complete")}</span>
{readPages.length > 0 && (
<span>
· read {readPages.length} page{readPages.length === 1 ? "" : "s"}
{t("· read {count} pages", { count: readPages.length })}
</span>
)}
<span className="ml-1 text-xs text-muted-foreground/70 group-open:hidden">view brief</span>
<span className="ml-1 hidden text-xs text-muted-foreground/70 group-open:inline">hide brief</span>
<span className="ml-1 text-xs text-muted-foreground/70 group-open:hidden">{t("view brief")}</span>
<span className="ml-1 hidden text-xs text-muted-foreground/70 group-open:inline">{t("hide brief")}</span>
</summary>
<div className="mt-2">
<PdfBriefCard output={result} />
@@ -110,15 +114,16 @@ function PdfCompleteBadge({ result }: { result: PdfResult }) {
* affordance): full markdown + sources, one click away, no extra fetch —
* the artifact part in the record is the single source of truth. */
function ResearchCompleteBadge({ result }: { result: ResearchResult }) {
const { t } = useI18n();
return (
<details data-testid="research-complete-badge" className="group w-full text-xs text-muted-foreground">
<summary className="flex cursor-pointer select-none list-none items-center gap-2 [&::-webkit-details-marker]:hidden">
<CheckIcon className="size-3.5" />
<span className="font-medium uppercase tracking-wide">Research</span>
<span>complete</span>
{result.sources.length > 0 && <span>· {result.sources.length} sources</span>}
<span className="ml-1 text-xs text-muted-foreground/70 group-open:hidden">view brief</span>
<span className="ml-1 hidden text-xs text-muted-foreground/70 group-open:inline">hide brief</span>
<span className="font-medium uppercase tracking-wide">{t("Research")}</span>
<span>{t("complete")}</span>
{result.sources.length > 0 && <span>· {t("{count} sources", { count: result.sources.length })}</span>}
<span className="ml-1 text-xs text-muted-foreground/70 group-open:hidden">{t("view brief")}</span>
<span className="ml-1 hidden text-xs text-muted-foreground/70 group-open:inline">{t("hide brief")}</span>
</summary>
<div className="mt-2">
<ResearchBriefCard output={result} />