feat(i18n): localize Settings → Integrations → GitHub watches and defaults (#2158)
Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,22 +24,33 @@ import {
|
||||
computeEditorHeight,
|
||||
} from "@/components/settings/profile-edit/script-editor";
|
||||
import type { ScriptPlaceholder } from "@/components/settings/profile-edit/script-editor-completions";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import type { TFunction } from "i18next";
|
||||
|
||||
const ACTION_PROMPT_PLACEHOLDERS: ScriptPlaceholder[] = [
|
||||
{
|
||||
key: "url",
|
||||
description: "URL of the PR or issue",
|
||||
example: "https://github.com/org/repo/pull/42",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "title",
|
||||
description: "Title of the PR or issue",
|
||||
example: "Fix login page crash",
|
||||
executor_types: [],
|
||||
},
|
||||
];
|
||||
// A function, not a const: `description` is copy, and a module-scope `t()` call
|
||||
// would freeze at the boot locale (see docs/i18n.md). `key` is the substitution
|
||||
// token and `example` is sample GitHub data — neither is translated.
|
||||
function actionPromptPlaceholders(t: TFunction): ScriptPlaceholder[] {
|
||||
return [
|
||||
{
|
||||
key: "url",
|
||||
description: t("github:placeholderActionUrl"),
|
||||
example: "https://github.com/org/repo/pull/42",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "title",
|
||||
description: t("github:placeholderActionTitle"),
|
||||
example: "Fix login page crash",
|
||||
executor_types: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// `label` is PERSISTED to workspace settings as part of `GitHubActionPreset` and
|
||||
// is immediately editable in the row below, so it must stay locale-neutral: a
|
||||
// preset seeded in one locale and saved unedited would keep that locale's text
|
||||
// forever. Same contract as DEFAULT_PR_PRESETS in my-github/action-presets.ts.
|
||||
function newPreset(): GitHubActionPreset {
|
||||
return {
|
||||
id: `preset_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`,
|
||||
@@ -59,11 +70,12 @@ function PresetIconSelect({
|
||||
isDirty: boolean;
|
||||
onChange: (v: string) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Select value={value} onValueChange={onChange}>
|
||||
<SelectTrigger
|
||||
className="!h-8 py-0.5 text-sm cursor-pointer"
|
||||
aria-label="Icon"
|
||||
aria-label={t("github:icon")}
|
||||
data-settings-dirty={isDirty}
|
||||
>
|
||||
<SelectValue>
|
||||
@@ -77,7 +89,7 @@ function PresetIconSelect({
|
||||
<SelectItem key={choice.key} value={choice.key} className="cursor-pointer">
|
||||
<span className="flex items-center gap-2">
|
||||
<ChoiceIcon className="h-3.5 w-3.5" />
|
||||
{choice.label}
|
||||
{t(choice.labelKey)}
|
||||
</span>
|
||||
</SelectItem>
|
||||
);
|
||||
@@ -102,6 +114,7 @@ function PresetRow({
|
||||
onPatch: (patch: Partial<GitHubActionPreset>) => void;
|
||||
onRemove: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div
|
||||
className="rounded-md border"
|
||||
@@ -110,7 +123,7 @@ function PresetRow({
|
||||
>
|
||||
<div className="flex items-end gap-2 p-2">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className="text-[10px] text-muted-foreground">Icon</span>
|
||||
<span className="text-[10px] text-muted-foreground">{t("github:icon")}</span>
|
||||
<PresetIconSelect
|
||||
value={preset.icon}
|
||||
isDirty={preset.icon !== baseline?.icon}
|
||||
@@ -118,22 +131,22 @@ function PresetRow({
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className="text-[10px] text-muted-foreground">Label</span>
|
||||
<span className="text-[10px] text-muted-foreground">{t("github:label")}</span>
|
||||
<Input
|
||||
className="h-8 w-40"
|
||||
value={preset.label}
|
||||
data-settings-dirty={preset.label !== baseline?.label}
|
||||
placeholder="Label"
|
||||
placeholder={t("github:label")}
|
||||
onChange={(e) => onPatch({ label: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-0.5 flex-1">
|
||||
<span className="text-[10px] text-muted-foreground">Hint</span>
|
||||
<span className="text-[10px] text-muted-foreground">{t("github:hint")}</span>
|
||||
<Input
|
||||
className="h-8"
|
||||
value={preset.hint}
|
||||
data-settings-dirty={preset.hint !== baseline?.hint}
|
||||
placeholder="Hint (optional)"
|
||||
placeholder={t("github:hintOptional")}
|
||||
onChange={(e) => onPatch({ hint: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
@@ -143,42 +156,64 @@ function PresetRow({
|
||||
className="h-8 cursor-pointer text-xs"
|
||||
onClick={onToggle}
|
||||
>
|
||||
{expanded ? "Hide prompt" : "Edit prompt"}
|
||||
{expanded ? t("github:hidePrompt") : t("github:editPrompt")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 cursor-pointer text-destructive"
|
||||
onClick={onRemove}
|
||||
aria-label="Remove"
|
||||
aria-label={t("github:remove")}
|
||||
>
|
||||
<IconTrash className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
{expanded && (
|
||||
<div className="px-2 pb-2 space-y-1">
|
||||
<div
|
||||
className="rounded-md border overflow-hidden"
|
||||
data-settings-dirty={preset.prompt_template !== baseline?.prompt_template}
|
||||
data-settings-dirty-level="container"
|
||||
>
|
||||
<ScriptEditor
|
||||
value={preset.prompt_template}
|
||||
onChange={(v) => onPatch({ prompt_template: v })}
|
||||
language="markdown"
|
||||
height={computeEditorHeight(preset.prompt_template)}
|
||||
lineNumbers="off"
|
||||
placeholders={ACTION_PROMPT_PLACEHOLDERS}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground/60">
|
||||
Type {"{{"} to see available placeholders.{" "}
|
||||
<code className="bg-muted px-1 py-0.5 rounded text-[10px]">{"{{url}}"}</code> and{" "}
|
||||
<code className="bg-muted px-1 py-0.5 rounded text-[10px]">{"{{title}}"}</code> are
|
||||
substituted when the action runs.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{expanded && <PresetPromptEditor preset={preset} baseline={baseline} onPatch={onPatch} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Split out of PresetRow to keep that component under the 100-line lint cap.
|
||||
function PresetPromptEditor({
|
||||
preset,
|
||||
baseline,
|
||||
onPatch,
|
||||
}: {
|
||||
preset: GitHubActionPreset;
|
||||
baseline?: GitHubActionPreset;
|
||||
onPatch: (patch: Partial<GitHubActionPreset>) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const placeholders = useMemo(() => actionPromptPlaceholders(t), [t]);
|
||||
return (
|
||||
<div className="px-2 pb-2 space-y-1">
|
||||
<div
|
||||
className="rounded-md border overflow-hidden"
|
||||
data-settings-dirty={preset.prompt_template !== baseline?.prompt_template}
|
||||
data-settings-dirty-level="container"
|
||||
>
|
||||
<ScriptEditor
|
||||
value={preset.prompt_template}
|
||||
onChange={(v) => onPatch({ prompt_template: v })}
|
||||
language="markdown"
|
||||
height={computeEditorHeight(preset.prompt_template)}
|
||||
lineNumbers="off"
|
||||
placeholders={placeholders}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground/60">
|
||||
{/* The three `{{…}}` tokens are passed as values, never written into the
|
||||
catalog, where i18next would interpolate them away. */}
|
||||
<Trans
|
||||
i18nKey="github:actionPromptPlaceholderHelp"
|
||||
values={{ token: "{{", url: "{{url}}", title: "{{title}}" }}
|
||||
>
|
||||
Type {"{{token}}"} to see available placeholders.{" "}
|
||||
<code className="bg-muted px-1 py-0.5 rounded text-[10px]">{"{{url}}"}</code> and{" "}
|
||||
<code className="bg-muted px-1 py-0.5 rounded text-[10px]">{"{{title}}"}</code> are
|
||||
substituted when the action runs.
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -315,6 +350,7 @@ function usePresetDrafts(workspaceId: string): {
|
||||
}
|
||||
|
||||
export function ActionPresetsSection({ workspaceId }: { workspaceId: string }) {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const {
|
||||
prDraft,
|
||||
@@ -332,26 +368,26 @@ export function ActionPresetsSection({ workspaceId }: { workspaceId: string }) {
|
||||
const handleSave = useCallback(async () => {
|
||||
try {
|
||||
await save();
|
||||
toast({ description: "Quick actions saved", variant: "success" });
|
||||
toast({ description: t("github:quickActionsSaved"), variant: "success" });
|
||||
} catch {
|
||||
toast({ description: "Failed to save quick actions", variant: "error" });
|
||||
toast({ description: t("github:failedToSaveQuickActions"), variant: "error" });
|
||||
throw new Error("Failed to save quick actions");
|
||||
}
|
||||
}, [save, toast]);
|
||||
}, [save, t, toast]);
|
||||
useSettingsSaveContributor({
|
||||
id: `github-action-presets:${workspaceId}`,
|
||||
revision: JSON.stringify([prDraft, issueDraft]),
|
||||
isDirty: dirty,
|
||||
canSave: !loading,
|
||||
invalidReason: loading ? "Quick actions are still loading." : undefined,
|
||||
invalidReason: loading ? t("github:quickActionsAreStillLoading") : undefined,
|
||||
save: handleSave,
|
||||
discard,
|
||||
});
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title="Quick actions"
|
||||
description="Prompts shown on /github when starting a task from a PR or issue."
|
||||
title={t("github:quickActions")}
|
||||
description={t("github:promptsShownOnGithubWhenStarting", { route: "/github" })}
|
||||
action={
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
@@ -362,7 +398,7 @@ export function ActionPresetsSection({ workspaceId }: { workspaceId: string }) {
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<IconRefresh className="h-3.5 w-3.5 mr-1" />
|
||||
Reset
|
||||
{t("common:reset")}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
@@ -373,10 +409,10 @@ export function ActionPresetsSection({ workspaceId }: { workspaceId: string }) {
|
||||
<Tabs defaultValue="pr">
|
||||
<TabsList>
|
||||
<TabsTrigger value="pr" className="cursor-pointer">
|
||||
Pull requests
|
||||
{t("github:pullRequests")}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="issue" className="cursor-pointer">
|
||||
Issues
|
||||
{t("github:issues")}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="pr">
|
||||
@@ -384,7 +420,7 @@ export function ActionPresetsSection({ workspaceId }: { workspaceId: string }) {
|
||||
presets={prDraft}
|
||||
baseline={prBaseline}
|
||||
onChange={setPrDraft}
|
||||
addLabel="Add PR action"
|
||||
addLabel={t("github:addPrAction")}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="issue">
|
||||
@@ -392,7 +428,7 @@ export function ActionPresetsSection({ workspaceId }: { workspaceId: string }) {
|
||||
presets={issueDraft}
|
||||
baseline={issueBaseline}
|
||||
onChange={setIssueDraft}
|
||||
addLabel="Add issue action"
|
||||
addLabel={t("github:addIssueAction")}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
@@ -20,7 +20,11 @@ import {
|
||||
PR_PRESETS as BUILTIN_PR_PRESETS,
|
||||
ISSUE_PRESETS as BUILTIN_ISSUE_PRESETS,
|
||||
} from "@/components/github/my-github/search-bar";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// `label` is PERSISTED as part of `StoredQueryPreset` (github_default_query_presets)
|
||||
// and is editable in the row below, so it must stay locale-neutral — see
|
||||
// `newPreset` in action-presets-section.tsx for the same contract.
|
||||
function newPreset(): StoredQueryPreset {
|
||||
return {
|
||||
value: `q_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`,
|
||||
@@ -41,6 +45,7 @@ function QueryRow({
|
||||
onPatch: (patch: Partial<StoredQueryPreset>) => void;
|
||||
onRemove: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div
|
||||
className="flex items-center gap-2 rounded-md border p-2"
|
||||
@@ -48,27 +53,27 @@ function QueryRow({
|
||||
data-settings-dirty-level="container"
|
||||
>
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className="text-[10px] text-muted-foreground">Label</span>
|
||||
<span className="text-[10px] text-muted-foreground">{t("github:label")}</span>
|
||||
<Input
|
||||
className="h-8 w-36"
|
||||
value={preset.label}
|
||||
data-settings-dirty={preset.label !== baseline?.label}
|
||||
placeholder="Label"
|
||||
placeholder={t("github:label")}
|
||||
onChange={(e) => onPatch({ label: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-0.5 flex-1">
|
||||
<span className="text-[10px] text-muted-foreground">Query</span>
|
||||
<span className="text-[10px] text-muted-foreground">{t("github:query")}</span>
|
||||
<Input
|
||||
className="h-8 font-mono text-xs"
|
||||
value={preset.filter}
|
||||
data-settings-dirty={preset.filter !== baseline?.filter}
|
||||
placeholder="e.g. review-requested:@me is:open"
|
||||
placeholder={t("github:queryExample", { query: "review-requested:@me is:open" })}
|
||||
onChange={(e) => onPatch({ filter: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className="text-[10px] text-muted-foreground">Group</span>
|
||||
<span className="text-[10px] text-muted-foreground">{t("github:group")}</span>
|
||||
<Select
|
||||
value={preset.group}
|
||||
onValueChange={(v) => onPatch({ group: v as "inbox" | "created" })}
|
||||
@@ -81,10 +86,10 @@ function QueryRow({
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="inbox" className="cursor-pointer">
|
||||
Inbox
|
||||
{t("github:inbox")}
|
||||
</SelectItem>
|
||||
<SelectItem value="created" className="cursor-pointer">
|
||||
Created
|
||||
{t("github:created")}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -94,7 +99,7 @@ function QueryRow({
|
||||
size="icon"
|
||||
className="h-8 w-8 cursor-pointer text-destructive mt-3.5"
|
||||
onClick={onRemove}
|
||||
aria-label="Remove"
|
||||
aria-label={t("github:remove")}
|
||||
>
|
||||
<IconTrash className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
@@ -145,6 +150,7 @@ function QueryEditor({
|
||||
}
|
||||
|
||||
function useDefaultQueryDrafts(workspaceId?: string) {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { prPresets, issuePresets, save, reset, isCustomized, isReady } = useDefaultQueryPresets(
|
||||
workspaceId ?? null,
|
||||
@@ -193,12 +199,12 @@ function useDefaultQueryDrafts(workspaceId?: string) {
|
||||
JSON.stringify(current) === JSON.stringify(submittedIssue) ? submittedIssue : current,
|
||||
);
|
||||
setResetRequested(false);
|
||||
toast({ description: "Default queries saved", variant: "success" });
|
||||
toast({ description: t("github:defaultQueriesSaved"), variant: "success" });
|
||||
} catch {
|
||||
toast({ description: "Failed to save default queries", variant: "error" });
|
||||
toast({ description: t("github:failedToSaveDefaultQueries"), variant: "error" });
|
||||
throw new Error("Failed to save default queries");
|
||||
}
|
||||
}, [issueDraft, prDraft, reset, resetRequested, save, toast]);
|
||||
}, [issueDraft, prDraft, reset, resetRequested, save, t, toast]);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
setPrDraft(toStored(BUILTIN_PR_PRESETS));
|
||||
@@ -221,7 +227,7 @@ function useDefaultQueryDrafts(workspaceId?: string) {
|
||||
revision: JSON.stringify([prDraft, issueDraft]),
|
||||
isDirty: dirty,
|
||||
canSave: isReady,
|
||||
invalidReason: isReady ? undefined : "Default queries are still loading.",
|
||||
invalidReason: isReady ? undefined : t("github:defaultQueriesAreStillLoading"),
|
||||
save: handleSave,
|
||||
discard,
|
||||
});
|
||||
@@ -247,12 +253,13 @@ function useDefaultQueryDrafts(workspaceId?: string) {
|
||||
}
|
||||
|
||||
export function DefaultQueriesSection({ workspaceId }: { workspaceId?: string }) {
|
||||
const { t } = useTranslation();
|
||||
const drafts = useDefaultQueryDrafts(workspaceId);
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title="Default queries"
|
||||
description="Sidebar queries shown on /github for pull requests and issues."
|
||||
title={t("github:defaultQueries")}
|
||||
description={t("github:sidebarQueriesShownOnGithubFor", { route: "/github" })}
|
||||
action={
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
@@ -263,7 +270,7 @@ export function DefaultQueriesSection({ workspaceId }: { workspaceId?: string })
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<IconRefresh className="h-3.5 w-3.5 mr-1" />
|
||||
Reset
|
||||
{t("common:reset")}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
@@ -273,10 +280,10 @@ export function DefaultQueriesSection({ workspaceId }: { workspaceId?: string })
|
||||
<Tabs defaultValue="pr">
|
||||
<TabsList>
|
||||
<TabsTrigger value="pr" className="cursor-pointer">
|
||||
Pull requests
|
||||
{t("github:pullRequests")}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="issue" className="cursor-pointer">
|
||||
Issues
|
||||
{t("github:issues")}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="pr">
|
||||
@@ -284,7 +291,7 @@ export function DefaultQueriesSection({ workspaceId }: { workspaceId?: string })
|
||||
presets={drafts.prDraft}
|
||||
baseline={drafts.prBaseline}
|
||||
onChange={drafts.setPrDraft}
|
||||
addLabel="Add PR query"
|
||||
addLabel={t("github:addPrQuery")}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="issue">
|
||||
@@ -292,7 +299,7 @@ export function DefaultQueriesSection({ workspaceId }: { workspaceId?: string })
|
||||
presets={drafts.issueDraft}
|
||||
baseline={drafts.issueBaseline}
|
||||
onChange={drafts.setIssueDraft}
|
||||
addLabel="Add issue query"
|
||||
addLabel={t("github:addIssueQuery")}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
@@ -105,7 +105,7 @@ export function GitHubAppImportSecretFields({
|
||||
</Field>
|
||||
))}
|
||||
<div className="sm:col-span-2">
|
||||
<Field label={t("github:privateKeyPem")} error={errors.privateKey}>
|
||||
<Field label={t("github:privateKeyPem", { extension: ".pem" })} error={errors.privateKey}>
|
||||
<Textarea
|
||||
className="min-h-28 font-mono text-xs"
|
||||
value={values.privateKey}
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
computeEditorHeight,
|
||||
} from "@/components/settings/profile-edit/script-editor";
|
||||
import {
|
||||
ISSUE_WATCH_PLACEHOLDERS,
|
||||
issueWatchPlaceholders,
|
||||
DEFAULT_ISSUE_WATCH_PROMPT,
|
||||
} from "@/components/github/issue-watch-placeholders";
|
||||
import { RepoFilterSelector } from "@/components/github/repo-filter-selector";
|
||||
@@ -39,6 +39,13 @@ import type {
|
||||
UpdateIssueWatchRequest,
|
||||
CleanupPolicy,
|
||||
} from "@/lib/types/github";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { TFunction } from "i18next";
|
||||
import {
|
||||
ISSUE_CLEANUP_POLICY_OPTIONS,
|
||||
cleanupPolicyDescription,
|
||||
cleanupPolicyItems,
|
||||
} from "./watch-cleanup-policy";
|
||||
|
||||
type IssueWatchDialogProps = {
|
||||
open: boolean;
|
||||
@@ -69,24 +76,6 @@ type FormState = {
|
||||
|
||||
const DEFAULT_QUERY = "type:issue state:open";
|
||||
|
||||
const CLEANUP_POLICY_OPTIONS: Array<{ id: CleanupPolicy; label: string; description: string }> = [
|
||||
{
|
||||
id: "auto",
|
||||
label: "Auto (recommended)",
|
||||
description: "Delete closed-issue tasks unless you typed a message in them.",
|
||||
},
|
||||
{
|
||||
id: "always",
|
||||
label: "Always delete",
|
||||
description: "Delete on close even if you engaged with the task.",
|
||||
},
|
||||
{
|
||||
id: "never",
|
||||
label: "Never auto-delete",
|
||||
description: "Keep all tasks. Delete them manually from the task list.",
|
||||
},
|
||||
];
|
||||
|
||||
function makeDefaultForm(workspaceId: string): FormState {
|
||||
return {
|
||||
workspaceId,
|
||||
@@ -155,6 +144,8 @@ function SectionHeader({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
|
||||
function PlaceholdersHelp() {
|
||||
const { t } = useTranslation();
|
||||
const placeholders = useMemo(() => issueWatchPlaceholders(t), [t]);
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
@@ -162,9 +153,9 @@ function PlaceholdersHelp() {
|
||||
<IconInfoCircle className="h-3.5 w-3.5 text-muted-foreground/50 hover:text-muted-foreground cursor-help shrink-0" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs" align="start">
|
||||
<p className="text-xs font-medium mb-1">Available placeholders:</p>
|
||||
<p className="text-xs font-medium mb-1">{t("github:availablePlaceholders")}</p>
|
||||
<ul className="text-xs space-y-0.5">
|
||||
{ISSUE_WATCH_PLACEHOLDERS.map((p) => (
|
||||
{placeholders.map((p) => (
|
||||
<li key={p.key}>
|
||||
<code className="text-[10px] bg-white/15 px-1 rounded">{`{{${p.key}}}`}</code>{" "}
|
||||
<span className="opacity-70">{p.description}</span>
|
||||
@@ -229,9 +220,10 @@ function IssueFilterFields({
|
||||
onAllReposChange: (checked: boolean) => void;
|
||||
onSelectedReposChange: (repos: RepoFilter[]) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<SectionHeader>Filter</SectionHeader>
|
||||
<SectionHeader>{t("github:filter")}</SectionHeader>
|
||||
<RepoFilterSelector
|
||||
allRepos={form.allRepos}
|
||||
selectedRepos={form.selectedRepos}
|
||||
@@ -240,27 +232,27 @@ function IssueFilterFields({
|
||||
workspaceId={form.workspaceId}
|
||||
/>
|
||||
<div className="space-y-1.5">
|
||||
<Label>Labels (comma-separated)</Label>
|
||||
<Label>{t("github:labelsCommaSeparated")}</Label>
|
||||
<Input
|
||||
value={form.labels}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, labels: e.target.value }))}
|
||||
placeholder="e.g. bug, enhancement, priority:high"
|
||||
placeholder={t("github:eGBugEnhancementPriorityHigh")}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Only match issues with these labels. Leave empty for all issues.
|
||||
{t("github:onlyMatchIssuesWithTheseLabels")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label>Custom Query</Label>
|
||||
<Label>{t("github:customQuery")}</Label>
|
||||
<Textarea
|
||||
value={form.customQuery}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, customQuery: e.target.value }))}
|
||||
placeholder="e.g. type:issue state:open label:bug"
|
||||
placeholder={t("github:queryExample", { query: "type:issue state:open label:bug" })}
|
||||
rows={1}
|
||||
className="font-mono text-xs resize-y"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
GitHub search query. When set, overrides the label filter above.
|
||||
{t("github:githubSearchQueryWhenSetOverrides")}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
@@ -274,24 +266,26 @@ function IssueAutomationFields({
|
||||
form: FormState;
|
||||
setForm: React.Dispatch<React.SetStateAction<FormState>>;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { workflows, agentProfiles, allExecutorProfiles } = useWatchFormData(form.workspaceId);
|
||||
const { steps: workflowSteps, loading: stepsLoading } = useWorkflowSteps(form.workflowId);
|
||||
const placeholders = useMemo(() => issueWatchPlaceholders(t), [t]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SectionHeader>Automation</SectionHeader>
|
||||
<SectionHeader>{t("github:automation")}</SectionHeader>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<SelectField
|
||||
label="Workflow"
|
||||
description="The workflow to create tasks in."
|
||||
label={t("github:workflow")}
|
||||
description={t("github:theWorkflowToCreateTasksIn")}
|
||||
value={form.workflowId}
|
||||
onChange={(v) => setForm((prev) => ({ ...prev, workflowId: v, workflowStepId: "" }))}
|
||||
placeholder="Select workflow"
|
||||
placeholder={t("github:selectWorkflow")}
|
||||
items={workflows.map((w) => ({ id: w.id, label: w.name }))}
|
||||
/>
|
||||
<SelectField
|
||||
label="Workflow Step"
|
||||
description="Initial step for new tasks."
|
||||
label={t("github:workflowStep")}
|
||||
description={t("github:initialStepForNewTasks")}
|
||||
value={form.workflowStepId}
|
||||
onChange={(v) => setForm((prev) => ({ ...prev, workflowStepId: v }))}
|
||||
placeholder={stepPlaceholder(form.workflowId, stepsLoading, workflowSteps.length)}
|
||||
@@ -301,8 +295,8 @@ function IssueAutomationFields({
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<SelectField
|
||||
label="Agent Profile"
|
||||
description="Optional — falls back to step default."
|
||||
label={t("github:agentProfile")}
|
||||
description={t("github:optionalFallsBackToStepDefault")}
|
||||
value={form.agentProfileId || STEP_DEFAULT}
|
||||
onChange={(v) => setForm((prev) => ({ ...prev, agentProfileId: resolveProfileId(v) }))}
|
||||
placeholder={STEP_DEFAULT_LABEL}
|
||||
@@ -316,8 +310,8 @@ function IssueAutomationFields({
|
||||
]}
|
||||
/>
|
||||
<SelectField
|
||||
label="Executor Profile"
|
||||
description="Optional — falls back to step default."
|
||||
label={t("github:executorProfile")}
|
||||
description={t("github:optionalFallsBackToStepDefault")}
|
||||
value={form.executorProfileId || STEP_DEFAULT}
|
||||
onChange={(v) => setForm((prev) => ({ ...prev, executorProfileId: resolveProfileId(v) }))}
|
||||
placeholder={STEP_DEFAULT_LABEL}
|
||||
@@ -329,11 +323,13 @@ function IssueAutomationFields({
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Label>Task Prompt</Label>
|
||||
<Label>{t("github:taskPrompt")}</Label>
|
||||
<PlaceholdersHelp />
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
The prompt sent to the agent for each new issue. Type {"{{"} to insert placeholders.
|
||||
{/* `{{` is passed as a value so it never reaches the catalog, where
|
||||
i18next would read it as an interpolation opener. */}
|
||||
{t("github:issueWatchPromptHelp", { token: "{{" })}
|
||||
</p>
|
||||
<div className="rounded-md border border-border overflow-hidden">
|
||||
<ScriptEditor
|
||||
@@ -342,7 +338,7 @@ function IssueAutomationFields({
|
||||
language="markdown"
|
||||
height={computeEditorHeight(form.prompt)}
|
||||
lineNumbers="off"
|
||||
placeholders={ISSUE_WATCH_PLACEHOLDERS}
|
||||
placeholders={placeholders}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -357,14 +353,13 @@ function IssueSettingsFields({
|
||||
form: FormState;
|
||||
setForm: React.Dispatch<React.SetStateAction<FormState>>;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<SectionHeader>Settings</SectionHeader>
|
||||
<SectionHeader>{t("common:settings")}</SectionHeader>
|
||||
<div className="space-y-1.5">
|
||||
<Label>Poll Interval (seconds)</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
How often to check for new issues. Minimum 60s, maximum 3600s.
|
||||
</p>
|
||||
<Label>{t("github:pollIntervalSeconds")}</Label>
|
||||
<p className="text-xs text-muted-foreground">{t("github:howOftenToCheckForNew")}</p>
|
||||
<Input
|
||||
type="number"
|
||||
value={form.pollInterval}
|
||||
@@ -375,8 +370,8 @@ function IssueSettingsFields({
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label>Enabled</Label>
|
||||
<p className="text-xs text-muted-foreground">Pause or resume polling.</p>
|
||||
<Label>{t("github:enabled")}</Label>
|
||||
<p className="text-xs text-muted-foreground">{t("github:pauseOrResumePolling")}</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={form.enabled}
|
||||
@@ -385,14 +380,12 @@ function IssueSettingsFields({
|
||||
/>
|
||||
</div>
|
||||
<SelectField
|
||||
label="Cleanup behavior"
|
||||
description={
|
||||
CLEANUP_POLICY_OPTIONS.find((p) => p.id === form.cleanupPolicy)?.description ?? ""
|
||||
}
|
||||
label={t("github:cleanupBehavior")}
|
||||
description={cleanupPolicyDescription(t, ISSUE_CLEANUP_POLICY_OPTIONS, form.cleanupPolicy)}
|
||||
value={form.cleanupPolicy}
|
||||
onChange={(v) => setForm((prev) => ({ ...prev, cleanupPolicy: v as CleanupPolicy }))}
|
||||
placeholder="Auto"
|
||||
items={CLEANUP_POLICY_OPTIONS.map((p) => ({ id: p.id, label: p.label }))}
|
||||
placeholder={t("github:auto")}
|
||||
items={cleanupPolicyItems(t, ISSUE_CLEANUP_POLICY_OPTIONS)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -405,8 +398,9 @@ function parseLabels(labelsStr: string): string[] {
|
||||
.filter((l) => l.length > 0);
|
||||
}
|
||||
|
||||
function getSaveLabel(watch: IssueWatch | null | undefined): string {
|
||||
return watch ? "Update" : "Create";
|
||||
// Plain function, so `t` is threaded in — the guard never inspects return values.
|
||||
function getSaveLabel(t: TFunction, watch: IssueWatch | null | undefined): string {
|
||||
return watch ? t("github:update") : t("github:create");
|
||||
}
|
||||
|
||||
export function IssueWatchDialog({
|
||||
@@ -417,6 +411,7 @@ export function IssueWatchDialog({
|
||||
onCreate,
|
||||
onUpdate,
|
||||
}: IssueWatchDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const activeWorkspaceId = useAppStore((s) => s.workspaces.activeId);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [form, setForm] = useState<FormState>(() => makeDefaultForm(workspaceId ?? ""));
|
||||
@@ -483,10 +478,10 @@ export function IssueWatchDialog({
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="w-full max-w-full sm:w-[900px] sm:max-w-none max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{watch ? "Edit Issue Watch" : "Create Issue Watch"}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Automatically create tasks when new GitHub issues match your criteria.
|
||||
</DialogDescription>
|
||||
<DialogTitle>
|
||||
{watch ? t("github:editIssueWatch") : t("github:createIssueWatch")}
|
||||
</DialogTitle>
|
||||
<DialogDescription>{t("github:automaticallyCreateTasksWhenNewGithub")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<IssueWatchFormFields
|
||||
form={form}
|
||||
@@ -497,10 +492,10 @@ export function IssueWatchDialog({
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)} className="cursor-pointer">
|
||||
Cancel
|
||||
{t("common:cancel")}
|
||||
</Button>
|
||||
<Button onClick={handleSave} disabled={saving || !canSave} className="cursor-pointer">
|
||||
{saving ? "Saving..." : getSaveLabel(watch)}
|
||||
{saving ? t("github:saving") : getSaveLabel(t, watch)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
@@ -559,14 +554,15 @@ function WorkspacePicker({
|
||||
onChange: (v: string) => void;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const workspaces = useAppStore((s) => s.workspaces.items);
|
||||
return (
|
||||
<SelectField
|
||||
label="Workspace"
|
||||
description="Tasks created by this watcher land in the selected workspace."
|
||||
label={t("common:workspace")}
|
||||
description={t("github:tasksCreatedByThisWatcherLand")}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select workspace"
|
||||
placeholder={t("github:selectWorkspace")}
|
||||
items={workspaces.map((w) => ({ id: w.id, label: w.name }))}
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
@@ -1,50 +1,61 @@
|
||||
import type { TFunction } from "i18next";
|
||||
import type { ScriptPlaceholder } from "@/components/settings/profile-edit/script-editor-completions";
|
||||
|
||||
export const ISSUE_WATCH_PLACEHOLDERS: ScriptPlaceholder[] = [
|
||||
{
|
||||
key: "issue.link",
|
||||
description: "Issue URL",
|
||||
example: "https://github.com/org/repo/issues/42",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "issue.number",
|
||||
description: "Issue number",
|
||||
example: "42",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "issue.title",
|
||||
description: "Issue title",
|
||||
example: "Fix login page crash",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "issue.author",
|
||||
description: "Issue author username",
|
||||
example: "octocat",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "issue.repo",
|
||||
description: "Repository (owner/name)",
|
||||
example: "org/repo",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "issue.labels",
|
||||
description: "Comma-separated labels",
|
||||
example: "bug, priority:high",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "issue.body",
|
||||
description: "Issue body text",
|
||||
example: "When clicking login...",
|
||||
executor_types: [],
|
||||
},
|
||||
];
|
||||
/**
|
||||
* Placeholder tokens available in the issue-watch prompt. See
|
||||
* `review-watch-placeholders.ts` for why this is a function, not a const.
|
||||
*/
|
||||
export function issueWatchPlaceholders(t: TFunction): ScriptPlaceholder[] {
|
||||
return [
|
||||
{
|
||||
key: "issue.link",
|
||||
description: t("github:placeholderIssueUrl"),
|
||||
example: "https://github.com/org/repo/issues/42",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "issue.number",
|
||||
description: t("github:placeholderIssueNumber"),
|
||||
example: "42",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "issue.title",
|
||||
description: t("github:placeholderIssueTitle"),
|
||||
example: "Fix login page crash",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "issue.author",
|
||||
description: t("github:placeholderIssueAuthor"),
|
||||
example: "octocat",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "issue.repo",
|
||||
description: t("github:placeholderRepository"),
|
||||
example: "org/repo",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "issue.labels",
|
||||
description: t("github:placeholderCommaSeparatedLabels"),
|
||||
example: "bug, priority:high",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "issue.body",
|
||||
description: t("github:placeholderIssueBody"),
|
||||
example: "When clicking login...",
|
||||
executor_types: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeded into the prompt field, then persisted and sent to the agent verbatim.
|
||||
* Deliberately NOT translated — see `DEFAULT_REVIEW_WATCH_PROMPT`.
|
||||
*/
|
||||
export const DEFAULT_ISSUE_WATCH_PROMPT = `You have been assigned a GitHub issue to work on.
|
||||
|
||||
**Issue:** {{issue.link}}
|
||||
|
||||
@@ -14,6 +14,9 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip";
|
||||
import { useToast } from "@/components/toast-provider";
|
||||
import { useAppStore } from "@/components/state-provider";
|
||||
import type { IssueWatch } from "@/lib/types/github";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { TFunction } from "i18next";
|
||||
import { formatRelative } from "@/lib/i18n/formats";
|
||||
|
||||
type IssueWatchTableProps = {
|
||||
watches: IssueWatch[];
|
||||
@@ -27,19 +30,16 @@ type IssueWatchTableProps = {
|
||||
onToggleEnabled: (watch: IssueWatch) => void;
|
||||
};
|
||||
|
||||
function formatLastPolled(dateStr: string | null): string {
|
||||
if (!dateStr) return "Never";
|
||||
const diff = Date.now() - new Date(dateStr).getTime();
|
||||
const minutes = Math.floor(diff / 60000);
|
||||
if (minutes < 1) return "Just now";
|
||||
if (minutes < 60) return `${minutes}m ago`;
|
||||
const hours = Math.floor(minutes / 60);
|
||||
if (hours < 24) return `${hours}h ago`;
|
||||
return `${Math.floor(hours / 24)}d ago`;
|
||||
// `formatRelative` already implements these exact buckets (just now / Nm / Nh /
|
||||
// Nd ago) against the catalog, so the local copy is dropped rather than
|
||||
// translated — it was English-only and duplicated the shared helper.
|
||||
function formatLastPolled(t: TFunction, dateStr: string | null): string {
|
||||
if (!dateStr) return t("github:never");
|
||||
return formatRelative(dateStr);
|
||||
}
|
||||
|
||||
function formatRepoNames(watch: IssueWatch): string {
|
||||
if (watch.repos.length === 0) return "All repositories";
|
||||
function formatRepoNames(t: TFunction, watch: IssueWatch): string {
|
||||
if (watch.repos.length === 0) return t("github:allRepositories");
|
||||
return watch.repos
|
||||
.map((r) => (r.name === "" ? `${r.owner}/*` : `${r.owner}/${r.name}`))
|
||||
.join(", ");
|
||||
@@ -59,6 +59,7 @@ type WatchActionsProps = {
|
||||
};
|
||||
|
||||
function WatchActions({ watch, onToggleEnabled, onTrigger, onReset, onDelete }: WatchActionsProps) {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
return (
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
@@ -80,7 +81,7 @@ function WatchActions({ watch, onToggleEnabled, onTrigger, onReset, onDelete }:
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{watch.enabled ? "Pause" : "Enable"}</TooltipContent>
|
||||
<TooltipContent>{watch.enabled ? t("github:pause") : t("github:enable")}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -91,13 +92,13 @@ function WatchActions({ watch, onToggleEnabled, onTrigger, onReset, onDelete }:
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onTrigger(watch.id);
|
||||
toast({ description: "Checking for new issues..." });
|
||||
toast({ description: t("github:checkingForNewIssues") });
|
||||
}}
|
||||
>
|
||||
<IconRefresh className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Check now</TooltipContent>
|
||||
<TooltipContent>{t("github:checkNow")}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -106,7 +107,7 @@ function WatchActions({ watch, onToggleEnabled, onTrigger, onReset, onDelete }:
|
||||
size="sm"
|
||||
className="h-7 w-7 p-0 cursor-pointer"
|
||||
data-testid="watch-reset-button"
|
||||
aria-label="Reset watch"
|
||||
aria-label={t("github:resetWatch")}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onReset(watch.id);
|
||||
@@ -115,7 +116,7 @@ function WatchActions({ watch, onToggleEnabled, onTrigger, onReset, onDelete }:
|
||||
<IconRestore className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Reset</TooltipContent>
|
||||
<TooltipContent>{t("common:reset")}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -131,7 +132,7 @@ function WatchActions({ watch, onToggleEnabled, onTrigger, onReset, onDelete }:
|
||||
<IconTrash className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Delete</TooltipContent>
|
||||
<TooltipContent>{t("github:delete")}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
@@ -146,13 +147,14 @@ export function IssueWatchTable({
|
||||
onReset,
|
||||
onToggleEnabled,
|
||||
}: IssueWatchTableProps) {
|
||||
const { t } = useTranslation();
|
||||
const workspaces = useAppStore((s) => s.workspaces.items);
|
||||
const workspaceName = (id: string) => workspaces.find((w) => w.id === id)?.name ?? id;
|
||||
|
||||
if (watches.length === 0) {
|
||||
return (
|
||||
<p className="text-sm text-muted-foreground py-4 text-center">
|
||||
No issue watches configured. Create one to start monitoring GitHub issues.
|
||||
{t("github:noIssueWatchesConfiguredCreateOne")}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -161,13 +163,13 @@ export function IssueWatchTable({
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{showWorkspace && <TableHead>Workspace</TableHead>}
|
||||
<TableHead>Repository</TableHead>
|
||||
<TableHead>Labels</TableHead>
|
||||
<TableHead>Interval</TableHead>
|
||||
<TableHead>Last Polled</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
{showWorkspace && <TableHead>{t("common:workspace")}</TableHead>}
|
||||
<TableHead>{t("github:repository")}</TableHead>
|
||||
<TableHead>{t("github:labels")}</TableHead>
|
||||
<TableHead>{t("github:interval")}</TableHead>
|
||||
<TableHead>{t("github:lastPolled")}</TableHead>
|
||||
<TableHead>{t("common:status")}</TableHead>
|
||||
<TableHead className="text-right">{t("github:actions")}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -178,7 +180,7 @@ export function IssueWatchTable({
|
||||
{workspaceName(watch.workspace_id)}
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell className="font-medium">{formatRepoNames(watch)}</TableCell>
|
||||
<TableCell className="font-medium">{formatRepoNames(t, watch)}</TableCell>
|
||||
<TableCell className="text-xs text-muted-foreground">
|
||||
{formatLabels(watch) || "—"}
|
||||
</TableCell>
|
||||
@@ -186,11 +188,11 @@ export function IssueWatchTable({
|
||||
{Math.round(watch.poll_interval_seconds / 60)}m
|
||||
</TableCell>
|
||||
<TableCell className="text-xs text-muted-foreground">
|
||||
{formatLastPolled(watch.last_polled_at)}
|
||||
{formatLastPolled(t, watch.last_polled_at)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={watch.enabled ? "default" : "secondary"} className="text-xs">
|
||||
{watch.enabled ? "Active" : "Paused"}
|
||||
{watch.enabled ? t("github:active") : t("github:paused")}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
|
||||
@@ -16,15 +16,22 @@ import type {
|
||||
} from "@/lib/types/github";
|
||||
import type { TaskPreset } from "./quick-task-launcher";
|
||||
|
||||
export const PRESET_ICON_CHOICES: { key: GitHubActionPresetIcon; icon: Icon; label: string }[] = [
|
||||
{ key: "eye", icon: IconEye, label: "Eye" },
|
||||
{ key: "message", icon: IconMessageDots, label: "Message" },
|
||||
{ key: "tool", icon: IconTool, label: "Tool" },
|
||||
{ key: "code", icon: IconCode, label: "Code" },
|
||||
{ key: "search", icon: IconSearch, label: "Search" },
|
||||
{ key: "bug", icon: IconBug, label: "Bug" },
|
||||
{ key: "sparkle", icon: IconSparkles, label: "Sparkle" },
|
||||
{ key: "check", icon: IconChecks, label: "Check" },
|
||||
// `key` is the persisted GitHubActionPresetIcon enum; only the label is copy, so
|
||||
// it travels as a catalog key resolved at render (module-scope `t()` would
|
||||
// freeze at the boot locale — see docs/i18n.md).
|
||||
export const PRESET_ICON_CHOICES: {
|
||||
key: GitHubActionPresetIcon;
|
||||
icon: Icon;
|
||||
labelKey: string;
|
||||
}[] = [
|
||||
{ key: "eye", icon: IconEye, labelKey: "github:presetIconEye" },
|
||||
{ key: "message", icon: IconMessageDots, labelKey: "github:presetIconMessage" },
|
||||
{ key: "tool", icon: IconTool, labelKey: "github:presetIconTool" },
|
||||
{ key: "code", icon: IconCode, labelKey: "github:presetIconCode" },
|
||||
{ key: "search", icon: IconSearch, labelKey: "github:presetIconSearch" },
|
||||
{ key: "bug", icon: IconBug, labelKey: "github:presetIconBug" },
|
||||
{ key: "sparkle", icon: IconSparkles, labelKey: "github:presetIconSparkle" },
|
||||
{ key: "check", icon: IconChecks, labelKey: "github:presetIconCheck" },
|
||||
];
|
||||
|
||||
const ICON_BY_KEY: Record<string, Icon> = Object.fromEntries(
|
||||
@@ -60,6 +67,13 @@ export function toTaskPreset(stored: GitHubActionPreset): TaskPreset {
|
||||
};
|
||||
}
|
||||
|
||||
// NOTE: `label`, `hint` and `prompt_template` below are NOT translated, and must
|
||||
// not be. These records seed the editable draft in action-presets-section.tsx
|
||||
// (`setPrDraft(DEFAULT_PR_PRESETS)`) and are persisted to workspace settings as
|
||||
// `GitHubActionPreset`, so translating them would write locale-dependent values
|
||||
// into a user's saved presets and leave them there after a locale switch.
|
||||
// `prompt_template` is additionally sent to the agent verbatim. Localizing these
|
||||
// needs a key/persisted-value split, the same open item as PR_PRESETS.
|
||||
export const DEFAULT_PR_PRESETS: GitHubActionPreset[] = [
|
||||
{
|
||||
id: "review",
|
||||
|
||||
@@ -19,6 +19,14 @@ export type PresetOption = {
|
||||
icon: Icon;
|
||||
};
|
||||
|
||||
// NOTE: `label` here is NOT translated, and must not be. `toStored()` in
|
||||
// use-default-query-presets.ts copies it verbatim into `StoredQueryPreset`,
|
||||
// which is persisted to `github_default_query_presets` in user and workspace
|
||||
// settings and is user-editable in Settings → Integrations → GitHub. Translating
|
||||
// it in place would write locale-dependent values into saved settings and leave
|
||||
// them there after a locale switch. Localizing these needs a key/persisted-value
|
||||
// split in this module, the same open item as the built-in layout profile names.
|
||||
// `value`, `filter` and `group` are wire values and a GitHub search query.
|
||||
export const PR_PRESETS: PresetOption[] = [
|
||||
{
|
||||
value: "review_requested",
|
||||
|
||||
@@ -27,6 +27,7 @@ import type {
|
||||
GitHubRepoInfo,
|
||||
GitHubWorkspaceSettings,
|
||||
} from "@/lib/types/github";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type RepoFilterSelectorProps = {
|
||||
allRepos: boolean;
|
||||
@@ -201,11 +202,12 @@ function OrgBadges({
|
||||
disabled: boolean;
|
||||
onToggleOrg: (login: string) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<IconLoader2 className="h-3 w-3 animate-spin" />
|
||||
Loading organizations...
|
||||
{t("github:loadingOrganizations")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -244,6 +246,7 @@ function RepoSearchCombobox({
|
||||
scope: GitHubWorkspaceSettings | null;
|
||||
onAdd: (owner: string, name: string) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [value, setValue] = useState("");
|
||||
|
||||
@@ -280,7 +283,7 @@ function RepoSearchCombobox({
|
||||
className="cursor-pointer text-xs gap-1"
|
||||
>
|
||||
<IconPlus className="h-3 w-3" />
|
||||
Add repository
|
||||
{t("github:addRepository")}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-72 p-0" align="start" portal={false}>
|
||||
@@ -290,14 +293,16 @@ function RepoSearchCombobox({
|
||||
{searchLoading && (
|
||||
<div className="flex items-center gap-2 px-3 py-3 text-xs text-muted-foreground">
|
||||
<IconLoader2 className="h-3 w-3 animate-spin" />
|
||||
Searching...
|
||||
{t("github:searching")}
|
||||
</div>
|
||||
)}
|
||||
{!searchLoading && org && filteredResults.length === 0 && (
|
||||
<CommandEmpty>No repos found for "{org}"</CommandEmpty>
|
||||
<CommandEmpty>{t("github:noReposFoundFor", { org })}</CommandEmpty>
|
||||
)}
|
||||
{!searchLoading && !org && value.length > 0 && (
|
||||
<CommandEmpty>Type owner/repo to search</CommandEmpty>
|
||||
<CommandEmpty>
|
||||
{t("github:typeOwnerRepoToSearch", { format: "owner/repo" })}
|
||||
</CommandEmpty>
|
||||
)}
|
||||
{filteredResults.length > 0 && (
|
||||
<CommandGroup>
|
||||
@@ -310,7 +315,9 @@ function RepoSearchCombobox({
|
||||
>
|
||||
{repo.full_name}
|
||||
{repo.private && (
|
||||
<span className="ml-auto text-xs text-muted-foreground">private</span>
|
||||
<span className="ml-auto text-xs text-muted-foreground">
|
||||
{t("github:private")}
|
||||
</span>
|
||||
)}
|
||||
</CommandItem>
|
||||
))}
|
||||
@@ -330,6 +337,7 @@ export function RepoFilterSelector({
|
||||
onSelectedReposChange,
|
||||
workspaceId,
|
||||
}: RepoFilterSelectorProps) {
|
||||
const { t } = useTranslation();
|
||||
const { orgs, loading: orgsLoading } = useGitHubOrgs(workspaceId);
|
||||
const scope = useWorkspaceRepoScope(workspaceId);
|
||||
const scopedOrgs = useMemo(
|
||||
@@ -372,9 +380,9 @@ export function RepoFilterSelector({
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label>Repositories</Label>
|
||||
<Label>{t("github:repositories")}</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Which allowed GitHub repositories to monitor for this watch.
|
||||
{t("github:whichAllowedGithubRepositoriesToMonitor")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -386,7 +394,7 @@ export function RepoFilterSelector({
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
<Label htmlFor="all-repos-toggle" className="font-normal cursor-pointer">
|
||||
All repositories allowed by this workspace
|
||||
{t("github:allRepositoriesAllowedByThisWorkspace")}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
@@ -394,7 +402,9 @@ export function RepoFilterSelector({
|
||||
<>
|
||||
{showOrgBadges && (
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs text-muted-foreground font-normal">Organizations</Label>
|
||||
<Label className="text-xs text-muted-foreground font-normal">
|
||||
{t("github:organizations")}
|
||||
</Label>
|
||||
<OrgBadges
|
||||
orgs={scopedOrgs}
|
||||
loading={orgsLoading}
|
||||
|
||||
@@ -37,6 +37,13 @@ import type {
|
||||
UpdateReviewWatchRequest,
|
||||
CleanupPolicy,
|
||||
} from "@/lib/types/github";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { TFunction } from "i18next";
|
||||
import {
|
||||
REVIEW_CLEANUP_POLICY_OPTIONS,
|
||||
cleanupPolicyDescription,
|
||||
cleanupPolicyItems,
|
||||
} from "./watch-cleanup-policy";
|
||||
|
||||
type ReviewWatchDialogProps = {
|
||||
open: boolean;
|
||||
@@ -104,26 +111,6 @@ function formStateFromWatch(watch: ReviewWatch): FormState {
|
||||
};
|
||||
}
|
||||
|
||||
const CLEANUP_POLICY_OPTIONS: Array<{ id: CleanupPolicy; label: string; description: string }> = [
|
||||
{
|
||||
id: "auto",
|
||||
label: "Auto (recommended)",
|
||||
description:
|
||||
"Retain merged/closed PR tasks when they have user engagement or enabled PR lifecycle prompts; otherwise delete them.",
|
||||
},
|
||||
{
|
||||
id: "always",
|
||||
label: "Always delete",
|
||||
description:
|
||||
"Always delete overrides retention: delete on merge/close even if the task has user engagement or enabled PR lifecycle prompts.",
|
||||
},
|
||||
{
|
||||
id: "never",
|
||||
label: "Never auto-delete",
|
||||
description: "Keep all tasks. Delete them manually from the task list.",
|
||||
},
|
||||
];
|
||||
|
||||
// --- Generic select field with description ---
|
||||
|
||||
type SelectFieldItem = { id: string; label: string; icon?: React.ReactNode };
|
||||
@@ -183,14 +170,15 @@ function WorkspacePicker({
|
||||
onChange: (v: string) => void;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const workspaces = useAppStore((s) => s.workspaces.items);
|
||||
return (
|
||||
<SelectField
|
||||
label="Workspace"
|
||||
description="Tasks created by this watcher land in the selected workspace."
|
||||
label={t("common:workspace")}
|
||||
description={t("github:tasksCreatedByThisWatcherLand")}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select workspace"
|
||||
placeholder={t("github:selectWorkspace")}
|
||||
items={workspaces.map((w) => ({ id: w.id, label: w.name }))}
|
||||
disabled={disabled}
|
||||
/>
|
||||
@@ -240,6 +228,7 @@ function QueryField({
|
||||
form: FormState;
|
||||
setForm: React.Dispatch<React.SetStateAction<FormState>>;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const current = form.customQuery.trim();
|
||||
const isMeAndTeams = current === QUERY_TEMPLATES.meAndTeams;
|
||||
const isMe = current === QUERY_TEMPLATES.me;
|
||||
@@ -247,7 +236,7 @@ function QueryField({
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label>Search Query</Label>
|
||||
<Label>{t("github:searchQuery")}</Label>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
@@ -261,11 +250,11 @@ function QueryField({
|
||||
setForm((prev) => ({ ...prev, customQuery: QUERY_TEMPLATES.meAndTeams }))
|
||||
}
|
||||
>
|
||||
Me & my teams
|
||||
{t("github:meMyTeams")}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs text-xs">
|
||||
PRs where you or any of your teams are requested as reviewers
|
||||
{t("github:prsWhereYouOrAnyOf")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
@@ -279,11 +268,11 @@ function QueryField({
|
||||
className="h-6 px-2 text-xs cursor-pointer"
|
||||
onClick={() => setForm((prev) => ({ ...prev, customQuery: QUERY_TEMPLATES.me }))}
|
||||
>
|
||||
Me
|
||||
{t("github:me")}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs text-xs">
|
||||
Only PRs where you are explicitly requested as a reviewer (not via team membership)
|
||||
{t("github:onlyPrsWhereYouAreExplicitly")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
@@ -292,12 +281,12 @@ function QueryField({
|
||||
<Textarea
|
||||
value={form.customQuery}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, customQuery: e.target.value }))}
|
||||
placeholder="e.g. type:pr state:open review-requested:@me"
|
||||
placeholder={t("github:queryExample", { query: "type:pr state:open review-requested:@me" })}
|
||||
rows={1}
|
||||
className="font-mono text-xs resize-y"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
GitHub search query. Supports full GitHub search syntax for maximum flexibility.
|
||||
{t("github:githubSearchQuerySupportsFullGithub")}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
@@ -316,6 +305,7 @@ function WatchFormFields({
|
||||
onAllReposChange: (checked: boolean) => void;
|
||||
onSelectedReposChange: (repos: RepoFilter[]) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { workflows, agentProfiles, allExecutorProfiles } = useWatchFormData(form.workspaceId);
|
||||
const { steps: workflowSteps, loading: stepsLoading } = useWorkflowSteps(form.workflowId);
|
||||
|
||||
@@ -345,7 +335,7 @@ function WatchFormFields({
|
||||
}
|
||||
disabled={workspaceLocked}
|
||||
/>
|
||||
<SectionHeader>Filter</SectionHeader>
|
||||
<SectionHeader>{t("github:filter")}</SectionHeader>
|
||||
<RepoFilterSelector
|
||||
allRepos={form.allRepos}
|
||||
selectedRepos={form.selectedRepos}
|
||||
@@ -354,7 +344,7 @@ function WatchFormFields({
|
||||
workspaceId={form.workspaceId}
|
||||
/>
|
||||
<QueryField form={form} setForm={setForm} />
|
||||
<SectionHeader>Automation</SectionHeader>
|
||||
<SectionHeader>{t("github:automation")}</SectionHeader>
|
||||
<WorkflowFields
|
||||
form={form}
|
||||
setForm={setForm}
|
||||
@@ -373,7 +363,7 @@ function WatchFormFields({
|
||||
value={form.prompt}
|
||||
onChange={(v) => setForm((prev) => ({ ...prev, prompt: v }))}
|
||||
/>
|
||||
<SectionHeader>Settings</SectionHeader>
|
||||
<SectionHeader>{t("common:settings")}</SectionHeader>
|
||||
<SettingsFields form={form} setForm={setForm} />
|
||||
</div>
|
||||
);
|
||||
@@ -394,19 +384,20 @@ function WorkflowFields({
|
||||
stepsLoading: boolean;
|
||||
onWorkflowChange: (v: string) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<SelectField
|
||||
label="Workflow"
|
||||
description="The workflow to create tasks in."
|
||||
label={t("github:workflow")}
|
||||
description={t("github:theWorkflowToCreateTasksIn")}
|
||||
value={form.workflowId}
|
||||
onChange={onWorkflowChange}
|
||||
placeholder="Select workflow"
|
||||
placeholder={t("github:selectWorkflow")}
|
||||
items={workflows.map((w) => ({ id: w.id, label: w.name }))}
|
||||
/>
|
||||
<SelectField
|
||||
label="Workflow Step"
|
||||
description="Initial step for new tasks. Auto-start is set on the step."
|
||||
label={t("github:workflowStep")}
|
||||
description={t("github:initialStepForNewTasksAuto")}
|
||||
value={form.workflowStepId}
|
||||
onChange={(v) => setForm((prev) => ({ ...prev, workflowStepId: v }))}
|
||||
placeholder={stepPlaceholder(form.workflowId, stepsLoading, workflowSteps.length)}
|
||||
@@ -441,11 +432,12 @@ function ProfileFields({
|
||||
agentProfiles: Array<{ id: string; label: string; cli_passthrough?: boolean }>;
|
||||
executorProfiles: Array<{ id: string; name: string }>;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<SelectField
|
||||
label="Agent Profile"
|
||||
description="Optional — falls back to step default."
|
||||
label={t("github:agentProfile")}
|
||||
description={t("github:optionalFallsBackToStepDefault")}
|
||||
value={form.agentProfileId || STEP_DEFAULT}
|
||||
onChange={(v) => setForm((prev) => ({ ...prev, agentProfileId: resolveProfileId(v) }))}
|
||||
placeholder={STEP_DEFAULT_LABEL}
|
||||
@@ -460,11 +452,15 @@ function ProfileFields({
|
||||
/>
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Label>Executor Profile</Label>
|
||||
<HelpTip text="The repository will be automatically cloned to ~/.kandev/repos/<owner>/<repo> if it is not already present in the workspace." />
|
||||
<Label>{t("github:executorProfile")}</Label>
|
||||
<HelpTip
|
||||
text={t("github:theRepositoryWillBeAutomaticallyCloned", {
|
||||
path: "~/.kandev/repos/<owner>/<repo>",
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Optional — falls back to step default. The executor environment where the agent will run.
|
||||
{t("github:optionalFallsBackToStepDefault2")}
|
||||
</p>
|
||||
<Select
|
||||
value={form.executorProfileId || STEP_DEFAULT}
|
||||
@@ -496,13 +492,12 @@ function SettingsFields({
|
||||
form: FormState;
|
||||
setForm: React.Dispatch<React.SetStateAction<FormState>>;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-1.5">
|
||||
<Label>Poll Interval (seconds)</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
How often to check for new PRs. Minimum 60s, maximum 3600s.
|
||||
</p>
|
||||
<Label>{t("github:pollIntervalSeconds")}</Label>
|
||||
<p className="text-xs text-muted-foreground">{t("github:howOftenToCheckForNew2")}</p>
|
||||
<Input
|
||||
type="number"
|
||||
value={form.pollInterval}
|
||||
@@ -513,8 +508,8 @@ function SettingsFields({
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label>Enabled</Label>
|
||||
<p className="text-xs text-muted-foreground">Pause or resume polling.</p>
|
||||
<Label>{t("github:enabled")}</Label>
|
||||
<p className="text-xs text-muted-foreground">{t("github:pauseOrResumePolling")}</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={form.enabled}
|
||||
@@ -523,22 +518,22 @@ function SettingsFields({
|
||||
/>
|
||||
</div>
|
||||
<SelectField
|
||||
label="Cleanup behavior"
|
||||
description={
|
||||
CLEANUP_POLICY_OPTIONS.find((p) => p.id === form.cleanupPolicy)?.description ?? ""
|
||||
}
|
||||
label={t("github:cleanupBehavior")}
|
||||
description={cleanupPolicyDescription(t, REVIEW_CLEANUP_POLICY_OPTIONS, form.cleanupPolicy)}
|
||||
value={form.cleanupPolicy}
|
||||
onChange={(v) => setForm((prev) => ({ ...prev, cleanupPolicy: v as CleanupPolicy }))}
|
||||
placeholder="Auto"
|
||||
items={CLEANUP_POLICY_OPTIONS.map((p) => ({ id: p.id, label: p.label }))}
|
||||
placeholder={t("github:auto")}
|
||||
items={cleanupPolicyItems(t, REVIEW_CLEANUP_POLICY_OPTIONS)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function getSaveButtonLabel(saving: boolean, isEditing: boolean): string {
|
||||
if (saving) return "Saving...";
|
||||
return isEditing ? "Update" : "Create";
|
||||
// Plain function, so `t` is threaded in — `mode: "jsx-only"` never inspects a
|
||||
// function's return value, which is why these three survived the sweep.
|
||||
function getSaveButtonLabel(t: TFunction, saving: boolean, isEditing: boolean): string {
|
||||
if (saving) return t("github:saving");
|
||||
return isEditing ? t("github:update") : t("github:create");
|
||||
}
|
||||
|
||||
export function ReviewWatchDialog({
|
||||
@@ -549,6 +544,7 @@ export function ReviewWatchDialog({
|
||||
onCreate,
|
||||
onUpdate,
|
||||
}: ReviewWatchDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const activeWorkspaceId = useAppStore((s) => s.workspaces.activeId);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [form, setForm] = useState<FormState>(() => makeDefaultForm(workspaceId ?? ""));
|
||||
@@ -615,10 +611,10 @@ export function ReviewWatchDialog({
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="w-full max-w-full sm:w-[900px] sm:max-w-none max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{watch ? "Edit Review Watch" : "Create Review Watch"}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Automatically create tasks when new pull requests need your review.
|
||||
</DialogDescription>
|
||||
<DialogTitle>
|
||||
{watch ? t("github:editReviewWatch") : t("github:createReviewWatch")}
|
||||
</DialogTitle>
|
||||
<DialogDescription>{t("github:automaticallyCreateTasksWhenNewPull")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<WatchFormFields
|
||||
form={form}
|
||||
@@ -629,10 +625,10 @@ export function ReviewWatchDialog({
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)} className="cursor-pointer">
|
||||
Cancel
|
||||
{t("common:cancel")}
|
||||
</Button>
|
||||
<Button onClick={handleSave} disabled={saving || !canSave} className="cursor-pointer">
|
||||
{getSaveButtonLabel(saving, !!watch)}
|
||||
{getSaveButtonLabel(t, saving, !!watch)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,50 +1,66 @@
|
||||
import type { TFunction } from "i18next";
|
||||
import type { ScriptPlaceholder } from "@/components/settings/profile-edit/script-editor-completions";
|
||||
|
||||
export const REVIEW_WATCH_PLACEHOLDERS: ScriptPlaceholder[] = [
|
||||
{
|
||||
key: "pr.link",
|
||||
description: "PR URL",
|
||||
example: "https://github.com/org/repo/pull/123",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "pr.number",
|
||||
description: "PR number",
|
||||
example: "123",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "pr.title",
|
||||
description: "PR title",
|
||||
example: "Add user authentication",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "pr.author",
|
||||
description: "PR author username",
|
||||
example: "octocat",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "pr.repo",
|
||||
description: "Repository (owner/name)",
|
||||
example: "org/repo",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "pr.branch",
|
||||
description: "Source branch",
|
||||
example: "feature/auth",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "pr.base_branch",
|
||||
description: "Target branch",
|
||||
example: "main",
|
||||
executor_types: [],
|
||||
},
|
||||
];
|
||||
/**
|
||||
* Placeholder tokens available in the review-watch prompt.
|
||||
*
|
||||
* A function rather than a module-scope const because `description` is copy: a
|
||||
* `t()` call at module scope would freeze at the boot locale (see docs/i18n.md).
|
||||
* `key` is the substitution token the backend expands and `example` is sample
|
||||
* GitHub data — neither is translated.
|
||||
*/
|
||||
export function reviewWatchPlaceholders(t: TFunction): ScriptPlaceholder[] {
|
||||
return [
|
||||
{
|
||||
key: "pr.link",
|
||||
description: t("github:placeholderPrUrl"),
|
||||
example: "https://github.com/org/repo/pull/123",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "pr.number",
|
||||
description: t("github:placeholderPrNumber"),
|
||||
example: "123",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "pr.title",
|
||||
description: t("github:placeholderPrTitle"),
|
||||
example: "Add user authentication",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "pr.author",
|
||||
description: t("github:placeholderPrAuthor"),
|
||||
example: "octocat",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "pr.repo",
|
||||
description: t("github:placeholderRepository"),
|
||||
example: "org/repo",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "pr.branch",
|
||||
description: t("github:placeholderSourceBranch"),
|
||||
example: "feature/auth",
|
||||
executor_types: [],
|
||||
},
|
||||
{
|
||||
key: "pr.base_branch",
|
||||
description: t("github:placeholderTargetBranch"),
|
||||
example: "main",
|
||||
executor_types: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeded into the prompt field, then persisted and sent to the agent verbatim.
|
||||
* Deliberately NOT translated: it is agent instructions and stored data, so
|
||||
* translating it would write locale-dependent text into a user's saved watch.
|
||||
*/
|
||||
export const DEFAULT_REVIEW_WATCH_PROMPT = `Review Pull Request #{{pr.number}}: {{pr.title}}
|
||||
Repository: {{pr.repo}}
|
||||
PR: {{pr.link}}
|
||||
|
||||
@@ -7,14 +7,25 @@ import {
|
||||
ScriptEditor,
|
||||
computeEditorHeight,
|
||||
} from "@/components/settings/profile-edit/script-editor";
|
||||
import { REVIEW_WATCH_PLACEHOLDERS } from "@/components/github/review-watch-placeholders";
|
||||
import { reviewWatchPlaceholders } from "@/components/github/review-watch-placeholders";
|
||||
import { useCustomPrompts } from "@/hooks/domains/settings/use-custom-prompts";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { useMemo } from "react";
|
||||
|
||||
// Example prompts shown verbatim in the help text. Interpolated as VALUES rather
|
||||
// than written into the catalog: they contain a literal `{{task_prompt}}` token,
|
||||
// which i18next would read as an interpolation and substitute away, and the
|
||||
// pseudo-locale would transliterate the URL.
|
||||
const STEP_PROMPT_EXAMPLE = "Analyze the task: {{task_prompt}}";
|
||||
const FINAL_PROMPT_EXAMPLE = "Analyze the task: Pull Request ready for review: https://...";
|
||||
|
||||
// Pulled out of review-watch-dialog.tsx to keep that file under the 600-line
|
||||
// linter cap; the prompt editor + its placeholder help bubble are co-owned by
|
||||
// this single screen and aren't reused elsewhere.
|
||||
|
||||
function PlaceholdersHelp() {
|
||||
const { t } = useTranslation();
|
||||
const placeholders = useMemo(() => reviewWatchPlaceholders(t), [t]);
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
@@ -22,9 +33,9 @@ function PlaceholdersHelp() {
|
||||
<IconInfoCircle className="h-3.5 w-3.5 text-muted-foreground/50 hover:text-muted-foreground cursor-help shrink-0" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs" align="start">
|
||||
<p className="text-xs font-medium mb-1">Available placeholders:</p>
|
||||
<p className="text-xs font-medium mb-1">{t("github:availablePlaceholders")}</p>
|
||||
<ul className="text-xs space-y-0.5">
|
||||
{REVIEW_WATCH_PLACEHOLDERS.map((p) => (
|
||||
{placeholders.map((p) => (
|
||||
<li key={p.key}>
|
||||
<code className="text-[10px] bg-white/15 px-1 rounded">{`{{${p.key}}}`}</code>{" "}
|
||||
<span className="opacity-70">{p.description}</span>
|
||||
@@ -44,17 +55,19 @@ export function ReviewWatchPromptField({
|
||||
value: string;
|
||||
onChange: (v: string) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { prompts } = useCustomPrompts();
|
||||
const placeholders = useMemo(() => reviewWatchPlaceholders(t), [t]);
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Label>Task Prompt</Label>
|
||||
<Label>{t("github:taskPrompt")}</Label>
|
||||
<PlaceholdersHelp />
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
The prompt sent to the agent for each new PR. Type {"{{"} to insert placeholders, or {"@"}{" "}
|
||||
to reference a saved prompt by name — it expands the same way it does in workflow step
|
||||
prompts, since this prompt becomes the task description passed through that same assembly.
|
||||
{/* The `{{` and `@` sigils are passed as values so they never reach the
|
||||
catalog, where i18next would read `{{` as an interpolation opener. */}
|
||||
{t("github:reviewWatchPromptHelp", { token: "{{", mention: "@" })}
|
||||
</p>
|
||||
<div className="rounded-md border border-border overflow-hidden">
|
||||
<ScriptEditor
|
||||
@@ -63,19 +76,20 @@ export function ReviewWatchPromptField({
|
||||
language="markdown"
|
||||
height={computeEditorHeight(value)}
|
||||
lineNumbers="off"
|
||||
placeholders={REVIEW_WATCH_PLACEHOLDERS}
|
||||
placeholders={placeholders}
|
||||
mentionPrompts={prompts}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground/70">
|
||||
The workflow step prompt wraps this prompt. For example, if the step prompt is{" "}
|
||||
<code className="text-[10px] bg-muted px-1 rounded">
|
||||
{"Analyze the task: {{task_prompt}}"}
|
||||
</code>
|
||||
, the final prompt becomes{" "}
|
||||
<code className="text-[10px] bg-muted px-1 rounded">
|
||||
{"Analyze the task: Pull Request ready for review: https://..."}
|
||||
</code>
|
||||
<Trans
|
||||
i18nKey="github:workflowStepPromptWrapHelp"
|
||||
values={{ stepPrompt: STEP_PROMPT_EXAMPLE, finalPrompt: FINAL_PROMPT_EXAMPLE }}
|
||||
>
|
||||
The workflow step prompt wraps this prompt. For example, if the step prompt is{" "}
|
||||
<code className="text-[10px] bg-muted px-1 rounded">{"{{stepPrompt}}"}</code>, the final
|
||||
prompt becomes{" "}
|
||||
<code className="text-[10px] bg-muted px-1 rounded">{"{{finalPrompt}}"}</code>
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,6 +14,9 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip";
|
||||
import { useToast } from "@/components/toast-provider";
|
||||
import { useAppStore } from "@/components/state-provider";
|
||||
import type { ReviewWatch } from "@/lib/types/github";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { TFunction } from "i18next";
|
||||
import { formatRelative } from "@/lib/i18n/formats";
|
||||
|
||||
type ReviewWatchTableProps = {
|
||||
watches: ReviewWatch[];
|
||||
@@ -27,19 +30,16 @@ type ReviewWatchTableProps = {
|
||||
onToggleEnabled: (watch: ReviewWatch) => void;
|
||||
};
|
||||
|
||||
function formatLastPolled(dateStr: string | null): string {
|
||||
if (!dateStr) return "Never";
|
||||
const diff = Date.now() - new Date(dateStr).getTime();
|
||||
const minutes = Math.floor(diff / 60000);
|
||||
if (minutes < 1) return "Just now";
|
||||
if (minutes < 60) return `${minutes}m ago`;
|
||||
const hours = Math.floor(minutes / 60);
|
||||
if (hours < 24) return `${hours}h ago`;
|
||||
return `${Math.floor(hours / 24)}d ago`;
|
||||
// `formatRelative` already implements these exact buckets (just now / Nm / Nh /
|
||||
// Nd ago) against the catalog, so the local copy is dropped rather than
|
||||
// translated — it was English-only and duplicated the shared helper.
|
||||
function formatLastPolled(t: TFunction, dateStr: string | null): string {
|
||||
if (!dateStr) return t("github:never");
|
||||
return formatRelative(dateStr);
|
||||
}
|
||||
|
||||
function formatRepoNames(watch: ReviewWatch): string {
|
||||
if (watch.repos.length === 0) return "All repositories";
|
||||
function formatRepoNames(t: TFunction, watch: ReviewWatch): string {
|
||||
if (watch.repos.length === 0) return t("github:allRepositories");
|
||||
return watch.repos
|
||||
.map((r) => (r.name === "" ? `${r.owner}/*` : `${r.owner}/${r.name}`))
|
||||
.join(", ");
|
||||
@@ -54,6 +54,7 @@ type WatchActionsProps = {
|
||||
};
|
||||
|
||||
function WatchActions({ watch, onToggleEnabled, onTrigger, onReset, onDelete }: WatchActionsProps) {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
return (
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
@@ -75,7 +76,7 @@ function WatchActions({ watch, onToggleEnabled, onTrigger, onReset, onDelete }:
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{watch.enabled ? "Pause" : "Enable"}</TooltipContent>
|
||||
<TooltipContent>{watch.enabled ? t("github:pause") : t("github:enable")}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -86,13 +87,13 @@ function WatchActions({ watch, onToggleEnabled, onTrigger, onReset, onDelete }:
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onTrigger(watch.id);
|
||||
toast({ description: "Checking for new PRs..." });
|
||||
toast({ description: t("github:checkingForNewPrs") });
|
||||
}}
|
||||
>
|
||||
<IconRefresh className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Check now</TooltipContent>
|
||||
<TooltipContent>{t("github:checkNow")}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -101,7 +102,7 @@ function WatchActions({ watch, onToggleEnabled, onTrigger, onReset, onDelete }:
|
||||
size="sm"
|
||||
className="h-7 w-7 p-0 cursor-pointer"
|
||||
data-testid="watch-reset-button"
|
||||
aria-label="Reset watch"
|
||||
aria-label={t("github:resetWatch")}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onReset(watch.id);
|
||||
@@ -110,7 +111,7 @@ function WatchActions({ watch, onToggleEnabled, onTrigger, onReset, onDelete }:
|
||||
<IconRestore className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Reset</TooltipContent>
|
||||
<TooltipContent>{t("common:reset")}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -126,7 +127,7 @@ function WatchActions({ watch, onToggleEnabled, onTrigger, onReset, onDelete }:
|
||||
<IconTrash className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Delete</TooltipContent>
|
||||
<TooltipContent>{t("github:delete")}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
@@ -141,13 +142,14 @@ export function ReviewWatchTable({
|
||||
onReset,
|
||||
onToggleEnabled,
|
||||
}: ReviewWatchTableProps) {
|
||||
const { t } = useTranslation();
|
||||
const workspaces = useAppStore((s) => s.workspaces.items);
|
||||
const workspaceName = (id: string) => workspaces.find((w) => w.id === id)?.name ?? id;
|
||||
|
||||
if (watches.length === 0) {
|
||||
return (
|
||||
<p className="text-sm text-muted-foreground py-4 text-center">
|
||||
No review watches configured. Create one to start monitoring PRs.
|
||||
{t("github:noReviewWatchesConfiguredCreateOne")}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -156,12 +158,12 @@ export function ReviewWatchTable({
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{showWorkspace && <TableHead>Workspace</TableHead>}
|
||||
<TableHead>Repository</TableHead>
|
||||
<TableHead>Interval</TableHead>
|
||||
<TableHead>Last Polled</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
{showWorkspace && <TableHead>{t("common:workspace")}</TableHead>}
|
||||
<TableHead>{t("github:repository")}</TableHead>
|
||||
<TableHead>{t("github:interval")}</TableHead>
|
||||
<TableHead>{t("github:lastPolled")}</TableHead>
|
||||
<TableHead>{t("common:status")}</TableHead>
|
||||
<TableHead className="text-right">{t("github:actions")}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -172,16 +174,16 @@ export function ReviewWatchTable({
|
||||
{workspaceName(watch.workspace_id)}
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell className="font-medium">{formatRepoNames(watch)}</TableCell>
|
||||
<TableCell className="font-medium">{formatRepoNames(t, watch)}</TableCell>
|
||||
<TableCell className="text-xs text-muted-foreground">
|
||||
{Math.round(watch.poll_interval_seconds / 60)}m
|
||||
</TableCell>
|
||||
<TableCell className="text-xs text-muted-foreground">
|
||||
{formatLastPolled(watch.last_polled_at)}
|
||||
{formatLastPolled(t, watch.last_polled_at)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={watch.enabled ? "default" : "secondary"} className="text-xs">
|
||||
{watch.enabled ? "Active" : "Paused"}
|
||||
{watch.enabled ? t("github:active") : t("github:paused")}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import type { TFunction } from "i18next";
|
||||
import type { CleanupPolicy } from "@/lib/types/github";
|
||||
|
||||
/**
|
||||
* Cleanup-policy choices for the review and issue watch dialogs.
|
||||
*
|
||||
* `id` is the wire `CleanupPolicy` enum the backend stores and switches on, so it
|
||||
* is never translated. The copy travels as catalog keys resolved at render: a
|
||||
* `t()` call at module scope would freeze at the boot locale (see docs/i18n.md).
|
||||
* Both key fields are required, so `t()` can never receive `string | undefined`.
|
||||
*
|
||||
* The labels are shared; only the descriptions differ, because a merged PR and a
|
||||
* closed issue retain tasks for different reasons.
|
||||
*/
|
||||
export type CleanupPolicyOption = {
|
||||
id: CleanupPolicy;
|
||||
labelKey: string;
|
||||
descriptionKey: string;
|
||||
};
|
||||
|
||||
export const REVIEW_CLEANUP_POLICY_OPTIONS: CleanupPolicyOption[] = [
|
||||
{
|
||||
id: "auto",
|
||||
labelKey: "github:cleanupPolicyAuto",
|
||||
descriptionKey: "github:cleanupPolicyAutoDescription",
|
||||
},
|
||||
{
|
||||
id: "always",
|
||||
labelKey: "github:cleanupPolicyAlways",
|
||||
descriptionKey: "github:cleanupPolicyAlwaysDescription",
|
||||
},
|
||||
{
|
||||
id: "never",
|
||||
labelKey: "github:cleanupPolicyNever",
|
||||
descriptionKey: "github:cleanupPolicyNeverDescription",
|
||||
},
|
||||
];
|
||||
|
||||
export const ISSUE_CLEANUP_POLICY_OPTIONS: CleanupPolicyOption[] = [
|
||||
{
|
||||
id: "auto",
|
||||
labelKey: "github:cleanupPolicyAuto",
|
||||
descriptionKey: "github:issueCleanupPolicyAutoDescription",
|
||||
},
|
||||
{
|
||||
id: "always",
|
||||
labelKey: "github:cleanupPolicyAlways",
|
||||
descriptionKey: "github:issueCleanupPolicyAlwaysDescription",
|
||||
},
|
||||
{
|
||||
id: "never",
|
||||
labelKey: "github:cleanupPolicyNever",
|
||||
descriptionKey: "github:cleanupPolicyNeverDescription",
|
||||
},
|
||||
];
|
||||
|
||||
/** Items for the policy `SelectField`. */
|
||||
export function cleanupPolicyItems(t: TFunction, options: CleanupPolicyOption[]) {
|
||||
return options.map((option) => ({ id: option.id, label: t(option.labelKey) }));
|
||||
}
|
||||
|
||||
/** Description shown under the policy select; "" when the id is unknown. */
|
||||
export function cleanupPolicyDescription(
|
||||
t: TFunction,
|
||||
options: CleanupPolicyOption[],
|
||||
policy: CleanupPolicy,
|
||||
): string {
|
||||
const option = options.find((candidate) => candidate.id === policy);
|
||||
return option ? t(option.descriptionKey) : "";
|
||||
}
|
||||
@@ -127,6 +127,16 @@ const SCREENS: Array<{ name: string; url: string; allow?: string[] }> = [
|
||||
],
|
||||
},
|
||||
{ name: "settings — task actions", url: "/settings/general/task-actions" },
|
||||
// NOT YET: "settings — integrations github". The page's own copy is fully
|
||||
// migrated (verified by running this oracle against it — every GitHub-owned
|
||||
// string renders accented), but the route expands the Workspaces > Integrations
|
||||
// branch of the settings nav, and `workspaces-group.tsx` / `settings-tree.tsx`
|
||||
// are not migrated: "Workspaces", "Integrations", "Automations", "Executors",
|
||||
// "Voice Mode", "Utility Agents", "External MCP", "Plugins", "System" and
|
||||
// "Toggle theme" all still render plain English there. The /settings/general/*
|
||||
// screens above pass because their expanded branch is `general-group.tsx`,
|
||||
// which is migrated. Add this entry in the PR that migrates the settings nav —
|
||||
// allowlisting those nav labels here would hide real misses instead.
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -273,4 +273,28 @@ export const i18nGuardFiles = [
|
||||
"components/github/pr-stats.tsx",
|
||||
"components/integrations/workspace-scoped-section.tsx",
|
||||
"components/watches/reset-watch-dialog.tsx",
|
||||
// Settings → Integrations → GitHub, watches and My-GitHub defaults: the review
|
||||
// and issue watch dialogs and tables, the prompt field, and the quick-action /
|
||||
// default-query sections. With these the page is fully migrated.
|
||||
//
|
||||
// The three `.ts` entries hold no JSX, so `mode: "jsx-only"` never inspects
|
||||
// them; the entries record that they are migrated, and only the pseudo-locale
|
||||
// can prove it stays that way.
|
||||
"components/github/action-presets-section.tsx",
|
||||
"components/github/default-queries-section.tsx",
|
||||
"components/github/issue-watch-dialog.tsx",
|
||||
"components/github/issue-watch-placeholders.ts",
|
||||
"components/github/issue-watch-table.tsx",
|
||||
"components/github/review-watch-dialog.tsx",
|
||||
"components/github/review-watch-placeholders.ts",
|
||||
"components/github/review-watch-prompt-field.tsx",
|
||||
"components/github/review-watch-table.tsx",
|
||||
"components/github/watch-cleanup-policy.ts",
|
||||
// Shared with the My GitHub page (`app/github`) and the automations trigger
|
||||
// config, neither of which is migrated. Per-file guard, so they are
|
||||
// unaffected.
|
||||
"components/github/my-github/action-presets.ts",
|
||||
"components/github/my-github/search-bar.tsx",
|
||||
"components/github/my-github/use-default-query-presets.ts",
|
||||
"components/github/repo-filter-selector.tsx",
|
||||
];
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"plugins": "Plugins",
|
||||
"prompts": "Prompts",
|
||||
"reset": "Reset",
|
||||
"resetWatchChecking": "Checking how many tasks would be deleted…",
|
||||
"resetting": "Resetting\u2026",
|
||||
"resetWatchChecking": "Checking how many tasks would be deleted\u2026",
|
||||
"resetWatchDeleteAllTasks": "This will delete every task previously created by the watch, including archived tasks. The watch's polling cursor is also cleared so the next check re-imports every currently-matching item. This cannot be undone.",
|
||||
"resetWatchDeleteTasks_one": "This will delete {{count}} task previously created by the watch, including archived tasks. The watch's polling cursor is also cleared so the next check re-imports every currently-matching item. This cannot be undone.",
|
||||
"resetWatchDeleteTasks_other": "This will delete {{count}} tasks previously created by the watch, including archived tasks. The watch's polling cursor is also cleared so the next check re-imports every currently-matching item. This cannot be undone.",
|
||||
"resetWatchNoTasks": "No tasks were created by this watch yet — only the polling state will be cleared. The watch's polling cursor is also cleared so the next check re-imports every currently-matching item. This cannot be undone.",
|
||||
"resetWatchNoTasks": "No tasks were created by this watch yet \u2014 only the polling state will be cleared. The watch's polling cursor is also cleared so the next check re-imports every currently-matching item. This cannot be undone.",
|
||||
"resetWatchPreviewFailedRequired": "Could not load the affected task count. Retry the preview before resetting this watch.",
|
||||
"resetWatchTitle": "Reset {{label}}?",
|
||||
"resetting": "Resetting…",
|
||||
"retryPreview": "Retry preview",
|
||||
"settings": "Settings",
|
||||
"shell": "Shell",
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
{
|
||||
"aRegistrationCanBeReusedAcross": "A registration can be reused across workspaces, or you can keep work and personal Apps separate. Each workspace selects and installs its own credential.",
|
||||
"accountsUnavailable": "Accounts unavailable",
|
||||
"actionPromptPlaceholderHelp": "Type {{token}} to see available placeholders. <4>{{url}}</4> and <7>{{title}}</7> are substituted when the action runs.",
|
||||
"actions": "Actions",
|
||||
"active": "Active",
|
||||
"addAnExistingGithubApp": "Add an existing GitHub App",
|
||||
"addExistingApp": "Add existing App",
|
||||
"addIssueAction": "Add issue action",
|
||||
"addIssueQuery": "Add issue query",
|
||||
"addPrAction": "Add PR action",
|
||||
"addPrQuery": "Add PR query",
|
||||
"addRepository": "Add repository",
|
||||
"addWatch": "Add Watch",
|
||||
"agentProfile": "Agent Profile",
|
||||
"allRepositories": "All repositories",
|
||||
"allRepositoriesAllowedByThisWorkspace": "All repositories allowed by this workspace",
|
||||
"allowsInstallationElsewhereItDoesNot": "Allows installation elsewhere. It does not publish to Marketplace or grant repos.",
|
||||
"anExecutorProfileGhTokenOr": "An executor-profile {{ghToken}} or {{githubToken}} overrides managed workspace credentials for that task.",
|
||||
"anImportedAppMustHaveAt": "An imported App must have at least these repository permissions and subscribed events.",
|
||||
@@ -13,14 +23,22 @@
|
||||
"appInstallationFailed": "App installation failed",
|
||||
"approvalRate": "Approval Rate",
|
||||
"authenticationUnavailable": "Authentication unavailable",
|
||||
"auto": "Auto",
|
||||
"automaticallyCreateTasksForGithubIssues": "Automatically create tasks for GitHub issues matching your criteria.",
|
||||
"automaticallyCreateTasksForPrsThat": "Automatically create tasks for PRs that need your review.",
|
||||
"automaticallyCreateTasksWhenNewGithub": "Automatically create tasks when new GitHub issues match your criteria.",
|
||||
"automaticallyCreateTasksWhenNewPull": "Automatically create tasks when new pull requests need your review.",
|
||||
"automation": "Automation",
|
||||
"available": "Available",
|
||||
"availablePlaceholders": "Available placeholders:",
|
||||
"avgTimeToMerge": "Avg Time to Merge",
|
||||
"backToRegisteredApps": "Back to registered Apps",
|
||||
"bestForPersonalOrCompanyInternal": "Best for personal or company-internal automation.",
|
||||
"changeConnection": "Change connection",
|
||||
"changeGithubConnection": "Change GitHub connection",
|
||||
"checkNow": "Check now",
|
||||
"checkingForNewIssues": "Checking for new issues...",
|
||||
"checkingForNewPrs": "Checking for new PRs...",
|
||||
"checkingGithubConnection": "Checking GitHub connection...",
|
||||
"chooseAGithubApp": "Choose a GitHub App",
|
||||
"chooseHowNewlyLaunchedTasksAuthenticate": "Choose how newly launched tasks authenticate to GitHub. This does not change the workspace automation connection above.",
|
||||
@@ -30,7 +48,14 @@
|
||||
"cleanUpClosed": "Clean up closed",
|
||||
"cleanUpMerged": "Clean up merged",
|
||||
"cleaning": "Cleaning…",
|
||||
"cleanupBehavior": "Cleanup behavior",
|
||||
"cleanupFailed": "Cleanup failed",
|
||||
"cleanupPolicyAlways": "Always delete",
|
||||
"cleanupPolicyAlwaysDescription": "Always delete overrides retention: delete on merge/close even if the task has user engagement or enabled PR lifecycle prompts.",
|
||||
"cleanupPolicyAuto": "Auto (recommended)",
|
||||
"cleanupPolicyAutoDescription": "Retain merged/closed PR tasks when they have user engagement or enabled PR lifecycle prompts; otherwise delete them.",
|
||||
"cleanupPolicyNever": "Never auto-delete",
|
||||
"cleanupPolicyNeverDescription": "Keep all tasks. Delete them manually from the task list.",
|
||||
"cliAccountActiveSuffix": " - active",
|
||||
"clientId": "Client ID",
|
||||
"clientSecret": "Client secret",
|
||||
@@ -43,16 +68,31 @@
|
||||
"controlsHowNewlyLaunchedTasksAuthenticate": "Controls how newly launched tasks authenticate to GitHub for Git HTTPS and gh commands. Managed workspace credentials use Kandev's workspace connection; executor credentials use the selected executor's Git or SSH setup.",
|
||||
"copy": "Copy {{label}}",
|
||||
"couldNotLoadGithubCliAccounts": "Could not load GitHub CLI accounts. {{error}}",
|
||||
"create": "Create",
|
||||
"createAGithubApp": "Create a GitHub App",
|
||||
"createIssueWatch": "Create Issue Watch",
|
||||
"createNewApp": "Create new App",
|
||||
"createReviewWatch": "Create Review Watch",
|
||||
"created": "Created",
|
||||
"createdByKandev": "Created by Kandev",
|
||||
"credentialUsedForRepositorySyncWatches": "Credential used for repository sync, watches, and managed agent git and gh commands. Executor profile tokens take precedence.",
|
||||
"currentGithubApiRateAndQuery": "Current GitHub API rate and query limits for this workspace connection.",
|
||||
"customQuery": "Custom Query",
|
||||
"defaultQueries": "Default queries",
|
||||
"defaultQueriesAreStillLoading": "Default queries are still loading.",
|
||||
"defaultQueriesSaved": "Default queries saved",
|
||||
"delete": "Delete",
|
||||
"deletedTasks_one": "Deleted {{count}} task",
|
||||
"deletedTasks_other": "Deleted {{count}} tasks",
|
||||
"disconnect": "Disconnect",
|
||||
"disconnectFailed": "Disconnect failed",
|
||||
"eGBugEnhancementPriorityHigh": "e.g. bug, enhancement, priority:high",
|
||||
"editDetails": "Edit details",
|
||||
"editIssueWatch": "Edit Issue Watch",
|
||||
"editPrompt": "Edit prompt",
|
||||
"editReviewWatch": "Edit Review Watch",
|
||||
"enable": "Enable",
|
||||
"enabled": "Enabled",
|
||||
"enterANameUpTo100Characters": "Enter a name up to 100 characters.",
|
||||
"enterAPublicHostNotLocalhost": "Enter a public host, not localhost.",
|
||||
"enterAPublicHttpsOrigin": "Enter a public HTTPS origin.",
|
||||
@@ -63,6 +103,7 @@
|
||||
"enterTheGithubAppSlug": "Enter the GitHub App slug.",
|
||||
"enterTheNumericGithubAppId": "Enter the numeric GitHub App ID.",
|
||||
"enterTheWebhookSecret": "Enter the webhook secret.",
|
||||
"executorProfile": "Executor Profile",
|
||||
"explainHowManagedTaskCredentialsWork": "Explain how managed task credentials work",
|
||||
"explainRepositoryScope": "Explain repository scope",
|
||||
"explainTaskGitAccess": "Explain task Git access",
|
||||
@@ -74,7 +115,10 @@
|
||||
"failedToLoadGithubWorkspaceSettings": "Failed to load GitHub workspace settings",
|
||||
"failedToResetIssueWatch": "Failed to reset issue watch",
|
||||
"failedToResetReviewWatch": "Failed to reset review watch",
|
||||
"failedToSaveDefaultQueries": "Failed to save default queries",
|
||||
"failedToSaveGithubWorkspaceSettings": "Failed to save GitHub workspace settings",
|
||||
"failedToSaveQuickActions": "Failed to save quick actions",
|
||||
"filter": "Filter",
|
||||
"forWebhooksChooseContentType": "For webhooks, choose <1>{{contentType}}</1> as the content type and keep SSL verification enabled.",
|
||||
"foundNewIssues_one": "Found {{count}} new issue",
|
||||
"foundNewIssues_other": "Found {{count}} new issues",
|
||||
@@ -103,25 +147,42 @@
|
||||
"githubIsReadyToCreateThe": "GitHub is ready to create the App",
|
||||
"githubMustReachThisHttpsOrigin": "GitHub must reach this HTTPS origin for signed webhooks and callbacks.",
|
||||
"githubOwnerLogin": "GitHub owner login",
|
||||
"githubSearchQuerySupportsFullGithub": "GitHub search query. Supports full GitHub search syntax for maximum flexibility.",
|
||||
"githubSearchQueryWhenSetOverrides": "GitHub search query. When set, overrides the label filter above.",
|
||||
"githubSetupWasNotCompleted": "GitHub setup was not completed",
|
||||
"githubWillShowTheGeneratedPermissions": "GitHub will show the generated permissions and ask you to confirm. Returning from GitHub adds the registration; installing it for this workspace remains an explicit step.",
|
||||
"githubWorkspaceSettingsSaved": "GitHub workspace settings saved",
|
||||
"graphqlQueryLimit": "GraphQL query limit",
|
||||
"group": "Group",
|
||||
"hidePrompt": "Hide prompt",
|
||||
"hideToken": "Hide token",
|
||||
"hint": "Hint",
|
||||
"hintOptional": "Hint (optional)",
|
||||
"howManagedTaskCredentialsWork": "How managed task credentials work",
|
||||
"howOftenToCheckForNew": "How often to check for new issues. Minimum 60s, maximum 3600s.",
|
||||
"howOftenToCheckForNew2": "How often to check for new PRs. Minimum 60s, maximum 3600s.",
|
||||
"icon": "Icon",
|
||||
"identityConnectionFailed": "Identity connection failed",
|
||||
"importSetupCouldNotStart": "Import setup could not start",
|
||||
"imported": "Imported",
|
||||
"inUse": "In use",
|
||||
"inbox": "Inbox",
|
||||
"inheritExecutorGitCredentials": "Inherit executor Git credentials",
|
||||
"initialStepForNewTasks": "Initial step for new tasks.",
|
||||
"initialStepForNewTasksAuto": "Initial step for new tasks. Auto-start is set on the step.",
|
||||
"installAGithubAppAboveTo": "Install a GitHub App above to change the workspace connection. Task access can be saved after the installation finishes.",
|
||||
"installForThisWorkspace": "Install for this workspace",
|
||||
"interval": "Interval",
|
||||
"issueCleanupPolicyAlwaysDescription": "Delete on close even if you engaged with the task.",
|
||||
"issueCleanupPolicyAutoDescription": "Delete closed-issue tasks unless you typed a message in them.",
|
||||
"issueWatch": "issue watch",
|
||||
"issueWatchCreated": "Issue watch created",
|
||||
"issueWatchDeleted": "Issue watch deleted",
|
||||
"issueWatchNotFound": "Issue watch not found",
|
||||
"issueWatchPromptHelp": "The prompt sent to the agent for each new issue. Type {{token}} to insert placeholders.",
|
||||
"issueWatchUpdated": "Issue watch updated",
|
||||
"issueWatches": "Issue Watches",
|
||||
"issues": "Issues",
|
||||
"kandevBrokersTheWorkspacePatNamed": "Kandev brokers the workspace PAT, named GitHub CLI account, or App identity to this task for GitHub HTTPS and gh.",
|
||||
"kandevFirstReservesExactCallbackAnd": "Kandev first reserves exact callback and webhook URLs, then guides you through GitHub's App settings. Existing workspace access is unchanged until you install the imported App.",
|
||||
"kandevGeneratesTheExactAppPolicy": "Kandev generates the exact App policy. You confirm creation on GitHub; Kandev then stores this registration so it can be selected by a workspace.",
|
||||
@@ -129,55 +190,108 @@
|
||||
"kandevManagedOperationsUseTheGithub": "Kandev-managed operations use the GitHub App installed for {{actor}}.",
|
||||
"kandevStoresThisTokenForThis": "Kandev stores this token for this workspace. GitHub records actions as the token owner.",
|
||||
"kandevUsesThisWorkspaceConnectionFor": "Kandev uses this workspace connection for repository sync, watches, background jobs, and managed agent GitHub commands. With a PAT or GitHub CLI account, My GitHub and actions you trigger use the same human identity.",
|
||||
"label": "Label",
|
||||
"labels": "Labels",
|
||||
"labelsCommaSeparated": "Labels (comma-separated)",
|
||||
"lastPolled": "Last Polled",
|
||||
"legacySharedConnection": "Legacy shared connection",
|
||||
"limitsGithubPullRequestsAndIssues": "Limits GitHub pull requests and issues shown or imported in this workspace.",
|
||||
"loadingAccounts": "Loading accounts...",
|
||||
"loadingOrganizations": "Loading organizations...",
|
||||
"loadingRegisteredApps": "Loading registered Apps...",
|
||||
"loadingTaskAccess": "Loading task access…",
|
||||
"localAndWorktreeTasksUseHost": "Local and Worktree tasks use host-visible Git or SSH credentials. Docker, SSH, and cloud tasks use credentials configured in that executor.",
|
||||
"managedWorkspaceCredentials": "Managed workspace credentials",
|
||||
"me": "Me",
|
||||
"meMyTeams": "Me & my teams",
|
||||
"missing": "Missing",
|
||||
"mode": "Mode",
|
||||
"myGithubIdentity": "My GitHub identity",
|
||||
"myGithubNowUsesYourVerified": "My GitHub now uses your verified personal identity.",
|
||||
"nameInKandev": "Name in Kandev",
|
||||
"needsAttention": "Needs attention",
|
||||
"never": "Never",
|
||||
"noAutomationConnection": "No automation connection",
|
||||
"noGhAccountsFound": "No gh accounts found",
|
||||
"noGithubAppsAreRegisteredYet": "No GitHub Apps are registered yet. Add an existing App or create one below.",
|
||||
"noIssueWatchesConfiguredCreateOne": "No issue watches configured. Create one to start monitoring GitHub issues.",
|
||||
"noNewIssuesFound": "No new issues found",
|
||||
"noNewPrsFound": "No new PRs found",
|
||||
"noReposFoundFor": "No repos found for \"{{org}}\"",
|
||||
"noReviewWatchesConfiguredCreateOne": "No review watches configured. Create one to start monitoring PRs.",
|
||||
"noStatsAvailableYet": "No stats available yet.",
|
||||
"noTasksToCleanUp": "No tasks to clean up",
|
||||
"onlyMatchIssuesWithTheseLabels": "Only match issues with these labels. Leave empty for all issues.",
|
||||
"onlyPrsWhereYouAreExplicitly": "Only PRs where you are explicitly requested as a reviewer (not via team membership)",
|
||||
"onlyTheAppOwner": "Only the App owner",
|
||||
"openGithubAppSettings": "Open GitHub App settings",
|
||||
"optionalFallsBackToStepDefault": "Optional — falls back to step default.",
|
||||
"optionalFallsBackToStepDefault2": "Optional — falls back to step default. The executor environment where the agent will run.",
|
||||
"optionallyConnectYourGithubUserFor": "Optionally connect your GitHub user for My GitHub and human-attributed actions. This identity is never given to managed agents; workspace automation continues as the App.",
|
||||
"organization": "Organization",
|
||||
"organizations": "Organizations",
|
||||
"otherGithubAccounts": "Other GitHub accounts",
|
||||
"pasteTheAppPrivateKey": "Paste the App private key.",
|
||||
"pause": "Pause",
|
||||
"pauseOrResumePolling": "Pause or resume polling.",
|
||||
"paused": "Paused",
|
||||
"permissionsAvailableToKandevThroughThis": "Permissions available to Kandev through this workspace's GitHub App installation.",
|
||||
"personalAccessToken": "Personal access token",
|
||||
"personalAccount": "Personal account",
|
||||
"personalGithubIdentityDisconnected": "Personal GitHub identity disconnected",
|
||||
"personalOauth": "Personal OAuth",
|
||||
"placeholderActionTitle": "Title of the PR or issue",
|
||||
"placeholderActionUrl": "URL of the PR or issue",
|
||||
"placeholderCommaSeparatedLabels": "Comma-separated labels",
|
||||
"placeholderIssueAuthor": "Issue author username",
|
||||
"placeholderIssueBody": "Issue body text",
|
||||
"placeholderIssueNumber": "Issue number",
|
||||
"placeholderIssueTitle": "Issue title",
|
||||
"placeholderIssueUrl": "Issue URL",
|
||||
"placeholderPrAuthor": "PR author username",
|
||||
"placeholderPrNumber": "PR number",
|
||||
"placeholderPrTitle": "PR title",
|
||||
"placeholderPrUrl": "PR URL",
|
||||
"placeholderRepository": "Repository (owner/name)",
|
||||
"placeholderSourceBranch": "Source branch",
|
||||
"placeholderTargetBranch": "Target branch",
|
||||
"pollIntervalSeconds": "Poll Interval (seconds)",
|
||||
"prAnalytics": "PR Analytics",
|
||||
"prepareAppOnGithub": "Prepare App on GitHub",
|
||||
"privateKeyPem": "Private key (.pem)",
|
||||
"presetIconBug": "Bug",
|
||||
"presetIconCheck": "Check",
|
||||
"presetIconCode": "Code",
|
||||
"presetIconEye": "Eye",
|
||||
"presetIconMessage": "Message",
|
||||
"presetIconSearch": "Search",
|
||||
"presetIconSparkle": "Sparkle",
|
||||
"presetIconTool": "Tool",
|
||||
"private": "private",
|
||||
"privateKeyPem": "Private key ({{extension}})",
|
||||
"promptsShownOnGithubWhenStarting": "Prompts shown on {{route}} when starting a task from a PR or issue.",
|
||||
"prsByDay": "PRs by Day",
|
||||
"prsCreated": "PRs Created",
|
||||
"prsOnDay_one": "{{date}}: {{count}} PR",
|
||||
"prsOnDay_other": "{{date}}: {{count}} PRs",
|
||||
"prsReviewed": "PRs Reviewed",
|
||||
"prsWhereYouOrAnyOf": "PRs where you or any of your teams are requested as reviewers",
|
||||
"publicKandevUrl": "Public Kandev URL",
|
||||
"pullRequestActivityForThisWorkspace": "Pull request activity for this workspace.",
|
||||
"pullRequests": "Pull requests",
|
||||
"query": "Query",
|
||||
"queryExample": "e.g. {{query}}",
|
||||
"quickActions": "Quick actions",
|
||||
"quickActionsAreStillLoading": "Quick actions are still loading.",
|
||||
"quickActionsSaved": "Quick actions saved",
|
||||
"rateLimitRemaining": "{{remaining}} of {{limit}} {{unit}} remaining",
|
||||
"rateLimitUnitPoints": "points",
|
||||
"rateLimitUnitRequests": "requests",
|
||||
"rateLimitUnknown": "unknown",
|
||||
"reconnectIdentity": "Reconnect identity",
|
||||
"refreshGithubConnection": "Refresh GitHub connection",
|
||||
"remove": "Remove",
|
||||
"repositories": "Repositories",
|
||||
"repository": "Repository",
|
||||
"repositoryPermissions": "Repository permissions",
|
||||
"repositoryScope": "Repository Scope",
|
||||
"repositoryScopeHelp": "Limits the GitHub pull requests and issues Kandev discovers for this workspace, including My GitHub results and review and issue watches. It does not change GitHub permissions or repository access.",
|
||||
@@ -185,6 +299,7 @@
|
||||
"resetCompleteDeletedTasks_one": "Reset complete — deleted {{count}} task; next poll will re-import.",
|
||||
"resetCompleteDeletedTasks_other": "Reset complete — deleted {{count}} tasks; next poll will re-import.",
|
||||
"resetCompleteNextPollWillRe": "Reset complete — next poll will re-import matches.",
|
||||
"resetWatch": "Reset watch",
|
||||
"resets": "· resets {{reset}}",
|
||||
"resolvedFromOneNamedHostAccount": "Resolved from one named host account when needed. Managed tasks receive a scoped brokered identity; host Git is not inherited unless selected below.",
|
||||
"reviewPermissions": "Review permissions",
|
||||
@@ -192,15 +307,22 @@
|
||||
"reviewWatchCreated": "Review watch created",
|
||||
"reviewWatchDeleted": "Review watch deleted",
|
||||
"reviewWatchNotFound": "Review watch not found",
|
||||
"reviewWatchPromptHelp": "The prompt sent to the agent for each new PR. Type {{token}} to insert placeholders, or {{mention}} to reference a saved prompt by name — it expands the same way it does in workflow step prompts, since this prompt becomes the task description passed through that same assembly.",
|
||||
"reviewWatchUpdated": "Review watch updated",
|
||||
"reviewWatches": "Review Watches",
|
||||
"saveChanges": "Save changes",
|
||||
"saving": "Saving...",
|
||||
"savingChanges": "Saving changes…",
|
||||
"searchLimit": "Search limit",
|
||||
"searchQuery": "Search Query",
|
||||
"searching": "Searching...",
|
||||
"selectWorkflow": "Select workflow",
|
||||
"selectWorkspace": "Select workspace",
|
||||
"selectedRepositories": "Selected repositories",
|
||||
"setTheseExactUrlsCreateAClientSecret": "Set these exact URLs, create a client secret and webhook secret, and download a private key. This one-time setup expires {{expiresAt}}.",
|
||||
"showGithubApiLimits": "Show GitHub API limits",
|
||||
"showToken": "Show token",
|
||||
"sidebarQueriesShownOnGithubFor": "Sidebar queries shown on {{route}} for pull requests and issues.",
|
||||
"signInWithGhAuthLogin": "Sign in with <1>{{command}}</1>, then reopen this dialog.",
|
||||
"someSettingsWereSavedButAnother": "Some settings were saved, but another change failed: {{detail}}",
|
||||
"startOver": "Start over",
|
||||
@@ -210,18 +332,24 @@
|
||||
"taskAccess": "Task access:",
|
||||
"taskAccessUnavailable": "Task access unavailable",
|
||||
"taskGitAccess": "Task Git access",
|
||||
"taskPrompt": "Task Prompt",
|
||||
"tasksCreatedByThisWatcherLand": "Tasks created by this watcher land in the selected workspace.",
|
||||
"theAppIsReadyToSelect": "The App is ready to select and install for this workspace.",
|
||||
"theExistingWorkspaceConnectionWasNot": "The existing workspace connection was not changed.",
|
||||
"theGithubResponseCouldNotBe": "The GitHub response could not be verified. Review the connection and try again.",
|
||||
"theRepositoryWillBeAutomaticallyCloned": "The repository will be automatically cloned to {{path}} if it is not already present in the workspace.",
|
||||
"theSettingsCouldNotBeSaved": "The settings could not be saved",
|
||||
"theStoredAppCredentialsAreUnavailable": "The stored App credentials are unavailable.",
|
||||
"theWorkflowToCreateTasksIn": "The workflow to create tasks in.",
|
||||
"theWorkspaceChangedBeforeTaskAccess": "The workspace changed before task access was saved",
|
||||
"thisAccountAlsoPowersMyGithub": "This account also powers My GitHub and user-triggered actions.",
|
||||
"thisAppRegistrationIsShared_one": "This App registration is shared by {{count}} workspace. Registration changes affect every workspace using it; installation access remains workspace-specific.",
|
||||
"thisAppRegistrationIsShared_other": "This App registration is shared by {{count}} workspaces. Registration changes affect every workspace using it; installation access remains workspace-specific.",
|
||||
"thisWorkspaceUsesOneCredentialFor": "This workspace uses one credential for repository sync, watches, background jobs, and managed agent GitHub access. Executor profile credentials can still take precedence.",
|
||||
"typeOwnerRepoToSearch": "Type {{format}} to search",
|
||||
"unableToLoadTheCurrentTask": "Unable to load the current task access setting.",
|
||||
"unexpectedResponseFromTheServer": "Unexpected response from the server",
|
||||
"update": "Update",
|
||||
"useAnAppWhenAutomationNeeds": "Use an App when automation needs its own identity, short-lived tokens, and repository-level installation control. Setup is more involved and requires a publicly reachable HTTPS URL.",
|
||||
"useCommaSeparatedOwnerRepoValues": "Use comma-separated owner/repo values.",
|
||||
"usedByWorkspaces_one": "Used by {{count}} workspace. Editing or deleting it affects all of them.",
|
||||
@@ -230,8 +358,12 @@
|
||||
"viewPermissions": "View permissions",
|
||||
"webhook": "Webhook",
|
||||
"webhookSecret": "Webhook secret",
|
||||
"whichAllowedGithubRepositoriesToMonitor": "Which allowed GitHub repositories to monitor for this watch.",
|
||||
"whoCanInstallThisApp": "Who can install this App?",
|
||||
"withManagedWorkspaceCredentialsKandevConfigures": "With Managed workspace credentials, Kandev configures agentctl as Git's credential helper inside newly launched task processes. When Git requests credentials for an attached GitHub HTTPS repository, the helper redeems a task- and repository-scoped broker lease. The credential is returned to Git on demand and is not written to the repository or Git config. The gh command uses Kandev's broker-aware shim. Executor inheritance skips both and uses credentials visible in the selected executor. Changes apply to new task launches and the next resume, not already-running processes.",
|
||||
"workflow": "Workflow",
|
||||
"workflowStep": "Workflow Step",
|
||||
"workflowStepPromptWrapHelp": "The workflow step prompt wraps this prompt. For example, if the step prompt is <2>{{stepPrompt}}</2>, the final prompt becomes <5>{{finalPrompt}}</5>",
|
||||
"workspaceAutomationNowUsesTheVerified": "Workspace automation now uses the verified App installation.",
|
||||
"workspaceGithubAccess": "Workspace GitHub access",
|
||||
"workspaceGithubConnectionRemoved": "Workspace GitHub connection removed",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"plugins": "Ƥĺũĝĩńś",
|
||||
"prompts": "Ƥŕōḿƥţś",
|
||||
"reset": "Ŕēśēţ",
|
||||
"resetting": "Ŕēśēţţĩńĝ…",
|
||||
"resetWatchChecking": "Ćĥēćķĩńĝ ĥōŵ ḿàńŷ ţàśķś ŵōũĺď ƀē ďēĺēţēď…",
|
||||
"resetWatchDeleteAllTasks": "Ţĥĩś ŵĩĺĺ ďēĺēţē ēvēŕŷ ţàśķ ƥŕēvĩōũśĺŷ ćŕēàţēď ƀŷ ţĥē ŵàţćĥ, ĩńćĺũďĩńĝ àŕćĥĩvēď ţàśķś. Ţĥē ŵàţćĥ'ś ƥōĺĺĩńĝ ćũŕśōŕ ĩś àĺśō ćĺēàŕēď śō ţĥē ńēxţ ćĥēćķ ŕē-ĩḿƥōŕţś ēvēŕŷ ćũŕŕēńţĺŷ-ḿàţćĥĩńĝ ĩţēḿ. Ţĥĩś ćàńńōţ ƀē ũńďōńē.",
|
||||
"resetWatchDeleteTasks_one": "Ţĥĩś ŵĩĺĺ ďēĺēţē {{count}} ţàśķ ƥŕēvĩōũśĺŷ ćŕēàţēď ƀŷ ţĥē ŵàţćĥ, ĩńćĺũďĩńĝ àŕćĥĩvēď ţàśķś. Ţĥē ŵàţćĥ'ś ƥōĺĺĩńĝ ćũŕśōŕ ĩś àĺśō ćĺēàŕēď śō ţĥē ńēxţ ćĥēćķ ŕē-ĩḿƥōŕţś ēvēŕŷ ćũŕŕēńţĺŷ-ḿàţćĥĩńĝ ĩţēḿ. Ţĥĩś ćàńńōţ ƀē ũńďōńē.",
|
||||
@@ -20,7 +21,6 @@
|
||||
"resetWatchNoTasks": "Ńō ţàśķś ŵēŕē ćŕēàţēď ƀŷ ţĥĩś ŵàţćĥ ŷēţ — ōńĺŷ ţĥē ƥōĺĺĩńĝ śţàţē ŵĩĺĺ ƀē ćĺēàŕēď. Ţĥē ŵàţćĥ'ś ƥōĺĺĩńĝ ćũŕśōŕ ĩś àĺśō ćĺēàŕēď śō ţĥē ńēxţ ćĥēćķ ŕē-ĩḿƥōŕţś ēvēŕŷ ćũŕŕēńţĺŷ-ḿàţćĥĩńĝ ĩţēḿ. Ţĥĩś ćàńńōţ ƀē ũńďōńē.",
|
||||
"resetWatchPreviewFailedRequired": "Ćōũĺď ńōţ ĺōàď ţĥē àƒƒēćţēď ţàśķ ćōũńţ. Ŕēţŕŷ ţĥē ƥŕēvĩēŵ ƀēƒōŕē ŕēśēţţĩńĝ ţĥĩś ŵàţćĥ.",
|
||||
"resetWatchTitle": "Ŕēśēţ {{label}}?",
|
||||
"resetting": "Ŕēśēţţĩńĝ…",
|
||||
"retryPreview": "Ŕēţŕŷ ƥŕēvĩēŵ",
|
||||
"settings": "Śēţţĩńĝś",
|
||||
"shell": "Śĥēĺĺ",
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
{
|
||||
"aRegistrationCanBeReusedAcross": "À ŕēĝĩśţŕàţĩōń ćàń ƀē ŕēũśēď àćŕōśś ŵōŕķśƥàćēś, ōŕ ŷōũ ćàń ķēēƥ ŵōŕķ àńď ƥēŕśōńàĺ Àƥƥś śēƥàŕàţē. Ēàćĥ ŵōŕķśƥàćē śēĺēćţś àńď ĩńśţàĺĺś ĩţś ōŵń ćŕēďēńţĩàĺ.",
|
||||
"accountsUnavailable": "Àććōũńţś ũńàvàĩĺàƀĺē",
|
||||
"actionPromptPlaceholderHelp": "Ţŷƥē {{token}} ţō śēē àvàĩĺàƀĺē ƥĺàćēĥōĺďēŕś. <4>{{url}}</4> àńď <7>{{title}}</7> àŕē śũƀśţĩţũţēď ŵĥēń ţĥē àćţĩōń ŕũńś.",
|
||||
"actions": "Àćţĩōńś",
|
||||
"active": "Àćţĩvē",
|
||||
"addAnExistingGithubApp": "Àďď àń ēxĩśţĩńĝ ĜĩţĤũƀ Àƥƥ",
|
||||
"addExistingApp": "Àďď ēxĩśţĩńĝ Àƥƥ",
|
||||
"addIssueAction": "Àďď ĩśśũē àćţĩōń",
|
||||
"addIssueQuery": "Àďď ĩśśũē qũēŕŷ",
|
||||
"addPrAction": "Àďď ƤŔ àćţĩōń",
|
||||
"addPrQuery": "Àďď ƤŔ qũēŕŷ",
|
||||
"addRepository": "Àďď ŕēƥōśĩţōŕŷ",
|
||||
"addWatch": "Àďď Ŵàţćĥ",
|
||||
"agentProfile": "Àĝēńţ Ƥŕōƒĩĺē",
|
||||
"allRepositories": "Àĺĺ ŕēƥōśĩţōŕĩēś",
|
||||
"allRepositoriesAllowedByThisWorkspace": "Àĺĺ ŕēƥōśĩţōŕĩēś àĺĺōŵēď ƀŷ ţĥĩś ŵōŕķśƥàćē",
|
||||
"allowsInstallationElsewhereItDoesNot": "Àĺĺōŵś ĩńśţàĺĺàţĩōń ēĺśēŵĥēŕē. Ĩţ ďōēś ńōţ ƥũƀĺĩśĥ ţō Ḿàŕķēţƥĺàćē ōŕ ĝŕàńţ ŕēƥōś.",
|
||||
"anExecutorProfileGhTokenOr": "Àń ēxēćũţōŕ-ƥŕōƒĩĺē {{ghToken}} ōŕ {{githubToken}} ōvēŕŕĩďēś ḿàńàĝēď ŵōŕķśƥàćē ćŕēďēńţĩàĺś ƒōŕ ţĥàţ ţàśķ.",
|
||||
"anImportedAppMustHaveAt": "Àń ĩḿƥōŕţēď Àƥƥ ḿũśţ ĥàvē àţ ĺēàśţ ţĥēśē ŕēƥōśĩţōŕŷ ƥēŕḿĩśśĩōńś àńď śũƀśćŕĩƀēď ēvēńţś.",
|
||||
@@ -13,14 +23,22 @@
|
||||
"appInstallationFailed": "Àƥƥ ĩńśţàĺĺàţĩōń ƒàĩĺēď",
|
||||
"approvalRate": "Àƥƥŕōvàĺ Ŕàţē",
|
||||
"authenticationUnavailable": "Àũţĥēńţĩćàţĩōń ũńàvàĩĺàƀĺē",
|
||||
"auto": "Àũţō",
|
||||
"automaticallyCreateTasksForGithubIssues": "Àũţōḿàţĩćàĺĺŷ ćŕēàţē ţàśķś ƒōŕ ĜĩţĤũƀ ĩśśũēś ḿàţćĥĩńĝ ŷōũŕ ćŕĩţēŕĩà.",
|
||||
"automaticallyCreateTasksForPrsThat": "Àũţōḿàţĩćàĺĺŷ ćŕēàţē ţàśķś ƒōŕ ƤŔś ţĥàţ ńēēď ŷōũŕ ŕēvĩēŵ.",
|
||||
"automaticallyCreateTasksWhenNewGithub": "Àũţōḿàţĩćàĺĺŷ ćŕēàţē ţàśķś ŵĥēń ńēŵ ĜĩţĤũƀ ĩśśũēś ḿàţćĥ ŷōũŕ ćŕĩţēŕĩà.",
|
||||
"automaticallyCreateTasksWhenNewPull": "Àũţōḿàţĩćàĺĺŷ ćŕēàţē ţàśķś ŵĥēń ńēŵ ƥũĺĺ ŕēqũēśţś ńēēď ŷōũŕ ŕēvĩēŵ.",
|
||||
"automation": "Àũţōḿàţĩōń",
|
||||
"available": "Àvàĩĺàƀĺē",
|
||||
"availablePlaceholders": "Àvàĩĺàƀĺē ƥĺàćēĥōĺďēŕś:",
|
||||
"avgTimeToMerge": "Àvĝ Ţĩḿē ţō Ḿēŕĝē",
|
||||
"backToRegisteredApps": "Ɓàćķ ţō ŕēĝĩśţēŕēď Àƥƥś",
|
||||
"bestForPersonalOrCompanyInternal": "Ɓēśţ ƒōŕ ƥēŕśōńàĺ ōŕ ćōḿƥàńŷ-ĩńţēŕńàĺ àũţōḿàţĩōń.",
|
||||
"changeConnection": "Ćĥàńĝē ćōńńēćţĩōń",
|
||||
"changeGithubConnection": "Ćĥàńĝē ĜĩţĤũƀ ćōńńēćţĩōń",
|
||||
"checkNow": "Ćĥēćķ ńōŵ",
|
||||
"checkingForNewIssues": "Ćĥēćķĩńĝ ƒōŕ ńēŵ ĩśśũēś...",
|
||||
"checkingForNewPrs": "Ćĥēćķĩńĝ ƒōŕ ńēŵ ƤŔś...",
|
||||
"checkingGithubConnection": "Ćĥēćķĩńĝ ĜĩţĤũƀ ćōńńēćţĩōń...",
|
||||
"chooseAGithubApp": "Ćĥōōśē à ĜĩţĤũƀ Àƥƥ",
|
||||
"chooseHowNewlyLaunchedTasksAuthenticate": "Ćĥōōśē ĥōŵ ńēŵĺŷ ĺàũńćĥēď ţàśķś àũţĥēńţĩćàţē ţō ĜĩţĤũƀ. Ţĥĩś ďōēś ńōţ ćĥàńĝē ţĥē ŵōŕķśƥàćē àũţōḿàţĩōń ćōńńēćţĩōń àƀōvē.",
|
||||
@@ -30,7 +48,14 @@
|
||||
"cleanUpClosed": "Ćĺēàń ũƥ ćĺōśēď",
|
||||
"cleanUpMerged": "Ćĺēàń ũƥ ḿēŕĝēď",
|
||||
"cleaning": "Ćĺēàńĩńĝ…",
|
||||
"cleanupBehavior": "Ćĺēàńũƥ ƀēĥàvĩōŕ",
|
||||
"cleanupFailed": "Ćĺēàńũƥ ƒàĩĺēď",
|
||||
"cleanupPolicyAlways": "Àĺŵàŷś ďēĺēţē",
|
||||
"cleanupPolicyAlwaysDescription": "Àĺŵàŷś ďēĺēţē ōvēŕŕĩďēś ŕēţēńţĩōń: ďēĺēţē ōń ḿēŕĝē/ćĺōśē ēvēń ĩƒ ţĥē ţàśķ ĥàś ũśēŕ ēńĝàĝēḿēńţ ōŕ ēńàƀĺēď ƤŔ ĺĩƒēćŷćĺē ƥŕōḿƥţś.",
|
||||
"cleanupPolicyAuto": "Àũţō (ŕēćōḿḿēńďēď)",
|
||||
"cleanupPolicyAutoDescription": "Ŕēţàĩń ḿēŕĝēď/ćĺōśēď ƤŔ ţàśķś ŵĥēń ţĥēŷ ĥàvē ũśēŕ ēńĝàĝēḿēńţ ōŕ ēńàƀĺēď ƤŔ ĺĩƒēćŷćĺē ƥŕōḿƥţś; ōţĥēŕŵĩśē ďēĺēţē ţĥēḿ.",
|
||||
"cleanupPolicyNever": "Ńēvēŕ àũţō-ďēĺēţē",
|
||||
"cleanupPolicyNeverDescription": "Ķēēƥ àĺĺ ţàśķś. Ďēĺēţē ţĥēḿ ḿàńũàĺĺŷ ƒŕōḿ ţĥē ţàśķ ĺĩśţ.",
|
||||
"cliAccountActiveSuffix": " - àćţĩvē",
|
||||
"clientId": "Ćĺĩēńţ ĨĎ",
|
||||
"clientSecret": "Ćĺĩēńţ śēćŕēţ",
|
||||
@@ -43,16 +68,31 @@
|
||||
"controlsHowNewlyLaunchedTasksAuthenticate": "Ćōńţŕōĺś ĥōŵ ńēŵĺŷ ĺàũńćĥēď ţàśķś àũţĥēńţĩćàţē ţō ĜĩţĤũƀ ƒōŕ Ĝĩţ ĤŢŢƤŚ àńď ĝĥ ćōḿḿàńďś. Ḿàńàĝēď ŵōŕķśƥàćē ćŕēďēńţĩàĺś ũśē Ķàńďēv'ś ŵōŕķśƥàćē ćōńńēćţĩōń; ēxēćũţōŕ ćŕēďēńţĩàĺś ũśē ţĥē śēĺēćţēď ēxēćũţōŕ'ś Ĝĩţ ōŕ ŚŚĤ śēţũƥ.",
|
||||
"copy": "Ćōƥŷ {{label}}",
|
||||
"couldNotLoadGithubCliAccounts": "Ćōũĺď ńōţ ĺōàď ĜĩţĤũƀ ĆĹĨ àććōũńţś. {{error}}",
|
||||
"create": "Ćŕēàţē",
|
||||
"createAGithubApp": "Ćŕēàţē à ĜĩţĤũƀ Àƥƥ",
|
||||
"createIssueWatch": "Ćŕēàţē Ĩśśũē Ŵàţćĥ",
|
||||
"createNewApp": "Ćŕēàţē ńēŵ Àƥƥ",
|
||||
"createReviewWatch": "Ćŕēàţē Ŕēvĩēŵ Ŵàţćĥ",
|
||||
"created": "Ćŕēàţēď",
|
||||
"createdByKandev": "Ćŕēàţēď ƀŷ Ķàńďēv",
|
||||
"credentialUsedForRepositorySyncWatches": "Ćŕēďēńţĩàĺ ũśēď ƒōŕ ŕēƥōśĩţōŕŷ śŷńć, ŵàţćĥēś, àńď ḿàńàĝēď àĝēńţ ĝĩţ àńď ĝĥ ćōḿḿàńďś. Ēxēćũţōŕ ƥŕōƒĩĺē ţōķēńś ţàķē ƥŕēćēďēńćē.",
|
||||
"currentGithubApiRateAndQuery": "Ćũŕŕēńţ ĜĩţĤũƀ ÀƤĨ ŕàţē àńď qũēŕŷ ĺĩḿĩţś ƒōŕ ţĥĩś ŵōŕķśƥàćē ćōńńēćţĩōń.",
|
||||
"customQuery": "Ćũśţōḿ Qũēŕŷ",
|
||||
"defaultQueries": "Ďēƒàũĺţ qũēŕĩēś",
|
||||
"defaultQueriesAreStillLoading": "Ďēƒàũĺţ qũēŕĩēś àŕē śţĩĺĺ ĺōàďĩńĝ.",
|
||||
"defaultQueriesSaved": "Ďēƒàũĺţ qũēŕĩēś śàvēď",
|
||||
"delete": "Ďēĺēţē",
|
||||
"deletedTasks_one": "Ďēĺēţēď {{count}} ţàśķ",
|
||||
"deletedTasks_other": "Ďēĺēţēď {{count}} ţàśķś",
|
||||
"disconnect": "Ďĩśćōńńēćţ",
|
||||
"disconnectFailed": "Ďĩśćōńńēćţ ƒàĩĺēď",
|
||||
"eGBugEnhancementPriorityHigh": "ē.ĝ. ƀũĝ, ēńĥàńćēḿēńţ, ƥŕĩōŕĩţŷ:ĥĩĝĥ",
|
||||
"editDetails": "Ēďĩţ ďēţàĩĺś",
|
||||
"editIssueWatch": "Ēďĩţ Ĩśśũē Ŵàţćĥ",
|
||||
"editPrompt": "Ēďĩţ ƥŕōḿƥţ",
|
||||
"editReviewWatch": "Ēďĩţ Ŕēvĩēŵ Ŵàţćĥ",
|
||||
"enable": "Ēńàƀĺē",
|
||||
"enabled": "Ēńàƀĺēď",
|
||||
"enterANameUpTo100Characters": "Ēńţēŕ à ńàḿē ũƥ ţō 100 ćĥàŕàćţēŕś.",
|
||||
"enterAPublicHostNotLocalhost": "Ēńţēŕ à ƥũƀĺĩć ĥōśţ, ńōţ ĺōćàĺĥōśţ.",
|
||||
"enterAPublicHttpsOrigin": "Ēńţēŕ à ƥũƀĺĩć ĤŢŢƤŚ ōŕĩĝĩń.",
|
||||
@@ -63,6 +103,7 @@
|
||||
"enterTheGithubAppSlug": "Ēńţēŕ ţĥē ĜĩţĤũƀ Àƥƥ śĺũĝ.",
|
||||
"enterTheNumericGithubAppId": "Ēńţēŕ ţĥē ńũḿēŕĩć ĜĩţĤũƀ Àƥƥ ĨĎ.",
|
||||
"enterTheWebhookSecret": "Ēńţēŕ ţĥē ŵēƀĥōōķ śēćŕēţ.",
|
||||
"executorProfile": "Ēxēćũţōŕ Ƥŕōƒĩĺē",
|
||||
"explainHowManagedTaskCredentialsWork": "Ēxƥĺàĩń ĥōŵ ḿàńàĝēď ţàśķ ćŕēďēńţĩàĺś ŵōŕķ",
|
||||
"explainRepositoryScope": "Ēxƥĺàĩń ŕēƥōśĩţōŕŷ śćōƥē",
|
||||
"explainTaskGitAccess": "Ēxƥĺàĩń ţàśķ Ĝĩţ àććēśś",
|
||||
@@ -74,7 +115,10 @@
|
||||
"failedToLoadGithubWorkspaceSettings": "Ƒàĩĺēď ţō ĺōàď ĜĩţĤũƀ ŵōŕķśƥàćē śēţţĩńĝś",
|
||||
"failedToResetIssueWatch": "Ƒàĩĺēď ţō ŕēśēţ ĩśśũē ŵàţćĥ",
|
||||
"failedToResetReviewWatch": "Ƒàĩĺēď ţō ŕēśēţ ŕēvĩēŵ ŵàţćĥ",
|
||||
"failedToSaveDefaultQueries": "Ƒàĩĺēď ţō śàvē ďēƒàũĺţ qũēŕĩēś",
|
||||
"failedToSaveGithubWorkspaceSettings": "Ƒàĩĺēď ţō śàvē ĜĩţĤũƀ ŵōŕķśƥàćē śēţţĩńĝś",
|
||||
"failedToSaveQuickActions": "Ƒàĩĺēď ţō śàvē qũĩćķ àćţĩōńś",
|
||||
"filter": "Ƒĩĺţēŕ",
|
||||
"forWebhooksChooseContentType": "Ƒōŕ ŵēƀĥōōķś, ćĥōōśē <1>{{contentType}}</1> àś ţĥē ćōńţēńţ ţŷƥē àńď ķēēƥ ŚŚĹ vēŕĩƒĩćàţĩōń ēńàƀĺēď.",
|
||||
"foundNewIssues_one": "Ƒōũńď {{count}} ńēŵ ĩśśũē",
|
||||
"foundNewIssues_other": "Ƒōũńď {{count}} ńēŵ ĩśśũēś",
|
||||
@@ -103,25 +147,42 @@
|
||||
"githubIsReadyToCreateThe": "ĜĩţĤũƀ ĩś ŕēàďŷ ţō ćŕēàţē ţĥē Àƥƥ",
|
||||
"githubMustReachThisHttpsOrigin": "ĜĩţĤũƀ ḿũśţ ŕēàćĥ ţĥĩś ĤŢŢƤŚ ōŕĩĝĩń ƒōŕ śĩĝńēď ŵēƀĥōōķś àńď ćàĺĺƀàćķś.",
|
||||
"githubOwnerLogin": "ĜĩţĤũƀ ōŵńēŕ ĺōĝĩń",
|
||||
"githubSearchQuerySupportsFullGithub": "ĜĩţĤũƀ śēàŕćĥ qũēŕŷ. Śũƥƥōŕţś ƒũĺĺ ĜĩţĤũƀ śēàŕćĥ śŷńţàx ƒōŕ ḿàxĩḿũḿ ƒĺēxĩƀĩĺĩţŷ.",
|
||||
"githubSearchQueryWhenSetOverrides": "ĜĩţĤũƀ śēàŕćĥ qũēŕŷ. Ŵĥēń śēţ, ōvēŕŕĩďēś ţĥē ĺàƀēĺ ƒĩĺţēŕ àƀōvē.",
|
||||
"githubSetupWasNotCompleted": "ĜĩţĤũƀ śēţũƥ ŵàś ńōţ ćōḿƥĺēţēď",
|
||||
"githubWillShowTheGeneratedPermissions": "ĜĩţĤũƀ ŵĩĺĺ śĥōŵ ţĥē ĝēńēŕàţēď ƥēŕḿĩśśĩōńś àńď àśķ ŷōũ ţō ćōńƒĩŕḿ. Ŕēţũŕńĩńĝ ƒŕōḿ ĜĩţĤũƀ àďďś ţĥē ŕēĝĩśţŕàţĩōń; ĩńśţàĺĺĩńĝ ĩţ ƒōŕ ţĥĩś ŵōŕķśƥàćē ŕēḿàĩńś àń ēxƥĺĩćĩţ śţēƥ.",
|
||||
"githubWorkspaceSettingsSaved": "ĜĩţĤũƀ ŵōŕķśƥàćē śēţţĩńĝś śàvēď",
|
||||
"graphqlQueryLimit": "ĜŕàƥĥQĹ qũēŕŷ ĺĩḿĩţ",
|
||||
"group": "Ĝŕōũƥ",
|
||||
"hidePrompt": "Ĥĩďē ƥŕōḿƥţ",
|
||||
"hideToken": "Ĥĩďē ţōķēń",
|
||||
"hint": "Ĥĩńţ",
|
||||
"hintOptional": "Ĥĩńţ (ōƥţĩōńàĺ)",
|
||||
"howManagedTaskCredentialsWork": "Ĥōŵ ḿàńàĝēď ţàśķ ćŕēďēńţĩàĺś ŵōŕķ",
|
||||
"howOftenToCheckForNew": "Ĥōŵ ōƒţēń ţō ćĥēćķ ƒōŕ ńēŵ ĩśśũēś. Ḿĩńĩḿũḿ 60ś, ḿàxĩḿũḿ 3600ś.",
|
||||
"howOftenToCheckForNew2": "Ĥōŵ ōƒţēń ţō ćĥēćķ ƒōŕ ńēŵ ƤŔś. Ḿĩńĩḿũḿ 60ś, ḿàxĩḿũḿ 3600ś.",
|
||||
"icon": "Ĩćōń",
|
||||
"identityConnectionFailed": "Ĩďēńţĩţŷ ćōńńēćţĩōń ƒàĩĺēď",
|
||||
"importSetupCouldNotStart": "Ĩḿƥōŕţ śēţũƥ ćōũĺď ńōţ śţàŕţ",
|
||||
"imported": "Ĩḿƥōŕţēď",
|
||||
"inUse": "Ĩń ũśē",
|
||||
"inbox": "Ĩńƀōx",
|
||||
"inheritExecutorGitCredentials": "Ĩńĥēŕĩţ ēxēćũţōŕ Ĝĩţ ćŕēďēńţĩàĺś",
|
||||
"initialStepForNewTasks": "Ĩńĩţĩàĺ śţēƥ ƒōŕ ńēŵ ţàśķś.",
|
||||
"initialStepForNewTasksAuto": "Ĩńĩţĩàĺ śţēƥ ƒōŕ ńēŵ ţàśķś. Àũţō-śţàŕţ ĩś śēţ ōń ţĥē śţēƥ.",
|
||||
"installAGithubAppAboveTo": "Ĩńśţàĺĺ à ĜĩţĤũƀ Àƥƥ àƀōvē ţō ćĥàńĝē ţĥē ŵōŕķśƥàćē ćōńńēćţĩōń. Ţàśķ àććēśś ćàń ƀē śàvēď àƒţēŕ ţĥē ĩńśţàĺĺàţĩōń ƒĩńĩśĥēś.",
|
||||
"installForThisWorkspace": "Ĩńśţàĺĺ ƒōŕ ţĥĩś ŵōŕķśƥàćē",
|
||||
"interval": "Ĩńţēŕvàĺ",
|
||||
"issueCleanupPolicyAlwaysDescription": "Ďēĺēţē ōń ćĺōśē ēvēń ĩƒ ŷōũ ēńĝàĝēď ŵĩţĥ ţĥē ţàśķ.",
|
||||
"issueCleanupPolicyAutoDescription": "Ďēĺēţē ćĺōśēď-ĩśśũē ţàśķś ũńĺēśś ŷōũ ţŷƥēď à ḿēśśàĝē ĩń ţĥēḿ.",
|
||||
"issueWatch": "ĩśśũē ŵàţćĥ",
|
||||
"issueWatchCreated": "Ĩśśũē ŵàţćĥ ćŕēàţēď",
|
||||
"issueWatchDeleted": "Ĩśśũē ŵàţćĥ ďēĺēţēď",
|
||||
"issueWatchNotFound": "Ĩśśũē ŵàţćĥ ńōţ ƒōũńď",
|
||||
"issueWatchPromptHelp": "Ţĥē ƥŕōḿƥţ śēńţ ţō ţĥē àĝēńţ ƒōŕ ēàćĥ ńēŵ ĩśśũē. Ţŷƥē {{token}} ţō ĩńśēŕţ ƥĺàćēĥōĺďēŕś.",
|
||||
"issueWatchUpdated": "Ĩśśũē ŵàţćĥ ũƥďàţēď",
|
||||
"issueWatches": "Ĩśśũē Ŵàţćĥēś",
|
||||
"issues": "Ĩśśũēś",
|
||||
"kandevBrokersTheWorkspacePatNamed": "Ķàńďēv ƀŕōķēŕś ţĥē ŵōŕķśƥàćē ƤÀŢ, ńàḿēď ĜĩţĤũƀ ĆĹĨ àććōũńţ, ōŕ Àƥƥ ĩďēńţĩţŷ ţō ţĥĩś ţàśķ ƒōŕ ĜĩţĤũƀ ĤŢŢƤŚ àńď ĝĥ.",
|
||||
"kandevFirstReservesExactCallbackAnd": "Ķàńďēv ƒĩŕśţ ŕēśēŕvēś ēxàćţ ćàĺĺƀàćķ àńď ŵēƀĥōōķ ŨŔĹś, ţĥēń ĝũĩďēś ŷōũ ţĥŕōũĝĥ ĜĩţĤũƀ'ś Àƥƥ śēţţĩńĝś. Ēxĩśţĩńĝ ŵōŕķśƥàćē àććēśś ĩś ũńćĥàńĝēď ũńţĩĺ ŷōũ ĩńśţàĺĺ ţĥē ĩḿƥōŕţēď Àƥƥ.",
|
||||
"kandevGeneratesTheExactAppPolicy": "Ķàńďēv ĝēńēŕàţēś ţĥē ēxàćţ Àƥƥ ƥōĺĩćŷ. Ŷōũ ćōńƒĩŕḿ ćŕēàţĩōń ōń ĜĩţĤũƀ; Ķàńďēv ţĥēń śţōŕēś ţĥĩś ŕēĝĩśţŕàţĩōń śō ĩţ ćàń ƀē śēĺēćţēď ƀŷ à ŵōŕķśƥàćē.",
|
||||
@@ -129,55 +190,108 @@
|
||||
"kandevManagedOperationsUseTheGithub": "Ķàńďēv-ḿàńàĝēď ōƥēŕàţĩōńś ũśē ţĥē ĜĩţĤũƀ Àƥƥ ĩńśţàĺĺēď ƒōŕ {{actor}}.",
|
||||
"kandevStoresThisTokenForThis": "Ķàńďēv śţōŕēś ţĥĩś ţōķēń ƒōŕ ţĥĩś ŵōŕķśƥàćē. ĜĩţĤũƀ ŕēćōŕďś àćţĩōńś àś ţĥē ţōķēń ōŵńēŕ.",
|
||||
"kandevUsesThisWorkspaceConnectionFor": "Ķàńďēv ũśēś ţĥĩś ŵōŕķśƥàćē ćōńńēćţĩōń ƒōŕ ŕēƥōśĩţōŕŷ śŷńć, ŵàţćĥēś, ƀàćķĝŕōũńď ĵōƀś, àńď ḿàńàĝēď àĝēńţ ĜĩţĤũƀ ćōḿḿàńďś. Ŵĩţĥ à ƤÀŢ ōŕ ĜĩţĤũƀ ĆĹĨ àććōũńţ, Ḿŷ ĜĩţĤũƀ àńď àćţĩōńś ŷōũ ţŕĩĝĝēŕ ũśē ţĥē śàḿē ĥũḿàń ĩďēńţĩţŷ.",
|
||||
"label": "Ĺàƀēĺ",
|
||||
"labels": "Ĺàƀēĺś",
|
||||
"labelsCommaSeparated": "Ĺàƀēĺś (ćōḿḿà-śēƥàŕàţēď)",
|
||||
"lastPolled": "Ĺàśţ Ƥōĺĺēď",
|
||||
"legacySharedConnection": "Ĺēĝàćŷ śĥàŕēď ćōńńēćţĩōń",
|
||||
"limitsGithubPullRequestsAndIssues": "Ĺĩḿĩţś ĜĩţĤũƀ ƥũĺĺ ŕēqũēśţś àńď ĩśśũēś śĥōŵń ōŕ ĩḿƥōŕţēď ĩń ţĥĩś ŵōŕķśƥàćē.",
|
||||
"loadingAccounts": "Ĺōàďĩńĝ àććōũńţś...",
|
||||
"loadingOrganizations": "Ĺōàďĩńĝ ōŕĝàńĩźàţĩōńś...",
|
||||
"loadingRegisteredApps": "Ĺōàďĩńĝ ŕēĝĩśţēŕēď Àƥƥś...",
|
||||
"loadingTaskAccess": "Ĺōàďĩńĝ ţàśķ àććēśś…",
|
||||
"localAndWorktreeTasksUseHost": "Ĺōćàĺ àńď Ŵōŕķţŕēē ţàśķś ũśē ĥōśţ-vĩśĩƀĺē Ĝĩţ ōŕ ŚŚĤ ćŕēďēńţĩàĺś. Ďōćķēŕ, ŚŚĤ, àńď ćĺōũď ţàśķś ũśē ćŕēďēńţĩàĺś ćōńƒĩĝũŕēď ĩń ţĥàţ ēxēćũţōŕ.",
|
||||
"managedWorkspaceCredentials": "Ḿàńàĝēď ŵōŕķśƥàćē ćŕēďēńţĩàĺś",
|
||||
"me": "Ḿē",
|
||||
"meMyTeams": "Ḿē & ḿŷ ţēàḿś",
|
||||
"missing": "Ḿĩśśĩńĝ",
|
||||
"mode": "Ḿōďē",
|
||||
"myGithubIdentity": "Ḿŷ ĜĩţĤũƀ ĩďēńţĩţŷ",
|
||||
"myGithubNowUsesYourVerified": "Ḿŷ ĜĩţĤũƀ ńōŵ ũśēś ŷōũŕ vēŕĩƒĩēď ƥēŕśōńàĺ ĩďēńţĩţŷ.",
|
||||
"nameInKandev": "Ńàḿē ĩń Ķàńďēv",
|
||||
"needsAttention": "Ńēēďś àţţēńţĩōń",
|
||||
"never": "Ńēvēŕ",
|
||||
"noAutomationConnection": "Ńō àũţōḿàţĩōń ćōńńēćţĩōń",
|
||||
"noGhAccountsFound": "Ńō ĝĥ àććōũńţś ƒōũńď",
|
||||
"noGithubAppsAreRegisteredYet": "Ńō ĜĩţĤũƀ Àƥƥś àŕē ŕēĝĩśţēŕēď ŷēţ. Àďď àń ēxĩśţĩńĝ Àƥƥ ōŕ ćŕēàţē ōńē ƀēĺōŵ.",
|
||||
"noIssueWatchesConfiguredCreateOne": "Ńō ĩśśũē ŵàţćĥēś ćōńƒĩĝũŕēď. Ćŕēàţē ōńē ţō śţàŕţ ḿōńĩţōŕĩńĝ ĜĩţĤũƀ ĩśśũēś.",
|
||||
"noNewIssuesFound": "Ńō ńēŵ ĩśśũēś ƒōũńď",
|
||||
"noNewPrsFound": "Ńō ńēŵ ƤŔś ƒōũńď",
|
||||
"noReposFoundFor": "Ńō ŕēƥōś ƒōũńď ƒōŕ \"{{org}}\"",
|
||||
"noReviewWatchesConfiguredCreateOne": "Ńō ŕēvĩēŵ ŵàţćĥēś ćōńƒĩĝũŕēď. Ćŕēàţē ōńē ţō śţàŕţ ḿōńĩţōŕĩńĝ ƤŔś.",
|
||||
"noStatsAvailableYet": "Ńō śţàţś àvàĩĺàƀĺē ŷēţ.",
|
||||
"noTasksToCleanUp": "Ńō ţàśķś ţō ćĺēàń ũƥ",
|
||||
"onlyMatchIssuesWithTheseLabels": "Ōńĺŷ ḿàţćĥ ĩśśũēś ŵĩţĥ ţĥēśē ĺàƀēĺś. Ĺēàvē ēḿƥţŷ ƒōŕ àĺĺ ĩśśũēś.",
|
||||
"onlyPrsWhereYouAreExplicitly": "Ōńĺŷ ƤŔś ŵĥēŕē ŷōũ àŕē ēxƥĺĩćĩţĺŷ ŕēqũēśţēď àś à ŕēvĩēŵēŕ (ńōţ vĩà ţēàḿ ḿēḿƀēŕśĥĩƥ)",
|
||||
"onlyTheAppOwner": "Ōńĺŷ ţĥē Àƥƥ ōŵńēŕ",
|
||||
"openGithubAppSettings": "Ōƥēń ĜĩţĤũƀ Àƥƥ śēţţĩńĝś",
|
||||
"optionalFallsBackToStepDefault": "Ōƥţĩōńàĺ — ƒàĺĺś ƀàćķ ţō śţēƥ ďēƒàũĺţ.",
|
||||
"optionalFallsBackToStepDefault2": "Ōƥţĩōńàĺ — ƒàĺĺś ƀàćķ ţō śţēƥ ďēƒàũĺţ. Ţĥē ēxēćũţōŕ ēńvĩŕōńḿēńţ ŵĥēŕē ţĥē àĝēńţ ŵĩĺĺ ŕũń.",
|
||||
"optionallyConnectYourGithubUserFor": "Ōƥţĩōńàĺĺŷ ćōńńēćţ ŷōũŕ ĜĩţĤũƀ ũśēŕ ƒōŕ Ḿŷ ĜĩţĤũƀ àńď ĥũḿàń-àţţŕĩƀũţēď àćţĩōńś. Ţĥĩś ĩďēńţĩţŷ ĩś ńēvēŕ ĝĩvēń ţō ḿàńàĝēď àĝēńţś; ŵōŕķśƥàćē àũţōḿàţĩōń ćōńţĩńũēś àś ţĥē Àƥƥ.",
|
||||
"organization": "Ōŕĝàńĩźàţĩōń",
|
||||
"organizations": "Ōŕĝàńĩźàţĩōńś",
|
||||
"otherGithubAccounts": "Ōţĥēŕ ĜĩţĤũƀ àććōũńţś",
|
||||
"pasteTheAppPrivateKey": "Ƥàśţē ţĥē Àƥƥ ƥŕĩvàţē ķēŷ.",
|
||||
"pause": "Ƥàũśē",
|
||||
"pauseOrResumePolling": "Ƥàũśē ōŕ ŕēśũḿē ƥōĺĺĩńĝ.",
|
||||
"paused": "Ƥàũśēď",
|
||||
"permissionsAvailableToKandevThroughThis": "Ƥēŕḿĩśśĩōńś àvàĩĺàƀĺē ţō Ķàńďēv ţĥŕōũĝĥ ţĥĩś ŵōŕķśƥàćē'ś ĜĩţĤũƀ Àƥƥ ĩńśţàĺĺàţĩōń.",
|
||||
"personalAccessToken": "Ƥēŕśōńàĺ àććēśś ţōķēń",
|
||||
"personalAccount": "Ƥēŕśōńàĺ àććōũńţ",
|
||||
"personalGithubIdentityDisconnected": "Ƥēŕśōńàĺ ĜĩţĤũƀ ĩďēńţĩţŷ ďĩśćōńńēćţēď",
|
||||
"personalOauth": "Ƥēŕśōńàĺ ŌÀũţĥ",
|
||||
"placeholderActionTitle": "Ţĩţĺē ōƒ ţĥē ƤŔ ōŕ ĩśśũē",
|
||||
"placeholderActionUrl": "ŨŔĹ ōƒ ţĥē ƤŔ ōŕ ĩśśũē",
|
||||
"placeholderCommaSeparatedLabels": "Ćōḿḿà-śēƥàŕàţēď ĺàƀēĺś",
|
||||
"placeholderIssueAuthor": "Ĩśśũē àũţĥōŕ ũśēŕńàḿē",
|
||||
"placeholderIssueBody": "Ĩśśũē ƀōďŷ ţēxţ",
|
||||
"placeholderIssueNumber": "Ĩśśũē ńũḿƀēŕ",
|
||||
"placeholderIssueTitle": "Ĩśśũē ţĩţĺē",
|
||||
"placeholderIssueUrl": "Ĩśśũē ŨŔĹ",
|
||||
"placeholderPrAuthor": "ƤŔ àũţĥōŕ ũśēŕńàḿē",
|
||||
"placeholderPrNumber": "ƤŔ ńũḿƀēŕ",
|
||||
"placeholderPrTitle": "ƤŔ ţĩţĺē",
|
||||
"placeholderPrUrl": "ƤŔ ŨŔĹ",
|
||||
"placeholderRepository": "Ŕēƥōśĩţōŕŷ (ōŵńēŕ/ńàḿē)",
|
||||
"placeholderSourceBranch": "Śōũŕćē ƀŕàńćĥ",
|
||||
"placeholderTargetBranch": "Ţàŕĝēţ ƀŕàńćĥ",
|
||||
"pollIntervalSeconds": "Ƥōĺĺ Ĩńţēŕvàĺ (śēćōńďś)",
|
||||
"prAnalytics": "ƤŔ Àńàĺŷţĩćś",
|
||||
"prepareAppOnGithub": "Ƥŕēƥàŕē Àƥƥ ōń ĜĩţĤũƀ",
|
||||
"privateKeyPem": "Ƥŕĩvàţē ķēŷ (.ƥēḿ)",
|
||||
"presetIconBug": "Ɓũĝ",
|
||||
"presetIconCheck": "Ćĥēćķ",
|
||||
"presetIconCode": "Ćōďē",
|
||||
"presetIconEye": "Ēŷē",
|
||||
"presetIconMessage": "Ḿēśśàĝē",
|
||||
"presetIconSearch": "Śēàŕćĥ",
|
||||
"presetIconSparkle": "Śƥàŕķĺē",
|
||||
"presetIconTool": "Ţōōĺ",
|
||||
"private": "ƥŕĩvàţē",
|
||||
"privateKeyPem": "Ƥŕĩvàţē ķēŷ ({{extension}})",
|
||||
"promptsShownOnGithubWhenStarting": "Ƥŕōḿƥţś śĥōŵń ōń {{route}} ŵĥēń śţàŕţĩńĝ à ţàśķ ƒŕōḿ à ƤŔ ōŕ ĩśśũē.",
|
||||
"prsByDay": "ƤŔś ƀŷ Ďàŷ",
|
||||
"prsCreated": "ƤŔś Ćŕēàţēď",
|
||||
"prsOnDay_one": "{{date}}: {{count}} ƤŔ",
|
||||
"prsOnDay_other": "{{date}}: {{count}} ƤŔś",
|
||||
"prsReviewed": "ƤŔś Ŕēvĩēŵēď",
|
||||
"prsWhereYouOrAnyOf": "ƤŔś ŵĥēŕē ŷōũ ōŕ àńŷ ōƒ ŷōũŕ ţēàḿś àŕē ŕēqũēśţēď àś ŕēvĩēŵēŕś",
|
||||
"publicKandevUrl": "Ƥũƀĺĩć Ķàńďēv ŨŔĹ",
|
||||
"pullRequestActivityForThisWorkspace": "Ƥũĺĺ ŕēqũēśţ àćţĩvĩţŷ ƒōŕ ţĥĩś ŵōŕķśƥàćē.",
|
||||
"pullRequests": "Ƥũĺĺ ŕēqũēśţś",
|
||||
"query": "Qũēŕŷ",
|
||||
"queryExample": "ē.ĝ. {{query}}",
|
||||
"quickActions": "Qũĩćķ àćţĩōńś",
|
||||
"quickActionsAreStillLoading": "Qũĩćķ àćţĩōńś àŕē śţĩĺĺ ĺōàďĩńĝ.",
|
||||
"quickActionsSaved": "Qũĩćķ àćţĩōńś śàvēď",
|
||||
"rateLimitRemaining": "{{remaining}} ōƒ {{limit}} {{unit}} ŕēḿàĩńĩńĝ",
|
||||
"rateLimitUnitPoints": "ƥōĩńţś",
|
||||
"rateLimitUnitRequests": "ŕēqũēśţś",
|
||||
"rateLimitUnknown": "ũńķńōŵń",
|
||||
"reconnectIdentity": "Ŕēćōńńēćţ ĩďēńţĩţŷ",
|
||||
"refreshGithubConnection": "Ŕēƒŕēśĥ ĜĩţĤũƀ ćōńńēćţĩōń",
|
||||
"remove": "Ŕēḿōvē",
|
||||
"repositories": "Ŕēƥōśĩţōŕĩēś",
|
||||
"repository": "Ŕēƥōśĩţōŕŷ",
|
||||
"repositoryPermissions": "Ŕēƥōśĩţōŕŷ ƥēŕḿĩśśĩōńś",
|
||||
"repositoryScope": "Ŕēƥōśĩţōŕŷ Śćōƥē",
|
||||
"repositoryScopeHelp": "Ĺĩḿĩţś ţĥē ĜĩţĤũƀ ƥũĺĺ ŕēqũēśţś àńď ĩśśũēś Ķàńďēv ďĩśćōvēŕś ƒōŕ ţĥĩś ŵōŕķśƥàćē, ĩńćĺũďĩńĝ Ḿŷ ĜĩţĤũƀ ŕēśũĺţś àńď ŕēvĩēŵ àńď ĩśśũē ŵàţćĥēś. Ĩţ ďōēś ńōţ ćĥàńĝē ĜĩţĤũƀ ƥēŕḿĩśśĩōńś ōŕ ŕēƥōśĩţōŕŷ àććēśś.",
|
||||
@@ -185,6 +299,7 @@
|
||||
"resetCompleteDeletedTasks_one": "Ŕēśēţ ćōḿƥĺēţē — ďēĺēţēď {{count}} ţàśķ; ńēxţ ƥōĺĺ ŵĩĺĺ ŕē-ĩḿƥōŕţ.",
|
||||
"resetCompleteDeletedTasks_other": "Ŕēśēţ ćōḿƥĺēţē — ďēĺēţēď {{count}} ţàśķś; ńēxţ ƥōĺĺ ŵĩĺĺ ŕē-ĩḿƥōŕţ.",
|
||||
"resetCompleteNextPollWillRe": "Ŕēśēţ ćōḿƥĺēţē — ńēxţ ƥōĺĺ ŵĩĺĺ ŕē-ĩḿƥōŕţ ḿàţćĥēś.",
|
||||
"resetWatch": "Ŕēśēţ ŵàţćĥ",
|
||||
"resets": "· ŕēśēţś {{reset}}",
|
||||
"resolvedFromOneNamedHostAccount": "Ŕēśōĺvēď ƒŕōḿ ōńē ńàḿēď ĥōśţ àććōũńţ ŵĥēń ńēēďēď. Ḿàńàĝēď ţàśķś ŕēćēĩvē à śćōƥēď ƀŕōķēŕēď ĩďēńţĩţŷ; ĥōśţ Ĝĩţ ĩś ńōţ ĩńĥēŕĩţēď ũńĺēśś śēĺēćţēď ƀēĺōŵ.",
|
||||
"reviewPermissions": "Ŕēvĩēŵ ƥēŕḿĩśśĩōńś",
|
||||
@@ -192,15 +307,22 @@
|
||||
"reviewWatchCreated": "Ŕēvĩēŵ ŵàţćĥ ćŕēàţēď",
|
||||
"reviewWatchDeleted": "Ŕēvĩēŵ ŵàţćĥ ďēĺēţēď",
|
||||
"reviewWatchNotFound": "Ŕēvĩēŵ ŵàţćĥ ńōţ ƒōũńď",
|
||||
"reviewWatchPromptHelp": "Ţĥē ƥŕōḿƥţ śēńţ ţō ţĥē àĝēńţ ƒōŕ ēàćĥ ńēŵ ƤŔ. Ţŷƥē {{token}} ţō ĩńśēŕţ ƥĺàćēĥōĺďēŕś, ōŕ {{mention}} ţō ŕēƒēŕēńćē à śàvēď ƥŕōḿƥţ ƀŷ ńàḿē — ĩţ ēxƥàńďś ţĥē śàḿē ŵàŷ ĩţ ďōēś ĩń ŵōŕķƒĺōŵ śţēƥ ƥŕōḿƥţś, śĩńćē ţĥĩś ƥŕōḿƥţ ƀēćōḿēś ţĥē ţàśķ ďēśćŕĩƥţĩōń ƥàśśēď ţĥŕōũĝĥ ţĥàţ śàḿē àśśēḿƀĺŷ.",
|
||||
"reviewWatchUpdated": "Ŕēvĩēŵ ŵàţćĥ ũƥďàţēď",
|
||||
"reviewWatches": "Ŕēvĩēŵ Ŵàţćĥēś",
|
||||
"saveChanges": "Śàvē ćĥàńĝēś",
|
||||
"saving": "Śàvĩńĝ...",
|
||||
"savingChanges": "Śàvĩńĝ ćĥàńĝēś…",
|
||||
"searchLimit": "Śēàŕćĥ ĺĩḿĩţ",
|
||||
"searchQuery": "Śēàŕćĥ Qũēŕŷ",
|
||||
"searching": "Śēàŕćĥĩńĝ...",
|
||||
"selectWorkflow": "Śēĺēćţ ŵōŕķƒĺōŵ",
|
||||
"selectWorkspace": "Śēĺēćţ ŵōŕķśƥàćē",
|
||||
"selectedRepositories": "Śēĺēćţēď ŕēƥōśĩţōŕĩēś",
|
||||
"setTheseExactUrlsCreateAClientSecret": "Śēţ ţĥēśē ēxàćţ ŨŔĹś, ćŕēàţē à ćĺĩēńţ śēćŕēţ àńď ŵēƀĥōōķ śēćŕēţ, àńď ďōŵńĺōàď à ƥŕĩvàţē ķēŷ. Ţĥĩś ōńē-ţĩḿē śēţũƥ ēxƥĩŕēś {{expiresAt}}.",
|
||||
"showGithubApiLimits": "Śĥōŵ ĜĩţĤũƀ ÀƤĨ ĺĩḿĩţś",
|
||||
"showToken": "Śĥōŵ ţōķēń",
|
||||
"sidebarQueriesShownOnGithubFor": "Śĩďēƀàŕ qũēŕĩēś śĥōŵń ōń {{route}} ƒōŕ ƥũĺĺ ŕēqũēśţś àńď ĩśśũēś.",
|
||||
"signInWithGhAuthLogin": "Śĩĝń ĩń ŵĩţĥ <1>{{command}}</1>, ţĥēń ŕēōƥēń ţĥĩś ďĩàĺōĝ.",
|
||||
"someSettingsWereSavedButAnother": "Śōḿē śēţţĩńĝś ŵēŕē śàvēď, ƀũţ àńōţĥēŕ ćĥàńĝē ƒàĩĺēď: {{detail}}",
|
||||
"startOver": "Śţàŕţ ōvēŕ",
|
||||
@@ -210,18 +332,24 @@
|
||||
"taskAccess": "Ţàśķ àććēśś:",
|
||||
"taskAccessUnavailable": "Ţàśķ àććēśś ũńàvàĩĺàƀĺē",
|
||||
"taskGitAccess": "Ţàśķ Ĝĩţ àććēśś",
|
||||
"taskPrompt": "Ţàśķ Ƥŕōḿƥţ",
|
||||
"tasksCreatedByThisWatcherLand": "Ţàśķś ćŕēàţēď ƀŷ ţĥĩś ŵàţćĥēŕ ĺàńď ĩń ţĥē śēĺēćţēď ŵōŕķśƥàćē.",
|
||||
"theAppIsReadyToSelect": "Ţĥē Àƥƥ ĩś ŕēàďŷ ţō śēĺēćţ àńď ĩńśţàĺĺ ƒōŕ ţĥĩś ŵōŕķśƥàćē.",
|
||||
"theExistingWorkspaceConnectionWasNot": "Ţĥē ēxĩśţĩńĝ ŵōŕķśƥàćē ćōńńēćţĩōń ŵàś ńōţ ćĥàńĝēď.",
|
||||
"theGithubResponseCouldNotBe": "Ţĥē ĜĩţĤũƀ ŕēśƥōńśē ćōũĺď ńōţ ƀē vēŕĩƒĩēď. Ŕēvĩēŵ ţĥē ćōńńēćţĩōń àńď ţŕŷ àĝàĩń.",
|
||||
"theRepositoryWillBeAutomaticallyCloned": "Ţĥē ŕēƥōśĩţōŕŷ ŵĩĺĺ ƀē àũţōḿàţĩćàĺĺŷ ćĺōńēď ţō {{path}} ĩƒ ĩţ ĩś ńōţ àĺŕēàďŷ ƥŕēśēńţ ĩń ţĥē ŵōŕķśƥàćē.",
|
||||
"theSettingsCouldNotBeSaved": "Ţĥē śēţţĩńĝś ćōũĺď ńōţ ƀē śàvēď",
|
||||
"theStoredAppCredentialsAreUnavailable": "Ţĥē śţōŕēď Àƥƥ ćŕēďēńţĩàĺś àŕē ũńàvàĩĺàƀĺē.",
|
||||
"theWorkflowToCreateTasksIn": "Ţĥē ŵōŕķƒĺōŵ ţō ćŕēàţē ţàśķś ĩń.",
|
||||
"theWorkspaceChangedBeforeTaskAccess": "Ţĥē ŵōŕķśƥàćē ćĥàńĝēď ƀēƒōŕē ţàśķ àććēśś ŵàś śàvēď",
|
||||
"thisAccountAlsoPowersMyGithub": "Ţĥĩś àććōũńţ àĺśō ƥōŵēŕś Ḿŷ ĜĩţĤũƀ àńď ũśēŕ-ţŕĩĝĝēŕēď àćţĩōńś.",
|
||||
"thisAppRegistrationIsShared_one": "Ţĥĩś Àƥƥ ŕēĝĩśţŕàţĩōń ĩś śĥàŕēď ƀŷ {{count}} ŵōŕķśƥàćē. Ŕēĝĩśţŕàţĩōń ćĥàńĝēś àƒƒēćţ ēvēŕŷ ŵōŕķśƥàćē ũśĩńĝ ĩţ; ĩńśţàĺĺàţĩōń àććēśś ŕēḿàĩńś ŵōŕķśƥàćē-śƥēćĩƒĩć.",
|
||||
"thisAppRegistrationIsShared_other": "Ţĥĩś Àƥƥ ŕēĝĩśţŕàţĩōń ĩś śĥàŕēď ƀŷ {{count}} ŵōŕķśƥàćēś. Ŕēĝĩśţŕàţĩōń ćĥàńĝēś àƒƒēćţ ēvēŕŷ ŵōŕķśƥàćē ũśĩńĝ ĩţ; ĩńśţàĺĺàţĩōń àććēśś ŕēḿàĩńś ŵōŕķśƥàćē-śƥēćĩƒĩć.",
|
||||
"thisWorkspaceUsesOneCredentialFor": "Ţĥĩś ŵōŕķśƥàćē ũśēś ōńē ćŕēďēńţĩàĺ ƒōŕ ŕēƥōśĩţōŕŷ śŷńć, ŵàţćĥēś, ƀàćķĝŕōũńď ĵōƀś, àńď ḿàńàĝēď àĝēńţ ĜĩţĤũƀ àććēśś. Ēxēćũţōŕ ƥŕōƒĩĺē ćŕēďēńţĩàĺś ćàń śţĩĺĺ ţàķē ƥŕēćēďēńćē.",
|
||||
"typeOwnerRepoToSearch": "Ţŷƥē {{format}} ţō śēàŕćĥ",
|
||||
"unableToLoadTheCurrentTask": "Ũńàƀĺē ţō ĺōàď ţĥē ćũŕŕēńţ ţàśķ àććēśś śēţţĩńĝ.",
|
||||
"unexpectedResponseFromTheServer": "Ũńēxƥēćţēď ŕēśƥōńśē ƒŕōḿ ţĥē śēŕvēŕ",
|
||||
"update": "Ũƥďàţē",
|
||||
"useAnAppWhenAutomationNeeds": "Ũśē àń Àƥƥ ŵĥēń àũţōḿàţĩōń ńēēďś ĩţś ōŵń ĩďēńţĩţŷ, śĥōŕţ-ĺĩvēď ţōķēńś, àńď ŕēƥōśĩţōŕŷ-ĺēvēĺ ĩńśţàĺĺàţĩōń ćōńţŕōĺ. Śēţũƥ ĩś ḿōŕē ĩńvōĺvēď àńď ŕēqũĩŕēś à ƥũƀĺĩćĺŷ ŕēàćĥàƀĺē ĤŢŢƤŚ ŨŔĹ.",
|
||||
"useCommaSeparatedOwnerRepoValues": "Ũśē ćōḿḿà-śēƥàŕàţēď ōŵńēŕ/ŕēƥō vàĺũēś.",
|
||||
"usedByWorkspaces_one": "Ũśēď ƀŷ {{count}} ŵōŕķśƥàćē. Ēďĩţĩńĝ ōŕ ďēĺēţĩńĝ ĩţ àƒƒēćţś àĺĺ ōƒ ţĥēḿ.",
|
||||
@@ -230,8 +358,12 @@
|
||||
"viewPermissions": "Vĩēŵ ƥēŕḿĩśśĩōńś",
|
||||
"webhook": "Ŵēƀĥōōķ",
|
||||
"webhookSecret": "Ŵēƀĥōōķ śēćŕēţ",
|
||||
"whichAllowedGithubRepositoriesToMonitor": "Ŵĥĩćĥ àĺĺōŵēď ĜĩţĤũƀ ŕēƥōśĩţōŕĩēś ţō ḿōńĩţōŕ ƒōŕ ţĥĩś ŵàţćĥ.",
|
||||
"whoCanInstallThisApp": "Ŵĥō ćàń ĩńśţàĺĺ ţĥĩś Àƥƥ?",
|
||||
"withManagedWorkspaceCredentialsKandevConfigures": "Ŵĩţĥ Ḿàńàĝēď ŵōŕķśƥàćē ćŕēďēńţĩàĺś, Ķàńďēv ćōńƒĩĝũŕēś àĝēńţćţĺ àś Ĝĩţ'ś ćŕēďēńţĩàĺ ĥēĺƥēŕ ĩńśĩďē ńēŵĺŷ ĺàũńćĥēď ţàśķ ƥŕōćēśśēś. Ŵĥēń Ĝĩţ ŕēqũēśţś ćŕēďēńţĩàĺś ƒōŕ àń àţţàćĥēď ĜĩţĤũƀ ĤŢŢƤŚ ŕēƥōśĩţōŕŷ, ţĥē ĥēĺƥēŕ ŕēďēēḿś à ţàśķ- àńď ŕēƥōśĩţōŕŷ-śćōƥēď ƀŕōķēŕ ĺēàśē. Ţĥē ćŕēďēńţĩàĺ ĩś ŕēţũŕńēď ţō Ĝĩţ ōń ďēḿàńď àńď ĩś ńōţ ŵŕĩţţēń ţō ţĥē ŕēƥōśĩţōŕŷ ōŕ Ĝĩţ ćōńƒĩĝ. Ţĥē ĝĥ ćōḿḿàńď ũśēś Ķàńďēv'ś ƀŕōķēŕ-àŵàŕē śĥĩḿ. Ēxēćũţōŕ ĩńĥēŕĩţàńćē śķĩƥś ƀōţĥ àńď ũśēś ćŕēďēńţĩàĺś vĩśĩƀĺē ĩń ţĥē śēĺēćţēď ēxēćũţōŕ. Ćĥàńĝēś àƥƥĺŷ ţō ńēŵ ţàśķ ĺàũńćĥēś àńď ţĥē ńēxţ ŕēśũḿē, ńōţ àĺŕēàďŷ-ŕũńńĩńĝ ƥŕōćēśśēś.",
|
||||
"workflow": "Ŵōŕķƒĺōŵ",
|
||||
"workflowStep": "Ŵōŕķƒĺōŵ Śţēƥ",
|
||||
"workflowStepPromptWrapHelp": "Ţĥē ŵōŕķƒĺōŵ śţēƥ ƥŕōḿƥţ ŵŕàƥś ţĥĩś ƥŕōḿƥţ. Ƒōŕ ēxàḿƥĺē, ĩƒ ţĥē śţēƥ ƥŕōḿƥţ ĩś <2>{{stepPrompt}}</2>, ţĥē ƒĩńàĺ ƥŕōḿƥţ ƀēćōḿēś <5>{{finalPrompt}}</5>",
|
||||
"workspaceAutomationNowUsesTheVerified": "Ŵōŕķśƥàćē àũţōḿàţĩōń ńōŵ ũśēś ţĥē vēŕĩƒĩēď Àƥƥ ĩńśţàĺĺàţĩōń.",
|
||||
"workspaceGithubAccess": "Ŵōŕķśƥàćē ĜĩţĤũƀ àććēśś",
|
||||
"workspaceGithubConnectionRemoved": "Ŵōŕķśƥàćē ĜĩţĤũƀ ćōńńēćţĩōń ŕēḿōvēď",
|
||||
|
||||
Reference in New Issue
Block a user