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
+16 -15
View File
@@ -1,4 +1,6 @@
"use client";
import { useI18n } from "@/components/i18n";
import { useContext, useEffect, useMemo, useRef, useState } from "react";
import { DownloadIcon } from "lucide-react";
@@ -100,12 +102,12 @@ export function DemoOutputCard({
messageId?: string;
initialInverted?: boolean;
}) {
const { t } = useI18n();
const result = output as DemoResult | undefined;
if (!result || typeof result.html !== "string" || typeof result.title !== "string") {
return (
<div className="rounded-xl border bg-card p-4 text-sm text-muted-foreground">
Demo completed, but the result could not be displayed.
</div>
{t("Demo completed, but the result could not be displayed.")}</div>
);
}
const failed = result.html.length === 0;
@@ -113,10 +115,9 @@ export function DemoOutputCard({
return (
<div className="rounded-xl border bg-card p-4 text-card-foreground" data-testid="demo-card-failed">
<div className="flex items-center gap-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
Interactive demo
<span className="rounded bg-red-500/15 px-1.5 py-0.5 text-red-500">failed</span>
{t("Interactive demo")}<span className="rounded bg-red-500/15 px-1.5 py-0.5 text-red-500">{t("failed")}</span>
</div>
<p className="mt-2 text-sm text-muted-foreground">{result.notes ?? "The demo could not be built."}</p>
<p className="mt-2 text-sm text-muted-foreground">{result.notes ?? t("The demo could not be built.")}</p>
</div>
);
}
@@ -136,6 +137,7 @@ function DemoFrame({
messageId?: string;
initialInverted?: boolean;
}) {
const { t } = useI18n();
const actions = useContext(ChatActionsContext);
const iframeRef = useRef<HTMLIFrameElement | null>(null);
const [state, setState] = useState<DemoBridgeState>(INITIAL_DEMO_BRIDGE_STATE);
@@ -177,22 +179,21 @@ function DemoFrame({
<div className="flex items-center justify-between gap-2">
<div className="flex min-w-0 items-baseline gap-2">
<span className="shrink-0 text-xs font-medium uppercase tracking-wide text-muted-foreground">
interactive demo
</span>
{t("interactive demo")}</span>
<span className="truncate text-xs font-medium tracking-wide text-foreground">{title}</span>
</div>
<div className="flex shrink-0 items-center gap-1.5">
<Toggle
checked={inverted}
onChange={toggleInverted}
label="Invert demo brightness"
title="Invert colors"
label={t("Invert demo brightness")}
title={t("Invert colors")}
discIcon={<InvertDiscIcon className="size-3" />}
/>
<button
type="button"
aria-label="Download demo as HTML"
title="Download as HTML"
aria-label={t("Download demo as HTML")}
title={t("Download as HTML")}
onClick={() => downloadFile(`${slugFilename(title)}.html`, html, "text/html")}
data-testid="demo-download"
className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
@@ -205,7 +206,7 @@ function DemoFrame({
<DemoErrorChip
title={title}
message={state.error}
onSend={actions ? () => actions.sendText(`The demo "${title}" threw an error: ${state.error}`) : undefined}
onSend={actions ? () => actions.sendText(t("The demo \"{v0}\" threw an error: {v1}", { v0: title, v1: state.error })) : undefined}
/>
)}
<iframe
@@ -245,12 +246,13 @@ export function DemoErrorChip({
message: string;
onSend?: () => void;
}) {
const { t } = useI18n();
return (
<div
className="mt-2 flex flex-wrap items-center gap-2 rounded-lg bg-red-500/10 px-3 py-2 text-sm text-red-500"
data-testid="demo-error-chip"
>
<span className="font-medium">This demo threw an error</span>
<span className="font-medium">{t("This demo threw an error")}</span>
<span className="min-w-0 flex-1 truncate font-mono text-xs" title={message}>
{message}
</span>
@@ -261,8 +263,7 @@ export function DemoErrorChip({
className="rounded-md border border-red-500/40 px-2 py-1 text-xs font-medium hover:bg-red-500/15"
data-testid="demo-error-send"
>
Send to teacher
</button>
{t("Send to teacher")}</button>
)}
</div>
);