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:
João Salavisa
2026-08-02 15:03:04 +02:00
committed by GitHub
parent 0e82887ef6
commit 5b12a37b0c
20 changed files with 871 additions and 391 deletions
@@ -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 &quot;{org}&quot;</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.
];
/**
+24
View File
@@ -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",
];
+3 -3
View File
@@ -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",
+133 -1
View File
@@ -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",
+1 -1
View File
@@ -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": "Śĥēĺĺ",
+133 -1
View File
@@ -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ēď",