diff --git a/apps/web/components/branch-refresh-button.tsx b/apps/web/components/branch-refresh-button.tsx index c7250725ff..f463c5c894 100644 --- a/apps/web/components/branch-refresh-button.tsx +++ b/apps/web/components/branch-refresh-button.tsx @@ -3,6 +3,8 @@ import { useState } from "react"; import { IconRefresh } from "@tabler/icons-react"; import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip"; +import { useTranslation } from "react-i18next"; +import type { TFunction } from "i18next"; type BranchRefreshButtonProps = { onRefresh: () => void; @@ -23,18 +25,19 @@ export function BranchRefreshButton({ testId = "branch-refresh-button", touchTarget = false, }: BranchRefreshButtonProps) { + const { t } = useTranslation(); // Controlled open so the tooltip only reacts to hover, not focus. // Radix Popover auto-focuses the first focusable child when it opens, which // would otherwise trigger this tooltip the moment the dropdown is opened. const [open, setOpen] = useState(false); const hasError = Boolean(fetchError); - const tooltip = formatRefreshTooltip(label, fetchedAt, refreshing, fetchError); + const tooltip = formatRefreshTooltip(t, label, fetchedAt, refreshing, fetchError); return ( )} {inputCommand?.inputPlaceholder ?? "Enter a value..."} + {inputCommand?.inputPlaceholder ?? t("common:enterAValue")} ) : ( - Press Enter to confirm + {t("common:pressEnterToConfirm")} ))} ); diff --git a/apps/web/components/command-panel-scope-switcher.tsx b/apps/web/components/command-panel-scope-switcher.tsx index 6f1586ee06..47f94dfd16 100644 --- a/apps/web/components/command-panel-scope-switcher.tsx +++ b/apps/web/components/command-panel-scope-switcher.tsx @@ -6,21 +6,27 @@ import type { ConfigurableShortcutId } from "@/lib/keyboard/shortcut-overrides"; import { getShortcut } from "@/lib/keyboard/shortcut-overrides"; import { formatShortcut } from "@/lib/keyboard/utils"; import { cn } from "@/lib/utils"; +import { useTranslation } from "react-i18next"; export type CommandPanelScopeMode = "commands" | "search-files" | "search-content"; type ScopeOption = { mode: CommandPanelScopeMode; - label: string; + /** + * Catalog key, resolved at render. `mode` stays the untranslated + * discriminant — it is compared with `===` in `isCommandPanelScopeMode` and + * threaded through the command-panel state. + */ + labelKey: string; shortcutId: ConfigurableShortcutId; }; const SCOPE_OPTIONS: ScopeOption[] = [ - { mode: "commands", label: "Commands", shortcutId: "SEARCH" }, - { mode: "search-files", label: "Files", shortcutId: "FILE_SEARCH" }, + { mode: "commands", labelKey: "common:scopeCommands", shortcutId: "SEARCH" }, + { mode: "search-files", labelKey: "common:scopeFiles", shortcutId: "FILE_SEARCH" }, { mode: "search-content", - label: "Contents", + labelKey: "common:scopeContents", shortcutId: "CONTENT_SEARCH", }, ]; @@ -46,26 +52,28 @@ export function CommandPanelScopeSwitcher({ mode: CommandPanelScopeMode; onScopeChange: (mode: CommandPanelScopeMode) => void; }) { + const { t } = useTranslation(); const keyboardShortcuts = useAppStore((state) => state.userSettings.keyboardShortcuts); return (
{SCOPE_OPTIONS.map((scope) => { const active = mode === scope.mode; const shortcut = formatShortcut(getShortcut(scope.shortcutId, keyboardShortcuts)); + const label = t(scope.labelKey); return ( ); })} diff --git a/apps/web/components/create-local-repository-surface.tsx b/apps/web/components/create-local-repository-surface.tsx index 6f928cda74..e13beb2600 100644 --- a/apps/web/components/create-local-repository-surface.tsx +++ b/apps/web/components/create-local-repository-surface.tsx @@ -30,7 +30,12 @@ import { useResponsiveBreakpoint } from "@/hooks/use-responsive-breakpoint"; import { initializeLocalRepository } from "@/lib/api/domains/workspace-api"; import { createDirectory } from "@/lib/api/domains/fs-api"; import type { Repository } from "@/lib/types/http"; +import { useTranslation } from "react-i18next"; +import type { TFunction } from "i18next"; +// The returned strings are never rendered: the only caller uses this as a +// boolean gate (`!nameError`), so they stay English rather than becoming dead +// catalog entries. export function validateLocalRepositoryName(name: string): string | null { const trimmed = name.trim(); if (!trimmed) return "Enter a repository name."; @@ -57,20 +62,27 @@ export type CreateLocalRepositorySurfaceProps = { onCreated: (repository: Repository) => void; }; +// `context` and `requiresSwitch` stay logic; only the notices they select are +// copy. The profile name is user data, interpolated rather than translated. function executorNotice( + t: TFunction, selection: DirectLocalExecutorSelection | null, context: NonNullable, ): string { if (context === "workspace") { - return "Creates an empty Git repository and registers it in this workspace."; + return t("common:createsAnEmptyGitRepository"); } if (!selection) { - return "A direct local executor profile is required to create and use an empty repository."; + return t("common:aDirectLocalExecutorProfileIsRequired"); } if (selection.requiresSwitch) { - return `Empty repositories run directly on this machine. This task will switch to “${selection.executorProfileName}”.`; + return t("common:emptyRepositoriesRunDirectlySwitch", { + profile: selection.executorProfileName, + }); } - return `This empty repository will run with “${selection.executorProfileName}” on this machine.`; + return t("common:thisEmptyRepositoryWillRunWith", { + profile: selection.executorProfileName, + }); } type RepositoryLocationFieldsProps = { @@ -90,10 +102,11 @@ function RepositoryLocationFields({ onParentPathChange, onLoadTypedDirectory, }: RepositoryLocationFieldsProps) { + const { t } = useTranslation(); return ( <>
@@ -135,9 +148,9 @@ function RepositoryLocationFields({
- Destination + {t("common:destination")} - {targetPath || parentPath || "Loading folder…"} + {targetPath || parentPath || t("common:loadingFolder")}
@@ -156,6 +169,7 @@ function RepositoryFormDetails({ submitError, ...locationFields }: RepositoryFormDetailsProps) { + const { t } = useTranslation(); return (
@@ -169,7 +183,7 @@ function RepositoryFormDetails({ } > -

{executorNotice(executorSelection, context)}

+

{executorNotice(t, executorSelection, context)}

{submitError ? (
); @@ -213,6 +228,7 @@ function CreateRepositoryForm({ onCreated, onDismiss, }: CreateLocalRepositorySurfaceProps & { onDismiss: () => void }) { + const { t } = useTranslation(); const [name, setName] = useState(""); const [parentPath, setParentPath] = useState(""); const [editingParentPath, setEditingParentPath] = useState(false); @@ -250,7 +266,7 @@ function CreateRepositoryForm({ setName(""); onDismiss(); } catch (err) { - setSubmitError(err instanceof Error ? err.message : "Failed to create repository"); + setSubmitError(err instanceof Error ? err.message : t("common:failedToCreateRepository")); } finally { setSubmitting(false); } @@ -313,6 +329,7 @@ function CreateRepositoryForm({ } export function CreateLocalRepositorySurface(props: CreateLocalRepositorySurfaceProps) { + const { t } = useTranslation(); const { isMobile } = useResponsiveBreakpoint(); const handleOpenChange = (open: boolean) => props.onOpenChange(open); const form = handleOpenChange(false)} />; @@ -325,10 +342,8 @@ export function CreateLocalRepositorySurface(props: CreateLocalRepositorySurface className="h-[88dvh] max-h-[88dvh] min-w-0 overflow-hidden pb-0" > - Create new repository - - Choose a folder or enter a new path on this machine. - + {t("common:createNewRepository")} + {t("common:chooseAFolderOrEnterA")} {form} @@ -343,10 +358,8 @@ export function CreateLocalRepositorySurface(props: CreateLocalRepositorySurface className="flex h-[min(640px,85dvh)] max-w-xl min-w-0 flex-col overflow-hidden p-0" > - Create new repository - - Choose a folder or enter a new path on this machine. - + {t("common:createNewRepository")} + {t("common:chooseAFolderOrEnterA")} {form} diff --git a/apps/web/components/discard-local-changes-dialog.tsx b/apps/web/components/discard-local-changes-dialog.tsx index f963a986e4..0fce31ea6d 100644 --- a/apps/web/components/discard-local-changes-dialog.tsx +++ b/apps/web/components/discard-local-changes-dialog.tsx @@ -10,6 +10,7 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@kandev/ui/alert-dialog"; +import { useTranslation } from "react-i18next"; const MAX_VISIBLE_FILES = 20; @@ -30,9 +31,15 @@ export function DiscardLocalChangesDialog({ onConfirm, onCancel, }: DiscardLocalChangesDialogProps) { + const { t } = useTranslation(); const visible = dirtyFiles.slice(0, MAX_VISIBLE_FILES); const overflow = dirtyFiles.length - visible.length; - const target = repoPath ? ` in your local clone at ${repoPath}` : " in your local clone"; + // One complete sentence per branch rather than a stem plus a " in your local + // clone at X" tail: the tail's position in the sentence is the translator's + // call, and a concatenated fragment freezes it here. + const description = repoPath + ? t("common:discardLocalChangesAtPathDescription", { repoPath }) + : t("common:discardLocalChangesDescription"); return ( @@ -44,11 +51,8 @@ export function DiscardLocalChangesDialog({ onClick={(e) => e.stopPropagation()} > - Discard local changes? - - Starting this task will permanently discard the uncommitted changes - {target}. Back up anything you want to keep before continuing. - + {t("common:discardLocalChanges")} + {description}
    - +{overflow} more + {t("common:andMoreFiles", { count: overflow })} )}
@@ -74,14 +78,14 @@ export function DiscardLocalChangesDialog({ data-testid="discard-local-changes-cancel" onClick={onCancel} > - Cancel + {t("common:cancel")} - Discard and continue + {t("common:discardAndContinue")} diff --git a/apps/web/components/enhance-prompt-button.tsx b/apps/web/components/enhance-prompt-button.tsx index 6396af6b22..d2e34780c6 100644 --- a/apps/web/components/enhance-prompt-button.tsx +++ b/apps/web/components/enhance-prompt-button.tsx @@ -5,6 +5,7 @@ import { Button } from "@kandev/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip"; import { GridSpinner } from "@/components/grid-spinner"; import { useTooltipMountGate } from "@/hooks/use-tooltip-mount-gate"; +import { useTranslation } from "react-i18next"; type EnhancePromptButtonProps = { onClick: () => void; @@ -17,11 +18,12 @@ export function EnhancePromptButton({ isLoading, isConfigured = true, }: EnhancePromptButtonProps) { + const { t } = useTranslation(); const { tooltipOpenState, handleTooltipOpenChange } = useTooltipMountGate(); const isDisabled = !isConfigured || isLoading; const tooltipText = isConfigured - ? "Enhance prompt with AI" - : "Configure a utility agent in settings to enable AI enhancement"; + ? t("common:enhancePromptWithAi") + : t("common:configureAUtilityAgentToEnhance"); return ( @@ -40,7 +42,7 @@ export function EnhancePromptButton({ className="h-7 w-7 cursor-pointer hover:bg-muted/40 text-slate-400" onClick={isConfigured ? onClick : undefined} disabled={isDisabled} - aria-label="Enhance prompt with AI" + aria-label={t("common:enhancePromptWithAi")} aria-busy={isLoading} data-testid="enhance-prompt-button" > diff --git a/apps/web/components/folder-picker.tsx b/apps/web/components/folder-picker.tsx index 78a50d2470..a4ffc76570 100644 --- a/apps/web/components/folder-picker.tsx +++ b/apps/web/components/folder-picker.tsx @@ -15,6 +15,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@kandev/ui/popover"; import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip"; import { cn } from "@/lib/utils"; import { listDirectory, type DirectoryListing } from "@/lib/api/domains/fs-api"; +import { useTranslation } from "react-i18next"; type FolderPickerProps = { /** Currently chosen absolute path; empty when nothing is picked. */ @@ -30,10 +31,11 @@ type FolderPickerProps = { * trigger lives in the chip row and the popover handles browse + commit. */ export function FolderPicker({ value, onChange, placeholder }: FolderPickerProps) { + const { t } = useTranslation(); const [open, setOpen] = useState(false); const { listing, loading, error, load } = useDirectoryListing(open, value); const leaf = leafName(value); - const triggerLabel = leaf || placeholder || "scratch workspace"; + const triggerLabel = leaf || placeholder || t("common:scratchWorkspace"); const hasValue = !!value; const triggerClass = cn( @@ -106,27 +108,31 @@ function leafName(path: string): string { } export function useDirectoryListing(open: boolean, value: string) { + const { t } = useTranslation(); const [listing, setListing] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const requestGeneration = useRef(0); - const load = useCallback(async (path: string) => { - const generation = ++requestGeneration.current; - setLoading(true); - setError(null); - setListing(null); - try { - const nextListing = await listDirectory(path); - if (generation !== requestGeneration.current) return; - setListing(nextListing); - } catch (err) { - if (generation !== requestGeneration.current) return; - setError(err instanceof Error ? err.message : "Failed to load directory"); - } finally { - if (generation === requestGeneration.current) setLoading(false); - } - }, []); + const load = useCallback( + async (path: string) => { + const generation = ++requestGeneration.current; + setLoading(true); + setError(null); + setListing(null); + try { + const nextListing = await listDirectory(path); + if (generation !== requestGeneration.current) return; + setListing(nextListing); + } catch (err) { + if (generation !== requestGeneration.current) return; + setError(err instanceof Error ? err.message : t("common:failedToLoadDirectory")); + } finally { + if (generation === requestGeneration.current) setLoading(false); + } + }, + [t], + ); useEffect(() => { // Reset cached listing when the popover closes so the next open reloads @@ -197,11 +203,12 @@ function Breadcrumb({ onNavigate: (p: string) => void; touchRows?: boolean; }) { + const { t } = useTranslation(); const segs = pathSegments(path); return (
{segs.length === 0 && ( - Loading… + {t("common:loading")} )} {segs.map((seg, i) => { const last = i === segs.length - 1; @@ -280,6 +287,7 @@ function DirectoryBrowserToolbar({ onCreateDirectory: (name: string) => Promise; touchRows: boolean; }) { + const { t } = useTranslation(); const [editing, setEditing] = useState(false); const [name, setName] = useState(""); const [creating, setCreating] = useState(false); @@ -294,7 +302,7 @@ function DirectoryBrowserToolbar({ await onCreateDirectory(trimmedName); setEditing(false); } catch (err) { - setError(err instanceof Error ? err.message : "Failed to create folder"); + setError(err instanceof Error ? err.message : t("common:failedToCreateFolder")); } finally { setCreating(false); } @@ -303,60 +311,21 @@ function DirectoryBrowserToolbar({ return (
{editing ? ( -
-
- - { - setName(event.target.value); - setError(null); - }} - onKeyDown={(event) => { - if (event.key === "Enter") { - event.preventDefault(); - void createFolder(); - } else if (event.key === "Escape") { - setEditing(false); - } - }} - placeholder="Folder name" - autoFocus - className={cn("min-w-0 flex-1", touchRows && "h-10")} - /> - - -
- {error ? ( -

- {error} -

- ) : null} -
+ { + setName(next); + setError(null); + }} + onSubmit={() => void createFolder()} + onCancel={() => setEditing(false)} + /> ) : (
- Folders + {t("common:folders")}
)} @@ -374,6 +343,78 @@ function DirectoryBrowserToolbar({ ); } +/** The inline "new folder" row, extracted to keep the toolbar under the + * max-lines-per-function cap. */ +function NewFolderForm({ + name, + error, + creating, + touchRows, + onNameChange, + onSubmit, + onCancel, +}: { + name: string; + error: string | null; + creating: boolean; + touchRows: boolean; + onNameChange: (next: string) => void; + onSubmit: () => void; + onCancel: () => void; +}) { + const { t } = useTranslation(); + return ( +
+
+ + onNameChange(event.target.value)} + onKeyDown={(event) => { + if (event.key === "Enter") { + event.preventDefault(); + onSubmit(); + } else if (event.key === "Escape") { + onCancel(); + } + }} + placeholder={t("common:folderName")} + autoFocus + className={cn("min-w-0 flex-1", touchRows && "h-10")} + /> + + +
+ {error ? ( +

+ {error} +

+ ) : null} +
+ ); +} + function Entries({ listing, loading, @@ -389,10 +430,11 @@ function Entries({ touchRows?: boolean; fillAvailableHeight?: boolean; }) { - if (loading) return ; + const { t } = useTranslation(); + if (loading) return ; if (error) return ; if (!listing || listing.entries.length === 0) { - return ; + return ; } return (
void; onChoose: () => void; }) { + const { t } = useTranslation(); return (
); diff --git a/apps/web/components/grid-spinner.tsx b/apps/web/components/grid-spinner.tsx index 52e0241b2a..710371025a 100644 --- a/apps/web/components/grid-spinner.tsx +++ b/apps/web/components/grid-spinner.tsx @@ -1,12 +1,18 @@ "use client"; +import { useTranslation } from "react-i18next"; type GridSpinnerProps = { className?: string; }; export function GridSpinner({ className }: GridSpinnerProps) { + const { t } = useTranslation(); return ( - + diff --git a/apps/web/components/improve-kandev-dialog-create.tsx b/apps/web/components/improve-kandev-dialog-create.tsx index ea8432e9f5..afe7b964ea 100644 --- a/apps/web/components/improve-kandev-dialog-create.tsx +++ b/apps/web/components/improve-kandev-dialog-create.tsx @@ -26,6 +26,8 @@ import { resolveImproveKandevWorkflow, type ImproveKandevKind, } from "./improve-kandev-dialog-model"; +import { Trans, useTranslation } from "react-i18next"; +import type { TFunction } from "i18next"; export type BootstrapState = | { kind: "idle" } @@ -43,22 +45,22 @@ type ImproveKind = ImproveKandevKind; // Surfaced in the dialog's submit-button tooltip while the bootstrap probe is // in flight, so the disabled state has an explanation the user can act on // instead of the usual missing-field reasons. -function bootstrapBlockedReason(bootstrap: BootstrapState): string | null { +function bootstrapBlockedReason(t: TFunction, bootstrap: BootstrapState): string | null { if (bootstrap.kind === "loading" || bootstrap.kind === "idle") { - return "Preparing kandev repository…"; + return t("common:preparingKandevRepository"); } if (bootstrap.kind === "error") { - return "Bootstrap failed — close and reopen to retry."; + return t("common:bootstrapFailedCloseAndReopen"); } return null; } -const PLACEHOLDERS: Record = { - bug: "E.g. When I open the kanban board, the column header text overlaps the task count badge on screens narrower than 1200px...", - feature: - "E.g. Add a keyboard shortcut to mark a task as Done from the task detail view, and show the shortcut in the task action menu...", - issue: - "Describe the problem or request you want published. The agent will ask for any details required by the matching GitHub issue template...", +// Keys, not resolved copy: a `t()` at module scope would freeze at the boot +// locale and the pseudo-locale could not see it. +const PLACEHOLDER_KEYS: Record = { + bug: "common:improveKandevBugPlaceholder", + feature: "common:improveKandevFeaturePlaceholder", + issue: "common:improveKandevIssuePlaceholder", }; type CreateModeViewProps = { @@ -74,6 +76,7 @@ type CreateModeViewProps = { }; export function CreateModeView(props: CreateModeViewProps) { + const { t } = useTranslation(); const { bootstrap, captureLogs, setCaptureLogs } = props; const [kind, setKind] = useState("bug"); const ready = bootstrap.kind === "ready" ? bootstrap : null; @@ -104,9 +107,9 @@ export function CreateModeView(props: CreateModeViewProps) { onSuccess={props.onTaskCreated} lockedFields={{ repository: true, branch: true, workflow: true }} submitBlockedReason={ - props.externalBlockedReason ?? forkBlockedReason ?? bootstrapBlockedReason(bootstrap) + props.externalBlockedReason ?? forkBlockedReason ?? bootstrapBlockedReason(t, bootstrap) } - descriptionPlaceholder={PLACEHOLDERS[kind]} + descriptionPlaceholder={t(PLACEHOLDER_KEYS[kind])} aboveDescriptionSlot={} extraFormSlot={ a.position - b.position); return (

- Workflow + {t("common:workflow")}

@@ -175,6 +179,7 @@ function WorkflowStepsPreview({ steps }: { steps: WorkflowStep[] }) { } function UsefulInfoCollapsible() { + const { t } = useTranslation(); const [open, setOpen] = useState(false); return ( @@ -184,7 +189,7 @@ function UsefulInfoCollapsible() { className="group flex w-full cursor-pointer items-center justify-between rounded-md border border-border bg-muted/30 px-3 py-2 text-left text-xs hover:bg-muted/50" > - Useful commands & agent skills + {t("common:usefulCommandsAgentSkills")} @@ -192,32 +197,33 @@ function UsefulInfoCollapsible() {

- Shell command you can run in the secondary instance, plus slash-command skills{" "} - you can ask the agent to run during the workflow. + + Shell command you can run in the secondary instance, plus slash-command{" "} + skills you can ask the agent to run during the workflow. +

- Shell + {t("common:shell")}

- Boots a secondary kandev dev instance with a clean DB so you can verify the change - without touching your main one. + {t("common:bootsASecondaryKandevDevInstance")}

- Agent skills (ask the agent to run) + {t("common:agentSkillsAskTheAgentTo")}

- Stages and commits changes using Conventional Commits. + {t("common:stagesAndCommitsChangesUsingConventional")} - Commits and pushes to the current branch. + {t("common:commitsAndPushesToTheCurrent")} - Runs format, typecheck, test, and lint across the monorepo. + {t("common:runsFormatTypecheckTestAndLint")} - Commits, pushes, and opens a pull request. + {t("common:commitsPushesAndOpensAPull")} - Waits for CI and automated reviews, then fixes failures and addresses comments. + {t("common:waitsForCiAndAutomatedReviews")}
@@ -242,17 +248,18 @@ function KindTabs({ kind: ImproveKind; onChange: (next: ImproveKind) => void; }) { + const { t } = useTranslation(); return ( onChange(v as ImproveKind)}> - Bug fix + {t("common:bugFix")} - Feature request + {t("common:featureRequest")} - Open issue + {t("common:openIssue")} @@ -270,6 +277,7 @@ function BootstrapStatusSlot({ captureLogs: boolean; setCaptureLogs: (v: boolean) => void; }) { + const { t } = useTranslation(); return (
@@ -278,32 +286,27 @@ function BootstrapStatusSlot({ className="rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs text-muted-foreground" data-testid="improve-kandev-issue-only-notice" > - This workflow will only create a GitHub issue from the repository template. The agent will - ask for missing details and publication approval; it will not implement the change or open - a pull request. + {t("common:thisWorkflowWillOnlyCreateA")}
)}
- Kandev keeps a small in-memory ring buffer of the most recent backend logs and browser - console events. When enabled, those logs are written to a temporary folder on your - machine, and the file paths are appended to the task description so the agent can read - them while investigating. + {t("common:kandevKeepsASmallInMemory")} @@ -313,11 +316,12 @@ function BootstrapStatusSlot({ } function BootstrapBanner({ bootstrap, kind }: { bootstrap: BootstrapState; kind: ImproveKind }) { + const { t } = useTranslation(); if (bootstrap.kind === "loading" || bootstrap.kind === "idle") { return (
- Preparing kandev repository in background. Fill in the details, submit when ready. + {t("common:preparingKandevRepositoryInBackground")}
); } @@ -339,12 +343,13 @@ function ContributorBanner({ data: ImproveKandevBootstrapResponse; kind: ImproveKind; }) { + const { t } = useTranslation(); const { github_login: login, has_write_access: hasWrite } = data; if (data.fork_status === "blocked_emu" && kind !== "issue") { return (
- {data.fork_message ?? "This GitHub account cannot fork kdlbs/kandev."} + {data.fork_message ?? t("common:thisGithubAccountCannotForkKdlbs")}
); } @@ -356,14 +361,18 @@ function ContributorBanner({ ) : ( )} + {/* One complete sentence per branch. Splitting this into a "Contributing + as" stem plus a tail would freeze the word order in JSX and leave the + tail untranslatable. */} - Contributing as{" "} {login ? ( - @{login} + + Contributing as @{login}.{" "} + {accessMessage} + ) : ( - your GitHub account + t("common:contributingAsYourGithubAccount", { access: accessMessage }) )} - . {accessMessage}
); diff --git a/apps/web/components/improve-kandev-dialog.test.tsx b/apps/web/components/improve-kandev-dialog.test.tsx new file mode 100644 index 0000000000..54aea4fc0f --- /dev/null +++ b/apps/web/components/improve-kandev-dialog.test.tsx @@ -0,0 +1,70 @@ +import { describe, expect, it } from "vitest"; +import { render } from "@testing-library/react"; +import { Trans } from "react-i18next"; + +/** + * These sentences interleave markup with a value, so the codemod half-converted + * them: it keyed the `` contents (`gh`, `kdlbs/kandev`) and left the prose + * around them as a JSX literal. That renders correctly in English and passes + * lint, `i18n:check`, `i18n:ratchet` and `i18n:removed` — the word order is just + * frozen in JSX where no translator can reach it. + * + * Rebuilt as one `` each. Two things are easy to get wrong and invisible + * afterwards, so they are pinned here: + * + * 1. The `` index must land on the element's position among ALL children, + * counting text and `{" "}` expression containers. Prettier reflowing a + * line inserts a `{" "}` and silently shifts every index after it. + * 2. `gh` and `kdlbs/kandev` travel through `values`, not as tag content, so + * a translator cannot reword a binary name or a repo slug. They cannot + * instead be left as bare children under an empty `<1>` tag — that + * form drops plain text children (it only preserves element children), + * which silently renders "needs the CLI". + */ +describe("improve-kandev dialog copy", () => { + it("renders the gh-auth notice byte-identically to the old literal", () => { + const { container } = render( + + The final step of this workflow opens a pull request, which needs the gh CLI to + be authenticated. {"Run gh auth login."} + , + ); + + expect(container.textContent).toBe( + "The final step of this workflow opens a pull request, which needs the gh CLI to be " + + "authenticated. Run gh auth login.", + ); + expect(container.querySelector("code")?.textContent).toBe("gh"); + }); + + it("renders the fork bullet with the repo slug intact", () => { + const { container } = render( + + The agent forks kdlbs/kandev to your GitHub account and opens a PR from your + fork, credited to you + , + ); + + expect(container.textContent).toBe( + "The agent forks kdlbs/kandev to your GitHub account and opens a PR from your fork, " + + "credited to you", + ); + expect(container.querySelector("code")?.textContent).toBe("kdlbs/kandev"); + }); + + it("keeps the contributor line one sentence per branch", () => { + const { container } = render( + + Contributing as @octocat. {"You have write access."} + , + ); + + expect(container.textContent).toBe("Contributing as @octocat. You have write access."); + }); +}); diff --git a/apps/web/components/improve-kandev-dialog.tsx b/apps/web/components/improve-kandev-dialog.tsx index d12263ace5..2b94071a12 100644 --- a/apps/web/components/improve-kandev-dialog.tsx +++ b/apps/web/components/improve-kandev-dialog.tsx @@ -22,6 +22,14 @@ import { readImproveKandevSkipIntro, writeImproveKandevSkipIntro, } from "./improve-kandev-dialog-model"; +import { Trans, useTranslation } from "react-i18next"; +// The module-level `t` — resolved when called, not at import — for the two +// error-only strings below. The hook-provided `t` changes identity on a locale +// switch, and putting it in these effects' dependency arrays would re-POST +// `bootstrapImproveKandev` (which forks a repository) and reset auth state +// whenever the user changes language. Render-time copy still uses the reactive +// `t` from `useTranslation()`. +import { t as tStatic } from "@/lib/i18n"; type ImproveKandevDialogProps = { open: boolean; @@ -38,6 +46,7 @@ type AuthState = | { kind: "missing"; message: string; fixUrl: string; fixLabel: string }; export function ImproveKandevDialog(props: ImproveKandevDialogProps) { + const { t } = useTranslation(); const { open, onOpenChange, workspaceId, onSuccess } = props; const [mode, setMode] = useState(() => initialImproveKandevMode(readSkipIntro())); const [skipIntro, setSkipIntro] = useState(() => readSkipIntro()); @@ -95,7 +104,9 @@ export function ImproveKandevDialog(props: ImproveKandevDialogProps) { setCaptureLogs={setCaptureLogs} transformDescription={transformDescription} onTaskCreated={handleSuccess} - externalBlockedReason={auth.kind === "checking" ? "Checking GitHub authentication…" : null} + externalBlockedReason={ + auth.kind === "checking" ? t("common:checkingGithubAuthentication") : null + } /> ); } @@ -106,7 +117,7 @@ export function ImproveKandevDialog(props: ImproveKandevDialogProps) { - Improve Kandev + {t("common:improveKandev")} ; onCancel: () => void; }) { + const { t } = useTranslation(); return (
-

GitHub CLI not authenticated

+

{t("common:githubCliNotAuthenticated")}

- The final step of this workflow opens a pull request, which needs the gh{" "} - CLI to be authenticated. {auth.message} + {/* `gh` is the CLI's binary name, so it stays literal in the children + and never becomes a catalog key. */} + + The final step of this workflow opens a pull request, which needs the gh{" "} + CLI to be authenticated. {auth.message} +

diff --git a/apps/web/components/onboarding-dialog.tsx b/apps/web/components/onboarding-dialog.tsx index 5ad8abda3c..7a7236c182 100644 --- a/apps/web/components/onboarding-dialog.tsx +++ b/apps/web/components/onboarding-dialog.tsx @@ -34,6 +34,7 @@ import { listAvailableAgents, listWorkflowTemplates } from "@/lib/api"; import { listAgentsAction, updateAgentProfileAction } from "@/app/actions/agents"; import { StepAgents, type AgentSetting } from "@/components/onboarding/step-agents"; import type { AvailableAgent, ToolStatus, WorkflowTemplate, AgentProfile } from "@/lib/types/http"; +import { Trans, useTranslation } from "react-i18next"; interface OnboardingDialogProps { open: boolean; @@ -42,36 +43,47 @@ interface OnboardingDialogProps { const TOTAL_STEPS = 4; +// Catalog keys, not resolved copy: `t()` at module scope would freeze at the +// boot locale. `id` stays untranslated so the React key is locale-independent. const RUNTIMES = [ { - name: "Local", - description: "Run agents directly on your machine with full access to your local filesystem.", + id: "local", + nameKey: "common:runtimeLocal", + descriptionKey: "common:runtimeLocalDescription", icon: IconFolder, }, { - name: "Git Worktree", - description: "Isolated branch environment under a worktree root for parallel work.", + id: "worktree", + nameKey: "common:runtimeGitWorktree", + descriptionKey: "common:runtimeGitWorktreeDescription", icon: IconFolders, }, { - name: "Docker", - description: "Containerized execution for full isolation and reproducibility.", + id: "docker", + nameKey: "common:runtimeDocker", + descriptionKey: "common:runtimeDockerDescription", icon: IconBrandDocker, }, { - name: "Sprites (Remote sprites.dev)", - description: "Hardware-isolated execution environment for arbitrary code.", + id: "sprites", + nameKey: "common:runtimeSprites", + descriptionKey: "common:runtimeSpritesDescription", icon: IconCloud, href: "https://sprites.dev", }, ]; -const STEP_TITLES = ["AI Agents", "Executors", "Agentic Workflows", "Command Panel"]; -const STEP_DESCRIPTIONS = [ - "Manage discovered agents and install new ones.", - "Agents can run in different executor environments — local, containerized, or remote.", - "Workflows define the steps and automation for your tasks.", - "Quick access to actions from anywhere with a keyboard shortcut.", +const STEP_TITLE_KEYS = [ + "common:onboardingStepAgentsTitle", + "common:onboardingStepExecutorsTitle", + "common:onboardingStepWorkflowsTitle", + "common:onboardingStepCommandPanelTitle", +]; +const STEP_DESCRIPTION_KEYS = [ + "common:onboardingStepAgentsDescription", + "common:onboardingStepExecutorsDescription", + "common:onboardingStepWorkflowsDescription", + "common:onboardingStepCommandPanelDescription", ]; function buildAgentSettings( @@ -215,29 +227,30 @@ function useOnboardingResources(open: boolean) { } function OnboardingFooter({ step, onSkip, onBack, onNext, onGetStarted }: OnboardingFooterProps) { + const { t } = useTranslation(); return (
{step > 0 && ( )} {step < TOTAL_STEPS - 1 ? ( ) : ( )}
@@ -247,6 +260,7 @@ function OnboardingFooter({ step, onSkip, onBack, onNext, onGetStarted }: Onboar } export function OnboardingDialog({ open, onComplete }: OnboardingDialogProps) { + const { t } = useTranslation(); const [step, setStep] = useState(0); const { availableAgents, @@ -305,8 +319,10 @@ export function OnboardingDialog({ open, onComplete }: OnboardingDialogProps) { {}}> - {STEP_TITLES[step]} - {STEP_DESCRIPTIONS[step]} + {t(STEP_TITLE_KEYS[step])} + + {t(STEP_DESCRIPTION_KEYS[step])} +
{step === 0 && ( @@ -336,6 +352,7 @@ export function OnboardingDialog({ open, onComplete }: OnboardingDialogProps) { } function StepEnvironments() { + const { t } = useTranslation(); return (
@@ -348,26 +365,26 @@ function StepEnvironments() { rel="noopener noreferrer" className="text-sm font-medium hover:underline cursor-pointer" > - {runtime.name} + {t(runtime.nameKey)} ) : ( -

{runtime.name}

+

{t(runtime.nameKey)}

); return ( -
+
{nameEl} -

{runtime.description}

+

{t(runtime.descriptionKey)}

); })}

- Configure executors in Settings to control where agents execute. + {t("common:configureExecutorsInSettingsToControl")}

); @@ -380,11 +397,12 @@ function StepWorkflows({ templates: WorkflowTemplate[]; loading: boolean; }) { + const { t } = useTranslation(); if (loading) { return (
- Loading workflow templates... + {t("common:loadingWorkflowTemplates")}
); } @@ -398,7 +416,7 @@ function StepWorkflows({ {defaultTemplate && } {otherTemplates.length > 0 && ( <> -

Available templates

+

{t("common:availableTemplates")}

{otherTemplates.map((template) => ( ))} @@ -406,22 +424,23 @@ function StepWorkflows({ )}

- Workflows control the steps, automation, and agent behavior for your tasks. You can add more - workflows from Settings. + {t("common:workflowsControlTheStepsAutomationAnd")}

); } +// `id` keeps the React key locale-independent; `labelKey` resolves at render. const COMMAND_PANEL_PREVIEW_ITEMS = [ - { icon: IconSearch, label: "Search Tasks", trailing: "→" }, - { icon: IconHome, label: "Go to Home" }, - { icon: IconGitCommit, label: "Commit Changes" }, - { icon: IconArrowDown, label: "Pull" }, - { icon: IconTerminal2, label: "Add Terminal Panel" }, + { id: "search", icon: IconSearch, labelKey: "common:commandPreviewSearchTasks", trailing: "→" }, + { id: "home", icon: IconHome, labelKey: "common:commandPreviewGoToHome" }, + { id: "commit", icon: IconGitCommit, labelKey: "common:commandPreviewCommitChanges" }, + { id: "pull", icon: IconArrowDown, labelKey: "common:commandPreviewPull" }, + { id: "terminal", icon: IconTerminal2, labelKey: "common:commandPreviewAddTerminalPanel" }, ]; function StepCommandPanel() { + const { t } = useTranslation(); return (
{/* Mock command panel preview */} @@ -429,7 +448,7 @@ function StepCommandPanel() { {/* Search input */}
- Type a command... + {t("common:typeACommand")}
{/* Sample commands */}
@@ -437,11 +456,11 @@ function StepCommandPanel() { const Icon = item.icon; return (
- {item.label} + {t(item.labelKey)} {item.trailing && ( {item.trailing} )} @@ -454,34 +473,39 @@ function StepCommandPanel() { ↑ ↓ - Navigate + {t("common:navigate")} ↵ - Select + {t("common:select")} + {/* A key name, not copy — it labels the physical key. */} esc - Close + {t("common:close")}
{/* Shortcut hint */} + {/* One message with indexed tags rather than a "Press" stem and a tail: + the flex children keep their gap-2 spacing, and a translator can still + move the key chord to wherever the sentence needs it. */}
- Press - - - - - K - - to open it anytime + + Press + + + + + K + + to open it anytime +

- Navigate between pages, search tasks, trigger git operations, and manage panels — all - without leaving the keyboard. Context-aware commands appear based on the active page. + {t("common:navigateBetweenPagesSearchTasksTrigger")}

); @@ -494,6 +518,7 @@ function TemplateCard({ template: WorkflowTemplate; isDefault?: boolean; }) { + const { t } = useTranslation(); const steps = (template.default_steps ?? []).slice().sort((a, b) => a.position - b.position); return ( @@ -505,7 +530,7 @@ function TemplateCard({ {isDefault && ( - Default + {t("common:defaultBadge")} )}
diff --git a/apps/web/components/prompt-result-recovery.tsx b/apps/web/components/prompt-result-recovery.tsx index cdf1fa63d8..3800b7a3c0 100644 --- a/apps/web/components/prompt-result-recovery.tsx +++ b/apps/web/components/prompt-result-recovery.tsx @@ -3,6 +3,7 @@ import { Button } from "@kandev/ui/button"; import type { UtilityGenerationResult } from "@/hooks/use-utility-agent-generator"; +import { useTranslation } from "react-i18next"; type PromptResultRecoveryProps = { pendingResult: UtilityGenerationResult | null; @@ -15,6 +16,7 @@ export function PromptResultRecovery({ onApply, onCopy, }: PromptResultRecoveryProps) { + const { t } = useTranslation(); if (!pendingResult) { return null; } @@ -25,7 +27,7 @@ export function PromptResultRecovery({ data-testid="prompt-result-recovery" className="flex flex-col gap-2 rounded-md border border-border/60 bg-muted/30 p-3" > -

An enhanced prompt is available.

+

{t("common:anEnhancedPromptIsAvailable")}

diff --git a/apps/web/components/vcs-multi-repo-menu.tsx b/apps/web/components/vcs-multi-repo-menu.tsx index 5efdce5661..f42206f729 100644 --- a/apps/web/components/vcs-multi-repo-menu.tsx +++ b/apps/web/components/vcs-multi-repo-menu.tsx @@ -11,6 +11,7 @@ import { IconGitPullRequest, IconLoader2, } from "@tabler/icons-react"; +import { useTranslation } from "react-i18next"; import { Button } from "@kandev/ui/button"; import { DropdownMenu, @@ -51,6 +52,21 @@ export type PrimaryButtonConfig = { tooltip: string; }; +/** + * One complete sentence per action rather than a single frame with the action + * name interpolated. `primaryButtonConfig.label` is itself already translated, + * and a translated value dropped into a translated frame cannot be declined to + * agree with the surrounding grammar (genitive in German/Russian, and so on) — + * nor can the frame be reordered around it. Keyed off `primaryAction`, which is + * an untranslated discriminant. + */ +const PICK_REPOSITORY_KEYS: Record<"commit" | "push" | "pr" | "rebase", string> = { + commit: "integrations:pickARepositoryForCommit", + push: "integrations:pickARepositoryForPush", + pr: "integrations:pickARepositoryForCreatePr", + rebase: "integrations:pickARepositoryForRebase", +}; + const ITEM_CLASS = "cursor-pointer gap-3"; const ICON_CLASS = "h-4 w-4 text-muted-foreground"; @@ -58,7 +74,8 @@ type ActionKey = "commit" | "push" | "pr" | "pull" | "rebase" | "merge" | "force type ActionDef = { key: ActionKey; - label: string; + /** Catalog key, resolved at render: `t()` here would freeze at the boot locale. */ + labelKey: string; icon: ReactNode; /** Returns true when this action should be disabled for a given repo. */ disabledFor: (status: PerRepoStatus | undefined) => boolean; @@ -68,49 +85,49 @@ type ActionDef = { const ACTION_DEFS: ActionDef[] = [ { key: "commit", - label: "Commit", + labelKey: "integrations:commit", icon: , disabledFor: (s) => !((s?.hasStaged ?? false) || (s?.hasUnstaged ?? false)), invoke: (repo, cb) => cb.onCommit(repo), }, { key: "push", - label: "Push", + labelKey: "integrations:push", icon: , disabledFor: (s) => (s?.ahead ?? 0) === 0, invoke: (repo, cb) => cb.onPush(false, repo), }, { key: "pr", - label: "Create PR", + labelKey: "integrations:createPr", icon: , disabledFor: () => false, invoke: (repo, cb) => cb.onPR(repo), }, { key: "pull", - label: "Pull", + labelKey: "integrations:pull", icon: , disabledFor: () => false, invoke: (repo, cb) => cb.onPull(repo), }, { key: "rebase", - label: "Rebase", + labelKey: "integrations:rebase", icon: , disabledFor: () => false, invoke: (repo, cb) => cb.onRebase(repo), }, { key: "merge", - label: "Merge", + labelKey: "integrations:merge", icon: , disabledFor: () => false, invoke: (repo, cb) => cb.onMerge(repo), }, { key: "force-push", - label: "Force Push", + labelKey: "integrations:forcePush", icon: , disabledFor: (s) => (s?.ahead ?? 0) === 0, invoke: (repo, cb) => cb.onPush(true, repo), @@ -138,19 +155,20 @@ function PerRepoActionSub({ repoDisplayName: (repositoryName: string) => string | undefined; callbacks: PerRepoCallbacks; }) { + const { t } = useTranslation(); const statusByName = new Map(perRepoStatus.map((s) => [s.repository_name, s])); return ( {action.icon} - {action.label} + {t(action.labelKey)} {repoNames.map((repo) => { const status = statusByName.get(repo); const ahead = status?.ahead ?? 0; const behind = status?.behind ?? 0; - const label = repoDisplayName(repo) || repo || "Repository"; + const label = repoDisplayName(repo) || repo || t("integrations:repository"); return ( string | undefined; callbacks: PerRepoCallbacks; }) { + const { t } = useTranslation(); return ( - Pick action, then repo + {t("integrations:pickActionThenRepo")} {ACTION_DEFS.map((action, idx) => (
@@ -221,10 +240,16 @@ function MultiRepoVcsDropdown({ } /** Inline footnote under Rebase/Merge showing the target branch. */ +// `type` stays a discriminant and never reaches the screen: rendering the union +// member directly would make "onto"/"from" untranslatable and couple the copy to +// the logic. Each branch resolves a whole message with the ref interpolated. function RebaseMergeFootnote({ target, type }: { target: string; type: "onto" | "from" }) { + const { t } = useTranslation(); return (
- {type} {target} + {type === "onto" + ? t("integrations:ontoBranch", { branch: target }) + : t("integrations:fromBranch", { branch: target })}
); } @@ -256,6 +281,7 @@ export function MultiRepoVcsButton({ repoDisplayName: (repositoryName: string) => string | undefined; callbacks: PerRepoCallbacks; }) { + const { t } = useTranslation(); return ( @@ -294,7 +320,7 @@ export function MultiRepoVcsButton({ - Pick a repository for {primaryButtonConfig.label} + {t(PICK_REPOSITORY_KEYS[primaryAction])} ); } diff --git a/apps/web/components/vcs-split-button.test.ts b/apps/web/components/vcs-split-button.test.ts new file mode 100644 index 0000000000..f45b5c567c --- /dev/null +++ b/apps/web/components/vcs-split-button.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it } from "vitest"; + +import { t } from "@/lib/i18n"; + +/** + * The VCS tooltips used to build their plural by hand: + * + * `Create PR (${aheadCount} commit${aheadCount !== 1 ? "s" : ""} ahead)` + * + * That puts the plural rule at the call site where no other locale can reach + * it, so the copy moved to `count` + `_one`/`_other`. These assertions pin the + * English to exactly what the concatenation emitted — E2E specs and the + * accessible-name queries depend on it, and a plural key is the easiest place + * to silently reword a string while every gate stays green. + */ +describe("vcs split-button count-bearing copy", () => { + const legacyCommit = (n: number) => `Commit ${n} changed file${n !== 1 ? "s" : ""}`; + const legacyPr = (n: number) => `Create PR (${n} commit${n !== 1 ? "s" : ""} ahead)`; + const legacyPush = (n: number) => `Push ${n} commit${n !== 1 ? "s" : ""} to remote`; + + // 1 is the only value the old ternary treated specially; 0 and 2 both take + // the "s" branch, and 0 is the one a naive `n > 1` port would get wrong. + for (const n of [0, 1, 2, 5]) { + it(`matches the pre-migration English for ${n} commit(s)`, () => { + expect(t("integrations:commitChangedFiles", { count: n })).toBe(legacyCommit(n)); + expect(t("integrations:createPrCommitsAhead", { count: n })).toBe(legacyPr(n)); + expect(t("integrations:pushCommitsToRemote", { count: n })).toBe(legacyPush(n)); + }); + } + + it("interpolates the git ref rather than translating it", () => { + expect(t("integrations:ontoBranch", { branch: "origin/main" })).toBe("onto origin/main"); + expect(t("integrations:fromBranch", { branch: "release/1.2" })).toBe("from release/1.2"); + expect(t("integrations:rebaseOntoBranchBehind", { branch: "origin/main", behind: 3 })).toBe( + "Rebase onto origin/main (3 behind)", + ); + }); + + it("keeps the divergence aria-labels count-invariant, as the shipped English was", () => { + expect(t("integrations:commitsAheadAriaLabel", { value: 1 })).toBe("1 commits ahead"); + expect(t("integrations:commitsBehindAriaLabel", { value: 2 })).toBe("2 commits behind"); + }); +}); + +/** + * The discard dialog used to concatenate a " in your local clone at X" tail + * onto a shared stem, which froze the tail's position in the sentence. Each + * branch is now a whole message; these pin both to the previous rendering. + */ +describe("discard-local-changes description", () => { + it("reads identically to the old stem + tail concatenation", () => { + const stem = "Starting this task will permanently discard the uncommitted changes"; + const tail = "Back up anything you want to keep before continuing."; + + expect(t("common:discardLocalChangesDescription")).toBe(`${stem} in your local clone. ${tail}`); + expect(t("common:discardLocalChangesAtPathDescription", { repoPath: "/srv/repo" })).toBe( + `${stem} in your local clone at /srv/repo. ${tail}`, + ); + }); +}); diff --git a/apps/web/components/vcs-split-button.tsx b/apps/web/components/vcs-split-button.tsx index 717dd5cc9d..95ebb8495f 100644 --- a/apps/web/components/vcs-split-button.tsx +++ b/apps/web/components/vcs-split-button.tsx @@ -1,6 +1,8 @@ "use client"; import { memo, useCallback, type ComponentProps, type ReactNode } from "react"; +import { useTranslation } from "react-i18next"; +import type { TFunction } from "i18next"; import { IconGitCommit, IconGitPullRequest, @@ -55,57 +57,75 @@ type PrimaryButtonConfig = { onClick: () => void; }; +// The count-bearing tooltips go through `count` + `_one`/`_other` rather than a +// concatenated `commit${n !== 1 ? "s" : ""}`: an English morpheme appended here +// puts the plural rule at the call site, where no other locale can reach it. function buildCommitConfig( + t: TFunction, uncommittedFileCount: number, openCommitDialog: () => void, ): PrimaryButtonConfig { return { icon: , - label: "Commit", + label: t("integrations:commit"), badge: uncommittedFileCount > 0 ? uncommittedFileCount : null, tooltip: uncommittedFileCount > 0 - ? `Commit ${uncommittedFileCount} changed file${uncommittedFileCount !== 1 ? "s" : ""}` - : "No changes to commit", + ? t("integrations:commitChangedFiles", { count: uncommittedFileCount }) + : t("integrations:noChangesToCommit"), onClick: openCommitDialog, }; } -function buildPrConfig(aheadCount: number, openPRDialog: () => void): PrimaryButtonConfig { +function buildPrConfig( + t: TFunction, + aheadCount: number, + openPRDialog: () => void, +): PrimaryButtonConfig { return { icon: , - label: "Create PR", + label: t("integrations:createPr"), badge: null, - tooltip: `Create PR (${aheadCount} commit${aheadCount !== 1 ? "s" : ""} ahead)`, + tooltip: t("integrations:createPrCommitsAhead", { count: aheadCount }), onClick: openPRDialog, }; } -function buildPushConfig(aheadCount: number, handlePush: () => void): PrimaryButtonConfig { +function buildPushConfig( + t: TFunction, + aheadCount: number, + handlePush: () => void, +): PrimaryButtonConfig { return { icon: , - label: "Push", + label: t("integrations:push"), badge: null, - tooltip: `Push ${aheadCount} commit${aheadCount !== 1 ? "s" : ""} to remote`, + tooltip: t("integrations:pushCommitsToRemote", { count: aheadCount }), onClick: handlePush, }; } function buildRebaseConfig( + t: TFunction, behindCount: number, baseBranch: string | undefined, handleRebase: () => void, ): PrimaryButtonConfig { return { icon: , - label: "Rebase", + label: t("integrations:rebase"), badge: behindCount > 0 ? behindCount : null, - tooltip: `Rebase onto ${baseBranch || DEFAULT_BASE_BRANCH} (${behindCount} behind)`, + // `branch` is a git ref — data, interpolated rather than translated. + tooltip: t("integrations:rebaseOntoBranchBehind", { + branch: baseBranch || DEFAULT_BASE_BRANCH, + behind: behindCount, + }), onClick: handleRebase, }; } type PrimaryConfigArgs = { + t: TFunction; primaryAction: "commit" | "push" | "pr" | "rebase"; uncommittedFileCount: number; aheadCount: number; @@ -118,6 +138,7 @@ type PrimaryConfigArgs = { }; function buildPrimaryButtonConfig({ + t, primaryAction, uncommittedFileCount, aheadCount, @@ -128,10 +149,11 @@ function buildPrimaryButtonConfig({ handlePush, handleRebase, }: PrimaryConfigArgs): PrimaryButtonConfig { - if (primaryAction === "push") return buildPushConfig(aheadCount, handlePush); - if (primaryAction === "pr") return buildPrConfig(aheadCount, openPRDialog); - if (primaryAction === "rebase") return buildRebaseConfig(behindCount, baseBranch, handleRebase); - return buildCommitConfig(uncommittedFileCount, openCommitDialog); + if (primaryAction === "push") return buildPushConfig(t, aheadCount, handlePush); + if (primaryAction === "pr") return buildPrConfig(t, aheadCount, openPRDialog); + if (primaryAction === "rebase") + return buildRebaseConfig(t, behindCount, baseBranch, handleRebase); + return buildCommitConfig(t, uncommittedFileCount, openCommitDialog); } type DivergenceTone = "ahead" | "behind"; @@ -144,17 +166,17 @@ const divergenceToneClass: Record = { function DivergencePill({ tone, value, - label, + ariaLabel, }: { tone: DivergenceTone; value: number; - label: string; + ariaLabel: string; }) { if (value <= 0) return null; return ( - - + + ); } @@ -202,16 +236,17 @@ function VcsDropdownItems({ onRebase, onMerge, }: VcsDropdownItemsProps) { + const { t } = useTranslation(); return ( - Create PR + {t("integrations:createPr")} - Pull + {t("integrations:pull")} {hasMatchingUpstream && behindCount > 0 && ( ↓{behindCount} @@ -221,7 +256,7 @@ function VcsDropdownItems({ - Push + {t("integrations:push")} {hasMatchingUpstream && aheadCount > 0 && ( ↑{aheadCount} @@ -235,7 +270,7 @@ function VcsDropdownItems({ disabled={disabled} > - Push + {t("integrations:push")} - Force Push + {t("integrations:forcePush")} - Rebase + {t("integrations:rebase")} + {/* The branch is a git ref — data. One message with the ref + interpolated, not an "onto" stem concatenated onto it. */} - onto {baseBranch || DEFAULT_BASE_BRANCH} + {t("integrations:ontoBranch", { branch: baseBranch || DEFAULT_BASE_BRANCH })} - Merge + {t("integrations:merge")} - from {baseBranch || DEFAULT_BASE_BRANCH} + {t("integrations:fromBranch", { branch: baseBranch || DEFAULT_BASE_BRANCH })} @@ -273,6 +310,11 @@ type VcsSplitButtonProps = { className?: string; }; +// The `label` values below stay English on purpose. They are operation *names* +// consumed by `useGitWithFeedback`, which concatenates them into its own English +// toast titles (`${operationName} failed`). `hooks/use-git-with-feedback.ts` is +// not migrated yet, so translating only this half would ship mixed-language +// toasts. They move when that hook's messages do. function useGitActions(git: ReturnType, baseBranch?: string) { const gitWithFeedback = useGitWithFeedback(); @@ -320,6 +362,7 @@ const VcsSplitButton = memo(function VcsSplitButton({ buttonSize = "sm", className, }: VcsSplitButtonProps) { + const { t } = useTranslation(); const git = useSessionGit(sessionId); const { openCommitDialog, openPRDialog } = useVcsDialogs(); const activePR = useActiveTaskPR(); @@ -347,6 +390,7 @@ const VcsSplitButton = memo(function VcsSplitButton({ hasOpenPR, ); const primaryButtonConfig = buildPrimaryButtonConfig({ + t, primaryAction, uncommittedFileCount, aheadCount, @@ -439,6 +483,7 @@ function SingleRepoVcsButton({ buttonSize?: ComponentProps["size"]; showDivergencePills?: boolean; }) { + const { t } = useTranslation(); return (
@@ -469,7 +514,7 @@ function SingleRepoVcsButton({ size={buttonSize} variant="outline" className="-ml-px rounded-l-none px-2 cursor-pointer" - aria-label="Open VCS options" + aria-label={t("integrations:openVcsOptions")} disabled={isDisabled} > {isGitLoading ? ( diff --git a/apps/web/components/watcher-repository-fields.tsx b/apps/web/components/watcher-repository-fields.tsx index 5c93eff7c6..c63470f21c 100644 --- a/apps/web/components/watcher-repository-fields.tsx +++ b/apps/web/components/watcher-repository-fields.tsx @@ -14,6 +14,7 @@ import { resolveBaseBranch, resolveRepositoryId, } from "@/lib/watcher-repository-default"; +import { useTranslation } from "react-i18next"; type PickItem = { id: string; label: string }; @@ -74,6 +75,7 @@ export function WatcherRepositoryFields({ onRepositoryChange: (repositoryId: string) => void; onBaseBranchChange: (baseBranch: string) => void; }) { + const { t } = useTranslation(); // forceRefresh: a repo created in settings doesn't update the shared store // slice, and the lazy fetch is gated by isLoaded — so without this the picker // could miss repositories created since the slice first loaded. The hook owns @@ -89,8 +91,8 @@ export function WatcherRepositoryFields({ return (
onRepositoryChange(resolveRepositoryId(v))} placeholder={NO_REPOSITORY_LABEL} @@ -100,8 +102,8 @@ export function WatcherRepositoryFields({ ]} /> onBaseBranchChange(resolveBaseBranch(v))} placeholder={branchPlaceholder(repositoryId, branchesLoading)} diff --git a/apps/web/components/workflow-selector-row.tsx b/apps/web/components/workflow-selector-row.tsx index d2bf99ce6b..aed61ceecb 100644 --- a/apps/web/components/workflow-selector-row.tsx +++ b/apps/web/components/workflow-selector-row.tsx @@ -1,6 +1,7 @@ "use client"; import { Fragment, memo, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; import { IconCheck, IconChevronDown, IconLogicBuffer } from "@tabler/icons-react"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@kandev/ui/tooltip"; import { Popover, PopoverContent, PopoverTrigger } from "@kandev/ui/popover"; @@ -25,6 +26,7 @@ function InlineSteps({ steps: StepItem[]; agentProfiles: AgentProfileOption[]; }) { + const { t } = useTranslation(); if (steps.length === 0) return null; return (
@@ -47,7 +49,7 @@ function InlineSteps({ * - Start step + {t("workflows:startStep")} )} @@ -97,6 +99,7 @@ export const WorkflowSelectorRow = memo(function WorkflowSelectorRow({ lastUsedWorkflowId, agentProfiles, }: WorkflowSelectorRowProps) { + const { t } = useTranslation(); const [open, setOpen] = useState(false); const selectedWorkflow = useMemo( @@ -124,12 +127,16 @@ export const WorkflowSelectorRow = memo(function WorkflowSelectorRow({ data-testid="workflow-selector-trigger" > - {selectedWorkflow?.name ?? "Select workflow"} + + {selectedWorkflow?.name ?? t("workflows:selectWorkflow")} + -
Workflow
+
+ {t("workflows:workflow")} +
{sortedWorkflows.map((wf) => { const isSelected = wf.id === selectedWorkflowId; const snapshot = snapshots[wf.id]; diff --git a/apps/web/components/workspace-content-search.tsx b/apps/web/components/workspace-content-search.tsx index b3f293c94e..a06442955d 100644 --- a/apps/web/components/workspace-content-search.tsx +++ b/apps/web/components/workspace-content-search.tsx @@ -7,6 +7,7 @@ import { useRepoDisplayName } from "@/hooks/domains/session/use-repo-display-nam import type { WorkspaceContentSearchError } from "@/hooks/domains/session/use-workspace-content-search"; import { groupByRepositoryName, isSingleRepoGroup } from "@/lib/group-by-repo"; import type { ContentSearchMatchRange, WorkspaceContentSearchResult } from "@/lib/types/backend"; +import { useTranslation } from "react-i18next"; type PreviewPart = { text: string; matched: boolean }; @@ -137,33 +138,38 @@ export function WorkspaceContentSearch({ sessionId, onSelect, }: WorkspaceContentSearchProps) { + const { t } = useTranslation(); const getRepoDisplayName = useRepoDisplayName(sessionId); if (error === "session-unavailable") { - return Content search needs an active task session.; + return {t("common:contentSearchNeedsAnActiveTask")}; } if (error === "query-too-long") { - return Search queries are limited to 200 characters.; + return {t("common:searchQueriesAreLimitedTo200")}; } if (error === "transport-error") { - return Search failed. Edit the query or reopen search to retry.; + return {t("common:searchFailedEditTheQueryOr")}; } if (isSearching && results.length === 0) { return ( - Searching task workspace… + {t("common:searchingTaskWorkspace")} ); } - if (!search.trim()) return Type to search task contents…; - if (results.length === 0) return No content matches found.; + if (!search.trim()) return {t("common:typeToSearchTaskContents")}; + if (results.length === 0) return {t("common:noContentMatchesFound")}; const groups = groupByRepositoryName(results, (result) => result.repository_name); const singleRepo = isSingleRepoGroup(groups); return groups.map((group) => ( @{{login}}. {{access}}", + "contributingAsYourGithubAccount": "Contributing as your GitHub account. {{access}}", + "copy": "Copy", + "couldNotPrepareImproveKandev": "Could not prepare Improve Kandev", + "createATaskDescribingYourBug": "Create a task describing your bug or feature request", "createAWorkspaceToConfigureThisIntegration": "Create a workspace to configure this integration.", + "createFolder": "Create folder", + "createNewRepository": "Create new repository", + "createRepository": "Create repository", + "createsAnEmptyGitRepository": "Creates an empty Git repository and registers it in this workspace.", + "creating": "Creating…", "dAgo": "{{diffDay}}d ago", "default": "default", + "defaultBadge": "Default", "deleteSession": "Delete session", + "describeABugYouHitOr": "Describe a bug you hit or a feature you'd like, and we'll create a task on your own agent to implement it in the kandev codebase.", + "destination": "Destination", + "discardAndContinue": "Discard and continue", + "discardLocalChanges": "Discard local changes?", + "discardLocalChangesAtPathDescription": "Starting this task will permanently discard the uncommitted changes in your local clone at {{repoPath}}. Back up anything you want to keep before continuing.", + "discardLocalChangesDescription": "Starting this task will permanently discard the uncommitted changes in your local clone. Back up anything you want to keep before continuing.", + "doNotShowThisAgain": "Do not show this again", "edit": "Edit", "editTask": "Edit task", - "gitScopeSkipped": "Skipped {{scope}} because a child repository operation failed ({{failedScopes}}).", + "emptyRepositoriesRunDirectlySwitch": "Empty repositories run directly on this machine. This task will switch to “{{profile}}”.", + "enhancePromptWithAi": "Enhance prompt with AI", + "enterAValue": "Enter a value...", + "enterValue": "Enter value...", "executors": "Executors", "externalMcp": "External MCP", + "failedToCreateFolder": "Failed to create folder", + "failedToCreateRepository": "Failed to create repository", + "failedToLoadDirectory": "Failed to load directory", "failedToLoadRoutine": "Failed to load routine", "failedToLoadSetup": "Failed to load setup", + "featureRequest": "Feature request", + "files": "Files", + "folderName": "Folder name", + "folders": "Folders", "forwardStdinStdoutStraightToThe": "Forward stdin/stdout straight to the CLI subprocess. Disables ACP.", + "getStarted": "Get Started", + "githubCliNotAuthenticated": "GitHub CLI not authenticated", + "gitScopeSkipped": "Skipped {{scope}} because a child repository operation failed ({{failedScopes}}).", "hAgo": "{{diffHr}}h ago", "hideAdvancedOptions": "Hide advanced options", + "howLogContextWorks": "How log context works", + "improveKandev": "Improve Kandev", + "improveKandevBugPlaceholder": "E.g. When I open the kanban board, the column header text overlaps the task count badge on screens narrower than 1200px...", + "improveKandevFeaturePlaceholder": "E.g. Add a keyboard shortcut to mark a task as Done from the task detail view, and show the shortcut in the task action menu...", + "improveKandevIssuePlaceholder": "Describe the problem or request you want published. The agent will ask for any details required by the matching GitHub issue template...", + "includeRecentBackendAndBrowserLogs": "Include recent backend & browser logs as context for the agent", "integrations": "Integrations", "justNow": "just now", "kandevAllowsEveryAgentPermissionRequest": "Kandev allows every agent permission request without prompting you.", "kandevCouldNotLoad": "Kandev couldn’t load.", + "kandevIsOpenSourceAndYou": "Kandev is open source, and you can help make it better.", + "kandevKeepsASmallInMemory": "Kandev keeps a small in-memory ring buffer of the most recent backend logs and browser console events. When enabled, those logs are written to a temporary folder on your machine, and the file paths are appended to the task description so the agent can read them while investigating.", + "lastRefreshFailed": "Last refresh failed: {{error}}", + "loading": "Loading…", "loadingEllipsis": "Loading...", + "loadingFolder": "Loading folder…", + "loadingIndicatorLabel": "Loading", "loadingLabel": "Loading {{label}}...", "loadingRoute": "Loading {{routeName}}…", "loadingRoutine": "Loading routine...", "loadingTask": "Loading task…", + "loadingWorkflowTemplates": "Loading workflow templates...", "mAgo": "{{diffMin}}m ago", "mode": "Mode", "model": "Model", "multiRepoUnavailableLocal": "Multi-repo tasks are unavailable on Local until its initial launch path can project sibling repositories.", "multiRepoUnavailableRemoteDocker": "Multi-repo tasks are unavailable on Remote Docker until it supports creating task instances.", "multiRepoUnsupportedExecutor": "Multi-repo tasks are not supported by this executor.", + "navigate": "Navigate", + "navigateBetweenPagesSearchTasksTrigger": "Navigate between pages, search tasks, trigger git operations, and manage panels — all without leaving the keyboard. Context-aware commands appear based on the active page.", + "newFolder": "New folder", + "newFolderName": "New folder name", + "next": "Next", "noCliClientsInstalledYetInstall": "No CLI clients installed yet. Install Claude / Codex / OpenCode / Amp to enable this picker.", + "noCommandsFound": "No commands found.", + "noContentMatchesFound": "No content matches found.", + "noFilesFound": "No files found.", + "noFoldersHerePickThisOne": "No folders here — pick this one or go up", "officeIsNotEnabledForThis": "Office is not enabled for this runtime.", "officeRouteNotPortedYet": "This Office route is handled by the SPA shell, but its dedicated client page is still being ported: <1>{{pathname}}", + "onboardingStepAgentsDescription": "Manage discovered agents and install new ones.", + "onboardingStepAgentsTitle": "AI Agents", + "onboardingStepCommandPanelDescription": "Quick access to actions from anywhere with a keyboard shortcut.", + "onboardingStepCommandPanelTitle": "Command Panel", + "onboardingStepExecutorsDescription": "Agents can run in different executor environments — local, containerized, or remote.", + "onboardingStepExecutorsTitle": "Executors", + "onboardingStepWorkflowsDescription": "Workflows define the steps and automation for your tasks.", + "onboardingStepWorkflowsTitle": "Agentic Workflows", + "open": "Open", "openFullPage": "Open full page", "openingLastTask": "Opening last task…", + "openIssue": "Open issue", + "optionalTheRepositoryTheAgentWorks": "Optional — the repository the agent works in.", "panels": "Panels", + "parentDirectory": "Parent directory", "permitTheCliToUploadCode": "Permit the CLI to upload code for cloud indexing (auggie / similar).", "pickACliClient": "Pick a CLI client", "pickACliClientToLoad": "Pick a CLI client to load its available models and modes.", @@ -85,6 +185,8 @@ "pluginPanelUnavailable": "This panel is no longer available.", "plugins": "Plugins", "prDetails": "PR Details", + "preparingKandevRepository": "Preparing kandev repository…", + "preparingKandevRepositoryInBackground": "Preparing kandev repository in background. Fill in the details, submit when ready.", "presetIconBug": "Bug", "presetIconCheck": "Check", "presetIconCode": "Code", @@ -93,15 +195,22 @@ "presetIconSearch": "Search", "presetIconSparkle": "Sparkle", "presetIconTool": "Tool", + "pressCmdKToOpenItAnytime": "<0>Press<1><2>to open it anytime", + "pressEnterToConfirm": "Press Enter to confirm", "profileName": "Profile name", "prompts": "Prompts", + "refreshAriaLabel": "Refresh {{label}}", + "refreshBranchesGitFetch": "Refresh branches (git fetch)", + "refreshingLabel": "Refreshing {{label}}...", + "refreshLabelLastFetched": "Refresh {{label}} (last fetched {{time}})", "reload": "Reload", "reloadTheApplicationToTryAgain": "Reload the application to try again.", - "requestFailed": "Request failed", "rename": "Rename", - "reviewSubmoduleBoundary": "Submodule: {{scope}}", + "repository": "Repository", + "repositoryName": "Repository name", + "requestFailed": "Request failed", "reset": "Reset", - "resizeTableColumns": "Resize table columns {{left}} and {{right}}", + "resetting": "Resetting…", "resetWatchChecking": "Checking how many tasks would be deleted…", "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.", @@ -109,24 +218,70 @@ "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.", "resetWatchPreviewFailedRequired": "Could not load the affected task count. Retry the preview before resetting this watch.", "resetWatchTitle": "Reset {{label}}?", - "resetting": "Resetting…", + "resizeTableColumns": "Resize table columns {{left}} and {{right}}", + "results": "Results", "retryPreview": "Retry preview", + "reviewSubmoduleBoundary": "Submodule: {{scope}}", + "runsFormatTypecheckTestAndLint": "Runs format, typecheck, test, and lint across the monorepo.", + "runtimeDocker": "Docker", + "runtimeDockerDescription": "Containerized execution for full isolation and reproducibility.", + "runtimeGitWorktree": "Git Worktree", + "runtimeGitWorktreeDescription": "Isolated branch environment under a worktree root for parallel work.", + "runtimeLocal": "Local", + "runtimeLocalDescription": "Run agents directly on your machine with full access to your local filesystem.", + "runtimeSprites": "Sprites (Remote sprites.dev)", + "runtimeSpritesDescription": "Hardware-isolated execution environment for arbitrary code.", "save": "Save", + "scopeCommands": "Commands", + "scopeContents": "Contents", + "scopeFiles": "Files", + "scopeTitleWithShortcut": "{{label}} ({{shortcut}})", + "scratchWorkspace": "scratch workspace", + "searchFailedEditTheQueryOr": "Search failed. Edit the query or reopen search to retry.", + "searchForFiles": "Search for files...", + "searchForTasks": "Search for tasks...", + "searchingTaskWorkspace": "Searching task workspace…", + "searchQueriesAreLimitedTo200": "Search queries are limited to 200 characters.", + "searchTaskContents": "Search task contents…", + "select": "Select", "settings": "Settings", "shell": "Shell", + "shellCommandPlusSlashCommandSkills": "Shell command you can run in the secondary instance, plus slash-command <2>skills you can ask the agent to run during the workflow.", "showAdvancedOptions": "Show advanced options", + "skip": "Skip", + "stagesAndCommitsChangesUsingConventional": "Stages and commits changes using Conventional Commits.", "status": "Status", "subtaskTitle": "Subtask title", + "switchMode": "Switch mode", "system": "System", "taskLoading": "Loading task...", + "tasks": "Tasks", "taskUnavailable": "Task unavailable", "taskUnavailableDescription": "We could not load this task. It may have been deleted or you may not have access.", + "theAgentForksKandevToYourAccount": "The agent forks <1>{{repo}} to your GitHub account and opens a PR from your fork, credited to you", + "theBaseBranchTheAgentStarts": "The base branch the agent starts from.", + "theFinalStepOpensAPullRequest": "The final step of this workflow opens a pull request, which needs the <1>{{binary}} CLI to be authenticated. {{message}}", + "thisEmptyRepositoryWillRunWith": "This empty repository will run with “{{profile}}” on this machine.", + "thisGithubAccountCannotForkKdlbs": "This GitHub account cannot fork kdlbs/kandev.", "thisPageCouldNotLoad": "This page couldn’t load.", + "thisWorkflowWillOnlyCreateA": "This workflow will only create a GitHub issue from the repository template. The agent will ask for missing details and publication approval; it will not implement the change or open a pull request.", "title": "Title", "toastRegionLabel": "Notifications", + "typeACommand": "Type a command...", + "typeToSearchFiles": "Type to search files...", + "typeToSearchTaskContents": "Type to search task contents…", "unknownPanel": "Unknown panel: {{component}}", "unsavedChanges": "Unsaved changes", + "usefulCommandsAgentSkills": "Useful commands & agent skills", + "useScratchInstead": "Use scratch instead", + "useThisFolder": "Use this folder", + "waitsForCiAndAutomatedReviews": "Waits for CI and automated reviews, then fixes failures and addresses comments.", + "whenItsDoneTheAgentOpensAPr": "When it's done, the agent opens a pull request to <2>{{repo}} for the maintainers to review, saving them time and shipping the improvement to everyone.", + "workflow": "Workflow", + "workflowsControlTheStepsAutomationAnd": "Workflows control the steps, automation, and agent behavior for your tasks. You can add more workflows from Settings.", "workspace": "Workspace", "workspaces": "Workspaces", - "youCanUseTheNavigationOr": "You can use the navigation or reload to try again." + "youCanUseTheNavigationOr": "You can use the navigation or reload to try again.", + "yourAgentImplementsItInThe": "Your agent implements it in the kandev repository, with tests", + "youVerifyAndTestTheChange": "You verify and test the change in a second kandev instance" } diff --git a/apps/web/src/locales/en/integrations.json b/apps/web/src/locales/en/integrations.json index 68925453e9..6209bcb5b1 100644 --- a/apps/web/src/locales/en/integrations.json +++ b/apps/web/src/locales/en/integrations.json @@ -2,31 +2,38 @@ "addDetailsAboutThisChange": "Add details about this change...", "allRepos": "All repos", "anErrorOccurred": "An error occurred", - "authRequiredHeading": "{{name}} authentication required", - "authRequiredInline": "{{name}} authentication required.", "authenticated": "Authenticated", "authenticationFailed": "Authentication failed: {{error}}", + "authRequiredHeading": "{{name}} authentication required", + "authRequiredInline": "{{name}} authentication required.", "branchPushedNotCreated": "Branch pushed; {{shortName}} not created", "branchWasPushedRetryCreation": "Branch was pushed; retry {{longName}} creation.", "checkedRelative": "· checked {{relative}}", "commit": "Commit", + "commitChangedFiles_one": "Commit {{count}} changed file", + "commitChangedFiles_other": "Commit {{count}} changed files", "commitChanges": "Commit Changes", "commitChangesScoped": "Commit Changes — {{scopedRepo}}", + "commitsAheadAriaLabel": "{{value}} commits ahead", + "commitsBehindAriaLabel": "{{value}} commits behind", "committingEllipsis": "Committing...", "configureAUtilityAgentInSettings": "Configure a utility agent in settings to enable AI generation", "copiedConfigTo": "Copied {{label}} config to {{targetName}}.", "copyConfig": "Copy config", "copyConfigSettingsOnly": "Copy the {{label}} settings from <4>{{sourceName}} into another workspace. This overwrites the target workspace's current {{label}} configuration. Watchers and automations are not copied.", "copyConfigToAnotherWorkspace": "Copy config to another workspace", - "copyConfigWithCredentials": "Copy the {{label}} settings and credentials from <4>{{sourceName}} into another workspace. This overwrites the target workspace's current {{label}} configuration. Watchers and automations are not copied.", "copyConfiguration": "Copy {{label}} configuration", + "copyConfigWithCredentials": "Copy the {{label}} settings and credentials from <4>{{sourceName}} into another workspace. This overwrites the target workspace's current {{label}} configuration. Watchers and automations are not copied.", "copyingConfigTo": "Copying {{label}} config to {{targetName}}…", "createAsDraft": "Create as draft", + "createdSuccessfully": "{{longName}} created successfully", "createFailed": "Create {{shortName}} failed", "createLongName": "Create {{longName}}", + "createPr": "Create PR", + "createPrCommitsAhead_one": "Create PR ({{count}} commit ahead)", + "createPrCommitsAhead_other": "Create PR ({{count}} commits ahead)", "createScoped": "Create {{longName}} — {{scopedRepo}}", "createShortName": "Create {{shortName}}", - "createdSuccessfully": "{{longName}} created successfully", "creatingEllipsis": "Creating...", "creatingFrom": "Creating {{shortName}} from <4>{{displayBranch}}", "creatingFromInto": "Creating {{shortName}} from <4>{{displayBranch}} → <6>{{baseBranch}}", @@ -40,6 +47,8 @@ "failedToCopyConfig": "Failed to copy config: {{err}}", "filesChanged_one": "<0>{{count}} file changed", "filesChanged_other": "<0>{{count}} files changed", + "forcePush": "Force Push", + "fromBranch": "from {{branch}}", "generateCommitDescriptionWithAi": "Generate commit description with AI", "generateCommitMessageWithAi": "Generate commit message with AI", "generateDescriptionWithAi": "Generate {{shortName}} description with AI", @@ -47,8 +56,22 @@ "loading": "Loading…", "longNameTitle": "{{longName}} title", "longNameTitlePlaceholder": "{{longName}} title...", + "merge": "Merge", "noChangesToCommit": "No changes to commit", "noSavedQueriesYet": "No saved queries yet", + "ontoBranch": "onto {{branch}}", + "openVcsOptions": "Open VCS options", + "pickActionThenRepo": "Pick action, then repo", + "pickARepositoryForCommit": "Pick a repository for Commit", + "pickARepositoryForCreatePr": "Pick a repository for Create PR", + "pickARepositoryForPush": "Pick a repository for Push", + "pickARepositoryForRebase": "Pick a repository for Rebase", + "pull": "Pull", + "push": "Push", + "pushCommitsToRemote_one": "Push {{count}} commit to remote", + "pushCommitsToRemote_other": "Push {{count}} commits to remote", + "rebase": "Rebase", + "rebaseOntoBranchBehind": "Rebase onto {{branch}} ({{behind}} behind)", "reconnectProvider": "Reconnect {{name}}", "repository": "Repository", "retryShortName": "Retry {{shortName}}", diff --git a/apps/web/src/locales/en/workflows.json b/apps/web/src/locales/en/workflows.json index d5413eea2c..0cffac640f 100644 --- a/apps/web/src/locales/en/workflows.json +++ b/apps/web/src/locales/en/workflows.json @@ -225,6 +225,7 @@ "wipLimit": "WIP limit", "wipLimitHelp": "Maximum admitted (active) tasks in this step. Overflow stays visible in a queue. Use 0 for unlimited.", "wipLimitsNote": "WIP limits active work, not visibility. Overflow remains on the board until capacity opens; if a selected feeder is also full, new task creation is rejected.", + "workflow": "Workflow", "workflowCycleBlocked": "Workflow cycle blocked", "workflowCycleBlockedDescription": "Change the workflow steps to remove the automatic cycle before continuing.", "workflowCycleBlockingBody": "This path re-enters \"{{stepName}}\" and can start the agent again without another user action.", diff --git a/apps/web/src/locales/pseudo/common.json b/apps/web/src/locales/pseudo/common.json index ec703791a9..6de7a8f5c4 100644 --- a/apps/web/src/locales/pseudo/common.json +++ b/apps/web/src/locales/pseudo/common.json @@ -2,19 +2,39 @@ "aboutContextCompactionCount": "Àƀōũţ ĩńƒēŕŕēď ćōḿƥàćţĩōń ćōũńţ", "activeSubagents_one": "{{count}} śũƀàĝēńţ ŕũńńĩńĝ", "activeSubagents_other": "{{count}} śũƀàĝēńţś ŕũńńĩńĝ", + "activeTasks": "Àćţĩvē Ţàśķś", + "aDirectLocalExecutorProfileIsRequired": "À ďĩŕēćţ ĺōćàĺ ēxēćũţōŕ ƥŕōƒĩĺē ĩś ŕēqũĩŕēď ţō ćŕēàţē àńď ũśē àń ēḿƥţŷ ŕēƥōśĩţōŕŷ.", "agentDashboard": "àĝēńţ ďàśĥƀōàŕď", "agentRun": "àĝēńţ ŕũń", "agentRuns": "àĝēńţ ŕũńś", "agents": "Àĝēńţś", + "agentSkillsAskTheAgentTo": "Àĝēńţ śķĩĺĺś (àśķ ţĥē àĝēńţ ţō ŕũń)", "allowIndexing": "Àĺĺōŵ ĩńďēxĩńĝ", + "andMoreFiles_one": "+{{count}} ḿōŕē", + "andMoreFiles_other": "+{{count}} ḿōŕē", + "anEnhancedPromptIsAvailable": "Àń ēńĥàńćēď ƥŕōḿƥţ ĩś àvàĩĺàƀĺē.", + "apply": "Àƥƥĺŷ", "autoApproveAllPermissions": "Àũţō-àƥƥŕōvē àĺĺ ƥēŕḿĩśśĩōńś", "automations": "Àũţōḿàţĩōńś", + "availableTemplates": "Àvàĩĺàƀĺē ţēḿƥĺàţēś", + "back": "Ɓàćķ", "backToTaskOverview": "Ɓàćķ ţō ţàśķ ōvēŕvĩēŵ", + "baseBranch": "Ɓàśē Ɓŕàńćĥ", + "bootsASecondaryKandevDevInstance": "Ɓōōţś à śēćōńďàŕŷ ķàńďēv ďēv ĩńśţàńćē ŵĩţĥ à ćĺēàń ĎƁ śō ŷōũ ćàń vēŕĩƒŷ ţĥē ćĥàńĝē ŵĩţĥōũţ ţōũćĥĩńĝ ŷōũŕ ḿàĩń ōńē.", + "bootstrapFailed": "Ɓōōţśţŕàƥ ƒàĩĺēď", + "bootstrapFailedCloseAndReopen": "Ɓōōţśţŕàƥ ƒàĩĺēď — ćĺōśē àńď ŕēōƥēń ţō ŕēţŕŷ.", "breadcrumb": "ƀŕēàďćŕũḿƀ", + "browseParentDirectory": "Ɓŕōŵśē ƥàŕēńţ ďĩŕēćţōŕŷ", + "bugFix": "Ɓũĝ ƒĩx", "cancel": "Ćàńćēĺ", + "cancelNewFolder": "Ćàńćēĺ ńēŵ ƒōĺďēŕ", "changelog": "Ćĥàńĝēĺōĝ", + "checkingGithubAuthentication": "Ćĥēćķĩńĝ ĜĩţĤũƀ àũţĥēńţĩćàţĩōń…", + "chooseAFolderOrEnterA": "Ćĥōōśē à ƒōĺďēŕ ōŕ ēńţēŕ à ńēŵ ƥàţĥ ōń ţĥĩś ḿàćĥĩńē.", "cliClient": "ĆĹĨ ćĺĩēńţ", + "cliModeYourPromptWillBe": "ĆĹĨ ḿōďē — ŷōũŕ ƥŕōḿƥţ ŵĩĺĺ ƀē àũţō-ĩńĵēćţēď ĩńţō ţĥē ţēŕḿĩńàĺ", "cliPassthrough": "ĆĹĨ ƥàśśţĥŕōũĝĥ", + "close": "Ćĺōśē", "commandAgentsSettingsKeywords": "àĝēńţś, àĝēńţ śēţţĩńĝś, àĝēńţ ƥŕōƒĩĺēś, ĩńśţàĺĺēď àĝēńţś, ćĺàũďē", "commandConfigChatKeywords": "ćōńƒĩĝ ćĥàţ, ćōńƒĩĝ ḿōďē, ćōńƒĩĝũŕē ķàńďēv, ŵōŕķƒĺōŵ ćōńƒĩĝũŕàţĩōń, ḿćƥ ćōńƒĩĝũŕàţĩōń", "commandExecutorsSettingsKeywords": "ēxēćũţōŕś, ēxēćũţōŕ ƥŕōƒĩĺēś, ēxēćũţĩōń ēńvĩŕōńḿēńţ, ēńvĩŕōńḿēńţ vàŕĩàƀĺēś, ŕũńţĩḿē, ćōḿƥũţē", @@ -33,6 +53,12 @@ "commandGroupNavigation": "Ńàvĩĝàţĩōń", "commandGroupPreferences": "Ƥŕēƒēŕēńćēś", "commandGroupSettings": "Śēţţĩńĝś", + "commandPaletteMode": "Ćōḿḿàńď ƥàĺēţţē ḿōďē", + "commandPreviewAddTerminalPanel": "Àďď Ţēŕḿĩńàĺ Ƥàńēĺ", + "commandPreviewCommitChanges": "Ćōḿḿĩţ Ćĥàńĝēś", + "commandPreviewGoToHome": "Ĝō ţō Ĥōḿē", + "commandPreviewPull": "Ƥũĺĺ", + "commandPreviewSearchTasks": "Śēàŕćĥ Ţàśķś", "commandPromptsSettingsKeywords": "ƥŕōḿƥţś, ƥŕōḿƥţ śēţţĩńĝś, ćũśţōḿ ƥŕōḿƥţś, ƥŕōḿƥţ śńĩƥƥēţś, ƥŕōḿƥţ ţēḿƥĺàţēś", "commandQuickChat": "Qũĩćķ Ćĥàţ", "commandQuickChatKeywords": "qũĩćķ ćĥàţ, ńēŵ qũĩćķ ćĥàţ, qũĩćķ qũēśţĩōń, àśķ àĝēńţ", @@ -40,44 +66,118 @@ "commandSwitchToLightMode": "Śŵĩţćĥ ţō Ĺĩĝĥţ Ḿōďē", "commandThemeKeywords": "ţĥēḿē, ćōĺōŕ ţĥēḿē, àƥƥēàŕàńćē", "commandWorkspaceSettingsKeywords": "ŵōŕķśƥàćē, ŵōŕķśƥàćēś", + "commitsAndPushesToTheCurrent": "Ćōḿḿĩţś àńď ƥũśĥēś ţō ţĥē ćũŕŕēńţ ƀŕàńćĥ.", + "commitsPushesAndOpensAPull": "Ćōḿḿĩţś, ƥũśĥēś, àńď ōƥēńś à ƥũĺĺ ŕēqũēśţ.", "configurationChat": "Ćōńƒĩĝũŕàţĩōń Ćĥàţ", + "configureAUtilityAgentToEnhance": "Ćōńƒĩĝũŕē à ũţĩĺĩţŷ àĝēńţ ĩń śēţţĩńĝś ţō ēńàƀĺē ÀĨ ēńĥàńćēḿēńţ", + "configureExecutorsInSettingsToControl": "Ćōńƒĩĝũŕē ēxēćũţōŕś ĩń Śēţţĩńĝś ţō ćōńţŕōĺ ŵĥēŕē àĝēńţś ēxēćũţē.", + "configureGithub": "Ćōńƒĩĝũŕē ĜĩţĤũƀ", + "confirm": "Ćōńƒĩŕḿ", + "contents": "Ćōńţēńţś", + "contentSearchNeedsAnActiveTask": "Ćōńţēńţ śēàŕćĥ ńēēďś àń àćţĩvē ţàśķ śēśśĩōń.", "contextCompactionCountHelp": "Ķàńďēv ĩńƒēŕś ţĥĩś ćōũńţ ƒŕōḿ ōƀśēŕvēď ćōńţēxţ ũśàĝē ďŕōƥś. Ḿĩśśĩńĝ śàḿƥĺēś ōŕ ƥŕōvĩďēŕ ŕēśēţś ćàń ḿàķē ĩţ àƥƥŕōxĩḿàţē.", "contextCompactions": "Ćōḿƥàćţĩōńś", + "contribute": "Ćōńţŕĩƀũţē", + "contributingAsLogin": "Ćōńţŕĩƀũţĩńĝ àś <1>@{{login}}. {{access}}", + "contributingAsYourGithubAccount": "Ćōńţŕĩƀũţĩńĝ àś ŷōũŕ ĜĩţĤũƀ àććōũńţ. {{access}}", + "copy": "Ćōƥŷ", + "couldNotPrepareImproveKandev": "Ćōũĺď ńōţ ƥŕēƥàŕē Ĩḿƥŕōvē Ķàńďēv", + "createATaskDescribingYourBug": "Ćŕēàţē à ţàśķ ďēśćŕĩƀĩńĝ ŷōũŕ ƀũĝ ōŕ ƒēàţũŕē ŕēqũēśţ", "createAWorkspaceToConfigureThisIntegration": "Ćŕēàţē à ŵōŕķśƥàćē ţō ćōńƒĩĝũŕē ţĥĩś ĩńţēĝŕàţĩōń.", + "createFolder": "Ćŕēàţē ƒōĺďēŕ", + "createNewRepository": "Ćŕēàţē ńēŵ ŕēƥōśĩţōŕŷ", + "createRepository": "Ćŕēàţē ŕēƥōśĩţōŕŷ", + "createsAnEmptyGitRepository": "Ćŕēàţēś àń ēḿƥţŷ Ĝĩţ ŕēƥōśĩţōŕŷ àńď ŕēĝĩśţēŕś ĩţ ĩń ţĥĩś ŵōŕķśƥàćē.", + "creating": "Ćŕēàţĩńĝ…", "dAgo": "{{diffDay}}ď àĝō", "default": "ďēƒàũĺţ", + "defaultBadge": "Ďēƒàũĺţ", "deleteSession": "Ďēĺēţē śēśśĩōń", + "describeABugYouHitOr": "Ďēśćŕĩƀē à ƀũĝ ŷōũ ĥĩţ ōŕ à ƒēàţũŕē ŷōũ'ď ĺĩķē, àńď ŵē'ĺĺ ćŕēàţē à ţàśķ ōń ŷōũŕ ōŵń àĝēńţ ţō ĩḿƥĺēḿēńţ ĩţ ĩń ţĥē ķàńďēv ćōďēƀàśē.", + "destination": "Ďēśţĩńàţĩōń", + "discardAndContinue": "Ďĩśćàŕď àńď ćōńţĩńũē", + "discardLocalChanges": "Ďĩśćàŕď ĺōćàĺ ćĥàńĝēś?", + "discardLocalChangesAtPathDescription": "Śţàŕţĩńĝ ţĥĩś ţàśķ ŵĩĺĺ ƥēŕḿàńēńţĺŷ ďĩśćàŕď ţĥē ũńćōḿḿĩţţēď ćĥàńĝēś ĩń ŷōũŕ ĺōćàĺ ćĺōńē àţ {{repoPath}}. Ɓàćķ ũƥ àńŷţĥĩńĝ ŷōũ ŵàńţ ţō ķēēƥ ƀēƒōŕē ćōńţĩńũĩńĝ.", + "discardLocalChangesDescription": "Śţàŕţĩńĝ ţĥĩś ţàśķ ŵĩĺĺ ƥēŕḿàńēńţĺŷ ďĩśćàŕď ţĥē ũńćōḿḿĩţţēď ćĥàńĝēś ĩń ŷōũŕ ĺōćàĺ ćĺōńē. Ɓàćķ ũƥ àńŷţĥĩńĝ ŷōũ ŵàńţ ţō ķēēƥ ƀēƒōŕē ćōńţĩńũĩńĝ.", + "doNotShowThisAgain": "Ďō ńōţ śĥōŵ ţĥĩś àĝàĩń", "edit": "Ēďĩţ", "editTask": "Ēďĩţ ţàśķ", - "gitScopeSkipped": "Śķĩƥƥēď {{scope}} ƀēćàũśē à ćĥĩĺď ŕēƥōśĩţōŕŷ ōƥēŕàţĩōń ƒàĩĺēď ({{failedScopes}}).", + "emptyRepositoriesRunDirectlySwitch": "Ēḿƥţŷ ŕēƥōśĩţōŕĩēś ŕũń ďĩŕēćţĺŷ ōń ţĥĩś ḿàćĥĩńē. Ţĥĩś ţàśķ ŵĩĺĺ śŵĩţćĥ ţō “{{profile}}”.", + "enhancePromptWithAi": "Ēńĥàńćē ƥŕōḿƥţ ŵĩţĥ ÀĨ", + "enterAValue": "Ēńţēŕ à vàĺũē...", + "enterValue": "Ēńţēŕ vàĺũē...", "executors": "Ēxēćũţōŕś", "externalMcp": "Ēxţēŕńàĺ ḾĆƤ", + "failedToCreateFolder": "Ƒàĩĺēď ţō ćŕēàţē ƒōĺďēŕ", + "failedToCreateRepository": "Ƒàĩĺēď ţō ćŕēàţē ŕēƥōśĩţōŕŷ", + "failedToLoadDirectory": "Ƒàĩĺēď ţō ĺōàď ďĩŕēćţōŕŷ", "failedToLoadRoutine": "Ƒàĩĺēď ţō ĺōàď ŕōũţĩńē", "failedToLoadSetup": "Ƒàĩĺēď ţō ĺōàď śēţũƥ", + "featureRequest": "Ƒēàţũŕē ŕēqũēśţ", + "files": "Ƒĩĺēś", + "folderName": "Ƒōĺďēŕ ńàḿē", + "folders": "Ƒōĺďēŕś", "forwardStdinStdoutStraightToThe": "Ƒōŕŵàŕď śţďĩń/śţďōũţ śţŕàĩĝĥţ ţō ţĥē ĆĹĨ śũƀƥŕōćēśś. Ďĩśàƀĺēś ÀĆƤ.", + "getStarted": "Ĝēţ Śţàŕţēď", + "githubCliNotAuthenticated": "ĜĩţĤũƀ ĆĹĨ ńōţ àũţĥēńţĩćàţēď", + "gitScopeSkipped": "Śķĩƥƥēď {{scope}} ƀēćàũśē à ćĥĩĺď ŕēƥōśĩţōŕŷ ōƥēŕàţĩōń ƒàĩĺēď ({{failedScopes}}).", "hAgo": "{{diffHr}}ĥ àĝō", "hideAdvancedOptions": "Ĥĩďē àďvàńćēď ōƥţĩōńś", + "howLogContextWorks": "Ĥōŵ ĺōĝ ćōńţēxţ ŵōŕķś", + "improveKandev": "Ĩḿƥŕōvē Ķàńďēv", + "improveKandevBugPlaceholder": "Ē.ĝ. Ŵĥēń Ĩ ōƥēń ţĥē ķàńƀàń ƀōàŕď, ţĥē ćōĺũḿń ĥēàďēŕ ţēxţ ōvēŕĺàƥś ţĥē ţàśķ ćōũńţ ƀàďĝē ōń śćŕēēńś ńàŕŕōŵēŕ ţĥàń 1200ƥx...", + "improveKandevFeaturePlaceholder": "Ē.ĝ. Àďď à ķēŷƀōàŕď śĥōŕţćũţ ţō ḿàŕķ à ţàśķ àś Ďōńē ƒŕōḿ ţĥē ţàśķ ďēţàĩĺ vĩēŵ, àńď śĥōŵ ţĥē śĥōŕţćũţ ĩń ţĥē ţàśķ àćţĩōń ḿēńũ...", + "improveKandevIssuePlaceholder": "Ďēśćŕĩƀē ţĥē ƥŕōƀĺēḿ ōŕ ŕēqũēśţ ŷōũ ŵàńţ ƥũƀĺĩśĥēď. Ţĥē àĝēńţ ŵĩĺĺ àśķ ƒōŕ àńŷ ďēţàĩĺś ŕēqũĩŕēď ƀŷ ţĥē ḿàţćĥĩńĝ ĜĩţĤũƀ ĩśśũē ţēḿƥĺàţē...", + "includeRecentBackendAndBrowserLogs": "Ĩńćĺũďē ŕēćēńţ ƀàćķēńď & ƀŕōŵśēŕ ĺōĝś àś ćōńţēxţ ƒōŕ ţĥē àĝēńţ", "integrations": "Ĩńţēĝŕàţĩōńś", "justNow": "ĵũśţ ńōŵ", "kandevAllowsEveryAgentPermissionRequest": "Ķàńďēv àĺĺōŵś ēvēŕŷ àĝēńţ ƥēŕḿĩśśĩōń ŕēqũēśţ ŵĩţĥōũţ ƥŕōḿƥţĩńĝ ŷōũ.", "kandevCouldNotLoad": "Ķàńďēv ćōũĺďń’ţ ĺōàď.", + "kandevIsOpenSourceAndYou": "Ķàńďēv ĩś ōƥēń śōũŕćē, àńď ŷōũ ćàń ĥēĺƥ ḿàķē ĩţ ƀēţţēŕ.", + "kandevKeepsASmallInMemory": "Ķàńďēv ķēēƥś à śḿàĺĺ ĩń-ḿēḿōŕŷ ŕĩńĝ ƀũƒƒēŕ ōƒ ţĥē ḿōśţ ŕēćēńţ ƀàćķēńď ĺōĝś àńď ƀŕōŵśēŕ ćōńśōĺē ēvēńţś. Ŵĥēń ēńàƀĺēď, ţĥōśē ĺōĝś àŕē ŵŕĩţţēń ţō à ţēḿƥōŕàŕŷ ƒōĺďēŕ ōń ŷōũŕ ḿàćĥĩńē, àńď ţĥē ƒĩĺē ƥàţĥś àŕē àƥƥēńďēď ţō ţĥē ţàśķ ďēśćŕĩƥţĩōń śō ţĥē àĝēńţ ćàń ŕēàď ţĥēḿ ŵĥĩĺē ĩńvēśţĩĝàţĩńĝ.", + "lastRefreshFailed": "Ĺàśţ ŕēƒŕēśĥ ƒàĩĺēď: {{error}}", + "loading": "Ĺōàďĩńĝ…", "loadingEllipsis": "Ĺōàďĩńĝ...", + "loadingFolder": "Ĺōàďĩńĝ ƒōĺďēŕ…", + "loadingIndicatorLabel": "Ĺōàďĩńĝ", "loadingLabel": "Ĺōàďĩńĝ {{label}}...", "loadingRoute": "Ĺōàďĩńĝ {{routeName}}…", "loadingRoutine": "Ĺōàďĩńĝ ŕōũţĩńē...", "loadingTask": "Ĺōàďĩńĝ ţàśķ…", + "loadingWorkflowTemplates": "Ĺōàďĩńĝ ŵōŕķƒĺōŵ ţēḿƥĺàţēś...", "mAgo": "{{diffMin}}ḿ àĝō", "mode": "Ḿōďē", "model": "Ḿōďēĺ", "multiRepoUnavailableLocal": "Ḿũĺţĩ-ŕēƥō ţàśķś àŕē ũńàvàĩĺàƀĺē ōń Ĺōćàĺ ũńţĩĺ ĩţś ĩńĩţĩàĺ ĺàũńćĥ ƥàţĥ ćàń ƥŕōĵēćţ śĩƀĺĩńĝ ŕēƥōśĩţōŕĩēś.", "multiRepoUnavailableRemoteDocker": "Ḿũĺţĩ-ŕēƥō ţàśķś àŕē ũńàvàĩĺàƀĺē ōń Ŕēḿōţē Ďōćķēŕ ũńţĩĺ ĩţ śũƥƥōŕţś ćŕēàţĩńĝ ţàśķ ĩńśţàńćēś.", "multiRepoUnsupportedExecutor": "Ḿũĺţĩ-ŕēƥō ţàśķś àŕē ńōţ śũƥƥōŕţēď ƀŷ ţĥĩś ēxēćũţōŕ.", + "navigate": "Ńàvĩĝàţē", + "navigateBetweenPagesSearchTasksTrigger": "Ńàvĩĝàţē ƀēţŵēēń ƥàĝēś, śēàŕćĥ ţàśķś, ţŕĩĝĝēŕ ĝĩţ ōƥēŕàţĩōńś, àńď ḿàńàĝē ƥàńēĺś — àĺĺ ŵĩţĥōũţ ĺēàvĩńĝ ţĥē ķēŷƀōàŕď. Ćōńţēxţ-àŵàŕē ćōḿḿàńďś àƥƥēàŕ ƀàśēď ōń ţĥē àćţĩvē ƥàĝē.", + "newFolder": "Ńēŵ ƒōĺďēŕ", + "newFolderName": "Ńēŵ ƒōĺďēŕ ńàḿē", + "next": "Ńēxţ", "noCliClientsInstalledYetInstall": "Ńō ĆĹĨ ćĺĩēńţś ĩńśţàĺĺēď ŷēţ. Ĩńśţàĺĺ Ćĺàũďē / Ćōďēx / ŌƥēńĆōďē / Àḿƥ ţō ēńàƀĺē ţĥĩś ƥĩćķēŕ.", + "noCommandsFound": "Ńō ćōḿḿàńďś ƒōũńď.", + "noContentMatchesFound": "Ńō ćōńţēńţ ḿàţćĥēś ƒōũńď.", + "noFilesFound": "Ńō ƒĩĺēś ƒōũńď.", + "noFoldersHerePickThisOne": "Ńō ƒōĺďēŕś ĥēŕē — ƥĩćķ ţĥĩś ōńē ōŕ ĝō ũƥ", "officeIsNotEnabledForThis": "Ōƒƒĩćē ĩś ńōţ ēńàƀĺēď ƒōŕ ţĥĩś ŕũńţĩḿē.", "officeRouteNotPortedYet": "Ţĥĩś Ōƒƒĩćē ŕōũţē ĩś ĥàńďĺēď ƀŷ ţĥē ŚƤÀ śĥēĺĺ, ƀũţ ĩţś ďēďĩćàţēď ćĺĩēńţ ƥàĝē ĩś śţĩĺĺ ƀēĩńĝ ƥōŕţēď: <1>{{pathname}}", + "onboardingStepAgentsDescription": "Ḿàńàĝē ďĩśćōvēŕēď àĝēńţś àńď ĩńśţàĺĺ ńēŵ ōńēś.", + "onboardingStepAgentsTitle": "ÀĨ Àĝēńţś", + "onboardingStepCommandPanelDescription": "Qũĩćķ àććēśś ţō àćţĩōńś ƒŕōḿ àńŷŵĥēŕē ŵĩţĥ à ķēŷƀōàŕď śĥōŕţćũţ.", + "onboardingStepCommandPanelTitle": "Ćōḿḿàńď Ƥàńēĺ", + "onboardingStepExecutorsDescription": "Àĝēńţś ćàń ŕũń ĩń ďĩƒƒēŕēńţ ēxēćũţōŕ ēńvĩŕōńḿēńţś — ĺōćàĺ, ćōńţàĩńēŕĩźēď, ōŕ ŕēḿōţē.", + "onboardingStepExecutorsTitle": "Ēxēćũţōŕś", + "onboardingStepWorkflowsDescription": "Ŵōŕķƒĺōŵś ďēƒĩńē ţĥē śţēƥś àńď àũţōḿàţĩōń ƒōŕ ŷōũŕ ţàśķś.", + "onboardingStepWorkflowsTitle": "Àĝēńţĩć Ŵōŕķƒĺōŵś", + "open": "Ōƥēń", "openFullPage": "Ōƥēń ƒũĺĺ ƥàĝē", "openingLastTask": "Ōƥēńĩńĝ ĺàśţ ţàśķ…", + "openIssue": "Ōƥēń ĩśśũē", + "optionalTheRepositoryTheAgentWorks": "Ōƥţĩōńàĺ — ţĥē ŕēƥōśĩţōŕŷ ţĥē àĝēńţ ŵōŕķś ĩń.", "panels": "Ƥàńēĺś", + "parentDirectory": "Ƥàŕēńţ ďĩŕēćţōŕŷ", "permitTheCliToUploadCode": "Ƥēŕḿĩţ ţĥē ĆĹĨ ţō ũƥĺōàď ćōďē ƒōŕ ćĺōũď ĩńďēxĩńĝ (àũĝĝĩē / śĩḿĩĺàŕ).", "pickACliClient": "Ƥĩćķ à ĆĹĨ ćĺĩēńţ", "pickACliClientToLoad": "Ƥĩćķ à ĆĹĨ ćĺĩēńţ ţō ĺōàď ĩţś àvàĩĺàƀĺē ḿōďēĺś àńď ḿōďēś.", @@ -85,6 +185,8 @@ "pluginPanelUnavailable": "Ţĥĩś ƥàńēĺ ĩś ńō ĺōńĝēŕ àvàĩĺàƀĺē.", "plugins": "Ƥĺũĝĩńś", "prDetails": "ƤŔ Ďēţàĩĺś", + "preparingKandevRepository": "Ƥŕēƥàŕĩńĝ ķàńďēv ŕēƥōśĩţōŕŷ…", + "preparingKandevRepositoryInBackground": "Ƥŕēƥàŕĩńĝ ķàńďēv ŕēƥōśĩţōŕŷ ĩń ƀàćķĝŕōũńď. Ƒĩĺĺ ĩń ţĥē ďēţàĩĺś, śũƀḿĩţ ŵĥēń ŕēàďŷ.", "presetIconBug": "Ɓũĝ", "presetIconCheck": "Ćĥēćķ", "presetIconCode": "Ćōďē", @@ -93,15 +195,22 @@ "presetIconSearch": "Śēàŕćĥ", "presetIconSparkle": "Śƥàŕķĺē", "presetIconTool": "Ţōōĺ", + "pressCmdKToOpenItAnytime": "<0>Ƥŕēśś<1><2>ţō ōƥēń ĩţ àńŷţĩḿē", + "pressEnterToConfirm": "Ƥŕēśś Ēńţēŕ ţō ćōńƒĩŕḿ", "profileName": "Ƥŕōƒĩĺē ńàḿē", "prompts": "Ƥŕōḿƥţś", + "refreshAriaLabel": "Ŕēƒŕēśĥ {{label}}", + "refreshBranchesGitFetch": "Ŕēƒŕēśĥ ƀŕàńćĥēś (ĝĩţ ƒēţćĥ)", + "refreshingLabel": "Ŕēƒŕēśĥĩńĝ {{label}}...", + "refreshLabelLastFetched": "Ŕēƒŕēśĥ {{label}} (ĺàśţ ƒēţćĥēď {{time}})", "reload": "Ŕēĺōàď", "reloadTheApplicationToTryAgain": "Ŕēĺōàď ţĥē àƥƥĺĩćàţĩōń ţō ţŕŷ àĝàĩń.", - "requestFailed": "Ŕēqũēśţ ƒàĩĺēď", "rename": "Ŕēńàḿē", - "reviewSubmoduleBoundary": "Śũƀḿōďũĺē: {{scope}}", + "repository": "Ŕēƥōśĩţōŕŷ", + "repositoryName": "Ŕēƥōśĩţōŕŷ ńàḿē", + "requestFailed": "Ŕēqũēśţ ƒàĩĺēď", "reset": "Ŕēśēţ", - "resizeTableColumns": "Ŕēśĩźē ţàƀĺē ćōĺũḿńś {{left}} àńď {{right}}", + "resetting": "Ŕēśēţţĩńĝ…", "resetWatchChecking": "Ćĥēćķĩńĝ ĥōŵ ḿàńŷ ţàśķś ŵōũĺď ƀē ďēĺēţēď…", "resetWatchDeleteAllTasks": "Ţĥĩś ŵĩĺĺ ďēĺēţē ēvēŕŷ ţàśķ ƥŕēvĩōũśĺŷ ćŕēàţēď ƀŷ ţĥē ŵàţćĥ, ĩńćĺũďĩńĝ àŕćĥĩvēď ţàśķś. Ţĥē ŵàţćĥ'ś ƥōĺĺĩńĝ ćũŕśōŕ ĩś àĺśō ćĺēàŕēď śō ţĥē ńēxţ ćĥēćķ ŕē-ĩḿƥōŕţś ēvēŕŷ ćũŕŕēńţĺŷ-ḿàţćĥĩńĝ ĩţēḿ. Ţĥĩś ćàńńōţ ƀē ũńďōńē.", "resetWatchDeleteTasks_one": "Ţĥĩś ŵĩĺĺ ďēĺēţē {{count}} ţàśķ ƥŕēvĩōũśĺŷ ćŕēàţēď ƀŷ ţĥē ŵàţćĥ, ĩńćĺũďĩńĝ àŕćĥĩvēď ţàśķś. Ţĥē ŵàţćĥ'ś ƥōĺĺĩńĝ ćũŕśōŕ ĩś àĺśō ćĺēàŕēď śō ţĥē ńēxţ ćĥēćķ ŕē-ĩḿƥōŕţś ēvēŕŷ ćũŕŕēńţĺŷ-ḿàţćĥĩńĝ ĩţēḿ. Ţĥĩś ćàńńōţ ƀē ũńďōńē.", @@ -109,24 +218,70 @@ "resetWatchNoTasks": "Ńō ţàśķś ŵēŕē ćŕēàţēď ƀŷ ţĥĩś ŵàţćĥ ŷēţ — ōńĺŷ ţĥē ƥōĺĺĩńĝ śţàţē ŵĩĺĺ ƀē ćĺēàŕēď. Ţĥē ŵàţćĥ'ś ƥōĺĺĩńĝ ćũŕśōŕ ĩś àĺśō ćĺēàŕēď śō ţĥē ńēxţ ćĥēćķ ŕē-ĩḿƥōŕţś ēvēŕŷ ćũŕŕēńţĺŷ-ḿàţćĥĩńĝ ĩţēḿ. Ţĥĩś ćàńńōţ ƀē ũńďōńē.", "resetWatchPreviewFailedRequired": "Ćōũĺď ńōţ ĺōàď ţĥē àƒƒēćţēď ţàśķ ćōũńţ. Ŕēţŕŷ ţĥē ƥŕēvĩēŵ ƀēƒōŕē ŕēśēţţĩńĝ ţĥĩś ŵàţćĥ.", "resetWatchTitle": "Ŕēśēţ {{label}}?", - "resetting": "Ŕēśēţţĩńĝ…", + "resizeTableColumns": "Ŕēśĩźē ţàƀĺē ćōĺũḿńś {{left}} àńď {{right}}", + "results": "Ŕēśũĺţś", "retryPreview": "Ŕēţŕŷ ƥŕēvĩēŵ", + "reviewSubmoduleBoundary": "Śũƀḿōďũĺē: {{scope}}", + "runsFormatTypecheckTestAndLint": "Ŕũńś ƒōŕḿàţ, ţŷƥēćĥēćķ, ţēśţ, àńď ĺĩńţ àćŕōśś ţĥē ḿōńōŕēƥō.", + "runtimeDocker": "Ďōćķēŕ", + "runtimeDockerDescription": "Ćōńţàĩńēŕĩźēď ēxēćũţĩōń ƒōŕ ƒũĺĺ ĩśōĺàţĩōń àńď ŕēƥŕōďũćĩƀĩĺĩţŷ.", + "runtimeGitWorktree": "Ĝĩţ Ŵōŕķţŕēē", + "runtimeGitWorktreeDescription": "Ĩśōĺàţēď ƀŕàńćĥ ēńvĩŕōńḿēńţ ũńďēŕ à ŵōŕķţŕēē ŕōōţ ƒōŕ ƥàŕàĺĺēĺ ŵōŕķ.", + "runtimeLocal": "Ĺōćàĺ", + "runtimeLocalDescription": "Ŕũń àĝēńţś ďĩŕēćţĺŷ ōń ŷōũŕ ḿàćĥĩńē ŵĩţĥ ƒũĺĺ àććēśś ţō ŷōũŕ ĺōćàĺ ƒĩĺēśŷśţēḿ.", + "runtimeSprites": "Śƥŕĩţēś (Ŕēḿōţē śƥŕĩţēś.ďēv)", + "runtimeSpritesDescription": "Ĥàŕďŵàŕē-ĩśōĺàţēď ēxēćũţĩōń ēńvĩŕōńḿēńţ ƒōŕ àŕƀĩţŕàŕŷ ćōďē.", "save": "Śàvē", + "scopeCommands": "Ćōḿḿàńďś", + "scopeContents": "Ćōńţēńţś", + "scopeFiles": "Ƒĩĺēś", + "scopeTitleWithShortcut": "{{label}} ({{shortcut}})", + "scratchWorkspace": "śćŕàţćĥ ŵōŕķśƥàćē", + "searchFailedEditTheQueryOr": "Śēàŕćĥ ƒàĩĺēď. Ēďĩţ ţĥē qũēŕŷ ōŕ ŕēōƥēń śēàŕćĥ ţō ŕēţŕŷ.", + "searchForFiles": "Śēàŕćĥ ƒōŕ ƒĩĺēś...", + "searchForTasks": "Śēàŕćĥ ƒōŕ ţàśķś...", + "searchingTaskWorkspace": "Śēàŕćĥĩńĝ ţàśķ ŵōŕķśƥàćē…", + "searchQueriesAreLimitedTo200": "Śēàŕćĥ qũēŕĩēś àŕē ĺĩḿĩţēď ţō 200 ćĥàŕàćţēŕś.", + "searchTaskContents": "Śēàŕćĥ ţàśķ ćōńţēńţś…", + "select": "Śēĺēćţ", "settings": "Śēţţĩńĝś", "shell": "Śĥēĺĺ", + "shellCommandPlusSlashCommandSkills": "Śĥēĺĺ ćōḿḿàńď ŷōũ ćàń ŕũń ĩń ţĥē śēćōńďàŕŷ ĩńśţàńćē, ƥĺũś śĺàśĥ-ćōḿḿàńď <2>śķĩĺĺś ŷōũ ćàń àśķ ţĥē àĝēńţ ţō ŕũń ďũŕĩńĝ ţĥē ŵōŕķƒĺōŵ.", "showAdvancedOptions": "Śĥōŵ àďvàńćēď ōƥţĩōńś", + "skip": "Śķĩƥ", + "stagesAndCommitsChangesUsingConventional": "Śţàĝēś àńď ćōḿḿĩţś ćĥàńĝēś ũśĩńĝ Ćōńvēńţĩōńàĺ Ćōḿḿĩţś.", "status": "Śţàţũś", "subtaskTitle": "Śũƀţàśķ ţĩţĺē", + "switchMode": "Śŵĩţćĥ ḿōďē", "system": "Śŷśţēḿ", "taskLoading": "Ĺōàďĩńĝ ţàśķ...", + "tasks": "Ţàśķś", "taskUnavailable": "Ţàśķ ũńàvàĩĺàƀĺē", "taskUnavailableDescription": "Ŵē ćōũĺď ńōţ ĺōàď ţĥĩś ţàśķ. Ĩţ ḿàŷ ĥàvē ƀēēń ďēĺēţēď ōŕ ŷōũ ḿàŷ ńōţ ĥàvē àććēśś.", + "theAgentForksKandevToYourAccount": "Ţĥē àĝēńţ ƒōŕķś <1>{{repo}} ţō ŷōũŕ ĜĩţĤũƀ àććōũńţ àńď ōƥēńś à ƤŔ ƒŕōḿ ŷōũŕ ƒōŕķ, ćŕēďĩţēď ţō ŷōũ", + "theBaseBranchTheAgentStarts": "Ţĥē ƀàśē ƀŕàńćĥ ţĥē àĝēńţ śţàŕţś ƒŕōḿ.", + "theFinalStepOpensAPullRequest": "Ţĥē ƒĩńàĺ śţēƥ ōƒ ţĥĩś ŵōŕķƒĺōŵ ōƥēńś à ƥũĺĺ ŕēqũēśţ, ŵĥĩćĥ ńēēďś ţĥē <1>{{binary}} ĆĹĨ ţō ƀē àũţĥēńţĩćàţēď. {{message}}", + "thisEmptyRepositoryWillRunWith": "Ţĥĩś ēḿƥţŷ ŕēƥōśĩţōŕŷ ŵĩĺĺ ŕũń ŵĩţĥ “{{profile}}” ōń ţĥĩś ḿàćĥĩńē.", + "thisGithubAccountCannotForkKdlbs": "Ţĥĩś ĜĩţĤũƀ àććōũńţ ćàńńōţ ƒōŕķ ķďĺƀś/ķàńďēv.", "thisPageCouldNotLoad": "Ţĥĩś ƥàĝē ćōũĺďń’ţ ĺōàď.", + "thisWorkflowWillOnlyCreateA": "Ţĥĩś ŵōŕķƒĺōŵ ŵĩĺĺ ōńĺŷ ćŕēàţē à ĜĩţĤũƀ ĩśśũē ƒŕōḿ ţĥē ŕēƥōśĩţōŕŷ ţēḿƥĺàţē. Ţĥē àĝēńţ ŵĩĺĺ àśķ ƒōŕ ḿĩśśĩńĝ ďēţàĩĺś àńď ƥũƀĺĩćàţĩōń àƥƥŕōvàĺ; ĩţ ŵĩĺĺ ńōţ ĩḿƥĺēḿēńţ ţĥē ćĥàńĝē ōŕ ōƥēń à ƥũĺĺ ŕēqũēśţ.", "title": "Ţĩţĺē", "toastRegionLabel": "Ńōţĩƒĩćàţĩōńś", + "typeACommand": "Ţŷƥē à ćōḿḿàńď...", + "typeToSearchFiles": "Ţŷƥē ţō śēàŕćĥ ƒĩĺēś...", + "typeToSearchTaskContents": "Ţŷƥē ţō śēàŕćĥ ţàśķ ćōńţēńţś…", "unknownPanel": "Ũńķńōŵń ƥàńēĺ: {{component}}", "unsavedChanges": "Ũńśàvēď ćĥàńĝēś", + "usefulCommandsAgentSkills": "Ũśēƒũĺ ćōḿḿàńďś & àĝēńţ śķĩĺĺś", + "useScratchInstead": "Ũśē śćŕàţćĥ ĩńśţēàď", + "useThisFolder": "Ũśē ţĥĩś ƒōĺďēŕ", + "waitsForCiAndAutomatedReviews": "Ŵàĩţś ƒōŕ ĆĨ àńď àũţōḿàţēď ŕēvĩēŵś, ţĥēń ƒĩxēś ƒàĩĺũŕēś àńď àďďŕēśśēś ćōḿḿēńţś.", + "whenItsDoneTheAgentOpensAPr": "Ŵĥēń ĩţ'ś ďōńē, ţĥē àĝēńţ ōƥēńś à ƥũĺĺ ŕēqũēśţ ţō <2>{{repo}} ƒōŕ ţĥē ḿàĩńţàĩńēŕś ţō ŕēvĩēŵ, śàvĩńĝ ţĥēḿ ţĩḿē àńď śĥĩƥƥĩńĝ ţĥē ĩḿƥŕōvēḿēńţ ţō ēvēŕŷōńē.", + "workflow": "Ŵōŕķƒĺōŵ", + "workflowsControlTheStepsAutomationAnd": "Ŵōŕķƒĺōŵś ćōńţŕōĺ ţĥē śţēƥś, àũţōḿàţĩōń, àńď àĝēńţ ƀēĥàvĩōŕ ƒōŕ ŷōũŕ ţàśķś. Ŷōũ ćàń àďď ḿōŕē ŵōŕķƒĺōŵś ƒŕōḿ Śēţţĩńĝś.", "workspace": "Ŵōŕķśƥàćē", "workspaces": "Ŵōŕķśƥàćēś", - "youCanUseTheNavigationOr": "Ŷōũ ćàń ũśē ţĥē ńàvĩĝàţĩōń ōŕ ŕēĺōàď ţō ţŕŷ àĝàĩń." + "youCanUseTheNavigationOr": "Ŷōũ ćàń ũśē ţĥē ńàvĩĝàţĩōń ōŕ ŕēĺōàď ţō ţŕŷ àĝàĩń.", + "yourAgentImplementsItInThe": "Ŷōũŕ àĝēńţ ĩḿƥĺēḿēńţś ĩţ ĩń ţĥē ķàńďēv ŕēƥōśĩţōŕŷ, ŵĩţĥ ţēśţś", + "youVerifyAndTestTheChange": "Ŷōũ vēŕĩƒŷ àńď ţēśţ ţĥē ćĥàńĝē ĩń à śēćōńď ķàńďēv ĩńśţàńćē" } diff --git a/apps/web/src/locales/pseudo/integrations.json b/apps/web/src/locales/pseudo/integrations.json index da3cbf1372..7d7df3fe9f 100644 --- a/apps/web/src/locales/pseudo/integrations.json +++ b/apps/web/src/locales/pseudo/integrations.json @@ -2,31 +2,38 @@ "addDetailsAboutThisChange": "Àďď ďēţàĩĺś àƀōũţ ţĥĩś ćĥàńĝē...", "allRepos": "Àĺĺ ŕēƥōś", "anErrorOccurred": "Àń ēŕŕōŕ ōććũŕŕēď", - "authRequiredHeading": "{{name}} àũţĥēńţĩćàţĩōń ŕēqũĩŕēď", - "authRequiredInline": "{{name}} àũţĥēńţĩćàţĩōń ŕēqũĩŕēď.", "authenticated": "Àũţĥēńţĩćàţēď", "authenticationFailed": "Àũţĥēńţĩćàţĩōń ƒàĩĺēď: {{error}}", + "authRequiredHeading": "{{name}} àũţĥēńţĩćàţĩōń ŕēqũĩŕēď", + "authRequiredInline": "{{name}} àũţĥēńţĩćàţĩōń ŕēqũĩŕēď.", "branchPushedNotCreated": "Ɓŕàńćĥ ƥũśĥēď; {{shortName}} ńōţ ćŕēàţēď", "branchWasPushedRetryCreation": "Ɓŕàńćĥ ŵàś ƥũśĥēď; ŕēţŕŷ {{longName}} ćŕēàţĩōń.", "checkedRelative": "· ćĥēćķēď {{relative}}", "commit": "Ćōḿḿĩţ", + "commitChangedFiles_one": "Ćōḿḿĩţ {{count}} ćĥàńĝēď ƒĩĺē", + "commitChangedFiles_other": "Ćōḿḿĩţ {{count}} ćĥàńĝēď ƒĩĺēś", "commitChanges": "Ćōḿḿĩţ Ćĥàńĝēś", "commitChangesScoped": "Ćōḿḿĩţ Ćĥàńĝēś — {{scopedRepo}}", + "commitsAheadAriaLabel": "{{value}} ćōḿḿĩţś àĥēàď", + "commitsBehindAriaLabel": "{{value}} ćōḿḿĩţś ƀēĥĩńď", "committingEllipsis": "Ćōḿḿĩţţĩńĝ...", "configureAUtilityAgentInSettings": "Ćōńƒĩĝũŕē à ũţĩĺĩţŷ àĝēńţ ĩń śēţţĩńĝś ţō ēńàƀĺē ÀĨ ĝēńēŕàţĩōń", "copiedConfigTo": "Ćōƥĩēď {{label}} ćōńƒĩĝ ţō {{targetName}}.", "copyConfig": "Ćōƥŷ ćōńƒĩĝ", "copyConfigSettingsOnly": "Ćōƥŷ ţĥē {{label}} śēţţĩńĝś ƒŕōḿ <4>{{sourceName}} ĩńţō àńōţĥēŕ ŵōŕķśƥàćē. Ţĥĩś ōvēŕŵŕĩţēś ţĥē ţàŕĝēţ ŵōŕķśƥàćē'ś ćũŕŕēńţ {{label}} ćōńƒĩĝũŕàţĩōń. Ŵàţćĥēŕś àńď àũţōḿàţĩōńś àŕē ńōţ ćōƥĩēď.", "copyConfigToAnotherWorkspace": "Ćōƥŷ ćōńƒĩĝ ţō àńōţĥēŕ ŵōŕķśƥàćē", - "copyConfigWithCredentials": "Ćōƥŷ ţĥē {{label}} śēţţĩńĝś àńď ćŕēďēńţĩàĺś ƒŕōḿ <4>{{sourceName}} ĩńţō àńōţĥēŕ ŵōŕķśƥàćē. Ţĥĩś ōvēŕŵŕĩţēś ţĥē ţàŕĝēţ ŵōŕķśƥàćē'ś ćũŕŕēńţ {{label}} ćōńƒĩĝũŕàţĩōń. Ŵàţćĥēŕś àńď àũţōḿàţĩōńś àŕē ńōţ ćōƥĩēď.", "copyConfiguration": "Ćōƥŷ {{label}} ćōńƒĩĝũŕàţĩōń", + "copyConfigWithCredentials": "Ćōƥŷ ţĥē {{label}} śēţţĩńĝś àńď ćŕēďēńţĩàĺś ƒŕōḿ <4>{{sourceName}} ĩńţō àńōţĥēŕ ŵōŕķśƥàćē. Ţĥĩś ōvēŕŵŕĩţēś ţĥē ţàŕĝēţ ŵōŕķśƥàćē'ś ćũŕŕēńţ {{label}} ćōńƒĩĝũŕàţĩōń. Ŵàţćĥēŕś àńď àũţōḿàţĩōńś àŕē ńōţ ćōƥĩēď.", "copyingConfigTo": "Ćōƥŷĩńĝ {{label}} ćōńƒĩĝ ţō {{targetName}}…", "createAsDraft": "Ćŕēàţē àś ďŕàƒţ", + "createdSuccessfully": "{{longName}} ćŕēàţēď śũććēśśƒũĺĺŷ", "createFailed": "Ćŕēàţē {{shortName}} ƒàĩĺēď", "createLongName": "Ćŕēàţē {{longName}}", + "createPr": "Ćŕēàţē ƤŔ", + "createPrCommitsAhead_one": "Ćŕēàţē ƤŔ ({{count}} ćōḿḿĩţ àĥēàď)", + "createPrCommitsAhead_other": "Ćŕēàţē ƤŔ ({{count}} ćōḿḿĩţś àĥēàď)", "createScoped": "Ćŕēàţē {{longName}} — {{scopedRepo}}", "createShortName": "Ćŕēàţē {{shortName}}", - "createdSuccessfully": "{{longName}} ćŕēàţēď śũććēśśƒũĺĺŷ", "creatingEllipsis": "Ćŕēàţĩńĝ...", "creatingFrom": "Ćŕēàţĩńĝ {{shortName}} ƒŕōḿ <4>{{displayBranch}}", "creatingFromInto": "Ćŕēàţĩńĝ {{shortName}} ƒŕōḿ <4>{{displayBranch}} → <6>{{baseBranch}}", @@ -40,6 +47,8 @@ "failedToCopyConfig": "Ƒàĩĺēď ţō ćōƥŷ ćōńƒĩĝ: {{err}}", "filesChanged_one": "<0>{{count}} ƒĩĺē ćĥàńĝēď", "filesChanged_other": "<0>{{count}} ƒĩĺēś ćĥàńĝēď", + "forcePush": "Ƒōŕćē Ƥũśĥ", + "fromBranch": "ƒŕōḿ {{branch}}", "generateCommitDescriptionWithAi": "Ĝēńēŕàţē ćōḿḿĩţ ďēśćŕĩƥţĩōń ŵĩţĥ ÀĨ", "generateCommitMessageWithAi": "Ĝēńēŕàţē ćōḿḿĩţ ḿēśśàĝē ŵĩţĥ ÀĨ", "generateDescriptionWithAi": "Ĝēńēŕàţē {{shortName}} ďēśćŕĩƥţĩōń ŵĩţĥ ÀĨ", @@ -47,8 +56,22 @@ "loading": "Ĺōàďĩńĝ…", "longNameTitle": "{{longName}} ţĩţĺē", "longNameTitlePlaceholder": "{{longName}} ţĩţĺē...", + "merge": "Ḿēŕĝē", "noChangesToCommit": "Ńō ćĥàńĝēś ţō ćōḿḿĩţ", "noSavedQueriesYet": "Ńō śàvēď qũēŕĩēś ŷēţ", + "ontoBranch": "ōńţō {{branch}}", + "openVcsOptions": "Ōƥēń VĆŚ ōƥţĩōńś", + "pickActionThenRepo": "Ƥĩćķ àćţĩōń, ţĥēń ŕēƥō", + "pickARepositoryForCommit": "Ƥĩćķ à ŕēƥōśĩţōŕŷ ƒōŕ Ćōḿḿĩţ", + "pickARepositoryForCreatePr": "Ƥĩćķ à ŕēƥōśĩţōŕŷ ƒōŕ Ćŕēàţē ƤŔ", + "pickARepositoryForPush": "Ƥĩćķ à ŕēƥōśĩţōŕŷ ƒōŕ Ƥũśĥ", + "pickARepositoryForRebase": "Ƥĩćķ à ŕēƥōśĩţōŕŷ ƒōŕ Ŕēƀàśē", + "pull": "Ƥũĺĺ", + "push": "Ƥũśĥ", + "pushCommitsToRemote_one": "Ƥũśĥ {{count}} ćōḿḿĩţ ţō ŕēḿōţē", + "pushCommitsToRemote_other": "Ƥũśĥ {{count}} ćōḿḿĩţś ţō ŕēḿōţē", + "rebase": "Ŕēƀàśē", + "rebaseOntoBranchBehind": "Ŕēƀàśē ōńţō {{branch}} ({{behind}} ƀēĥĩńď)", "reconnectProvider": "Ŕēćōńńēćţ {{name}}", "repository": "Ŕēƥōśĩţōŕŷ", "retryShortName": "Ŕēţŕŷ {{shortName}}", diff --git a/apps/web/src/locales/pseudo/workflows.json b/apps/web/src/locales/pseudo/workflows.json index a1a0a12834..640450d2a0 100644 --- a/apps/web/src/locales/pseudo/workflows.json +++ b/apps/web/src/locales/pseudo/workflows.json @@ -225,6 +225,7 @@ "wipLimit": "ŴĨƤ ĺĩḿĩţ", "wipLimitHelp": "Ḿàxĩḿũḿ àďḿĩţţēď (àćţĩvē) ţàśķś ĩń ţĥĩś śţēƥ. Ōvēŕƒĺōŵ śţàŷś vĩśĩƀĺē ĩń à qũēũē. Ũśē 0 ƒōŕ ũńĺĩḿĩţēď.", "wipLimitsNote": "ŴĨƤ ĺĩḿĩţś àćţĩvē ŵōŕķ, ńōţ vĩśĩƀĩĺĩţŷ. Ōvēŕƒĺōŵ ŕēḿàĩńś ōń ţĥē ƀōàŕď ũńţĩĺ ćàƥàćĩţŷ ōƥēńś; ĩƒ à śēĺēćţēď ƒēēďēŕ ĩś àĺśō ƒũĺĺ, ńēŵ ţàśķ ćŕēàţĩōń ĩś ŕēĵēćţēď.", + "workflow": "Ŵōŕķƒĺōŵ", "workflowCycleBlocked": "Ŵōŕķƒĺōŵ ćŷćĺē ƀĺōćķēď", "workflowCycleBlockedDescription": "Ćĥàńĝē ţĥē ŵōŕķƒĺōŵ śţēƥś ţō ŕēḿōvē ţĥē àũţōḿàţĩć ćŷćĺē ƀēƒōŕē ćōńţĩńũĩńĝ.", "workflowCycleBlockingBody": "Ţĥĩś ƥàţĥ ŕē-ēńţēŕś \"{{stepName}}\" àńď ćàń śţàŕţ ţĥē àĝēńţ àĝàĩń ŵĩţĥōũţ àńōţĥēŕ ũśēŕ àćţĩōń.",