fix(i18n): externalize remaining frontend copy (#2616)
Co-authored-by: Kandev Agent <agent@kandev.dev>
This commit is contained in:
@@ -12,16 +12,17 @@ import {
|
||||
type DiscoveredFile,
|
||||
} from "@/lib/api/domains/debug-api";
|
||||
import { IconChevronDown, IconChevronRight, IconRefresh } from "@tabler/icons-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type ToolFilter = "all" | "tool_edit" | "tool_read" | "tool_execute" | "tool_call";
|
||||
type ViewMode = "fixtures" | "events";
|
||||
|
||||
const TOOL_TABS: { value: ToolFilter; label: string }[] = [
|
||||
{ value: "all", label: "All" },
|
||||
{ value: "tool_edit", label: "Edit" },
|
||||
{ value: "tool_read", label: "Read" },
|
||||
{ value: "tool_execute", label: "Execute" },
|
||||
{ value: "tool_call", label: "Call" },
|
||||
const TOOL_TABS: { value: ToolFilter; labelKey: string }[] = [
|
||||
{ value: "all", labelKey: "common:all" },
|
||||
{ value: "tool_edit", labelKey: "common:edit" },
|
||||
{ value: "tool_read", labelKey: "common:read" },
|
||||
{ value: "tool_execute", labelKey: "common:execute" },
|
||||
{ value: "tool_call", labelKey: "common:call" },
|
||||
];
|
||||
|
||||
function fixtureToMessage(fixture: NormalizedFixture, index: number): Message {
|
||||
@@ -56,6 +57,7 @@ function fixtureToMessage(fixture: NormalizedFixture, index: number): Message {
|
||||
// --- Hooks ---
|
||||
|
||||
function useAgentMessages() {
|
||||
const { t } = useTranslation("common");
|
||||
const [viewMode, setViewMode] = useState<ViewMode>("events");
|
||||
const [fixtureFiles, setFixtureFiles] = useState<DiscoveredFile[]>([]);
|
||||
const [selectedFixtureFile, setSelectedFixtureFile] = useState<string>("");
|
||||
@@ -83,14 +85,13 @@ function useAgentMessages() {
|
||||
setSelectedFixtureFile(fixtureFilesData[0].path);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to load files");
|
||||
setError(err instanceof Error ? err.message : t("common:demoFailedToLoadFiles"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
loadFiles();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [selectedEventFile, selectedFixtureFile, t]);
|
||||
|
||||
const loadFixtures = useCallback(async () => {
|
||||
if (!selectedFixtureFile) return;
|
||||
@@ -100,11 +101,11 @@ function useAgentMessages() {
|
||||
const data = await fetchNormalizedMessages(selectedFixtureFile);
|
||||
setFixtures(data);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to load fixtures");
|
||||
setError(err instanceof Error ? err.message : t("common:demoFailedToLoadFixtures"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [selectedFixtureFile]);
|
||||
}, [selectedFixtureFile, t]);
|
||||
|
||||
const loadMessages = useCallback(async () => {
|
||||
if (!selectedEventFile) return;
|
||||
@@ -114,11 +115,11 @@ function useAgentMessages() {
|
||||
const data = await fetchNormalizedEventsAsMessages(selectedEventFile);
|
||||
setMessages(data);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to load messages");
|
||||
setError(err instanceof Error ? err.message : t("common:demoFailedToLoadMessages"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [selectedEventFile]);
|
||||
}, [selectedEventFile, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (viewMode === "fixtures" && selectedFixtureFile) {
|
||||
@@ -192,6 +193,7 @@ function JsonPanel({
|
||||
}
|
||||
|
||||
function FixtureCard({ fixture, index }: { fixture: NormalizedFixture; index: number }) {
|
||||
const { t } = useTranslation("common");
|
||||
const message = fixtureToMessage(fixture, index);
|
||||
|
||||
return (
|
||||
@@ -208,13 +210,15 @@ function FixtureCard({ fixture, index }: { fixture: NormalizedFixture; index: nu
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 border-b">
|
||||
<div className="text-xs text-muted-foreground mb-2 font-medium">Rendered Output:</div>
|
||||
<div className="text-xs text-muted-foreground mb-2 font-medium">
|
||||
{t("common:demoRenderedOutput")}
|
||||
</div>
|
||||
<MessageRenderer comment={message} isTaskDescription={false} taskId="demo-task" />
|
||||
</div>
|
||||
<div className="p-4 space-y-2">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
<JsonPanel title="Raw Input" data={fixture.input} />
|
||||
<JsonPanel title="Normalized Payload" data={fixture.payload} />
|
||||
<JsonPanel title={t("common:demoRawInput")} data={fixture.input} />
|
||||
<JsonPanel title={t("common:demoNormalizedPayload")} data={fixture.payload} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -222,6 +226,7 @@ function FixtureCard({ fixture, index }: { fixture: NormalizedFixture; index: nu
|
||||
}
|
||||
|
||||
function MessageCard({ message }: { message: Message }) {
|
||||
const { t } = useTranslation("common");
|
||||
const toolName = message.metadata?.tool_name as string | undefined;
|
||||
const status = message.metadata?.status as string | undefined;
|
||||
|
||||
@@ -241,7 +246,9 @@ function MessageCard({ message }: { message: Message }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 border-b">
|
||||
<div className="text-xs text-muted-foreground mb-2 font-medium">Rendered Output:</div>
|
||||
<div className="text-xs text-muted-foreground mb-2 font-medium">
|
||||
{t("common:demoRenderedOutput")}
|
||||
</div>
|
||||
<MessageRenderer
|
||||
comment={message}
|
||||
isTaskDescription={false}
|
||||
@@ -249,7 +256,7 @@ function MessageCard({ message }: { message: Message }) {
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<JsonPanel title="Message Data" data={message} />
|
||||
<JsonPanel title={t("common:demoMessageData")} data={message} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -262,6 +269,7 @@ function ViewModeTabs({
|
||||
viewMode: ViewMode;
|
||||
setViewMode: (v: ViewMode) => void;
|
||||
}) {
|
||||
const { t } = useTranslation("common");
|
||||
return (
|
||||
<div className="mb-6">
|
||||
<div className="flex gap-1 p-1 bg-muted/30 rounded-lg w-fit">
|
||||
@@ -275,7 +283,7 @@ function ViewModeTabs({
|
||||
: "hover:bg-muted/50 text-muted-foreground"
|
||||
}`}
|
||||
>
|
||||
{mode === "events" ? "Normalized Events" : "Test Fixtures"}
|
||||
{t(mode === "events" ? "common:demoNormalizedEvents" : "common:demoTestFixtures")}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -298,28 +306,35 @@ function FiltersBar({
|
||||
toolFilter: ToolFilter;
|
||||
setToolFilter: (v: ToolFilter) => void;
|
||||
}) {
|
||||
const { t } = useTranslation("common");
|
||||
return (
|
||||
<div className="mb-6 space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-muted-foreground">
|
||||
{viewMode === "events" ? "Event File:" : "Fixture File:"}
|
||||
{t(viewMode === "events" ? "common:demoEventFile" : "common:demoFixtureFile")}
|
||||
</span>
|
||||
<select
|
||||
value={selectedFile}
|
||||
onChange={(e) => setSelectedFile(e.target.value)}
|
||||
className="px-3 py-1.5 text-sm rounded-md border bg-background hover:bg-muted/50 transition-colors min-w-[300px]"
|
||||
>
|
||||
{currentFiles.length === 0 && <option value="">No files found</option>}
|
||||
{currentFiles.length === 0 && <option value="">{t("common:demoNoFilesFound")}</option>}
|
||||
{currentFiles.map((file) => (
|
||||
<option key={file.path} value={file.path}>
|
||||
{file.protocol} - {file.agent || "unknown"} ({file.message_count} messages)
|
||||
{t("common:demoFileOption", {
|
||||
protocol: file.protocol,
|
||||
agent: file.agent || t("common:unknown"),
|
||||
count: file.message_count,
|
||||
})}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{viewMode === "fixtures" && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-muted-foreground">Tool Type:</span>
|
||||
<span className="text-sm font-medium text-muted-foreground">
|
||||
{t("common:demoToolType")}
|
||||
</span>
|
||||
<div className="flex gap-1">
|
||||
{TOOL_TABS.map((tab) => (
|
||||
<button
|
||||
@@ -331,7 +346,7 @@ function FiltersBar({
|
||||
: "hover:bg-muted/50"
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
{t(tab.labelKey)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -356,6 +371,7 @@ function ContentArea({
|
||||
filteredFixtures: NormalizedFixture[];
|
||||
messages: Message[];
|
||||
}) {
|
||||
const { t } = useTranslation("common");
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
@@ -367,7 +383,11 @@ function ContentArea({
|
||||
if (error) {
|
||||
return (
|
||||
<div className="rounded-lg border border-destructive/50 bg-destructive/10 p-4 text-destructive">
|
||||
<div className="font-medium">Error loading {viewMode}</div>
|
||||
<div className="font-medium">
|
||||
{t("common:demoErrorLoading", {
|
||||
mode: t(viewMode === "events" ? "common:demoEvents" : "common:demoFixtures"),
|
||||
})}
|
||||
</div>
|
||||
<div className="text-sm">{error}</div>
|
||||
</div>
|
||||
);
|
||||
@@ -376,9 +396,7 @@ function ContentArea({
|
||||
if (itemCount === 0) {
|
||||
return (
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
{viewMode === "events"
|
||||
? "No normalized event files found. Run an agent with KANDEV_DEBUG_AGENT_MESSAGES=1 to generate event logs."
|
||||
: "No fixtures found. Select a fixture file from the dropdown above."}
|
||||
{t(viewMode === "events" ? "common:demoNoNormalizedEvents" : "common:demoNoFixtures")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -405,6 +423,7 @@ function ContentArea({
|
||||
// --- Page ---
|
||||
|
||||
export default function AgentMessagesPage() {
|
||||
const { t } = useTranslation("common");
|
||||
const data = useAgentMessages();
|
||||
|
||||
return (
|
||||
@@ -412,20 +431,17 @@ export default function AgentMessagesPage() {
|
||||
<div className="max-w-5xl mx-auto">
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h1 className="text-2xl font-bold">Agent Messages QA</h1>
|
||||
<h1 className="text-2xl font-bold">{t("common:demoAgentMessagesQa")}</h1>
|
||||
<button
|
||||
onClick={data.loadContent}
|
||||
disabled={data.loading || !data.selectedFile}
|
||||
className="flex items-center gap-2 px-3 py-1.5 text-sm rounded-md border hover:bg-muted/50 disabled:opacity-50 transition-colors"
|
||||
>
|
||||
<IconRefresh className={`h-4 w-4 ${data.loading ? "animate-spin" : ""}`} />
|
||||
Refresh
|
||||
{t("refresh")}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-muted-foreground">
|
||||
Visual inspection of agent message normalization. Each card shows the rendered output
|
||||
alongside raw JSON.
|
||||
</p>
|
||||
<p className="text-muted-foreground">{t("common:demoAgentMessagesDescription")}</p>
|
||||
</div>
|
||||
<ViewModeTabs viewMode={data.viewMode} setViewMode={data.setViewMode} />
|
||||
<FiltersBar
|
||||
@@ -437,8 +453,15 @@ export default function AgentMessagesPage() {
|
||||
setToolFilter={data.setToolFilter}
|
||||
/>
|
||||
<div className="mb-6 text-sm text-muted-foreground">
|
||||
Showing {data.itemCount} of {data.totalCount}{" "}
|
||||
{data.viewMode === "events" ? "messages" : "fixtures"}
|
||||
{t(
|
||||
data.viewMode === "events"
|
||||
? "common:demoShowingMessages"
|
||||
: "common:demoShowingFixtures",
|
||||
{
|
||||
count: data.itemCount,
|
||||
total: data.totalCount,
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
<ContentArea
|
||||
viewMode={data.viewMode}
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
import type { Message } from "@/lib/types/http";
|
||||
import { MessageRenderer } from "@/components/task/chat/message-renderer";
|
||||
import { dummyMessages } from "./demo-messages-data";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function MessagesDemoPage() {
|
||||
const { t } = useTranslation("common");
|
||||
const permissionsByToolCallId = new Map<string, Message>();
|
||||
const permMsg = dummyMessages.find((m) => m.id === "permission-1");
|
||||
if (permMsg) {
|
||||
@@ -15,10 +17,8 @@ export default function MessagesDemoPage() {
|
||||
<div className="min-h-screen bg-background p-8">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-bold mb-2">Message Types Demo</h1>
|
||||
<p className="text-muted-foreground">
|
||||
All message types rendered for styling and testing
|
||||
</p>
|
||||
<h1 className="text-2xl font-bold mb-2">{t("common:demoMessageTypes")}</h1>
|
||||
<p className="text-muted-foreground">{t("common:demoMessageTypesDescription")}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 border rounded-lg p-5 bg-card">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Button } from "@kandev/ui/button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { AzureDevOpsBrowseMode } from "./azure-devops-filters";
|
||||
|
||||
@@ -7,13 +8,18 @@ type AzureDevOpsModeTabsProps = {
|
||||
onModeChange: (mode: AzureDevOpsBrowseMode) => void;
|
||||
};
|
||||
|
||||
const modes: Array<{ mode: AzureDevOpsBrowseMode; label: string; testId: string }> = [
|
||||
{ mode: "board", label: "Board", testId: "azure-devops-board-mode" },
|
||||
{ mode: "work-items", label: "Work items", testId: "azure-devops-work-items-mode" },
|
||||
{ mode: "pull-requests", label: "Pull requests", testId: "azure-devops-pull-requests-mode" },
|
||||
const modes: Array<{ mode: AzureDevOpsBrowseMode; labelKey: string; testId: string }> = [
|
||||
{ mode: "board", labelKey: "azuredevops:board", testId: "azure-devops-board-mode" },
|
||||
{ mode: "work-items", labelKey: "azuredevops:workItems", testId: "azure-devops-work-items-mode" },
|
||||
{
|
||||
mode: "pull-requests",
|
||||
labelKey: "azuredevops:pullRequests",
|
||||
testId: "azure-devops-pull-requests-mode",
|
||||
},
|
||||
];
|
||||
|
||||
export function AzureDevOpsModeTabs({ mode, onModeChange }: AzureDevOpsModeTabsProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flex items-center gap-1 border-b px-4 py-2">
|
||||
{modes.map((option) => (
|
||||
@@ -26,7 +32,7 @@ export function AzureDevOpsModeTabs({ mode, onModeChange }: AzureDevOpsModeTabsP
|
||||
onClick={() => onModeChange(option.mode)}
|
||||
data-testid={option.testId}
|
||||
>
|
||||
{option.label}
|
||||
{t(option.labelKey)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -45,4 +45,28 @@ describe("Azure DevOps presets", () => {
|
||||
filters: { status: "active", reviewer: "team-id", creator: "@me" },
|
||||
});
|
||||
});
|
||||
|
||||
it("translates unchanged built-in labels but preserves customized labels", () => {
|
||||
const translate = (key: string) => `translated:${key}`;
|
||||
expect(presetsForKind("work_item", undefined, translate)[0].label).toBe(
|
||||
"translated:azuredevops:defaultQueryRecentlyUpdated",
|
||||
);
|
||||
|
||||
const customized = presetsForKind(
|
||||
"work_item",
|
||||
{
|
||||
workItems: [
|
||||
{
|
||||
id: "recent",
|
||||
label: "My recent work",
|
||||
group: "inbox",
|
||||
filters: { wiql: "SELECT [System.Id] FROM WorkItems", top: 10 },
|
||||
},
|
||||
],
|
||||
pullRequests: [],
|
||||
},
|
||||
translate,
|
||||
);
|
||||
expect(customized[0].label).toBe("My recent work");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ import type { AzureDevOpsFiltersState } from "./azure-devops-filters";
|
||||
import {
|
||||
DEFAULT_AZURE_PULL_REQUEST_QUERIES,
|
||||
DEFAULT_AZURE_WORK_ITEM_QUERIES,
|
||||
azureQueryDisplayLabel,
|
||||
} from "./azure-devops-workspace-defaults";
|
||||
|
||||
export type AzureDevOpsPresetKind = "work_item" | "pull_request";
|
||||
@@ -75,11 +76,17 @@ export type AzureDevOpsQueryPresets = {
|
||||
export function presetsForKind(
|
||||
kind: AzureDevOpsPresetKind,
|
||||
configured?: AzureDevOpsQueryPresets,
|
||||
translate?: (key: string) => string,
|
||||
): AzureDevOpsPreset[] {
|
||||
const defaults =
|
||||
kind === "work_item" ? DEFAULT_AZURE_WORK_ITEM_QUERIES : DEFAULT_AZURE_PULL_REQUEST_QUERIES;
|
||||
const candidates = kind === "work_item" ? configured?.workItems : configured?.pullRequests;
|
||||
return (candidates?.length ? candidates : defaults).map((preset) => toBrowsePreset(kind, preset));
|
||||
return (candidates?.length ? candidates : defaults).map((preset) => {
|
||||
const result = toBrowsePreset(kind, preset);
|
||||
return translate
|
||||
? { ...result, label: azureQueryDisplayLabel(kind, preset, translate) }
|
||||
: result;
|
||||
});
|
||||
}
|
||||
|
||||
export const AZURE_WORK_ITEM_PRESETS = presetsForKind("work_item");
|
||||
|
||||
@@ -20,6 +20,7 @@ import type {
|
||||
AzureDevOpsWorkItem,
|
||||
} from "@/lib/types/azure-devops";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { azureActionDisplayCopy } from "./azure-devops-workspace-defaults";
|
||||
|
||||
function EmptyResult({ loading, error }: { loading: boolean; error: string | null }) {
|
||||
const { t } = useTranslation();
|
||||
@@ -74,14 +75,21 @@ function TaskActionsMenu<T extends { id: number }>({
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{actions.map((action) => (
|
||||
<DropdownMenuItem key={action.id} onSelect={() => onSelect(item, action)}>
|
||||
<span className="flex flex-col">
|
||||
<span>{action.label}</span>
|
||||
{action.hint && <span className="text-xs text-muted-foreground">{action.hint}</span>}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
{actions.map((action) => {
|
||||
const copy = azureActionDisplayCopy(
|
||||
itemKind === "workItem" ? "work_item" : "pull_request",
|
||||
action,
|
||||
t,
|
||||
);
|
||||
return (
|
||||
<DropdownMenuItem key={action.id} onSelect={() => onSelect(item, action)}>
|
||||
<span className="flex flex-col">
|
||||
<span>{copy.label}</span>
|
||||
{copy.hint && <span className="text-xs text-muted-foreground">{copy.hint}</span>}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
type ScopeSelection,
|
||||
} from "@/components/integrations/presets-scope-bar-base";
|
||||
import type { AzureDevOpsSavedView } from "@/lib/types/azure-devops";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
presetsForKind,
|
||||
type AzureDevOpsPresetKind,
|
||||
@@ -13,11 +14,6 @@ import {
|
||||
|
||||
export type AzureDevOpsScopeSelection = ScopeSelection<AzureDevOpsPresetKind>;
|
||||
|
||||
const KINDS = [
|
||||
{ value: "work_item", label: "Work items" },
|
||||
{ value: "pull_request", label: "Pull requests" },
|
||||
] as const;
|
||||
|
||||
export function AzureDevOpsScopeBar({
|
||||
selected,
|
||||
onSelect,
|
||||
@@ -35,14 +31,19 @@ export function AzureDevOpsScopeBar({
|
||||
onSaveCurrent: () => void;
|
||||
queryPresets: AzureDevOpsQueryPresets;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const kinds = [
|
||||
{ value: "work_item" as const, label: t("azuredevops:workItems") },
|
||||
{ value: "pull_request" as const, label: t("azuredevops:pullRequests") },
|
||||
];
|
||||
return (
|
||||
<IntegrationScopeBar
|
||||
testId="azure-devops-presets-scope-bar"
|
||||
savedMenuTestId="azure-devops-saved-views-menu"
|
||||
kinds={KINDS}
|
||||
kinds={kinds}
|
||||
selected={selected}
|
||||
onSelect={onSelect}
|
||||
presetsByKind={(kind) => presetsForKind(kind, queryPresets)}
|
||||
presetsByKind={(kind) => presetsForKind(kind, queryPresets, t)}
|
||||
savedPresets={savedViews}
|
||||
onDeleteSaved={onDeleteSaved}
|
||||
canSaveCurrent={canSaveCurrent}
|
||||
|
||||
@@ -30,10 +30,10 @@ function taskPullRequest(
|
||||
describe("getAzureDevOpsPullRequestPresentation", () => {
|
||||
it("prioritizes terminal Azure states", () => {
|
||||
expect(getAzureDevOpsPullRequestPresentation(taskPullRequest({ status: "completed" }))).toEqual(
|
||||
{ provider: "azure_devops", label: "Completed", tone: "success" },
|
||||
{ provider: "azure_devops", labelKey: "azuredevops:prStatusCompleted", tone: "success" },
|
||||
);
|
||||
expect(getAzureDevOpsPullRequestPresentation(taskPullRequest({ status: "abandoned" }))).toEqual(
|
||||
{ provider: "azure_devops", label: "Abandoned", tone: "muted" },
|
||||
{ provider: "azure_devops", labelKey: "azuredevops:prStatusAbandoned", tone: "muted" },
|
||||
);
|
||||
});
|
||||
|
||||
@@ -42,20 +42,24 @@ describe("getAzureDevOpsPullRequestPresentation", () => {
|
||||
getAzureDevOpsPullRequestPresentation(
|
||||
taskPullRequest({ policyState: "failure", reviewState: "approved" }),
|
||||
),
|
||||
).toEqual({ provider: "azure_devops", label: "Policy failed", tone: "danger" });
|
||||
).toEqual({
|
||||
provider: "azure_devops",
|
||||
labelKey: "azuredevops:prStatusPolicyFailed",
|
||||
tone: "danger",
|
||||
});
|
||||
});
|
||||
|
||||
it("distinguishes drafts, review waits, and ready pull requests", () => {
|
||||
expect(getAzureDevOpsPullRequestPresentation(taskPullRequest({ isDraft: true })).label).toBe(
|
||||
"Draft",
|
||||
expect(getAzureDevOpsPullRequestPresentation(taskPullRequest({ isDraft: true })).labelKey).toBe(
|
||||
"azuredevops:prStatusDraft",
|
||||
);
|
||||
expect(
|
||||
getAzureDevOpsPullRequestPresentation(taskPullRequest({ reviewState: "waiting" })).label,
|
||||
).toBe("Waiting for review");
|
||||
getAzureDevOpsPullRequestPresentation(taskPullRequest({ reviewState: "waiting" })).labelKey,
|
||||
).toBe("azuredevops:prStatusWaitingForReview");
|
||||
expect(
|
||||
getAzureDevOpsPullRequestPresentation(
|
||||
taskPullRequest({ reviewState: "approved", policyState: "success" }),
|
||||
),
|
||||
).toEqual({ provider: "azure_devops", label: "Ready", tone: "success" });
|
||||
).toEqual({ provider: "azure_devops", labelKey: "azuredevops:prStatusReady", tone: "success" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { AzureDevOpsTaskPullRequest } from "@/lib/types/azure-devops";
|
||||
|
||||
export type AzureDevOpsPullRequestPresentation = {
|
||||
provider: "azure_devops";
|
||||
label: string;
|
||||
labelKey: string;
|
||||
tone: "success" | "danger" | "warning" | "muted" | "info";
|
||||
};
|
||||
|
||||
@@ -11,28 +11,44 @@ export function getAzureDevOpsPullRequestPresentation(
|
||||
): AzureDevOpsPullRequestPresentation {
|
||||
const status = pullRequest.status.toLowerCase();
|
||||
if (status === "completed") {
|
||||
return { provider: "azure_devops", label: "Completed", tone: "success" };
|
||||
return { provider: "azure_devops", labelKey: "azuredevops:prStatusCompleted", tone: "success" };
|
||||
}
|
||||
if (status === "abandoned") {
|
||||
return { provider: "azure_devops", label: "Abandoned", tone: "muted" };
|
||||
return { provider: "azure_devops", labelKey: "azuredevops:prStatusAbandoned", tone: "muted" };
|
||||
}
|
||||
if (pullRequest.policyState === "failure") {
|
||||
return { provider: "azure_devops", label: "Policy failed", tone: "danger" };
|
||||
return {
|
||||
provider: "azure_devops",
|
||||
labelKey: "azuredevops:prStatusPolicyFailed",
|
||||
tone: "danger",
|
||||
};
|
||||
}
|
||||
if (pullRequest.reviewState === "rejected") {
|
||||
return { provider: "azure_devops", label: "Changes requested", tone: "danger" };
|
||||
return {
|
||||
provider: "azure_devops",
|
||||
labelKey: "azuredevops:prStatusChangesRequested",
|
||||
tone: "danger",
|
||||
};
|
||||
}
|
||||
if (pullRequest.isDraft) {
|
||||
return { provider: "azure_devops", label: "Draft", tone: "muted" };
|
||||
return { provider: "azure_devops", labelKey: "azuredevops:prStatusDraft", tone: "muted" };
|
||||
}
|
||||
if (pullRequest.policyState === "pending") {
|
||||
return { provider: "azure_devops", label: "Policy running", tone: "warning" };
|
||||
return {
|
||||
provider: "azure_devops",
|
||||
labelKey: "azuredevops:prStatusPolicyRunning",
|
||||
tone: "warning",
|
||||
};
|
||||
}
|
||||
if (pullRequest.reviewState === "waiting") {
|
||||
return { provider: "azure_devops", label: "Waiting for review", tone: "warning" };
|
||||
return {
|
||||
provider: "azure_devops",
|
||||
labelKey: "azuredevops:prStatusWaitingForReview",
|
||||
tone: "warning",
|
||||
};
|
||||
}
|
||||
if (pullRequest.reviewState === "approved" && pullRequest.policyState === "success") {
|
||||
return { provider: "azure_devops", label: "Ready", tone: "success" };
|
||||
return { provider: "azure_devops", labelKey: "azuredevops:prStatusReady", tone: "success" };
|
||||
}
|
||||
return { provider: "azure_devops", label: "Active", tone: "info" };
|
||||
return { provider: "azure_devops", labelKey: "azuredevops:prStatusActive", tone: "info" };
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { IconBrandAzure, IconExternalLink } from "@tabler/icons-react";
|
||||
import { Button } from "@kandev/ui/button";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppStore } from "@/components/state-provider";
|
||||
import { useAzureDevOpsTaskPullRequests } from "@/hooks/domains/azure-devops/use-azure-devops-task-pull-requests";
|
||||
import { getAzureDevOpsPullRequestPresentation } from "./azure-devops-status";
|
||||
@@ -16,13 +17,19 @@ const TONE_CLASS = {
|
||||
};
|
||||
|
||||
export function AzureDevOpsTaskPullRequestChip({ taskId }: { taskId: string | null }) {
|
||||
const { t } = useTranslation();
|
||||
const workspaceId = useAppStore((state) => state.workspaces.activeId);
|
||||
const pullRequests = useAzureDevOpsTaskPullRequests(workspaceId, taskId);
|
||||
const first = pullRequests[0];
|
||||
if (!first) return null;
|
||||
const presentation = getAzureDevOpsPullRequestPresentation(first);
|
||||
const statusLabel = t(presentation.labelKey);
|
||||
const suffix = pullRequests.length > 1 ? ` +${pullRequests.length - 1}` : "";
|
||||
const label = `Azure PR ${first.pullRequestId}: ${presentation.label}${suffix}`;
|
||||
const label = t("azuredevops:pullRequestStatusAria", {
|
||||
id: first.pullRequestId,
|
||||
status: statusLabel,
|
||||
suffix,
|
||||
});
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
@@ -36,7 +43,7 @@ export function AzureDevOpsTaskPullRequestChip({ taskId }: { taskId: string | nu
|
||||
<a href={first.pullRequestUrl} target="_blank" rel="noreferrer" aria-label={label}>
|
||||
<IconBrandAzure className="h-3.5 w-3.5 shrink-0" />
|
||||
<span className="truncate">PR {first.pullRequestId}</span>
|
||||
<span className="hidden truncate sm:inline">{presentation.label}</span>
|
||||
<span className="hidden truncate sm:inline">{statusLabel}</span>
|
||||
{suffix && <span className="shrink-0">{suffix}</span>}
|
||||
<IconExternalLink className="h-3 w-3 shrink-0" />
|
||||
</a>
|
||||
@@ -44,7 +51,7 @@ export function AzureDevOpsTaskPullRequestChip({ taskId }: { taskId: string | nu
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-80">
|
||||
<p className="font-medium">{first.title}</p>
|
||||
<p>{presentation.label}</p>
|
||||
<p>{statusLabel}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -28,6 +28,7 @@ import type {
|
||||
} from "@/lib/types/azure-devops";
|
||||
import { markdownComponents, remarkPlugins } from "@/components/shared/markdown-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { azureActionDisplayCopy } from "./azure-devops-workspace-defaults";
|
||||
|
||||
type BoardContext = {
|
||||
board: AzureDevOpsBoard;
|
||||
@@ -215,18 +216,21 @@ function QuickActions({
|
||||
<div className="space-y-2" data-testid="azure-work-item-quick-actions">
|
||||
<Label>{t("azuredevops:taskActions")}</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{actions.map((action) => (
|
||||
<Button
|
||||
key={action.id}
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="min-h-11 cursor-pointer"
|
||||
onClick={() => onStartTask(item, action)}
|
||||
>
|
||||
<IconPlus className="h-4 w-4" />
|
||||
{action.label}
|
||||
</Button>
|
||||
))}
|
||||
{actions.map((action) => {
|
||||
const copy = azureActionDisplayCopy("work_item", action, t);
|
||||
return (
|
||||
<Button
|
||||
key={action.id}
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="min-h-11 cursor-pointer"
|
||||
onClick={() => onStartTask(item, action)}
|
||||
>
|
||||
<IconPlus className="h-4 w-4" />
|
||||
{copy.label}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
*/
|
||||
import type { AzureDevOpsActionPreset, AzureDevOpsQueryPreset } from "@/lib/types/azure-devops";
|
||||
|
||||
type Translate = (key: string) => string;
|
||||
|
||||
const WIQL_START = "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @project";
|
||||
const WIQL_ORDER = " ORDER BY [System.ChangedDate] DESC";
|
||||
|
||||
@@ -139,3 +141,68 @@ export const DEFAULT_AZURE_PULL_REQUEST_ACTIONS: AzureDevOpsActionPreset[] = [
|
||||
promptTemplate: "Investigate and fix CI failures for the Azure DevOps pull request at {{url}}.",
|
||||
},
|
||||
];
|
||||
|
||||
const QUERY_LABEL_KEYS: Record<string, string> = {
|
||||
"work_item:recent": "azuredevops:defaultQueryRecentlyUpdated",
|
||||
"work_item:assigned": "azuredevops:defaultQueryAssignedToMe",
|
||||
"work_item:active": "azuredevops:defaultQueryActive",
|
||||
"work_item:created": "azuredevops:defaultQueryCreatedByMe",
|
||||
"pull_request:review-requested": "azuredevops:defaultQueryReviewRequested",
|
||||
"pull_request:active": "azuredevops:defaultQueryOpen",
|
||||
"pull_request:completed": "azuredevops:defaultQueryCompleted",
|
||||
"pull_request:created": "azuredevops:defaultQueryCreatedByMe",
|
||||
};
|
||||
|
||||
const ACTION_COPY_KEYS: Record<string, { labelKey: string; hintKey: string }> = {
|
||||
"work_item:implement": {
|
||||
labelKey: "azuredevops:defaultActionImplement",
|
||||
hintKey: "azuredevops:defaultActionImplementHint",
|
||||
},
|
||||
"work_item:investigate": {
|
||||
labelKey: "azuredevops:defaultActionInvestigate",
|
||||
hintKey: "azuredevops:defaultActionInvestigateHint",
|
||||
},
|
||||
"work_item:reproduce": {
|
||||
labelKey: "azuredevops:defaultActionReproduce",
|
||||
hintKey: "azuredevops:defaultActionReproduceHint",
|
||||
},
|
||||
"pull_request:review": {
|
||||
labelKey: "azuredevops:defaultActionReview",
|
||||
hintKey: "azuredevops:defaultActionReviewHint",
|
||||
},
|
||||
"pull_request:address-feedback": {
|
||||
labelKey: "azuredevops:defaultActionAddressFeedback",
|
||||
hintKey: "azuredevops:defaultActionAddressFeedbackHint",
|
||||
},
|
||||
"pull_request:fix-ci": {
|
||||
labelKey: "azuredevops:defaultActionFixCi",
|
||||
hintKey: "azuredevops:defaultActionFixCiHint",
|
||||
},
|
||||
};
|
||||
|
||||
export function azureQueryDisplayLabel(
|
||||
kind: "work_item" | "pull_request",
|
||||
preset: AzureDevOpsQueryPreset,
|
||||
t: Translate,
|
||||
): string {
|
||||
const defaults =
|
||||
kind === "work_item" ? DEFAULT_AZURE_WORK_ITEM_QUERIES : DEFAULT_AZURE_PULL_REQUEST_QUERIES;
|
||||
const baseline = defaults.find((candidate) => candidate.id === preset.id);
|
||||
const key = QUERY_LABEL_KEYS[`${kind}:${preset.id}`];
|
||||
return key && baseline?.label === preset.label ? t(key) : preset.label;
|
||||
}
|
||||
|
||||
export function azureActionDisplayCopy(
|
||||
kind: "work_item" | "pull_request",
|
||||
action: AzureDevOpsActionPreset,
|
||||
t: Translate,
|
||||
): { label: string; hint: string } {
|
||||
const defaults =
|
||||
kind === "work_item" ? DEFAULT_AZURE_WORK_ITEM_ACTIONS : DEFAULT_AZURE_PULL_REQUEST_ACTIONS;
|
||||
const baseline = defaults.find((candidate) => candidate.id === action.id);
|
||||
const keys = ACTION_COPY_KEYS[`${kind}:${action.id}`];
|
||||
if (!keys || baseline?.label !== action.label || baseline.hint !== action.hint) {
|
||||
return { label: action.label, hint: action.hint };
|
||||
}
|
||||
return { label: t(keys.labelKey), hint: t(keys.hintKey) };
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { Icon } from "@tabler/icons-react";
|
||||
import { TaskCreateDialog } from "@/components/task-create-dialog";
|
||||
import { useAppStore } from "@/components/state-provider";
|
||||
@@ -101,6 +102,7 @@ export function QuickTaskLauncher({
|
||||
}: QuickTaskLauncherProps) {
|
||||
const router = useRouter();
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation("gitlab");
|
||||
const setTaskMR = useAppStore((state) => state.setTaskMR);
|
||||
const workflow = workflows[0];
|
||||
const workflowSteps = useMemo(
|
||||
@@ -132,10 +134,10 @@ export function QuickTaskLauncher({
|
||||
setTaskMR(workspaceId, task.id, association);
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Task created, but merge request was not linked",
|
||||
description: `Open the task and use Link GitLab merge request to retry. ${
|
||||
error instanceof Error ? error.message : "GitLab rejected the association."
|
||||
}`,
|
||||
title: t("gitlab:taskCreatedMrNotLinked"),
|
||||
description: t("gitlab:taskCreatedMrNotLinkedDescription", {
|
||||
error: error instanceof Error ? error.message : t("gitlab:gitlabRejectedAssociation"),
|
||||
}),
|
||||
variant: "error",
|
||||
duration: 8000,
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ describe("getStepAdjacency", () => {
|
||||
];
|
||||
const stepsWithOrphan: WorkflowStep[] = [
|
||||
...realSteps,
|
||||
{ id: ORPHAN_STEP_ID, title: "Needs Reassignment", color: "#f59e0b" },
|
||||
{ id: ORPHAN_STEP_ID, title: "", color: "#f59e0b" },
|
||||
];
|
||||
|
||||
it("offers both neighbors for a middle real step with no orphan present", () => {
|
||||
|
||||
@@ -12,12 +12,13 @@ import { useTranslation } from "react-i18next";
|
||||
export function getGraph2DisplayState(
|
||||
tasks: Task[],
|
||||
steps: WorkflowStep[],
|
||||
orphanStepTitle: string,
|
||||
): { displayTasks: Task[]; displaySteps: WorkflowStep[] } {
|
||||
const stepIds = new Set(steps.map((step) => step.id));
|
||||
const { tasks: displayTasks, hasOrphans } = remapOrphanTasks(tasks, stepIds, ORPHAN_STEP_ID);
|
||||
return {
|
||||
displayTasks,
|
||||
displaySteps: hasOrphans ? [...steps, ORPHAN_STEP] : steps,
|
||||
displaySteps: hasOrphans ? [...steps, { ...ORPHAN_STEP, title: orphanStepTitle }] : steps,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,8 +43,8 @@ export function SwimlaneGraph2Content({
|
||||
});
|
||||
const [movingTaskId, setMovingTaskId] = useState<string | null>(null);
|
||||
const { displayTasks, displaySteps } = useMemo(
|
||||
() => getGraph2DisplayState(tasks, steps),
|
||||
[tasks, steps],
|
||||
() => getGraph2DisplayState(tasks, steps, t("kanban:needsReassignment")),
|
||||
[tasks, steps, t],
|
||||
);
|
||||
|
||||
const sortedTasks = useMemo(
|
||||
|
||||
@@ -23,6 +23,7 @@ describe("getGraph2DisplayState", () => {
|
||||
const { displayTasks, displaySteps } = getGraph2DisplayState(
|
||||
[makeTask("valid", "todo"), makeTask("orphan", "deleted-step")],
|
||||
steps,
|
||||
"Needs Reassignment",
|
||||
);
|
||||
|
||||
expect(displaySteps.map((step) => step.title)).toEqual(["Todo", "Done", "Needs Reassignment"]);
|
||||
@@ -34,6 +35,7 @@ describe("getGraph2DisplayState", () => {
|
||||
const { displayTasks, displaySteps } = getGraph2DisplayState(
|
||||
[makeTask("valid", "todo"), makeTask("finished", "done")],
|
||||
steps,
|
||||
"Needs Reassignment",
|
||||
);
|
||||
|
||||
expect(displaySteps).toBe(steps);
|
||||
|
||||
@@ -43,7 +43,7 @@ export const ORPHAN_STEP_ID = "__kandev_orphan__";
|
||||
|
||||
export const ORPHAN_STEP: WorkflowStep = {
|
||||
id: ORPHAN_STEP_ID,
|
||||
title: "Needs Reassignment",
|
||||
title: "",
|
||||
color: "#f59e0b",
|
||||
};
|
||||
|
||||
@@ -234,12 +234,15 @@ function useOrphanDisplay(
|
||||
tasks: Task[],
|
||||
steps: WorkflowStep[],
|
||||
): { displayTasks: Task[]; displaySteps: WorkflowStep[] } {
|
||||
const { t } = useTranslation("kanban");
|
||||
return useMemo(() => {
|
||||
const stepIds = new Set(steps.map((s) => s.id));
|
||||
const { tasks: displayTasks, hasOrphans } = remapOrphanTasks(tasks, stepIds, ORPHAN_STEP_ID);
|
||||
const displaySteps = hasOrphans ? [...steps, ORPHAN_STEP] : steps;
|
||||
const displaySteps = hasOrphans
|
||||
? [...steps, { ...ORPHAN_STEP, title: t("kanban:needsReassignment") }]
|
||||
: steps;
|
||||
return { displayTasks, displaySteps };
|
||||
}, [tasks, steps]);
|
||||
}, [tasks, steps, t]);
|
||||
}
|
||||
|
||||
function useTasksByStep(tasks: Task[]) {
|
||||
|
||||
@@ -309,17 +309,17 @@ function useReviewDialogHandlers(opts: ReviewDialogHandlerOptions) {
|
||||
try {
|
||||
const result = await discard([path], repositoryName);
|
||||
if (result.success)
|
||||
toast({ title: "Changes discarded", description: path, variant: "success" });
|
||||
toast({ title: t("review:changesDiscarded"), description: path, variant: "success" });
|
||||
else
|
||||
toast({
|
||||
title: "Discard failed",
|
||||
title: t("review:discardFailed"),
|
||||
description: result.error || t("common:anErrorOccurred"),
|
||||
variant: "error",
|
||||
});
|
||||
} catch (e) {
|
||||
toast({
|
||||
title: "Discard failed",
|
||||
description: e instanceof Error ? e.message : "An error occurred",
|
||||
title: t("review:discardFailed"),
|
||||
description: e instanceof Error ? e.message : t("common:anErrorOccurred"),
|
||||
variant: "error",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -231,6 +231,21 @@ function buildConflictLabels(groups: ShortcutConflictGroup[]): Map<string, strin
|
||||
|
||||
const CONFIGURABLE_SHORTCUT_IDS = Object.keys(CONFIGURABLE_SHORTCUTS) as ConfigurableShortcutId[];
|
||||
|
||||
function useShortcutConflictLabels(
|
||||
pluginEntries: ShortcutEntry[],
|
||||
overrides: StoredShortcutOverrides,
|
||||
translate: NonNullable<Parameters<typeof coreShortcutEntries>[0]>,
|
||||
) {
|
||||
return useMemo(() => {
|
||||
const allEntries = [...coreShortcutEntries(translate), ...pluginEntries];
|
||||
const resolved = allEntries.map((entry) => ({
|
||||
entry,
|
||||
shortcut: resolveShortcutEntry(entry, overrides),
|
||||
}));
|
||||
return buildConflictLabels(findShortcutConflicts(resolved, isMac()));
|
||||
}, [pluginEntries, overrides, translate]);
|
||||
}
|
||||
|
||||
export function KeyboardShortcutsCard({
|
||||
overrides,
|
||||
baselineOverrides = {},
|
||||
@@ -247,15 +262,7 @@ export function KeyboardShortcutsCard({
|
||||
const shortcuts = resolveAllShortcuts(overrides);
|
||||
const baselineShortcuts = resolveAllShortcuts(baselineOverrides);
|
||||
|
||||
const allEntries = useMemo(() => [...coreShortcutEntries(), ...pluginEntries], [pluginEntries]);
|
||||
|
||||
const conflictLabels = useMemo(() => {
|
||||
const resolved = allEntries.map((entry) => ({
|
||||
entry,
|
||||
shortcut: resolveShortcutEntry(entry, overrides),
|
||||
}));
|
||||
return buildConflictLabels(findShortcutConflicts(resolved, isMac()));
|
||||
}, [allEntries, overrides]);
|
||||
const conflictLabels = useShortcutConflictLabels(pluginEntries, overrides, t);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(id: string, shortcut: KeyboardShortcut) => {
|
||||
@@ -294,7 +301,7 @@ export function KeyboardShortcutsCard({
|
||||
<ShortcutRecorder
|
||||
key={id}
|
||||
shortcutId={id}
|
||||
label={CONFIGURABLE_SHORTCUTS[id].label}
|
||||
label={t(CONFIGURABLE_SHORTCUTS[id].labelKey)}
|
||||
defaultShortcut={CONFIGURABLE_SHORTCUTS[id].default}
|
||||
current={shortcuts[id]}
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -61,6 +61,7 @@ function useMermaidRender(
|
||||
resolvedTheme: string | undefined,
|
||||
toast: ToastFn,
|
||||
taskId: string | null,
|
||||
errorTitle: string,
|
||||
) {
|
||||
const [svg, setSvg] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -105,7 +106,7 @@ function useMermaidRender(
|
||||
// screen — the error banner is also suppressed in that case, so a
|
||||
// toast here would surface a failure the user never sees in the UI.
|
||||
if (svgRef.current === null) {
|
||||
showMermaidErrorToast(toast, taskId, err.message);
|
||||
showMermaidErrorToast(toast, taskId, err.message, errorTitle);
|
||||
}
|
||||
});
|
||||
}, RENDER_DEBOUNCE_MS);
|
||||
@@ -114,7 +115,7 @@ function useMermaidRender(
|
||||
cancelled = true;
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [code, resolvedTheme, taskId, toast]);
|
||||
}, [code, errorTitle, resolvedTheme, taskId, toast]);
|
||||
|
||||
return { svg, error };
|
||||
}
|
||||
@@ -128,7 +129,13 @@ export function MermaidBlock({ code, taskId }: MermaidBlockProps) {
|
||||
const [showCode, setShowCode] = useState(false);
|
||||
const { resolvedTheme } = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { svg, error } = useMermaidRender(code, resolvedTheme, toast, taskId ?? null);
|
||||
const { svg, error } = useMermaidRender(
|
||||
code,
|
||||
resolvedTheme,
|
||||
toast,
|
||||
taskId ?? null,
|
||||
t("common:failedToRenderDiagram"),
|
||||
);
|
||||
const viewportWidth = useMermaidViewportWidth(scrollRegionElement);
|
||||
const { scale, zoomIn, zoomOut, zoomReset, resetAutoScale } = useMermaidScale(
|
||||
svgSize,
|
||||
|
||||
@@ -57,7 +57,7 @@ describe("useMermaidErrorToast", () => {
|
||||
detail: { message: "Plan parser error again", taskId: "task-1" },
|
||||
}),
|
||||
);
|
||||
showMermaidErrorToast(mockToast, "task-1", "Chat parser error");
|
||||
showMermaidErrorToast(mockToast, "task-1", "Chat parser error", "Failed to render diagram");
|
||||
});
|
||||
|
||||
expect(mockToast).toHaveBeenCalledOnce();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useToast } from "@/components/toast-provider";
|
||||
import { MERMAID_ERROR_EVENT } from "./mermaid-utils";
|
||||
|
||||
@@ -13,13 +14,14 @@ export function showMermaidErrorToast(
|
||||
toast: ToastFn,
|
||||
taskId: string | null | undefined,
|
||||
message: string,
|
||||
title: string,
|
||||
): void {
|
||||
if (taskId) {
|
||||
if (notifiedTaskIds.has(taskId)) return;
|
||||
notifiedTaskIds.add(taskId);
|
||||
}
|
||||
|
||||
toast({ title: "Failed to render diagram", description: message, variant: "error" });
|
||||
toast({ title, description: message, variant: "error" });
|
||||
}
|
||||
|
||||
export function resetMermaidErrorToastHistoryForTest(): void {
|
||||
@@ -28,13 +30,14 @@ export function resetMermaidErrorToastHistoryForTest(): void {
|
||||
|
||||
export function useMermaidErrorToast(): void {
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: Event) => {
|
||||
const detail = (e as CustomEvent<MermaidErrorDetail>).detail;
|
||||
showMermaidErrorToast(toast, detail?.taskId, detail?.message);
|
||||
showMermaidErrorToast(toast, detail?.taskId, detail?.message, t("failedToRenderDiagram"));
|
||||
};
|
||||
document.addEventListener(MERMAID_ERROR_EVENT, handler);
|
||||
return () => document.removeEventListener(MERMAID_ERROR_EVENT, handler);
|
||||
}, [toast]);
|
||||
}, [t, toast]);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from "@/components/task-create-dialog-autopick";
|
||||
import { useRepositoryAutoSelectEffect } from "@/components/task-create-dialog-repository-autopick";
|
||||
import { createDebugLogger, isDebug } from "@/lib/debug/log";
|
||||
import { t } from "@/lib/i18n";
|
||||
|
||||
// Re-export autopick hooks for callers that imported them from this module.
|
||||
export { useWorkflowAgentProfileEffect };
|
||||
@@ -92,8 +93,8 @@ export function useDiscoverReposEffect(
|
||||
})
|
||||
.catch((e) => {
|
||||
toast({
|
||||
title: "Failed to discover repositories",
|
||||
description: e instanceof Error ? e.message : "Request failed",
|
||||
title: t("task:failedToDiscoverRepositories"),
|
||||
description: e instanceof Error ? e.message : t("common:requestFailed"),
|
||||
variant: "error",
|
||||
});
|
||||
setDiscoveredRepositories([]);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ApiError } from "@/lib/api/client";
|
||||
import { createTask as createTaskREST } from "@/lib/api";
|
||||
import type { useToast } from "@/components/toast-provider";
|
||||
import type { TaskCreateSubmit } from "@/components/task-create-dialog-types";
|
||||
import { t } from "@/lib/i18n";
|
||||
|
||||
type CreateTaskParams = Parameters<typeof createTaskREST>[0];
|
||||
type CreateTaskResponse = Awaited<ReturnType<typeof createTaskREST>>;
|
||||
@@ -84,8 +85,8 @@ export function useFreshBranchConsent({
|
||||
return await promptForList(status.dirty_files);
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Failed to check local repository status",
|
||||
description: error instanceof Error ? error.message : "Request failed",
|
||||
title: t("task:failedToCheckRepositoryStatus"),
|
||||
description: error instanceof Error ? error.message : t("common:requestFailed"),
|
||||
variant: "error",
|
||||
});
|
||||
return null;
|
||||
|
||||
@@ -11,9 +11,12 @@ import { buildStartRequest } from "@/lib/services/session-launch-helpers";
|
||||
import { useToast } from "@/components/toast-provider";
|
||||
import { linkToTask } from "@/lib/links";
|
||||
import type { SubmitHandlersDeps } from "@/components/task-create-dialog-types";
|
||||
import { t } from "@/lib/i18n";
|
||||
import { useFreshBranchConsent } from "@/components/task-create-dialog-fresh-branch-consent";
|
||||
import { queueTaskCreateLastUsedFromPayload } from "@/components/task-create-dialog-handlers";
|
||||
|
||||
const GENERIC_ERROR_KEY = "common:anErrorOccurred";
|
||||
|
||||
import {
|
||||
activatePlanMode,
|
||||
buildCreateTaskPayload,
|
||||
@@ -27,18 +30,14 @@ import {
|
||||
} from "@/components/task-create-dialog-helpers";
|
||||
import { hasRegisteredRepositoryProviderCandidate } from "@/lib/plugins/repository-provider-url-resolution";
|
||||
|
||||
const GENERIC_ERROR_MESSAGE = "An error occurred";
|
||||
const DUPLICATE_REPO_TITLE = "Duplicate repository";
|
||||
const UNRESOLVED_REPO_TITLE = "Repository is still being verified";
|
||||
|
||||
function notifyQueuedTask(
|
||||
response: { queued_for_step_id?: string | null },
|
||||
notify: (input: { title: string; description: string }) => unknown,
|
||||
) {
|
||||
if (!response.queued_for_step_id) return;
|
||||
notify({
|
||||
title: "Task queued",
|
||||
description: "The workflow step is at its WIP limit; this task will start when capacity opens.",
|
||||
title: t("task:taskQueued"),
|
||||
description: t("task:taskQueuedWipLimit"),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -168,8 +167,8 @@ export function useTaskSubmitHandlers({
|
||||
const duplicate = findDuplicateRemoteRepo(remoteRepos);
|
||||
if (!duplicate) return false;
|
||||
toast({
|
||||
title: DUPLICATE_REPO_TITLE,
|
||||
description: `${duplicate} is added more than once - remove the duplicate row.`,
|
||||
title: t("task:duplicateRepository"),
|
||||
description: t("task:duplicateRepositoryDescription", { repository: duplicate }),
|
||||
variant: "error",
|
||||
});
|
||||
return true;
|
||||
@@ -188,10 +187,10 @@ export function useTaskSubmitHandlers({
|
||||
if (!unresolved) return false;
|
||||
const resolutionError = prInfoByUrl.error(unresolved.url);
|
||||
toast({
|
||||
title: UNRESOLVED_REPO_TITLE,
|
||||
title: t("task:repositoryStillBeingVerified"),
|
||||
description: resolutionError
|
||||
? "Kandev could not verify this provider URL. Use Retry on the repository row."
|
||||
: "Wait for provider inspection to finish before creating the task.",
|
||||
? t("task:repositoryProviderVerificationFailed")
|
||||
: t("task:repositoryProviderVerificationPending"),
|
||||
variant: "error",
|
||||
});
|
||||
return true;
|
||||
@@ -240,8 +239,7 @@ export function useTaskSubmitHandlers({
|
||||
freshBranch: buildFreshBranchPayload(consentedDirtyFiles),
|
||||
});
|
||||
},
|
||||
// buildFreshBranchPayload is a closure over current scope; lint exception kept narrow.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// buildFreshBranchPayload is a closure over current scope; dependencies stay explicit below.
|
||||
[
|
||||
noRepository,
|
||||
useRemote,
|
||||
@@ -290,8 +288,8 @@ export function useTaskSubmitHandlers({
|
||||
router.push(linkToTask(taskId));
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Failed to create session",
|
||||
description: error instanceof Error ? error.message : GENERIC_ERROR_MESSAGE,
|
||||
title: t("task:failedToCreateSession"),
|
||||
description: error instanceof Error ? error.message : t(GENERIC_ERROR_KEY),
|
||||
variant: "error",
|
||||
});
|
||||
} finally {
|
||||
@@ -357,8 +355,8 @@ export function useTaskSubmitHandlers({
|
||||
onSuccess?.(updatedTask, "edit", { taskSessionId });
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Failed to update task",
|
||||
description: error instanceof Error ? error.message : GENERIC_ERROR_MESSAGE,
|
||||
title: t("task:failedToUpdateTask"),
|
||||
description: error instanceof Error ? error.message : t(GENERIC_ERROR_KEY),
|
||||
variant: "error",
|
||||
});
|
||||
} finally {
|
||||
@@ -386,8 +384,8 @@ export function useTaskSubmitHandlers({
|
||||
onSuccess?.(result.updatedTask, "edit");
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Failed to update task",
|
||||
description: error instanceof Error ? error.message : GENERIC_ERROR_MESSAGE,
|
||||
title: t("task:failedToUpdateTask"),
|
||||
description: error instanceof Error ? error.message : t(GENERIC_ERROR_KEY),
|
||||
variant: "error",
|
||||
});
|
||||
} finally {
|
||||
@@ -538,8 +536,8 @@ export function useTaskSubmitHandlers({
|
||||
await performEditWithPlanMode();
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Failed to start task in plan mode",
|
||||
description: error instanceof Error ? error.message : GENERIC_ERROR_MESSAGE,
|
||||
title: t("task:failedToStartTaskPlanMode"),
|
||||
description: error instanceof Error ? error.message : t(GENERIC_ERROR_KEY),
|
||||
variant: "error",
|
||||
});
|
||||
} finally {
|
||||
@@ -569,8 +567,8 @@ export function useTaskSubmitHandlers({
|
||||
});
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Failed to start task in plan mode",
|
||||
description: error instanceof Error ? error.message : GENERIC_ERROR_MESSAGE,
|
||||
title: t("task:failedToStartTaskPlanMode"),
|
||||
description: error instanceof Error ? error.message : t(GENERIC_ERROR_KEY),
|
||||
variant: "error",
|
||||
});
|
||||
} finally {
|
||||
@@ -637,8 +635,8 @@ export function useTaskSubmitHandlers({
|
||||
await submitCreateTask({ trimmedTitle, trimmedDescription, consent, attachments });
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Failed to create task",
|
||||
description: error instanceof Error ? error.message : GENERIC_ERROR_MESSAGE,
|
||||
title: t("task:failedToCreateTask"),
|
||||
description: error instanceof Error ? error.message : t(GENERIC_ERROR_KEY),
|
||||
variant: "error",
|
||||
});
|
||||
} finally {
|
||||
@@ -706,8 +704,8 @@ export function useTaskSubmitHandlers({
|
||||
onOpenChange(false);
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Failed to create task",
|
||||
description: error instanceof Error ? error.message : GENERIC_ERROR_MESSAGE,
|
||||
title: t("task:failedToCreateTask"),
|
||||
description: error instanceof Error ? error.message : t(GENERIC_ERROR_KEY),
|
||||
variant: "error",
|
||||
});
|
||||
} finally {
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { TaskFormInputsHandle } from "@/components/task-create-dialog-types
|
||||
import type { AgentProfileOption } from "@/lib/state/slices";
|
||||
import type { SummarizeSessionResult } from "@/hooks/use-summarize-session";
|
||||
import { applySummarizeSessionResult, type SummaryToastFn } from "./session-context-summary";
|
||||
import { t } from "@/lib/i18n";
|
||||
|
||||
type SessionContextChangeOpts = {
|
||||
promptRef: RefObject<TaskFormInputsHandle | null>;
|
||||
@@ -107,8 +108,8 @@ export function useSessionLaunchSubmit({
|
||||
onClose();
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Failed to create session",
|
||||
description: error instanceof Error ? error.message : "Unknown error",
|
||||
title: t("task:failedToCreateSession"),
|
||||
description: error instanceof Error ? error.message : t("common:unknownError"),
|
||||
variant: "error",
|
||||
});
|
||||
} finally {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useToast } from "@/components/toast-provider";
|
||||
import { useAppStore } from "@/components/state-provider";
|
||||
import { useTaskPR } from "@/hooks/domains/github/use-task-pr";
|
||||
@@ -19,14 +20,15 @@ import { useTaskReviewShortcut } from "./use-task-review-shortcut";
|
||||
*/
|
||||
export function TaskPRShortcut({ taskId }: { taskId: string | null }) {
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation("task");
|
||||
const { prs } = useTaskPR(taskId);
|
||||
const mrs = useTaskMRs(taskId);
|
||||
const overrides = useAppStore((s) => s.userSettings.keyboardShortcuts);
|
||||
const shortcut = getShortcut("OPEN_TASK_PR", overrides);
|
||||
const targets = useMemo(() => buildTaskReviewTargets(prs, mrs), [mrs, prs]);
|
||||
const onNoTargets = useCallback(
|
||||
() => toast({ description: "No pull request or merge request linked to this task" }),
|
||||
[toast],
|
||||
() => toast({ description: t("task:noPullOrMergeRequestLinked") }),
|
||||
[t, toast],
|
||||
);
|
||||
const onOpenTarget = useCallback((target: TaskReviewTarget) => {
|
||||
void openExternalLink(target.url).catch(() => undefined);
|
||||
|
||||
@@ -1901,4 +1901,32 @@ export const i18nGuardFiles = [
|
||||
// comment deferred the `label`/`keywords` to "the components/task migration".
|
||||
// Both are palette copy and belong with the rest of it.
|
||||
"components/task/recent-task-switcher-hooks.ts",
|
||||
// Second-audit gaps hidden in hook toasts, configuration objects, synthetic
|
||||
// display records, and developer QA page chrome rather than JSX text.
|
||||
"app/demo/agent-messages/page.tsx",
|
||||
"app/demo/messages/page.tsx",
|
||||
"components/azure-devops/azure-devops-mode-tabs.tsx",
|
||||
"components/azure-devops/azure-devops-scope-bar.tsx",
|
||||
"components/azure-devops/azure-devops-task-pull-request-chip.tsx",
|
||||
"components/gitlab/my-gitlab/quick-task-launcher.tsx",
|
||||
"components/shared/mermaid-error-toast.tsx",
|
||||
"components/task/task-pr-shortcut.tsx",
|
||||
"components/task-create-dialog-effects.ts",
|
||||
"components/task-create-dialog-fresh-branch-consent.ts",
|
||||
"components/task-create-dialog-submit.tsx",
|
||||
"hooks/domains/kanban/use-implement-fresh.ts",
|
||||
"hooks/domains/kanban/use-plan-actions.ts",
|
||||
"hooks/domains/review/use-finding-actions.ts",
|
||||
"hooks/domains/review/use-send-finding-to-agent.ts",
|
||||
"hooks/domains/session/use-commit-diff.ts",
|
||||
"hooks/domains/session/use-request-changes-walkthrough.ts",
|
||||
"hooks/domains/session/use-session-actions.ts",
|
||||
"hooks/domains/session/use-task-plan.ts",
|
||||
"hooks/use-file-editors.ts",
|
||||
"hooks/use-file-operations.ts",
|
||||
"hooks/use-nest-task.ts",
|
||||
"hooks/use-open-session-in-editor.ts",
|
||||
"hooks/use-sidebar-views-sync.ts",
|
||||
"hooks/use-task-workflow-move.ts",
|
||||
"lib/keyboard/shortcut-overrides.ts",
|
||||
];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type React from "react";
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppStore } from "@/components/state-provider";
|
||||
import { useToast } from "@/components/toast-provider";
|
||||
import { setChatDraftContent } from "@/lib/local-storage";
|
||||
@@ -51,6 +52,7 @@ export function useImplementFresh(
|
||||
const setActiveSession = useAppStore((s) => s.setActiveSession);
|
||||
const setTaskPlan = useAppStore((s) => s.setTaskPlan);
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation("task");
|
||||
|
||||
return useCallback(async () => {
|
||||
if (!taskId || !resolvedSessionId || !planningSession?.agent_profile_id) {
|
||||
@@ -87,7 +89,7 @@ export function useImplementFresh(
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("Failed to launch fresh implementation session:", err);
|
||||
toast({ description: "Failed to start implementation session", variant: "error" });
|
||||
toast({ description: t("task:failedToStartImplementationSession"), variant: "error" });
|
||||
return false;
|
||||
}
|
||||
}, [
|
||||
@@ -98,5 +100,6 @@ export function useImplementFresh(
|
||||
setTaskPlan,
|
||||
setActiveSession,
|
||||
toast,
|
||||
t,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useCallback, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppStore } from "@/components/state-provider";
|
||||
import { useToast } from "@/components/toast-provider";
|
||||
import { getWebSocketClient } from "@/lib/ws/connection";
|
||||
@@ -21,6 +22,7 @@ const AUTO_TRANSITION_ACTIONS = ["move_to_next", "move_to_previous", "move_to_st
|
||||
|
||||
export function useNextWorkflowStep(taskId: string | null) {
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation("task");
|
||||
const workflowId = useAppStore((s) => s.kanban.workflowId);
|
||||
const steps = useAppStore((s) => s.kanban.steps);
|
||||
const taskStepId = useAppStore((s) => {
|
||||
@@ -83,11 +85,11 @@ export function useNextWorkflowStep(taskId: string | null) {
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("Failed to proceed to next step:", err);
|
||||
toast({ description: "Failed to proceed to next step", variant: "error" });
|
||||
toast({ description: t("task:failedToProceedToNextStep"), variant: "error" });
|
||||
setMoveFromSessionId(null);
|
||||
return false;
|
||||
}
|
||||
}, [taskId, workflowId, nextStep, activeSessionId, toast]);
|
||||
}, [taskId, workflowId, nextStep, activeSessionId, t, toast]);
|
||||
|
||||
const proceedStepName = nextStep && !currentStepAutoTransitions ? nextStep.title : null;
|
||||
|
||||
@@ -158,6 +160,7 @@ function useImplementPlan(
|
||||
) {
|
||||
const setTaskPlan = useAppStore((s) => s.setTaskPlan);
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation("task");
|
||||
return useCallback(async (): Promise<boolean> => {
|
||||
if (!resolvedSessionId || !taskId) return false;
|
||||
|
||||
@@ -209,7 +212,7 @@ function useImplementPlan(
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("Failed to start implementation:", err);
|
||||
toast({ description: "Failed to start implementing the plan", variant: "error" });
|
||||
toast({ description: t("task:failedToStartImplementingPlan"), variant: "error" });
|
||||
return false;
|
||||
}
|
||||
}, [
|
||||
@@ -220,6 +223,7 @@ function useImplementPlan(
|
||||
clearPlanModeAfterSend,
|
||||
handlePlanModeChange,
|
||||
toast,
|
||||
t,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppStoreApi } from "@/components/state-provider";
|
||||
import { useToast } from "@/components/toast-provider";
|
||||
import { updateReviewFindingStatus } from "@/lib/api/domains/review-api";
|
||||
@@ -16,6 +17,7 @@ import type { ReviewFindingStatus, TaskReviewFinding } from "@/lib/types/review"
|
||||
export function useFindingActions(taskId: string | null | undefined) {
|
||||
const storeApi = useAppStoreApi();
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation("review");
|
||||
|
||||
const setStatus = useCallback(
|
||||
async (finding: TaskReviewFinding, status: ReviewFindingStatus) => {
|
||||
@@ -28,13 +30,13 @@ export function useFindingActions(taskId: string | null | undefined) {
|
||||
} catch (error) {
|
||||
storeApi.getState().updateReviewFinding(taskId, previous);
|
||||
toast({
|
||||
title: "Could not update finding",
|
||||
description: error instanceof Error ? error.message : "An error occurred",
|
||||
title: t("review:couldNotUpdateFinding"),
|
||||
description: error instanceof Error ? error.message : t("common:anErrorOccurred"),
|
||||
variant: "error",
|
||||
});
|
||||
}
|
||||
},
|
||||
[taskId, storeApi, toast],
|
||||
[taskId, storeApi, t, toast],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppStoreApi } from "@/components/state-provider";
|
||||
import { useToast } from "@/components/toast-provider";
|
||||
import { appendToQueue } from "@/lib/api/domains/queue-api";
|
||||
@@ -29,6 +30,7 @@ function isAgentBusy(state: string | undefined): boolean {
|
||||
export function useSendFindingToAgent({ taskId, sessionId }: Params) {
|
||||
const storeApi = useAppStoreApi();
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation("review");
|
||||
|
||||
return useCallback(
|
||||
async (finding: TaskReviewFinding) => {
|
||||
@@ -46,7 +48,7 @@ export function useSendFindingToAgent({ taskId, sessionId }: Params) {
|
||||
content,
|
||||
...(planMode ? { plan_mode: true } : {}),
|
||||
});
|
||||
toast({ title: "Finding queued for the agent", variant: "success" });
|
||||
toast({ title: t("review:findingQueuedForAgent"), variant: "success" });
|
||||
return;
|
||||
}
|
||||
const client = getWebSocketClient();
|
||||
@@ -66,15 +68,15 @@ export function useSendFindingToAgent({ taskId, sessionId }: Params) {
|
||||
// Add the returned message directly so the chat updates even if the
|
||||
// broadcast is missed; addMessage is idempotent on id.
|
||||
if (created?.id && created.session_id) storeApi.getState().addMessage(created);
|
||||
toast({ title: "Finding sent to the agent", variant: "success" });
|
||||
toast({ title: t("review:findingSentToAgent"), variant: "success" });
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Could not send the finding",
|
||||
description: error instanceof Error ? error.message : "An error occurred",
|
||||
title: t("review:couldNotSendFinding"),
|
||||
description: error instanceof Error ? error.message : t("common:anErrorOccurred"),
|
||||
variant: "error",
|
||||
});
|
||||
}
|
||||
},
|
||||
[taskId, sessionId, storeApi, toast],
|
||||
[taskId, sessionId, storeApi, t, toast],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppStore } from "@/components/state-provider";
|
||||
import { useToast } from "@/components/toast-provider";
|
||||
import { requestCommitDiff } from "@/components/task/commit-diff-request";
|
||||
@@ -29,6 +30,7 @@ export function useCommitDiff(commitSha: string, repo?: string): UseCommitDiffRe
|
||||
: false,
|
||||
);
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation("task");
|
||||
|
||||
const [files, setFiles] = useState<Record<string, FileInfo> | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -57,8 +59,8 @@ export function useCommitDiff(commitSha: string, repo?: string): UseCommitDiffRe
|
||||
} catch (err) {
|
||||
if (requestSeq !== requestSeqRef.current) return;
|
||||
toast({
|
||||
title: "Failed to load commit diff",
|
||||
description: err instanceof Error ? err.message : "An unexpected error occurred",
|
||||
title: t("task:failedToLoadCommitDiff"),
|
||||
description: err instanceof Error ? err.message : t("common:anUnexpectedErrorOccurred"),
|
||||
variant: "error",
|
||||
});
|
||||
} finally {
|
||||
@@ -66,7 +68,7 @@ export function useCommitDiff(commitSha: string, repo?: string): UseCommitDiffRe
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}, [activeSessionId, activeTaskId, agentctlReady, commitSha, repo, sessionTaskId, toast]);
|
||||
}, [activeSessionId, activeTaskId, agentctlReady, commitSha, repo, sessionTaskId, t, toast]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDiff();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppStoreApi } from "@/components/state-provider";
|
||||
import { useToast } from "@/components/toast-provider";
|
||||
import { listPrompts } from "@/lib/api";
|
||||
@@ -79,6 +80,7 @@ export function useRequestChangesWalkthrough({
|
||||
}: UseRequestChangesWalkthroughParams) {
|
||||
const storeApi = useAppStoreApi();
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation("review");
|
||||
|
||||
return useCallback(async () => {
|
||||
if (!taskId || !sessionId) return;
|
||||
@@ -88,7 +90,7 @@ export function useRequestChangesWalkthrough({
|
||||
const inputMode = deriveSessionInputMode(activeSession);
|
||||
const planModeEnabled = state.chatInput.planModeBySessionId[sessionId] ?? false;
|
||||
if (!ready) {
|
||||
toast({ title: "Changes are still loading", variant: "error" });
|
||||
toast({ title: t("review:changesStillLoading"), variant: "error" });
|
||||
return;
|
||||
}
|
||||
if (inputMode === "unavailable") {
|
||||
@@ -97,8 +99,8 @@ export function useRequestChangesWalkthrough({
|
||||
// is nothing session-specific to say.
|
||||
const title =
|
||||
activeSession && TERMINAL_SESSION_STATES.has(activeSession.state)
|
||||
? "Session has ended. Please create a new session to continue."
|
||||
: "Session is not available for input";
|
||||
? t("task:sessionEndedCreateNew")
|
||||
: t("task:sessionUnavailableForInput");
|
||||
toast({ title, variant: "error" });
|
||||
return;
|
||||
}
|
||||
@@ -112,15 +114,15 @@ export function useRequestChangesWalkthrough({
|
||||
content,
|
||||
planModeEnabled,
|
||||
});
|
||||
toast({ title: "Walkthrough request queued", variant: "success" });
|
||||
toast({ title: t("review:walkthroughRequestQueued"), variant: "success" });
|
||||
return;
|
||||
}
|
||||
|
||||
await sendWalkthroughRequest({ taskId, sessionId, content, planModeEnabled, state });
|
||||
toast({ title: "Walkthrough request sent", variant: "success" });
|
||||
toast({ title: t("review:walkthroughRequestSent"), variant: "success" });
|
||||
} catch (error) {
|
||||
console.error("Failed to request walkthrough:", error);
|
||||
toast({ title: "Failed to request walkthrough", variant: "error" });
|
||||
toast({ title: t("review:failedToRequestWalkthrough"), variant: "error" });
|
||||
}
|
||||
}, [ready, sessionId, storeApi, taskId, toast]);
|
||||
}, [ready, sessionId, storeApi, t, taskId, toast]);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppStore, useAppStoreApi } from "@/components/state-provider";
|
||||
import { useToast } from "@/components/toast-provider";
|
||||
import { getWebSocketClient } from "@/lib/ws/connection";
|
||||
@@ -39,29 +40,39 @@ type WsActionFn = (
|
||||
|
||||
function useWsAction(): WsActionFn {
|
||||
const { toast, updateToast } = useToast();
|
||||
const { t } = useTranslation("task");
|
||||
return useCallback(
|
||||
async (action, label, payload, timeout = 15000, feedback = "toast") => {
|
||||
async (action, labelKey, payload, timeout = 15000, feedback = "toast") => {
|
||||
const client = getWebSocketClient();
|
||||
if (!client) return false;
|
||||
const toastId =
|
||||
feedback === "toast" ? toast({ title: `${label}...`, variant: "loading" }) : null;
|
||||
feedback === "toast"
|
||||
? toast({
|
||||
title: t("task:sessionActionProgress", { action: t(labelKey) }),
|
||||
variant: "loading",
|
||||
})
|
||||
: null;
|
||||
try {
|
||||
await client.request(action, payload, timeout);
|
||||
if (toastId) {
|
||||
updateToast(toastId, { title: `${label} successful`, variant: "success" });
|
||||
updateToast(toastId, {
|
||||
title: t("task:sessionActionSuccessful", { action: t(labelKey) }),
|
||||
variant: "success",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
const msg = error instanceof Error ? error.message : "Unknown error";
|
||||
const msg = error instanceof Error ? error.message : t("common:unknownError");
|
||||
const title = t("task:sessionActionFailed", { action: t(labelKey) });
|
||||
if (toastId) {
|
||||
updateToast(toastId, { title: `${label} failed`, description: msg, variant: "error" });
|
||||
updateToast(toastId, { title, description: msg, variant: "error" });
|
||||
} else {
|
||||
toast({ title: `${label} failed`, description: msg, variant: "error" });
|
||||
toast({ title, description: msg, variant: "error" });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[toast, updateToast],
|
||||
[t, toast, updateToast],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -78,12 +89,19 @@ export function useSessionActions({ sessionId, taskId, onDeleted }: SessionActio
|
||||
const setPrimary = useCallback(
|
||||
() =>
|
||||
sessionId &&
|
||||
wsAction("session.set_primary", "Set primary", { session_id: sessionId }, 15000, "inline"),
|
||||
wsAction(
|
||||
"session.set_primary",
|
||||
"task:sessionActionSetPrimary",
|
||||
{ session_id: sessionId },
|
||||
15000,
|
||||
"inline",
|
||||
),
|
||||
[sessionId, wsAction],
|
||||
);
|
||||
|
||||
const stop = useCallback(
|
||||
() => sessionId && wsAction("session.stop", "Stopping session", { session_id: sessionId }),
|
||||
() =>
|
||||
sessionId && wsAction("session.stop", "task:sessionActionStop", { session_id: sessionId }),
|
||||
[sessionId, wsAction],
|
||||
);
|
||||
|
||||
@@ -93,7 +111,7 @@ export function useSessionActions({ sessionId, taskId, onDeleted }: SessionActio
|
||||
taskId &&
|
||||
wsAction(
|
||||
"session.launch",
|
||||
"Resuming session",
|
||||
"task:sessionActionResume",
|
||||
{ task_id: taskId, intent: "resume", session_id: sessionId },
|
||||
30000,
|
||||
),
|
||||
@@ -105,7 +123,7 @@ export function useSessionActions({ sessionId, taskId, onDeleted }: SessionActio
|
||||
if (!sessionId || !taskId) return false;
|
||||
const ok = await wsAction(
|
||||
"session.delete",
|
||||
"Deleting session",
|
||||
"task:sessionActionDelete",
|
||||
{ session_id: sessionId },
|
||||
15000,
|
||||
options.feedback,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useCallback, useState, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppStore, useAppStoreApi } from "@/components/state-provider";
|
||||
import {
|
||||
getTaskPlan,
|
||||
@@ -20,6 +21,7 @@ const EMPTY_REVISIONS: readonly TaskPlanRevision[] = Object.freeze([]);
|
||||
* @param options.visible - When true, refetches the plan (use for tab visibility)
|
||||
*/
|
||||
export function useTaskPlan(taskId: string | null, options?: { visible?: boolean }) {
|
||||
const { t } = useTranslation("task");
|
||||
const { visible = true } = options ?? {};
|
||||
const prevVisibleRef = useRef(visible);
|
||||
const plan = useAppStore((state) => (taskId ? state.taskPlans.byTaskId[taskId] : undefined));
|
||||
@@ -52,13 +54,12 @@ export function useTaskPlan(taskId: string | null, options?: { visible?: boolean
|
||||
markTaskPlanSeen(taskId);
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch task plan:", err);
|
||||
setError(err instanceof Error ? err.message : "Failed to fetch plan");
|
||||
setError(err instanceof Error ? err.message : t("task:failedToFetchPlan"));
|
||||
} finally {
|
||||
setTaskPlanLoading(taskId, false);
|
||||
}
|
||||
}, [taskId, setTaskPlan, setTaskPlanLoading, markTaskPlanSeen]);
|
||||
}, [taskId, setTaskPlan, setTaskPlanLoading, markTaskPlanSeen, t]);
|
||||
|
||||
// Fetch plan on mount or when taskId changes
|
||||
useEffect(() => {
|
||||
if (connectionStatus !== "connected") return;
|
||||
if (taskId && !isLoaded && !isLoading) {
|
||||
@@ -69,11 +70,10 @@ export function useTaskPlan(taskId: string | null, options?: { visible?: boolean
|
||||
// Refetch when becoming visible (e.g., tab switch)
|
||||
useEffect(() => {
|
||||
const wasHidden = !prevVisibleRef.current;
|
||||
const isNowVisible = visible;
|
||||
prevVisibleRef.current = visible;
|
||||
|
||||
// Only refetch when transitioning from hidden to visible
|
||||
if (wasHidden && isNowVisible && connectionStatus === "connected" && taskId) {
|
||||
if (wasHidden && visible && connectionStatus === "connected" && taskId) {
|
||||
fetchPlan();
|
||||
}
|
||||
}, [visible, connectionStatus, taskId, fetchPlan]);
|
||||
@@ -97,13 +97,13 @@ export function useTaskPlan(taskId: string | null, options?: { visible?: boolean
|
||||
return savedPlan;
|
||||
} catch (err) {
|
||||
console.error("Failed to save task plan:", err);
|
||||
setError(err instanceof Error ? err.message : "Failed to save plan");
|
||||
setError(err instanceof Error ? err.message : t("task:failedToSavePlan"));
|
||||
return null;
|
||||
} finally {
|
||||
setTaskPlanSaving(taskId, false);
|
||||
}
|
||||
},
|
||||
[taskId, plan, setTaskPlan, setTaskPlanSaving],
|
||||
[taskId, plan, setTaskPlan, setTaskPlanSaving, t],
|
||||
);
|
||||
|
||||
const removePlan = useCallback(async (): Promise<boolean> => {
|
||||
@@ -117,12 +117,12 @@ export function useTaskPlan(taskId: string | null, options?: { visible?: boolean
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("Failed to delete task plan:", err);
|
||||
setError(err instanceof Error ? err.message : "Failed to delete plan");
|
||||
setError(err instanceof Error ? err.message : t("task:failedToDeletePlan"));
|
||||
return false;
|
||||
} finally {
|
||||
setTaskPlanSaving(taskId, false);
|
||||
}
|
||||
}, [taskId, setTaskPlan, setTaskPlanSaving]);
|
||||
}, [taskId, setTaskPlan, setTaskPlanSaving, t]);
|
||||
|
||||
const revisionsBundle = useTaskPlanRevisions(taskId, setTaskPlanSaving, setError);
|
||||
|
||||
@@ -148,6 +148,7 @@ function useTaskPlanRevisions(
|
||||
setTaskPlanSaving: (taskId: string, saving: boolean) => void,
|
||||
setError: (err: string | null) => void,
|
||||
) {
|
||||
const { t } = useTranslation("task");
|
||||
const revisions = useAppStore((state) =>
|
||||
taskId ? (state.taskPlans.revisionsByTaskId[taskId] ?? EMPTY_REVISIONS) : EMPTY_REVISIONS,
|
||||
) as TaskPlanRevision[];
|
||||
@@ -171,11 +172,11 @@ function useTaskPlanRevisions(
|
||||
setPlanRevisions(taskId, list);
|
||||
} catch (err) {
|
||||
console.error("Failed to load plan revisions:", err);
|
||||
setError(err instanceof Error ? err.message : "Failed to load revisions");
|
||||
setError(err instanceof Error ? err.message : t("task:failedToLoadPlanRevisions"));
|
||||
} finally {
|
||||
setPlanRevisionsLoading(taskId, false);
|
||||
}
|
||||
}, [taskId, setPlanRevisions, setPlanRevisionsLoading, setError]);
|
||||
}, [taskId, setPlanRevisions, setPlanRevisionsLoading, setError, t]);
|
||||
|
||||
// Load revisions once on mount — events may have fired before the WS connected.
|
||||
useEffect(() => {
|
||||
@@ -211,13 +212,13 @@ function useTaskPlanRevisions(
|
||||
return await revertPlanRevision(taskId, revisionId, authorName);
|
||||
} catch (err) {
|
||||
console.error("Failed to revert plan:", err);
|
||||
setError(err instanceof Error ? err.message : "Failed to revert plan");
|
||||
setError(err instanceof Error ? err.message : t("task:failedToRevertPlan"));
|
||||
return null;
|
||||
} finally {
|
||||
setTaskPlanSaving(taskId, false);
|
||||
}
|
||||
},
|
||||
[taskId, setTaskPlanSaving, setError],
|
||||
[taskId, setTaskPlanSaving, setError, t],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { useSessionGitStatus } from "@/hooks/domains/session/use-session-git-sta
|
||||
import { useSaveDeleteActions } from "./use-file-save-delete";
|
||||
import { buildRepoScopedItemId, PREVIEW_FILE_EDITOR_ID } from "@/lib/state/dockview-panel-actions";
|
||||
import { useOpenFileWorkspaceSync } from "./file-editors-sync";
|
||||
import { t } from "@/lib/i18n";
|
||||
import {
|
||||
fetchFileEditorState,
|
||||
getPreviewItemIdToRemoveOnReplace,
|
||||
@@ -410,8 +411,8 @@ function useOpenFileAction({
|
||||
setFileState(fileKey, state);
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Failed to open file",
|
||||
description: error instanceof Error ? error.message : "Unknown error",
|
||||
title: t("task:failedToOpenFile"),
|
||||
description: error instanceof Error ? error.message : t("common:unknownError"),
|
||||
variant: "error",
|
||||
});
|
||||
}
|
||||
@@ -489,8 +490,8 @@ function useMarkdownPreviewAction({
|
||||
setFileState(fileKey, { ...state, markdownPreview: true });
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Failed to open file",
|
||||
description: error instanceof Error ? error.message : "Unknown error",
|
||||
title: t("task:failedToOpenFile"),
|
||||
description: error instanceof Error ? error.message : t("common:unknownError"),
|
||||
variant: "error",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { downloadFileContent } from "./use-file-operations";
|
||||
|
||||
const SESSION_ID = "sess-1";
|
||||
const NESTED_PATH = "src/foo/bar.ts";
|
||||
const UNKNOWN_ERROR = "Unknown error";
|
||||
const FAKE_CLIENT = {} as unknown as Parameters<typeof downloadFileContent>[0];
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -36,7 +37,7 @@ describe("downloadFileContent", () => {
|
||||
is_binary: false,
|
||||
});
|
||||
|
||||
const result = await downloadFileContent(FAKE_CLIENT, SESSION_ID, NESTED_PATH);
|
||||
const result = await downloadFileContent(FAKE_CLIENT, SESSION_ID, NESTED_PATH, UNKNOWN_ERROR);
|
||||
|
||||
expect(result).toEqual({ ok: true });
|
||||
expect(requestFileContentMock).toHaveBeenCalledWith(FAKE_CLIENT, SESSION_ID, NESTED_PATH);
|
||||
@@ -54,7 +55,7 @@ describe("downloadFileContent", () => {
|
||||
is_binary: true,
|
||||
});
|
||||
|
||||
await downloadFileContent(FAKE_CLIENT, SESSION_ID, "assets/logo.png");
|
||||
await downloadFileContent(FAKE_CLIENT, SESSION_ID, "assets/logo.png", UNKNOWN_ERROR);
|
||||
|
||||
expect(triggerFileDownloadMock).toHaveBeenCalledWith({
|
||||
fileName: "assets/logo.png",
|
||||
@@ -70,7 +71,7 @@ describe("downloadFileContent", () => {
|
||||
error: "Permission denied",
|
||||
});
|
||||
|
||||
const result = await downloadFileContent(FAKE_CLIENT, SESSION_ID, "src/foo.ts");
|
||||
const result = await downloadFileContent(FAKE_CLIENT, SESSION_ID, "src/foo.ts", UNKNOWN_ERROR);
|
||||
|
||||
expect(result).toEqual({ ok: false, error: "Permission denied" });
|
||||
expect(triggerFileDownloadMock).not.toHaveBeenCalled();
|
||||
@@ -79,7 +80,7 @@ describe("downloadFileContent", () => {
|
||||
it("returns {ok: false} when the request throws", async () => {
|
||||
requestFileContentMock.mockRejectedValueOnce(new Error("boom"));
|
||||
|
||||
const result = await downloadFileContent(FAKE_CLIENT, SESSION_ID, "src/foo.ts");
|
||||
const result = await downloadFileContent(FAKE_CLIENT, SESSION_ID, "src/foo.ts", UNKNOWN_ERROR);
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) expect(result.error).toContain("boom");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getWebSocketClient } from "@/lib/ws/connection";
|
||||
import type { WebSocketClient } from "@/lib/ws/client";
|
||||
import { createFile, deleteFile, renameFile, requestFileContent } from "@/lib/ws/workspace-files";
|
||||
@@ -8,7 +9,6 @@ import { useToast } from "@/components/toast-provider";
|
||||
type ToastFn = ReturnType<typeof useToast>["toast"];
|
||||
|
||||
const ERROR_VARIANT = "error" as const;
|
||||
const UNKNOWN_ERROR = "An unknown error occurred";
|
||||
|
||||
type DownloadResult = { ok: true } | { ok: false; error?: string };
|
||||
|
||||
@@ -20,6 +20,7 @@ export async function downloadFileContent(
|
||||
client: WebSocketClient,
|
||||
sessionId: string,
|
||||
path: string,
|
||||
unknownError: string,
|
||||
): Promise<DownloadResult> {
|
||||
try {
|
||||
const response = await requestFileContent(client, sessionId, path);
|
||||
@@ -31,24 +32,25 @@ export async function downloadFileContent(
|
||||
});
|
||||
return { ok: true };
|
||||
} catch (error) {
|
||||
return { ok: false, error: error instanceof Error ? error.message : UNKNOWN_ERROR };
|
||||
return { ok: false, error: error instanceof Error ? error.message : unknownError };
|
||||
}
|
||||
}
|
||||
|
||||
async function runFileOp<T extends { success: boolean; error?: string }>(
|
||||
op: () => Promise<T>,
|
||||
title: string,
|
||||
unknownError: string,
|
||||
toast: ToastFn,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const response = await op();
|
||||
if (!response.success) {
|
||||
toast({ title, description: response.error || UNKNOWN_ERROR, variant: ERROR_VARIANT });
|
||||
toast({ title, description: response.error || unknownError, variant: ERROR_VARIANT });
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
const description = error instanceof Error ? error.message : UNKNOWN_ERROR;
|
||||
const description = error instanceof Error ? error.message : unknownError;
|
||||
toast({ title, description, variant: ERROR_VARIANT });
|
||||
return false;
|
||||
}
|
||||
@@ -56,23 +58,35 @@ async function runFileOp<T extends { success: boolean; error?: string }>(
|
||||
|
||||
export function useFileOperations(sessionId: string | null) {
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation("task");
|
||||
const unknownError = t("common:anUnknownErrorOccurred");
|
||||
|
||||
const handleCreateFile = useCallback(
|
||||
async (path: string): Promise<boolean> => {
|
||||
const client = getWebSocketClient();
|
||||
if (!client || !sessionId) return false;
|
||||
return runFileOp(() => createFile(client, sessionId, path), "Failed to create file", toast);
|
||||
return runFileOp(
|
||||
() => createFile(client, sessionId, path),
|
||||
t("task:failedToCreateFile"),
|
||||
unknownError,
|
||||
toast,
|
||||
);
|
||||
},
|
||||
[sessionId, toast],
|
||||
[sessionId, t, toast, unknownError],
|
||||
);
|
||||
|
||||
const handleDeleteFile = useCallback(
|
||||
async (path: string): Promise<boolean> => {
|
||||
const client = getWebSocketClient();
|
||||
if (!client || !sessionId) return false;
|
||||
return runFileOp(() => deleteFile(client, sessionId, path), "Failed to delete item", toast);
|
||||
return runFileOp(
|
||||
() => deleteFile(client, sessionId, path),
|
||||
t("task:failedToDeleteItem"),
|
||||
unknownError,
|
||||
toast,
|
||||
);
|
||||
},
|
||||
[sessionId, toast],
|
||||
[sessionId, t, toast, unknownError],
|
||||
);
|
||||
|
||||
const handleRenameFile = useCallback(
|
||||
@@ -81,29 +95,30 @@ export function useFileOperations(sessionId: string | null) {
|
||||
if (!client || !sessionId) return false;
|
||||
return runFileOp(
|
||||
() => renameFile(client, sessionId, oldPath, newPath),
|
||||
"Failed to rename item",
|
||||
t("task:failedToRenameItem"),
|
||||
unknownError,
|
||||
toast,
|
||||
);
|
||||
},
|
||||
[sessionId, toast],
|
||||
[sessionId, t, toast, unknownError],
|
||||
);
|
||||
|
||||
const handleDownloadFile = useCallback(
|
||||
async (path: string): Promise<boolean> => {
|
||||
const client = getWebSocketClient();
|
||||
if (!client || !sessionId) return false;
|
||||
const result = await downloadFileContent(client, sessionId, path);
|
||||
const result = await downloadFileContent(client, sessionId, path, unknownError);
|
||||
if (!result.ok) {
|
||||
toast({
|
||||
title: "Failed to download file",
|
||||
description: result.error || UNKNOWN_ERROR,
|
||||
title: t("task:failedToDownloadFile"),
|
||||
description: result.error || unknownError,
|
||||
variant: ERROR_VARIANT,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[sessionId, toast],
|
||||
[sessionId, t, toast, unknownError],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { toast } from "@/lib/toast/sonner";
|
||||
import { detachTask, updateTask } from "@/lib/api";
|
||||
import { useAppStoreApi } from "@/components/state-provider";
|
||||
import type { WorkflowSnapshotData } from "@/lib/state/slices/kanban/types";
|
||||
import { t } from "@/lib/i18n";
|
||||
|
||||
type StoreApi = ReturnType<typeof useAppStoreApi>;
|
||||
type SnapshotTask = WorkflowSnapshotData["tasks"][number];
|
||||
@@ -127,7 +128,7 @@ export function useNestTask() {
|
||||
}
|
||||
} catch (error) {
|
||||
rollbackParent(op);
|
||||
toast.error(error instanceof Error ? error.message : "Failed to nest task");
|
||||
toast.error(error instanceof Error ? error.message : t("task:failedToNestTask"));
|
||||
}
|
||||
},
|
||||
[store],
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useRequest } from "@/lib/http/use-request";
|
||||
import { useDockviewStore } from "@/lib/state/dockview-store";
|
||||
import { openFileInVscode } from "@/lib/api/domains/vscode-api";
|
||||
import { useToast } from "@/components/toast-provider";
|
||||
import { t } from "@/lib/i18n";
|
||||
|
||||
type OpenEditorOptions = {
|
||||
filePath?: string;
|
||||
@@ -88,8 +89,8 @@ export function useOpenSessionInEditor(sessionId?: string | null) {
|
||||
return await request.run(options);
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Failed to open editor",
|
||||
description: error instanceof Error ? error.message : "Request failed",
|
||||
title: t("editors:failedToOpenEditor"),
|
||||
description: error instanceof Error ? error.message : t("common:requestFailed"),
|
||||
variant: "error",
|
||||
});
|
||||
return null;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppStore } from "@/components/state-provider";
|
||||
import { useToast } from "@/components/toast-provider";
|
||||
|
||||
@@ -11,16 +12,21 @@ export function useSidebarViewsSync() {
|
||||
const clearError = useAppStore((s) => s.clearSidebarSyncError);
|
||||
const clearTaskPrefsError = useAppStore((s) => s.clearSidebarTaskPrefsSyncError);
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation("sidebar");
|
||||
|
||||
useEffect(() => {
|
||||
if (!syncError) return;
|
||||
toast({ title: "Sidebar views", description: syncError, variant: "error" });
|
||||
toast({ title: t("sidebar:views"), description: syncError, variant: "error" });
|
||||
clearError();
|
||||
}, [syncError, toast, clearError]);
|
||||
}, [syncError, t, toast, clearError]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!taskPrefsSyncError) return;
|
||||
toast({ title: "Sidebar task preferences", description: taskPrefsSyncError, variant: "error" });
|
||||
toast({
|
||||
title: t("sidebar:taskPreferences"),
|
||||
description: taskPrefsSyncError,
|
||||
variant: "error",
|
||||
});
|
||||
clearTaskPrefsError();
|
||||
}, [taskPrefsSyncError, toast, clearTaskPrefsError]);
|
||||
}, [taskPrefsSyncError, t, toast, clearTaskPrefsError]);
|
||||
}
|
||||
|
||||
@@ -1,31 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { bulkMoveSelectedTasks } from "@/lib/api";
|
||||
import { useToast } from "@/components/toast-provider";
|
||||
|
||||
function errorMessage(error: unknown) {
|
||||
return error instanceof Error ? error.message : "Failed to move task";
|
||||
}
|
||||
|
||||
function movedTitle(movedCount: number, destination: "step" | "workflow") {
|
||||
if (movedCount === 1) return `Moved task to ${destination}`;
|
||||
return `Moved ${movedCount} tasks to ${destination}`;
|
||||
}
|
||||
|
||||
function movedDescription(movedCount: number, destination: "step" | "workflow") {
|
||||
if (destination === "step") {
|
||||
return movedCount === 1
|
||||
? "The task is now in the selected step."
|
||||
: "The tasks are now in the selected step.";
|
||||
}
|
||||
return movedCount === 1
|
||||
? "Switch to the destination workflow to see it."
|
||||
: "Switch to the destination workflow to see them.";
|
||||
}
|
||||
|
||||
export function useTaskWorkflowMove() {
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation("task");
|
||||
|
||||
return useCallback(
|
||||
async (
|
||||
@@ -43,19 +25,26 @@ export function useTaskWorkflowMove() {
|
||||
target_step_id: targetStepId,
|
||||
});
|
||||
toast({
|
||||
title: movedTitle(result.moved_count, destination),
|
||||
description: movedDescription(result.moved_count, destination),
|
||||
title: t(destination === "step" ? "task:movedTasksToStep" : "task:movedTasksToWorkflow", {
|
||||
count: result.moved_count,
|
||||
}),
|
||||
description: t(
|
||||
destination === "step"
|
||||
? "task:movedTasksStepDescription"
|
||||
: "task:movedTasksWorkflowDescription",
|
||||
{ count: result.moved_count },
|
||||
),
|
||||
variant: "success",
|
||||
});
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Failed to move task",
|
||||
description: errorMessage(error),
|
||||
title: t("task:failedToMoveTask"),
|
||||
description: error instanceof Error ? error.message : t("task:failedToMoveTask"),
|
||||
variant: "error",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[toast],
|
||||
[t, toast],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -59,11 +59,12 @@ describe("buildConfigurableShortcutEntries", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const entries = buildConfigurableShortcutEntries([plugin]);
|
||||
const entries = buildConfigurableShortcutEntries([plugin], (key) => `translated:${key}`);
|
||||
const coreEntry = entries.find((e) => e.source === "core" && e.id === "SEARCH");
|
||||
const pluginEntry = entries.find((e) => e.source === "plugin" && e.id === NAMESPACED_ID);
|
||||
|
||||
expect(coreEntry).toBeDefined();
|
||||
expect(coreEntry?.label).toBe("translated:settings:shortcutCommandPanel");
|
||||
expect(pluginEntry).toMatchObject({
|
||||
source: "plugin",
|
||||
id: NAMESPACED_ID,
|
||||
|
||||
@@ -45,11 +45,15 @@ export type ShortcutEntry =
|
||||
* than raw `PluginRecord[]`) can merge core + plugin entries without
|
||||
* re-deriving the core list themselves.
|
||||
*/
|
||||
export function coreShortcutEntries(): ShortcutEntry[] {
|
||||
type TranslateShortcutLabel = (key: string) => string;
|
||||
|
||||
export function coreShortcutEntries(
|
||||
translate: TranslateShortcutLabel = (key) => key,
|
||||
): ShortcutEntry[] {
|
||||
return (Object.keys(CONFIGURABLE_SHORTCUTS) as ConfigurableShortcutId[]).map((id) => ({
|
||||
source: "core",
|
||||
id,
|
||||
label: CONFIGURABLE_SHORTCUTS[id].label,
|
||||
label: translate(CONFIGURABLE_SHORTCUTS[id].labelKey),
|
||||
default: CONFIGURABLE_SHORTCUTS[id].default,
|
||||
}));
|
||||
}
|
||||
@@ -89,8 +93,11 @@ function buildPluginEntries(plugins: PluginRecord[]): ShortcutEntry[] {
|
||||
* by plugin (dynamic) entries — for rendering in Settings > Keyboard
|
||||
* Shortcuts. Core behavior/order is unchanged from `CONFIGURABLE_SHORTCUTS`.
|
||||
*/
|
||||
export function buildConfigurableShortcutEntries(plugins: PluginRecord[]): ShortcutEntry[] {
|
||||
return [...coreShortcutEntries(), ...buildPluginEntries(plugins)];
|
||||
export function buildConfigurableShortcutEntries(
|
||||
plugins: PluginRecord[],
|
||||
translate?: TranslateShortcutLabel,
|
||||
): ShortcutEntry[] {
|
||||
return [...coreShortcutEntries(translate), ...buildPluginEntries(plugins)];
|
||||
}
|
||||
|
||||
/** Builds only the plugin-sourced entries — used by callers that already have core entries. */
|
||||
|
||||
@@ -31,49 +31,26 @@ describe("CONFIGURABLE_SHORTCUTS", () => {
|
||||
expect(ids).toHaveLength(16);
|
||||
});
|
||||
|
||||
it("each entry has a label and default matching SHORTCUTS", () => {
|
||||
expect(CONFIGURABLE_SHORTCUTS.CONTENT_SEARCH.label).toBe("Search Task Contents");
|
||||
it("each entry has a catalog key and default matching SHORTCUTS", () => {
|
||||
for (const entry of Object.values(CONFIGURABLE_SHORTCUTS)) {
|
||||
expect(entry.labelKey).toMatch(/^settings:shortcut/);
|
||||
}
|
||||
expect(CONFIGURABLE_SHORTCUTS.CONTENT_SEARCH.default).toBe(SHORTCUTS.CONTENT_SEARCH);
|
||||
expect(CONFIGURABLE_SHORTCUTS.BOTTOM_TERMINAL.label).toBe("Toggle Bottom Terminal");
|
||||
expect(CONFIGURABLE_SHORTCUTS.BOTTOM_TERMINAL.default).toBe(SHORTCUTS.BOTTOM_TERMINAL);
|
||||
|
||||
expect(CONFIGURABLE_SHORTCUTS.TOGGLE_SIDEBAR.label).toBe("Toggle Sidebar");
|
||||
expect(CONFIGURABLE_SHORTCUTS.TOGGLE_SIDEBAR.default).toBe(UNBOUND_SHORTCUT);
|
||||
|
||||
expect(CONFIGURABLE_SHORTCUTS.COMMAND_PANEL.label).toBe("Command Panel (Alt)");
|
||||
expect(CONFIGURABLE_SHORTCUTS.COMMAND_PANEL.default).toBe(SHORTCUTS.COMMAND_PANEL);
|
||||
|
||||
expect(CONFIGURABLE_SHORTCUTS.NEW_TASK.label).toBe("New Task");
|
||||
expect(CONFIGURABLE_SHORTCUTS.NEW_TASK.default).toBe(SHORTCUTS.NEW_TASK);
|
||||
|
||||
expect(CONFIGURABLE_SHORTCUTS.FOCUS_INPUT.label).toBe("Focus Chat Input");
|
||||
expect(CONFIGURABLE_SHORTCUTS.FOCUS_INPUT.default).toBe(SHORTCUTS.FOCUS_INPUT);
|
||||
|
||||
expect(CONFIGURABLE_SHORTCUTS.FOCUS_PASSTHROUGH_INPUT.label).toBe("Focus CLI Chat Input");
|
||||
expect(CONFIGURABLE_SHORTCUTS.FOCUS_PASSTHROUGH_INPUT.default).toBe(
|
||||
SHORTCUTS.FOCUS_PASSTHROUGH_INPUT,
|
||||
);
|
||||
|
||||
expect(CONFIGURABLE_SHORTCUTS.TOGGLE_PLAN_MODE.label).toBe("Toggle Plan Mode");
|
||||
expect(CONFIGURABLE_SHORTCUTS.TOGGLE_PLAN_MODE.default).toBe(SHORTCUTS.TOGGLE_PLAN_MODE);
|
||||
|
||||
expect(CONFIGURABLE_SHORTCUTS.TASK_SWITCHER.label).toBe("Recent Task Switcher");
|
||||
expect(CONFIGURABLE_SHORTCUTS.TASK_SWITCHER.default).toBe(SHORTCUTS.TASK_SWITCHER);
|
||||
|
||||
expect(CONFIGURABLE_SHORTCUTS.TASK_SWITCHER_REVERSE.label).toBe(
|
||||
"Recent Task Switcher (Backward)",
|
||||
);
|
||||
expect(CONFIGURABLE_SHORTCUTS.TASK_SWITCHER_REVERSE.default).toBe(
|
||||
SHORTCUTS.TASK_SWITCHER_REVERSE,
|
||||
);
|
||||
|
||||
expect(CONFIGURABLE_SHORTCUTS.REVERSE_SEARCH.label).toBe("Reverse Chat Search");
|
||||
expect(CONFIGURABLE_SHORTCUTS.REVERSE_SEARCH.default).toBe(SHORTCUTS.REVERSE_SEARCH);
|
||||
|
||||
expect(CONFIGURABLE_SHORTCUTS.OPEN_TASK_PR.label).toBe("Open Task Pull Request");
|
||||
expect(CONFIGURABLE_SHORTCUTS.OPEN_TASK_PR.default).toBe(SHORTCUTS.OPEN_TASK_PR);
|
||||
|
||||
expect(CONFIGURABLE_SHORTCUTS.WORKSPACE_PICKER.label).toBe("Open Workspace Picker");
|
||||
expect(CONFIGURABLE_SHORTCUTS.WORKSPACE_PICKER.default).toBe(SHORTCUTS.WORKSPACE_PICKER);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -36,30 +36,51 @@ export function isUnboundShortcut(shortcut: KeyboardShortcut | undefined | null)
|
||||
|
||||
export const CONFIGURABLE_SHORTCUTS: Record<
|
||||
ConfigurableShortcutId,
|
||||
{ label: string; default: KeyboardShortcut }
|
||||
{ labelKey: string; default: KeyboardShortcut }
|
||||
> = {
|
||||
SEARCH: { label: "Command Panel", default: SHORTCUTS.SEARCH },
|
||||
FILE_SEARCH: { label: "File Search", default: SHORTCUTS.FILE_SEARCH },
|
||||
CONTENT_SEARCH: { label: "Search Task Contents", default: SHORTCUTS.CONTENT_SEARCH },
|
||||
QUICK_CHAT: { label: "Quick Chat", default: SHORTCUTS.QUICK_CHAT },
|
||||
BOTTOM_TERMINAL: { label: "Toggle Bottom Terminal", default: SHORTCUTS.BOTTOM_TERMINAL },
|
||||
TOGGLE_SIDEBAR: { label: "Toggle Sidebar", default: UNBOUND_SHORTCUT },
|
||||
COMMAND_PANEL: { label: "Command Panel (Alt)", default: SHORTCUTS.COMMAND_PANEL },
|
||||
NEW_TASK: { label: "New Task", default: SHORTCUTS.NEW_TASK },
|
||||
FOCUS_INPUT: { label: "Focus Chat Input", default: SHORTCUTS.FOCUS_INPUT },
|
||||
SEARCH: { labelKey: "settings:shortcutCommandPanel", default: SHORTCUTS.SEARCH },
|
||||
FILE_SEARCH: { labelKey: "settings:shortcutFileSearch", default: SHORTCUTS.FILE_SEARCH },
|
||||
CONTENT_SEARCH: {
|
||||
labelKey: "settings:shortcutTaskContentSearch",
|
||||
default: SHORTCUTS.CONTENT_SEARCH,
|
||||
},
|
||||
QUICK_CHAT: { labelKey: "settings:shortcutQuickChat", default: SHORTCUTS.QUICK_CHAT },
|
||||
BOTTOM_TERMINAL: {
|
||||
labelKey: "settings:shortcutBottomTerminal",
|
||||
default: SHORTCUTS.BOTTOM_TERMINAL,
|
||||
},
|
||||
TOGGLE_SIDEBAR: { labelKey: "settings:shortcutToggleSidebar", default: UNBOUND_SHORTCUT },
|
||||
COMMAND_PANEL: { labelKey: "settings:shortcutCommandPanelAlt", default: SHORTCUTS.COMMAND_PANEL },
|
||||
NEW_TASK: { labelKey: "settings:shortcutNewTask", default: SHORTCUTS.NEW_TASK },
|
||||
FOCUS_INPUT: { labelKey: "settings:shortcutFocusChatInput", default: SHORTCUTS.FOCUS_INPUT },
|
||||
FOCUS_PASSTHROUGH_INPUT: {
|
||||
label: "Focus CLI Chat Input",
|
||||
labelKey: "settings:shortcutFocusCliChatInput",
|
||||
default: SHORTCUTS.FOCUS_PASSTHROUGH_INPUT,
|
||||
},
|
||||
TOGGLE_PLAN_MODE: { label: "Toggle Plan Mode", default: SHORTCUTS.TOGGLE_PLAN_MODE },
|
||||
TASK_SWITCHER: { label: "Recent Task Switcher", default: SHORTCUTS.TASK_SWITCHER },
|
||||
TOGGLE_PLAN_MODE: {
|
||||
labelKey: "settings:shortcutTogglePlanMode",
|
||||
default: SHORTCUTS.TOGGLE_PLAN_MODE,
|
||||
},
|
||||
TASK_SWITCHER: {
|
||||
labelKey: "settings:shortcutRecentTaskSwitcher",
|
||||
default: SHORTCUTS.TASK_SWITCHER,
|
||||
},
|
||||
TASK_SWITCHER_REVERSE: {
|
||||
label: "Recent Task Switcher (Backward)",
|
||||
labelKey: "settings:shortcutRecentTaskSwitcherBackward",
|
||||
default: SHORTCUTS.TASK_SWITCHER_REVERSE,
|
||||
},
|
||||
REVERSE_SEARCH: { label: "Reverse Chat Search", default: SHORTCUTS.REVERSE_SEARCH },
|
||||
OPEN_TASK_PR: { label: "Open Task Pull Request", default: SHORTCUTS.OPEN_TASK_PR },
|
||||
WORKSPACE_PICKER: { label: "Open Workspace Picker", default: SHORTCUTS.WORKSPACE_PICKER },
|
||||
REVERSE_SEARCH: {
|
||||
labelKey: "settings:shortcutReverseChatSearch",
|
||||
default: SHORTCUTS.REVERSE_SEARCH,
|
||||
},
|
||||
OPEN_TASK_PR: {
|
||||
labelKey: "settings:shortcutOpenTaskPullRequest",
|
||||
default: SHORTCUTS.OPEN_TASK_PR,
|
||||
},
|
||||
WORKSPACE_PICKER: {
|
||||
labelKey: "settings:shortcutOpenWorkspacePicker",
|
||||
default: SHORTCUTS.WORKSPACE_PICKER,
|
||||
},
|
||||
};
|
||||
|
||||
export function getShortcut(
|
||||
|
||||
@@ -253,5 +253,34 @@
|
||||
"workItemUnavailable": "Work item unavailable.",
|
||||
"workItemWatches": "Work-item watches",
|
||||
"workItemWatchesDescription": "Create tasks automatically from matching Azure DevOps work-item queries.",
|
||||
"workItemWatchFieldsRequired": "Project, WIQL, repository, workflow, step, agent, and executor are required."
|
||||
"workItemWatchFieldsRequired": "Project, WIQL, repository, workflow, step, agent, and executor are required.",
|
||||
"prStatusAbandoned": "Abandoned",
|
||||
"prStatusActive": "Active",
|
||||
"prStatusChangesRequested": "Changes requested",
|
||||
"prStatusCompleted": "Completed",
|
||||
"prStatusDraft": "Draft",
|
||||
"prStatusPolicyFailed": "Policy failed",
|
||||
"prStatusPolicyRunning": "Policy running",
|
||||
"prStatusReady": "Ready",
|
||||
"prStatusWaitingForReview": "Waiting for review",
|
||||
"pullRequestStatusAria": "Azure PR {{id}}: {{status}}{{suffix}}",
|
||||
"defaultActionAddressFeedback": "Address feedback",
|
||||
"defaultActionAddressFeedbackHint": "Apply review comments",
|
||||
"defaultActionFixCi": "Fix CI",
|
||||
"defaultActionFixCiHint": "Diagnose failing checks",
|
||||
"defaultActionImplement": "Implement",
|
||||
"defaultActionImplementHint": "Build and open a PR",
|
||||
"defaultActionInvestigate": "Investigate",
|
||||
"defaultActionInvestigateHint": "Find the root cause",
|
||||
"defaultActionReproduce": "Reproduce",
|
||||
"defaultActionReproduceHint": "Document repro steps",
|
||||
"defaultActionReview": "Review",
|
||||
"defaultActionReviewHint": "Read the diff, flag issues",
|
||||
"defaultQueryActive": "Active",
|
||||
"defaultQueryAssignedToMe": "Assigned to me",
|
||||
"defaultQueryCompleted": "Completed",
|
||||
"defaultQueryCreatedByMe": "Created by me",
|
||||
"defaultQueryOpen": "Open",
|
||||
"defaultQueryRecentlyUpdated": "Recently updated",
|
||||
"defaultQueryReviewRequested": "Review requested"
|
||||
}
|
||||
|
||||
@@ -441,5 +441,34 @@
|
||||
"you": "You",
|
||||
"youCanUseTheNavigationOr": "You can use the navigation or reload to try again.",
|
||||
"yourAgentImplementsItInThe": "Your agent implements it in the kandev repository, with tests",
|
||||
"youVerifyAndTestTheChange": "You verify and test the change in a second kandev instance"
|
||||
"youVerifyAndTestTheChange": "You verify and test the change in a second kandev instance",
|
||||
"failedToRenderDiagram": "Failed to render diagram",
|
||||
"demoAgentMessagesDescription": "Visual inspection of agent message normalization. Each card shows the rendered output alongside raw JSON.",
|
||||
"demoAgentMessagesQa": "Agent Messages QA",
|
||||
"demoErrorLoading": "Error loading {{mode}}",
|
||||
"demoEventFile": "Event file:",
|
||||
"demoEvents": "events",
|
||||
"demoFailedToLoadFiles": "Failed to load files",
|
||||
"demoFailedToLoadFixtures": "Failed to load fixtures",
|
||||
"demoFailedToLoadMessages": "Failed to load messages",
|
||||
"demoFileOption_one": "{{protocol}} - {{agent}} ({{count}} message)",
|
||||
"demoFileOption_other": "{{protocol}} - {{agent}} ({{count}} messages)",
|
||||
"demoFixtureFile": "Fixture file:",
|
||||
"demoFixtures": "fixtures",
|
||||
"demoMessageData": "Message data",
|
||||
"demoMessageTypes": "Message Types Demo",
|
||||
"demoMessageTypesDescription": "All message types rendered for styling and testing",
|
||||
"demoNoFilesFound": "No files found",
|
||||
"demoNoFixtures": "No fixtures found. Select a fixture file from the dropdown above.",
|
||||
"demoNoNormalizedEvents": "No normalized event files found. Run an agent with KANDEV_DEBUG_AGENT_MESSAGES=1 to generate event logs.",
|
||||
"demoNormalizedEvents": "Normalized Events",
|
||||
"demoNormalizedPayload": "Normalized Payload",
|
||||
"demoRawInput": "Raw Input",
|
||||
"demoRenderedOutput": "Rendered output:",
|
||||
"demoShowingFixtures_one": "Showing {{count}} of {{total}} fixture",
|
||||
"demoShowingFixtures_other": "Showing {{count}} of {{total}} fixtures",
|
||||
"demoShowingMessages_one": "Showing {{count}} of {{total}} message",
|
||||
"demoShowingMessages_other": "Showing {{count}} of {{total}} messages",
|
||||
"demoTestFixtures": "Test Fixtures",
|
||||
"demoToolType": "Tool type:"
|
||||
}
|
||||
|
||||
@@ -85,5 +85,6 @@
|
||||
"switchToUnifiedView": "Switch to unified view",
|
||||
"toggleCode": "Toggle code",
|
||||
"toggleWordWrap": "Toggle word wrap",
|
||||
"underlineCmdU": "Underline (Cmd+U)"
|
||||
"underlineCmdU": "Underline (Cmd+U)",
|
||||
"failedToOpenEditor": "Failed to open editor"
|
||||
}
|
||||
|
||||
@@ -347,5 +347,8 @@
|
||||
"mrChipAutoMerge": "Auto-merge",
|
||||
"mrChipDrawerTitle": "Merge request !{{mriid}}",
|
||||
"mrChipDrawerDescription": "Pipeline status, review status, and merge request actions.",
|
||||
"mrChipDrawerClose": "Close merge request status"
|
||||
"mrChipDrawerClose": "Close merge request status",
|
||||
"gitlabRejectedAssociation": "GitLab rejected the association.",
|
||||
"taskCreatedMrNotLinked": "Task created, but merge request was not linked",
|
||||
"taskCreatedMrNotLinkedDescription": "Open the task and use Link GitLab merge request to retry. {{error}}"
|
||||
}
|
||||
|
||||
@@ -91,5 +91,6 @@
|
||||
"workflow": "Workflow",
|
||||
"workflowStepFallback": "workflow step {{stepId}}",
|
||||
"columns": "Columns",
|
||||
"columnsForWorkflow": "Columns for {{name}}"
|
||||
"columnsForWorkflow": "Columns for {{name}}",
|
||||
"needsReassignment": "Needs Reassignment"
|
||||
}
|
||||
|
||||
@@ -78,5 +78,15 @@
|
||||
"walkMeThroughTheseReviewChanges": "Walk me through these review changes",
|
||||
"walkthrough": "Walkthrough",
|
||||
"walkthroughDiscarded": "Walkthrough discarded",
|
||||
"wrapLongLines": "Wrap long lines"
|
||||
"wrapLongLines": "Wrap long lines",
|
||||
"changesDiscarded": "Changes discarded",
|
||||
"changesStillLoading": "Changes are still loading",
|
||||
"couldNotSendFinding": "Could not send the finding",
|
||||
"couldNotUpdateFinding": "Could not update finding",
|
||||
"discardFailed": "Discard failed",
|
||||
"failedToRequestWalkthrough": "Failed to request walkthrough",
|
||||
"findingQueuedForAgent": "Finding queued for the agent",
|
||||
"findingSentToAgent": "Finding sent to the agent",
|
||||
"walkthroughRequestQueued": "Walkthrough request queued",
|
||||
"walkthroughRequestSent": "Walkthrough request sent"
|
||||
}
|
||||
|
||||
@@ -692,5 +692,21 @@
|
||||
"visualEditorUnavailable": "Visual editor unavailable",
|
||||
"vsCodeRemoteSsh": "VS Code Remote SSH",
|
||||
"whenEnabledInstallYourProjectS": "When enabled, install your project's dependencies (e.g. <2>{{command}}</2> via repository setup scripts) to avoid missing type errors.",
|
||||
"externalMcpToolListTaskSessions": "List every agent session on a task, newest first, so another tool can address one by ID."
|
||||
"externalMcpToolListTaskSessions": "List every agent session on a task, newest first, so another tool can address one by ID.",
|
||||
"shortcutBottomTerminal": "Toggle Bottom Terminal",
|
||||
"shortcutCommandPanel": "Command Panel",
|
||||
"shortcutCommandPanelAlt": "Command Panel (Alt)",
|
||||
"shortcutFileSearch": "File Search",
|
||||
"shortcutFocusChatInput": "Focus Chat Input",
|
||||
"shortcutFocusCliChatInput": "Focus CLI Chat Input",
|
||||
"shortcutNewTask": "New Task",
|
||||
"shortcutOpenTaskPullRequest": "Open Task Pull Request",
|
||||
"shortcutOpenWorkspacePicker": "Open Workspace Picker",
|
||||
"shortcutQuickChat": "Quick Chat",
|
||||
"shortcutRecentTaskSwitcher": "Recent Task Switcher",
|
||||
"shortcutRecentTaskSwitcherBackward": "Recent Task Switcher (Backward)",
|
||||
"shortcutReverseChatSearch": "Reverse Chat Search",
|
||||
"shortcutTaskContentSearch": "Search Task Contents",
|
||||
"shortcutTogglePlanMode": "Toggle Plan Mode",
|
||||
"shortcutToggleSidebar": "Toggle Sidebar"
|
||||
}
|
||||
|
||||
@@ -106,5 +106,7 @@
|
||||
"viewLabel": "View: {{name}}",
|
||||
"whatsNew": "What's new",
|
||||
"work": "Work",
|
||||
"workspaceFallback": "Workspace"
|
||||
"workspaceFallback": "Workspace",
|
||||
"taskPreferences": "Sidebar task preferences",
|
||||
"views": "Sidebar views"
|
||||
}
|
||||
|
||||
@@ -1516,5 +1516,51 @@
|
||||
"autopilotDescription": "The agent works independently and asks the parent task only when a critical decision blocks progress. It then waits for the answer before continuing.",
|
||||
"autopilotInfoLabel": "More information about autopilot",
|
||||
"autopilotSidebarDescription": "Autopilot task",
|
||||
"autopilotChatDescription": "Autopilot is enabled. The agent asks its parent only for critical decisions."
|
||||
"autopilotChatDescription": "Autopilot is enabled. The agent asks its parent only for critical decisions.",
|
||||
"duplicateRepository": "Duplicate repository",
|
||||
"duplicateRepositoryDescription": "{{repository}} is added more than once. Remove the duplicate row.",
|
||||
"repositoryStillBeingVerified": "Repository is still being verified",
|
||||
"repositoryProviderVerificationFailed": "Kandev could not verify this provider URL. Use Retry on the repository row.",
|
||||
"repositoryProviderVerificationPending": "Wait for provider inspection to finish before creating the task.",
|
||||
"failedToCreateFile": "Failed to create file",
|
||||
"failedToCreateSession": "Failed to create session",
|
||||
"failedToCreateTask": "Failed to create task",
|
||||
"failedToDeleteItem": "Failed to delete item",
|
||||
"failedToDeletePlan": "Failed to delete plan",
|
||||
"failedToDownloadFile": "Failed to download file",
|
||||
"failedToFetchPlan": "Failed to fetch plan",
|
||||
"failedToLoadPlanRevisions": "Failed to load revisions",
|
||||
"failedToRenameItem": "Failed to rename item",
|
||||
"failedToRevertPlan": "Failed to revert plan",
|
||||
"failedToSavePlan": "Failed to save plan",
|
||||
"failedToStartTaskPlanMode": "Failed to start task in plan mode",
|
||||
"failedToUpdateTask": "Failed to update task",
|
||||
"noPullOrMergeRequestLinked": "No pull request or merge request linked to this task",
|
||||
"sessionEndedCreateNew": "Session has ended. Please create a new session to continue.",
|
||||
"sessionUnavailableForInput": "Session is not available for input",
|
||||
"taskQueued": "Task queued",
|
||||
"taskQueuedWipLimit": "The workflow step is at its WIP limit; this task will start when capacity opens.",
|
||||
"failedToCheckRepositoryStatus": "Failed to check local repository status",
|
||||
"failedToDiscoverRepositories": "Failed to discover repositories",
|
||||
"failedToLoadCommitDiff": "Failed to load commit diff",
|
||||
"failedToNestTask": "Failed to nest task",
|
||||
"failedToProceedToNextStep": "Failed to proceed to next step",
|
||||
"failedToStartImplementingPlan": "Failed to start implementing the plan",
|
||||
"failedToStartImplementationSession": "Failed to start implementation session",
|
||||
"failedToMoveTask": "Failed to move task",
|
||||
"movedTasksStepDescription_one": "The task is now in the selected step.",
|
||||
"movedTasksStepDescription_other": "The tasks are now in the selected step.",
|
||||
"movedTasksToStep_one": "Moved task to step",
|
||||
"movedTasksToStep_other": "Moved {{count}} tasks to step",
|
||||
"movedTasksToWorkflow_one": "Moved task to workflow",
|
||||
"movedTasksToWorkflow_other": "Moved {{count}} tasks to workflow",
|
||||
"movedTasksWorkflowDescription_one": "Switch to the destination workflow to see it.",
|
||||
"movedTasksWorkflowDescription_other": "Switch to the destination workflow to see them.",
|
||||
"sessionActionDelete": "Deleting session",
|
||||
"sessionActionFailed": "{{action}} failed",
|
||||
"sessionActionProgress": "{{action}}...",
|
||||
"sessionActionResume": "Resuming session",
|
||||
"sessionActionSetPrimary": "Set primary",
|
||||
"sessionActionStop": "Stopping session",
|
||||
"sessionActionSuccessful": "{{action}} successful"
|
||||
}
|
||||
|
||||
@@ -253,5 +253,34 @@
|
||||
"workItemUnavailable": "Ŵōŕķ ĩţēḿ ũńàvàĩĺàƀĺē.",
|
||||
"workItemWatches": "Ŵōŕķ-ĩţēḿ ŵàţćĥēś",
|
||||
"workItemWatchesDescription": "Ćŕēàţē ţàśķś àũţōḿàţĩćàĺĺŷ ƒŕōḿ ḿàţćĥĩńĝ Àźũŕē ĎēvŌƥś ŵōŕķ-ĩţēḿ qũēŕĩēś.",
|
||||
"workItemWatchFieldsRequired": "Ƥŕōĵēćţ, ŴĨQĹ, ŕēƥōśĩţōŕŷ, ŵōŕķƒĺōŵ, śţēƥ, àĝēńţ, àńď ēxēćũţōŕ àŕē ŕēqũĩŕēď."
|
||||
"workItemWatchFieldsRequired": "Ƥŕōĵēćţ, ŴĨQĹ, ŕēƥōśĩţōŕŷ, ŵōŕķƒĺōŵ, śţēƥ, àĝēńţ, àńď ēxēćũţōŕ àŕē ŕēqũĩŕēď.",
|
||||
"prStatusAbandoned": "Àƀàńďōńēď",
|
||||
"prStatusActive": "Àćţĩvē",
|
||||
"prStatusChangesRequested": "Ćĥàńĝēś ŕēqũēśţēď",
|
||||
"prStatusCompleted": "Ćōḿƥĺēţēď",
|
||||
"prStatusDraft": "Ďŕàƒţ",
|
||||
"prStatusPolicyFailed": "Ƥōĺĩćŷ ƒàĩĺēď",
|
||||
"prStatusPolicyRunning": "Ƥōĺĩćŷ ŕũńńĩńĝ",
|
||||
"prStatusReady": "Ŕēàďŷ",
|
||||
"prStatusWaitingForReview": "Ŵàĩţĩńĝ ƒōŕ ŕēvĩēŵ",
|
||||
"pullRequestStatusAria": "Àźũŕē ƤŔ {{id}}: {{status}}{{suffix}}",
|
||||
"defaultActionAddressFeedback": "Àďďŕēśś ƒēēďƀàćķ",
|
||||
"defaultActionAddressFeedbackHint": "Àƥƥĺŷ ŕēvĩēŵ ćōḿḿēńţś",
|
||||
"defaultActionFixCi": "Ƒĩx ĆĨ",
|
||||
"defaultActionFixCiHint": "Ďĩàĝńōśē ƒàĩĺĩńĝ ćĥēćķś",
|
||||
"defaultActionImplement": "Ĩḿƥĺēḿēńţ",
|
||||
"defaultActionImplementHint": "Ɓũĩĺď àńď ōƥēń à ƤŔ",
|
||||
"defaultActionInvestigate": "Ĩńvēśţĩĝàţē",
|
||||
"defaultActionInvestigateHint": "Ƒĩńď ţĥē ŕōōţ ćàũśē",
|
||||
"defaultActionReproduce": "Ŕēƥŕōďũćē",
|
||||
"defaultActionReproduceHint": "Ďōćũḿēńţ ŕēƥŕō śţēƥś",
|
||||
"defaultActionReview": "Ŕēvĩēŵ",
|
||||
"defaultActionReviewHint": "Ŕēàď ţĥē ďĩƒƒ, ƒĺàĝ ĩśśũēś",
|
||||
"defaultQueryActive": "Àćţĩvē",
|
||||
"defaultQueryAssignedToMe": "Àśśĩĝńēď ţō ḿē",
|
||||
"defaultQueryCompleted": "Ćōḿƥĺēţēď",
|
||||
"defaultQueryCreatedByMe": "Ćŕēàţēď ƀŷ ḿē",
|
||||
"defaultQueryOpen": "Ōƥēń",
|
||||
"defaultQueryRecentlyUpdated": "Ŕēćēńţĺŷ ũƥďàţēď",
|
||||
"defaultQueryReviewRequested": "Ŕēvĩēŵ ŕēqũēśţēď"
|
||||
}
|
||||
|
||||
@@ -441,5 +441,33 @@
|
||||
"you": "Ŷōũ",
|
||||
"youCanUseTheNavigationOr": "Ŷōũ ćàń ũśē ţĥē ńàvĩĝàţĩōń ōŕ ŕēĺōàď ţō ţŕŷ àĝàĩń.",
|
||||
"yourAgentImplementsItInThe": "Ŷōũŕ àĝēńţ ĩḿƥĺēḿēńţś ĩţ ĩń ţĥē ķàńďēv ŕēƥōśĩţōŕŷ, ŵĩţĥ ţēśţś",
|
||||
"youVerifyAndTestTheChange": "Ŷōũ vēŕĩƒŷ àńď ţēśţ ţĥē ćĥàńĝē ĩń à śēćōńď ķàńďēv ĩńśţàńćē"
|
||||
"youVerifyAndTestTheChange": "Ŷōũ vēŕĩƒŷ àńď ţēśţ ţĥē ćĥàńĝē ĩń à śēćōńď ķàńďēv ĩńśţàńćē",
|
||||
"demoAgentMessagesDescription": "Vĩśũàĺ ĩńśƥēćţĩōń ōƒ àĝēńţ ḿēśśàĝē ńōŕḿàĺĩźàţĩōń. Ēàćĥ ćàŕď śĥōŵś ţĥē ŕēńďēŕēď ōũţƥũţ àĺōńĝśĩďē ŕàŵ ĴŚŌŃ.",
|
||||
"demoAgentMessagesQa": "Àĝēńţ Ḿēśśàĝēś QÀ",
|
||||
"demoErrorLoading": "Ēŕŕōŕ ĺōàďĩńĝ {{mode}}",
|
||||
"demoEventFile": "Ēvēńţ ƒĩĺē:",
|
||||
"demoEvents": "ēvēńţś",
|
||||
"demoFailedToLoadFiles": "Ƒàĩĺēď ţō ĺōàď ƒĩĺēś",
|
||||
"demoFailedToLoadFixtures": "Ƒàĩĺēď ţō ĺōàď ƒĩxţũŕēś",
|
||||
"demoFailedToLoadMessages": "Ƒàĩĺēď ţō ĺōàď ḿēśśàĝēś",
|
||||
"demoFileOption_one": "{{protocol}} - {{agent}} ({{count}} ḿēśśàĝē)",
|
||||
"demoFileOption_other": "{{protocol}} - {{agent}} ({{count}} ḿēśśàĝēś)",
|
||||
"demoFixtureFile": "Ƒĩxţũŕē ƒĩĺē:",
|
||||
"demoFixtures": "ƒĩxţũŕēś",
|
||||
"demoMessageData": "Ḿēśśàĝē ďàţà",
|
||||
"demoMessageTypes": "Ḿēśśàĝē Ţŷƥēś Ďēḿō",
|
||||
"demoMessageTypesDescription": "Àĺĺ ḿēśśàĝē ţŷƥēś ŕēńďēŕēď ƒōŕ śţŷĺĩńĝ àńď ţēśţĩńĝ",
|
||||
"demoNoFilesFound": "Ńō ƒĩĺēś ƒōũńď",
|
||||
"demoNoFixtures": "Ńō ƒĩxţũŕēś ƒōũńď. Śēĺēćţ à ƒĩxţũŕē ƒĩĺē ƒŕōḿ ţĥē ďŕōƥďōŵń àƀōvē.",
|
||||
"demoNoNormalizedEvents": "Ńō ńōŕḿàĺĩźēď ēvēńţ ƒĩĺēś ƒōũńď. Ŕũń àń àĝēńţ ŵĩţĥ ĶÀŃĎĒV_ĎĒƁŨĜ_ÀĜĒŃŢ_ḾĒŚŚÀĜĒŚ=1 ţō ĝēńēŕàţē ēvēńţ ĺōĝś.",
|
||||
"demoNormalizedEvents": "Ńōŕḿàĺĩźēď Ēvēńţś",
|
||||
"demoNormalizedPayload": "Ńōŕḿàĺĩźēď Ƥàŷĺōàď",
|
||||
"demoRawInput": "Ŕàŵ Ĩńƥũţ",
|
||||
"demoRenderedOutput": "Ŕēńďēŕēď ōũţƥũţ:",
|
||||
"demoShowingFixtures_one": "Śĥōŵĩńĝ {{count}} ōƒ {{total}} ƒĩxţũŕē",
|
||||
"demoShowingFixtures_other": "Śĥōŵĩńĝ {{count}} ōƒ {{total}} ƒĩxţũŕēś",
|
||||
"demoShowingMessages_one": "Śĥōŵĩńĝ {{count}} ōƒ {{total}} ḿēśśàĝē",
|
||||
"demoShowingMessages_other": "Śĥōŵĩńĝ {{count}} ōƒ {{total}} ḿēśśàĝēś",
|
||||
"demoTestFixtures": "Ţēśţ Ƒĩxţũŕēś",
|
||||
"demoToolType": "Ţōōĺ ţŷƥē:"
|
||||
}
|
||||
|
||||
@@ -85,5 +85,6 @@
|
||||
"switchToUnifiedView": "Śŵĩţćĥ ţō ũńĩƒĩēď vĩēŵ",
|
||||
"toggleCode": "Ţōĝĝĺē ćōďē",
|
||||
"toggleWordWrap": "Ţōĝĝĺē ŵōŕď ŵŕàƥ",
|
||||
"underlineCmdU": "Ũńďēŕĺĩńē (Ćḿď+Ũ)"
|
||||
"underlineCmdU": "Ũńďēŕĺĩńē (Ćḿď+Ũ)",
|
||||
"failedToOpenEditor": "Ƒàĩĺēď ţō ōƥēń ēďĩţōŕ"
|
||||
}
|
||||
|
||||
@@ -491,7 +491,7 @@
|
||||
"selectWorkspace": "Śēĺēćţ ŵōŕķśƥàćē",
|
||||
"selectedRepositories": "Śēĺēćţēď ŕēƥōśĩţōŕĩēś",
|
||||
"selectWorkspaceBeforeRequestingReview": "Śēĺēćţ à ŵōŕķśƥàćē ƀēƒōŕē ŕēqũēśţĩńĝ ŕēvĩēŵ.",
|
||||
"setTheseExactUrlsCreateAClientSecret": "Śēţ ţĥēśē ēxàćţ ŨŔĹś, ćŕēàţē à ćĺĩēńţ śēćŕēţ àńď ŵēƀĥōōķ śēćŕēţ, àńď ďōŵńĺōàď à ƥŕĩvàţē ķēŷ. Ţĥĩś ōńē-ţĩḿē śēţũƥ ēxƥĩŕēś {{expiresAt}}.",
|
||||
"setTheseExactUrlsCreateAClientSecret": "Àďď ţĥē ţŵō ũśēŕ àũţĥōŕĩźàţĩōń ćàĺĺƀàćķ ŨŔĹś ĩń ţĥē ōŕďēŕ śĥōŵń, śēţ ţĥē ŕēḿàĩńĩńĝ ēxàćţ vàĺũēś, ćŕēàţē à ćĺĩēńţ śēćŕēţ àńď ŵēƀĥōōķ śēćŕēţ, àńď ďōŵńĺōàď à ƥŕĩvàţē ķēŷ. Ţĥĩś ōńē-ţĩḿē śēţũƥ ēxƥĩŕēś {{expiresAt}}.",
|
||||
"showGithubApiLimits": "Śĥōŵ ĜĩţĤũƀ ÀƤĨ ĺĩḿĩţś",
|
||||
"showLess": "Śĥōŵ ĺēśś",
|
||||
"showMore": "Śĥōŵ ḿōŕē",
|
||||
|
||||
@@ -347,5 +347,8 @@
|
||||
"mrChipAutoMerge": "Àũţō-ḿēŕĝē",
|
||||
"mrChipDrawerTitle": "Ḿēŕĝē ŕēqũēśţ !{{mriid}}",
|
||||
"mrChipDrawerDescription": "Ƥĩƥēĺĩńē śţàţũś, ŕēvĩēŵ śţàţũś, àńď ḿēŕĝē ŕēqũēśţ àćţĩōńś.",
|
||||
"mrChipDrawerClose": "Ćĺōśē ḿēŕĝē ŕēqũēśţ śţàţũś"
|
||||
"mrChipDrawerClose": "Ćĺōśē ḿēŕĝē ŕēqũēśţ śţàţũś",
|
||||
"gitlabRejectedAssociation": "ĜĩţĹàƀ ŕēĵēćţēď ţĥē àśśōćĩàţĩōń.",
|
||||
"taskCreatedMrNotLinked": "Ţàśķ ćŕēàţēď, ƀũţ ḿēŕĝē ŕēqũēśţ ŵàś ńōţ ĺĩńķēď",
|
||||
"taskCreatedMrNotLinkedDescription": "Ōƥēń ţĥē ţàśķ àńď ũśē Ĺĩńķ ĜĩţĹàƀ ḿēŕĝē ŕēqũēśţ ţō ŕēţŕŷ. {{error}}"
|
||||
}
|
||||
|
||||
@@ -53,9 +53,9 @@
|
||||
"copyingConfigTo": "Ćōƥŷĩńĝ {{label}} ćōńƒĩĝ ţō {{targetName}}…",
|
||||
"createAsDraft": "Ćŕēàţē àś ďŕàƒţ",
|
||||
"createdSuccessfully": "{{longName}} ćŕēàţēď śũććēśśƒũĺĺŷ",
|
||||
"createdLinkNeedsAttention": "{{shortName}} ćŕēàţēď; ţàśķ ĺĩñķ ñēēďś àţţēñţĩőñ",
|
||||
"associationRetryViaTaskMenu": "{{message}}. Ũśē Ĺĩñķ ĩñ ţĥē ţàśķ mēñũ ţő ŕēţŕŷ.",
|
||||
"taskAssociationCouldNotBeSaved": "Ţàśķ àśśőćĩàţĩőñ ćőũĺď ñőţ bē śàvēď.",
|
||||
"createdLinkNeedsAttention": "{{shortName}} ćŕēàţēď; ţàśķ ĺĩńķ ńēēďś àţţēńţĩōń",
|
||||
"associationRetryViaTaskMenu": "{{message}}. Ũśē Ĺĩńķ ĩń ţĥē ţàśķ ḿēńũ ţō ŕēţŕŷ.",
|
||||
"taskAssociationCouldNotBeSaved": "Ţàśķ àśśōćĩàţĩōń ćōũĺď ńōţ ƀē śàvēď.",
|
||||
"createFailed": "Ćŕēàţē {{shortName}} ƒàĩĺēď",
|
||||
"createLongName": "Ćŕēàţē {{longName}}",
|
||||
"createPr": "Ćŕēàţē ƤŔ",
|
||||
@@ -187,6 +187,6 @@
|
||||
"queryNamePlaceholder": "ē.ĝ. Ńēēďś ḿŷ ŕēvĩēŵ",
|
||||
"saveQuery": "Śàvē qũēŕŷ",
|
||||
"waitingForTheNextBackendHealth": "Ŵàĩţĩńĝ ƒōŕ ţĥē ńēxţ ƀàćķēńď ĥēàĺţĥ ćĥēćķ…",
|
||||
"cursorPaginationSummary_one": "Ƥàĝē {{page}} · {{count}} ŕēśũļţ",
|
||||
"cursorPaginationSummary_other": "Ƥàĝē {{page}} · {{count}} ŕēśũļţś"
|
||||
"cursorPaginationSummary_one": "Ƥàĝē {{page}} · {{count}} ŕēśũĺţ",
|
||||
"cursorPaginationSummary_other": "Ƥàĝē {{page}} · {{count}} ŕēśũĺţś"
|
||||
}
|
||||
|
||||
@@ -91,5 +91,6 @@
|
||||
"workflow": "Ŵōŕķƒĺōŵ",
|
||||
"workflowStepFallback": "ŵōŕķƒĺōŵ śţēƥ {{stepId}}",
|
||||
"columns": "Ćōĺũḿńś",
|
||||
"columnsForWorkflow": "Ćōĺũḿńś ƒōŕ {{name}}"
|
||||
"columnsForWorkflow": "Ćōĺũḿńś ƒōŕ {{name}}",
|
||||
"needsReassignment": "Ńēēďś Ŕēàśśĩĝńḿēńţ"
|
||||
}
|
||||
|
||||
@@ -78,5 +78,15 @@
|
||||
"walkMeThroughTheseReviewChanges": "Ŵàĺķ ḿē ţĥŕōũĝĥ ţĥēśē ŕēvĩēŵ ćĥàńĝēś",
|
||||
"walkthrough": "Ŵàĺķţĥŕōũĝĥ",
|
||||
"walkthroughDiscarded": "Ŵàĺķţĥŕōũĝĥ ďĩśćàŕďēď",
|
||||
"wrapLongLines": "Ŵŕàƥ ĺōńĝ ĺĩńēś"
|
||||
"wrapLongLines": "Ŵŕàƥ ĺōńĝ ĺĩńēś",
|
||||
"changesDiscarded": "Ćĥàńĝēś ďĩśćàŕďēď",
|
||||
"changesStillLoading": "Ćĥàńĝēś àŕē śţĩĺĺ ĺōàďĩńĝ",
|
||||
"couldNotSendFinding": "Ćōũĺď ńōţ śēńď ţĥē ƒĩńďĩńĝ",
|
||||
"couldNotUpdateFinding": "Ćōũĺď ńōţ ũƥďàţē ƒĩńďĩńĝ",
|
||||
"discardFailed": "Ďĩśćàŕď ƒàĩĺēď",
|
||||
"failedToRequestWalkthrough": "Ƒàĩĺēď ţō ŕēqũēśţ ŵàĺķţĥŕōũĝĥ",
|
||||
"findingQueuedForAgent": "Ƒĩńďĩńĝ qũēũēď ƒōŕ ţĥē àĝēńţ",
|
||||
"findingSentToAgent": "Ƒĩńďĩńĝ śēńţ ţō ţĥē àĝēńţ",
|
||||
"walkthroughRequestQueued": "Ŵàĺķţĥŕōũĝĥ ŕēqũēśţ qũēũēď",
|
||||
"walkthroughRequestSent": "Ŵàĺķţĥŕōũĝĥ ŕēqũēśţ śēńţ"
|
||||
}
|
||||
|
||||
@@ -692,5 +692,21 @@
|
||||
"visualEditorUnavailable": "Vĩśũàĺ ēďĩţōŕ ũńàvàĩĺàƀĺē",
|
||||
"vsCodeRemoteSsh": "VŚ Ćōďē Ŕēḿōţē ŚŚĤ",
|
||||
"whenEnabledInstallYourProjectS": "Ŵĥēń ēńàƀĺēď, ĩńśţàĺĺ ŷōũŕ ƥŕōĵēćţ'ś ďēƥēńďēńćĩēś (ē.ĝ. <2>{{command}}</2> vĩà ŕēƥōśĩţōŕŷ śēţũƥ śćŕĩƥţś) ţō àvōĩď ḿĩśśĩńĝ ţŷƥē ēŕŕōŕś.",
|
||||
"externalMcpToolListTaskSessions": "Ĺĩśţ ēvēŕŷ àĝēńţ śēśśĩōń ōń à ţàśķ, ńēŵēśţ ƒĩŕśţ, śō àńōţĥēŕ ţōōĺ ćàń àďďŕēśś ōńē ƀŷ ĨĎ."
|
||||
"externalMcpToolListTaskSessions": "Ĺĩśţ ēvēŕŷ àĝēńţ śēśśĩōń ōń à ţàśķ, ńēŵēśţ ƒĩŕśţ, śō àńōţĥēŕ ţōōĺ ćàń àďďŕēśś ōńē ƀŷ ĨĎ.",
|
||||
"shortcutBottomTerminal": "Ţōĝĝĺē Ɓōţţōḿ Ţēŕḿĩńàĺ",
|
||||
"shortcutCommandPanel": "Ćōḿḿàńď Ƥàńēĺ",
|
||||
"shortcutCommandPanelAlt": "Ćōḿḿàńď Ƥàńēĺ (Àĺţ)",
|
||||
"shortcutFileSearch": "Ƒĩĺē Śēàŕćĥ",
|
||||
"shortcutFocusChatInput": "Ƒōćũś Ćĥàţ Ĩńƥũţ",
|
||||
"shortcutFocusCliChatInput": "Ƒōćũś ĆĹĨ Ćĥàţ Ĩńƥũţ",
|
||||
"shortcutNewTask": "Ńēŵ Ţàśķ",
|
||||
"shortcutOpenTaskPullRequest": "Ōƥēń Ţàśķ Ƥũĺĺ Ŕēqũēśţ",
|
||||
"shortcutOpenWorkspacePicker": "Ōƥēń Ŵōŕķśƥàćē Ƥĩćķēŕ",
|
||||
"shortcutQuickChat": "Qũĩćķ Ćĥàţ",
|
||||
"shortcutRecentTaskSwitcher": "Ŕēćēńţ Ţàśķ Śŵĩţćĥēŕ",
|
||||
"shortcutRecentTaskSwitcherBackward": "Ŕēćēńţ Ţàśķ Śŵĩţćĥēŕ (Ɓàćķŵàŕď)",
|
||||
"shortcutReverseChatSearch": "Ŕēvēŕśē Ćĥàţ Śēàŕćĥ",
|
||||
"shortcutTaskContentSearch": "Śēàŕćĥ Ţàśķ Ćōńţēńţś",
|
||||
"shortcutTogglePlanMode": "Ţōĝĝĺē Ƥĺàń Ḿōďē",
|
||||
"shortcutToggleSidebar": "Ţōĝĝĺē Śĩďēƀàŕ"
|
||||
}
|
||||
|
||||
@@ -106,5 +106,7 @@
|
||||
"viewLabel": "Vĩēŵ: {{name}}",
|
||||
"whatsNew": "Ŵĥàţ'ś ńēŵ",
|
||||
"work": "Ŵōŕķ",
|
||||
"workspaceFallback": "Ŵōŕķśƥàćē"
|
||||
"workspaceFallback": "Ŵōŕķśƥàćē",
|
||||
"taskPreferences": "Śĩďēƀàŕ ţàśķ ƥŕēƒēŕēńćēś",
|
||||
"views": "Śĩďēƀàŕ vĩēŵś"
|
||||
}
|
||||
|
||||
@@ -934,8 +934,8 @@
|
||||
"prompt": "Ƥŕōḿƥţ",
|
||||
"promptFieldLabel": "ƥŕōḿƥţ",
|
||||
"providerPrivateContextThatKandevDid": "Ƥŕōvĩďēŕ-ƥŕĩvàţē ćōńţēxţ ţĥàţ Ķàńďēv ďĩď ńōţ ŕēćōŕď ḿàŷ ńōţ ćàŕŕŷ ōvēŕ. Ōƥēń ţēŕḿĩńàĺś, ďēv śēŕvēŕś, àńď ōţĥēŕ ŵōŕķśƥàćē ƥŕōćēśśēś śţōƥ, ĩńćĺũďĩńĝ ţĥē ţàśķ ēďĩţōŕ śēŕvēŕ.",
|
||||
"providerHistoryUnavailable": "Ćũŕŕēńţ ƤŔ ĥĩśţōŕŷ ĩś ũńàṽàĩĺàƀĺē. Ťĥē ćĥēćķōũţ ĥĩśţōŕŷ ĩś śĥōŵń ŵĩţĥōũţ àśśũḿĩńĝ à ŕēŵŕĩţē.",
|
||||
"providerUnavailable": "Ƥŕōvĩďēŕ ũńàvàĩĺàƀĺē",
|
||||
"providerHistoryUnavailable": "Ćũŕŕēńţ ƤŔ ĥĩśţōŕŷ ĩś ũńàvàĩĺàƀĺē. Ţĥē ćĥēćķōũţ ĥĩśţōŕŷ ĩś śĥōŵń ŵĩţĥōũţ àśśũḿĩńĝ à ŕēŵŕĩţē.",
|
||||
"proxy": "Ƥŕōxŷ",
|
||||
"publishARedactedSnapshotOfThis": "Ƥũƀĺĩśĥ à ŕēďàćţēď śńàƥśĥōţ ōƒ ţĥĩś ćōḿƥĺēţēď ţàśķ àś à śēćŕēţ ĜĩţĤũƀ Ĝĩśţ ōń ŷōũŕ àććōũńţ.",
|
||||
"publishToGithubGist": "Ƥũƀĺĩśĥ ţō ĜĩţĤũƀ Ĝĩśţ",
|
||||
@@ -992,9 +992,9 @@
|
||||
"replacePRBranch": "Ŕēƥĺàćē ƤŔ ƀŕàńćĥ",
|
||||
"usePRVersion": "Ũśē ƤŔ vēŕśĩōń",
|
||||
"prNumberVersion": "ƤŔ #{{number}} vēŕśĩōń",
|
||||
"remoteContributionReplaceDescription": "Ŕēƥĺàćē ţĥē ƥũƀĺĩśĥēď ƤŔ ƀŕàńćĥ ŵĩţĥ ŷōũŕ ũńćĥàńĝēď ţàśķ vēŕśĩōń. Ŕēƥũĩŕēś ćōńƒĩŕḿàţĩōń àńď àń ēxàćţ ƥŕōvĩďēŕ-ĥēàď ḿàţćĥ.",
|
||||
"remoteContributionUseDescription": "Ũśē ţĥē ćũŕŕēńţ ƤŔ vēŕśĩōń ĩń ţĥĩś ţàśķ. Ķàńďēv ćŕēàţēś à ŕēćōvēŕŷ ƀŕàńćĥ ƒĩŕśţ àńď ŕēƥũĩŕēś à ćĺēàń ŵōŕķĩńĝ ţŕēē.",
|
||||
"remoteContributionViewDescription": "Őƥēń ƤŔ #{{number}} vēŕśĩōń ŵĩţĥōũţ ćĥàńĝĩńĝ ţĥē ţàśķ ćĥēćķōũţ.",
|
||||
"remoteContributionReplaceDescription": "Ŕēƥĺàćē ţĥē ƥũƀĺĩśĥēď ƤŔ ƀŕàńćĥ ŵĩţĥ ŷōũŕ ũńćĥàńĝēď ţàśķ vēŕśĩōń. Ŕēqũĩŕēś ćōńƒĩŕḿàţĩōń àńď àń ēxàćţ ƥŕōvĩďēŕ-ĥēàď ḿàţćĥ.",
|
||||
"remoteContributionUseDescription": "Ũśē ţĥē ćũŕŕēńţ ƤŔ vēŕśĩōń ĩń ţĥĩś ţàśķ. Ķàńďēv ćŕēàţēś à ŕēćōvēŕŷ ƀŕàńćĥ ƒĩŕśţ àńď ŕēqũĩŕēś à ćĺēàń ŵōŕķĩńĝ ţŕēē.",
|
||||
"remoteContributionViewDescription": "Ōƥēń ƤŔ #{{number}} vēŕśĩōń ŵĩţĥōũţ ćĥàńĝĩńĝ ţĥē ţàśķ ćĥēćķōũţ.",
|
||||
"replacePRBranchConfirmation": "Ŕēƥĺàćē ţĥē ƥũƀĺĩśĥēď ƤŔ ƀŕàńćĥ ƒōŕ {{repository}} àţ {{head}} ŵĩţĥ ţĥē ţàśķ vēŕśĩōń? Ţĥĩś ćĥàńĝēś ţĥē ƥŕōvĩďēŕ ƀŕàńćĥ ţō ḿàţćĥ ţĥē ţàśķ vēŕśĩōń.",
|
||||
"usePRVersionConfirmation": "Ũśē ţĥē ƤŔ vēŕśĩōń àţ {{head}}? Ķàńďēv ŵĩĺĺ ćŕēàţē à ŕēćōvēŕŷ ƀŕàńćĥ ƒōŕ ţĥē ćũŕŕēńţ ţàśķ vēŕśĩōń ƒĩŕśţ. Ţĥē ŵōŕķĩńĝ ţŕēē ḿũśţ ƀē ćĺēàń.",
|
||||
"remoteContributionProviderChangedAgain": "Ţĥē ƤŔ ćĥàńĝēď àĝàĩń. Ŕēƒŕēśĥ ţĥē ƥŕōvĩďēŕ vēŕśĩōń àńď ţŕŷ àĝàĩń.",
|
||||
@@ -1516,5 +1516,50 @@
|
||||
"autopilotDescription": "Ţĥē àĝēńţ ŵōŕķś ĩńďēƥēńďēńţĺŷ àńď àśķś ţĥē ƥàŕēńţ ţàśķ ōńĺŷ ŵĥēń à ćŕĩţĩćàĺ ďēćĩśĩōń ƀĺōćķś ƥŕōĝŕēśś. Ĩţ ţĥēń ŵàĩţś ƒōŕ ţĥē àńśŵēŕ ƀēƒōŕē ćōńţĩńũĩńĝ.",
|
||||
"autopilotInfoLabel": "Ḿōŕē ĩńƒōŕḿàţĩōń àƀōũţ àũţōƥĩĺōţ",
|
||||
"autopilotSidebarDescription": "Àũţōƥĩĺōţ ţàśķ",
|
||||
"autopilotChatDescription": "Àũţōƥĩĺōţ ĩś ēńàƀĺēď. Ţĥē àĝēńţ àśķś ĩţś ƥàŕēńţ ōńĺŷ ƒōŕ ćŕĩţĩćàĺ ďēćĩśĩōńś."
|
||||
"autopilotChatDescription": "Àũţōƥĩĺōţ ĩś ēńàƀĺēď. Ţĥē àĝēńţ àśķś ĩţś ƥàŕēńţ ōńĺŷ ƒōŕ ćŕĩţĩćàĺ ďēćĩśĩōńś.",
|
||||
"duplicateRepository": "Ďũƥĺĩćàţē ŕēƥōśĩţōŕŷ",
|
||||
"duplicateRepositoryDescription": "{{repository}} ĩś àďďēď ḿōŕē ţĥàń ōńćē. Ŕēḿōvē ţĥē ďũƥĺĩćàţē ŕōŵ.",
|
||||
"repositoryStillBeingVerified": "Ŕēƥōśĩţōŕŷ ĩś śţĩĺĺ ƀēĩńĝ vēŕĩƒĩēď",
|
||||
"repositoryProviderVerificationFailed": "Ķàńďēv ćōũĺď ńōţ vēŕĩƒŷ ţĥĩś ƥŕōvĩďēŕ ŨŔĹ. Ũśē Ŕēţŕŷ ōń ţĥē ŕēƥōśĩţōŕŷ ŕōŵ.",
|
||||
"repositoryProviderVerificationPending": "Ŵàĩţ ƒōŕ ƥŕōvĩďēŕ ĩńśƥēćţĩōń ţō ƒĩńĩśĥ ƀēƒōŕē ćŕēàţĩńĝ ţĥē ţàśķ.",
|
||||
"failedToCreateFile": "Ƒàĩĺēď ţō ćŕēàţē ƒĩĺē",
|
||||
"failedToCreateSession": "Ƒàĩĺēď ţō ćŕēàţē śēśśĩōń",
|
||||
"failedToCreateTask": "Ƒàĩĺēď ţō ćŕēàţē ţàśķ",
|
||||
"failedToDeleteItem": "Ƒàĩĺēď ţō ďēĺēţē ĩţēḿ",
|
||||
"failedToDeletePlan": "Ƒàĩĺēď ţō ďēĺēţē ƥĺàń",
|
||||
"failedToDownloadFile": "Ƒàĩĺēď ţō ďōŵńĺōàď ƒĩĺē",
|
||||
"failedToFetchPlan": "Ƒàĩĺēď ţō ƒēţćĥ ƥĺàń",
|
||||
"failedToLoadPlanRevisions": "Ƒàĩĺēď ţō ĺōàď ŕēvĩśĩōńś",
|
||||
"failedToRevertPlan": "Ƒàĩĺēď ţō ŕēvēŕţ ƥĺàń",
|
||||
"failedToSavePlan": "Ƒàĩĺēď ţō śàvē ƥĺàń",
|
||||
"failedToStartTaskPlanMode": "Ƒàĩĺēď ţō śţàŕţ ţàśķ ĩń ƥĺàń ḿōďē",
|
||||
"failedToUpdateTask": "Ƒàĩĺēď ţō ũƥďàţē ţàśķ",
|
||||
"noPullOrMergeRequestLinked": "Ńō ƥũĺĺ ŕēqũēśţ ōŕ ḿēŕĝē ŕēqũēśţ ĺĩńķēď ţō ţĥĩś ţàśķ",
|
||||
"sessionEndedCreateNew": "Śēśśĩōń ĥàś ēńďēď. Ƥĺēàśē ćŕēàţē à ńēŵ śēśśĩōń ţō ćōńţĩńũē.",
|
||||
"sessionUnavailableForInput": "Śēśśĩōń ĩś ńōţ àvàĩĺàƀĺē ƒōŕ ĩńƥũţ",
|
||||
"taskQueued": "Ţàśķ qũēũēď",
|
||||
"taskQueuedWipLimit": "Ţĥē ŵōŕķƒĺōŵ śţēƥ ĩś àţ ĩţś ŴĨƤ ĺĩḿĩţ; ţĥĩś ţàśķ ŵĩĺĺ śţàŕţ ŵĥēń ćàƥàćĩţŷ ōƥēńś.",
|
||||
"failedToCheckRepositoryStatus": "Ƒàĩĺēď ţō ćĥēćķ ĺōćàĺ ŕēƥōśĩţōŕŷ śţàţũś",
|
||||
"failedToDiscoverRepositories": "Ƒàĩĺēď ţō ďĩśćōvēŕ ŕēƥōśĩţōŕĩēś",
|
||||
"failedToLoadCommitDiff": "Ƒàĩĺēď ţō ĺōàď ćōḿḿĩţ ďĩƒƒ",
|
||||
"failedToNestTask": "Ƒàĩĺēď ţō ńēśţ ţàśķ",
|
||||
"failedToProceedToNextStep": "Ƒàĩĺēď ţō ƥŕōćēēď ţō ńēxţ śţēƥ",
|
||||
"failedToStartImplementingPlan": "Ƒàĩĺēď ţō śţàŕţ ĩḿƥĺēḿēńţĩńĝ ţĥē ƥĺàń",
|
||||
"failedToStartImplementationSession": "Ƒàĩĺēď ţō śţàŕţ ĩḿƥĺēḿēńţàţĩōń śēśśĩōń",
|
||||
"failedToMoveTask": "Ƒàĩĺēď ţō ḿōvē ţàśķ",
|
||||
"movedTasksStepDescription_one": "Ţĥē ţàśķ ĩś ńōŵ ĩń ţĥē śēĺēćţēď śţēƥ.",
|
||||
"movedTasksStepDescription_other": "Ţĥē ţàśķś àŕē ńōŵ ĩń ţĥē śēĺēćţēď śţēƥ.",
|
||||
"movedTasksToStep_one": "Ḿōvēď ţàśķ ţō śţēƥ",
|
||||
"movedTasksToStep_other": "Ḿōvēď {{count}} ţàśķś ţō śţēƥ",
|
||||
"movedTasksToWorkflow_one": "Ḿōvēď ţàśķ ţō ŵōŕķƒĺōŵ",
|
||||
"movedTasksToWorkflow_other": "Ḿōvēď {{count}} ţàśķś ţō ŵōŕķƒĺōŵ",
|
||||
"movedTasksWorkflowDescription_one": "Śŵĩţćĥ ţō ţĥē ďēśţĩńàţĩōń ŵōŕķƒĺōŵ ţō śēē ĩţ.",
|
||||
"movedTasksWorkflowDescription_other": "Śŵĩţćĥ ţō ţĥē ďēśţĩńàţĩōń ŵōŕķƒĺōŵ ţō śēē ţĥēḿ.",
|
||||
"sessionActionDelete": "Ďēĺēţĩńĝ śēśśĩōń",
|
||||
"sessionActionFailed": "{{action}} ƒàĩĺēď",
|
||||
"sessionActionProgress": "{{action}}...",
|
||||
"sessionActionResume": "Ŕēśũḿĩńĝ śēśśĩōń",
|
||||
"sessionActionSetPrimary": "Śēţ ƥŕĩḿàŕŷ",
|
||||
"sessionActionStop": "Śţōƥƥĩńĝ śēśśĩōń",
|
||||
"sessionActionSuccessful": "{{action}} śũććēśśƒũĺ"
|
||||
}
|
||||
|
||||
@@ -253,5 +253,34 @@
|
||||
"workItemUnavailable": "Item de trabalho indisponível.",
|
||||
"workItemWatches": "Vigilâncias de itens de trabalho",
|
||||
"workItemWatchesDescription": "Crie tarefas automaticamente a partir das consultas de itens de trabalho correspondentes do Azure DevOps.",
|
||||
"workItemWatchFieldsRequired": "O projeto, o WIQL, o repositório, o fluxo de trabalho, o passo, o agente e o executor são obrigatórios."
|
||||
"workItemWatchFieldsRequired": "O projeto, o WIQL, o repositório, o fluxo de trabalho, o passo, o agente e o executor são obrigatórios.",
|
||||
"prStatusAbandoned": "Abandonado",
|
||||
"prStatusActive": "Ativo",
|
||||
"prStatusChangesRequested": "Alterações solicitadas",
|
||||
"prStatusCompleted": "Concluído",
|
||||
"prStatusDraft": "Rascunho",
|
||||
"prStatusPolicyFailed": "Política falhou",
|
||||
"prStatusPolicyRunning": "Política em execução",
|
||||
"prStatusReady": "Pronto",
|
||||
"prStatusWaitingForReview": "A aguardar revisão",
|
||||
"pullRequestStatusAria": "PR Azure {{id}}: {{status}}{{suffix}}",
|
||||
"defaultActionAddressFeedback": "Responder ao feedback",
|
||||
"defaultActionAddressFeedbackHint": "Aplicar comentários da revisão",
|
||||
"defaultActionFixCi": "Corrigir CI",
|
||||
"defaultActionFixCiHint": "Diagnosticar verificações com falha",
|
||||
"defaultActionImplement": "Implementar",
|
||||
"defaultActionImplementHint": "Desenvolver e abrir um PR",
|
||||
"defaultActionInvestigate": "Investigar",
|
||||
"defaultActionInvestigateHint": "Encontrar a causa raiz",
|
||||
"defaultActionReproduce": "Reproduzir",
|
||||
"defaultActionReproduceHint": "Documentar passos de reprodução",
|
||||
"defaultActionReview": "Rever",
|
||||
"defaultActionReviewHint": "Ler as diferenças e assinalar problemas",
|
||||
"defaultQueryActive": "Ativos",
|
||||
"defaultQueryAssignedToMe": "Atribuídos a mim",
|
||||
"defaultQueryCompleted": "Concluídos",
|
||||
"defaultQueryCreatedByMe": "Criados por mim",
|
||||
"defaultQueryOpen": "Abertos",
|
||||
"defaultQueryRecentlyUpdated": "Atualizados recentemente",
|
||||
"defaultQueryReviewRequested": "Revisão solicitada"
|
||||
}
|
||||
|
||||
@@ -431,5 +431,34 @@
|
||||
"you": "Você",
|
||||
"youCanUseTheNavigationOr": "Pode usar a navegação ou recarregar para tentar novamente.",
|
||||
"yourAgentImplementsItInThe": "O seu agente implementa-a no repositório do kandev, com testes",
|
||||
"youVerifyAndTestTheChange": "Verifica e testa a alteração numa segunda instância do kandev"
|
||||
"youVerifyAndTestTheChange": "Verifica e testa a alteração numa segunda instância do kandev",
|
||||
"failedToRenderDiagram": "Falha ao renderizar o diagrama",
|
||||
"demoAgentMessagesDescription": "Inspeção visual da normalização de mensagens do agente. Cada cartão mostra o resultado renderizado junto ao JSON original.",
|
||||
"demoAgentMessagesQa": "QA de mensagens do agente",
|
||||
"demoErrorLoading": "Erro ao carregar {{mode}}",
|
||||
"demoEventFile": "Ficheiro de eventos:",
|
||||
"demoEvents": "eventos",
|
||||
"demoFailedToLoadFiles": "Falha ao carregar ficheiros",
|
||||
"demoFailedToLoadFixtures": "Falha ao carregar dados de teste",
|
||||
"demoFailedToLoadMessages": "Falha ao carregar mensagens",
|
||||
"demoFileOption_one": "{{protocol}} - {{agent}} ({{count}} mensagem)",
|
||||
"demoFileOption_other": "{{protocol}} - {{agent}} ({{count}} mensagens)",
|
||||
"demoFixtureFile": "Ficheiro de teste:",
|
||||
"demoFixtures": "dados de teste",
|
||||
"demoMessageData": "Dados da mensagem",
|
||||
"demoMessageTypes": "Demonstração de tipos de mensagem",
|
||||
"demoMessageTypesDescription": "Todos os tipos de mensagem renderizados para estilo e testes",
|
||||
"demoNoFilesFound": "Nenhum ficheiro encontrado",
|
||||
"demoNoFixtures": "Nenhum dado de teste encontrado. Selecione um ficheiro na lista acima.",
|
||||
"demoNoNormalizedEvents": "Nenhum ficheiro de eventos normalizados encontrado. Execute um agente com KANDEV_DEBUG_AGENT_MESSAGES=1 para gerar registos de eventos.",
|
||||
"demoNormalizedEvents": "Eventos normalizados",
|
||||
"demoNormalizedPayload": "Conteúdo normalizado",
|
||||
"demoRawInput": "Entrada original",
|
||||
"demoRenderedOutput": "Resultado renderizado:",
|
||||
"demoShowingFixtures_one": "A mostrar {{count}} de {{total}} dado de teste",
|
||||
"demoShowingFixtures_other": "A mostrar {{count}} de {{total}} dados de teste",
|
||||
"demoShowingMessages_one": "A mostrar {{count}} de {{total}} mensagem",
|
||||
"demoShowingMessages_other": "A mostrar {{count}} de {{total}} mensagens",
|
||||
"demoTestFixtures": "Dados de teste",
|
||||
"demoToolType": "Tipo de ferramenta:"
|
||||
}
|
||||
|
||||
@@ -85,5 +85,6 @@
|
||||
"switchToUnifiedView": "Mudar para a vista unificada",
|
||||
"toggleCode": "Alternar código",
|
||||
"toggleWordWrap": "Alternar a quebra de linha",
|
||||
"underlineCmdU": "Sublinhado (Cmd+U)"
|
||||
"underlineCmdU": "Sublinhado (Cmd+U)",
|
||||
"failedToOpenEditor": "Falha ao abrir o editor"
|
||||
}
|
||||
|
||||
@@ -331,5 +331,8 @@
|
||||
"mrTooltipState": "Estado: {{state}}",
|
||||
"mrTooltipReview": "Revisão: {{state}}",
|
||||
"mrTooltipPipeline": "Pipeline: {{state}}",
|
||||
"mrTooltipReadyToMerge": "Pronto para merge"
|
||||
"mrTooltipReadyToMerge": "Pronto para merge",
|
||||
"gitlabRejectedAssociation": "O GitLab rejeitou a associação.",
|
||||
"taskCreatedMrNotLinked": "Tarefa criada, mas o merge request não foi associado",
|
||||
"taskCreatedMrNotLinkedDescription": "Abra a tarefa e use Associar merge request do GitLab para tentar novamente. {{error}}"
|
||||
}
|
||||
|
||||
@@ -89,5 +89,6 @@
|
||||
"whenEnabledClickingATaskOpens": "Quando ativado, clicar numa tarefa abre o painel de pré-visualização. Quando desativado, clicar navega diretamente para a sessão.",
|
||||
"wipTaskCount": "Tarefas em {{label}}",
|
||||
"workflow": "Fluxo de trabalho",
|
||||
"workflowStepFallback": "passo do fluxo de trabalho {{stepId}}"
|
||||
"workflowStepFallback": "passo do fluxo de trabalho {{stepId}}",
|
||||
"needsReassignment": "Precisa de reatribuição"
|
||||
}
|
||||
|
||||
@@ -78,5 +78,15 @@
|
||||
"walkMeThroughTheseReviewChanges": "Explicar passo a passo estas alterações da revisão",
|
||||
"walkthrough": "Passo a passo",
|
||||
"walkthroughDiscarded": "Passo a passo descartado",
|
||||
"wrapLongLines": "Quebrar as linhas longas"
|
||||
"wrapLongLines": "Quebrar as linhas longas",
|
||||
"changesDiscarded": "Alterações descartadas",
|
||||
"changesStillLoading": "As alterações ainda estão a carregar",
|
||||
"couldNotSendFinding": "Não foi possível enviar a constatação",
|
||||
"couldNotUpdateFinding": "Não foi possível atualizar a constatação",
|
||||
"discardFailed": "Falha ao descartar",
|
||||
"failedToRequestWalkthrough": "Falha ao solicitar o passo a passo",
|
||||
"findingQueuedForAgent": "Constatação adicionada à fila do agente",
|
||||
"findingSentToAgent": "Constatação enviada ao agente",
|
||||
"walkthroughRequestQueued": "Pedido de passo a passo em fila",
|
||||
"walkthroughRequestSent": "Pedido de passo a passo enviado"
|
||||
}
|
||||
|
||||
@@ -676,5 +676,21 @@
|
||||
"viewAvailableSettings": "Ver as definições disponíveis",
|
||||
"visualEditorUnavailable": "Editor visual indisponível",
|
||||
"vsCodeRemoteSsh": "VS Code Remote SSH",
|
||||
"whenEnabledInstallYourProjectS": "Quando está ativado, instale as dependências do seu projeto (por exemplo, <2>{{command}}</2> através dos scripts de configuração do repositório) para evitar erros de tipos por falta de dependências."
|
||||
"whenEnabledInstallYourProjectS": "Quando está ativado, instale as dependências do seu projeto (por exemplo, <2>{{command}}</2> através dos scripts de configuração do repositório) para evitar erros de tipos por falta de dependências.",
|
||||
"shortcutBottomTerminal": "Alternar terminal inferior",
|
||||
"shortcutCommandPanel": "Painel de comandos",
|
||||
"shortcutCommandPanelAlt": "Painel de comandos (alternativo)",
|
||||
"shortcutFileSearch": "Pesquisa de ficheiros",
|
||||
"shortcutFocusChatInput": "Focar entrada do chat",
|
||||
"shortcutFocusCliChatInput": "Focar entrada do chat CLI",
|
||||
"shortcutNewTask": "Nova tarefa",
|
||||
"shortcutOpenTaskPullRequest": "Abrir pull request da tarefa",
|
||||
"shortcutOpenWorkspacePicker": "Abrir seletor de áreas de trabalho",
|
||||
"shortcutQuickChat": "Chat rápido",
|
||||
"shortcutRecentTaskSwitcher": "Seletor de tarefas recentes",
|
||||
"shortcutRecentTaskSwitcherBackward": "Seletor de tarefas recentes (para trás)",
|
||||
"shortcutReverseChatSearch": "Pesquisa inversa no chat",
|
||||
"shortcutTaskContentSearch": "Pesquisar conteúdo da tarefa",
|
||||
"shortcutTogglePlanMode": "Alternar modo de plano",
|
||||
"shortcutToggleSidebar": "Alternar barra lateral"
|
||||
}
|
||||
|
||||
@@ -104,5 +104,7 @@
|
||||
"viewLabel": "Vista: {{name}}",
|
||||
"whatsNew": "Novidades",
|
||||
"work": "Trabalho",
|
||||
"workspaceFallback": "Área de trabalho"
|
||||
"workspaceFallback": "Área de trabalho",
|
||||
"taskPreferences": "Preferências de tarefas da barra lateral",
|
||||
"views": "Vistas da barra lateral"
|
||||
}
|
||||
|
||||
@@ -1516,5 +1516,51 @@
|
||||
"autopilotDescription": "O agente trabalha de forma independente e pergunta à tarefa pai apenas quando uma decisão crítica bloqueia o progresso. Depois, espera pela resposta antes de continuar.",
|
||||
"autopilotInfoLabel": "Mais informações sobre o piloto automático",
|
||||
"autopilotSidebarDescription": "Tarefa em piloto automático",
|
||||
"autopilotChatDescription": "O piloto automático está ativo. O agente pergunta ao seu pai apenas sobre decisões críticas."
|
||||
"autopilotChatDescription": "O piloto automático está ativo. O agente pergunta ao seu pai apenas sobre decisões críticas.",
|
||||
"duplicateRepository": "Repositório duplicado",
|
||||
"duplicateRepositoryDescription": "{{repository}} foi adicionado mais de uma vez. Remova a linha duplicada.",
|
||||
"repositoryStillBeingVerified": "O repositório ainda está a ser verificado",
|
||||
"repositoryProviderVerificationFailed": "O Kandev não conseguiu verificar este URL do fornecedor. Use Repetir na linha do repositório.",
|
||||
"repositoryProviderVerificationPending": "Aguarde que a inspeção do fornecedor termine antes de criar a tarefa.",
|
||||
"failedToCreateFile": "Falha ao criar o ficheiro",
|
||||
"failedToCreateSession": "Falha ao criar a sessão",
|
||||
"failedToCreateTask": "Falha ao criar a tarefa",
|
||||
"failedToDeleteItem": "Falha ao eliminar o item",
|
||||
"failedToDeletePlan": "Falha ao eliminar o plano",
|
||||
"failedToDownloadFile": "Falha ao transferir o ficheiro",
|
||||
"failedToFetchPlan": "Falha ao obter o plano",
|
||||
"failedToLoadPlanRevisions": "Falha ao carregar revisões",
|
||||
"failedToRenameItem": "Falha ao mudar o nome do item",
|
||||
"failedToRevertPlan": "Falha ao reverter o plano",
|
||||
"failedToSavePlan": "Falha ao guardar o plano",
|
||||
"failedToStartTaskPlanMode": "Falha ao iniciar a tarefa no modo de plano",
|
||||
"failedToUpdateTask": "Falha ao atualizar a tarefa",
|
||||
"noPullOrMergeRequestLinked": "Nenhum pull request ou merge request associado a esta tarefa",
|
||||
"sessionEndedCreateNew": "A sessão terminou. Crie uma nova sessão para continuar.",
|
||||
"sessionUnavailableForInput": "A sessão não está disponível para entrada",
|
||||
"taskQueued": "Tarefa em fila",
|
||||
"taskQueuedWipLimit": "A etapa do fluxo de trabalho atingiu o limite WIP; esta tarefa será iniciada quando houver capacidade.",
|
||||
"failedToCheckRepositoryStatus": "Falha ao verificar o estado do repositório local",
|
||||
"failedToDiscoverRepositories": "Falha ao descobrir repositórios",
|
||||
"failedToLoadCommitDiff": "Falha ao carregar as diferenças do commit",
|
||||
"failedToNestTask": "Falha ao aninhar a tarefa",
|
||||
"failedToProceedToNextStep": "Falha ao avançar para o passo seguinte",
|
||||
"failedToStartImplementingPlan": "Falha ao iniciar a implementação do plano",
|
||||
"failedToStartImplementationSession": "Falha ao iniciar a sessão de implementação",
|
||||
"failedToMoveTask": "Falha ao mover a tarefa",
|
||||
"movedTasksStepDescription_one": "A tarefa está agora no passo selecionado.",
|
||||
"movedTasksStepDescription_other": "As tarefas estão agora no passo selecionado.",
|
||||
"movedTasksToStep_one": "Tarefa movida para o passo",
|
||||
"movedTasksToStep_other": "{{count}} tarefas movidas para o passo",
|
||||
"movedTasksToWorkflow_one": "Tarefa movida para o fluxo de trabalho",
|
||||
"movedTasksToWorkflow_other": "{{count}} tarefas movidas para o fluxo de trabalho",
|
||||
"movedTasksWorkflowDescription_one": "Mude para o fluxo de trabalho de destino para a ver.",
|
||||
"movedTasksWorkflowDescription_other": "Mude para o fluxo de trabalho de destino para as ver.",
|
||||
"sessionActionDelete": "A eliminar sessão",
|
||||
"sessionActionFailed": "Falha ao {{action}}",
|
||||
"sessionActionProgress": "A executar: {{action}}...",
|
||||
"sessionActionResume": "A retomar sessão",
|
||||
"sessionActionSetPrimary": "Definir como principal",
|
||||
"sessionActionStop": "A parar sessão",
|
||||
"sessionActionSuccessful": "{{action}} concluído"
|
||||
}
|
||||
|
||||
@@ -253,5 +253,34 @@
|
||||
"workItemUnavailable": "工作项不可用。",
|
||||
"workItemWatches": "工作项监控",
|
||||
"workItemWatchesDescription": "根据匹配的 Azure DevOps 工作项查询自动创建任务。",
|
||||
"workItemWatchFieldsRequired": "项目、WIQL、仓库、工作流、步骤、代理和执行器均为必填项。"
|
||||
"workItemWatchFieldsRequired": "项目、WIQL、仓库、工作流、步骤、代理和执行器均为必填项。",
|
||||
"prStatusAbandoned": "已放弃",
|
||||
"prStatusActive": "活动",
|
||||
"prStatusChangesRequested": "已请求更改",
|
||||
"prStatusCompleted": "已完成",
|
||||
"prStatusDraft": "草稿",
|
||||
"prStatusPolicyFailed": "策略失败",
|
||||
"prStatusPolicyRunning": "策略运行中",
|
||||
"prStatusReady": "就绪",
|
||||
"prStatusWaitingForReview": "等待审查",
|
||||
"pullRequestStatusAria": "Azure PR {{id}}:{{status}}{{suffix}}",
|
||||
"defaultActionAddressFeedback": "处理反馈",
|
||||
"defaultActionAddressFeedbackHint": "应用审查意见",
|
||||
"defaultActionFixCi": "修复 CI",
|
||||
"defaultActionFixCiHint": "诊断失败的检查",
|
||||
"defaultActionImplement": "实施",
|
||||
"defaultActionImplementHint": "构建并打开 PR",
|
||||
"defaultActionInvestigate": "调查",
|
||||
"defaultActionInvestigateHint": "查找根本原因",
|
||||
"defaultActionReproduce": "复现",
|
||||
"defaultActionReproduceHint": "记录复现步骤",
|
||||
"defaultActionReview": "审查",
|
||||
"defaultActionReviewHint": "阅读差异并标记问题",
|
||||
"defaultQueryActive": "活动",
|
||||
"defaultQueryAssignedToMe": "分配给我",
|
||||
"defaultQueryCompleted": "已完成",
|
||||
"defaultQueryCreatedByMe": "由我创建",
|
||||
"defaultQueryOpen": "打开",
|
||||
"defaultQueryRecentlyUpdated": "最近更新",
|
||||
"defaultQueryReviewRequested": "已请求审查"
|
||||
}
|
||||
|
||||
@@ -441,5 +441,34 @@
|
||||
"you": "您",
|
||||
"youCanUseTheNavigationOr": "你可以使用导航或重新加载以重试。",
|
||||
"yourAgentImplementsItInThe": "你的代理在 kandev 仓库中实现它,并附带测试",
|
||||
"youVerifyAndTestTheChange": "你在第二个 kandev 实例中验证并测试该改动"
|
||||
"youVerifyAndTestTheChange": "你在第二个 kandev 实例中验证并测试该改动",
|
||||
"failedToRenderDiagram": "图表渲染失败",
|
||||
"demoAgentMessagesDescription": "目视检查代理消息规范化。每张卡片同时显示渲染结果和原始 JSON。",
|
||||
"demoAgentMessagesQa": "代理消息质量检查",
|
||||
"demoErrorLoading": "加载{{mode}}时出错",
|
||||
"demoEventFile": "事件文件:",
|
||||
"demoEvents": "事件",
|
||||
"demoFailedToLoadFiles": "加载文件失败",
|
||||
"demoFailedToLoadFixtures": "加载测试数据失败",
|
||||
"demoFailedToLoadMessages": "加载消息失败",
|
||||
"demoFileOption_one": "{{protocol}} - {{agent}}({{count}} 条消息)",
|
||||
"demoFileOption_other": "{{protocol}} - {{agent}}({{count}} 条消息)",
|
||||
"demoFixtureFile": "测试数据文件:",
|
||||
"demoFixtures": "测试数据",
|
||||
"demoMessageData": "消息数据",
|
||||
"demoMessageTypes": "消息类型演示",
|
||||
"demoMessageTypesDescription": "呈现所有消息类型以供样式设计和测试",
|
||||
"demoNoFilesFound": "未找到文件",
|
||||
"demoNoFixtures": "未找到测试数据。请从上方下拉列表中选择文件。",
|
||||
"demoNoNormalizedEvents": "未找到规范化事件文件。请使用 KANDEV_DEBUG_AGENT_MESSAGES=1 运行代理以生成事件日志。",
|
||||
"demoNormalizedEvents": "规范化事件",
|
||||
"demoNormalizedPayload": "规范化内容",
|
||||
"demoRawInput": "原始输入",
|
||||
"demoRenderedOutput": "渲染结果:",
|
||||
"demoShowingFixtures_one": "显示 {{count}} / {{total}} 个测试数据",
|
||||
"demoShowingFixtures_other": "显示 {{count}} / {{total}} 个测试数据",
|
||||
"demoShowingMessages_one": "显示 {{count}} / {{total}} 条消息",
|
||||
"demoShowingMessages_other": "显示 {{count}} / {{total}} 条消息",
|
||||
"demoTestFixtures": "测试数据",
|
||||
"demoToolType": "工具类型:"
|
||||
}
|
||||
|
||||
@@ -85,5 +85,6 @@
|
||||
"switchToUnifiedView": "切换到统一视图",
|
||||
"toggleCode": "切换代码",
|
||||
"toggleWordWrap": "切换自动换行",
|
||||
"underlineCmdU": "下划线(Cmd+U)"
|
||||
"underlineCmdU": "下划线(Cmd+U)",
|
||||
"failedToOpenEditor": "打开编辑器失败"
|
||||
}
|
||||
|
||||
@@ -347,5 +347,8 @@
|
||||
"mrChipAutoMerge": "自动合并",
|
||||
"mrChipDrawerTitle": "合并请求 !{{mriid}}",
|
||||
"mrChipDrawerDescription": "流水线状态、审阅状态和合并请求操作。",
|
||||
"mrChipDrawerClose": "关闭合并请求状态面板"
|
||||
"mrChipDrawerClose": "关闭合并请求状态面板",
|
||||
"gitlabRejectedAssociation": "GitLab 拒绝了关联。",
|
||||
"taskCreatedMrNotLinked": "任务已创建,但未关联合并请求",
|
||||
"taskCreatedMrNotLinkedDescription": "打开任务并使用“关联 GitLab 合并请求”重试。{{error}}"
|
||||
}
|
||||
|
||||
@@ -91,5 +91,6 @@
|
||||
"workflow": "工作流",
|
||||
"workflowStepFallback": "工作流步骤 {{stepId}}",
|
||||
"columns": "列",
|
||||
"columnsForWorkflow": "{{name}} 的列"
|
||||
"columnsForWorkflow": "{{name}} 的列",
|
||||
"needsReassignment": "需要重新分配"
|
||||
}
|
||||
|
||||
@@ -78,5 +78,15 @@
|
||||
"walkMeThroughTheseReviewChanges": "带我浏览这些审阅更改",
|
||||
"walkthrough": "导览",
|
||||
"walkthroughDiscarded": "导览已丢弃",
|
||||
"wrapLongLines": "长行自动换行"
|
||||
"wrapLongLines": "长行自动换行",
|
||||
"changesDiscarded": "更改已丢弃",
|
||||
"changesStillLoading": "更改仍在加载中",
|
||||
"couldNotSendFinding": "无法发送发现项",
|
||||
"couldNotUpdateFinding": "无法更新发现项",
|
||||
"discardFailed": "丢弃失败",
|
||||
"failedToRequestWalkthrough": "请求导览失败",
|
||||
"findingQueuedForAgent": "发现项已加入代理队列",
|
||||
"findingSentToAgent": "发现项已发送给代理",
|
||||
"walkthroughRequestQueued": "导览请求已排队",
|
||||
"walkthroughRequestSent": "导览请求已发送"
|
||||
}
|
||||
|
||||
@@ -692,5 +692,21 @@
|
||||
"visualEditorUnavailable": "可视化编辑器不可用",
|
||||
"vsCodeRemoteSsh": "VS Code Remote SSH",
|
||||
"whenEnabledInstallYourProjectS": "启用后,会通过仓库设置脚本安装项目依赖(例如运行 <2>{{command}}</2>),以避免缺少类型错误。",
|
||||
"externalMcpToolListTaskSessions": "按最近启动的顺序列出任务的所有代理会话,以便其他工具通过 ID 定位会话。"
|
||||
"externalMcpToolListTaskSessions": "按最近启动的顺序列出任务的所有代理会话,以便其他工具通过 ID 定位会话。",
|
||||
"shortcutBottomTerminal": "切换底部终端",
|
||||
"shortcutCommandPanel": "命令面板",
|
||||
"shortcutCommandPanelAlt": "命令面板(备用)",
|
||||
"shortcutFileSearch": "文件搜索",
|
||||
"shortcutFocusChatInput": "聚焦聊天输入框",
|
||||
"shortcutFocusCliChatInput": "聚焦 CLI 聊天输入框",
|
||||
"shortcutNewTask": "新建任务",
|
||||
"shortcutOpenTaskPullRequest": "打开任务拉取请求",
|
||||
"shortcutOpenWorkspacePicker": "打开工作区选择器",
|
||||
"shortcutQuickChat": "快速聊天",
|
||||
"shortcutRecentTaskSwitcher": "最近任务切换器",
|
||||
"shortcutRecentTaskSwitcherBackward": "最近任务切换器(向后)",
|
||||
"shortcutReverseChatSearch": "反向聊天搜索",
|
||||
"shortcutTaskContentSearch": "搜索任务内容",
|
||||
"shortcutTogglePlanMode": "切换计划模式",
|
||||
"shortcutToggleSidebar": "切换侧边栏"
|
||||
}
|
||||
|
||||
@@ -106,5 +106,7 @@
|
||||
"viewLabel": "视图:{{name}}",
|
||||
"whatsNew": "新功能",
|
||||
"work": "工作",
|
||||
"workspaceFallback": "工作区"
|
||||
"workspaceFallback": "工作区",
|
||||
"taskPreferences": "侧边栏任务偏好设置",
|
||||
"views": "侧边栏视图"
|
||||
}
|
||||
|
||||
@@ -1516,5 +1516,51 @@
|
||||
"autopilotDescription": "代理会自主工作,只有在关键决策阻碍进展时才会向父任务询问,然后等待答复再继续。",
|
||||
"autopilotInfoLabel": "了解自动驾驶模式",
|
||||
"autopilotSidebarDescription": "自动驾驶任务",
|
||||
"autopilotChatDescription": "自动驾驶已启用。代理只会向父任务询问关键决策。"
|
||||
"autopilotChatDescription": "自动驾驶已启用。代理只会向父任务询问关键决策。",
|
||||
"duplicateRepository": "仓库重复",
|
||||
"duplicateRepositoryDescription": "{{repository}} 被添加了多次。请删除重复行。",
|
||||
"repositoryStillBeingVerified": "仍在验证仓库",
|
||||
"repositoryProviderVerificationFailed": "Kandev 无法验证此提供商 URL。请在仓库行中使用重试。",
|
||||
"repositoryProviderVerificationPending": "请等待提供商检查完成后再创建任务。",
|
||||
"failedToCreateFile": "创建文件失败",
|
||||
"failedToCreateSession": "创建会话失败",
|
||||
"failedToCreateTask": "创建任务失败",
|
||||
"failedToDeleteItem": "删除项目失败",
|
||||
"failedToDeletePlan": "删除计划失败",
|
||||
"failedToDownloadFile": "下载文件失败",
|
||||
"failedToFetchPlan": "获取计划失败",
|
||||
"failedToLoadPlanRevisions": "加载修订版本失败",
|
||||
"failedToRenameItem": "重命名项目失败",
|
||||
"failedToRevertPlan": "还原计划失败",
|
||||
"failedToSavePlan": "保存计划失败",
|
||||
"failedToStartTaskPlanMode": "以计划模式启动任务失败",
|
||||
"failedToUpdateTask": "更新任务失败",
|
||||
"noPullOrMergeRequestLinked": "此任务未关联拉取请求或合并请求",
|
||||
"sessionEndedCreateNew": "会话已结束。请创建新会话以继续。",
|
||||
"sessionUnavailableForInput": "会话不可用于输入",
|
||||
"taskQueued": "任务已排队",
|
||||
"taskQueuedWipLimit": "工作流步骤已达到 WIP 限制;此任务将在有可用容量时启动。",
|
||||
"failedToCheckRepositoryStatus": "检查本地仓库状态失败",
|
||||
"failedToDiscoverRepositories": "发现仓库失败",
|
||||
"failedToLoadCommitDiff": "加载提交差异失败",
|
||||
"failedToNestTask": "嵌套任务失败",
|
||||
"failedToProceedToNextStep": "进入下一步失败",
|
||||
"failedToStartImplementingPlan": "开始实施计划失败",
|
||||
"failedToStartImplementationSession": "启动实施会话失败",
|
||||
"failedToMoveTask": "移动任务失败",
|
||||
"movedTasksStepDescription_one": "任务现已位于所选步骤中。",
|
||||
"movedTasksStepDescription_other": "任务现已位于所选步骤中。",
|
||||
"movedTasksToStep_one": "已将任务移动到步骤",
|
||||
"movedTasksToStep_other": "已将 {{count}} 个任务移动到步骤",
|
||||
"movedTasksToWorkflow_one": "已将任务移动到工作流",
|
||||
"movedTasksToWorkflow_other": "已将 {{count}} 个任务移动到工作流",
|
||||
"movedTasksWorkflowDescription_one": "切换到目标工作流即可查看。",
|
||||
"movedTasksWorkflowDescription_other": "切换到目标工作流即可查看。",
|
||||
"sessionActionDelete": "删除会话中",
|
||||
"sessionActionFailed": "{{action}}失败",
|
||||
"sessionActionProgress": "正在{{action}}...",
|
||||
"sessionActionResume": "恢复会话中",
|
||||
"sessionActionSetPrimary": "设为主要会话",
|
||||
"sessionActionStop": "停止会话中",
|
||||
"sessionActionSuccessful": "{{action}}成功"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
---
|
||||
spec: docs/specs/i18n-second-audit-gaps/spec.md
|
||||
created: 2026-08-13
|
||||
status: completed
|
||||
---
|
||||
|
||||
# Implementation Plan: Additional I18n Audit Gaps
|
||||
|
||||
## Approach
|
||||
|
||||
Externalize the second-audit gaps without translating stable contracts. Plain configuration will
|
||||
store catalog keys or locale-neutral states; React hooks/components will resolve keys through
|
||||
`useTranslation`, and operation helpers will receive resolved copy or a call-time translator. Existing
|
||||
server/domain error messages remain detail text, with localized owned fallbacks used only when no
|
||||
detail exists. English, pseudo, Portuguese, and Simplified Chinese catalogs must retain key parity.
|
||||
|
||||
## Implementation
|
||||
|
||||
### Stable-value refactors
|
||||
|
||||
- Replace `label` strings in `lib/keyboard/shortcut-overrides.ts` with typed catalog keys. Resolve them
|
||||
in all Settings consumers without changing shortcut IDs, persistence, or bindings.
|
||||
- Replace Azure DevOps display strings in `azure-devops-mode-tabs.tsx`, `azure-devops-status.ts`, and
|
||||
`azure-devops-workspace-defaults.ts` with typed keys/states and update their consumers. Preserve WIQL,
|
||||
IDs, provider/status values, and prompt templates byte-for-byte.
|
||||
|
||||
### Operation and toast copy
|
||||
|
||||
- Localize review finding actions, send-to-agent results, review-dialog discard copy, task-plan
|
||||
fallback errors, walkthrough request states, file-operation fallbacks, task creation/launch results,
|
||||
and sidebar synchronization titles.
|
||||
- Complete the source-audit clusters in task/session/file operations, task movement, task creation,
|
||||
GitHub/GitLab feedback, and related hooks/components. At minimum inspect and address
|
||||
`use-session-actions.ts`, `use-commit-diff.ts`, `use-task-workflow-move.ts`, `use-implement-fresh.ts`,
|
||||
`use-plan-actions.ts`, `use-mr-feedback.ts`, `use-task-mr.ts`, `use-file-editors.ts`,
|
||||
`use-open-session-in-editor.ts`, task-create setup/effects/fresh-branch consent, new-session actions,
|
||||
file-browser actions, quick task launchers, and task-deleted/removal notifications.
|
||||
- Use `rg` over toast calls and plain-object `title`, `description`, and `message` fields. Translate only
|
||||
text proven to reach users; preserve console diagnostics and raw domain/server detail.
|
||||
|
||||
### Isolated surfaces and demos
|
||||
|
||||
- Localize the task PR shortcut fallback and Mermaid toast title at call/render time.
|
||||
- Make the orphan swimlane's stable object locale-neutral and inject the localized title into the
|
||||
rendered step without changing filtering/move behavior.
|
||||
- Resolve the 21 lint violations in `app/demo/agent-messages/page.tsx` and
|
||||
`app/demo/messages/page.tsx`; localize page chrome, counts with plural keys, and accessibility copy,
|
||||
but retain intentional message fixture payloads.
|
||||
|
||||
### Guard and catalogs
|
||||
|
||||
- Add keys to the narrowest existing namespaces (`review`, `task`, `common`, `settings`, `azuredevops`,
|
||||
`sidebar`, and others matching surface ownership) across every locale.
|
||||
- Append every newly migrated path not already covered to `i18nGuardFiles`; never remove entries.
|
||||
|
||||
## Mobile Parity
|
||||
|
||||
This work changes copy and locale-resolution timing only. Existing desktop/mobile composition,
|
||||
navigation, overlays, scrolling, and touch targets remain unchanged. The nearest exemplars are the
|
||||
currently shipped versions of each touched dialog, toast, Settings row, kanban lane, and demo page.
|
||||
Focused rendered tests at the existing responsive surfaces satisfy mobile parity; no new mobile E2E
|
||||
is required unless implementation changes structure or interaction.
|
||||
|
||||
## TDD And Verification
|
||||
|
||||
- Add failing focused assertions before changing voice mapping, Azure/shortcut metadata, orphan-step
|
||||
derivation, or any helper whose output contract changes.
|
||||
- Update existing hook/component tests for translated toast payloads and live locale switching where a
|
||||
component remains mounted across locale changes.
|
||||
- Run each task's focused Vitest command, then from `apps/web` run:
|
||||
|
||||
```bash
|
||||
pnpm run i18n:check
|
||||
pnpm run i18n:sweep -- app components hooks lib
|
||||
pnpm run lint:i18n -- <changed paths>
|
||||
```
|
||||
|
||||
Record every remaining sweep candidate and its intentional untranslated category. Run focused
|
||||
rendered/browser verification for the touched UI; if unavailable, record the exact blocker.
|
||||
|
||||
## Risks
|
||||
|
||||
- Translating an error message too early can freeze locale or accidentally alter a stable domain
|
||||
contract. Keep keys/states separate from presentation and resolve at call/render time.
|
||||
- Helper exports shared by tests and non-React consumers may require typed translation-key metadata
|
||||
rather than a hook dependency.
|
||||
- The broad source audit can include fixture/domain strings that must not be translated; each candidate
|
||||
needs reachability review rather than mechanical replacement.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] [Task 02: Azure and shortcut metadata](task-02-azure-shortcut-metadata.md)
|
||||
- [x] [Task 03: Review and plan operations](task-03-review-plan-operations.md)
|
||||
- [x] [Task 04: Production operation audit](task-04-production-operation-audit.md)
|
||||
- [x] [Task 05: Isolated UI leaks](task-05-isolated-ui-leaks.md)
|
||||
- [x] [Task 06: Demo pages and final audit](task-06-demo-pages-final-audit.md)
|
||||
|
||||
Task 01 was removed after rebasing onto main because `#2576` removed core Voice Mode in favor of the
|
||||
Voice plugin. Remaining tasks are sequential because they share locale catalogs and guard
|
||||
configuration. No implementation subagents are authorized by this plan.
|
||||
|
||||
## Verification Results
|
||||
|
||||
- Focused Vitest: 48 tests passed across 8 final regression files; earlier expanded runs passed 51
|
||||
tests across 10 files and 43 tests across 11 files.
|
||||
- `pnpm run typecheck`: passed.
|
||||
- Targeted `pnpm run lint:i18n -- <changed paths>`: passed with only specialized-config warnings for
|
||||
unrelated rule suppressions.
|
||||
- `pnpm run i18n:check`: passed; 127 pre-existing real-locale advisories and two pre-existing orphan
|
||||
keys (`task:downloadingPercent`, `task:failedToLoad`) remain.
|
||||
- `pnpm run i18n:sweep -- app components hooks lib`: completed across 2,445 files. Remaining hits are
|
||||
intentional agent/demo fixture payloads, WIQL/query syntax, prompt/markdown builders, console
|
||||
diagnostics, code examples/selectors, provider/domain values, and formatting glyphs. The two plural
|
||||
findings are known agent-facing CI prompts and must remain English.
|
||||
- `node scripts/check-guard-allowlist.mjs`: passed with 508 entries and 26 additions.
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: "02-azure-shortcut-metadata"
|
||||
title: "Localize configuration metadata"
|
||||
status: done
|
||||
wave: 2
|
||||
depends_on: []
|
||||
plan: "plan.md"
|
||||
spec: "../../specs/i18n-second-audit-gaps/spec.md"
|
||||
---
|
||||
|
||||
# Task 02: Azure And Shortcut Metadata
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Azure mode, PR status, default-query, action, and hint labels resolve from keys at render time.
|
||||
- Shortcut Settings names resolve from keys while IDs, bindings, WIQL, statuses, provider values, and
|
||||
agent prompt templates remain unchanged.
|
||||
- Focused tests cover stable metadata and translated consumers.
|
||||
|
||||
## Likely Files
|
||||
|
||||
`apps/web/components/azure-devops/azure-devops-mode-tabs.tsx`, `azure-devops-status.ts`,
|
||||
`azure-devops-workspace-defaults.ts`, their consumers/tests, `apps/web/lib/keyboard/shortcut-overrides.ts`,
|
||||
its tests and Settings consumers, catalogs, guard configuration.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
cd apps/web && pnpm test -- --run components/azure-devops/azure-devops-status.test.ts components/azure-devops/azure-devops-presets.test.ts lib/keyboard/shortcut-overrides.test.ts && pnpm run lint:i18n -- components/azure-devops lib/keyboard app/settings
|
||||
```
|
||||
|
||||
## Risks
|
||||
|
||||
Configuration objects cross render and persistence boundaries; only display metadata may change shape.
|
||||
|
||||
## Results
|
||||
|
||||
Azure modes/statuses and unchanged built-in query/action copy resolve from catalog keys; customized
|
||||
records, IDs, bindings, WIQL, statuses, and prompts remain stable. Focused tests and typecheck passed.
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: "03-review-plan-operations"
|
||||
title: "Localize review and plan operations"
|
||||
status: done
|
||||
wave: 3
|
||||
depends_on: ["02-azure-shortcut-metadata"]
|
||||
plan: "plan.md"
|
||||
spec: "../../specs/i18n-second-audit-gaps/spec.md"
|
||||
---
|
||||
|
||||
# Task 03: Review And Plan Operations
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Review finding, send-to-agent, review-dialog discard, walkthrough request, and task-plan fallbacks use
|
||||
active-locale copy at operation time.
|
||||
- Fetch, save, delete, revision-load, and revert plan fallbacks are all covered.
|
||||
- Server error detail and agent-facing prompt content remain untranslated.
|
||||
|
||||
## Likely Files
|
||||
|
||||
The three review files named in the spec, `apps/web/hooks/domains/session/use-task-plan.ts`,
|
||||
`use-request-changes-walkthrough.ts`, their focused tests, catalogs, and guard configuration.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
cd apps/web && pnpm test -- --run hooks/domains/session/use-request-changes-walkthrough.test.ts components/review/review-dialog-pr-state.test.tsx && pnpm run lint:i18n -- hooks/domains/review hooks/domains/session/use-task-plan.ts hooks/domains/session/use-request-changes-walkthrough.ts components/review/review-dialog.tsx
|
||||
```
|
||||
|
||||
## Risks
|
||||
|
||||
Callback dependency lists must include translation resolution so live locale changes do not retain stale copy.
|
||||
|
||||
## Results
|
||||
|
||||
Review, walkthrough, discard, and all five task-plan fallback paths resolve active-locale copy at
|
||||
operation time while raw server detail and prompt content remain unchanged.
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
id: "04-production-operation-audit"
|
||||
title: "Localize production operation copy"
|
||||
status: done
|
||||
wave: 4
|
||||
depends_on: ["03-review-plan-operations"]
|
||||
plan: "plan.md"
|
||||
spec: "../../specs/i18n-second-audit-gaps/spec.md"
|
||||
---
|
||||
|
||||
# Task 04: Production Operation Audit
|
||||
|
||||
## Acceptance
|
||||
|
||||
- File operations, task creation, session/task actions, task movement, GitHub/GitLab feedback, and
|
||||
sidebar synchronization expose no owned English toast/error copy found by the source audit.
|
||||
- Unknown owned fallbacks are localized while raw server/domain details and console diagnostics remain unchanged.
|
||||
- Changed helper logic receives failing tests first and all nearest focused tests pass.
|
||||
|
||||
## Likely Files
|
||||
|
||||
`apps/web/hooks/use-file-operations.ts`, `use-sidebar-views-sync.ts`, task/session/movement/file hooks,
|
||||
GitHub/GitLab feedback hooks, `apps/web/components/task-create-dialog-submit.tsx`, task-create helpers,
|
||||
new-session/file-browser/quick-launch components, tests, catalogs, and guard configuration.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
cd apps/web && pnpm test -- --run hooks/use-file-operations.test.ts hooks/use-sidebar-views-sync.test.ts components/task-create-dialog-submit.test.tsx hooks/domains/session/use-session-actions.test.ts hooks/use-task-workflow-move.test.ts && pnpm run lint:i18n -- hooks components/task-create-dialog-submit.tsx components/task-create-dialog-effects.ts components/task-create-dialog-fresh-branch-consent.ts
|
||||
```
|
||||
|
||||
## Risks
|
||||
|
||||
The sweep includes tests, fixtures, and domain data; translate only copy with a confirmed user-visible path.
|
||||
|
||||
## Results
|
||||
|
||||
Localized task/session/file operations, task creation and movement, GitLab association recovery,
|
||||
repository discovery, editor fallbacks, plan actions, nesting, and sidebar synchronization. Focused
|
||||
tests, typecheck, source audit, and guard lint passed.
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
id: "05-isolated-ui-leaks"
|
||||
title: "Localize isolated UI leaks"
|
||||
status: done
|
||||
wave: 5
|
||||
depends_on: ["04-production-operation-audit"]
|
||||
plan: "plan.md"
|
||||
spec: "../../specs/i18n-second-audit-gaps/spec.md"
|
||||
---
|
||||
|
||||
# Task 05: Isolated UI Leaks
|
||||
|
||||
## Acceptance
|
||||
|
||||
- The task PR shortcut and Mermaid toast use localized owned fallbacks without translating remote details.
|
||||
- The orphan swimlane title is localized while its sentinel, filtering, and move-target exclusion remain stable.
|
||||
- Existing focused tests cover toast behavior and orphan-step semantics.
|
||||
|
||||
## Likely Files
|
||||
|
||||
`apps/web/components/task/task-pr-shortcut.tsx`, `components/shared/mermaid-error-toast.tsx`,
|
||||
`components/kanban/swimlane-kanban-content.tsx`, their tests, catalogs, and guard configuration.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
cd apps/web && pnpm test -- --run components/task/task-pr-shortcut.test.tsx components/shared/mermaid-error-toast.test.tsx components/kanban/orphan-move-target.test.ts && pnpm run lint:i18n -- components/task/task-pr-shortcut.tsx components/shared/mermaid-error-toast.tsx components/kanban/swimlane-kanban-content.tsx
|
||||
```
|
||||
|
||||
## Risks
|
||||
|
||||
Do not place translated text into the module-level synthetic workflow-step constant.
|
||||
|
||||
## Results
|
||||
|
||||
PR shortcut and Mermaid owned fallbacks are localized, and the orphan step remains locale-neutral at
|
||||
module scope with its title injected during render. Focused tests passed.
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: "06-demo-pages-final-audit"
|
||||
title: "Localize demos and verify audit"
|
||||
status: done
|
||||
wave: 6
|
||||
depends_on: ["05-isolated-ui-leaks"]
|
||||
plan: "plan.md"
|
||||
spec: "../../specs/i18n-second-audit-gaps/spec.md"
|
||||
---
|
||||
|
||||
# Task 06: Demo Pages And Final Audit
|
||||
|
||||
## Acceptance
|
||||
|
||||
- All 21 demo-page lint violations in page chrome are localized, with count-based plurals and localized accessibility copy.
|
||||
- Intentional agent-message fixture payloads remain verbatim and every remaining sweep candidate is categorized.
|
||||
- Catalog parity, module-scope checks, guard lint, and focused rendered verification pass or have an exact recorded blocker.
|
||||
|
||||
## Likely Files
|
||||
|
||||
`apps/web/app/demo/agent-messages/page.tsx`, `app/demo/messages/page.tsx`, catalogs,
|
||||
`apps/web/eslint.i18n.options.mjs`, and plan/task result sections.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
cd apps/web && pnpm run lint:i18n -- app/demo/agent-messages/page.tsx app/demo/messages/page.tsx && pnpm run i18n:check && pnpm run i18n:sweep -- app components hooks lib
|
||||
```
|
||||
|
||||
## Risks
|
||||
|
||||
Fixture content is intentionally visible but not application chrome; document rather than translate those candidates.
|
||||
|
||||
## Results
|
||||
|
||||
All demo page-chrome violations are externalized with count-based plurals. Fixture payloads remain
|
||||
verbatim. Required guard lint, catalog checks, sweep, typecheck, and focused tests passed with the
|
||||
pre-existing advisories documented in the plan.
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
status: draft
|
||||
created: 2026-08-13
|
||||
owner: Kandev
|
||||
---
|
||||
|
||||
# Additional I18n Audit Gaps
|
||||
|
||||
## Why
|
||||
|
||||
The second localization audit found English display copy that escapes JSX literal linting because it
|
||||
lives in hooks, plain-object configuration, helper return values, toast payloads, or developer demo
|
||||
pages. These leaks make locale switching and the pseudo-locale incomplete even though the guarded
|
||||
JSX migration is complete.
|
||||
|
||||
## What
|
||||
|
||||
- Review findings, task plans, file operations, task/session actions, task movement, task creation,
|
||||
sidebar synchronization, walkthrough requests, GitHub/GitLab feedback, and related production
|
||||
operations resolve user-facing success and failure copy from catalogs when the operation runs.
|
||||
- Azure DevOps configuration retains WIQL, IDs, provider/status values, and agent prompt templates as
|
||||
stable data while mode labels, pull-request statuses, default query labels, action labels, and hints
|
||||
are localized at render time.
|
||||
- Configurable keyboard shortcuts retain stable IDs and bindings while Settings resolves catalog keys
|
||||
for display names at render time.
|
||||
- The review shortcut, Mermaid error toast, orphan swimlane title, and developer demo/QA page chrome
|
||||
are localized. Agent-message fixture payloads remain intentionally untranslated.
|
||||
- Newly migrated files are added to `i18nGuardFiles` where they are not already covered. Translation
|
||||
lookup never occurs at module scope, and plural copy uses `count` with plural catalog entries.
|
||||
|
||||
## Scenarios
|
||||
|
||||
- **GIVEN** a review, plan, file, task, session, movement, feedback, or synchronization operation
|
||||
succeeds or fails, **WHEN** the UI reports the result, **THEN** owned fallback copy uses the active
|
||||
locale while server/domain error details remain unmodified.
|
||||
- **GIVEN** Azure DevOps or keyboard shortcut configuration is rendered, **WHEN** the locale changes,
|
||||
**THEN** display labels update without changing stored IDs, values, bindings, WIQL, or prompts.
|
||||
- **GIVEN** an orphaned workflow task is rendered, **WHEN** the synthetic swimlane appears, **THEN**
|
||||
its title is localized while `__kandev_orphan__` remains the stable sentinel and is never offered as
|
||||
a move destination.
|
||||
- **GIVEN** either developer demo page is rendered, **WHEN** controls, headings, counts, empty/error
|
||||
states, or accessibility names appear, **THEN** page chrome is localized while intentional agent
|
||||
fixture content remains verbatim.
|
||||
- **GIVEN** the affected source trees are swept, **WHEN** remaining candidates are reviewed, **THEN**
|
||||
every untranslated candidate is documented as domain data, diagnostics, product/code text, or an
|
||||
agent-facing prompt rather than silently ignored.
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
- Watcher labels, the unknown-task fallback, and orphan catalog cleanup owned by task
|
||||
`0e0b8b61-35f9-49fa-9bcd-2cab7484510c`.
|
||||
- Core Voice Mode copy removed from main by `#2576`; voice input is now plugin-owned.
|
||||
- Translating console diagnostics, raw server errors, identifiers, product names, code examples,
|
||||
WIQL, provider/status values, branch/repository data, or agent-facing prompt templates.
|
||||
- Layout, navigation, touch behavior, viewport branching, or other mobile composition changes.
|
||||
- Translating fixture payload content whose purpose is to demonstrate agent messages.
|
||||
Reference in New Issue
Block a user