fix(i18n): localize watcher fallback labels (#2596)
Co-authored-by: Kandev Agent <agent@kandev.dev>
This commit is contained in:
@@ -31,7 +31,7 @@ import {
|
||||
DEFAULT_ISSUE_WATCH_PROMPT,
|
||||
} from "@/components/github/issue-watch-placeholders";
|
||||
import { RepoFilterSelector } from "@/components/github/repo-filter-selector";
|
||||
import { STEP_DEFAULT, STEP_DEFAULT_LABEL, resolveProfileId } from "@/lib/watcher-profile-default";
|
||||
import { STEP_DEFAULT, resolveProfileId } from "@/lib/watcher-profile-default";
|
||||
import type {
|
||||
RepoFilter,
|
||||
IssueWatch,
|
||||
@@ -271,6 +271,7 @@ function IssueAutomationFields({
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { workflows, agentProfiles, allExecutorProfiles } = useWatchFormData(form.workspaceId);
|
||||
const stepDefaultLabel = t("common:useStepDefaultOption");
|
||||
const { steps: workflowSteps, loading: stepsLoading } = useWorkflowSteps(form.workflowId);
|
||||
const placeholders = useMemo(() => issueWatchPlaceholders(t), [t]);
|
||||
|
||||
@@ -302,9 +303,9 @@ function IssueAutomationFields({
|
||||
description={t("github:optionalFallsBackToStepDefault")}
|
||||
value={form.agentProfileId || STEP_DEFAULT}
|
||||
onChange={(v) => setForm((prev) => ({ ...prev, agentProfileId: resolveProfileId(v) }))}
|
||||
placeholder={STEP_DEFAULT_LABEL}
|
||||
placeholder={stepDefaultLabel}
|
||||
items={[
|
||||
{ id: STEP_DEFAULT, label: STEP_DEFAULT_LABEL },
|
||||
{ id: STEP_DEFAULT, label: stepDefaultLabel },
|
||||
...agentProfiles.map((p) => ({
|
||||
id: p.id,
|
||||
label: p.label,
|
||||
@@ -317,9 +318,9 @@ function IssueAutomationFields({
|
||||
description={t("github:optionalFallsBackToStepDefault")}
|
||||
value={form.executorProfileId || STEP_DEFAULT}
|
||||
onChange={(v) => setForm((prev) => ({ ...prev, executorProfileId: resolveProfileId(v) }))}
|
||||
placeholder={STEP_DEFAULT_LABEL}
|
||||
placeholder={stepDefaultLabel}
|
||||
items={[
|
||||
{ id: STEP_DEFAULT, label: STEP_DEFAULT_LABEL },
|
||||
{ id: STEP_DEFAULT, label: stepDefaultLabel },
|
||||
...allExecutorProfiles.map((p) => ({ id: p.id, label: p.name })),
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
import { DEFAULT_REVIEW_WATCH_PROMPT } from "@/components/github/review-watch-placeholders";
|
||||
import { ReviewWatchPromptField } from "@/components/github/review-watch-prompt-field";
|
||||
import { RepoFilterSelector } from "@/components/github/repo-filter-selector";
|
||||
import { STEP_DEFAULT, STEP_DEFAULT_LABEL, resolveProfileId } from "@/lib/watcher-profile-default";
|
||||
import { STEP_DEFAULT, resolveProfileId } from "@/lib/watcher-profile-default";
|
||||
import type {
|
||||
RepoFilter,
|
||||
ReviewWatch,
|
||||
@@ -433,6 +433,7 @@ function ProfileFields({
|
||||
executorProfiles: Array<{ id: string; name: string }>;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const stepDefaultLabel = t("common:useStepDefaultOption");
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<SelectField
|
||||
@@ -440,9 +441,9 @@ function ProfileFields({
|
||||
description={t("github:optionalFallsBackToStepDefault")}
|
||||
value={form.agentProfileId || STEP_DEFAULT}
|
||||
onChange={(v) => setForm((prev) => ({ ...prev, agentProfileId: resolveProfileId(v) }))}
|
||||
placeholder={STEP_DEFAULT_LABEL}
|
||||
placeholder={stepDefaultLabel}
|
||||
items={[
|
||||
{ id: STEP_DEFAULT, label: STEP_DEFAULT_LABEL },
|
||||
{ id: STEP_DEFAULT, label: stepDefaultLabel },
|
||||
...agentProfiles.map((p) => ({
|
||||
id: p.id,
|
||||
label: p.label,
|
||||
@@ -469,10 +470,10 @@ function ProfileFields({
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="cursor-pointer">
|
||||
<SelectValue placeholder={STEP_DEFAULT_LABEL} />
|
||||
<SelectValue placeholder={stepDefaultLabel} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={STEP_DEFAULT}>{STEP_DEFAULT_LABEL}</SelectItem>
|
||||
<SelectItem value={STEP_DEFAULT}>{stepDefaultLabel}</SelectItem>
|
||||
{executorProfiles.map((p) => (
|
||||
<SelectItem key={p.id} value={p.id}>
|
||||
{p.name}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { WatcherRepositoryFields } from "@/components/watcher-repository-fields"
|
||||
import { useSettingsData } from "@/hooks/domains/settings/use-settings-data";
|
||||
import { useWorkflowSteps, stepPlaceholder } from "@/hooks/use-workflow-steps";
|
||||
import { useWorkflows } from "@/hooks/use-workflows";
|
||||
import { STEP_DEFAULT, STEP_DEFAULT_LABEL, resolveProfileId } from "@/lib/watcher-profile-default";
|
||||
import { STEP_DEFAULT, resolveProfileId } from "@/lib/watcher-profile-default";
|
||||
import type {
|
||||
CreateIssueWatchRequest,
|
||||
CreateReviewWatchRequest,
|
||||
@@ -205,6 +205,7 @@ type FormFieldsProps = {
|
||||
|
||||
function AutomationFields({ kind, form, setForm }: FormFieldsProps) {
|
||||
const { t } = useTranslation();
|
||||
const stepDefaultLabel = t("common:useStepDefaultOption");
|
||||
const data = useDialogData(form.workspaceId, form.workflowId);
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@@ -247,9 +248,9 @@ function AutomationFields({ kind, form, setForm }: FormFieldsProps) {
|
||||
onChange={(value) =>
|
||||
setForm((current) => ({ ...current, agentProfileId: resolveProfileId(value) }))
|
||||
}
|
||||
placeholder={STEP_DEFAULT_LABEL}
|
||||
placeholder={stepDefaultLabel}
|
||||
items={[
|
||||
{ id: STEP_DEFAULT, label: STEP_DEFAULT_LABEL },
|
||||
{ id: STEP_DEFAULT, label: stepDefaultLabel },
|
||||
...data.agentProfiles.map((item) => ({ id: item.id, label: item.label })),
|
||||
]}
|
||||
/>
|
||||
@@ -260,9 +261,9 @@ function AutomationFields({ kind, form, setForm }: FormFieldsProps) {
|
||||
onChange={(value) =>
|
||||
setForm((current) => ({ ...current, executorProfileId: resolveProfileId(value) }))
|
||||
}
|
||||
placeholder={STEP_DEFAULT_LABEL}
|
||||
placeholder={stepDefaultLabel}
|
||||
items={[
|
||||
{ id: STEP_DEFAULT, label: STEP_DEFAULT_LABEL },
|
||||
{ id: STEP_DEFAULT, label: stepDefaultLabel },
|
||||
...data.executorProfiles.map((item) => ({ id: item.id, label: item.name })),
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
jiraIssueWatchPlaceholders,
|
||||
DEFAULT_JIRA_ISSUE_WATCH_PROMPT,
|
||||
} from "@/components/jira/jira-issue-watch-placeholders";
|
||||
import { STEP_DEFAULT, STEP_DEFAULT_LABEL, resolveProfileId } from "@/lib/watcher-profile-default";
|
||||
import { STEP_DEFAULT, resolveProfileId } from "@/lib/watcher-profile-default";
|
||||
import { WatcherRepositoryFields } from "@/components/watcher-repository-fields";
|
||||
import { clearWorkspaceScopedForm } from "@/lib/watcher-repository-default";
|
||||
import type {
|
||||
@@ -339,6 +339,7 @@ function AutomationFields({
|
||||
setForm: React.Dispatch<React.SetStateAction<FormState>>;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const stepDefaultLabel = t("common:useStepDefaultOption");
|
||||
const { workflows, agentProfiles, allExecutorProfiles } = useFormData(form.workspaceId);
|
||||
const { steps, loading: stepsLoading } = useWorkflowSteps(form.workflowId);
|
||||
return (
|
||||
@@ -377,9 +378,9 @@ function AutomationFields({
|
||||
description={t("jira:optionalFallsBackToStepDefault")}
|
||||
value={form.agentProfileId || STEP_DEFAULT}
|
||||
onChange={(v) => setForm((p) => ({ ...p, agentProfileId: resolveProfileId(v) }))}
|
||||
placeholder={STEP_DEFAULT_LABEL}
|
||||
placeholder={stepDefaultLabel}
|
||||
items={[
|
||||
{ id: STEP_DEFAULT, label: STEP_DEFAULT_LABEL },
|
||||
{ id: STEP_DEFAULT, label: stepDefaultLabel },
|
||||
...agentProfiles.map((p) => ({
|
||||
id: p.id,
|
||||
label: p.label,
|
||||
@@ -392,9 +393,9 @@ function AutomationFields({
|
||||
description={t("jira:optionalFallsBackToStepDefault")}
|
||||
value={form.executorProfileId || STEP_DEFAULT}
|
||||
onChange={(v) => setForm((p) => ({ ...p, executorProfileId: resolveProfileId(v) }))}
|
||||
placeholder={STEP_DEFAULT_LABEL}
|
||||
placeholder={stepDefaultLabel}
|
||||
items={[
|
||||
{ id: STEP_DEFAULT, label: STEP_DEFAULT_LABEL },
|
||||
{ id: STEP_DEFAULT, label: stepDefaultLabel },
|
||||
...allExecutorProfiles.map((p) => ({ id: p.id, label: p.name })),
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
import { SettingsFields } from "./linear-issue-watch-fields";
|
||||
import { FilterFields, SelectField } from "./linear-issue-watch-filter-fields";
|
||||
import { linearIssueWatchPlaceholders } from "./linear-issue-watch-placeholders";
|
||||
import { STEP_DEFAULT, STEP_DEFAULT_LABEL, resolveProfileId } from "@/lib/watcher-profile-default";
|
||||
import { STEP_DEFAULT, resolveProfileId } from "@/lib/watcher-profile-default";
|
||||
import { WatcherRepositoryFields } from "@/components/watcher-repository-fields";
|
||||
import { clearWorkspaceScopedForm } from "@/lib/watcher-repository-default";
|
||||
import {
|
||||
@@ -159,6 +159,7 @@ function AutomationFields({
|
||||
setForm: React.Dispatch<React.SetStateAction<FormState>>;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const stepDefaultLabel = t("common:useStepDefaultOption");
|
||||
const { workflows, agentProfiles, allExecutorProfiles } = useFormData(form.workspaceId);
|
||||
const { steps, loading: stepsLoading } = useWorkflowSteps(form.workflowId);
|
||||
return (
|
||||
@@ -197,9 +198,9 @@ function AutomationFields({
|
||||
description={t("linear:fallsBackToStepDefault")}
|
||||
value={form.agentProfileId || STEP_DEFAULT}
|
||||
onChange={(v) => setForm((p) => ({ ...p, agentProfileId: resolveProfileId(v) }))}
|
||||
placeholder={STEP_DEFAULT_LABEL}
|
||||
placeholder={stepDefaultLabel}
|
||||
items={[
|
||||
{ id: STEP_DEFAULT, label: STEP_DEFAULT_LABEL },
|
||||
{ id: STEP_DEFAULT, label: stepDefaultLabel },
|
||||
...agentProfiles.map((p) => ({
|
||||
id: p.id,
|
||||
label: p.label,
|
||||
@@ -212,9 +213,9 @@ function AutomationFields({
|
||||
description={t("linear:fallsBackToStepDefault")}
|
||||
value={form.executorProfileId || STEP_DEFAULT}
|
||||
onChange={(v) => setForm((p) => ({ ...p, executorProfileId: resolveProfileId(v) }))}
|
||||
placeholder={STEP_DEFAULT_LABEL}
|
||||
placeholder={stepDefaultLabel}
|
||||
items={[
|
||||
{ id: STEP_DEFAULT, label: STEP_DEFAULT_LABEL },
|
||||
{ id: STEP_DEFAULT, label: stepDefaultLabel },
|
||||
...allExecutorProfiles.map((p) => ({ id: p.id, label: p.name })),
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ReactNode } from "react";
|
||||
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
||||
import { act, cleanup, fireEvent, render, screen } from "@testing-library/react";
|
||||
import { StateProvider } from "@/components/state-provider";
|
||||
import { ChatMessage } from "./chat-message";
|
||||
import { entityReferenceMarkdown } from "@/lib/entity-references/message-references";
|
||||
import type { EntityReference } from "@/lib/types/entity-reference";
|
||||
import { activateLocale } from "@/lib/i18n";
|
||||
import {
|
||||
sessionId as toSessionId,
|
||||
taskId as toTaskId,
|
||||
@@ -370,6 +371,26 @@ describe("ChatMessage sender badge", () => {
|
||||
expect(badge?.textContent).toContain("Old title");
|
||||
});
|
||||
|
||||
it("localizes the fallback when no sender task title is available", async () => {
|
||||
await activateLocale("en");
|
||||
const { container } = renderWithSender([], {
|
||||
sender_task_id: "task-deleted",
|
||||
sender_task_title: "",
|
||||
});
|
||||
|
||||
const badge = container.querySelector(SENDER_BADGE_SELECTOR);
|
||||
expect(badge?.textContent).toContain("(unknown task)");
|
||||
|
||||
await act(async () => {
|
||||
await activateLocale("pseudo");
|
||||
});
|
||||
|
||||
expect(badge?.textContent).toContain("(ũńķńōŵń ţàśķ)");
|
||||
await act(async () => {
|
||||
await activateLocale("en");
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the live title when it differs from the snapshot", () => {
|
||||
// The badge re-resolves the title from the kanban store so renames are
|
||||
// reflected without re-sending the message.
|
||||
|
||||
@@ -50,7 +50,7 @@ export function SenderTaskBadge({ sender, size = "sm" }: SenderTaskBadgeProps) {
|
||||
const liveSessionName = useAppStore((state) =>
|
||||
sender.sessionId ? (state.taskSessions.items[sender.sessionId]?.name ?? null) : null,
|
||||
);
|
||||
const fullTitle = liveTask?.title || sender.snapshotTitle || "(unknown task)";
|
||||
const fullTitle = liveTask?.title || sender.snapshotTitle || t("task:unknownTaskFallback");
|
||||
const sessionName = liveSessionName ?? sender.sessionName ?? "";
|
||||
const truncated = sessionName
|
||||
? `${truncateTitle(fullTitle)} · ${truncateTitle(sessionName)}`
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { WatcherRepositoryFields } from "./watcher-repository-fields";
|
||||
|
||||
const branchState = vi.hoisted(() => ({ loading: false }));
|
||||
|
||||
vi.mock("@/hooks/domains/workspace/use-repositories", () => ({
|
||||
useRepositories: () => ({ repositories: [] }),
|
||||
}));
|
||||
|
||||
vi.mock("@/hooks/domains/workspace/use-repository-branches", () => ({
|
||||
useBranches: () => ({ branches: [], isLoading: branchState.loading }),
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
branchState.loading = false;
|
||||
});
|
||||
|
||||
describe("WatcherRepositoryFields", () => {
|
||||
it("shows the repository-first placeholder while the branch selector is disabled", () => {
|
||||
render(
|
||||
<WatcherRepositoryFields
|
||||
workspaceId="workspace-1"
|
||||
repositoryId=""
|
||||
baseBranch=""
|
||||
onRepositoryChange={vi.fn()}
|
||||
onBaseBranchChange={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByLabelText("Base Branch").textContent).toContain("Pick a repository first");
|
||||
});
|
||||
|
||||
it("shows the loading placeholder instead of selecting the default branch", () => {
|
||||
branchState.loading = true;
|
||||
render(
|
||||
<WatcherRepositoryFields
|
||||
workspaceId="workspace-1"
|
||||
repositoryId="repository-1"
|
||||
baseBranch=""
|
||||
onRepositoryChange={vi.fn()}
|
||||
onBaseBranchChange={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByLabelText("Base Branch").textContent).toContain("Loading…");
|
||||
});
|
||||
});
|
||||
@@ -7,9 +7,7 @@ import { useRepositories } from "@/hooks/domains/workspace/use-repositories";
|
||||
import { useBranches } from "@/hooks/domains/workspace/use-repository-branches";
|
||||
import {
|
||||
NO_REPOSITORY,
|
||||
NO_REPOSITORY_LABEL,
|
||||
DEFAULT_BRANCH,
|
||||
DEFAULT_BRANCH_LABEL,
|
||||
branchPlaceholder,
|
||||
resolveBaseBranch,
|
||||
resolveRepositoryId,
|
||||
@@ -21,7 +19,7 @@ type PickItem = { id: string; label: string };
|
||||
function PickSelect(props: {
|
||||
label: string;
|
||||
description: string;
|
||||
value: string;
|
||||
value: string | undefined;
|
||||
onChange: (v: string) => void;
|
||||
placeholder: string;
|
||||
items: PickItem[];
|
||||
@@ -83,6 +81,15 @@ export function WatcherRepositoryFields({
|
||||
const { repositories } = useRepositories(workspaceId, !!workspaceId, true);
|
||||
const branchSource = repositoryId ? ({ kind: "id", workspaceId, repositoryId } as const) : null;
|
||||
const { branches, isLoading: branchesLoading } = useBranches(branchSource, !!repositoryId);
|
||||
const noRepositoryLabel = t("common:noRepositoryOption");
|
||||
const defaultBranchLabel = t("common:repositoryDefaultBranchOption");
|
||||
const branchPlaceholderLabels = {
|
||||
defaultBranch: defaultBranchLabel,
|
||||
loading: t("common:watcherLoadingBranchesPlaceholder"),
|
||||
pickRepository: t("common:watcherPickRepositoryPlaceholder"),
|
||||
};
|
||||
const branchPlaceholderLabel =
|
||||
branchPlaceholderLabels[branchPlaceholder(repositoryId, branchesLoading)];
|
||||
// A branch name can appear twice (local + remote tracking, e.g. "main" and
|
||||
// origin/"main"), which would emit two <SelectItem value="main"> — Radix then
|
||||
// renders every matching item's text in the trigger ("mainmain") and React
|
||||
@@ -95,20 +102,20 @@ export function WatcherRepositoryFields({
|
||||
description={t("common:optionalTheRepositoryTheAgentWorks")}
|
||||
value={repositoryId || NO_REPOSITORY}
|
||||
onChange={(v) => onRepositoryChange(resolveRepositoryId(v))}
|
||||
placeholder={NO_REPOSITORY_LABEL}
|
||||
placeholder={noRepositoryLabel}
|
||||
items={[
|
||||
{ id: NO_REPOSITORY, label: NO_REPOSITORY_LABEL },
|
||||
{ id: NO_REPOSITORY, label: noRepositoryLabel },
|
||||
...repositories.map((r) => ({ id: r.id, label: r.name })),
|
||||
]}
|
||||
/>
|
||||
<PickSelect
|
||||
label={t("common:baseBranch")}
|
||||
description={t("common:theBaseBranchTheAgentStarts")}
|
||||
value={baseBranch || DEFAULT_BRANCH}
|
||||
value={repositoryId && !branchesLoading ? baseBranch || DEFAULT_BRANCH : undefined}
|
||||
onChange={(v) => onBaseBranchChange(resolveBaseBranch(v))}
|
||||
placeholder={branchPlaceholder(repositoryId, branchesLoading)}
|
||||
placeholder={branchPlaceholderLabel}
|
||||
items={[
|
||||
{ id: DEFAULT_BRANCH, label: DEFAULT_BRANCH_LABEL },
|
||||
{ id: DEFAULT_BRANCH, label: defaultBranchLabel },
|
||||
...uniqueBranchNames.map((name) => ({ id: name, label: name })),
|
||||
]}
|
||||
disabled={!repositoryId || branchesLoading}
|
||||
|
||||
@@ -422,10 +422,8 @@ const SCREENS: Screen[] = [
|
||||
// the nav. `components/integrations/**` is now migrated, which cleared
|
||||
// `drafted-integration-enabled-control.tsx` ("Enabled"/"Disabled"),
|
||||
// `auth-status-banner.tsx` ("Authenticated", "· checked <relative>") and the
|
||||
// watcher card's loading state. What is left is
|
||||
// `components/watcher-repository-fields.tsx` ("Repository", "Base Branch",
|
||||
// "(no repository)"), `STEP_DEFAULT_LABEL` and `stepPlaceholder` — all shared
|
||||
// with the un-migrated Azure DevOps surface — plus
|
||||
// watcher card's loading state. What is left is `stepPlaceholder`, shared
|
||||
// with the un-migrated Azure DevOps surface, plus
|
||||
// `components/integrations/settings-section.tsx` chrome and `@kandev/ui`'s
|
||||
// built-in dialog "Close" label.
|
||||
//
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
// disallows <SelectItem value="">, so the dropdown item carries this sentinel
|
||||
// value and maps back to "" on change.
|
||||
export const STEP_DEFAULT = "__step_default__";
|
||||
export const STEP_DEFAULT_LABEL = "(use step default)";
|
||||
|
||||
// Map a select value back to the stored profile id, collapsing the sentinel to
|
||||
// "" so the payload keeps signalling "use step default".
|
||||
|
||||
@@ -2,7 +2,6 @@ import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
NO_REPOSITORY,
|
||||
DEFAULT_BRANCH,
|
||||
DEFAULT_BRANCH_LABEL,
|
||||
resolveRepositoryId,
|
||||
resolveBaseBranch,
|
||||
clearWorkspaceScopedForm,
|
||||
@@ -29,15 +28,15 @@ describe("resolveBaseBranch", () => {
|
||||
|
||||
describe("branchPlaceholder", () => {
|
||||
it("prompts to pick a repository first when none is selected", () => {
|
||||
expect(branchPlaceholder("", false)).toBe("Pick a repository first");
|
||||
expect(branchPlaceholder("", false)).toBe("pickRepository");
|
||||
// No repo takes precedence over the loading state.
|
||||
expect(branchPlaceholder("", true)).toBe("Pick a repository first");
|
||||
expect(branchPlaceholder("", true)).toBe("pickRepository");
|
||||
});
|
||||
it("shows a loading hint while branches stream in", () => {
|
||||
expect(branchPlaceholder("repo-1", true)).toBe("Loading…");
|
||||
expect(branchPlaceholder("repo-1", true)).toBe("loading");
|
||||
});
|
||||
it("shows the default-branch label once a repo is selected and loaded", () => {
|
||||
expect(branchPlaceholder("repo-1", false)).toBe(DEFAULT_BRANCH_LABEL);
|
||||
expect(branchPlaceholder("repo-1", false)).toBe("defaultBranch");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -11,9 +11,7 @@
|
||||
// or repository id (which would otherwise let resolve*() silently rewrite a
|
||||
// user's selection).
|
||||
export const NO_REPOSITORY = "kandev:no-repository";
|
||||
export const NO_REPOSITORY_LABEL = "(no repository)";
|
||||
export const DEFAULT_BRANCH = "kandev:default-branch";
|
||||
export const DEFAULT_BRANCH_LABEL = "(repository default branch)";
|
||||
|
||||
// Map a repository select value back to the stored id, collapsing the sentinel
|
||||
// to "" so the payload keeps signalling "no repository".
|
||||
@@ -56,8 +54,10 @@ export function clearWorkspaceScopedForm<
|
||||
// branchPlaceholder mirrors the workflow-step placeholder pattern: it nudges the
|
||||
// user to pick a repository first, then shows a loading hint while branches
|
||||
// stream in.
|
||||
export function branchPlaceholder(repositoryId: string, loading: boolean): string {
|
||||
if (!repositoryId) return "Pick a repository first";
|
||||
if (loading) return "Loading…";
|
||||
return DEFAULT_BRANCH_LABEL;
|
||||
export type BranchPlaceholder = "pickRepository" | "loading" | "defaultBranch";
|
||||
|
||||
export function branchPlaceholder(repositoryId: string, loading: boolean): BranchPlaceholder {
|
||||
if (!repositoryId) return "pickRepository";
|
||||
if (loading) return "loading";
|
||||
return "defaultBranch";
|
||||
}
|
||||
|
||||
@@ -161,7 +161,6 @@
|
||||
"profileNameRequiredTitle": "Profile name is required",
|
||||
"profileNotFound": "Profile not found.",
|
||||
"profileSettings": "Profile settings",
|
||||
"profileStartModelSettings": "Profile start model settings",
|
||||
"promptTokenHint": "<0>{{token}}</0> will be replaced with your task description or follow-up message.",
|
||||
"ptyConnectionError": "Connection error",
|
||||
"refresh": "Refresh",
|
||||
@@ -185,7 +184,6 @@
|
||||
"scanningForInstalledAgents": "Scanning for installed agents...",
|
||||
"searchModels": "Search models...",
|
||||
"searchModes": "Search modes...",
|
||||
"selectAModel": "Select a model...",
|
||||
"selectAModelShort": "Select a model",
|
||||
"selectModel": "Select model...",
|
||||
"sessionEnded": "Session ended.",
|
||||
|
||||
@@ -319,6 +319,8 @@
|
||||
"reloadTheApplicationToTryAgain": "Reload the application to try again.",
|
||||
"rename": "Rename",
|
||||
"repository": "Repository",
|
||||
"noRepositoryOption": "(no repository)",
|
||||
"repositoryDefaultBranchOption": "(repository default branch)",
|
||||
"repositoryName": "Repository name",
|
||||
"requestFailed": "Request failed",
|
||||
"reset": "Reset",
|
||||
@@ -422,6 +424,9 @@
|
||||
"untitledTask": "Untitled task",
|
||||
"usefulCommandsAgentSkills": "Useful commands & agent skills",
|
||||
"useScratchInstead": "Use scratch instead",
|
||||
"useStepDefaultOption": "(use step default)",
|
||||
"watcherLoadingBranchesPlaceholder": "Loading…",
|
||||
"watcherPickRepositoryPlaceholder": "Pick a repository first",
|
||||
"useThisFolder": "Use this folder",
|
||||
"utilities": "Utilities",
|
||||
"viewFullReleaseOnGithub": "View full release on GitHub",
|
||||
|
||||
@@ -531,7 +531,6 @@
|
||||
"thisWorkspaceUsesOneCredentialFor": "This workspace uses one credential for repository sync, watches, background jobs, and managed agent GitHub access. Executor profile credentials can still take precedence.",
|
||||
"titleIssues": "Issues",
|
||||
"titlePullRequests": "Pull requests",
|
||||
"togglePrEventPromptAutomation": "Toggle PR event prompt automation",
|
||||
"toggleReviewFollowUpAutomation": "Toggle review follow-up automation",
|
||||
"typeACustomQueryFirst": "Type a custom query first",
|
||||
"typeOwnerRepoToSearch": "Type {{format}} to search",
|
||||
|
||||
@@ -81,7 +81,6 @@
|
||||
"completeTheRequiredEditorFieldsBefore": "Complete the required editor fields before saving.",
|
||||
"configChatAgentDescription": "Choose which agent profile to use for the Configuration Chat. This agent can manage your workflows, agent profiles, and MCP configuration.",
|
||||
"configChatAgentNoDefault": "No default",
|
||||
"configChatAgentPlaceholder": "Choose an agent profile...",
|
||||
"configChatAgentTitle": "Configuration Chat Agent",
|
||||
"configureArchiveSafeguardsAndDefaultsFor": "Configure archive safeguards and defaults for tasks created by agents",
|
||||
"configureBackendAndExecutionResourceSampling": "Configure backend and execution resource sampling",
|
||||
@@ -413,8 +412,6 @@
|
||||
"mergeWithTarget": "Merge with {{target}}",
|
||||
"messageSubmitKey": "Message Submit Key",
|
||||
"metrics": "Metrics",
|
||||
"mode": "Mode",
|
||||
"model": "Model",
|
||||
"moveTabLeft": "Move tab left",
|
||||
"moveTabRight": "Move tab right",
|
||||
"moveTabToSplit": "Move tab to split",
|
||||
@@ -682,13 +679,8 @@
|
||||
"utilityCustomAgentsDescription": "Create your own utility agents with custom prompts.",
|
||||
"utilityCustomAgentsEmpty": "No custom utility agents.",
|
||||
"utilityCustomAgentsTitle": "Custom utility agents",
|
||||
"utilityDefaultModelAriaLabel": "Default utility model settings",
|
||||
"utilityDefaultModelDescription": "Select the profile used by built-in utility actions. The profile controls the model, permissions, and launch settings.",
|
||||
"utilityDefaultModelTitle": "Default utility agent model",
|
||||
"utilityDefaultWithModel": "Default ({{model}})",
|
||||
"utilityModelNotConfigured": "Not configured",
|
||||
"utilitySelectAgent": "Select agent...",
|
||||
"utilitySelectModel": "Select model...",
|
||||
"utilityAgentProfile": "Agent profile",
|
||||
"utilitySelectProfile": "Select profile...",
|
||||
"utilitySearchProfiles": "Search agent profiles...",
|
||||
|
||||
@@ -1009,7 +1009,6 @@
|
||||
"remoteStateLine": "State: {{state}}",
|
||||
"remoteStatusFailedLine": "Status failed: {{error}}",
|
||||
"currentPRCommit": "Current PR commit",
|
||||
"currentPRCommits": "Current PR commits",
|
||||
"remoteUrlPressEnter": "<0>Remote URL</0>; press Enter to submit it.",
|
||||
"remove": "Remove",
|
||||
"remove2": "Remove {{name}}",
|
||||
@@ -1499,6 +1498,7 @@
|
||||
"workspaceRepository": "Workspace repository",
|
||||
"workspaceRequiresConfiguration": "Workspace requires configuration",
|
||||
"workspaceUnavailable": "Workspace unavailable",
|
||||
"unknownTaskFallback": "(unknown task)",
|
||||
"worktree": "Worktree",
|
||||
"wouldCreateABlockerCycle": "Would create a blocker cycle: {{cycle}}",
|
||||
"writeAComment": "Write a comment…",
|
||||
|
||||
@@ -161,7 +161,6 @@
|
||||
"profileNameRequiredTitle": "Ƥŕōƒĩĺē ńàḿē ĩś ŕēqũĩŕēď",
|
||||
"profileNotFound": "Ƥŕōƒĩĺē ńōţ ƒōũńď.",
|
||||
"profileSettings": "Ƥŕōƒĩĺē śēţţĩńĝś",
|
||||
"profileStartModelSettings": "Ƥŕōƒĩĺē śţàŕţ ḿōďēĺ śēţţĩńĝś",
|
||||
"promptTokenHint": "<0>{{token}}</0> ŵĩĺĺ ƀē ŕēƥĺàćēď ŵĩţĥ ŷōũŕ ţàśķ ďēśćŕĩƥţĩōń ōŕ ƒōĺĺōŵ-ũƥ ḿēśśàĝē.",
|
||||
"ptyConnectionError": "Ćōńńēćţĩōń ēŕŕōŕ",
|
||||
"refresh": "Ŕēƒŕēśĥ",
|
||||
@@ -185,7 +184,6 @@
|
||||
"scanningForInstalledAgents": "Śćàńńĩńĝ ƒōŕ ĩńśţàĺĺēď àĝēńţś...",
|
||||
"searchModels": "Śēàŕćĥ ḿōďēĺś...",
|
||||
"searchModes": "Śēàŕćĥ ḿōďēś...",
|
||||
"selectAModel": "Śēĺēćţ à ḿōďēĺ...",
|
||||
"selectAModelShort": "Śēĺēćţ à ḿōďēĺ",
|
||||
"selectModel": "Śēĺēćţ ḿōďēĺ...",
|
||||
"sessionEnded": "Śēśśĩōń ēńďēď.",
|
||||
|
||||
@@ -319,6 +319,8 @@
|
||||
"reloadTheApplicationToTryAgain": "Ŕēĺōàď ţĥē àƥƥĺĩćàţĩōń ţō ţŕŷ àĝàĩń.",
|
||||
"rename": "Ŕēńàḿē",
|
||||
"repository": "Ŕēƥōśĩţōŕŷ",
|
||||
"noRepositoryOption": "(ńō ŕēƥōśĩţōŕŷ)",
|
||||
"repositoryDefaultBranchOption": "(ŕēƥōśĩţōŕŷ ďēƒàũĺţ ƀŕàńćĥ)",
|
||||
"repositoryName": "Ŕēƥōśĩţōŕŷ ńàḿē",
|
||||
"requestFailed": "Ŕēqũēśţ ƒàĩĺēď",
|
||||
"reset": "Ŕēśēţ",
|
||||
@@ -422,6 +424,9 @@
|
||||
"untitledTask": "Ũńţĩţĺēď ţàśķ",
|
||||
"usefulCommandsAgentSkills": "Ũśēƒũĺ ćōḿḿàńďś & àĝēńţ śķĩĺĺś",
|
||||
"useScratchInstead": "Ũśē śćŕàţćĥ ĩńśţēàď",
|
||||
"useStepDefaultOption": "(ũśē śţēƥ ďēƒàũĺţ)",
|
||||
"watcherLoadingBranchesPlaceholder": "Ĺōàďĩńĝ…",
|
||||
"watcherPickRepositoryPlaceholder": "Ƥĩćķ à ŕēƥōśĩţōŕŷ ƒĩŕśţ",
|
||||
"useThisFolder": "Ũśē ţĥĩś ƒōĺďēŕ",
|
||||
"utilities": "Ũţĩĺĩţĩēś",
|
||||
"viewFullReleaseOnGithub": "Vĩēŵ ƒũĺĺ ŕēĺēàśē ōń ĜĩţĤũƀ",
|
||||
|
||||
@@ -531,7 +531,6 @@
|
||||
"thisWorkspaceUsesOneCredentialFor": "Ţĥĩś ŵōŕķśƥàćē ũśēś ōńē ćŕēďēńţĩàĺ ƒōŕ ŕēƥōśĩţōŕŷ śŷńć, ŵàţćĥēś, ƀàćķĝŕōũńď ĵōƀś, àńď ḿàńàĝēď àĝēńţ ĜĩţĤũƀ àććēśś. Ēxēćũţōŕ ƥŕōƒĩĺē ćŕēďēńţĩàĺś ćàń śţĩĺĺ ţàķē ƥŕēćēďēńćē.",
|
||||
"titleIssues": "Ĩśśũēś",
|
||||
"titlePullRequests": "Ƥũĺĺ ŕēqũēśţś",
|
||||
"togglePrEventPromptAutomation": "Ţōĝĝĺē ƤŔ ēvēńţ ƥŕōḿƥţ àũţōḿàţĩōń",
|
||||
"toggleReviewFollowUpAutomation": "Ţōĝĝĺē ŕēvĩēŵ ƒōĺĺōŵ-ũƥ àũţōḿàţĩōń",
|
||||
"typeACustomQueryFirst": "Ţŷƥē à ćũśţōḿ qũēŕŷ ƒĩŕśţ",
|
||||
"typeOwnerRepoToSearch": "Ţŷƥē {{format}} ţō śēàŕćĥ",
|
||||
|
||||
@@ -81,7 +81,6 @@
|
||||
"completeTheRequiredEditorFieldsBefore": "Ćōḿƥĺēţē ţĥē ŕēqũĩŕēď ēďĩţōŕ ƒĩēĺďś ƀēƒōŕē śàvĩńĝ.",
|
||||
"configChatAgentDescription": "Ćĥōōśē ŵĥĩćĥ àĝēńţ ƥŕōƒĩĺē ţō ũśē ƒōŕ ţĥē Ćōńƒĩĝũŕàţĩōń Ćĥàţ. Ţĥĩś àĝēńţ ćàń ḿàńàĝē ŷōũŕ ŵōŕķƒĺōŵś, àĝēńţ ƥŕōƒĩĺēś, àńď ḾĆƤ ćōńƒĩĝũŕàţĩōń.",
|
||||
"configChatAgentNoDefault": "Ńō ďēƒàũĺţ",
|
||||
"configChatAgentPlaceholder": "Ćĥōōśē àń àĝēńţ ƥŕōƒĩĺē...",
|
||||
"configChatAgentTitle": "Ćōńƒĩĝũŕàţĩōń Ćĥàţ Àĝēńţ",
|
||||
"configureArchiveSafeguardsAndDefaultsFor": "Ćōńƒĩĝũŕē àŕćĥĩvē śàƒēĝũàŕďś àńď ďēƒàũĺţś ƒōŕ ţàśķś ćŕēàţēď ƀŷ àĝēńţś",
|
||||
"configureBackendAndExecutionResourceSampling": "Ćōńƒĩĝũŕē ƀàćķēńď àńď ēxēćũţĩōń ŕēśōũŕćē śàḿƥĺĩńĝ",
|
||||
@@ -413,8 +412,6 @@
|
||||
"mergeWithTarget": "Ḿēŕĝē ŵĩţĥ {{target}}",
|
||||
"messageSubmitKey": "Ḿēśśàĝē Śũƀḿĩţ Ķēŷ",
|
||||
"metrics": "Ḿēţŕĩćś",
|
||||
"mode": "Ḿōďē",
|
||||
"model": "Ḿōďēĺ",
|
||||
"moveTabLeft": "Ḿōvē ţàƀ ĺēƒţ",
|
||||
"moveTabRight": "Ḿōvē ţàƀ ŕĩĝĥţ",
|
||||
"moveTabToSplit": "Ḿōvē ţàƀ ţō śƥĺĩţ",
|
||||
@@ -682,13 +679,8 @@
|
||||
"utilityCustomAgentsDescription": "Ćŕēàţē ŷōũŕ ōŵń ũţĩĺĩţŷ àĝēńţś ŵĩţĥ ćũśţōḿ ƥŕōḿƥţś.",
|
||||
"utilityCustomAgentsEmpty": "Ńō ćũśţōḿ ũţĩĺĩţŷ àĝēńţś.",
|
||||
"utilityCustomAgentsTitle": "Ćũśţōḿ ũţĩĺĩţŷ àĝēńţś",
|
||||
"utilityDefaultModelAriaLabel": "Ďēƒàũĺţ ũţĩĺĩţŷ ḿōďēĺ śēţţĩńĝś",
|
||||
"utilityDefaultModelDescription": "Śēĺēćţ ţĥē ƥŕōƒĩĺē ũśēď ƀŷ ƀũĩĺţ-ĩń ũţĩĺĩţŷ àćţĩōńś. Ţĥē ƥŕōƒĩĺē ćōńţŕōĺś ţĥē ḿōďēĺ, ƥēŕḿĩśśĩōńś, àńď ĺàũńćĥ śēţţĩńĝś.",
|
||||
"utilityDefaultModelTitle": "Ďēƒàũĺţ ũţĩĺĩţŷ àĝēńţ ḿōďēĺ",
|
||||
"utilityDefaultWithModel": "Ďēƒàũĺţ ({{model}})",
|
||||
"utilityModelNotConfigured": "Ńōţ ćōńƒĩĝũŕēď",
|
||||
"utilitySelectAgent": "Śēĺēćţ àĝēńţ...",
|
||||
"utilitySelectModel": "Śēĺēćţ ḿōďēĺ...",
|
||||
"utilityAgentProfile": "Àĝēńţ ƥŕōƒĩĺē",
|
||||
"utilitySelectProfile": "Śēĺēćţ ƥŕōƒĩĺē...",
|
||||
"utilitySearchProfiles": "Śēàŕćĥ àĝēńţ ƥŕōƒĩĺēś...",
|
||||
|
||||
@@ -1009,7 +1009,6 @@
|
||||
"remoteStateLine": "Śţàţē: {{state}}",
|
||||
"remoteStatusFailedLine": "Śţàţũś ƒàĩĺēď: {{error}}",
|
||||
"currentPRCommit": "Ćũŕŕēńţ ƤŔ ćōḿḿĩţ",
|
||||
"currentPRCommits": "Ćũŕŕēńţ ƤŔ ćōḿḿĩţś",
|
||||
"remoteUrlPressEnter": "<0>Ŕēḿōţē ŨŔĹ</0>; ƥŕēśś Ēńţēŕ ţō śũƀḿĩţ ĩţ.",
|
||||
"remove": "Ŕēḿōvē",
|
||||
"remove2": "Ŕēḿōvē {{name}}",
|
||||
@@ -1499,6 +1498,7 @@
|
||||
"workspaceRepository": "Ŵōŕķśƥàćē ŕēƥōśĩţōŕŷ",
|
||||
"workspaceRequiresConfiguration": "Ŵōŕķśƥàćē ŕēqũĩŕēś ćōńƒĩĝũŕàţĩōń",
|
||||
"workspaceUnavailable": "Ŵōŕķśƥàćē ũńàvàĩĺàƀĺē",
|
||||
"unknownTaskFallback": "(ũńķńōŵń ţàśķ)",
|
||||
"worktree": "Ŵōŕķţŕēē",
|
||||
"wouldCreateABlockerCycle": "Ŵōũĺď ćŕēàţē à ƀĺōćķēŕ ćŷćĺē: {{cycle}}",
|
||||
"writeAComment": "Ŵŕĩţē à ćōḿḿēńţ…",
|
||||
|
||||
@@ -153,7 +153,6 @@
|
||||
"profileNameRequiredTitle": "O nome do perfil é obrigatório",
|
||||
"profileNotFound": "Perfil não encontrado.",
|
||||
"profileSettings": "Definições do perfil",
|
||||
"profileStartModelSettings": "Definições do modelo inicial do perfil",
|
||||
"promptTokenHint": "<0>{{token}}</0> será substituído pela descrição da sua tarefa ou pela mensagem de seguimento.",
|
||||
"ptyConnectionError": "Erro de ligação",
|
||||
"refresh": "Atualizar",
|
||||
@@ -177,7 +176,6 @@
|
||||
"scanningForInstalledAgents": "À procura de agentes instalados...",
|
||||
"searchModels": "Pesquisar modelos...",
|
||||
"searchModes": "Pesquisar modos...",
|
||||
"selectAModel": "Selecione um modelo...",
|
||||
"selectAModelShort": "Selecionar um modelo",
|
||||
"selectModel": "Selecionar modelo...",
|
||||
"sessionEnded": "A sessão terminou.",
|
||||
|
||||
@@ -312,6 +312,8 @@
|
||||
"reloadTheApplicationToTryAgain": "Recarregue a aplicação para tentar novamente.",
|
||||
"rename": "Mudar o nome",
|
||||
"repository": "Repositório",
|
||||
"noRepositoryOption": "(sem repositório)",
|
||||
"repositoryDefaultBranchOption": "(ramo predefinido do repositório)",
|
||||
"repositoryName": "Nome do repositório",
|
||||
"requestFailed": "O pedido falhou",
|
||||
"reset": "Repor",
|
||||
@@ -413,6 +415,9 @@
|
||||
"untitledTask": "Tarefa sem título",
|
||||
"usefulCommandsAgentSkills": "Comandos úteis e competências do agente",
|
||||
"useScratchInstead": "Usar antes uma área temporária",
|
||||
"useStepDefaultOption": "(usar predefinição do passo)",
|
||||
"watcherLoadingBranchesPlaceholder": "A carregar…",
|
||||
"watcherPickRepositoryPlaceholder": "Escolha primeiro um repositório",
|
||||
"useThisFolder": "Usar esta pasta",
|
||||
"viewFullReleaseOnGithub": "Ver a versão completa no GitHub",
|
||||
"waitsForCiAndAutomatedReviews": "Aguarda pelo CI e pelas revisões automáticas e depois corrige as falhas e responde aos comentários.",
|
||||
|
||||
@@ -512,7 +512,6 @@
|
||||
"thisWorkspaceUsesOneCredentialFor": "Esta área de trabalho usa uma credencial para a sincronização de repositórios, as vigilâncias, as tarefas em segundo plano e o acesso ao GitHub dos agentes geridos. As credenciais dos perfis de executor podem continuar a ter precedência.",
|
||||
"titleIssues": "Issues",
|
||||
"titlePullRequests": "Pull requests",
|
||||
"togglePrEventPromptAutomation": "Alternar a automatização de prompts de eventos de PR",
|
||||
"typeACustomQueryFirst": "Escreva primeiro uma consulta personalizada",
|
||||
"typeOwnerRepoToSearch": "Escreva {{format}} para pesquisar",
|
||||
"unableToLoadTheCurrentTask": "Não foi possível carregar a definição atual de acesso das tarefas.",
|
||||
|
||||
@@ -79,7 +79,6 @@
|
||||
"completeTheRequiredEditorFieldsBefore": "Preencha os campos obrigatórios do editor antes de guardar.",
|
||||
"configChatAgentDescription": "Escolha o perfil de agente a usar no Chat de configuração. Este agente pode gerir os seus fluxos de trabalho, perfis de agente e configuração MCP.",
|
||||
"configChatAgentNoDefault": "Sem pré-definição",
|
||||
"configChatAgentPlaceholder": "Escolha um perfil de agente...",
|
||||
"configChatAgentTitle": "Agente do Chat de configuração",
|
||||
"configureArchiveSafeguardsAndDefaultsFor": "Configure as salvaguardas de arquivo e as pré-definições das tarefas criadas por agentes",
|
||||
"configureBackendAndExecutionResourceSampling": "Configure a amostragem de recursos do backend e da execução",
|
||||
@@ -378,7 +377,6 @@
|
||||
"manageApiKeysAndCredentialsSecrets": "Faça a gestão das chaves de API e das credenciais. Os segredos são guardados encriptados e injetados nos ambientes dos agentes através das variáveis de ambiente do perfil de executor.",
|
||||
"globalScope": "Global",
|
||||
"modelFallbackSuffix": "(alternativo)",
|
||||
"mode": "Modo",
|
||||
"modelWithValue": "Modelo: {{model}}",
|
||||
"profileFallbackWillBeUsed": "O modelo inicial {{model}} já não está disponível; será usado o alternativo {{fallback}}.",
|
||||
"profileStartModelUnavailable": "O modelo inicial {{model}} já não está disponível; altere-o no perfil do agente.",
|
||||
@@ -407,7 +405,6 @@
|
||||
"mergeWithTarget": "Fundir com {{target}}",
|
||||
"messageSubmitKey": "Tecla de envio de mensagens",
|
||||
"metrics": "Métricas",
|
||||
"model": "Modelo",
|
||||
"moveTabLeft": "Mover o separador para a esquerda",
|
||||
"moveTabRight": "Mover o separador para a direita",
|
||||
"moveTabToSplit": "Mover o separador para uma divisão",
|
||||
@@ -667,13 +664,8 @@
|
||||
"utilityCustomAgentsDescription": "Crie os seus próprios agentes utilitários com prompts personalizados.",
|
||||
"utilityCustomAgentsEmpty": "Não há agentes utilitários personalizados.",
|
||||
"utilityCustomAgentsTitle": "Agentes utilitários personalizados",
|
||||
"utilityDefaultModelAriaLabel": "Definições do modelo utilitário pré-definido",
|
||||
"utilityDefaultModelDescription": "Selecione o perfil usado pelas ações utilitárias incorporadas. O perfil define o modelo, as permissões e as definições de execução.",
|
||||
"utilityDefaultModelTitle": "Modelo pré-definido dos agentes utilitários",
|
||||
"utilityDefaultWithModel": "Pré-definido ({{model}})",
|
||||
"utilityModelNotConfigured": "Não configurado",
|
||||
"utilitySelectAgent": "Selecionar agente...",
|
||||
"utilitySelectModel": "Selecionar modelo...",
|
||||
"utilityAgentProfile": "Perfil do agente",
|
||||
"utilitySelectProfile": "Selecionar perfil...",
|
||||
"utilitySearchProfiles": "Pesquisar perfis de agente...",
|
||||
|
||||
@@ -1009,7 +1009,6 @@
|
||||
"remoteStateLine": "Estado: {{state}}",
|
||||
"remoteStatusFailedLine": "Falha ao obter o estado: {{error}}",
|
||||
"currentPRCommit": "Commit atual do PR",
|
||||
"currentPRCommits": "Commits atuais do PR",
|
||||
"remoteUrlPressEnter": "<0>URL remoto</0>; prima Enter para o submeter.",
|
||||
"remove": "Remover",
|
||||
"remove2": "Remover {{name}}",
|
||||
@@ -1499,6 +1498,7 @@
|
||||
"workspaceRepository": "Repositório da área de trabalho",
|
||||
"workspaceRequiresConfiguration": "A área de trabalho requer configuração",
|
||||
"workspaceUnavailable": "Área de trabalho indisponível",
|
||||
"unknownTaskFallback": "(tarefa desconhecida)",
|
||||
"worktree": "Worktree",
|
||||
"wouldCreateABlockerCycle": "Criaria um ciclo de bloqueios: {{cycle}}",
|
||||
"writeAComment": "Escreva um comentário…",
|
||||
|
||||
@@ -161,7 +161,6 @@
|
||||
"profileNameRequiredTitle": "配置名称为必填项",
|
||||
"profileNotFound": "未找到配置。",
|
||||
"profileSettings": "配置设置",
|
||||
"profileStartModelSettings": "配置启动模型设置",
|
||||
"promptTokenHint": "<0>{{token}}</0> 将替换为你的任务描述或后续消息。",
|
||||
"ptyConnectionError": "连接错误",
|
||||
"refresh": "刷新",
|
||||
@@ -185,7 +184,6 @@
|
||||
"scanningForInstalledAgents": "正在扫描已安装的代理…",
|
||||
"searchModels": "搜索模型…",
|
||||
"searchModes": "搜索模式…",
|
||||
"selectAModel": "选择模型…",
|
||||
"selectAModelShort": "选择模型",
|
||||
"selectModel": "选择模型...",
|
||||
"sessionEnded": "会话已结束。",
|
||||
|
||||
@@ -319,6 +319,8 @@
|
||||
"reloadTheApplicationToTryAgain": "重新加载应用以重试。",
|
||||
"rename": "重命名",
|
||||
"repository": "仓库",
|
||||
"noRepositoryOption": "(无仓库)",
|
||||
"repositoryDefaultBranchOption": "(仓库默认分支)",
|
||||
"repositoryName": "仓库名称",
|
||||
"requestFailed": "请求失败",
|
||||
"reset": "重置",
|
||||
@@ -422,6 +424,9 @@
|
||||
"untitledTask": "未命名任务",
|
||||
"usefulCommandsAgentSkills": "实用命令与代理技能",
|
||||
"useScratchInstead": "改用临时工作区",
|
||||
"useStepDefaultOption": "(使用步骤默认值)",
|
||||
"watcherLoadingBranchesPlaceholder": "加载中…",
|
||||
"watcherPickRepositoryPlaceholder": "请先选择仓库",
|
||||
"useThisFolder": "使用此文件夹",
|
||||
"utilities": "实用工具",
|
||||
"viewFullReleaseOnGithub": "在 GitHub 上查看完整发行说明",
|
||||
|
||||
@@ -529,7 +529,6 @@
|
||||
"thisWorkspaceUsesOneCredentialFor": "此工作区使用一个凭据执行仓库同步、监控、后台任务和托管代理的 GitHub 访问。执行器配置凭据仍可优先使用。",
|
||||
"titleIssues": "议题",
|
||||
"titlePullRequests": "拉取请求",
|
||||
"togglePrEventPromptAutomation": "切换 PR 事件提示自动化",
|
||||
"typeACustomQueryFirst": "请先输入自定义查询",
|
||||
"typeOwnerRepoToSearch": "输入 {{format}} 进行搜索",
|
||||
"unableToLoadTheCurrentTask": "无法加载当前任务访问设置。",
|
||||
|
||||
@@ -81,7 +81,6 @@
|
||||
"completeTheRequiredEditorFieldsBefore": "保存前请填写必需的编辑器字段。",
|
||||
"configChatAgentDescription": "选择用于配置聊天的代理配置。此代理可管理你的工作流、代理配置与 MCP 配置。",
|
||||
"configChatAgentNoDefault": "无默认",
|
||||
"configChatAgentPlaceholder": "选择代理配置…",
|
||||
"configChatAgentTitle": "配置聊天代理",
|
||||
"configureArchiveSafeguardsAndDefaultsFor": "配置代理创建任务时的归档保护和默认设置",
|
||||
"configureBackendAndExecutionResourceSampling": "配置后端和执行资源采样",
|
||||
@@ -413,8 +412,6 @@
|
||||
"mergeWithTarget": "与 {{target}} 合并",
|
||||
"messageSubmitKey": "消息提交快捷键",
|
||||
"metrics": "指标",
|
||||
"mode": "模式",
|
||||
"model": "模型",
|
||||
"moveTabLeft": "左移标签页",
|
||||
"moveTabRight": "右移标签页",
|
||||
"moveTabToSplit": "将标签页移至拆分区域",
|
||||
@@ -682,13 +679,8 @@
|
||||
"utilityCustomAgentsDescription": "使用自定义提示词创建你自己的实用代理。",
|
||||
"utilityCustomAgentsEmpty": "无自定义实用代理。",
|
||||
"utilityCustomAgentsTitle": "自定义实用代理",
|
||||
"utilityDefaultModelAriaLabel": "默认实用模型设置",
|
||||
"utilityDefaultModelDescription": "选择内置实用操作使用的配置。配置包含模型、权限和启动设置。",
|
||||
"utilityDefaultModelTitle": "默认实用代理模型",
|
||||
"utilityDefaultWithModel": "默认({{model}})",
|
||||
"utilityModelNotConfigured": "未配置",
|
||||
"utilitySelectAgent": "选择代理…",
|
||||
"utilitySelectModel": "选择模型…",
|
||||
"utilityAgentProfile": "代理配置",
|
||||
"utilitySelectProfile": "选择配置…",
|
||||
"utilitySearchProfiles": "搜索代理配置…",
|
||||
|
||||
@@ -1009,7 +1009,6 @@
|
||||
"remoteStateLine": "状态:{{state}}",
|
||||
"remoteStatusFailedLine": "状态失败:{{error}}",
|
||||
"currentPRCommit": "当前 PR 提交",
|
||||
"currentPRCommits": "当前 PR 提交",
|
||||
"remoteUrlPressEnter": "<0>远程 URL</0>; 按 Enter 提交。",
|
||||
"remove": "移除",
|
||||
"remove2": "移除 {{name}}",
|
||||
@@ -1499,6 +1498,7 @@
|
||||
"workspaceRepository": "工作区仓库",
|
||||
"workspaceRequiresConfiguration": "工作区需要配置",
|
||||
"workspaceUnavailable": "工作区不可用",
|
||||
"unknownTaskFallback": "(未知任务)",
|
||||
"worktree": "工作树",
|
||||
"wouldCreateABlockerCycle": "会形成阻塞循环:{{cycle}}",
|
||||
"writeAComment": "撰写评论…",
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
spec: docs/specs/i18n-audit-watcher-copy/spec.md
|
||||
created: 2026-08-12
|
||||
status: completed
|
||||
---
|
||||
|
||||
# Implementation Plan: Watcher And Task Fallback Localization
|
||||
|
||||
## Overview
|
||||
|
||||
Separate stable watcher domain values from render-time copy, then update every watcher consumer and
|
||||
the sender-task fallback. Finish by deleting the 12 audit-confirmed obsolete keys from all catalogs
|
||||
and running focused tests plus the required i18n audits. The confirmed root cause is that display
|
||||
labels were exported from plain helper modules as English constants or returned as English strings;
|
||||
the orphan keys survived UI replacements without source references.
|
||||
|
||||
## Frontend
|
||||
|
||||
### Watcher profile defaults
|
||||
|
||||
- In `apps/web/lib/watcher-profile-default.ts`, retain `STEP_DEFAULT` and `resolveProfileId` as the
|
||||
untranslated domain contract and remove `STEP_DEFAULT_LABEL`.
|
||||
- Add a shared catalog key for the step-default display label and resolve it through each dialog's
|
||||
existing `useTranslation()` call in GitHub issue/review, GitLab issue/review, Jira, and Linear
|
||||
watcher dialogs.
|
||||
- Keep the sentinel-to-empty-string payload behavior unchanged and update focused helper/dialog tests
|
||||
where they assert the rendered option.
|
||||
|
||||
### Watcher repository defaults
|
||||
|
||||
- In `apps/web/lib/watcher-repository-default.ts`, retain only sentinel/domain behavior and replace
|
||||
`branchPlaceholder`'s English return values with a locale-neutral state/key result.
|
||||
- In `apps/web/components/watcher-repository-fields.tsx`, resolve no-repository, default-branch,
|
||||
repository-first, and loading copy from catalog keys during render. Never translate repository
|
||||
names, IDs, or branch names.
|
||||
- Extend `apps/web/lib/watcher-repository-default.test.ts` to prove locale-neutral state selection and
|
||||
unchanged sentinel normalization before changing production behavior.
|
||||
|
||||
### Sender task fallback
|
||||
|
||||
- In `apps/web/components/task/chat/messages/sender-task-badge.tsx`, resolve the unknown-task fallback
|
||||
from `task` catalog copy inside the component.
|
||||
- Extend the nearest existing chat-message component test to render a sender with no live or snapshot
|
||||
title and assert the visible fallback and tooltip text.
|
||||
|
||||
### Catalog cleanup
|
||||
|
||||
- Remove the following confirmed obsolete entries from English, pseudo, Portuguese, and Simplified
|
||||
Chinese catalogs: `agents:profileStartModelSettings`, `agents:selectAModel`,
|
||||
`github:togglePrEventPromptAutomation`, `settings:configChatAgentPlaceholder`, `settings:mode`,
|
||||
`settings:model`, `settings:utilityDefaultModelAriaLabel`, `settings:utilityDefaultWithModel`,
|
||||
`settings:utilityModelNotConfigured`, `settings:utilitySelectAgent`,
|
||||
`settings:utilitySelectModel`, and `task:currentPRCommits`.
|
||||
- The source-history audit ties these keys to superseded profile model controls, profile-based utility
|
||||
agent pickers, per-PR automation controls, configuration chat selection, core Voice Mode, and an old
|
||||
Changes header. Current replacement surfaces already use different, more specific keys.
|
||||
|
||||
### Mobile parity
|
||||
|
||||
This is copy-only work inside existing responsive dialogs and badges. It changes no composition,
|
||||
touch target, scrolling, navigation, or viewport branch, so focused component/rendered tests satisfy
|
||||
the mobile-parity exception; no new mobile Playwright scenario is required.
|
||||
|
||||
## Tests
|
||||
|
||||
- **What:** watcher sentinels remain stable while placeholder selection becomes locale-neutral.
|
||||
**File:** `apps/web/lib/watcher-profile-default.test.ts` and
|
||||
`apps/web/lib/watcher-repository-default.test.ts`. **How:** focused Vitest unit assertions.
|
||||
- **What:** translated watcher defaults appear through representative dialog/shared-field render
|
||||
paths without changing submitted IDs. **File:** existing watcher dialog tests and, if the shared
|
||||
fields lack a suitable harness, `apps/web/components/watcher-repository-fields.test.tsx`.
|
||||
**How:** focused Vitest component tests using the initialized test i18n instance.
|
||||
- **What:** an absent sender task title renders the translated fallback. **File:**
|
||||
`apps/web/components/task/chat/messages/chat-message.test.tsx` or the closest existing sender badge
|
||||
harness. **How:** focused Vitest component assertion.
|
||||
- **What:** catalogs contain no audit-reported orphan keys and new references are valid. **How:**
|
||||
`pnpm run i18n:check` and `pnpm run i18n:sweep -- components lib` from `apps/web`.
|
||||
|
||||
## Verification Results
|
||||
|
||||
- Focused Vitest: 46 tests passed across 7 files, including repository-first and branch-loading rendered placeholder coverage added during PR review.
|
||||
- CI remediation: 31 focused component tests and `pnpm run typecheck` passed after allowing the disabled branch select to carry an unset presentation value; live locale switching is asserted with exact English and pseudo fallback copy.
|
||||
- `pnpm run i18n:check`: passed with 0 orphans, pseudo in sync, and 127 unchanged out-of-scope real-locale parity advisories.
|
||||
- `pnpm run i18n:sweep -- components lib`: completed across 1,850 files; only the two documented prompt-builder plural findings and 91 unrelated review-by-eye candidates remain.
|
||||
|
||||
## Implementation Waves And Parallel Candidates
|
||||
|
||||
Wave 1:
|
||||
- [x] [task-01-localize-watcher-and-task-fallbacks](task-01-localize-watcher-and-task-fallbacks.md)
|
||||
|
||||
Wave 2:
|
||||
- [x] [task-02-remove-obsolete-catalog-entries](task-02-remove-obsolete-catalog-entries.md)
|
||||
|
||||
Tasks are sequential because both touch shared locale catalogs and the final orphan audit depends on
|
||||
the new references from Task 01.
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
id: "01-localize-watcher-and-task-fallbacks"
|
||||
title: "Localize watcher and task fallbacks"
|
||||
status: done
|
||||
wave: 1
|
||||
depends_on: []
|
||||
plan: "plan.md"
|
||||
spec: "../../specs/i18n-audit-watcher-copy/spec.md"
|
||||
---
|
||||
|
||||
# Task 01: Localize Watcher And Task Fallbacks
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Every GitHub, GitLab, Jira, and Linear watcher profile default resolves localized copy during
|
||||
render while sentinel values and submitted empty IDs remain unchanged.
|
||||
- Watcher repository empty/loading/default copy resolves during render while repository and branch
|
||||
domain values remain untranslated.
|
||||
- A sender badge with no live or snapshot title renders the localized unknown-task fallback.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
cd apps/web && pnpm test -- --run lib/watcher-profile-default.test.ts lib/watcher-repository-default.test.ts components/task/chat/messages/chat-message.test.tsx components/github/review-watch-dialog.test.tsx components/gitlab/issue-watch-dialog.test.tsx components/gitlab/review-watch-dialog.test.tsx
|
||||
```
|
||||
|
||||
## Files likely touched
|
||||
|
||||
- `apps/web/lib/watcher-profile-default.ts`
|
||||
- `apps/web/lib/watcher-profile-default.test.ts`
|
||||
- `apps/web/lib/watcher-repository-default.ts`
|
||||
- `apps/web/lib/watcher-repository-default.test.ts`
|
||||
- `apps/web/components/watcher-repository-fields.tsx`
|
||||
- focused shared-field test if needed
|
||||
- `apps/web/components/github/issue-watch-dialog.tsx`
|
||||
- `apps/web/components/github/review-watch-dialog.tsx`
|
||||
- `apps/web/components/gitlab/watch-dialog.tsx`
|
||||
- `apps/web/components/jira/jira-issue-watch-dialog.tsx`
|
||||
- `apps/web/components/linear/linear-issue-watch-dialog.tsx`
|
||||
- `apps/web/components/task/chat/messages/sender-task-badge.tsx`
|
||||
- closest existing sender badge/chat-message test
|
||||
- affected locale namespace files for newly referenced keys
|
||||
- this task file and `plan.md`
|
||||
|
||||
## Dependencies
|
||||
|
||||
None.
|
||||
|
||||
## Parallelism
|
||||
|
||||
Sequential. The dialogs share helper and catalog contracts.
|
||||
|
||||
## Inputs
|
||||
|
||||
- Spec scenarios for watcher profile defaults, repository states, and unknown sender tasks.
|
||||
- Existing sentinel normalization tests and existing watcher dialog component-test patterns.
|
||||
- Frontend i18n rule forbidding module-scope `t()` and translation of domain values.
|
||||
|
||||
## Output contract
|
||||
|
||||
Report RED/GREEN evidence, exact key ownership, unchanged sentinel/payload behavior, files changed,
|
||||
tests run, blockers and risks, then update this task and `plan.md` statuses.
|
||||
|
||||
## Results
|
||||
|
||||
RED: `cd apps/web && pnpm test -- --run lib/watcher-repository-default.test.ts components/task/chat/messages/chat-message.test.tsx` failed on the three English-returning placeholder states and the pseudo-locale unknown-task fallback. The remaining chat failures were cascading locale leakage after the intended assertion aborted.
|
||||
|
||||
GREEN: `cd apps/web && pnpm test -- --run lib/watcher-profile-default.test.ts lib/watcher-repository-default.test.ts components/task/chat/messages/chat-message.test.tsx components/github/review-watch-dialog.test.tsx components/gitlab/issue-watch-dialog.test.tsx components/gitlab/review-watch-dialog.test.tsx` passed 44 tests in 6 files.
|
||||
|
||||
PR review remediation: `cd apps/web && pnpm test -- --run components/watcher-repository-fields.test.tsx lib/watcher-profile-default.test.ts lib/watcher-repository-default.test.ts components/task/chat/messages/chat-message.test.tsx components/github/review-watch-dialog.test.tsx components/gitlab/issue-watch-dialog.test.tsx components/gitlab/review-watch-dialog.test.tsx` passed 46 tests in 7 files after proving that repository-first and loading placeholders remain visible while the branch selector is disabled.
|
||||
|
||||
CI/review follow-up: `cd apps/web && pnpm test -- --run components/watcher-repository-fields.test.tsx components/task/chat/messages/chat-message.test.tsx && pnpm run typecheck` passed 31 tests in 2 files and the full web TypeScript check. The sender fallback test now proves an already-mounted badge changes from exact English to exact pseudo copy after a live locale switch.
|
||||
|
||||
Sentinel values and empty-ID normalization are unchanged. Display copy resolves inside React render paths; no external side effects or security boundaries apply.
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
id: "02-remove-obsolete-catalog-entries"
|
||||
title: "Remove obsolete catalog entries"
|
||||
status: done
|
||||
wave: 2
|
||||
depends_on: ["01-localize-watcher-and-task-fallbacks"]
|
||||
plan: "plan.md"
|
||||
spec: "../../specs/i18n-audit-watcher-copy/spec.md"
|
||||
---
|
||||
|
||||
# Task 02: Remove Obsolete Catalog Entries
|
||||
|
||||
## Acceptance
|
||||
|
||||
- All 12 audit-reported keys, confirmed obsolete through current-source and source-history review,
|
||||
are absent from English, pseudo, Portuguese, and Simplified Chinese catalogs.
|
||||
- `i18n:check` reports none of those orphan entries and pseudo remains synchronized; existing
|
||||
real-locale parity advisories are not repaired in this task.
|
||||
- The required component/lib sweep reports no hardcoded user-facing copy introduced or retained by
|
||||
this repair.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
cd apps/web && pnpm run i18n:check && pnpm run i18n:sweep -- components lib
|
||||
```
|
||||
|
||||
## Files likely touched
|
||||
|
||||
- `apps/web/src/locales/en/agents.json`
|
||||
- `apps/web/src/locales/en/github.json`
|
||||
- `apps/web/src/locales/en/settings.json`
|
||||
- `apps/web/src/locales/en/task.json`
|
||||
- matching files under `apps/web/src/locales/pseudo/`
|
||||
- matching files under `apps/web/src/locales/pt-pt/`
|
||||
- matching files under `apps/web/src/locales/zh-cn/`
|
||||
- this task file and `plan.md`
|
||||
|
||||
## Dependencies
|
||||
|
||||
Task 01.
|
||||
|
||||
## Parallelism
|
||||
|
||||
Sequential. It validates shared catalogs after Task 01 adds the new fallback references.
|
||||
|
||||
## Inputs
|
||||
|
||||
- The source-history classification recorded in `plan.md`.
|
||||
- Task 01's final catalog key set.
|
||||
- User constraint excluding the real-locale parity finding.
|
||||
|
||||
## Output contract
|
||||
|
||||
Report the removed key list, exact checker/sweep results including unchanged advisory count, files
|
||||
changed, blockers and risks, then update this task and `plan.md` statuses.
|
||||
|
||||
## Results
|
||||
|
||||
`cd apps/web && pnpm run i18n:check && pnpm run i18n:sweep -- components lib` passed. The checker reports 0 orphan entries, pseudo in sync, and the unchanged 127 out-of-scope Portuguese/Chinese parity advisories. The sweep scanned 1,850 files and reported only the two documented prompt-builder plural concatenations plus 91 review-by-eye literals; none is introduced or retained by this repair.
|
||||
|
||||
All 12 named obsolete keys were removed from English, pseudo, Portuguese, and Simplified Chinese catalogs. External side effects and security/trust boundaries: None.
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
status: draft
|
||||
created: 2026-08-12
|
||||
owner: Kandev
|
||||
---
|
||||
|
||||
# Watcher And Task Fallback Localization
|
||||
|
||||
## Why
|
||||
|
||||
Watcher dialogs and inter-task message badges expose several English-only fallback labels even
|
||||
when another locale is active. Obsolete catalog entries also obscure whether the current UI is
|
||||
fully covered by localization checks.
|
||||
|
||||
## What
|
||||
|
||||
- Watcher profile selectors display the localized equivalent of `(use step default)` while their
|
||||
stored sentinel and submitted empty profile ID remain locale-independent.
|
||||
- Watcher repository and branch selectors localize the no-repository option, default-branch option,
|
||||
repository-first prompt, and loading prompt at render time. Repository IDs, branch names, and
|
||||
sentinel values remain untranslated.
|
||||
- Inter-task sender badges display a localized unknown-task fallback when neither a live title nor a
|
||||
snapshot title exists.
|
||||
- English catalog entries with no current source reference and no current user-interface owner are
|
||||
removed from every catalog. Current behavior is not changed merely to retain an obsolete key.
|
||||
- Translation lookup does not occur at module scope.
|
||||
|
||||
## Scenarios
|
||||
|
||||
- **GIVEN** any GitHub issue/review, GitLab issue/review, Jira, or Linear watcher dialog is rendered
|
||||
in a non-English locale, **WHEN** no agent or executor profile override is selected, **THEN** the
|
||||
step-default option and placeholder use that locale while saving still submits an empty profile ID.
|
||||
- **GIVEN** a watcher repository picker is rendered in a non-English locale, **WHEN** repository and
|
||||
branch selection move through empty, loading, and loaded states, **THEN** only UI fallback copy is
|
||||
translated and all repository/branch domain values remain unchanged.
|
||||
- **GIVEN** an inter-task message has no resolvable live or snapshot task title, **WHEN** its sender
|
||||
badge renders, **THEN** the badge and tooltip use the localized unknown-task fallback.
|
||||
- **GIVEN** the catalogs contain the 12 audit-reported unreferenced keys, **WHEN** the i18n checker
|
||||
runs after cleanup, **THEN** it reports no orphan entries from that set and pseudo remains in sync.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Resolving the existing Portuguese and Simplified Chinese catalog parity advisories.
|
||||
- Changing watcher persistence, payload contracts, repository selection behavior, dialog layout, or
|
||||
mobile interaction behavior.
|
||||
- Restoring UI that was previously removed or replaced solely to consume an old catalog key.
|
||||
Reference in New Issue
Block a user