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 -11
View File
@@ -1,4 +1,6 @@
"use client";
import { useI18n } from "@/components/i18n";
import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
@@ -45,17 +47,18 @@ export function DeleteNodeBody({
onClose: () => void;
onRun: () => void;
}) {
const { t } = useI18n();
return (
<div
role="dialog"
aria-modal="true"
aria-label="Delete node"
aria-label={t("Delete node")}
data-testid="delete-node-dialog"
onClick={(e) => e.stopPropagation()}
className="w-[32rem] max-w-[calc(100vw-2rem)] rounded-xl border bg-card p-5 shadow-xl"
>
<h2 className="text-sm font-semibold text-destructive">
{nodes.length === 1 ? "Delete node?" : `Delete ${nodes.length} nodes?`}
{nodes.length === 1 ? t("Delete node?") : t("Delete {v0} nodes?", { v0: nodes.length })}
</h2>
{/* The node name(s) below the title in JetBrains Mono (owner,
2026-09-24): the name is the payload — mono suits it, and
@@ -74,14 +77,14 @@ export function DeleteNodeBody({
) : (
<code data-testid="node-names-code" className="font-mono text-sm text-foreground">
{nodes.slice(0, 8).map((n) => n.name).join(", ")}
{nodes.length > 8 ? `, and ${nodes.length - 8} more` : ""}
{nodes.length > 8 ? t(", and {v0} more", { v0: nodes.length - 8 }) : ""}
</code>
)}
</div>
<p className="mt-2 text-sm text-muted-foreground" data-testid="node-permanence-line">
{nodes.length === 1
? "This permanently deletes the node — this cannot be undone."
: `This permanently deletes all ${nodes.length} nodes — this cannot be undone.`}
? t("This permanently deletes the node — this cannot be undone.")
: t("This permanently deletes all {v0} nodes — this cannot be undone.", { v0: nodes.length })}
</p>
{failure && (
<div
@@ -90,7 +93,7 @@ export function DeleteNodeBody({
className="mt-3 rounded-sm border border-border/60 px-3 py-2 text-xs"
>
<span className="text-foreground">
{failure.code === "extraction_running" ? EXTRACTION_RETRY_LINE : failure.error}
{t(failure.code === "extraction_running" ? EXTRACTION_RETRY_LINE : failure.error)}
</span>
<button
type="button"
@@ -99,8 +102,7 @@ export function DeleteNodeBody({
disabled={busy}
className="ml-3 text-xs text-foreground underline-offset-2 hover:underline disabled:opacity-40"
>
Try again
</button>
{t("Try again")}</button>
</div>
)}
<div className="mt-4 flex justify-end gap-2">
@@ -111,8 +113,7 @@ export function DeleteNodeBody({
disabled={busy}
className="rounded-sm px-3 py-1.5 text-xs text-muted-foreground transition-colors hover:text-foreground disabled:opacity-40"
>
Cancel
</button>
{t("Cancel")}</button>
<button
type="button"
data-testid="node-delete-confirm"
@@ -123,7 +124,7 @@ export function DeleteNodeBody({
// refusal banner): border + text, fill on hover.
className="rounded-sm border border-destructive/50 bg-destructive/10 px-3 py-1.5 text-xs text-destructive transition-colors hover:bg-destructive/20 disabled:opacity-40"
>
{busy ? "Deleting…" : "Delete"}
{busy ? t("Deleting…") : t("Delete")}
</button>
</div>
</div>
@@ -146,6 +147,7 @@ export function DeleteNodeDialog({
* selection-side callers can prune. */
onDeleted: (nodeIds: string[], graphRevision: number) => void;
}) {
const { t } = useI18n();
const [busy, setBusy] = useState(false);
const backdropDismiss = useBackdropDismiss(() => {
if (!busy) onClose();