diff --git a/src/main/db.schema.ts b/src/main/db.schema.ts index 53117c818..76948b13d 100644 --- a/src/main/db.schema.ts +++ b/src/main/db.schema.ts @@ -12,6 +12,7 @@ export const projects = sqliteTable("projects", { scripts: text("scripts"), // JSON searchSettings: text("search_settings"), // JSON mcpServers: text("mcp_servers"), // JSON + workspaceId: text("workspace_id"), disabled: integer("disabled", { mode: "boolean" }).notNull().default(false), sortOrder: integer("sort_order").notNull().default(0), createdAt: text("created_at").notNull(), diff --git a/src/main/db/connection.ts b/src/main/db/connection.ts index 31eebd405..d30fd8202 100644 --- a/src/main/db/connection.ts +++ b/src/main/db/connection.ts @@ -544,6 +544,12 @@ export function initDatabase(dbPath: string) { } if (storedVersion < 28) { + // Workspace a project belongs to. Left NULL on upgrade; the renderer's + // first-run bootstrap files existing projects into the default workspace. + const cols = sqlite.prepare("PRAGMA table_info(projects)").all() as { name: string }[]; + if (!cols.some((c) => c.name === "workspace_id")) { + sqlite.exec("ALTER TABLE projects ADD COLUMN workspace_id TEXT"); + } sqlite.exec(` CREATE TABLE IF NOT EXISTS pr_watches ( project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, diff --git a/src/main/db/projectsThreads.test.ts b/src/main/db/projectsThreads.test.ts index 8359b0cea..8d16b0f52 100644 --- a/src/main/db/projectsThreads.test.ts +++ b/src/main/db/projectsThreads.test.ts @@ -139,6 +139,48 @@ describe("projectsThreads (real sqlite round-trip)", () => { }); }); + it("round-trips the project workspace through the projects table", () => { + const project = { + id: "project-1", + name: "Test project", + location: { kind: "posix" as const, path: "/tmp/project" }, + createdAt: "2026-01-01T00:00:00.000Z", + }; + + // Unfiled by default — a project created before workspaces existed must not + // come back pinned to some arbitrary group. + expect(dbGetProject("project-1")?.workspaceId).toBeUndefined(); + + dbUpsertProject({ ...project, workspaceId: "ws-work" }, 0); + expect(dbGetProject("project-1")?.workspaceId).toBe("ws-work"); + + dbUpsertProject({ ...project, workspaceId: "ws-side" }, 0); + expect(dbGetProject("project-1")?.workspaceId).toBe("ws-side"); + + // Unfiling clears the column rather than leaving the previous value behind. + dbUpsertProject(project, 0); + expect(dbGetProject("project-1")?.workspaceId).toBeUndefined(); + }); + + it("round-trips the project workspace through the bulk renderer sync", () => { + const project = { + id: "project-bulk", + name: "Bulk project", + location: { kind: "posix" as const, path: "/tmp/project-bulk" }, + createdAt: "2026-01-01T00:00:00.000Z", + }; + const viewJson = JSON.stringify({ kind: "home" }); + + dbSyncAll([{ ...project, workspaceId: "ws-work" }], [], viewJson); + expect(dbGetProject(project.id)?.workspaceId).toBe("ws-work"); + + dbSyncAll([{ ...project, workspaceId: "ws-side" }], [], viewJson); + expect(dbGetProject(project.id)?.workspaceId).toBe("ws-side"); + + dbSyncAll([project], [], viewJson); + expect(dbGetProject(project.id)?.workspaceId).toBeUndefined(); + }); + it("persists candidate threads and the experiment record atomically", () => { const existing = testThread({ id: "candidate-existing" }); dbPersistExperimentState({ diff --git a/src/main/db/projectsThreads.ts b/src/main/db/projectsThreads.ts index 477e61bcd..a3c392340 100644 --- a/src/main/db/projectsThreads.ts +++ b/src/main/db/projectsThreads.ts @@ -64,6 +64,7 @@ export function dbUpsertProject(project: Project, sortOrder: number): void { scripts: project.scripts ? JSON.stringify(project.scripts) : null, searchSettings: project.searchSettings ? JSON.stringify(project.searchSettings) : null, mcpServers: project.mcpServers ? JSON.stringify(project.mcpServers) : null, + workspaceId: project.workspaceId ?? null, disabled: !!project.disabled, sortOrder, createdAt: project.createdAt, @@ -77,6 +78,7 @@ export function dbUpsertProject(project: Project, sortOrder: number): void { scripts: project.scripts ? JSON.stringify(project.scripts) : null, searchSettings: project.searchSettings ? JSON.stringify(project.searchSettings) : null, mcpServers: project.mcpServers ? JSON.stringify(project.mcpServers) : null, + workspaceId: project.workspaceId ?? null, disabled: !!project.disabled, sortOrder, }, diff --git a/src/main/db/rowMappers.ts b/src/main/db/rowMappers.ts index 516a5d1ff..074a0a05b 100644 --- a/src/main/db/rowMappers.ts +++ b/src/main/db/rowMappers.ts @@ -43,6 +43,7 @@ export function rowToProject(row: typeof schema.projects.$inferSelect): Project ...(row.scripts ? { scripts: JSON.parse(row.scripts) } : {}), ...(row.searchSettings ? { searchSettings: JSON.parse(row.searchSettings) } : {}), ...(row.mcpServers ? { mcpServers: JSON.parse(row.mcpServers) } : {}), + ...(row.workspaceId ? { workspaceId: row.workspaceId } : {}), ...(row.disabled ? { disabled: true } : {}), createdAt: row.createdAt, }; diff --git a/src/main/db/sync.ts b/src/main/db/sync.ts index 39b92dae7..929da8e02 100644 --- a/src/main/db/sync.ts +++ b/src/main/db/sync.ts @@ -92,12 +92,12 @@ function prepareProjectSyncStatement(sqlite: InstanceType): Sql return sqlite.prepare(` INSERT INTO projects ( id, name, location_kind, location_path, location_distro, location_linux_path, - location_unc_path, last_draft_config, scripts, search_settings, mcp_servers, disabled, - sort_order, created_at + location_unc_path, last_draft_config, scripts, search_settings, mcp_servers, workspace_id, + disabled, sort_order, created_at ) VALUES ( @id, @name, @locationKind, @locationPath, @locationDistro, @locationLinuxPath, - @locationUncPath, @lastDraftConfig, @scripts, @searchSettings, @mcpServers, @disabled, - @sortOrder, @createdAt + @locationUncPath, @lastDraftConfig, @scripts, @searchSettings, @mcpServers, @workspaceId, + @disabled, @sortOrder, @createdAt ) ON CONFLICT(id) DO UPDATE SET name = excluded.name, @@ -110,6 +110,7 @@ function prepareProjectSyncStatement(sqlite: InstanceType): Sql scripts = excluded.scripts, search_settings = excluded.search_settings, mcp_servers = excluded.mcp_servers, + workspace_id = excluded.workspace_id, disabled = excluded.disabled, sort_order = excluded.sort_order `); @@ -124,6 +125,7 @@ function runProjectSync(stmt: SqliteStatement, project: Project, sortOrder: numb scripts: project.scripts ? JSON.stringify(project.scripts) : null, searchSettings: project.searchSettings ? JSON.stringify(project.searchSettings) : null, mcpServers: project.mcpServers ? JSON.stringify(project.mcpServers) : null, + workspaceId: project.workspaceId ?? null, disabled: project.disabled ? 1 : 0, sortOrder, createdAt: project.createdAt, diff --git a/src/main/sharedSettingsFile.test.ts b/src/main/sharedSettingsFile.test.ts index 0cf12484d..db84f13dd 100644 --- a/src/main/sharedSettingsFile.test.ts +++ b/src/main/sharedSettingsFile.test.ts @@ -125,6 +125,7 @@ describe("sharedSettingsFile", () => { notifyL2Cli: true, remotePushEnabled: true, remotePushRedactContent: false, + workspaces: [], favoriteModels: [], recentModels: [], agentHookSupport: {}, @@ -247,6 +248,7 @@ describe("sharedSettingsFile", () => { notifyL2Cli: true, remotePushEnabled: true, remotePushRedactContent: false, + workspaces: [], favoriteModels: [], recentModels: [], agentHookSupport: {}, diff --git a/src/renderer/actions/createProjectActions.test.ts b/src/renderer/actions/createProjectActions.test.ts index c461ad65c..115168956 100644 --- a/src/renderer/actions/createProjectActions.test.ts +++ b/src/renderer/actions/createProjectActions.test.ts @@ -1,15 +1,19 @@ import { beforeEach, describe, expect, test, vi } from "vitest"; +const ACTIVE_WORKSPACE_ID = "ws-active"; + const mocks = vi.hoisted(() => ({ createProjectDirectory: vi.fn<(p: unknown) => Promise<{ path: string }>>(), cloneRepo: vi.fn<(p: unknown) => Promise<{ path: string }>>(), pickFolder: vi.fn<(d?: string) => Promise>(), - addProject: vi.fn<(location: unknown, name?: string) => unknown>((location, name) => ({ - id: "p1", - name: name ?? "x", - location, - createdAt: "t", - })), + addProject: vi.fn<(location: unknown, name?: string, workspaceId?: string) => unknown>( + (location, name) => ({ + id: "p1", + name: name ?? "x", + location, + createdAt: "t", + }), + ), openDraft: vi.fn<(id: string) => void>(), setLastUsedProjectDir: vi.fn<(key: string, dir: string) => void>(), autoDetectSetupScript: vi.fn<(project: unknown) => void>(), @@ -33,6 +37,10 @@ vi.mock("@/renderer/actions/projectActions", () => ({ vi.mock("@/renderer/state/appStore", () => ({ useAppStore: { getState: () => ({ addProject: mocks.addProject, openDraft: mocks.openDraft }) }, })); +// New projects inherit whichever workspace the user is currently viewing. +vi.mock("@/renderer/state/workspaceStore", () => ({ + getActiveWorkspaceId: () => ACTIVE_WORKSPACE_ID, +})); vi.mock("@/renderer/state/sharedSettingsStore", () => ({ useSharedSettings: { getState: () => ({ @@ -67,7 +75,11 @@ describe("commitCreateProject", () => { }); expect(createProjectDirectory).not.toHaveBeenCalled(); - expect(addProject).toHaveBeenCalledWith({ kind: "posix", path: "/Users/me/code/app" }, "app"); + expect(addProject).toHaveBeenCalledWith( + { kind: "posix", path: "/Users/me/code/app" }, + "app", + ACTIVE_WORKSPACE_ID, + ); expect(setLastUsedProjectDir).toHaveBeenCalledWith("native", "/Users/me/code"); expect(openDraft).toHaveBeenCalledWith("p1"); }); @@ -87,7 +99,11 @@ describe("commitCreateProject", () => { name: "new", kind: "posix", }); - expect(addProject).toHaveBeenCalledWith({ kind: "posix", path: "/Users/me/code/new" }, "new"); + expect(addProject).toHaveBeenCalledWith( + { kind: "posix", path: "/Users/me/code/new" }, + "new", + ACTIVE_WORKSPACE_ID, + ); // scratch records the parent the user browsed, not the new folder. expect(setLastUsedProjectDir).toHaveBeenCalledWith("native", "/Users/me/code"); }); @@ -142,6 +158,7 @@ describe("commitCloneProject", () => { expect(addProject).toHaveBeenCalledWith( { kind: "posix", path: "/Users/me/code/poracode" }, "poracode", + ACTIVE_WORKSPACE_ID, ); // Records the parent the user cloned into, not the new folder. expect(setLastUsedProjectDir).toHaveBeenCalledWith("native", "/Users/me/code"); @@ -163,7 +180,11 @@ describe("commitCloneProject", () => { name: "repo", source: { kind: "url", url: "https://github.com/owner/repo.git" }, }); - expect(addProject).toHaveBeenCalledWith({ kind: "posix", path: "/Users/me/code/repo" }, "repo"); + expect(addProject).toHaveBeenCalledWith( + { kind: "posix", path: "/Users/me/code/repo" }, + "repo", + ACTIVE_WORKSPACE_ID, + ); }); test("clone failure propagates and does not add a project", async () => { @@ -199,6 +220,7 @@ describe("addExistingProject", () => { expect(addProject).toHaveBeenCalledWith( { kind: "posix", path: "/Users/me/code/app" }, undefined, + ACTIVE_WORKSPACE_ID, ); expect(setLastUsedProjectDir).toHaveBeenCalledWith("native", "/Users/me/code"); expect(createProjectDirectory).not.toHaveBeenCalled(); @@ -227,6 +249,7 @@ describe("addExistingProject", () => { uncPath: "\\\\wsl.localhost\\Ubuntu\\home\\demo\\app", }, undefined, + ACTIVE_WORKSPACE_ID, ); expect(setLastUsedProjectDir).toHaveBeenCalledWith( "Ubuntu", diff --git a/src/renderer/actions/createProjectActions.ts b/src/renderer/actions/createProjectActions.ts index 4bb970467..59abfdff9 100644 --- a/src/renderer/actions/createProjectActions.ts +++ b/src/renderer/actions/createProjectActions.ts @@ -13,6 +13,7 @@ import { readBridge } from "@/renderer/bridge"; import { loadHomeScopeLocation } from "@/renderer/actions/projectActions"; import { useAppStore } from "@/renderer/state/appStore"; import { useSharedSettings } from "@/renderer/state/sharedSettingsStore"; +import { getActiveWorkspaceId } from "@/renderer/state/workspaceStore"; import { autoDetectSetupScript } from "@/renderer/utils/gitHelpers"; /** Whether a project is being created from scratch or opened from an existing folder. */ @@ -35,7 +36,11 @@ function registerNewProject(location: ProjectLocation, name: string, lastUsedDir useSharedSettings.getState().setLastUsedProjectDir(runtimeKeyForLocation(location), lastUsedDir); startTransition(() => { - const project = useAppStore.getState().addProject(location, name || undefined); + // New projects join the workspace the user is currently looking at, + // otherwise they'd land unfiled and show up in every workspace. + const project = useAppStore + .getState() + .addProject(location, name || undefined, getActiveWorkspaceId() ?? undefined); autoDetectSetupScript(project); useAppStore.getState().openDraft(project.id); }); diff --git a/src/renderer/actions/panelActions.ts b/src/renderer/actions/panelActions.ts index 38bdbe116..db89dc425 100644 --- a/src/renderer/actions/panelActions.ts +++ b/src/renderer/actions/panelActions.ts @@ -91,6 +91,10 @@ export function openChangelogSettings(): void { usePanelStore.getState().openSettingsSection("changelog"); } +export function openWorkspaceSettings(): void { + usePanelStore.getState().openSettingsSection("workspaces"); +} + /** Open the docked usage panel, or close all right-side panels if it is already active. */ export function openUsagePanel(): void { const panelStore = usePanelStore.getState(); diff --git a/src/renderer/actions/workspaceActions.test.ts b/src/renderer/actions/workspaceActions.test.ts new file mode 100644 index 000000000..495e2dd76 --- /dev/null +++ b/src/renderer/actions/workspaceActions.test.ts @@ -0,0 +1,95 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { useAppStore } from "@/renderer/state/appStore"; +import { useSharedSettings } from "@/renderer/state/sharedSettingsStore"; +import { useWorkspaceStore } from "@/renderer/state/workspaceStore"; +import { createWorkspace, deleteWorkspace, renameWorkspace } from "./workspaceActions"; + +vi.mock("@heroui/react", () => ({ + toast: { + info: vi.fn<(message: string) => void>(), + warning: vi.fn<(message: string) => void>(), + danger: vi.fn<(message: string) => void>(), + }, +})); + +function addProject(name: string, workspaceId?: string) { + return useAppStore + .getState() + .addProject({ kind: "posix", path: `/repos/${name}` }, name, workspaceId); +} + +describe("workspaceActions", () => { + beforeEach(() => { + localStorage.clear(); + useSharedSettings.setState({ workspaces: [] }); + useWorkspaceStore.setState({ activeWorkspaceId: null }); + useAppStore.setState((state) => ({ ...state, projects: [], threads: [] })); + }); + + it("creates a workspace and switches to it", () => { + const first = createWorkspace("Work"); + const second = createWorkspace("Side Hustle"); + + expect(useSharedSettings.getState().workspaces.map((w) => w.name)).toEqual([ + "Work", + "Side Hustle", + ]); + expect(useSharedSettings.getState().workspaces.map((w) => w.icon)).toEqual([ + "briefcase", + "rocket", + ]); + expect(useWorkspaceStore.getState().activeWorkspaceId).toBe(second!.id); + expect(first!.id).not.toBe(second!.id); + }); + + it("refuses a blank name", () => { + expect(createWorkspace(" ")).toBeNull(); + expect(useSharedSettings.getState().workspaces).toHaveLength(0); + }); + + it("trims names on create and rename", () => { + const workspace = createWorkspace(" Work ")!; + expect(workspace.name).toBe("Work"); + + renameWorkspace(workspace.id, " Client "); + expect(useSharedSettings.getState().workspaces[0]!.name).toBe("Client"); + }); + + it("moves projects to another workspace when one is deleted", () => { + const work = createWorkspace("Work")!; + const side = createWorkspace("Side Hustle")!; + const alpha = addProject("alpha", work.id); + const beta = addProject("beta", side.id); + + deleteWorkspace(work.id); + + const projects = useAppStore.getState().projects; + // Deleting a grouping must never delete the work inside it. + expect(projects).toHaveLength(2); + expect(projects.find((p) => p.id === alpha.id)?.workspaceId).toBe(side.id); + expect(projects.find((p) => p.id === beta.id)?.workspaceId).toBe(side.id); + expect(useSharedSettings.getState().workspaces.map((w) => w.id)).toEqual([side.id]); + }); + + it("moves the active workspace off a deleted one", () => { + const work = createWorkspace("Work")!; + const side = createWorkspace("Side Hustle")!; + useWorkspaceStore.setState({ activeWorkspaceId: side.id }); + + deleteWorkspace(side.id); + + expect(useWorkspaceStore.getState().activeWorkspaceId).toBe(work.id); + }); + + it("refuses to delete the last workspace", () => { + const only = createWorkspace("Work")!; + const alpha = addProject("alpha", only.id); + + deleteWorkspace(only.id); + + expect(useSharedSettings.getState().workspaces).toHaveLength(1); + expect(useAppStore.getState().projects.find((p) => p.id === alpha.id)?.workspaceId).toBe( + only.id, + ); + }); +}); diff --git a/src/renderer/actions/workspaceActions.ts b/src/renderer/actions/workspaceActions.ts new file mode 100644 index 000000000..259cfac95 --- /dev/null +++ b/src/renderer/actions/workspaceActions.ts @@ -0,0 +1,66 @@ +import { toast } from "@heroui/react"; +import { msg, plural } from "@lingui/core/macro"; +import type { Workspace } from "@/shared/contracts"; +import { i18n } from "@/renderer/i18n/i18n"; +import { useAppStore } from "@/renderer/state/appStore"; +import { useSharedSettings } from "@/renderer/state/sharedSettingsStore"; +import { getActiveWorkspaceId, useWorkspaceStore } from "@/renderer/state/workspaceStore"; + +/** Create a workspace and switch to it. Returns null when the name is blank. */ +export function createWorkspace(name: string): Workspace | null { + const trimmed = name.trim(); + if (!trimmed) return null; + const workspace = useSharedSettings.getState().addWorkspace(trimmed); + useWorkspaceStore.getState().setActiveWorkspaceId(workspace.id); + return workspace; +} + +export function renameWorkspace(workspaceId: string, name: string): void { + const trimmed = name.trim(); + if (!trimmed) return; + useSharedSettings.getState().renameWorkspace(workspaceId, trimmed); +} + +/** + * Where a deleted workspace's projects would land. Exported so the confirm dialog + * names the same destination the deletion actually uses — two copies of this rule + * would let the dialog promise one workspace while the action picks another. + */ +export function workspaceDeletionFallback(workspaceId: string): Workspace | undefined { + return useSharedSettings.getState().workspaces.find((w) => w.id !== workspaceId); +} + +/** + * Delete a workspace, handing its projects to another one first. Deleting a + * grouping must never delete the work inside it, so the projects are always + * re-filed rather than left pointing at a workspace that no longer exists. + */ +export function deleteWorkspace(workspaceId: string): void { + const fallback = workspaceDeletionFallback(workspaceId); + if (!fallback) { + toast.warning(i18n._(msg`Keep at least one workspace.`)); + return; + } + + let movedCount = 0; + for (const project of useAppStore.getState().projects) { + if (project.workspaceId === workspaceId) movedCount += 1; + } + const wasActive = getActiveWorkspaceId() === workspaceId; + + useAppStore.getState().refileProjects(workspaceId, fallback.id); + useSharedSettings.getState().removeWorkspace(workspaceId); + if (wasActive) useWorkspaceStore.getState().setActiveWorkspaceId(fallback.id); + + if (movedCount > 0) { + const target = fallback.name; + toast.info( + i18n._( + msg`${plural(movedCount, { + one: `Moved # project to ${target}.`, + other: `Moved # projects to ${target}.`, + })}`, + ), + ); + } +} diff --git a/src/renderer/components/common/SidebarButton.tsx b/src/renderer/components/common/SidebarButton.tsx index ba681f1db..27f56ebd8 100644 --- a/src/renderer/components/common/SidebarButton.tsx +++ b/src/renderer/components/common/SidebarButton.tsx @@ -3,6 +3,19 @@ import { Tooltip } from "@heroui/react"; import type { StatusTone } from "@/renderer/components/providers/statusTone"; import { handleKeyActivate } from "@/renderer/utils/a11y"; +/** + * The idle sidebar-row treatment, shared with rows that can't be a + * `SidebarButton` because they are a menu trigger and must own their own + * element (e.g. the workspace switcher). Keeping one definition means padding, + * radius, and hover can't drift between neighbouring rows. + */ +export function sidebarRowClass(options: { density?: "default" | "compact" } = {}): string { + const compact = options.density === "compact"; + return `flex w-full shrink-0 cursor-default items-center gap-2 rounded-3xl px-2 ${ + compact ? "py-1" : "py-1.5" + } text-left text-sm text-muted outline-none transition-colors hover:bg-[var(--row-hover)] hover:text-foreground focus-visible:focus-ring`; +} + export function SidebarButton(props: { ref?: React.Ref; icon: React.ReactNode; diff --git a/src/renderer/components/workspace/WorkspaceIcon.tsx b/src/renderer/components/workspace/WorkspaceIcon.tsx new file mode 100644 index 000000000..272ff4a3a --- /dev/null +++ b/src/renderer/components/workspace/WorkspaceIcon.tsx @@ -0,0 +1,36 @@ +import { + Briefcase, + Building2, + Code2, + Gamepad2, + Globe2, + GraduationCap, + Heart, + Lightbulb, + Music2, + Palette, + Rocket, + Sprout, + type LucideIcon, +} from "lucide-react"; +import type { WorkspaceIconId } from "@/shared/contracts"; + +const WORKSPACE_ICONS: Record = { + briefcase: Briefcase, + rocket: Rocket, + palette: Palette, + code: Code2, + building: Building2, + graduation: GraduationCap, + heart: Heart, + sprout: Sprout, + globe: Globe2, + lightbulb: Lightbulb, + music: Music2, + gamepad: Gamepad2, +}; + +export function WorkspaceIcon(props: { icon: WorkspaceIconId; className?: string }) { + const Icon = WORKSPACE_ICONS[props.icon]; + return ; +} diff --git a/src/renderer/components/workspace/WorkspaceNameDialog.tsx b/src/renderer/components/workspace/WorkspaceNameDialog.tsx new file mode 100644 index 000000000..8d0b49eaf --- /dev/null +++ b/src/renderer/components/workspace/WorkspaceNameDialog.tsx @@ -0,0 +1,71 @@ +import { useEffect, useState } from "react"; +import { Input, Label, Modal, TextField } from "@heroui/react"; +import { Trans, useLingui } from "@lingui/react/macro"; +import { Button } from "@/renderer/components/common/Button"; + +/** + * Single-field dialog for naming a workspace, shared by "Add workspace" in the + * sidebar switcher and "Rename" in the Workspaces settings section. + */ +export function WorkspaceNameDialog(props: { + isOpen: boolean; + mode: "create" | "rename"; + initialName?: string; + onSubmit: (name: string) => void; + onClose: () => void; +}) { + const { t } = useLingui(); + const { isOpen, mode, initialName = "", onSubmit, onClose } = props; + const [name, setName] = useState(initialName); + + // Reset on each open so a cancelled edit doesn't leak into the next one. + useEffect(() => { + if (isOpen) setName(initialName); + }, [isOpen, initialName]); + + const trimmed = name.trim(); + const canSubmit = trimmed.length > 0; + + function submit() { + if (!canSubmit) return; + onSubmit(trimmed); + onClose(); + } + + return ( + !open && onClose()}> + + + + + + {mode === "create" ? New workspace : Rename workspace} + + + + { + if (event.key === "Enter") submit(); + }} + > + + + + + + + + + + + + ); +} diff --git a/src/renderer/components/workspace/workspaceMenuKeys.ts b/src/renderer/components/workspace/workspaceMenuKeys.ts new file mode 100644 index 000000000..126df1c09 --- /dev/null +++ b/src/renderer/components/workspace/workspaceMenuKeys.ts @@ -0,0 +1,32 @@ +/** + * Menu keys for the workspace surfaces. `ContextMenu` and `OptionMenu` are + * key-string APIs with a single `onAction`/`onChange`, so workspace choices have + * to be encoded into ids — building and parsing them here keeps the encoding in + * one place instead of letting each menu invent its own prefix. + */ + +const WORKSPACE_KEY_PREFIX = "workspace:"; + +/** "Unfiled" — belongs to no workspace, so it shows in all of them. */ +export const WORKSPACE_UNFILED_KEY = "workspace:unfiled"; +export const WORKSPACE_ADD_KEY = "workspace:add"; +export const WORKSPACE_MANAGE_KEY = "workspace:manage"; + +export function workspaceMenuKey(workspaceId: string): string { + return `${WORKSPACE_KEY_PREFIX}${workspaceId}`; +} + +export type WorkspaceMenuSelection = + | { kind: "workspace"; workspaceId: string } + | { kind: "unfiled" } + | { kind: "add" } + | { kind: "manage" }; + +/** Returns null for keys owned by another namespace (e.g. `action:` entries). */ +export function parseWorkspaceMenuKey(key: string): WorkspaceMenuSelection | null { + if (key === WORKSPACE_UNFILED_KEY) return { kind: "unfiled" }; + if (key === WORKSPACE_ADD_KEY) return { kind: "add" }; + if (key === WORKSPACE_MANAGE_KEY) return { kind: "manage" }; + if (!key.startsWith(WORKSPACE_KEY_PREFIX)) return null; + return { kind: "workspace", workspaceId: key.slice(WORKSPACE_KEY_PREFIX.length) }; +} diff --git a/src/renderer/hooks/useAppHydration.ts b/src/renderer/hooks/useAppHydration.ts index c43bb3b0e..b01a0e63a 100644 --- a/src/renderer/hooks/useAppHydration.ts +++ b/src/renderer/hooks/useAppHydration.ts @@ -10,6 +10,7 @@ import { import { recoverExperimentCandidateWorktrees } from "@/renderer/state/experimentHydration"; import { hydrateThreadRuntimeItems } from "@/renderer/state/chatRuntimePersister"; import { useSharedSettings } from "@/renderer/state/sharedSettingsStore"; +import { bootstrapWorkspaces } from "@/renderer/state/workspaceStore"; import { startPrMergeAutoDone } from "@/renderer/state/prMergeAutoDone"; import { startDeferredFeaturePrewarm } from "@/renderer/deferredFeatures"; @@ -91,6 +92,15 @@ export function useAppHydration(options: { runtimeOwner?: boolean } = {}) { `[renderer] +${Date.now() - loadT0}ms: store hydrated, view=${JSON.stringify(restoredView)}, ${useAppStore.getState().projects.length} projects, ${useAppStore.getState().threads.length} threads`, ); + // Seed default workspaces and file pre-existing projects. Runtime-owner + // only: a remote client must not decide how the desktop's projects are + // grouped. Projects are hydrated by now, so nothing is missed. + if (runtimeOwner) { + void bootstrapWorkspaces().catch((error: unknown) => { + captureRendererException(error, { featureArea: "hydration" }); + }); + } + void (async () => { const candidateRecovery = recoverExperimentCandidateWorktrees(); if (candidateRecovery) await candidateRecovery; diff --git a/src/renderer/locales/de/messages.po b/src/renderer/locales/de/messages.po index ed915d140..7d35fa4b5 100644 --- a/src/renderer/locales/de/messages.po +++ b/src/renderer/locales/de/messages.po @@ -66,8 +66,10 @@ msgstr "{0, plural, one {# gekoppelte Verbindung} other {# gekoppelte Verbindung msgid "{0, plural, one {# paired desktop} other {# paired desktops}}" msgstr "{0, plural, one {# gekoppelter Desktop} other {# gekoppelte Desktops}}" +#. placeholder {0}: projectCounts.get(workspace.id) ?? 0 #. placeholder {0}: props.projects.length #: src/mobile/views/ManageProjectsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "{0, plural, one {# project} other {# projects}}" msgstr "{0, plural, one {# Projekt} other {# Projekte}}" @@ -221,6 +223,11 @@ msgstr "{0} benötigt eine Eingabe." msgid "{0} of {matchCount}" msgstr "{0} von {matchCount}" +#. placeholder {0}: projectCount(workspace.id) +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "{0} projects" +#~ msgstr "{0} Projekte" + #. placeholder {0}: cell.count #: src/renderer/views/ProfileOverlay/parts/ActivityHeatmap.tsx msgid "{0} prompt - {when}" @@ -366,6 +373,10 @@ msgstr "{head} oder {tail}" msgid "{heading} mode" msgstr "{heading}-Modus" +#: src/renderer/views/SchedulesView/SchedulesView.tsx +msgid "{hiddenByWorkspaceCount, plural, one {# schedule belongs to a project in another workspace.} other {# schedules belong to projects in another workspace.}}" +msgstr "{hiddenByWorkspaceCount, plural, one {# Zeitplan gehört zu einem Projekt in einem anderen Arbeitsbereich.} other {# Zeitpläne gehören zu Projekten in anderen Arbeitsbereichen.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{hook} hook failed" msgstr "{hook}-Hook ist fehlgeschlagen" @@ -445,6 +456,10 @@ msgstr "vor {minutes} min" msgid "{missingAuthLabel} needs authentication." msgstr "{missingAuthLabel} benötigt eine Authentifizierung." +#: src/renderer/actions/workspaceActions.ts +msgid "{movedCount, plural, one {Moved # project to {target}.} other {Moved # projects to {target}.}}" +msgstr "{movedCount, plural, one {# Projekt nach {target} verschoben.} other {# Projekte nach {target} verschoben.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{original}\n" "Worktree cleanup also failed: {cleanup}" @@ -545,6 +560,10 @@ msgstr "~{money}{tokens} · {0} · geschätzt" msgid "≈{0}% by reset" msgstr "≈{0}% bis zum Reset" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "1 project" +#~ msgstr "1 Projekt" + #: src/mobile/views/ThreadsView.tsx msgid "1 thread" msgstr "1 Thread" @@ -654,6 +673,7 @@ msgstr "Aktionen für {title}" #: src/renderer/components/thread/ThreadGoalDock.tsx #: src/renderer/views/SchedulesView/ScheduleEditor.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Active" msgstr "Aktiv" @@ -826,6 +846,11 @@ msgstr "Zum Home-Bildschirm hinzufügen" msgid "Add to-do" msgstr "Aufgabe hinzufügen" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Add workspace" +msgstr "Arbeitsbereich hinzufügen" + #: src/renderer/views/SettingsOverlay/parts/RemoteAccessSettings.tsx msgid "Advertise a custom base URL, e.g. a reverse proxy or tunnel. Leave empty for automatic." msgstr "Eine benutzerdefinierte Basis-URL bekanntgeben, z. B. für einen Reverse-Proxy oder Tunnel. Für automatisch leer lassen." @@ -982,6 +1007,11 @@ msgstr "Alle Projekte" msgid "All sources" msgstr "Alle Quellen" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "All workspaces" +msgstr "Alle Arbeitsbereiche" + #: src/mobile/push/WebPushPermissionPrompt.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/helpers.ts #: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx @@ -1643,6 +1673,7 @@ msgstr "Ports konnten nicht geladen werden" #: src/renderer/components/thread/ThreadGoalControls.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/structuredElicitation.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/ThreadRuntimeRequestPanel.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeRunDialog.tsx #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/GitReviewSidebar.tsx @@ -2889,6 +2920,7 @@ msgid "Couldn't update the skill." msgstr "Der Skill konnte nicht aktualisiert werden." #: src/renderer/components/thread/ChatPane/parts/items/FileChange.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Create" msgstr "Erstellen" @@ -3167,6 +3199,7 @@ msgstr "Delegieren Sie Aufgaben an andere KI-Agenten." #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" msgstr "Löschen" @@ -3255,6 +3288,14 @@ msgstr "Thread löschen?" msgid "Delete to-do" msgstr "To-do löschen" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace" +msgstr "Arbeitsbereich löschen" + +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace?" +msgstr "Arbeitsbereich löschen?" + #: src/mobile/ThreadTitleRow.tsx #: src/mobile/views/threadActionSurfaces.tsx #: src/mobile/views/threadContextMenus.tsx @@ -4649,6 +4690,10 @@ msgstr "Gruppenname" msgid "Group open threads" msgstr "Offene Threads gruppieren" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Group projects so the sidebar, schedules, and pull requests only show one set at a time. Switch between them at the bottom of the sidebar." +msgstr "Gruppiere Projekte, damit Seitenleiste, Zeitpläne und Pull Requests jeweils nur eine Gruppe zeigen. Wechsle unten in der Seitenleiste zwischen ihnen." + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts msgid "GUI chat font size" @@ -5077,6 +5122,14 @@ msgstr "Zu Bereich springen" msgid "just now" msgstr "gerade eben" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Keep at least one workspace" +msgstr "Behalte mindestens einen Arbeitsbereich" + +#: src/renderer/actions/workspaceActions.ts +msgid "Keep at least one workspace." +msgstr "Behalte mindestens einen Arbeitsbereich." + #: src/renderer/views/MainView/parts/Sidebar/parts/ForceDeleteBranchDialog.tsx msgid "Keep Branch" msgstr "Zweig behalten" @@ -5452,6 +5505,10 @@ msgstr "Verwalten Sie die MCP-Server-Konfigurationen, die Poracode beim Start un #~ msgid "Manage the skills Poracode makes available to supported agents. Project skills can be managed in each project's settings." #~ msgstr "Verwalte die Skills, die Poracode unterstützten Agents zur Verfügung stellt. Projekt-Skills können in den Einstellungen des jeweiligen Projekts verwaltet werden." +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Manage workspaces" +msgstr "Arbeitsbereiche verwalten" + #: src/renderer/components/skills/SkillsManager.tsx #~ msgid "Managed" #~ msgstr "Verwaltet" @@ -5947,10 +6004,24 @@ msgstr "Browser in Fenster verschieben" msgid "Move changes to a new worktree" msgstr "Änderungen in einen neuen Arbeitsbaum verschieben" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +msgid "Move to Workspace" +msgstr "In Arbeitsbereich verschieben" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Verschieben Sie das ToDo-Dock in den rechten Bereich" +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved {movedCount} projects to {0}." +#~ msgstr "{movedCount} Projekte nach {0} verschoben." + +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved 1 project to {0}." +#~ msgstr "1 Projekt nach {0} verschoben." + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Moved your changes into a new worktree on \"{newBranch}\". \"{currentBranch}\" is now clean." msgstr "Ihre Änderungen wurden in einen neuen Arbeitsbaum unter „{newBranch}“ verschoben. „{currentBranch}“ ist jetzt sauber." @@ -5965,6 +6036,7 @@ msgstr "my-mcp-server" #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/skills/SkillMarketplaceModal.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx msgid "Name" @@ -6162,6 +6234,10 @@ msgstr "Neuer Thread im Worktree" msgid "New Thread in Worktree" msgstr "Neuer Thread im Arbeitsbaum" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +msgid "New workspace" +msgstr "Neuer Arbeitsbereich" + #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx msgid "New worktree" msgstr "Neuer Arbeitsbaum" @@ -6454,6 +6530,10 @@ msgstr "Noch keine Phasen." msgid "No projects" msgstr "Keine Projekte" +#: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx +msgid "No projects in this workspace" +msgstr "Keine Projekte in diesem Arbeitsbereich" + #: src/renderer/views/SettingsOverlay/parts/RemoteServersSettings.tsx msgid "No projects on this server." msgstr "Keine Projekte auf diesem Server." @@ -6462,6 +6542,10 @@ msgstr "Keine Projekte auf diesem Server." msgid "No projects yet" msgstr "Noch keine Projekte" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "No projects yet." +msgstr "Noch keine Projekte." + #: src/renderer/components/providers/codex/index.tsx msgid "No prompts" msgstr "Keine Prompts" @@ -7540,9 +7624,16 @@ msgstr "Projektspezifische Überschreibungen zusätzlich zu den globalen Suchein #: src/mobile/WideShell.tsx #: src/renderer/components/mcp/McpProjectDestinationDropdown.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Projects" msgstr "Projekte" +#. placeholder {0}: deleting.name +#. placeholder {1}: deleteFallback.name +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Projects in {0} move to {1}. No project is deleted." +msgstr "Projekte in {0} werden nach {1} verschoben. Kein Projekt wird gelöscht." + #: src/mobile/setupEmptyState.tsx msgid "Projects live on the connected desktop. Add one before starting a thread." msgstr "Projekte befinden sich auf dem verbundenen Desktop. Füge eines hinzu, bevor du einen Thread startest." @@ -8095,6 +8186,11 @@ msgstr "Thread umbenennen" msgid "Rename to" msgstr "Umbenennen in" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Rename workspace" +msgstr "Arbeitsbereich umbenennen" + #: src/renderer/RendererCrashScreen.tsx msgid "Renderer crashed" msgstr "Der Renderer ist abgestürzt" @@ -8539,6 +8635,7 @@ msgstr "Laufzeitoptionen" #: src/mobile/views/FilesView.tsx #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/thread/ThreadGoalControls.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/FileEditorOverlay/parts/FileEditorPane/parts/EditorToolbar.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx @@ -9727,6 +9824,10 @@ msgstr "Zum vorherigen Chat wechseln" msgid "Switch to the previous tab" msgstr "Zum vorherigen Tab wechseln" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Switch workspace" +msgstr "Arbeitsbereich wechseln" + #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/parts/CommitSyncPanel.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/useWorktreeActions.ts @@ -11350,6 +11451,7 @@ msgid "Winner merged." msgstr "Gewinner zusammengeführt." #: src/renderer/components/providers/composerControlBuilders.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Work" msgstr "Arbeit" @@ -11414,6 +11516,7 @@ msgstr "Arbeiten…" #: src/renderer/components/mcp/McpServersManager.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Workspace" msgstr "Arbeitsbereich" @@ -11425,6 +11528,12 @@ msgstr "MCP-Server des Arbeitsbereichs überschreiben gleichnamige Benutzerserve msgid "Workspace view" msgstr "Arbeitsbereichsansicht" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Workspaces" +msgstr "Arbeitsbereiche" + #: src/mobile/views/ThreadsView.tsx #: src/renderer/components/thread/WorktreeModeSelect.tsx msgid "Worktree" diff --git a/src/renderer/locales/en/messages.po b/src/renderer/locales/en/messages.po index 03f6617f0..f48bcc24f 100644 --- a/src/renderer/locales/en/messages.po +++ b/src/renderer/locales/en/messages.po @@ -66,8 +66,10 @@ msgstr "{0, plural, one {# paired connection} other {# paired connections}}" msgid "{0, plural, one {# paired desktop} other {# paired desktops}}" msgstr "{0, plural, one {# paired desktop} other {# paired desktops}}" +#. placeholder {0}: projectCounts.get(workspace.id) ?? 0 #. placeholder {0}: props.projects.length #: src/mobile/views/ManageProjectsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "{0, plural, one {# project} other {# projects}}" msgstr "{0, plural, one {# project} other {# projects}}" @@ -221,6 +223,11 @@ msgstr "{0} needs input." msgid "{0} of {matchCount}" msgstr "{0} of {matchCount}" +#. placeholder {0}: projectCount(workspace.id) +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "{0} projects" +#~ msgstr "{0} projects" + #. placeholder {0}: cell.count #: src/renderer/views/ProfileOverlay/parts/ActivityHeatmap.tsx msgid "{0} prompt - {when}" @@ -366,6 +373,10 @@ msgstr "{head}, or {tail}" msgid "{heading} mode" msgstr "{heading} mode" +#: src/renderer/views/SchedulesView/SchedulesView.tsx +msgid "{hiddenByWorkspaceCount, plural, one {# schedule belongs to a project in another workspace.} other {# schedules belong to projects in another workspace.}}" +msgstr "{hiddenByWorkspaceCount, plural, one {# schedule belongs to a project in another workspace.} other {# schedules belong to projects in another workspace.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{hook} hook failed" msgstr "{hook} hook failed" @@ -445,6 +456,10 @@ msgstr "{minutes}m ago" msgid "{missingAuthLabel} needs authentication." msgstr "{missingAuthLabel} needs authentication." +#: src/renderer/actions/workspaceActions.ts +msgid "{movedCount, plural, one {Moved # project to {target}.} other {Moved # projects to {target}.}}" +msgstr "{movedCount, plural, one {Moved # project to {target}.} other {Moved # projects to {target}.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{original}\n" "Worktree cleanup also failed: {cleanup}" @@ -545,6 +560,10 @@ msgstr "~{money}{tokens} · {0} · est." msgid "≈{0}% by reset" msgstr "≈{0}% by reset" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "1 project" +#~ msgstr "1 project" + #: src/mobile/views/ThreadsView.tsx msgid "1 thread" msgstr "1 thread" @@ -654,6 +673,7 @@ msgstr "Actions for {title}" #: src/renderer/components/thread/ThreadGoalDock.tsx #: src/renderer/views/SchedulesView/ScheduleEditor.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Active" msgstr "Active" @@ -826,6 +846,11 @@ msgstr "Add to Home Screen" msgid "Add to-do" msgstr "Add to-do" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Add workspace" +msgstr "Add workspace" + #: src/renderer/views/SettingsOverlay/parts/RemoteAccessSettings.tsx msgid "Advertise a custom base URL, e.g. a reverse proxy or tunnel. Leave empty for automatic." msgstr "Advertise a custom base URL, e.g. a reverse proxy or tunnel. Leave empty for automatic." @@ -982,6 +1007,11 @@ msgstr "All projects" msgid "All sources" msgstr "All sources" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "All workspaces" +msgstr "All workspaces" + #: src/mobile/push/WebPushPermissionPrompt.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/helpers.ts #: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx @@ -1643,6 +1673,7 @@ msgstr "Can't load ports" #: src/renderer/components/thread/ThreadGoalControls.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/structuredElicitation.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/ThreadRuntimeRequestPanel.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeRunDialog.tsx #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/GitReviewSidebar.tsx @@ -2889,6 +2920,7 @@ msgid "Couldn't update the skill." msgstr "Couldn't update the skill." #: src/renderer/components/thread/ChatPane/parts/items/FileChange.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Create" msgstr "Create" @@ -3167,6 +3199,7 @@ msgstr "Delegate work to other AI agents." #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" msgstr "Delete" @@ -3255,6 +3288,14 @@ msgstr "Delete thread?" msgid "Delete to-do" msgstr "Delete to-do" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace" +msgstr "Delete workspace" + +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace?" +msgstr "Delete workspace?" + #: src/mobile/ThreadTitleRow.tsx #: src/mobile/views/threadActionSurfaces.tsx #: src/mobile/views/threadContextMenus.tsx @@ -4649,6 +4690,10 @@ msgstr "Group name" msgid "Group open threads" msgstr "Group open threads" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Group projects so the sidebar, schedules, and pull requests only show one set at a time. Switch between them at the bottom of the sidebar." +msgstr "Group projects so the sidebar, schedules, and pull requests only show one set at a time. Switch between them at the bottom of the sidebar." + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts msgid "GUI chat font size" @@ -5077,6 +5122,14 @@ msgstr "Jump to section" msgid "just now" msgstr "just now" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Keep at least one workspace" +msgstr "Keep at least one workspace" + +#: src/renderer/actions/workspaceActions.ts +msgid "Keep at least one workspace." +msgstr "Keep at least one workspace." + #: src/renderer/views/MainView/parts/Sidebar/parts/ForceDeleteBranchDialog.tsx msgid "Keep Branch" msgstr "Keep Branch" @@ -5452,6 +5505,10 @@ msgstr "Manage the MCP server configurations Poracode adds when starting support #~ msgid "Manage the skills Poracode makes available to supported agents. Project skills can be managed in each project's settings." #~ msgstr "Manage the skills Poracode makes available to supported agents. Project skills can be managed in each project's settings." +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Manage workspaces" +msgstr "Manage workspaces" + #: src/renderer/components/skills/SkillsManager.tsx #~ msgid "Managed" #~ msgstr "Managed" @@ -5947,10 +6004,24 @@ msgstr "Move browser to window" msgid "Move changes to a new worktree" msgstr "Move changes to a new worktree" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +msgid "Move to Workspace" +msgstr "Move to Workspace" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Move todo dock to right panel" +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved {movedCount} projects to {0}." +#~ msgstr "Moved {movedCount} projects to {0}." + +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved 1 project to {0}." +#~ msgstr "Moved 1 project to {0}." + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Moved your changes into a new worktree on \"{newBranch}\". \"{currentBranch}\" is now clean." msgstr "Moved your changes into a new worktree on \"{newBranch}\". \"{currentBranch}\" is now clean." @@ -5965,6 +6036,7 @@ msgstr "my-mcp-server" #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/skills/SkillMarketplaceModal.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx msgid "Name" @@ -6162,6 +6234,10 @@ msgstr "New thread in worktree" msgid "New Thread in Worktree" msgstr "New Thread in Worktree" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +msgid "New workspace" +msgstr "New workspace" + #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx msgid "New worktree" msgstr "New worktree" @@ -6454,6 +6530,10 @@ msgstr "No phases yet." msgid "No projects" msgstr "No projects" +#: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx +msgid "No projects in this workspace" +msgstr "No projects in this workspace" + #: src/renderer/views/SettingsOverlay/parts/RemoteServersSettings.tsx msgid "No projects on this server." msgstr "No projects on this server." @@ -6462,6 +6542,10 @@ msgstr "No projects on this server." msgid "No projects yet" msgstr "No projects yet" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "No projects yet." +msgstr "No projects yet." + #: src/renderer/components/providers/codex/index.tsx msgid "No prompts" msgstr "No prompts" @@ -7540,9 +7624,16 @@ msgstr "Project-specific overrides on top of the global search settings." #: src/mobile/WideShell.tsx #: src/renderer/components/mcp/McpProjectDestinationDropdown.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Projects" msgstr "Projects" +#. placeholder {0}: deleting.name +#. placeholder {1}: deleteFallback.name +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Projects in {0} move to {1}. No project is deleted." +msgstr "Projects in {0} move to {1}. No project is deleted." + #: src/mobile/setupEmptyState.tsx msgid "Projects live on the connected desktop. Add one before starting a thread." msgstr "Projects live on the connected desktop. Add one before starting a thread." @@ -8095,6 +8186,11 @@ msgstr "Rename thread" msgid "Rename to" msgstr "Rename to" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Rename workspace" +msgstr "Rename workspace" + #: src/renderer/RendererCrashScreen.tsx msgid "Renderer crashed" msgstr "Renderer crashed" @@ -8539,6 +8635,7 @@ msgstr "Runtime options" #: src/mobile/views/FilesView.tsx #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/thread/ThreadGoalControls.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/FileEditorOverlay/parts/FileEditorPane/parts/EditorToolbar.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx @@ -9727,6 +9824,10 @@ msgstr "Switch to the previous chat" msgid "Switch to the previous tab" msgstr "Switch to the previous tab" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Switch workspace" +msgstr "Switch workspace" + #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/parts/CommitSyncPanel.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/useWorktreeActions.ts @@ -11350,6 +11451,7 @@ msgid "Winner merged." msgstr "Winner merged." #: src/renderer/components/providers/composerControlBuilders.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Work" msgstr "Work" @@ -11414,6 +11516,7 @@ msgstr "Working…" #: src/renderer/components/mcp/McpServersManager.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Workspace" msgstr "Workspace" @@ -11425,6 +11528,12 @@ msgstr "Workspace MCP servers override user servers with the same name and are a msgid "Workspace view" msgstr "Workspace view" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Workspaces" +msgstr "Workspaces" + #: src/mobile/views/ThreadsView.tsx #: src/renderer/components/thread/WorktreeModeSelect.tsx msgid "Worktree" diff --git a/src/renderer/locales/es/messages.po b/src/renderer/locales/es/messages.po index e598df8ec..e8091f400 100644 --- a/src/renderer/locales/es/messages.po +++ b/src/renderer/locales/es/messages.po @@ -66,8 +66,10 @@ msgstr "{0, plural, one {# conexión emparejada} other {# conexiones emparejadas msgid "{0, plural, one {# paired desktop} other {# paired desktops}}" msgstr "{0, plural, one {# escritorio emparejado} other {# escritorios emparejados}}" +#. placeholder {0}: projectCounts.get(workspace.id) ?? 0 #. placeholder {0}: props.projects.length #: src/mobile/views/ManageProjectsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "{0, plural, one {# project} other {# projects}}" msgstr "{0, plural, one {# proyecto} other {# proyectos}}" @@ -221,6 +223,11 @@ msgstr "{0} necesita una entrada." msgid "{0} of {matchCount}" msgstr "{0} de {matchCount}" +#. placeholder {0}: projectCount(workspace.id) +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "{0} projects" +#~ msgstr "{0} proyectos" + #. placeholder {0}: cell.count #: src/renderer/views/ProfileOverlay/parts/ActivityHeatmap.tsx msgid "{0} prompt - {when}" @@ -366,6 +373,10 @@ msgstr "{head} o {tail}" msgid "{heading} mode" msgstr "Modo {heading}" +#: src/renderer/views/SchedulesView/SchedulesView.tsx +msgid "{hiddenByWorkspaceCount, plural, one {# schedule belongs to a project in another workspace.} other {# schedules belong to projects in another workspace.}}" +msgstr "{hiddenByWorkspaceCount, plural, one {# programación pertenece a un proyecto de otro espacio de trabajo.} other {# programaciones pertenecen a proyectos de otros espacios de trabajo.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{hook} hook failed" msgstr "El hook {hook} falló" @@ -445,6 +456,10 @@ msgstr "hace {minutes}m" msgid "{missingAuthLabel} needs authentication." msgstr "{missingAuthLabel} necesita autenticación." +#: src/renderer/actions/workspaceActions.ts +msgid "{movedCount, plural, one {Moved # project to {target}.} other {Moved # projects to {target}.}}" +msgstr "{movedCount, plural, one {Se movió # proyecto a {target}.} other {Se movieron # proyectos a {target}.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{original}\n" "Worktree cleanup also failed: {cleanup}" @@ -545,6 +560,10 @@ msgstr "~{money}{tokens} · {0} · est." msgid "≈{0}% by reset" msgstr "≈{0}% para el restablecimiento" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "1 project" +#~ msgstr "1 proyecto" + #: src/mobile/views/ThreadsView.tsx msgid "1 thread" msgstr "1 hilo" @@ -654,6 +673,7 @@ msgstr "Acciones para {title}" #: src/renderer/components/thread/ThreadGoalDock.tsx #: src/renderer/views/SchedulesView/ScheduleEditor.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Active" msgstr "Activo" @@ -826,6 +846,11 @@ msgstr "Agregar a la pantalla de inicio" msgid "Add to-do" msgstr "Añadir tarea" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Add workspace" +msgstr "Añadir espacio de trabajo" + #: src/renderer/views/SettingsOverlay/parts/RemoteAccessSettings.tsx msgid "Advertise a custom base URL, e.g. a reverse proxy or tunnel. Leave empty for automatic." msgstr "Anuncia una URL base personalizada, por ejemplo un proxy inverso o un túnel. Déjalo vacío para que sea automático." @@ -982,6 +1007,11 @@ msgstr "Todos los proyectos" msgid "All sources" msgstr "Todas las fuentes" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "All workspaces" +msgstr "Todos los espacios de trabajo" + #: src/mobile/push/WebPushPermissionPrompt.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/helpers.ts #: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx @@ -1643,6 +1673,7 @@ msgstr "No se pudieron cargar los puertos" #: src/renderer/components/thread/ThreadGoalControls.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/structuredElicitation.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/ThreadRuntimeRequestPanel.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeRunDialog.tsx #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/GitReviewSidebar.tsx @@ -2889,6 +2920,7 @@ msgid "Couldn't update the skill." msgstr "No se pudo actualizar el skill." #: src/renderer/components/thread/ChatPane/parts/items/FileChange.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Create" msgstr "Crear" @@ -3167,6 +3199,7 @@ msgstr "Delega trabajo a otros agentes de IA." #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" msgstr "Eliminar" @@ -3255,6 +3288,14 @@ msgstr "¿Eliminar el hilo?" msgid "Delete to-do" msgstr "Eliminar tarea" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace" +msgstr "Eliminar espacio de trabajo" + +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace?" +msgstr "¿Eliminar el espacio de trabajo?" + #: src/mobile/ThreadTitleRow.tsx #: src/mobile/views/threadActionSurfaces.tsx #: src/mobile/views/threadContextMenus.tsx @@ -4649,6 +4690,10 @@ msgstr "Nombre del grupo" msgid "Group open threads" msgstr "Agrupar hilos abiertos" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Group projects so the sidebar, schedules, and pull requests only show one set at a time. Switch between them at the bottom of the sidebar." +msgstr "Agrupa proyectos para que la barra lateral, las programaciones y los pull requests muestren solo un conjunto a la vez. Cambia entre ellos en la parte inferior de la barra lateral." + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts msgid "GUI chat font size" @@ -5077,6 +5122,14 @@ msgstr "Ir a la sección" msgid "just now" msgstr "ahora mismo" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Keep at least one workspace" +msgstr "Conserva al menos un espacio de trabajo" + +#: src/renderer/actions/workspaceActions.ts +msgid "Keep at least one workspace." +msgstr "Conserva al menos un espacio de trabajo." + #: src/renderer/views/MainView/parts/Sidebar/parts/ForceDeleteBranchDialog.tsx msgid "Keep Branch" msgstr "Mantener rama" @@ -5452,6 +5505,10 @@ msgstr "Administra las configuraciones de servidores MCP que Poracode añade al #~ msgid "Manage the skills Poracode makes available to supported agents. Project skills can be managed in each project's settings." #~ msgstr "Gestiona los skills que Poracode pone a disposición de los agentes compatibles. Los skills de proyecto se pueden gestionar en la configuración de cada proyecto." +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Manage workspaces" +msgstr "Gestionar espacios de trabajo" + #: src/renderer/components/skills/SkillsManager.tsx #~ msgid "Managed" #~ msgstr "Gestionado" @@ -5947,10 +6004,24 @@ msgstr "Mover navegador a una ventana" msgid "Move changes to a new worktree" msgstr "Mover cambios a un nuevo worktree" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +msgid "Move to Workspace" +msgstr "Mover a espacio de trabajo" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Mover el dock de tareas al panel derecho" +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved {movedCount} projects to {0}." +#~ msgstr "Se movieron {movedCount} proyectos a {0}." + +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved 1 project to {0}." +#~ msgstr "Se movió 1 proyecto a {0}." + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Moved your changes into a new worktree on \"{newBranch}\". \"{currentBranch}\" is now clean." msgstr "Movió tus cambios a un nuevo worktree en \"{newBranch}\". \"{currentBranch}\" ahora está limpio." @@ -5965,6 +6036,7 @@ msgstr "my-mcp-server" #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/skills/SkillMarketplaceModal.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx msgid "Name" @@ -6162,6 +6234,10 @@ msgstr "Nuevo hilo en worktree" msgid "New Thread in Worktree" msgstr "Nuevo hilo en worktree" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +msgid "New workspace" +msgstr "Nuevo espacio de trabajo" + #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx msgid "New worktree" msgstr "Nuevo worktree" @@ -6454,6 +6530,10 @@ msgstr "Aún no hay fases." msgid "No projects" msgstr "No hay proyectos" +#: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx +msgid "No projects in this workspace" +msgstr "No hay proyectos en este espacio de trabajo" + #: src/renderer/views/SettingsOverlay/parts/RemoteServersSettings.tsx msgid "No projects on this server." msgstr "No hay proyectos en este servidor." @@ -6462,6 +6542,10 @@ msgstr "No hay proyectos en este servidor." msgid "No projects yet" msgstr "Aún no hay proyectos" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "No projects yet." +msgstr "Aún no hay proyectos." + #: src/renderer/components/providers/codex/index.tsx msgid "No prompts" msgstr "Sin prompts" @@ -7540,9 +7624,16 @@ msgstr "Anulaciones específicas del proyecto sobre los ajustes de búsqueda glo #: src/mobile/WideShell.tsx #: src/renderer/components/mcp/McpProjectDestinationDropdown.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Projects" msgstr "Proyectos" +#. placeholder {0}: deleting.name +#. placeholder {1}: deleteFallback.name +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Projects in {0} move to {1}. No project is deleted." +msgstr "Los proyectos de {0} pasan a {1}. No se elimina ningún proyecto." + #: src/mobile/setupEmptyState.tsx msgid "Projects live on the connected desktop. Add one before starting a thread." msgstr "Los proyectos están en el escritorio conectado. Agrega uno antes de iniciar un hilo." @@ -8095,6 +8186,11 @@ msgstr "Renombrar hilo" msgid "Rename to" msgstr "Renombrar a" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Rename workspace" +msgstr "Renombrar espacio de trabajo" + #: src/renderer/RendererCrashScreen.tsx msgid "Renderer crashed" msgstr "El renderizador falló" @@ -8539,6 +8635,7 @@ msgstr "Opciones de runtime" #: src/mobile/views/FilesView.tsx #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/thread/ThreadGoalControls.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/FileEditorOverlay/parts/FileEditorPane/parts/EditorToolbar.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx @@ -9727,6 +9824,10 @@ msgstr "Cambiar al chat anterior" msgid "Switch to the previous tab" msgstr "Cambiar a la pestaña anterior" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Switch workspace" +msgstr "Cambiar de espacio de trabajo" + #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/parts/CommitSyncPanel.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/useWorktreeActions.ts @@ -11350,6 +11451,7 @@ msgid "Winner merged." msgstr "Ganador fusionado." #: src/renderer/components/providers/composerControlBuilders.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Work" msgstr "Trabajo" @@ -11414,6 +11516,7 @@ msgstr "Trabajando…" #: src/renderer/components/mcp/McpServersManager.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Workspace" msgstr "Espacio de trabajo" @@ -11425,6 +11528,12 @@ msgstr "Los servidores MCP del espacio de trabajo anulan los servidores de usuar msgid "Workspace view" msgstr "Vista del espacio de trabajo" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Workspaces" +msgstr "Espacios de trabajo" + #: src/mobile/views/ThreadsView.tsx #: src/renderer/components/thread/WorktreeModeSelect.tsx msgid "Worktree" diff --git a/src/renderer/locales/fr/messages.po b/src/renderer/locales/fr/messages.po index 8050dfd39..88d832ff1 100644 --- a/src/renderer/locales/fr/messages.po +++ b/src/renderer/locales/fr/messages.po @@ -66,8 +66,10 @@ msgstr "{0, plural, one {# connexion associée} other {# connexions associées}} msgid "{0, plural, one {# paired desktop} other {# paired desktops}}" msgstr "{0, plural, one {# ordinateur associé} other {# ordinateurs associés}}" +#. placeholder {0}: projectCounts.get(workspace.id) ?? 0 #. placeholder {0}: props.projects.length #: src/mobile/views/ManageProjectsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "{0, plural, one {# project} other {# projects}}" msgstr "{0, plural, one {# projet} other {# projets}}" @@ -221,6 +223,11 @@ msgstr "{0} a besoin d'une réponse." msgid "{0} of {matchCount}" msgstr "{0} sur {matchCount}" +#. placeholder {0}: projectCount(workspace.id) +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "{0} projects" +#~ msgstr "{0} projets" + #. placeholder {0}: cell.count #: src/renderer/views/ProfileOverlay/parts/ActivityHeatmap.tsx msgid "{0} prompt - {when}" @@ -366,6 +373,10 @@ msgstr "{head}, ou {tail}" msgid "{heading} mode" msgstr "Mode {heading}" +#: src/renderer/views/SchedulesView/SchedulesView.tsx +msgid "{hiddenByWorkspaceCount, plural, one {# schedule belongs to a project in another workspace.} other {# schedules belong to projects in another workspace.}}" +msgstr "{hiddenByWorkspaceCount, plural, one {# planification appartient à un projet d'un autre espace de travail.} other {# planifications appartiennent à des projets d'autres espaces de travail.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{hook} hook failed" msgstr "Le hook {hook} a échoué" @@ -445,6 +456,10 @@ msgstr "il y a {minutes} min" msgid "{missingAuthLabel} needs authentication." msgstr "{missingAuthLabel} nécessite une authentification." +#: src/renderer/actions/workspaceActions.ts +msgid "{movedCount, plural, one {Moved # project to {target}.} other {Moved # projects to {target}.}}" +msgstr "{movedCount, plural, one {# projet déplacé vers {target}.} other {# projets déplacés vers {target}.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{original}\n" "Worktree cleanup also failed: {cleanup}" @@ -545,6 +560,10 @@ msgstr "~{money}{tokens} · {0} · est." msgid "≈{0}% by reset" msgstr "≈{0}% par remise à zéro" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "1 project" +#~ msgstr "1 projet" + #: src/mobile/views/ThreadsView.tsx msgid "1 thread" msgstr "1 fil" @@ -654,6 +673,7 @@ msgstr "Actions pour {title}" #: src/renderer/components/thread/ThreadGoalDock.tsx #: src/renderer/views/SchedulesView/ScheduleEditor.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Active" msgstr "Actif" @@ -826,6 +846,11 @@ msgstr "Ajouter à l'écran d'accueil" msgid "Add to-do" msgstr "Ajouter une tâche" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Add workspace" +msgstr "Ajouter un espace de travail" + #: src/renderer/views/SettingsOverlay/parts/RemoteAccessSettings.tsx msgid "Advertise a custom base URL, e.g. a reverse proxy or tunnel. Leave empty for automatic." msgstr "Annoncez une URL de base personnalisée, par ex. un proxy inverse ou un tunnel. Laissez vide pour un choix automatique." @@ -982,6 +1007,11 @@ msgstr "Tous les projets" msgid "All sources" msgstr "Toutes les sources" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "All workspaces" +msgstr "Tous les espaces de travail" + #: src/mobile/push/WebPushPermissionPrompt.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/helpers.ts #: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx @@ -1643,6 +1673,7 @@ msgstr "Impossible de charger les ports" #: src/renderer/components/thread/ThreadGoalControls.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/structuredElicitation.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/ThreadRuntimeRequestPanel.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeRunDialog.tsx #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/GitReviewSidebar.tsx @@ -2889,6 +2920,7 @@ msgid "Couldn't update the skill." msgstr "Impossible de mettre à jour la compétence." #: src/renderer/components/thread/ChatPane/parts/items/FileChange.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Create" msgstr "Créer" @@ -3167,6 +3199,7 @@ msgstr "Déléguez le travail à d’autres agents IA." #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" msgstr "Supprimer" @@ -3255,6 +3288,14 @@ msgstr "Supprimer le fil ?" msgid "Delete to-do" msgstr "Supprimer la tâche" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace" +msgstr "Supprimer l'espace de travail" + +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace?" +msgstr "Supprimer l'espace de travail ?" + #: src/mobile/ThreadTitleRow.tsx #: src/mobile/views/threadActionSurfaces.tsx #: src/mobile/views/threadContextMenus.tsx @@ -4649,6 +4690,10 @@ msgstr "Nom du groupe" msgid "Group open threads" msgstr "Grouper les discussions ouvertes" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Group projects so the sidebar, schedules, and pull requests only show one set at a time. Switch between them at the bottom of the sidebar." +msgstr "Regroupez les projets pour que la barre latérale, les planifications et les pull requests n'affichent qu'un ensemble à la fois. Changez d'espace en bas de la barre latérale." + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts msgid "GUI chat font size" @@ -5077,6 +5122,14 @@ msgstr "Aller à la section" msgid "just now" msgstr "juste maintenant" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Keep at least one workspace" +msgstr "Conservez au moins un espace de travail" + +#: src/renderer/actions/workspaceActions.ts +msgid "Keep at least one workspace." +msgstr "Conservez au moins un espace de travail." + #: src/renderer/views/MainView/parts/Sidebar/parts/ForceDeleteBranchDialog.tsx msgid "Keep Branch" msgstr "Garder la branche" @@ -5452,6 +5505,10 @@ msgstr "Gérez les configurations de serveurs MCP que Poracode ajoute au démarr #~ msgid "Manage the skills Poracode makes available to supported agents. Project skills can be managed in each project's settings." #~ msgstr "Gérez les compétences que Poracode met à la disposition des agents pris en charge. Les compétences de projet peuvent être gérées dans les paramètres de chaque projet." +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Manage workspaces" +msgstr "Gérer les espaces de travail" + #: src/renderer/components/skills/SkillsManager.tsx #~ msgid "Managed" #~ msgstr "Gérée" @@ -5946,10 +6003,24 @@ msgstr "Déplacer le navigateur vers une fenêtre" msgid "Move changes to a new worktree" msgstr "Déplacer les modifications vers un nouvel arbre de travail" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +msgid "Move to Workspace" +msgstr "Déplacer vers un espace de travail" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Déplacer le dock Todo vers le panneau de droite" +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved {movedCount} projects to {0}." +#~ msgstr "{movedCount} projets déplacés vers {0}." + +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved 1 project to {0}." +#~ msgstr "1 projet déplacé vers {0}." + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Moved your changes into a new worktree on \"{newBranch}\". \"{currentBranch}\" is now clean." msgstr "Vos modifications ont été déplacées vers un nouveau worktree sur \"{newBranch}\". \"{currentBranch}\" est désormais propre." @@ -5964,6 +6035,7 @@ msgstr "my-mcp-server" #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/skills/SkillMarketplaceModal.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx msgid "Name" @@ -6161,6 +6233,10 @@ msgstr "Nouveau fil dans le worktree" msgid "New Thread in Worktree" msgstr "Nouveau fil de discussion dans le worktree" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +msgid "New workspace" +msgstr "Nouvel espace de travail" + #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx msgid "New worktree" msgstr "Nouvel arbre de travail" @@ -6453,6 +6529,10 @@ msgstr "Aucune phase pour l'instant." msgid "No projects" msgstr "Aucun projet" +#: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx +msgid "No projects in this workspace" +msgstr "Aucun projet dans cet espace de travail" + #: src/renderer/views/SettingsOverlay/parts/RemoteServersSettings.tsx msgid "No projects on this server." msgstr "Aucun projet sur ce serveur." @@ -6461,6 +6541,10 @@ msgstr "Aucun projet sur ce serveur." msgid "No projects yet" msgstr "Aucun projet pour l'instant" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "No projects yet." +msgstr "Aucun projet pour le moment." + #: src/renderer/components/providers/codex/index.tsx msgid "No prompts" msgstr "Aucune invite" @@ -7539,9 +7623,16 @@ msgstr "Remplacements spécifiques au projet en plus des paramètres de recherch #: src/mobile/WideShell.tsx #: src/renderer/components/mcp/McpProjectDestinationDropdown.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Projects" msgstr "Projets" +#. placeholder {0}: deleting.name +#. placeholder {1}: deleteFallback.name +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Projects in {0} move to {1}. No project is deleted." +msgstr "Les projets de {0} sont déplacés vers {1}. Aucun projet n'est supprimé." + #: src/mobile/setupEmptyState.tsx msgid "Projects live on the connected desktop. Add one before starting a thread." msgstr "Les projets se trouvent sur le bureau connecté. Ajoutez-en un avant de démarrer un fil." @@ -8094,6 +8185,11 @@ msgstr "Renommer le fil de discussion" msgid "Rename to" msgstr "Renommer en" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Rename workspace" +msgstr "Renommer l'espace de travail" + #: src/renderer/RendererCrashScreen.tsx msgid "Renderer crashed" msgstr "Le moteur de rendu a planté" @@ -8538,6 +8634,7 @@ msgstr "Options d'exécution" #: src/mobile/views/FilesView.tsx #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/thread/ThreadGoalControls.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/FileEditorOverlay/parts/FileEditorPane/parts/EditorToolbar.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx @@ -9726,6 +9823,10 @@ msgstr "Passer au chat précédent" msgid "Switch to the previous tab" msgstr "Passer à l'onglet précédent" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Switch workspace" +msgstr "Changer d'espace de travail" + #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/parts/CommitSyncPanel.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/useWorktreeActions.ts @@ -11349,6 +11450,7 @@ msgid "Winner merged." msgstr "Gagnant fusionné." #: src/renderer/components/providers/composerControlBuilders.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Work" msgstr "Travail" @@ -11413,6 +11515,7 @@ msgstr "En cours…" #: src/renderer/components/mcp/McpServersManager.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Workspace" msgstr "Espace de travail" @@ -11424,6 +11527,12 @@ msgstr "Les serveurs MCP de l’espace de travail remplacent les serveurs utilis msgid "Workspace view" msgstr "Vue de l’espace de travail" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Workspaces" +msgstr "Espaces de travail" + #: src/mobile/views/ThreadsView.tsx #: src/renderer/components/thread/WorktreeModeSelect.tsx msgid "Worktree" diff --git a/src/renderer/locales/ja/messages.po b/src/renderer/locales/ja/messages.po index 42d5e560b..a8c44f5b6 100644 --- a/src/renderer/locales/ja/messages.po +++ b/src/renderer/locales/ja/messages.po @@ -66,8 +66,10 @@ msgstr "{0, plural, one {# 件のペアリング済み接続} other {# 件のペ msgid "{0, plural, one {# paired desktop} other {# paired desktops}}" msgstr "{0, plural, one {# 台のペアリング済みデスクトップ} other {# 台のペアリング済みデスクトップ}}" +#. placeholder {0}: projectCounts.get(workspace.id) ?? 0 #. placeholder {0}: props.projects.length #: src/mobile/views/ManageProjectsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "{0, plural, one {# project} other {# projects}}" msgstr "{0, plural, one {#件のプロジェクト} other {#件のプロジェクト}}" @@ -221,6 +223,11 @@ msgstr "{0}には入力が必要です。" msgid "{0} of {matchCount}" msgstr "{0} / {matchCount}" +#. placeholder {0}: projectCount(workspace.id) +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "{0} projects" +#~ msgstr "{0} 件のプロジェクト" + #. placeholder {0}: cell.count #: src/renderer/views/ProfileOverlay/parts/ActivityHeatmap.tsx msgid "{0} prompt - {when}" @@ -366,6 +373,10 @@ msgstr "{head}または{tail}" msgid "{heading} mode" msgstr "{heading}モード" +#: src/renderer/views/SchedulesView/SchedulesView.tsx +msgid "{hiddenByWorkspaceCount, plural, one {# schedule belongs to a project in another workspace.} other {# schedules belong to projects in another workspace.}}" +msgstr "{hiddenByWorkspaceCount, plural, other {# 件のスケジュールは他のワークスペースのプロジェクトに属しています。}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{hook} hook failed" msgstr "{hook}フックが失敗しました" @@ -445,6 +456,10 @@ msgstr "{minutes}分前" msgid "{missingAuthLabel} needs authentication." msgstr "{missingAuthLabel}には認証が必要です。" +#: src/renderer/actions/workspaceActions.ts +msgid "{movedCount, plural, one {Moved # project to {target}.} other {Moved # projects to {target}.}}" +msgstr "{movedCount, plural, other {# 件のプロジェクトを {target} に移動しました。}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{original}\n" "Worktree cleanup also failed: {cleanup}" @@ -544,6 +559,10 @@ msgstr "~{money}{tokens}·{0}· 推定。" msgid "≈{0}% by reset" msgstr "≈{0}% (リセットまでに)" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "1 project" +#~ msgstr "1 件のプロジェクト" + #: src/mobile/views/ThreadsView.tsx msgid "1 thread" msgstr "1 件のスレッド" @@ -653,6 +672,7 @@ msgstr "{title} のアクション" #: src/renderer/components/thread/ThreadGoalDock.tsx #: src/renderer/views/SchedulesView/ScheduleEditor.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Active" msgstr "アクティブ" @@ -825,6 +845,11 @@ msgstr "ホーム画面に追加" msgid "Add to-do" msgstr "タスクを追加" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Add workspace" +msgstr "ワークスペースを追加" + #: src/renderer/views/SettingsOverlay/parts/RemoteAccessSettings.tsx msgid "Advertise a custom base URL, e.g. a reverse proxy or tunnel. Leave empty for automatic." msgstr "カスタムのベースURLを通知します(例: リバースプロキシやトンネル)。空欄の場合は自動的に設定されます。" @@ -981,6 +1006,11 @@ msgstr "すべてのプロジェクト" msgid "All sources" msgstr "すべてのソース" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "All workspaces" +msgstr "すべてのワークスペース" + #: src/mobile/push/WebPushPermissionPrompt.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/helpers.ts #: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx @@ -1642,6 +1672,7 @@ msgstr "ポートを読み込めません" #: src/renderer/components/thread/ThreadGoalControls.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/structuredElicitation.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/ThreadRuntimeRequestPanel.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeRunDialog.tsx #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/GitReviewSidebar.tsx @@ -2888,6 +2919,7 @@ msgid "Couldn't update the skill." msgstr "スキルを更新できませんでした。" #: src/renderer/components/thread/ChatPane/parts/items/FileChange.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Create" msgstr "作成" @@ -3166,6 +3198,7 @@ msgstr "他の AI エージェントに作業を委任します。" #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" msgstr "削除" @@ -3254,6 +3287,14 @@ msgstr "スレッドを削除しますか?" msgid "Delete to-do" msgstr "To-Doを削除する" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace" +msgstr "ワークスペースを削除" + +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace?" +msgstr "ワークスペースを削除しますか?" + #: src/mobile/ThreadTitleRow.tsx #: src/mobile/views/threadActionSurfaces.tsx #: src/mobile/views/threadContextMenus.tsx @@ -4648,6 +4689,10 @@ msgstr "グループ名" msgid "Group open threads" msgstr "開いているスレッドをグループ化する" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Group projects so the sidebar, schedules, and pull requests only show one set at a time. Switch between them at the bottom of the sidebar." +msgstr "プロジェクトをグループ化して、サイドバー・スケジュール・プルリクエストに一度に1組だけ表示します。サイドバー下部で切り替えられます。" + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts msgid "GUI chat font size" @@ -5076,6 +5121,14 @@ msgstr "セクションへ移動" msgid "just now" msgstr "たった今" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Keep at least one workspace" +msgstr "ワークスペースは少なくとも1つ残してください" + +#: src/renderer/actions/workspaceActions.ts +msgid "Keep at least one workspace." +msgstr "ワークスペースは少なくとも1つ残してください。" + #: src/renderer/views/MainView/parts/Sidebar/parts/ForceDeleteBranchDialog.tsx msgid "Keep Branch" msgstr "ブランチを維持する" @@ -5451,6 +5504,10 @@ msgstr "対応エージェントの起動時にPoracodeが追加するMCPサー #~ msgid "Manage the skills Poracode makes available to supported agents. Project skills can be managed in each project's settings." #~ msgstr "Poracode が対応エージェントに提供するスキルを管理します。プロジェクトスキルは各プロジェクトの設定で管理できます。" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Manage workspaces" +msgstr "ワークスペースを管理" + #: src/renderer/components/skills/SkillsManager.tsx #~ msgid "Managed" #~ msgstr "管理対象" @@ -5945,10 +6002,24 @@ msgstr "ブラウザーをウィンドウに移動" msgid "Move changes to a new worktree" msgstr "変更を新しいワークツリーに移動する" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +msgid "Move to Workspace" +msgstr "ワークスペースへ移動" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Todo ドックを右側のパネルに移動します" +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved {movedCount} projects to {0}." +#~ msgstr "{movedCount} 件のプロジェクトを {0} に移動しました。" + +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved 1 project to {0}." +#~ msgstr "1 件のプロジェクトを {0} に移動しました。" + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Moved your changes into a new worktree on \"{newBranch}\". \"{currentBranch}\" is now clean." msgstr "変更内容を「{newBranch}」の新しいワークツリーに移動しました。「{currentBranch}」はクリーンになりました。" @@ -5963,6 +6034,7 @@ msgstr "my-mcp-server" #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/skills/SkillMarketplaceModal.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx msgid "Name" @@ -6160,6 +6232,10 @@ msgstr "worktree で新規スレッド" msgid "New Thread in Worktree" msgstr "ワークツリーの新しいスレッド" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +msgid "New workspace" +msgstr "新しいワークスペース" + #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx msgid "New worktree" msgstr "新しいワークツリー" @@ -6452,6 +6528,10 @@ msgstr "まだフェーズはありません。" msgid "No projects" msgstr "プロジェクトがありません" +#: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx +msgid "No projects in this workspace" +msgstr "このワークスペースにプロジェクトはありません" + #: src/renderer/views/SettingsOverlay/parts/RemoteServersSettings.tsx msgid "No projects on this server." msgstr "このサーバーにプロジェクトはありません。" @@ -6460,6 +6540,10 @@ msgstr "このサーバーにプロジェクトはありません。" msgid "No projects yet" msgstr "まだプロジェクトがありません" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "No projects yet." +msgstr "プロジェクトはまだありません。" + #: src/renderer/components/providers/codex/index.tsx msgid "No prompts" msgstr "プロンプトはありません" @@ -7538,9 +7622,16 @@ msgstr "グローバル検索設定に加えてプロジェクト固有のオー #: src/mobile/WideShell.tsx #: src/renderer/components/mcp/McpProjectDestinationDropdown.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Projects" msgstr "プロジェクト" +#. placeholder {0}: deleting.name +#. placeholder {1}: deleteFallback.name +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Projects in {0} move to {1}. No project is deleted." +msgstr "{0} のプロジェクトは {1} に移動します。プロジェクトは削除されません。" + #: src/mobile/setupEmptyState.tsx msgid "Projects live on the connected desktop. Add one before starting a thread." msgstr "プロジェクトは接続されたデスクトップにあります。スレッドを開始する前に追加してください。" @@ -8093,6 +8184,11 @@ msgstr "スレッド名の変更" msgid "Rename to" msgstr "名前を変更" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Rename workspace" +msgstr "ワークスペースの名前を変更" + #: src/renderer/RendererCrashScreen.tsx msgid "Renderer crashed" msgstr "レンダラーがクラッシュしました" @@ -8537,6 +8633,7 @@ msgstr "実行時オプション" #: src/mobile/views/FilesView.tsx #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/thread/ThreadGoalControls.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/FileEditorOverlay/parts/FileEditorPane/parts/EditorToolbar.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx @@ -9725,6 +9822,10 @@ msgstr "前のチャットに切り替え" msgid "Switch to the previous tab" msgstr "前のタブに切り替え" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Switch workspace" +msgstr "ワークスペースを切り替え" + #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/parts/CommitSyncPanel.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/useWorktreeActions.ts @@ -11348,6 +11449,7 @@ msgid "Winner merged." msgstr "勝者をマージしました。" #: src/renderer/components/providers/composerControlBuilders.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Work" msgstr "作業" @@ -11412,6 +11514,7 @@ msgstr "実行中…" #: src/renderer/components/mcp/McpServersManager.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Workspace" msgstr "ワークスペース" @@ -11423,6 +11526,12 @@ msgstr "ワークスペースのMCPサーバーは同名のユーザーサーバ msgid "Workspace view" msgstr "ワークスペース表示" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Workspaces" +msgstr "ワークスペース" + #: src/mobile/views/ThreadsView.tsx #: src/renderer/components/thread/WorktreeModeSelect.tsx msgid "Worktree" diff --git a/src/renderer/locales/ko/messages.po b/src/renderer/locales/ko/messages.po index 595b6d8ea..388e7b756 100644 --- a/src/renderer/locales/ko/messages.po +++ b/src/renderer/locales/ko/messages.po @@ -66,8 +66,10 @@ msgstr "{0, plural, one {#개의 페어링된 연결} other {#개의 페어링 msgid "{0, plural, one {# paired desktop} other {# paired desktops}}" msgstr "{0, plural, one {#개의 페어링된 데스크톱} other {#개의 페어링된 데스크톱}}" +#. placeholder {0}: projectCounts.get(workspace.id) ?? 0 #. placeholder {0}: props.projects.length #: src/mobile/views/ManageProjectsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "{0, plural, one {# project} other {# projects}}" msgstr "{0, plural, one {#개 프로젝트} other {#개 프로젝트}}" @@ -221,6 +223,11 @@ msgstr "{0}의 입력이 필요합니다." msgid "{0} of {matchCount}" msgstr "{0} / {matchCount}" +#. placeholder {0}: projectCount(workspace.id) +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "{0} projects" +#~ msgstr "프로젝트 {0}개" + #. placeholder {0}: cell.count #: src/renderer/views/ProfileOverlay/parts/ActivityHeatmap.tsx msgid "{0} prompt - {when}" @@ -366,6 +373,10 @@ msgstr "{head} 또는 {tail}" msgid "{heading} mode" msgstr "{heading} 모드" +#: src/renderer/views/SchedulesView/SchedulesView.tsx +msgid "{hiddenByWorkspaceCount, plural, one {# schedule belongs to a project in another workspace.} other {# schedules belong to projects in another workspace.}}" +msgstr "{hiddenByWorkspaceCount, plural, other {일정 #개가 다른 작업 영역의 프로젝트에 속해 있습니다.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{hook} hook failed" msgstr "{hook} 후크 실패" @@ -445,6 +456,10 @@ msgstr "{minutes}분 전" msgid "{missingAuthLabel} needs authentication." msgstr "{missingAuthLabel}에는 인증이 필요합니다." +#: src/renderer/actions/workspaceActions.ts +msgid "{movedCount, plural, one {Moved # project to {target}.} other {Moved # projects to {target}.}}" +msgstr "{movedCount, plural, other {프로젝트 #개를 {target}(으)로 옮겼습니다.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{original}\n" "Worktree cleanup also failed: {cleanup}" @@ -545,6 +560,10 @@ msgstr "~{money}{tokens} · {0} · 예상" msgid "≈{0}% by reset" msgstr "재설정 시 ≈{0}%" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "1 project" +#~ msgstr "프로젝트 1개" + #: src/mobile/views/ThreadsView.tsx msgid "1 thread" msgstr "스레드 1개" @@ -654,6 +673,7 @@ msgstr "{title} 작업" #: src/renderer/components/thread/ThreadGoalDock.tsx #: src/renderer/views/SchedulesView/ScheduleEditor.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Active" msgstr "활성" @@ -826,6 +846,11 @@ msgstr "홈 화면에 추가" msgid "Add to-do" msgstr "할 일 추가" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Add workspace" +msgstr "작업 영역 추가" + #: src/renderer/views/SettingsOverlay/parts/RemoteAccessSettings.tsx msgid "Advertise a custom base URL, e.g. a reverse proxy or tunnel. Leave empty for automatic." msgstr "사용자 정의 기본 URL을 게시합니다(예: 리버스 프록시 또는 터널). 자동으로 설정하려면 비워 두세요." @@ -982,6 +1007,11 @@ msgstr "모든 프로젝트" msgid "All sources" msgstr "모든 소스" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "All workspaces" +msgstr "모든 작업 영역" + #: src/mobile/push/WebPushPermissionPrompt.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/helpers.ts #: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx @@ -1643,6 +1673,7 @@ msgstr "포트를 불러올 수 없습니다" #: src/renderer/components/thread/ThreadGoalControls.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/structuredElicitation.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/ThreadRuntimeRequestPanel.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeRunDialog.tsx #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/GitReviewSidebar.tsx @@ -2889,6 +2920,7 @@ msgid "Couldn't update the skill." msgstr "스킬을 업데이트하지 못했습니다." #: src/renderer/components/thread/ChatPane/parts/items/FileChange.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Create" msgstr "생성" @@ -3167,6 +3199,7 @@ msgstr "다른 AI 에이전트에게 작업을 위임합니다." #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" msgstr "삭제" @@ -3255,6 +3288,14 @@ msgstr "스레드를 삭제하시겠습니까?" msgid "Delete to-do" msgstr "할 일 삭제" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace" +msgstr "작업 영역 삭제" + +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace?" +msgstr "작업 영역을 삭제할까요?" + #: src/mobile/ThreadTitleRow.tsx #: src/mobile/views/threadActionSurfaces.tsx #: src/mobile/views/threadContextMenus.tsx @@ -4649,6 +4690,10 @@ msgstr "그룹 이름" msgid "Group open threads" msgstr "열린 스레드 그룹화" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Group projects so the sidebar, schedules, and pull requests only show one set at a time. Switch between them at the bottom of the sidebar." +msgstr "프로젝트를 그룹으로 묶어 사이드바, 일정, 풀 리퀘스트에 한 번에 한 묶음만 표시합니다. 사이드바 하단에서 전환할 수 있습니다." + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts msgid "GUI chat font size" @@ -5077,6 +5122,14 @@ msgstr "섹션으로 이동" msgid "just now" msgstr "지금 막" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Keep at least one workspace" +msgstr "작업 영역을 최소 하나는 남겨 두세요" + +#: src/renderer/actions/workspaceActions.ts +msgid "Keep at least one workspace." +msgstr "작업 영역을 최소 하나는 남겨 두세요." + #: src/renderer/views/MainView/parts/Sidebar/parts/ForceDeleteBranchDialog.tsx msgid "Keep Branch" msgstr "브랜치 유지" @@ -5452,6 +5505,10 @@ msgstr "지원되는 에이전트를 시작할 때 Poracode에서 추가하는 M #~ msgid "Manage the skills Poracode makes available to supported agents. Project skills can be managed in each project's settings." #~ msgstr "Poracode가 지원되는 에이전트에 제공하는 스킬을 관리합니다. 프로젝트 스킬은 각 프로젝트의 설정에서 관리할 수 있습니다." +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Manage workspaces" +msgstr "작업 영역 관리" + #: src/renderer/components/skills/SkillsManager.tsx #~ msgid "Managed" #~ msgstr "관리됨" @@ -5947,10 +6004,24 @@ msgstr "브라우저를 창으로 이동" msgid "Move changes to a new worktree" msgstr "변경 사항을 새 작업 트리로 이동" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +msgid "Move to Workspace" +msgstr "작업 영역으로 이동" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "할 일 도크를 오른쪽 패널로 이동" +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved {movedCount} projects to {0}." +#~ msgstr "프로젝트 {movedCount}개를 {0}(으)로 옮겼습니다." + +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved 1 project to {0}." +#~ msgstr "프로젝트 1개를 {0}(으)로 옮겼습니다." + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Moved your changes into a new worktree on \"{newBranch}\". \"{currentBranch}\" is now clean." msgstr "변경 사항을 \"{newBranch}\"의 새 작업 트리로 옮겼습니다. \"{currentBranch}\"은 이제 깨끗합니다." @@ -5965,6 +6036,7 @@ msgstr "my-mcp-server" #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/skills/SkillMarketplaceModal.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx msgid "Name" @@ -6162,6 +6234,10 @@ msgstr "worktree에서 새 스레드" msgid "New Thread in Worktree" msgstr "작업 트리의 새 스레드" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +msgid "New workspace" +msgstr "새 작업 영역" + #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx msgid "New worktree" msgstr "새 작업 트리" @@ -6454,6 +6530,10 @@ msgstr "아직 단계가 없습니다." msgid "No projects" msgstr "프로젝트 없음" +#: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx +msgid "No projects in this workspace" +msgstr "이 작업 영역에 프로젝트가 없습니다" + #: src/renderer/views/SettingsOverlay/parts/RemoteServersSettings.tsx msgid "No projects on this server." msgstr "이 서버에 프로젝트가 없습니다." @@ -6462,6 +6542,10 @@ msgstr "이 서버에 프로젝트가 없습니다." msgid "No projects yet" msgstr "아직 프로젝트가 없습니다" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "No projects yet." +msgstr "아직 프로젝트가 없습니다." + #: src/renderer/components/providers/codex/index.tsx msgid "No prompts" msgstr "프롬프트 없음" @@ -7540,9 +7624,16 @@ msgstr "전역 검색 설정 위에 프로젝트별 재정의가 적용됩니다 #: src/mobile/WideShell.tsx #: src/renderer/components/mcp/McpProjectDestinationDropdown.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Projects" msgstr "프로젝트" +#. placeholder {0}: deleting.name +#. placeholder {1}: deleteFallback.name +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Projects in {0} move to {1}. No project is deleted." +msgstr "{0}의 프로젝트가 {1}(으)로 이동합니다. 삭제되는 프로젝트는 없습니다." + #: src/mobile/setupEmptyState.tsx msgid "Projects live on the connected desktop. Add one before starting a thread." msgstr "프로젝트는 연결된 데스크톱에 있습니다. 스레드를 시작하기 전에 프로젝트를 추가하세요." @@ -8095,6 +8186,11 @@ msgstr "스레드 이름 바꾸기" msgid "Rename to" msgstr "다음으로 이름 변경" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Rename workspace" +msgstr "작업 영역 이름 바꾸기" + #: src/renderer/RendererCrashScreen.tsx msgid "Renderer crashed" msgstr "렌더러가 충돌했습니다." @@ -8539,6 +8635,7 @@ msgstr "런타임 옵션" #: src/mobile/views/FilesView.tsx #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/thread/ThreadGoalControls.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/FileEditorOverlay/parts/FileEditorPane/parts/EditorToolbar.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx @@ -9727,6 +9824,10 @@ msgstr "이전 채팅으로 전환" msgid "Switch to the previous tab" msgstr "이전 탭으로 전환" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Switch workspace" +msgstr "작업 영역 전환" + #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/parts/CommitSyncPanel.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/useWorktreeActions.ts @@ -11350,6 +11451,7 @@ msgid "Winner merged." msgstr "우승자를 병합했습니다." #: src/renderer/components/providers/composerControlBuilders.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Work" msgstr "작업" @@ -11414,6 +11516,7 @@ msgstr "작업 중…" #: src/renderer/components/mcp/McpServersManager.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Workspace" msgstr "작업 영역" @@ -11425,6 +11528,12 @@ msgstr "작업 영역 MCP 서버는 이름이 같은 사용자 서버를 재정 msgid "Workspace view" msgstr "작업 영역 보기" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Workspaces" +msgstr "작업 영역" + #: src/mobile/views/ThreadsView.tsx #: src/renderer/components/thread/WorktreeModeSelect.tsx msgid "Worktree" diff --git a/src/renderer/locales/pl/messages.po b/src/renderer/locales/pl/messages.po index 025d52f70..34ad9acb8 100644 --- a/src/renderer/locales/pl/messages.po +++ b/src/renderer/locales/pl/messages.po @@ -66,8 +66,10 @@ msgstr "{0, plural, one {# sparowane połączenie} few {# sparowane połączenia msgid "{0, plural, one {# paired desktop} other {# paired desktops}}" msgstr "{0, plural, one {# sparowany desktop} few {# sparowane desktopy} many {# sparowanych desktopów} other {# sparowanego desktopa}}" +#. placeholder {0}: projectCounts.get(workspace.id) ?? 0 #. placeholder {0}: props.projects.length #: src/mobile/views/ManageProjectsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "{0, plural, one {# project} other {# projects}}" msgstr "{0, plural, one {# projekt} few {# projekty} many {# projektów} other {# projektu}}" @@ -221,6 +223,11 @@ msgstr "{0} wymaga danych wejściowych." msgid "{0} of {matchCount}" msgstr "{0} z {matchCount}" +#. placeholder {0}: projectCount(workspace.id) +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "{0} projects" +#~ msgstr "Projekty: {0}" + #. placeholder {0}: cell.count #: src/renderer/views/ProfileOverlay/parts/ActivityHeatmap.tsx msgid "{0} prompt - {when}" @@ -366,6 +373,10 @@ msgstr "{head} lub {tail}" msgid "{heading} mode" msgstr "Tryb {heading}" +#: src/renderer/views/SchedulesView/SchedulesView.tsx +msgid "{hiddenByWorkspaceCount, plural, one {# schedule belongs to a project in another workspace.} other {# schedules belong to projects in another workspace.}}" +msgstr "{hiddenByWorkspaceCount, plural, one {# harmonogram należy do projektu w innym obszarze roboczym.} few {# harmonogramy należą do projektów w innych obszarach roboczych.} many {# harmonogramów należy do projektów w innych obszarach roboczych.} other {# harmonogramu należy do projektów w innych obszarach roboczych.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{hook} hook failed" msgstr "Hak {hook} nie powiódł się" @@ -445,6 +456,10 @@ msgstr "{minutes} min temu" msgid "{missingAuthLabel} needs authentication." msgstr "{missingAuthLabel} wymaga uwierzytelnienia." +#: src/renderer/actions/workspaceActions.ts +msgid "{movedCount, plural, one {Moved # project to {target}.} other {Moved # projects to {target}.}}" +msgstr "{movedCount, plural, one {Przeniesiono # projekt do {target}.} few {Przeniesiono # projekty do {target}.} many {Przeniesiono # projektów do {target}.} other {Przeniesiono # projektu do {target}.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{original}\n" "Worktree cleanup also failed: {cleanup}" @@ -545,6 +560,10 @@ msgstr "~{money}{tokens} · {0} · szac." msgid "≈{0}% by reset" msgstr "≈{0}% do resetu" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "1 project" +#~ msgstr "1 projekt" + #: src/mobile/views/ThreadsView.tsx msgid "1 thread" msgstr "1 wątek" @@ -654,6 +673,7 @@ msgstr "Akcje dla {title}" #: src/renderer/components/thread/ThreadGoalDock.tsx #: src/renderer/views/SchedulesView/ScheduleEditor.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Active" msgstr "Aktywny" @@ -826,6 +846,11 @@ msgstr "Dodaj do ekranu głównego" msgid "Add to-do" msgstr "Dodaj zadanie" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Add workspace" +msgstr "Dodaj obszar roboczy" + #: src/renderer/views/SettingsOverlay/parts/RemoteAccessSettings.tsx msgid "Advertise a custom base URL, e.g. a reverse proxy or tunnel. Leave empty for automatic." msgstr "Ustaw niestandardowy bazowy URL, np. reverse proxy lub tunel. Pozostaw puste, aby ustawić automatycznie." @@ -982,6 +1007,11 @@ msgstr "Wszystkie projekty" msgid "All sources" msgstr "Wszystkie źródła" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "All workspaces" +msgstr "Wszystkie obszary robocze" + #: src/mobile/push/WebPushPermissionPrompt.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/helpers.ts #: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx @@ -1643,6 +1673,7 @@ msgstr "Nie można wczytać portów" #: src/renderer/components/thread/ThreadGoalControls.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/structuredElicitation.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/ThreadRuntimeRequestPanel.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeRunDialog.tsx #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/GitReviewSidebar.tsx @@ -2889,6 +2920,7 @@ msgid "Couldn't update the skill." msgstr "Nie udało się zaktualizować umiejętności." #: src/renderer/components/thread/ChatPane/parts/items/FileChange.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Create" msgstr "Utwórz" @@ -3167,6 +3199,7 @@ msgstr "Deleguj zadania innym agentom AI." #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" msgstr "Usuń" @@ -3255,6 +3288,14 @@ msgstr "Usunąć wątek?" msgid "Delete to-do" msgstr "Usuń zadanie" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace" +msgstr "Usuń obszar roboczy" + +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace?" +msgstr "Usunąć obszar roboczy?" + #: src/mobile/ThreadTitleRow.tsx #: src/mobile/views/threadActionSurfaces.tsx #: src/mobile/views/threadContextMenus.tsx @@ -4649,6 +4690,10 @@ msgstr "Nazwa grupy" msgid "Group open threads" msgstr "Grupuj otwarte wątki" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Group projects so the sidebar, schedules, and pull requests only show one set at a time. Switch between them at the bottom of the sidebar." +msgstr "Grupuj projekty, aby pasek boczny, harmonogramy i żądania ściągnięcia pokazywały tylko jeden zestaw naraz. Przełączaj je u dołu paska bocznego." + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts msgid "GUI chat font size" @@ -5077,6 +5122,14 @@ msgstr "Przejdź do sekcji" msgid "just now" msgstr "właśnie teraz" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Keep at least one workspace" +msgstr "Zachowaj co najmniej jeden obszar roboczy" + +#: src/renderer/actions/workspaceActions.ts +msgid "Keep at least one workspace." +msgstr "Zachowaj co najmniej jeden obszar roboczy." + #: src/renderer/views/MainView/parts/Sidebar/parts/ForceDeleteBranchDialog.tsx msgid "Keep Branch" msgstr "Zachowaj gałąź" @@ -5452,6 +5505,10 @@ msgstr "Zarządzaj konfiguracjami serwerów MCP dodawanymi przez Poracode podcza #~ msgid "Manage the skills Poracode makes available to supported agents. Project skills can be managed in each project's settings." #~ msgstr "Zarządzaj umiejętnościami udostępnianymi przez Poracode obsługiwanym agentom. Umiejętnościami projektu można zarządzać w ustawieniach każdego projektu." +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Manage workspaces" +msgstr "Zarządzaj obszarami roboczymi" + #: src/renderer/components/skills/SkillsManager.tsx #~ msgid "Managed" #~ msgstr "Zarządzana" @@ -5947,10 +6004,24 @@ msgstr "Przenieś przeglądarkę do okna" msgid "Move changes to a new worktree" msgstr "Przenieś zmiany do nowego drzewa roboczego" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +msgid "Move to Workspace" +msgstr "Przenieś do obszaru roboczego" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Przenieś dok zadań do prawego panelu" +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved {movedCount} projects to {0}." +#~ msgstr "Przeniesiono {movedCount} projektów do {0}." + +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved 1 project to {0}." +#~ msgstr "Przeniesiono 1 projekt do {0}." + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Moved your changes into a new worktree on \"{newBranch}\". \"{currentBranch}\" is now clean." msgstr "Przeniesiono zmiany do nowego drzewa roboczego na „{newBranch}”. „{currentBranch}” jest teraz czysty." @@ -5965,6 +6036,7 @@ msgstr "my-mcp-server" #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/skills/SkillMarketplaceModal.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx msgid "Name" @@ -6162,6 +6234,10 @@ msgstr "Nowy wątek w worktree" msgid "New Thread in Worktree" msgstr "Nowy wątek w drzewie roboczym" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +msgid "New workspace" +msgstr "Nowy obszar roboczy" + #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx msgid "New worktree" msgstr "Nowe drzewo robocze" @@ -6454,6 +6530,10 @@ msgstr "Nie ma jeszcze faz." msgid "No projects" msgstr "Brak projektów" +#: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx +msgid "No projects in this workspace" +msgstr "Brak projektów w tym obszarze roboczym" + #: src/renderer/views/SettingsOverlay/parts/RemoteServersSettings.tsx msgid "No projects on this server." msgstr "Brak projektów na tym serwerze." @@ -6462,6 +6542,10 @@ msgstr "Brak projektów na tym serwerze." msgid "No projects yet" msgstr "Brak projektów" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "No projects yet." +msgstr "Nie ma jeszcze projektów." + #: src/renderer/components/providers/codex/index.tsx msgid "No prompts" msgstr "Brak podpowiedzi" @@ -7540,9 +7624,16 @@ msgstr "Zastąpienia specyficzne dla projektu ponad globalnymi ustawieniami wysz #: src/mobile/WideShell.tsx #: src/renderer/components/mcp/McpProjectDestinationDropdown.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Projects" msgstr "Projekty" +#. placeholder {0}: deleting.name +#. placeholder {1}: deleteFallback.name +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Projects in {0} move to {1}. No project is deleted." +msgstr "Projekty z {0} zostaną przeniesione do {1}. Żaden projekt nie zostanie usunięty." + #: src/mobile/setupEmptyState.tsx msgid "Projects live on the connected desktop. Add one before starting a thread." msgstr "Projekty znajdują się na połączonym komputerze. Dodaj projekt, zanim rozpoczniesz wątek." @@ -8095,6 +8186,11 @@ msgstr "Zmień nazwę wątku" msgid "Rename to" msgstr "Zmień nazwę na" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Rename workspace" +msgstr "Zmień nazwę obszaru roboczego" + #: src/renderer/RendererCrashScreen.tsx msgid "Renderer crashed" msgstr "Renderer uległ awarii" @@ -8539,6 +8635,7 @@ msgstr "Opcje środowiska wykonawczego" #: src/mobile/views/FilesView.tsx #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/thread/ThreadGoalControls.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/FileEditorOverlay/parts/FileEditorPane/parts/EditorToolbar.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx @@ -9727,6 +9824,10 @@ msgstr "Przełącz na poprzedni czat" msgid "Switch to the previous tab" msgstr "Przełącz na poprzednią kartę" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Switch workspace" +msgstr "Przełącz obszar roboczy" + #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/parts/CommitSyncPanel.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/useWorktreeActions.ts @@ -11350,6 +11451,7 @@ msgid "Winner merged." msgstr "Scalono zmiany zwycięzcy." #: src/renderer/components/providers/composerControlBuilders.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Work" msgstr "Praca" @@ -11414,6 +11516,7 @@ msgstr "Pracuję…" #: src/renderer/components/mcp/McpServersManager.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Workspace" msgstr "Obszar roboczy" @@ -11425,6 +11528,12 @@ msgstr "Serwery MCP obszaru roboczego zastępują serwery użytkownika o tej sam msgid "Workspace view" msgstr "Widok obszaru roboczego" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Workspaces" +msgstr "Obszary robocze" + #: src/mobile/views/ThreadsView.tsx #: src/renderer/components/thread/WorktreeModeSelect.tsx msgid "Worktree" diff --git a/src/renderer/locales/pt-BR/messages.po b/src/renderer/locales/pt-BR/messages.po index 682f7dba0..1e1c0657f 100644 --- a/src/renderer/locales/pt-BR/messages.po +++ b/src/renderer/locales/pt-BR/messages.po @@ -66,8 +66,10 @@ msgstr "{0, plural, one {# conexão pareada} other {# conexões pareadas}}" msgid "{0, plural, one {# paired desktop} other {# paired desktops}}" msgstr "{0, plural, one {# desktop pareado} other {# desktops pareados}}" +#. placeholder {0}: projectCounts.get(workspace.id) ?? 0 #. placeholder {0}: props.projects.length #: src/mobile/views/ManageProjectsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "{0, plural, one {# project} other {# projects}}" msgstr "{0, plural, one {# projeto} other {# projetos}}" @@ -221,6 +223,11 @@ msgstr "{0} precisa de entrada." msgid "{0} of {matchCount}" msgstr "{0} de {matchCount}" +#. placeholder {0}: projectCount(workspace.id) +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "{0} projects" +#~ msgstr "{0} projetos" + #. placeholder {0}: cell.count #: src/renderer/views/ProfileOverlay/parts/ActivityHeatmap.tsx msgid "{0} prompt - {when}" @@ -366,6 +373,10 @@ msgstr "{head} ou {tail}" msgid "{heading} mode" msgstr "Modo {heading}" +#: src/renderer/views/SchedulesView/SchedulesView.tsx +msgid "{hiddenByWorkspaceCount, plural, one {# schedule belongs to a project in another workspace.} other {# schedules belong to projects in another workspace.}}" +msgstr "{hiddenByWorkspaceCount, plural, one {# agendamento pertence a um projeto de outro espaço de trabalho.} other {# agendamentos pertencem a projetos de outros espaços de trabalho.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{hook} hook failed" msgstr "hook {hook} falhou" @@ -445,6 +456,10 @@ msgstr "há {minutes}min" msgid "{missingAuthLabel} needs authentication." msgstr "{missingAuthLabel} precisa de autenticação." +#: src/renderer/actions/workspaceActions.ts +msgid "{movedCount, plural, one {Moved # project to {target}.} other {Moved # projects to {target}.}}" +msgstr "{movedCount, plural, one {# projeto movido para {target}.} other {# projetos movidos para {target}.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{original}\n" "Worktree cleanup also failed: {cleanup}" @@ -545,6 +560,10 @@ msgstr "~{money}{tokens} · {0} · est." msgid "≈{0}% by reset" msgstr "≈{0}% até a redefinição" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "1 project" +#~ msgstr "1 projeto" + #: src/mobile/views/ThreadsView.tsx msgid "1 thread" msgstr "1 thread" @@ -654,6 +673,7 @@ msgstr "Ações para {title}" #: src/renderer/components/thread/ThreadGoalDock.tsx #: src/renderer/views/SchedulesView/ScheduleEditor.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Active" msgstr "Ativo" @@ -826,6 +846,11 @@ msgstr "Adicionar à tela inicial" msgid "Add to-do" msgstr "Adicionar tarefa" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Add workspace" +msgstr "Adicionar espaço de trabalho" + #: src/renderer/views/SettingsOverlay/parts/RemoteAccessSettings.tsx msgid "Advertise a custom base URL, e.g. a reverse proxy or tunnel. Leave empty for automatic." msgstr "Anuncie uma URL base personalizada, por exemplo, um proxy reverso ou túnel. Deixe em branco para automático." @@ -982,6 +1007,11 @@ msgstr "Todos os projetos" msgid "All sources" msgstr "Todas as fontes" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "All workspaces" +msgstr "Todos os espaços de trabalho" + #: src/mobile/push/WebPushPermissionPrompt.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/helpers.ts #: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx @@ -1643,6 +1673,7 @@ msgstr "Não foi possível carregar as portas" #: src/renderer/components/thread/ThreadGoalControls.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/structuredElicitation.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/ThreadRuntimeRequestPanel.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeRunDialog.tsx #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/GitReviewSidebar.tsx @@ -2889,6 +2920,7 @@ msgid "Couldn't update the skill." msgstr "Não foi possível atualizar a skill." #: src/renderer/components/thread/ChatPane/parts/items/FileChange.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Create" msgstr "Criar" @@ -3167,6 +3199,7 @@ msgstr "Delegue o trabalho a outros agentes de IA." #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" msgstr "Excluir" @@ -3255,6 +3288,14 @@ msgstr "Excluir tópico?" msgid "Delete to-do" msgstr "Excluir tarefa" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace" +msgstr "Excluir espaço de trabalho" + +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace?" +msgstr "Excluir o espaço de trabalho?" + #: src/mobile/ThreadTitleRow.tsx #: src/mobile/views/threadActionSurfaces.tsx #: src/mobile/views/threadContextMenus.tsx @@ -4649,6 +4690,10 @@ msgstr "Nome do grupo" msgid "Group open threads" msgstr "Agrupar tópicos abertos" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Group projects so the sidebar, schedules, and pull requests only show one set at a time. Switch between them at the bottom of the sidebar." +msgstr "Agrupe projetos para que a barra lateral, os agendamentos e os pull requests mostrem apenas um conjunto por vez. Alterne entre eles na parte inferior da barra lateral." + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts msgid "GUI chat font size" @@ -5077,6 +5122,14 @@ msgstr "Ir para a seção" msgid "just now" msgstr "agora mesmo" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Keep at least one workspace" +msgstr "Mantenha pelo menos um espaço de trabalho" + +#: src/renderer/actions/workspaceActions.ts +msgid "Keep at least one workspace." +msgstr "Mantenha pelo menos um espaço de trabalho." + #: src/renderer/views/MainView/parts/Sidebar/parts/ForceDeleteBranchDialog.tsx msgid "Keep Branch" msgstr "Manter branch" @@ -5452,6 +5505,10 @@ msgstr "Gerencie as configurações de servidores MCP que o Poracode adiciona ao #~ msgid "Manage the skills Poracode makes available to supported agents. Project skills can be managed in each project's settings." #~ msgstr "Gerencie as skills que o Poracode disponibiliza para agentes compatíveis. As skills de projeto podem ser gerenciadas nas configurações de cada projeto." +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Manage workspaces" +msgstr "Gerenciar espaços de trabalho" + #: src/renderer/components/skills/SkillsManager.tsx #~ msgid "Managed" #~ msgstr "Gerenciada" @@ -5947,10 +6004,24 @@ msgstr "Mover navegador para uma janela" msgid "Move changes to a new worktree" msgstr "Mover alterações para uma nova árvore de trabalho" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +msgid "Move to Workspace" +msgstr "Mover para espaço de trabalho" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Mover dock de tarefas para o painel direito" +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved {movedCount} projects to {0}." +#~ msgstr "{movedCount} projetos movidos para {0}." + +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved 1 project to {0}." +#~ msgstr "1 projeto movido para {0}." + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Moved your changes into a new worktree on \"{newBranch}\". \"{currentBranch}\" is now clean." msgstr "Movemos suas alterações para uma nova árvore de trabalho em \"{newBranch}\". \"{currentBranch}\" agora está limpo." @@ -5965,6 +6036,7 @@ msgstr "my-mcp-server" #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/skills/SkillMarketplaceModal.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx msgid "Name" @@ -6162,6 +6234,10 @@ msgstr "Nova thread no worktree" msgid "New Thread in Worktree" msgstr "Novo tópico na árvore de trabalho" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +msgid "New workspace" +msgstr "Novo espaço de trabalho" + #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx msgid "New worktree" msgstr "Nova árvore de trabalho" @@ -6454,6 +6530,10 @@ msgstr "Ainda não há fases." msgid "No projects" msgstr "Nenhum projeto" +#: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx +msgid "No projects in this workspace" +msgstr "Nenhum projeto neste espaço de trabalho" + #: src/renderer/views/SettingsOverlay/parts/RemoteServersSettings.tsx msgid "No projects on this server." msgstr "Nenhum projeto neste servidor." @@ -6462,6 +6542,10 @@ msgstr "Nenhum projeto neste servidor." msgid "No projects yet" msgstr "Ainda não há projetos" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "No projects yet." +msgstr "Ainda não há projetos." + #: src/renderer/components/providers/codex/index.tsx msgid "No prompts" msgstr "Sem avisos" @@ -7540,9 +7624,16 @@ msgstr "Substituições específicas do projeto nas configurações de pesquisa #: src/mobile/WideShell.tsx #: src/renderer/components/mcp/McpProjectDestinationDropdown.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Projects" msgstr "Projetos" +#. placeholder {0}: deleting.name +#. placeholder {1}: deleteFallback.name +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Projects in {0} move to {1}. No project is deleted." +msgstr "Os projetos em {0} vão para {1}. Nenhum projeto é excluído." + #: src/mobile/setupEmptyState.tsx msgid "Projects live on the connected desktop. Add one before starting a thread." msgstr "Os projetos ficam no desktop conectado. Adicione um antes de iniciar uma conversa." @@ -8095,6 +8186,11 @@ msgstr "Renomear tópico" msgid "Rename to" msgstr "Renomear para" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Rename workspace" +msgstr "Renomear espaço de trabalho" + #: src/renderer/RendererCrashScreen.tsx msgid "Renderer crashed" msgstr "O renderizador travou" @@ -8539,6 +8635,7 @@ msgstr "Opções de tempo de execução" #: src/mobile/views/FilesView.tsx #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/thread/ThreadGoalControls.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/FileEditorOverlay/parts/FileEditorPane/parts/EditorToolbar.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx @@ -9727,6 +9824,10 @@ msgstr "Mudar para o chat anterior" msgid "Switch to the previous tab" msgstr "Mudar para a guia anterior" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Switch workspace" +msgstr "Trocar de espaço de trabalho" + #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/parts/CommitSyncPanel.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/useWorktreeActions.ts @@ -11350,6 +11451,7 @@ msgid "Winner merged." msgstr "Vencedor mesclado." #: src/renderer/components/providers/composerControlBuilders.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Work" msgstr "Trabalho" @@ -11414,6 +11516,7 @@ msgstr "Trabalhando…" #: src/renderer/components/mcp/McpServersManager.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Workspace" msgstr "Espaço de trabalho" @@ -11425,6 +11528,12 @@ msgstr "Os servidores MCP do espaço de trabalho substituem os servidores do usu msgid "Workspace view" msgstr "Visualização do espaço de trabalho" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Workspaces" +msgstr "Espaços de trabalho" + #: src/mobile/views/ThreadsView.tsx #: src/renderer/components/thread/WorktreeModeSelect.tsx msgid "Worktree" diff --git a/src/renderer/locales/ru/messages.po b/src/renderer/locales/ru/messages.po index e0693fec0..88d21532d 100644 --- a/src/renderer/locales/ru/messages.po +++ b/src/renderer/locales/ru/messages.po @@ -66,8 +66,10 @@ msgstr "{0, plural, one {# подключение} few {# подключения msgid "{0, plural, one {# paired desktop} other {# paired desktops}}" msgstr "{0, plural, one {# связанный десктоп} few {# связанных десктопа} many {# связанных десктопов} other {# связанного десктопа}}" +#. placeholder {0}: projectCounts.get(workspace.id) ?? 0 #. placeholder {0}: props.projects.length #: src/mobile/views/ManageProjectsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "{0, plural, one {# project} other {# projects}}" msgstr "{0, plural, one {# проект} few {# проекта} many {# проектов} other {# проекта}}" @@ -221,6 +223,11 @@ msgstr "{0} требуется ввод." msgid "{0} of {matchCount}" msgstr "{0} из {matchCount}" +#. placeholder {0}: projectCount(workspace.id) +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "{0} projects" +#~ msgstr "Проектов: {0}" + #. placeholder {0}: cell.count #: src/renderer/views/ProfileOverlay/parts/ActivityHeatmap.tsx msgid "{0} prompt - {when}" @@ -366,6 +373,10 @@ msgstr "{head} или {tail}" msgid "{heading} mode" msgstr "Режим {heading}" +#: src/renderer/views/SchedulesView/SchedulesView.tsx +msgid "{hiddenByWorkspaceCount, plural, one {# schedule belongs to a project in another workspace.} other {# schedules belong to projects in another workspace.}}" +msgstr "{hiddenByWorkspaceCount, plural, one {# расписание относится к проекту из другой рабочей области.} few {# расписания относятся к проектам из других рабочих областей.} many {# расписаний относятся к проектам из других рабочих областей.} other {# расписания относятся к проектам из других рабочих областей.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{hook} hook failed" msgstr "Сбой хука {hook}" @@ -445,6 +456,10 @@ msgstr "{minutes}м назад" msgid "{missingAuthLabel} needs authentication." msgstr "{missingAuthLabel} требует аутентификации." +#: src/renderer/actions/workspaceActions.ts +msgid "{movedCount, plural, one {Moved # project to {target}.} other {Moved # projects to {target}.}}" +msgstr "{movedCount, plural, one {# проект перемещён в {target}.} few {# проекта перемещены в {target}.} many {# проектов перемещены в {target}.} other {# проекта перемещены в {target}.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{original}\n" "Worktree cleanup also failed: {cleanup}" @@ -545,6 +560,10 @@ msgstr "~{money}{tokens} · {0} · прибл." msgid "≈{0}% by reset" msgstr "≈{0}% к сбросу" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "1 project" +#~ msgstr "1 проект" + #: src/mobile/views/ThreadsView.tsx msgid "1 thread" msgstr "1 поток" @@ -654,6 +673,7 @@ msgstr "Действия для {title}" #: src/renderer/components/thread/ThreadGoalDock.tsx #: src/renderer/views/SchedulesView/ScheduleEditor.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Active" msgstr "Активно" @@ -826,6 +846,11 @@ msgstr "Добавить на главный экран" msgid "Add to-do" msgstr "Добавить задачу" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Add workspace" +msgstr "Добавить рабочую область" + #: src/renderer/views/SettingsOverlay/parts/RemoteAccessSettings.tsx msgid "Advertise a custom base URL, e.g. a reverse proxy or tunnel. Leave empty for automatic." msgstr "Укажите собственный базовый URL, например обратный прокси или туннель. Оставьте пустым для автоматического режима." @@ -982,6 +1007,11 @@ msgstr "Все проекты" msgid "All sources" msgstr "Все источники" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "All workspaces" +msgstr "Все рабочие области" + #: src/mobile/push/WebPushPermissionPrompt.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/helpers.ts #: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx @@ -1643,6 +1673,7 @@ msgstr "Не удалось загрузить порты" #: src/renderer/components/thread/ThreadGoalControls.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/structuredElicitation.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/ThreadRuntimeRequestPanel.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeRunDialog.tsx #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/GitReviewSidebar.tsx @@ -2889,6 +2920,7 @@ msgid "Couldn't update the skill." msgstr "Не удалось обновить навык." #: src/renderer/components/thread/ChatPane/parts/items/FileChange.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Create" msgstr "Создать" @@ -3167,6 +3199,7 @@ msgstr "Делегируйте работу другим ИИ-агентам." #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" msgstr "Удалить" @@ -3255,6 +3288,14 @@ msgstr "Удалить тред?" msgid "Delete to-do" msgstr "Удалить задачу" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace" +msgstr "Удалить рабочую область" + +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace?" +msgstr "Удалить рабочую область?" + #: src/mobile/ThreadTitleRow.tsx #: src/mobile/views/threadActionSurfaces.tsx #: src/mobile/views/threadContextMenus.tsx @@ -4649,6 +4690,10 @@ msgstr "Название группы" msgid "Group open threads" msgstr "Группировать открытые треды" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Group projects so the sidebar, schedules, and pull requests only show one set at a time. Switch between them at the bottom of the sidebar." +msgstr "Группируйте проекты, чтобы боковая панель, расписания и pull request показывали только один набор за раз. Переключайтесь между ними внизу боковой панели." + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts msgid "GUI chat font size" @@ -5077,6 +5122,14 @@ msgstr "Перейти к разделу" msgid "just now" msgstr "только что" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Keep at least one workspace" +msgstr "Оставьте хотя бы одну рабочую область" + +#: src/renderer/actions/workspaceActions.ts +msgid "Keep at least one workspace." +msgstr "Оставьте хотя бы одну рабочую область." + #: src/renderer/views/MainView/parts/Sidebar/parts/ForceDeleteBranchDialog.tsx msgid "Keep Branch" msgstr "Сохранить ветку" @@ -5452,6 +5505,10 @@ msgstr "Управляйте конфигурациями серверов MCP, #~ msgid "Manage the skills Poracode makes available to supported agents. Project skills can be managed in each project's settings." #~ msgstr "Управляйте навыками, которые Poracode предоставляет поддерживаемым агентам. Навыками проекта можно управлять в настройках каждого проекта." +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Manage workspaces" +msgstr "Управление рабочими областями" + #: src/renderer/components/skills/SkillsManager.tsx #~ msgid "Managed" #~ msgstr "Управляется" @@ -5947,10 +6004,24 @@ msgstr "Переместить браузер в окно" msgid "Move changes to a new worktree" msgstr "Переместить изменения в новый worktree" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +msgid "Move to Workspace" +msgstr "Переместить в рабочую область" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Переместить панель задач в правую панель" +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved {movedCount} projects to {0}." +#~ msgstr "Перемещено проектов: {movedCount} — в {0}." + +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved 1 project to {0}." +#~ msgstr "1 проект перемещён в {0}." + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Moved your changes into a new worktree on \"{newBranch}\". \"{currentBranch}\" is now clean." msgstr "Ваши изменения перемещены в новый worktree на \"{newBranch}\". \"{currentBranch}\" теперь чист." @@ -5965,6 +6036,7 @@ msgstr "my-mcp-server" #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/skills/SkillMarketplaceModal.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx msgid "Name" @@ -6162,6 +6234,10 @@ msgstr "Новый поток в worktree" msgid "New Thread in Worktree" msgstr "Новый тред в worktree" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +msgid "New workspace" +msgstr "Новая рабочая область" + #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx msgid "New worktree" msgstr "Новый worktree" @@ -6454,6 +6530,10 @@ msgstr "Фаз пока нет." msgid "No projects" msgstr "Нет проектов" +#: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx +msgid "No projects in this workspace" +msgstr "В этой рабочей области нет проектов" + #: src/renderer/views/SettingsOverlay/parts/RemoteServersSettings.tsx msgid "No projects on this server." msgstr "На этом сервере нет проектов." @@ -6462,6 +6542,10 @@ msgstr "На этом сервере нет проектов." msgid "No projects yet" msgstr "Пока нет проектов" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "No projects yet." +msgstr "Проектов пока нет." + #: src/renderer/components/providers/codex/index.tsx msgid "No prompts" msgstr "Нет промптов" @@ -7540,9 +7624,16 @@ msgstr "Переопределения для конкретного проек #: src/mobile/WideShell.tsx #: src/renderer/components/mcp/McpProjectDestinationDropdown.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Projects" msgstr "Проекты" +#. placeholder {0}: deleting.name +#. placeholder {1}: deleteFallback.name +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Projects in {0} move to {1}. No project is deleted." +msgstr "Проекты из {0} перейдут в {1}. Ни один проект не будет удалён." + #: src/mobile/setupEmptyState.tsx msgid "Projects live on the connected desktop. Add one before starting a thread." msgstr "Проекты находятся на подключенном компьютере. Добавьте проект, прежде чем начать тред." @@ -8095,6 +8186,11 @@ msgstr "Переименовать тред" msgid "Rename to" msgstr "Переименовать в" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Rename workspace" +msgstr "Переименовать рабочую область" + #: src/renderer/RendererCrashScreen.tsx msgid "Renderer crashed" msgstr "Рендерер аварийно завершился" @@ -8539,6 +8635,7 @@ msgstr "Параметры среды выполнения" #: src/mobile/views/FilesView.tsx #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/thread/ThreadGoalControls.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/FileEditorOverlay/parts/FileEditorPane/parts/EditorToolbar.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx @@ -9727,6 +9824,10 @@ msgstr "Перейти к предыдущему чату" msgid "Switch to the previous tab" msgstr "Перейти к предыдущей вкладке" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Switch workspace" +msgstr "Сменить рабочую область" + #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/parts/CommitSyncPanel.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/useWorktreeActions.ts @@ -11350,6 +11451,7 @@ msgid "Winner merged." msgstr "Изменения победителя слиты." #: src/renderer/components/providers/composerControlBuilders.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Work" msgstr "Работа" @@ -11414,6 +11516,7 @@ msgstr "Выполняется…" #: src/renderer/components/mcp/McpServersManager.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Workspace" msgstr "Рабочая область" @@ -11425,6 +11528,12 @@ msgstr "Серверы MCP рабочей области переопредел msgid "Workspace view" msgstr "Представление рабочей области" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Workspaces" +msgstr "Рабочие области" + #: src/mobile/views/ThreadsView.tsx #: src/renderer/components/thread/WorktreeModeSelect.tsx msgid "Worktree" diff --git a/src/renderer/locales/tr/messages.po b/src/renderer/locales/tr/messages.po index d23b14284..0af5ae5d2 100644 --- a/src/renderer/locales/tr/messages.po +++ b/src/renderer/locales/tr/messages.po @@ -66,8 +66,10 @@ msgstr "{0, plural, one {# eşleştirilmiş bağlantı} other {# eşleştirilmi msgid "{0, plural, one {# paired desktop} other {# paired desktops}}" msgstr "{0, plural, one {# eşleştirilmiş masaüstü} other {# eşleştirilmiş masaüstü}}" +#. placeholder {0}: projectCounts.get(workspace.id) ?? 0 #. placeholder {0}: props.projects.length #: src/mobile/views/ManageProjectsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "{0, plural, one {# project} other {# projects}}" msgstr "{0, plural, one {# proje} other {# proje}}" @@ -221,6 +223,11 @@ msgstr "{0} giriş gerektiriyor." msgid "{0} of {matchCount}" msgstr "{0} / {matchCount}" +#. placeholder {0}: projectCount(workspace.id) +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "{0} projects" +#~ msgstr "{0} proje" + #. placeholder {0}: cell.count #: src/renderer/views/ProfileOverlay/parts/ActivityHeatmap.tsx msgid "{0} prompt - {when}" @@ -366,6 +373,10 @@ msgstr "{head} veya {tail}" msgid "{heading} mode" msgstr "{heading} modu" +#: src/renderer/views/SchedulesView/SchedulesView.tsx +msgid "{hiddenByWorkspaceCount, plural, one {# schedule belongs to a project in another workspace.} other {# schedules belong to projects in another workspace.}}" +msgstr "{hiddenByWorkspaceCount, plural, one {# zamanlama başka bir çalışma alanındaki projeye ait.} other {# zamanlama başka çalışma alanlarındaki projelere ait.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{hook} hook failed" msgstr "{hook} kancası başarısız oldu" @@ -445,6 +456,10 @@ msgstr "{minutes} dk önce" msgid "{missingAuthLabel} needs authentication." msgstr "{missingAuthLabel} kimlik doğrulaması gerektiriyor." +#: src/renderer/actions/workspaceActions.ts +msgid "{movedCount, plural, one {Moved # project to {target}.} other {Moved # projects to {target}.}}" +msgstr "{movedCount, plural, one {# proje {target} alanına taşındı.} other {# proje {target} alanına taşındı.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{original}\n" "Worktree cleanup also failed: {cleanup}" @@ -545,6 +560,10 @@ msgstr "~{money}{tokens} · {0} · tahmini" msgid "≈{0}% by reset" msgstr "sıfırlamaya kadar ≈%{0}" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "1 project" +#~ msgstr "1 proje" + #: src/mobile/views/ThreadsView.tsx msgid "1 thread" msgstr "1 iş parçacığı" @@ -654,6 +673,7 @@ msgstr "{title} için işlemler" #: src/renderer/components/thread/ThreadGoalDock.tsx #: src/renderer/views/SchedulesView/ScheduleEditor.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Active" msgstr "Aktif" @@ -826,6 +846,11 @@ msgstr "Ana Ekrana ekle" msgid "Add to-do" msgstr "Yapılacak ekle" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Add workspace" +msgstr "Çalışma alanı ekle" + #: src/renderer/views/SettingsOverlay/parts/RemoteAccessSettings.tsx msgid "Advertise a custom base URL, e.g. a reverse proxy or tunnel. Leave empty for automatic." msgstr "Özel bir temel URL duyurun, örn. bir ters proxy veya tünel. Otomatik için boş bırakın." @@ -982,6 +1007,11 @@ msgstr "Tüm projeler" msgid "All sources" msgstr "Tüm kaynaklar" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "All workspaces" +msgstr "Tüm çalışma alanları" + #: src/mobile/push/WebPushPermissionPrompt.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/helpers.ts #: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx @@ -1643,6 +1673,7 @@ msgstr "Portlar yüklenemedi" #: src/renderer/components/thread/ThreadGoalControls.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/structuredElicitation.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/ThreadRuntimeRequestPanel.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeRunDialog.tsx #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/GitReviewSidebar.tsx @@ -2889,6 +2920,7 @@ msgid "Couldn't update the skill." msgstr "Beceri güncellenemedi." #: src/renderer/components/thread/ChatPane/parts/items/FileChange.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Create" msgstr "Oluştur" @@ -3167,6 +3199,7 @@ msgstr "İşi diğer yapay zeka aracılarına devredin." #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" msgstr "Sil" @@ -3255,6 +3288,14 @@ msgstr "Konu silinsin mi?" msgid "Delete to-do" msgstr "Yapılacak işi sil" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace" +msgstr "Çalışma alanını sil" + +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace?" +msgstr "Çalışma alanı silinsin mi?" + #: src/mobile/ThreadTitleRow.tsx #: src/mobile/views/threadActionSurfaces.tsx #: src/mobile/views/threadContextMenus.tsx @@ -4649,6 +4690,10 @@ msgstr "Grup adı" msgid "Group open threads" msgstr "Açık konuları gruplandır" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Group projects so the sidebar, schedules, and pull requests only show one set at a time. Switch between them at the bottom of the sidebar." +msgstr "Projeleri gruplayın; böylece kenar çubuğu, zamanlamalar ve çekme istekleri her seferinde yalnızca bir grubu gösterir. Kenar çubuğunun altından geçiş yapabilirsiniz." + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts msgid "GUI chat font size" @@ -5077,6 +5122,14 @@ msgstr "Bölüme git" msgid "just now" msgstr "şimdi" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Keep at least one workspace" +msgstr "En az bir çalışma alanı bırakın" + +#: src/renderer/actions/workspaceActions.ts +msgid "Keep at least one workspace." +msgstr "En az bir çalışma alanı bırakın." + #: src/renderer/views/MainView/parts/Sidebar/parts/ForceDeleteBranchDialog.tsx msgid "Keep Branch" msgstr "Şubeyi Koru" @@ -5452,6 +5505,10 @@ msgstr "Poracode'un desteklenen aracıları başlatırken eklediği MCP sunucusu #~ msgid "Manage the skills Poracode makes available to supported agents. Project skills can be managed in each project's settings." #~ msgstr "Poracode'un desteklenen aracılara sunduğu becerileri yönetin. Proje becerileri her projenin ayarlarından yönetilebilir." +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Manage workspaces" +msgstr "Çalışma alanlarını yönet" + #: src/renderer/components/skills/SkillsManager.tsx #~ msgid "Managed" #~ msgstr "Yönetiliyor" @@ -5947,10 +6004,24 @@ msgstr "Tarayıcıyı pencereye taşı" msgid "Move changes to a new worktree" msgstr "Değişiklikleri yeni bir çalışma ağacına taşı" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +msgid "Move to Workspace" +msgstr "Çalışma alanına taşı" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Yapılacaklar panelini sağ panele taşı" +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved {movedCount} projects to {0}." +#~ msgstr "{movedCount} proje {0} alanına taşındı." + +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved 1 project to {0}." +#~ msgstr "1 proje {0} alanına taşındı." + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Moved your changes into a new worktree on \"{newBranch}\". \"{currentBranch}\" is now clean." msgstr "Değişiklikleriniz \"{newBranch}\" üzerindeki yeni bir çalışma ağacına taşındı. \"{currentBranch}\" artık temiz." @@ -5965,6 +6036,7 @@ msgstr "my-mcp-server" #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/skills/SkillMarketplaceModal.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx msgid "Name" @@ -6162,6 +6234,10 @@ msgstr "Worktree'de yeni iş parçacığı" msgid "New Thread in Worktree" msgstr "Worktree'de Yeni Konu" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +msgid "New workspace" +msgstr "Yeni çalışma alanı" + #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx msgid "New worktree" msgstr "Yeni çalışma ağacı" @@ -6454,6 +6530,10 @@ msgstr "Henüz aşama yok." msgid "No projects" msgstr "Proje yok" +#: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx +msgid "No projects in this workspace" +msgstr "Bu çalışma alanında proje yok" + #: src/renderer/views/SettingsOverlay/parts/RemoteServersSettings.tsx msgid "No projects on this server." msgstr "Bu sunucuda proje yok." @@ -6462,6 +6542,10 @@ msgstr "Bu sunucuda proje yok." msgid "No projects yet" msgstr "Henüz proje yok" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "No projects yet." +msgstr "Henüz proje yok." + #: src/renderer/components/providers/codex/index.tsx msgid "No prompts" msgstr "İstem yok" @@ -7540,9 +7624,16 @@ msgstr "Genel arama ayarlarının yanı sıra projeye özel geçersiz kılmalar. #: src/mobile/WideShell.tsx #: src/renderer/components/mcp/McpProjectDestinationDropdown.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Projects" msgstr "Projeler" +#. placeholder {0}: deleting.name +#. placeholder {1}: deleteFallback.name +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Projects in {0} move to {1}. No project is deleted." +msgstr "{0} içindeki projeler {1} alanına taşınır. Hiçbir proje silinmez." + #: src/mobile/setupEmptyState.tsx msgid "Projects live on the connected desktop. Add one before starting a thread." msgstr "Projeler bağlı masaüstünde bulunur. Bir iş parçacığı başlatmadan önce bir proje ekleyin." @@ -8095,6 +8186,11 @@ msgstr "Konuyu yeniden adlandır" msgid "Rename to" msgstr "Şu adla yeniden adlandır" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Rename workspace" +msgstr "Çalışma alanını yeniden adlandır" + #: src/renderer/RendererCrashScreen.tsx msgid "Renderer crashed" msgstr "Oluşturucu çöktü" @@ -8539,6 +8635,7 @@ msgstr "Çalışma zamanı seçenekleri" #: src/mobile/views/FilesView.tsx #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/thread/ThreadGoalControls.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/FileEditorOverlay/parts/FileEditorPane/parts/EditorToolbar.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx @@ -9727,6 +9824,10 @@ msgstr "Önceki sohbete geç" msgid "Switch to the previous tab" msgstr "Önceki sekmeye geç" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Switch workspace" +msgstr "Çalışma alanını değiştir" + #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/parts/CommitSyncPanel.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/useWorktreeActions.ts @@ -11350,6 +11451,7 @@ msgid "Winner merged." msgstr "Kazanan birleştirildi." #: src/renderer/components/providers/composerControlBuilders.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Work" msgstr "İş" @@ -11414,6 +11516,7 @@ msgstr "Çalışıyor…" #: src/renderer/components/mcp/McpServersManager.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Workspace" msgstr "Çalışma alanı" @@ -11425,6 +11528,12 @@ msgstr "Çalışma alanı MCP sunucuları aynı adlı kullanıcı sunucularını msgid "Workspace view" msgstr "Çalışma alanı görünümü" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Workspaces" +msgstr "Çalışma alanları" + #: src/mobile/views/ThreadsView.tsx #: src/renderer/components/thread/WorktreeModeSelect.tsx msgid "Worktree" diff --git a/src/renderer/locales/uk/messages.po b/src/renderer/locales/uk/messages.po index f4bced4d4..36e2298df 100644 --- a/src/renderer/locales/uk/messages.po +++ b/src/renderer/locales/uk/messages.po @@ -66,8 +66,10 @@ msgstr "{0, plural, one {# підключення} few {# підключення msgid "{0, plural, one {# paired desktop} other {# paired desktops}}" msgstr "{0, plural, one {# підключений десктоп} few {# підключені десктопи} many {# підключених десктопів} other {# підключеного десктопа}}" +#. placeholder {0}: projectCounts.get(workspace.id) ?? 0 #. placeholder {0}: props.projects.length #: src/mobile/views/ManageProjectsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "{0, plural, one {# project} other {# projects}}" msgstr "{0, plural, one {# проєкт} few {# проєкти} many {# проєктів} other {# проєкту}}" @@ -221,6 +223,11 @@ msgstr "{0} потребує введення." msgid "{0} of {matchCount}" msgstr "{0} з {matchCount}" +#. placeholder {0}: projectCount(workspace.id) +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "{0} projects" +#~ msgstr "Проєктів: {0}" + #. placeholder {0}: cell.count #: src/renderer/views/ProfileOverlay/parts/ActivityHeatmap.tsx msgid "{0} prompt - {when}" @@ -366,6 +373,10 @@ msgstr "{head} або {tail}" msgid "{heading} mode" msgstr "Режим {heading}" +#: src/renderer/views/SchedulesView/SchedulesView.tsx +msgid "{hiddenByWorkspaceCount, plural, one {# schedule belongs to a project in another workspace.} other {# schedules belong to projects in another workspace.}}" +msgstr "{hiddenByWorkspaceCount, plural, one {# розклад належить проєкту з іншої робочої області.} few {# розклади належать проєктам з інших робочих областей.} many {# розкладів належать проєктам з інших робочих областей.} other {# розкладу належать проєктам з інших робочих областей.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{hook} hook failed" msgstr "Збій хука {hook}" @@ -445,6 +456,10 @@ msgstr "{minutes}хв тому" msgid "{missingAuthLabel} needs authentication." msgstr "{missingAuthLabel} потребує автентифікації." +#: src/renderer/actions/workspaceActions.ts +msgid "{movedCount, plural, one {Moved # project to {target}.} other {Moved # projects to {target}.}}" +msgstr "{movedCount, plural, one {# проєкт переміщено до {target}.} few {# проєкти переміщено до {target}.} many {# проєктів переміщено до {target}.} other {# проєкту переміщено до {target}.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{original}\n" "Worktree cleanup also failed: {cleanup}" @@ -545,6 +560,10 @@ msgstr "~{money}{tokens} · {0} · прибл." msgid "≈{0}% by reset" msgstr "≈{0}% до скидання" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "1 project" +#~ msgstr "1 проєкт" + #: src/mobile/views/ThreadsView.tsx msgid "1 thread" msgstr "1 потік" @@ -654,6 +673,7 @@ msgstr "Дії для {title}" #: src/renderer/components/thread/ThreadGoalDock.tsx #: src/renderer/views/SchedulesView/ScheduleEditor.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Active" msgstr "Активно" @@ -826,6 +846,11 @@ msgstr "Додати на головний екран" msgid "Add to-do" msgstr "Додати завдання" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Add workspace" +msgstr "Додати робочу область" + #: src/renderer/views/SettingsOverlay/parts/RemoteAccessSettings.tsx msgid "Advertise a custom base URL, e.g. a reverse proxy or tunnel. Leave empty for automatic." msgstr "Оголосіть власний базовий URL, наприклад зворотний проксі або тунель. Залиште порожнім для автоматичного режиму." @@ -982,6 +1007,11 @@ msgstr "Усі проєкти" msgid "All sources" msgstr "Усі джерела" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "All workspaces" +msgstr "Усі робочі області" + #: src/mobile/push/WebPushPermissionPrompt.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/helpers.ts #: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx @@ -1643,6 +1673,7 @@ msgstr "Не вдалося завантажити порти" #: src/renderer/components/thread/ThreadGoalControls.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/structuredElicitation.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/ThreadRuntimeRequestPanel.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeRunDialog.tsx #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/GitReviewSidebar.tsx @@ -2889,6 +2920,7 @@ msgid "Couldn't update the skill." msgstr "Не вдалося оновити навичку." #: src/renderer/components/thread/ChatPane/parts/items/FileChange.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Create" msgstr "Створити" @@ -3167,6 +3199,7 @@ msgstr "Делегуйте роботу іншим ШІ-агентам." #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" msgstr "Видалити" @@ -3255,6 +3288,14 @@ msgstr "Видалити тред?" msgid "Delete to-do" msgstr "Видалити завдання" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace" +msgstr "Видалити робочу область" + +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace?" +msgstr "Видалити робочу область?" + #: src/mobile/ThreadTitleRow.tsx #: src/mobile/views/threadActionSurfaces.tsx #: src/mobile/views/threadContextMenus.tsx @@ -4649,6 +4690,10 @@ msgstr "Назва групи" msgid "Group open threads" msgstr "Групувати відкриті треди" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Group projects so the sidebar, schedules, and pull requests only show one set at a time. Switch between them at the bottom of the sidebar." +msgstr "Групуйте проєкти, щоб бічна панель, розклади та pull request показували лише один набір водночас. Перемикайтеся між ними внизу бічної панелі." + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts msgid "GUI chat font size" @@ -5077,6 +5122,14 @@ msgstr "Перейти до розділу" msgid "just now" msgstr "щойно" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Keep at least one workspace" +msgstr "Залиште хоча б одну робочу область" + +#: src/renderer/actions/workspaceActions.ts +msgid "Keep at least one workspace." +msgstr "Залиште хоча б одну робочу область." + #: src/renderer/views/MainView/parts/Sidebar/parts/ForceDeleteBranchDialog.tsx msgid "Keep Branch" msgstr "Зберегти гілку" @@ -5452,6 +5505,10 @@ msgstr "Керуйте конфігураціями серверів MCP, які #~ msgid "Manage the skills Poracode makes available to supported agents. Project skills can be managed in each project's settings." #~ msgstr "Керуйте навичками, які Poracode надає підтримуваним агентам. Навичками проєкту можна керувати в налаштуваннях кожного проєкту." +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Manage workspaces" +msgstr "Керування робочими областями" + #: src/renderer/components/skills/SkillsManager.tsx #~ msgid "Managed" #~ msgstr "Керується" @@ -5947,10 +6004,24 @@ msgstr "Перемістити браузер у вікно" msgid "Move changes to a new worktree" msgstr "Перемістити зміни в новий worktree" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +msgid "Move to Workspace" +msgstr "Перемістити до робочої області" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Перемістити панель завдань у праву панель" +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved {movedCount} projects to {0}." +#~ msgstr "Переміщено проєктів: {movedCount} — до {0}." + +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved 1 project to {0}." +#~ msgstr "1 проєкт переміщено до {0}." + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Moved your changes into a new worktree on \"{newBranch}\". \"{currentBranch}\" is now clean." msgstr "Ваші зміни переміщено в новий worktree на \"{newBranch}\". \"{currentBranch}\" тепер чистий." @@ -5965,6 +6036,7 @@ msgstr "my-mcp-server" #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/skills/SkillMarketplaceModal.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx msgid "Name" @@ -6162,6 +6234,10 @@ msgstr "Новий потік у worktree" msgid "New Thread in Worktree" msgstr "Новий тред у worktree" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +msgid "New workspace" +msgstr "Нова робоча область" + #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx msgid "New worktree" msgstr "Новий worktree" @@ -6454,6 +6530,10 @@ msgstr "Фаз поки немає." msgid "No projects" msgstr "Немає проєктів" +#: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx +msgid "No projects in this workspace" +msgstr "У цій робочій області немає проєктів" + #: src/renderer/views/SettingsOverlay/parts/RemoteServersSettings.tsx msgid "No projects on this server." msgstr "На цьому сервері немає проєктів." @@ -6462,6 +6542,10 @@ msgstr "На цьому сервері немає проєктів." msgid "No projects yet" msgstr "Ще немає проєктів" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "No projects yet." +msgstr "Проєктів ще немає." + #: src/renderer/components/providers/codex/index.tsx msgid "No prompts" msgstr "Немає промптів" @@ -7540,9 +7624,16 @@ msgstr "Перевизначення для конкретного проєкт #: src/mobile/WideShell.tsx #: src/renderer/components/mcp/McpProjectDestinationDropdown.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Projects" msgstr "Проєкти" +#. placeholder {0}: deleting.name +#. placeholder {1}: deleteFallback.name +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Projects in {0} move to {1}. No project is deleted." +msgstr "Проєкти з {0} перейдуть до {1}. Жоден проєкт не буде видалено." + #: src/mobile/setupEmptyState.tsx msgid "Projects live on the connected desktop. Add one before starting a thread." msgstr "Проєкти розміщені на підключеному комп’ютері. Додайте проєкт, перш ніж почати тред." @@ -8095,6 +8186,11 @@ msgstr "Перейменувати тред" msgid "Rename to" msgstr "Перейменувати на" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Rename workspace" +msgstr "Перейменувати робочу область" + #: src/renderer/RendererCrashScreen.tsx msgid "Renderer crashed" msgstr "Рендерер аварійно завершився" @@ -8539,6 +8635,7 @@ msgstr "Параметри середовища виконання" #: src/mobile/views/FilesView.tsx #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/thread/ThreadGoalControls.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/FileEditorOverlay/parts/FileEditorPane/parts/EditorToolbar.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx @@ -9727,6 +9824,10 @@ msgstr "Перейти до попереднього чату" msgid "Switch to the previous tab" msgstr "Перейти до попередньої вкладки" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Switch workspace" +msgstr "Змінити робочу область" + #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/parts/CommitSyncPanel.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/useWorktreeActions.ts @@ -11350,6 +11451,7 @@ msgid "Winner merged." msgstr "Зміни переможця злито." #: src/renderer/components/providers/composerControlBuilders.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Work" msgstr "Робота" @@ -11414,6 +11516,7 @@ msgstr "Виконується…" #: src/renderer/components/mcp/McpServersManager.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Workspace" msgstr "Робоча область" @@ -11425,6 +11528,12 @@ msgstr "Сервери MCP робочої області перевизнача msgid "Workspace view" msgstr "Подання робочої області" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Workspaces" +msgstr "Робочі області" + #: src/mobile/views/ThreadsView.tsx #: src/renderer/components/thread/WorktreeModeSelect.tsx msgid "Worktree" diff --git a/src/renderer/locales/vi/messages.po b/src/renderer/locales/vi/messages.po index 4aa8930d5..74a57b347 100644 --- a/src/renderer/locales/vi/messages.po +++ b/src/renderer/locales/vi/messages.po @@ -66,8 +66,10 @@ msgstr "{0, plural, one {# kết nối đã ghép đôi} other {# kết nối đ msgid "{0, plural, one {# paired desktop} other {# paired desktops}}" msgstr "{0, plural, one {# desktop đã ghép đôi} other {# desktop đã ghép đôi}}" +#. placeholder {0}: projectCounts.get(workspace.id) ?? 0 #. placeholder {0}: props.projects.length #: src/mobile/views/ManageProjectsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "{0, plural, one {# project} other {# projects}}" msgstr "{0, plural, one {# dự án} other {# dự án}}" @@ -221,6 +223,11 @@ msgstr "{0} cần thông tin nhập." msgid "{0} of {matchCount}" msgstr "{0} / {matchCount}" +#. placeholder {0}: projectCount(workspace.id) +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "{0} projects" +#~ msgstr "{0} dự án" + #. placeholder {0}: cell.count #: src/renderer/views/ProfileOverlay/parts/ActivityHeatmap.tsx msgid "{0} prompt - {when}" @@ -366,6 +373,10 @@ msgstr "{head}, hoặc {tail}" msgid "{heading} mode" msgstr "Chế độ {heading}" +#: src/renderer/views/SchedulesView/SchedulesView.tsx +msgid "{hiddenByWorkspaceCount, plural, one {# schedule belongs to a project in another workspace.} other {# schedules belong to projects in another workspace.}}" +msgstr "{hiddenByWorkspaceCount, plural, other {# lịch biểu thuộc về dự án ở không gian làm việc khác.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{hook} hook failed" msgstr "Hook {hook} không thành công" @@ -445,6 +456,10 @@ msgstr "{minutes} phút trước" msgid "{missingAuthLabel} needs authentication." msgstr "{missingAuthLabel} cần xác thực." +#: src/renderer/actions/workspaceActions.ts +msgid "{movedCount, plural, one {Moved # project to {target}.} other {Moved # projects to {target}.}}" +msgstr "{movedCount, plural, other {Đã chuyển # dự án sang {target}.}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{original}\n" "Worktree cleanup also failed: {cleanup}" @@ -545,6 +560,10 @@ msgstr "~{money}{tokens} · {0} · ước tính." msgid "≈{0}% by reset" msgstr "≈{0}% khi đặt lại" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "1 project" +#~ msgstr "1 dự án" + #: src/mobile/views/ThreadsView.tsx msgid "1 thread" msgstr "1 luồng" @@ -654,6 +673,7 @@ msgstr "Hành động cho {title}" #: src/renderer/components/thread/ThreadGoalDock.tsx #: src/renderer/views/SchedulesView/ScheduleEditor.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Active" msgstr "Đang hoạt động" @@ -826,6 +846,11 @@ msgstr "Thêm vào màn hình chính" msgid "Add to-do" msgstr "Thêm việc cần làm" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Add workspace" +msgstr "Thêm không gian làm việc" + #: src/renderer/views/SettingsOverlay/parts/RemoteAccessSettings.tsx msgid "Advertise a custom base URL, e.g. a reverse proxy or tunnel. Leave empty for automatic." msgstr "Công bố một URL cơ sở tùy chỉnh, ví dụ một reverse proxy hoặc tunnel. Để trống để dùng tự động." @@ -982,6 +1007,11 @@ msgstr "Tất cả dự án" msgid "All sources" msgstr "Tất cả nguồn" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "All workspaces" +msgstr "Tất cả không gian làm việc" + #: src/mobile/push/WebPushPermissionPrompt.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/helpers.ts #: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx @@ -1643,6 +1673,7 @@ msgstr "Không thể tải danh sách cổng" #: src/renderer/components/thread/ThreadGoalControls.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/structuredElicitation.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/ThreadRuntimeRequestPanel.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeRunDialog.tsx #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/GitReviewSidebar.tsx @@ -2889,6 +2920,7 @@ msgid "Couldn't update the skill." msgstr "Không thể cập nhật skill." #: src/renderer/components/thread/ChatPane/parts/items/FileChange.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Create" msgstr "Tạo" @@ -3167,6 +3199,7 @@ msgstr "Giao việc cho các tác nhân AI khác." #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" msgstr "Xóa" @@ -3255,6 +3288,14 @@ msgstr "Xóa chủ đề?" msgid "Delete to-do" msgstr "Xóa việc cần làm" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace" +msgstr "Xóa không gian làm việc" + +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace?" +msgstr "Xóa không gian làm việc?" + #: src/mobile/ThreadTitleRow.tsx #: src/mobile/views/threadActionSurfaces.tsx #: src/mobile/views/threadContextMenus.tsx @@ -4649,6 +4690,10 @@ msgstr "Tên nhóm" msgid "Group open threads" msgstr "Nhóm chủ đề mở" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Group projects so the sidebar, schedules, and pull requests only show one set at a time. Switch between them at the bottom of the sidebar." +msgstr "Nhóm các dự án để thanh bên, lịch biểu và pull request chỉ hiển thị một nhóm mỗi lần. Chuyển giữa chúng ở cuối thanh bên." + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts msgid "GUI chat font size" @@ -5077,6 +5122,14 @@ msgstr "Chuyển đến phần" msgid "just now" msgstr "ngay bây giờ" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Keep at least one workspace" +msgstr "Giữ lại ít nhất một không gian làm việc" + +#: src/renderer/actions/workspaceActions.ts +msgid "Keep at least one workspace." +msgstr "Giữ lại ít nhất một không gian làm việc." + #: src/renderer/views/MainView/parts/Sidebar/parts/ForceDeleteBranchDialog.tsx msgid "Keep Branch" msgstr "Giữ nhánh" @@ -5452,6 +5505,10 @@ msgstr "Quản lý cấu hình máy chủ MCP mà Poracode thêm khi khởi đ #~ msgid "Manage the skills Poracode makes available to supported agents. Project skills can be managed in each project's settings." #~ msgstr "Quản lý các skill mà Poracode cung cấp cho những tác tử được hỗ trợ. Có thể quản lý skill dự án trong phần cài đặt của từng dự án." +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Manage workspaces" +msgstr "Quản lý không gian làm việc" + #: src/renderer/components/skills/SkillsManager.tsx #~ msgid "Managed" #~ msgstr "Được quản lý" @@ -5947,10 +6004,24 @@ msgstr "Chuyển trình duyệt sang cửa sổ" msgid "Move changes to a new worktree" msgstr "Di chuyển các thay đổi sang một cây làm việc mới" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +msgid "Move to Workspace" +msgstr "Chuyển tới không gian làm việc" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Di chuyển khay việc cần làm sang bảng bên phải" +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved {movedCount} projects to {0}." +#~ msgstr "Đã chuyển {movedCount} dự án sang {0}." + +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved 1 project to {0}." +#~ msgstr "Đã chuyển 1 dự án sang {0}." + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Moved your changes into a new worktree on \"{newBranch}\". \"{currentBranch}\" is now clean." msgstr "Đã chuyển các thay đổi của bạn sang một cây làm việc mới trên \"{newBranch}\". \"{currentBranch}\" hiện đã sạch." @@ -5965,6 +6036,7 @@ msgstr "my-mcp-server" #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/skills/SkillMarketplaceModal.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx msgid "Name" @@ -6162,6 +6234,10 @@ msgstr "Luồng mới trong worktree" msgid "New Thread in Worktree" msgstr "Luồng mới trong Worktree" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +msgid "New workspace" +msgstr "Không gian làm việc mới" + #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx msgid "New worktree" msgstr "Cây làm việc mới" @@ -6454,6 +6530,10 @@ msgstr "Chưa có giai đoạn nào." msgid "No projects" msgstr "Không có dự án" +#: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx +msgid "No projects in this workspace" +msgstr "Không có dự án nào trong không gian làm việc này" + #: src/renderer/views/SettingsOverlay/parts/RemoteServersSettings.tsx msgid "No projects on this server." msgstr "Không có dự án nào trên máy chủ này." @@ -6462,6 +6542,10 @@ msgstr "Không có dự án nào trên máy chủ này." msgid "No projects yet" msgstr "Chưa có dự án nào" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "No projects yet." +msgstr "Chưa có dự án nào." + #: src/renderer/components/providers/codex/index.tsx msgid "No prompts" msgstr "Không có lời nhắc" @@ -7540,9 +7624,16 @@ msgstr "Ghi đè dành riêng cho dự án ở đầu cài đặt tìm kiếm ch #: src/mobile/WideShell.tsx #: src/renderer/components/mcp/McpProjectDestinationDropdown.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Projects" msgstr "Dự án" +#. placeholder {0}: deleting.name +#. placeholder {1}: deleteFallback.name +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Projects in {0} move to {1}. No project is deleted." +msgstr "Các dự án trong {0} sẽ chuyển sang {1}. Không dự án nào bị xóa." + #: src/mobile/setupEmptyState.tsx msgid "Projects live on the connected desktop. Add one before starting a thread." msgstr "Dự án nằm trên máy tính đã kết nối. Hãy thêm một dự án trước khi bắt đầu luồng." @@ -8095,6 +8186,11 @@ msgstr "Đổi tên luồng" msgid "Rename to" msgstr "Đổi tên thành" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Rename workspace" +msgstr "Đổi tên không gian làm việc" + #: src/renderer/RendererCrashScreen.tsx msgid "Renderer crashed" msgstr "Trình kết xuất bị lỗi" @@ -8539,6 +8635,7 @@ msgstr "Tùy chọn thời gian chạy" #: src/mobile/views/FilesView.tsx #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/thread/ThreadGoalControls.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/FileEditorOverlay/parts/FileEditorPane/parts/EditorToolbar.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx @@ -9727,6 +9824,10 @@ msgstr "Chuyển đến cuộc trò chuyện trước" msgid "Switch to the previous tab" msgstr "Chuyển đến tab trước" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Switch workspace" +msgstr "Chuyển không gian làm việc" + #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/parts/CommitSyncPanel.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/useWorktreeActions.ts @@ -11350,6 +11451,7 @@ msgid "Winner merged." msgstr "Đã hợp nhất ứng viên thắng." #: src/renderer/components/providers/composerControlBuilders.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Work" msgstr "công việc" @@ -11414,6 +11516,7 @@ msgstr "Đang làm việc…" #: src/renderer/components/mcp/McpServersManager.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Workspace" msgstr "Không gian làm việc" @@ -11425,6 +11528,12 @@ msgstr "Máy chủ MCP trong không gian làm việc sẽ ghi đè máy chủ ng msgid "Workspace view" msgstr "Chế độ xem không gian làm việc" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Workspaces" +msgstr "Không gian làm việc" + #: src/mobile/views/ThreadsView.tsx #: src/renderer/components/thread/WorktreeModeSelect.tsx msgid "Worktree" diff --git a/src/renderer/locales/zh-CN/messages.po b/src/renderer/locales/zh-CN/messages.po index 89ba460cc..012ccc6ea 100644 --- a/src/renderer/locales/zh-CN/messages.po +++ b/src/renderer/locales/zh-CN/messages.po @@ -66,8 +66,10 @@ msgstr "{0, plural, one {# 个已配对连接} other {# 个已配对连接}}" msgid "{0, plural, one {# paired desktop} other {# paired desktops}}" msgstr "{0, plural, one {# 个已配对桌面} other {# 个已配对桌面}}" +#. placeholder {0}: projectCounts.get(workspace.id) ?? 0 #. placeholder {0}: props.projects.length #: src/mobile/views/ManageProjectsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "{0, plural, one {# project} other {# projects}}" msgstr "{0, plural, one {# 个项目} other {# 个项目}}" @@ -221,6 +223,11 @@ msgstr "{0} 需要输入。" msgid "{0} of {matchCount}" msgstr "{0} / {matchCount}" +#. placeholder {0}: projectCount(workspace.id) +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "{0} projects" +#~ msgstr "{0} 个项目" + #. placeholder {0}: cell.count #: src/renderer/views/ProfileOverlay/parts/ActivityHeatmap.tsx msgid "{0} prompt - {when}" @@ -366,6 +373,10 @@ msgstr "{head}或{tail}" msgid "{heading} mode" msgstr "{heading}模式" +#: src/renderer/views/SchedulesView/SchedulesView.tsx +msgid "{hiddenByWorkspaceCount, plural, one {# schedule belongs to a project in another workspace.} other {# schedules belong to projects in another workspace.}}" +msgstr "{hiddenByWorkspaceCount, plural, other {有 # 个计划属于其他工作区的项目。}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{hook} hook failed" msgstr "{hook}挂钩失败" @@ -445,6 +456,10 @@ msgstr "{minutes} 分钟前" msgid "{missingAuthLabel} needs authentication." msgstr "{missingAuthLabel}需要身份验证。" +#: src/renderer/actions/workspaceActions.ts +msgid "{movedCount, plural, one {Moved # project to {target}.} other {Moved # projects to {target}.}}" +msgstr "{movedCount, plural, other {已将 # 个项目移动到 {target}。}}" + #: src/renderer/i18n/sharedMessages.ts msgid "{original}\n" "Worktree cleanup also failed: {cleanup}" @@ -545,6 +560,10 @@ msgstr "~{money}{tokens} · {0} · 预计" msgid "≈{0}% by reset" msgstr "≈{0}% 重置时" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +#~ msgid "1 project" +#~ msgstr "1 个项目" + #: src/mobile/views/ThreadsView.tsx msgid "1 thread" msgstr "1 个线程" @@ -654,6 +673,7 @@ msgstr "{title} 的操作" #: src/renderer/components/thread/ThreadGoalDock.tsx #: src/renderer/views/SchedulesView/ScheduleEditor.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Active" msgstr "活动" @@ -826,6 +846,11 @@ msgstr "添加到主屏幕" msgid "Add to-do" msgstr "添加待办事项" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Add workspace" +msgstr "添加工作区" + #: src/renderer/views/SettingsOverlay/parts/RemoteAccessSettings.tsx msgid "Advertise a custom base URL, e.g. a reverse proxy or tunnel. Leave empty for automatic." msgstr "公布自定义基础 URL,例如反向代理或隧道。留空则自动设置。" @@ -982,6 +1007,11 @@ msgstr "所有项目" msgid "All sources" msgstr "所有来源" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "All workspaces" +msgstr "所有工作区" + #: src/mobile/push/WebPushPermissionPrompt.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/helpers.ts #: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx @@ -1643,6 +1673,7 @@ msgstr "无法加载端口" #: src/renderer/components/thread/ThreadGoalControls.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/structuredElicitation.tsx #: src/renderer/components/thread/ThreadRuntimeRequestPanel/ThreadRuntimeRequestPanel.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #: src/renderer/views/ExperimentView/parts/ExperimentJudgeRunDialog.tsx #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/GitReviewSidebar.tsx @@ -2889,6 +2920,7 @@ msgid "Couldn't update the skill." msgstr "无法更新技能。" #: src/renderer/components/thread/ChatPane/parts/items/FileChange.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Create" msgstr "创建" @@ -3167,6 +3199,7 @@ msgstr "将工作委派给其他 AI 智能体。" #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" msgstr "删除" @@ -3255,6 +3288,14 @@ msgstr "删除线程?" msgid "Delete to-do" msgstr "删除待办事项" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace" +msgstr "删除工作区" + +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Delete workspace?" +msgstr "删除工作区?" + #: src/mobile/ThreadTitleRow.tsx #: src/mobile/views/threadActionSurfaces.tsx #: src/mobile/views/threadContextMenus.tsx @@ -4649,6 +4690,10 @@ msgstr "分组名称" msgid "Group open threads" msgstr "对打开的线程进行分组" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Group projects so the sidebar, schedules, and pull requests only show one set at a time. Switch between them at the bottom of the sidebar." +msgstr "将项目分组,使侧边栏、计划和拉取请求一次只显示一组。在侧边栏底部切换工作区。" + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts msgid "GUI chat font size" @@ -5077,6 +5122,14 @@ msgstr "跳转到部分" msgid "just now" msgstr "刚才" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Keep at least one workspace" +msgstr "至少保留一个工作区" + +#: src/renderer/actions/workspaceActions.ts +msgid "Keep at least one workspace." +msgstr "至少保留一个工作区。" + #: src/renderer/views/MainView/parts/Sidebar/parts/ForceDeleteBranchDialog.tsx msgid "Keep Branch" msgstr "保留分支" @@ -5452,6 +5505,10 @@ msgstr "管理 Poracode 在启动受支持的代理时添加的 MCP 服务器配 #~ msgid "Manage the skills Poracode makes available to supported agents. Project skills can be managed in each project's settings." #~ msgstr "管理 Poracode 向受支持智能体提供的技能。可以在每个项目的设置中管理项目技能。" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Manage workspaces" +msgstr "管理工作区" + #: src/renderer/components/skills/SkillsManager.tsx #~ msgid "Managed" #~ msgstr "已托管" @@ -5946,10 +6003,24 @@ msgstr "将浏览器移到窗口" msgid "Move changes to a new worktree" msgstr "将更改移至新工作树" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx +msgid "Move to Workspace" +msgstr "移动到工作区" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "将待办事项停靠栏移至右侧面板" +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved {movedCount} projects to {0}." +#~ msgstr "已将 {movedCount} 个项目移动到 {0}。" + +#. placeholder {0}: fallback.name +#: src/renderer/actions/workspaceActions.ts +#~ msgid "Moved 1 project to {0}." +#~ msgstr "已将 1 个项目移动到 {0}。" + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Moved your changes into a new worktree on \"{newBranch}\". \"{currentBranch}\" is now clean." msgstr "已将您的更改移至“{newBranch}”上的新工作树中。“{currentBranch}”现在是干净的。" @@ -5964,6 +6035,7 @@ msgstr "my-mcp-server" #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/skills/SkillMarketplaceModal.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx msgid "Name" @@ -6161,6 +6233,10 @@ msgstr "在 worktree 中新建线程" msgid "New Thread in Worktree" msgstr "工作树中的新线程" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +msgid "New workspace" +msgstr "新建工作区" + #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx msgid "New worktree" msgstr "新工作树" @@ -6453,6 +6529,10 @@ msgstr "还没有阶段。" msgid "No projects" msgstr "没有项目" +#: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx +msgid "No projects in this workspace" +msgstr "此工作区中没有项目" + #: src/renderer/views/SettingsOverlay/parts/RemoteServersSettings.tsx msgid "No projects on this server." msgstr "此服务器上没有项目。" @@ -6461,6 +6541,10 @@ msgstr "此服务器上没有项目。" msgid "No projects yet" msgstr "暂无项目" +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "No projects yet." +msgstr "还没有项目。" + #: src/renderer/components/providers/codex/index.tsx msgid "No prompts" msgstr "无提示" @@ -7539,9 +7623,16 @@ msgstr "在全局搜索设置基础上的项目特定覆盖。" #: src/mobile/WideShell.tsx #: src/renderer/components/mcp/McpProjectDestinationDropdown.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Projects" msgstr "项目" +#. placeholder {0}: deleting.name +#. placeholder {1}: deleteFallback.name +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Projects in {0} move to {1}. No project is deleted." +msgstr "{0} 中的项目将移动到 {1}。不会删除任何项目。" + #: src/mobile/setupEmptyState.tsx msgid "Projects live on the connected desktop. Add one before starting a thread." msgstr "项目位于已连接的桌面端。开始线程前请先添加一个项目。" @@ -8094,6 +8185,11 @@ msgstr "重命名线程" msgid "Rename to" msgstr "重命名为" +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Rename workspace" +msgstr "重命名工作区" + #: src/renderer/RendererCrashScreen.tsx msgid "Renderer crashed" msgstr "渲染器崩溃" @@ -8538,6 +8634,7 @@ msgstr "运行时选项" #: src/mobile/views/FilesView.tsx #: src/renderer/components/mcp/McpServerEditor.tsx #: src/renderer/components/thread/ThreadGoalControls.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx #: src/renderer/views/FileEditorOverlay/parts/FileEditorPane/parts/EditorToolbar.tsx #: src/renderer/views/ProfileOverlay/parts/EditProfileDialog.tsx #: src/renderer/views/SettingsOverlay/parts/ClaudeProfileSettings.tsx @@ -9726,6 +9823,10 @@ msgstr "切换到上一个聊天" msgid "Switch to the previous tab" msgstr "切换到上一个选项卡" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +msgid "Switch workspace" +msgstr "切换工作区" + #: src/renderer/views/GitReviewOverlay/parts/GitReviewSidebar/parts/CommitSyncPanel.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarProjectHeader.tsx #: src/renderer/views/MainView/parts/Sidebar/parts/useWorktreeActions.ts @@ -11349,6 +11450,7 @@ msgid "Winner merged." msgstr "已合并优胜者。" #: src/renderer/components/providers/composerControlBuilders.tsx +#: src/renderer/components/workspace/WorkspaceNameDialog.tsx msgid "Work" msgstr "工作" @@ -11413,6 +11515,7 @@ msgstr "工作…" #: src/renderer/components/mcp/McpServersManager.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Workspace" msgstr "工作区" @@ -11424,6 +11527,12 @@ msgstr "工作区 MCP 服务器会覆盖同名的用户服务器,并且仅在 msgid "Workspace view" msgstr "工作区视图" +#: src/renderer/views/MainView/parts/Sidebar/parts/SidebarWorkspaceSwitcher.tsx +#: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +#: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx +msgid "Workspaces" +msgstr "工作区" + #: src/mobile/views/ThreadsView.tsx #: src/renderer/components/thread/WorktreeModeSelect.tsx msgid "Worktree" diff --git a/src/renderer/state/sharedSettingsStore.ts b/src/renderer/state/sharedSettingsStore.ts index 54be70048..f209d59f8 100644 --- a/src/renderer/state/sharedSettingsStore.ts +++ b/src/renderer/state/sharedSettingsStore.ts @@ -26,7 +26,9 @@ import type { WorktreeStorageMode, BuiltInMcpServerId, McpServer, + Workspace, } from "@/shared/contracts"; +import { nextWorkspaceIconId } from "@/shared/contracts"; const STORAGE_KEY = "poracode-shared-settings"; @@ -136,6 +138,13 @@ interface SharedSettingsState extends SharedSettings { error?: boolean; }) => void; setNotifyL2Cli: (value: boolean) => void; + /** Append a workspace and return it, so callers can activate the new entry. */ + addWorkspace: (name: string) => Workspace; + renameWorkspace: (workspaceId: string, name: string) => void; + /** Remove a workspace. No-op on the last remaining one — a project must always have a home. */ + removeWorkspace: (workspaceId: string) => void; + /** Replace the whole list in one persist write (used to seed the defaults). */ + setWorkspaces: (workspaces: Workspace[]) => void; toggleFavoriteModel: ( agentKind: string, modelId: string, @@ -689,6 +698,36 @@ export const useSharedSettings = create()((set, get) => ({ set({ notifyL2Cli }); persistSettings(selectSharedSettings(get())); }, + addWorkspace: (name) => { + const current = get().workspaces; + const workspace: Workspace = { + id: crypto.randomUUID(), + name, + createdAt: new Date().toISOString(), + icon: nextWorkspaceIconId(current), + }; + set({ workspaces: [...current, workspace] }); + persistSettings(selectSharedSettings(get())); + return workspace; + }, + renameWorkspace: (workspaceId, name) => { + const current = get().workspaces; + if (!current.some((w) => w.id === workspaceId && w.name !== name)) return; + set({ workspaces: current.map((w) => (w.id === workspaceId ? { ...w, name } : w)) }); + persistSettings(selectSharedSettings(get())); + }, + removeWorkspace: (workspaceId) => { + const current = get().workspaces; + // Refuse to drop the last workspace: with none left there is no valid + // active id, and every project would render as unfiled. + if (current.length <= 1 || !current.some((w) => w.id === workspaceId)) return; + set({ workspaces: current.filter((w) => w.id !== workspaceId) }); + persistSettings(selectSharedSettings(get())); + }, + setWorkspaces: (workspaces) => { + set({ workspaces }); + persistSettings(selectSharedSettings(get())); + }, toggleFavoriteModel: (agentKind, modelId, presentationMode) => { const current = get().favoriteModels; const idx = current.findIndex( @@ -841,6 +880,7 @@ function selectSharedSettings(state: SharedSettingsState): SharedSettingsInput { notifyL2Cli: state.notifyL2Cli, remotePushEnabled: state.remotePushEnabled, remotePushRedactContent: state.remotePushRedactContent, + workspaces: state.workspaces, favoriteModels: state.favoriteModels, recentModels: state.recentModels, browser: state.browser, diff --git a/src/renderer/state/slices/projectSlice.ts b/src/renderer/state/slices/projectSlice.ts index 61a350140..76fb048e8 100644 --- a/src/renderer/state/slices/projectSlice.ts +++ b/src/renderer/state/slices/projectSlice.ts @@ -37,7 +37,12 @@ function projectDraftConfigEqual( export interface ProjectSlice { projects: Project[]; - addProject: (location: ProjectLocation, nameOverride?: string) => Project; + addProject: ( + location: ProjectLocation, + nameOverride?: string, + /** Workspace to file the new project into; omitted leaves it visible in every workspace. */ + workspaceId?: string, + ) => Project; ensureHomeProject: (location: ProjectLocation) => Project; deleteProject: (projectId: string) => void; updateProjectDraftConfig: (projectId: string, draftConfig: ProjectDraftConfig) => void; @@ -54,17 +59,28 @@ export interface ProjectSlice { updateProjectMcpServers: (projectId: string, mcpServers: McpServer[]) => void; updateProjectLocation: (projectId: string, location: ProjectLocation) => void; renameProject: (projectId: string, name: string) => void; + /** Move a project into a workspace; `undefined` unfiles it (visible in every workspace). */ + setProjectWorkspace: (projectId: string, workspaceId: string | undefined) => void; + /** + * Move every project currently in `from` into `to`, where `undefined` means + * "unfiled". Serves both the first-run bootstrap (`undefined` → the default + * workspace, so an existing install lands wholly in it) and workspace deletion + * (the doomed workspace → a surviving one). Never deletes projects, and leaves + * the synthetic Home project unfiled. + */ + refileProjects: (from: string | undefined, to: string) => void; setProjectDisabled: (projectId: string, disabled: boolean) => void; reorderProjects: (sourceId: string, targetId: string, placement: ReorderPlacement) => void; } export const createProjectSlice: SliceCreator = (set) => ({ projects: [], - addProject: (location, nameOverride) => { + addProject: (location, nameOverride, workspaceId) => { const project: Project = { id: crypto.randomUUID(), name: nameOverride?.trim() || getProjectName(location), location, + ...(workspaceId ? { workspaceId } : {}), createdAt: new Date().toISOString(), }; @@ -231,6 +247,34 @@ export const createProjectSlice: SliceCreator = (set) => ({ project.id === projectId ? { ...project, name } : project, ), })), + setProjectWorkspace: (projectId, workspaceId) => + set((state) => { + const target = state.projects.find((project) => project.id === projectId); + if (!target || target.workspaceId === workspaceId) return {}; + return { + projects: state.projects.map((project) => { + if (project.id !== projectId) return project; + if (!workspaceId) { + const { workspaceId: _, ...rest } = project; + return rest; + } + return { ...project, workspaceId }; + }), + }; + }), + refileProjects: (from, to) => + set((state) => { + if (from === to) return {}; + // Home is synthetic and belongs to every workspace, so it stays unfiled. + const shouldMove = (project: Project) => + project.workspaceId === from && project.id !== HOME_PROJECT_ID; + if (!state.projects.some(shouldMove)) return {}; + return { + projects: state.projects.map((project) => + shouldMove(project) ? { ...project, workspaceId: to } : project, + ), + }; + }), setProjectDisabled: (projectId, disabled) => set((state) => { const target = state.projects.find((p) => p.id === projectId); diff --git a/src/renderer/state/workspaceSelectors.ts b/src/renderer/state/workspaceSelectors.ts new file mode 100644 index 000000000..0213ab613 --- /dev/null +++ b/src/renderer/state/workspaceSelectors.ts @@ -0,0 +1,64 @@ +import { useShallow } from "zustand/react/shallow"; +import { isProjectInWorkspace, type Project, type Workspace } from "@/shared/contracts"; +import { isHomeProjectId } from "@/shared/homeScope"; +import { useAppStore } from "./appStore"; +import { useSharedSettings } from "./sharedSettingsStore"; +import { useActiveWorkspaceId } from "./workspaceStore"; + +/** + * Workspace-id lookup cached on the identity of the settings array. The list + * changes only when the user edits it, so rebuilding the Set on every render of + * every consumer would be pure waste. + */ +let cachedWorkspaces: readonly Workspace[] | null = null; +let cachedWorkspaceIds: ReadonlySet = new Set(); + +function workspaceIdSet(workspaces: readonly Workspace[]): ReadonlySet { + if (workspaces !== cachedWorkspaces) { + cachedWorkspaces = workspaces; + cachedWorkspaceIds = new Set(workspaces.map((workspace) => workspace.id)); + } + return cachedWorkspaceIds; +} + +/** + * Reactive predicate for "is this project in the active workspace". Returned as + * a plain function so callers can filter their own lists without each one + * re-deriving the rule (which lives in `@/shared/contracts/workspace`). + */ +export function useWorkspaceProjectFilter(): (project: Project) => boolean { + const knownWorkspaceIds = workspaceIdSet(useSharedSettings((state) => state.workspaces)); + const activeWorkspaceId = useActiveWorkspaceId(); + return (project) => isProjectInWorkspace(project, activeWorkspaceId, knownWorkspaceIds); +} + +/** Ids of projects the active workspace shows, in store order. Excludes Home. */ +export function useWorkspaceProjectIds(): string[] { + const isVisible = useWorkspaceProjectFilter(); + return useAppStore( + useShallow((state) => + state.projects + .filter((project) => !isHomeProjectId(project.id) && isVisible(project)) + .map((project) => project.id), + ), + ); +} + +/** + * Project ids the active workspace hides. Consumed both as a count (the sidebar's + * "this workspace is empty but others aren't" copy) and as a membership test (the + * schedules view, which hides schedules belonging to those projects). + */ +export function useProjectIdsHiddenByWorkspace(): ReadonlySet { + const isVisible = useWorkspaceProjectFilter(); + return useAppStore( + useShallow( + (state) => + new Set( + state.projects + .filter((project) => !isHomeProjectId(project.id) && !isVisible(project)) + .map((project) => project.id), + ), + ), + ); +} diff --git a/src/renderer/state/workspaceStore.test.ts b/src/renderer/state/workspaceStore.test.ts new file mode 100644 index 000000000..8c7601735 --- /dev/null +++ b/src/renderer/state/workspaceStore.test.ts @@ -0,0 +1,98 @@ +import { beforeEach, describe, expect, it } from "vitest"; +import { DEFAULT_WORKSPACE_ICONS, DEFAULT_WORKSPACE_NAMES } from "@/shared/contracts"; +import { HOME_PROJECT_ID } from "@/shared/homeScope"; +import { useAppStore } from "./appStore"; +import { useSharedSettings } from "./sharedSettingsStore"; +import { bootstrapWorkspaces, resolveActiveWorkspaceId, useWorkspaceStore } from "./workspaceStore"; + +function addProject(name: string) { + return useAppStore.getState().addProject({ kind: "posix", path: `/repos/${name}` }, name); +} + +describe("resolveActiveWorkspaceId", () => { + it("keeps a stored id that still resolves", () => { + expect(resolveActiveWorkspaceId([{ id: "a" }, { id: "b" }], "b")).toBe("b"); + }); + + it("falls back to the first workspace when the stored id is stale", () => { + // A workspace deleted on another device must not blank the sidebar. + expect(resolveActiveWorkspaceId([{ id: "a" }, { id: "b" }], "gone")).toBe("a"); + }); + + it("returns null when there are no workspaces at all", () => { + expect(resolveActiveWorkspaceId([], "a")).toBeNull(); + }); +}); + +describe("bootstrapWorkspaces", () => { + beforeEach(() => { + localStorage.clear(); + useSharedSettings.setState({ workspaces: [] }); + useWorkspaceStore.setState({ activeWorkspaceId: null }); + useAppStore.setState((state) => ({ ...state, projects: [], threads: [] })); + }); + + it("seeds the default workspaces in a single settings write", async () => { + const writes: number[] = []; + const unsubscribe = useSharedSettings.subscribe((state) => + writes.push(state.workspaces.length), + ); + + await bootstrapWorkspaces(); + unsubscribe(); + + // One write, not one per default name — each would serialize all settings. + expect(writes).toEqual([2]); + + const names = useSharedSettings.getState().workspaces.map((w) => w.name); + expect(names).toEqual([...DEFAULT_WORKSPACE_NAMES]); + expect(useSharedSettings.getState().workspaces.map((w) => w.icon)).toEqual([ + ...DEFAULT_WORKSPACE_ICONS, + ]); + expect(useWorkspaceStore.getState().activeWorkspaceId).toBe( + useSharedSettings.getState().workspaces[0]!.id, + ); + }); + + it("files pre-existing projects into the first workspace", async () => { + const alpha = addProject("alpha"); + const beta = addProject("beta"); + + await bootstrapWorkspaces(); + + const workId = useSharedSettings.getState().workspaces[0]!.id; + const projects = useAppStore.getState().projects; + expect(projects.find((p) => p.id === alpha.id)?.workspaceId).toBe(workId); + expect(projects.find((p) => p.id === beta.id)?.workspaceId).toBe(workId); + }); + + it("leaves the synthetic Home project unfiled", async () => { + useAppStore.getState().ensureHomeProject({ kind: "posix", path: "/Users/me" }); + + await bootstrapWorkspaces(); + + const home = useAppStore.getState().projects.find((p) => p.id === HOME_PROJECT_ID); + expect(home?.workspaceId).toBeUndefined(); + }); + + it("is idempotent once workspaces exist", async () => { + await bootstrapWorkspaces(); + const before = useSharedSettings.getState().workspaces; + + await bootstrapWorkspaces(); + + expect(useSharedSettings.getState().workspaces).toEqual(before); + }); + + it("repairs a stale active id instead of reseeding", async () => { + await bootstrapWorkspaces(); + const [work, side] = useSharedSettings.getState().workspaces; + useWorkspaceStore.setState({ activeWorkspaceId: "gone" }); + + await bootstrapWorkspaces(); + + expect(useSharedSettings.getState().workspaces).toHaveLength(2); + expect(useWorkspaceStore.getState().activeWorkspaceId).toBe(work!.id); + expect(side).toBeDefined(); + }); +}); diff --git a/src/renderer/state/workspaceStore.ts b/src/renderer/state/workspaceStore.ts new file mode 100644 index 000000000..41a7354b1 --- /dev/null +++ b/src/renderer/state/workspaceStore.ts @@ -0,0 +1,103 @@ +import { create } from "zustand"; +import { createJSONStorage, persist } from "zustand/middleware"; +import { + DEFAULT_WORKSPACE_ICONS, + DEFAULT_WORKSPACE_NAMES, + type Workspace, +} from "@/shared/contracts"; +import { useAppStore } from "./appStore"; +import { useSharedSettings, whenSharedSettingsHydrated } from "./sharedSettingsStore"; + +const PERSIST_KEY = "poracode-active-workspace"; + +interface WorkspaceUiState { + /** + * Which workspace this window is looking at. Deliberately *not* part of + * `sharedSettings`, which is a single file shared by every window on the + * device — switching in one window must not yank the others along with it. + */ + activeWorkspaceId: string | null; + setActiveWorkspaceId: (workspaceId: string | null) => void; +} + +export const useWorkspaceStore = create()( + persist( + (set) => ({ + activeWorkspaceId: null, + setActiveWorkspaceId: (activeWorkspaceId) => set({ activeWorkspaceId }), + }), + { + name: PERSIST_KEY, + version: 1, + storage: createJSONStorage(() => localStorage), + }, + ), +); + +/** + * The workspace actually in effect: the stored id when it still resolves, + * otherwise the first workspace. A stale id (workspace deleted on another + * device) must not blank the sidebar. + */ +export function resolveActiveWorkspaceId( + workspaces: readonly { id: string }[], + storedId: string | null, +): string | null { + if (workspaces.length === 0) return null; + if (storedId && workspaces.some((workspace) => workspace.id === storedId)) return storedId; + return workspaces[0]!.id; +} + +export function useActiveWorkspaceId(): string | null { + const workspaces = useSharedSettings((state) => state.workspaces); + const storedId = useWorkspaceStore((state) => state.activeWorkspaceId); + return resolveActiveWorkspaceId(workspaces, storedId); +} + +export function getActiveWorkspaceId(): string | null { + return resolveActiveWorkspaceId( + // `?? []` guards partially-stubbed stores in tests; production settings + // always carry the key via `defaultSharedSettings`. + useSharedSettings.getState().workspaces ?? [], + useWorkspaceStore.getState().activeWorkspaceId, + ); +} + +/** + * Seed the default workspaces on first run and file every pre-existing project + * into the first one, so an install that predates workspaces opens showing + * exactly what it showed before. + * + * Idempotent: does nothing once any workspace exists. + */ +export async function bootstrapWorkspaces(): Promise { + await whenSharedSettingsHydrated(); + const settings = useSharedSettings.getState(); + const existing = settings.workspaces ?? []; + if (existing.length > 0) { + // Already seeded. Still make sure this window points somewhere real. + const resolved = resolveActiveWorkspaceId( + existing, + useWorkspaceStore.getState().activeWorkspaceId, + ); + if (resolved !== useWorkspaceStore.getState().activeWorkspaceId) { + useWorkspaceStore.getState().setActiveWorkspaceId(resolved); + } + return; + } + + // Seed in a single write: every `addWorkspace` call would serialize the whole + // settings object to localStorage and fire its own IPC write. + const createdAt = new Date().toISOString(); + const seeded: Workspace[] = DEFAULT_WORKSPACE_NAMES.map((name, index) => ({ + id: crypto.randomUUID(), + name, + createdAt, + icon: DEFAULT_WORKSPACE_ICONS[index]!, + })); + settings.setWorkspaces(seeded); + + const primary = seeded[0]!; + useAppStore.getState().refileProjects(undefined, primary.id); + useWorkspaceStore.getState().setActiveWorkspaceId(primary.id); +} diff --git a/src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx b/src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx index 0c90804aa..e3e25fa02 100644 --- a/src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx +++ b/src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -51,7 +51,12 @@ import { useAppStore } from "@/renderer/state/appStore"; import { usePanelStore } from "@/renderer/state/panelStore"; import { useSidebarUiStore } from "@/renderer/state/sidebarUiStore"; import { useSharedSettings } from "@/renderer/state/sharedSettingsStore"; +import { + useProjectIdsHiddenByWorkspace, + useWorkspaceProjectIds, +} from "@/renderer/state/workspaceSelectors"; import { useUpdateStore } from "@/renderer/state/updateStore"; +import { SidebarWorkspaceSwitcher } from "./parts/SidebarWorkspaceSwitcher"; import { SidebarProjectThreadList } from "./parts/SidebarProjectThreadList"; import { WhatsNewButton } from "./parts/WhatsNewButton"; import { DeferredSettingsOverlay } from "@/renderer/deferredFeatures"; @@ -216,11 +221,10 @@ function CollapsedThreadRail() { export function Sidebar() { const { t } = useLingui(); - const projectIds = useAppStore( - useShallow((state) => - state.projects.filter((project) => !isHomeProject(project)).map((project) => project.id), - ), - ); + // Only the active workspace's projects; Home is handled separately below and + // belongs to every workspace. + const projectIds = useWorkspaceProjectIds(); + const hiddenProjectCount = useProjectIdsHiddenByWorkspace().size; const homeProject = useAppStore((state) => state.projects.find(isHomeProject)); const homeScopeEnabled = useSharedSettings((s) => s.homeScopeEnabled); const remoteAccessEnabled = useSharedSettings((s) => s.remoteAccessEnabled); @@ -395,7 +399,13 @@ export function Sidebar() { {projectIds.length === 0 && !(homeScopeEnabled && homeProject) ? (

- Add a project to start + {hiddenProjectCount > 0 ? ( + // Distinguish "you own no projects" from "this workspace is + // empty but others aren't" — otherwise the sidebar looks broken. + No projects in this workspace + ) : ( + Add a project to start + )}

) : ( @@ -447,6 +457,7 @@ export function Sidebar() {
+ s.toggleProjectCollapsed); + const workspaces = useSharedSettings((s) => s.workspaces); const hasTerminal = useIsProjectTerminalOpen(project.id); const isActiveTerminal = useIsProjectTerminalActive(project.id); const isBusyTerminal = useIsProjectTerminalBusy(project.id); @@ -100,6 +110,30 @@ export function SidebarProjectHeader(props: { ] : []), ]), + ...(workspaces.length > 1 + ? [ + { + type: "submenu" as const, + id: "move-to-workspace", + label: t`Move to Workspace`, + icon: , + items: [ + ...workspaces.map((workspace) => ({ + id: workspaceMenuKey(workspace.id), + label: workspace.name, + icon: , + isDisabled: workspace.id === project.workspaceId, + })), + { + id: WORKSPACE_UNFILED_KEY, + label: t`All workspaces`, + icon: , + isDisabled: !project.workspaceId, + }, + ], + }, + ] + : []), { id: "toggle-disabled", label: isDisabled ? t`Enable Project` : t`Disable Project`, @@ -121,6 +155,12 @@ export function SidebarProjectHeader(props: { if (key.startsWith("action:")) { runProjectAction(project.id, key.slice("action:".length)); } + const workspaceChoice = parseWorkspaceMenuKey(key); + if (workspaceChoice?.kind === "unfiled") { + useAppStore.getState().setProjectWorkspace(project.id, undefined); + } else if (workspaceChoice?.kind === "workspace") { + useAppStore.getState().setProjectWorkspace(project.id, workspaceChoice.workspaceId); + } }} > state.workspaces); + const activeWorkspaceId = useActiveWorkspaceId(); + const { mobile } = useResponsiveMenu(); + const [isOpen, setIsOpen] = useState(false); + const [addOpen, setAddOpen] = useState(false); + + // Nothing to switch between before the first-run bootstrap has seeded the + // defaults; showing an empty switcher would just be a dead control. + if (workspaces.length === 0) return null; + + // `useActiveWorkspaceId` falls back to the first workspace, so this resolves. + const active = workspaces.find((w) => w.id === activeWorkspaceId) ?? workspaces[0]!; + + function handleAction(key: string) { + setIsOpen(false); + const selection = parseWorkspaceMenuKey(key); + if (!selection) return; + if (selection.kind === "add") setAddOpen(true); + else if (selection.kind === "manage") openWorkspaceSettings(); + else if (selection.kind === "workspace") { + useWorkspaceStore.getState().setActiveWorkspaceId(selection.workspaceId); + } + } + + // Built once so the desktop menu and the mobile sheet can never drift apart. + const workspaceEntries: MenuEntry[] = workspaces.map((workspace) => ({ + key: workspaceMenuKey(workspace.id), + label: workspace.name, + icon: , + isSelected: workspace.id === active.id, + })); + const actionEntries: MenuEntry[] = [ + { + key: WORKSPACE_ADD_KEY, + label: t`Add workspace`, + icon: , + }, + { + key: WORKSPACE_MANAGE_KEY, + label: t`Manage workspaces`, + icon: , + }, + ]; + + const triggerContent = ( + <> + + + +
+ {active.name} +
+ + + + + ); + + const trigger = ( + + ); + + const dialog = ( + createWorkspace(name)} + onClose={() => setAddOpen(false)} + /> + ); + + if (mobile) { + return ( + <> + +
+ {workspaceEntries.map((entry) => ( + + ))} + + {actionEntries.map((entry) => ( + + ))} +
+
+ {dialog} + + ); + } + + return ( + <> + + + {triggerContent} + + + handleAction(String(key))} + > + + {workspaceEntries.map((entry) => ( + + {entry.icon} + + + ))} + + + + {actionEntries.map((entry) => ( + + {entry.icon} + + + ))} + + + + + {dialog} + + ); +} diff --git a/src/renderer/views/PullRequestsView/PullRequestsView.tsx b/src/renderer/views/PullRequestsView/PullRequestsView.tsx index 6e1b89f67..1d7169fdc 100644 --- a/src/renderer/views/PullRequestsView/PullRequestsView.tsx +++ b/src/renderer/views/PullRequestsView/PullRequestsView.tsx @@ -10,6 +10,7 @@ import { readBridge } from "@/renderer/bridge"; import { LightballTabs } from "@/renderer/components/common/LightballTabs"; import { RelativeTime } from "@/renderer/components/common/RelativeTime"; import { useAppStore } from "@/renderer/state/appStore"; +import { useWorkspaceProjectFilter } from "@/renderer/state/workspaceSelectors"; import { buildBranchNamePrKey } from "@/renderer/state/gitSelectors"; import { useGitStore } from "@/renderer/state/gitStore"; import { usePanelStore } from "@/renderer/state/panelStore"; @@ -36,9 +37,14 @@ interface PullRequestEntry { export function PullRequestsView() { const { t } = useLingui(); + // Scoped to the active workspace, so this view agrees with the sidebar about + // which projects exist right now. + const isInWorkspace = useWorkspaceProjectFilter(); const activeProjects = useAppStore( useShallow((state) => - state.projects.filter((project) => !project.disabled && !isHomeProject(project)), + state.projects.filter( + (project) => !project.disabled && !isHomeProject(project) && isInWorkspace(project), + ), ), ); const prReviewOpen = usePanelStore((state) => state.prReviewContext !== null); diff --git a/src/renderer/views/SchedulesView/SchedulesView.tsx b/src/renderer/views/SchedulesView/SchedulesView.tsx index 55bbf8baa..3b93979ba 100644 --- a/src/renderer/views/SchedulesView/SchedulesView.tsx +++ b/src/renderer/views/SchedulesView/SchedulesView.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { Button, ButtonGroup, Dropdown, Input, Label, TextField } from "@heroui/react"; -import { Trans, useLingui } from "@lingui/react/macro"; +import { Plural, Trans, useLingui } from "@lingui/react/macro"; import { Bot, CalendarClock, ChevronDown, Clock3, Loader2, Plus, Sparkles } from "lucide-react"; import type { ScheduledTask, ScheduledTaskInput } from "@/shared/contracts"; import { readBridge } from "@/renderer/bridge"; @@ -10,6 +10,7 @@ import { ensureHomeScopeProject } from "@/renderer/actions/projectActions"; import { openThread } from "@/renderer/actions/threadActions"; import { useAgentStatusesStore } from "@/renderer/state/agentStatusesStore"; import { useAppStore } from "@/renderer/state/appStore"; +import { useProjectIdsHiddenByWorkspace } from "@/renderer/state/workspaceSelectors"; import { SettingsPage } from "@/renderer/views/SettingsOverlay/parts/SettingsForm"; import { ScheduleEditor } from "./ScheduleEditor"; import { PreviousRunsModal } from "./parts/PreviousRunsModal"; @@ -41,6 +42,12 @@ export function SchedulesView() { const [draft, setDraft] = useState(null); const [deleteTask, setDeleteTask] = useState(null); const [runsTaskId, setRunsTaskId] = useState(null); + /** + * Only projects the active workspace hides are excluded, which lets both the + * device-wide schedules (no project) and schedules whose project was deleted + * stay visible without needing their own special cases. + */ + const hiddenProjectIds = useProjectIdsHiddenByWorkspace(); const agentStatuses = useAgentStatusesStore((state) => state.agentStatuses); const agents = agentStatuses.filter( (agent) => @@ -85,7 +92,11 @@ export function SchedulesView() { }, [hasRunningTask]); const normalizedQuery = query.trim().toLocaleLowerCase(); - const visibleTasks = tasks.filter((task) => { + const workspaceTasks = tasks.filter( + (task) => !task.projectId || !hiddenProjectIds.has(task.projectId), + ); + const hiddenByWorkspaceCount = tasks.length - workspaceTasks.length; + const visibleTasks = workspaceTasks.filter((task) => { if (filter === "active" && !task.enabled) return false; if (filter === "paused" && task.enabled) return false; return ( @@ -388,7 +399,7 @@ export function SchedulesView() {
) : ( <> - {tasks.length === 0 ? ( + {workspaceTasks.length === 0 ? (

@@ -423,6 +434,16 @@ export function SchedulesView() {

)} + {hiddenByWorkspaceCount > 0 ? ( +

+ +

+ ) : null} + {suggestions} {agents.length === 0 ? ( diff --git a/src/renderer/views/SettingsOverlay/SettingsOverlay.tsx b/src/renderer/views/SettingsOverlay/SettingsOverlay.tsx index b4dd4b20d..6ddf4d89c 100644 --- a/src/renderer/views/SettingsOverlay/SettingsOverlay.tsx +++ b/src/renderer/views/SettingsOverlay/SettingsOverlay.tsx @@ -33,11 +33,13 @@ import { DevSettings } from "./parts/DevSettings"; import { McpServersSettings } from "./parts/McpServersSettings"; import { SkillsSettings } from "./parts/SkillsSettings"; import { SettingsSidebar } from "./parts/SettingsSidebar"; +import { WorkspacesSettings } from "./parts/WorkspacesSettings"; import { AgentSettingsEmpty, SingleAgentSettings } from "./parts/SingleAgentSettings"; import type { SettingsSection } from "./parts/types"; const SECTION_VIEWS: Partial ReactNode>> = { profile: () => , + workspaces: () => , general: () => , audio: () => , appearance: () => , diff --git a/src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx b/src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx index 1a885d038..0accf11cc 100644 --- a/src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx +++ b/src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx @@ -14,6 +14,7 @@ import { Globe, Info, Keyboard, + Layers, Megaphone, Mic, MessageSquare, @@ -225,6 +226,7 @@ export function SettingsSidebar(props: { label: t`Personal`, sections: [ { id: "profile", icon: , label: t`Profile` }, + { id: "workspaces", icon: , label: t`Workspaces` }, { id: "general", icon: , label: t`General` }, { id: "appearance", icon: , label: t`Appearance` }, { id: "audio", icon: , label: t`Audio` }, diff --git a/src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx b/src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx new file mode 100644 index 000000000..4517d65ad --- /dev/null +++ b/src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx @@ -0,0 +1,208 @@ +import { useState } from "react"; +import { Button, Surface, Tooltip } from "@heroui/react"; +import { Layers, Pencil, Plus, Trash2 } from "lucide-react"; +import { Plural, Trans, useLingui } from "@lingui/react/macro"; +import type { Workspace } from "@/shared/contracts"; +import { isHomeProject } from "@/shared/homeScope"; +import { + createWorkspace, + deleteWorkspace, + renameWorkspace, + workspaceDeletionFallback, +} from "@/renderer/actions/workspaceActions"; +import { ConfirmDialog } from "@/renderer/components/common/ConfirmDialog"; +import { OptionMenu } from "@/renderer/components/common/OptionMenu"; +import { WorkspaceIcon } from "@/renderer/components/workspace/WorkspaceIcon"; +import { WorkspaceNameDialog } from "@/renderer/components/workspace/WorkspaceNameDialog"; +import { + WORKSPACE_UNFILED_KEY, + workspaceMenuKey, +} from "@/renderer/components/workspace/workspaceMenuKeys"; +import { useAppStore } from "@/renderer/state/appStore"; +import { useSharedSettings } from "@/renderer/state/sharedSettingsStore"; +import { useActiveWorkspaceId } from "@/renderer/state/workspaceStore"; +import { SettingsPage } from "./SettingsForm"; + +type NameDialogState = { mode: "create" } | { mode: "rename"; workspace: Workspace }; + +export function WorkspacesSettings() { + const { t } = useLingui(); + const workspaces = useSharedSettings((state) => state.workspaces); + const activeWorkspaceId = useActiveWorkspaceId(); + const allProjects = useAppStore((state) => state.projects); + // Home is synthetic and belongs to every workspace, so it isn't assignable. + const projects = allProjects.filter((project) => !isHomeProject(project)); + + const [nameDialog, setNameDialog] = useState(null); + const [deleting, setDeleting] = useState(null); + + // One grouped pass instead of a filter per row. + const projectCounts = new Map(); + for (const project of projects) { + if (project.workspaceId) { + projectCounts.set(project.workspaceId, (projectCounts.get(project.workspaceId) ?? 0) + 1); + } + } + const deleteFallback = deleting ? workspaceDeletionFallback(deleting.id) : undefined; + const isLastWorkspace = workspaces.length <= 1; + + const workspaceOptions = [ + ...workspaces.map((workspace) => ({ + id: workspaceMenuKey(workspace.id), + label: workspace.name, + icon: , + })), + { + id: WORKSPACE_UNFILED_KEY, + label: t`All workspaces`, + icon: , + }, + ]; + + return ( + + Group projects so the sidebar, schedules, and pull requests only show one set at a time. + Switch between them at the bottom of the sidebar. + + } + bodyClassName="" + actions={ + + } + > + + {workspaces.map((workspace) => ( +
+ +
+

+ {workspace.name} + {workspace.id === activeWorkspaceId ? ( + + Active + + ) : null} +

+

+ +

+
+
+ + + + + + Rename workspace + + + + + + + + {isLastWorkspace ? ( + Keep at least one workspace + ) : ( + Delete workspace + )} + + +
+
+ ))} +
+ +

+ Projects +

+ {projects.length === 0 ? ( +

+ No projects yet. +

+ ) : ( + + {projects.map((project) => ( +
+
+

{project.name}

+
+ { + const workspaceId = + next === WORKSPACE_UNFILED_KEY + ? undefined + : workspaces.find((w) => workspaceMenuKey(w.id) === next)?.id; + useAppStore.getState().setProjectWorkspace(project.id, workspaceId); + }} + /> +
+ ))} +
+ )} + + { + if (nameDialog?.mode === "rename") renameWorkspace(nameDialog.workspace.id, name); + else createWorkspace(name); + }} + onClose={() => setNameDialog(null)} + /> + + Projects in {deleting.name} move to {deleteFallback.name}. No project is deleted. + + ) : null + } + confirmLabel={t`Delete`} + onConfirm={() => { + if (deleting) deleteWorkspace(deleting.id); + setDeleting(null); + }} + onClose={() => setDeleting(null)} + /> +
+ ); +} diff --git a/src/renderer/views/SettingsOverlay/parts/types.ts b/src/renderer/views/SettingsOverlay/parts/types.ts index 1daab5e0f..32fd5bad0 100644 --- a/src/renderer/views/SettingsOverlay/parts/types.ts +++ b/src/renderer/views/SettingsOverlay/parts/types.ts @@ -1,5 +1,6 @@ export type SettingsSection = | "profile" + | "workspaces" | "general" | "audio" | "appearance" diff --git a/src/shared/contracts.ts b/src/shared/contracts.ts index d46266da4..41a6e5f4b 100644 --- a/src/shared/contracts.ts +++ b/src/shared/contracts.ts @@ -19,3 +19,4 @@ export * from "./contracts/prWatch"; export * from "./contracts/mcpServer"; export * from "./contracts/skill"; export * from "./contracts/experiment"; +export * from "./contracts/workspace"; diff --git a/src/shared/contracts/project.ts b/src/shared/contracts/project.ts index 1d5fe5281..dc2a85fa8 100644 --- a/src/shared/contracts/project.ts +++ b/src/shared/contracts/project.ts @@ -66,6 +66,11 @@ export const projectSchema = z.object({ worktreeLocation: projectWorktreeLocationSchema.optional(), /** Project MCP entries override global entries by name (case-insensitive). */ mcpServers: mcpServerListSchema.optional(), + /** + * Workspace this project belongs to (see `./workspace.ts`). Absent or dangling + * means "unfiled" — such a project stays visible in every workspace. + */ + workspaceId: z.string().optional(), disabled: z.boolean().optional(), createdAt: z.string().min(1), }); diff --git a/src/shared/contracts/workspace.test.ts b/src/shared/contracts/workspace.test.ts new file mode 100644 index 000000000..06e52da41 --- /dev/null +++ b/src/shared/contracts/workspace.test.ts @@ -0,0 +1,77 @@ +import { describe, expect, test } from "vitest"; +import { + isProjectInWorkspace, + nextWorkspaceIconId, + workspaceListSchema, + workspaceSchema, +} from "./workspace"; + +const KNOWN = new Set(["ws-work", "ws-side"]); + +describe("workspaceSchema", () => { + test("accepts a workspace and rejects a blank name", () => { + expect( + workspaceSchema.parse({ id: "ws-work", name: "Work", createdAt: "2026-01-01T00:00:00.000Z" }), + ).toEqual({ + id: "ws-work", + name: "Work", + createdAt: "2026-01-01T00:00:00.000Z", + icon: "briefcase", + }); + expect(workspaceSchema.safeParse({ id: "ws-work", name: "", createdAt: "t" }).success).toBe( + false, + ); + }); + + test("migrates legacy workspaces to distinct icons", () => { + const workspaces = workspaceListSchema.parse([ + { id: "ws-work", name: "Work", createdAt: "2026-01-01T00:00:00.000Z" }, + { id: "ws-side", name: "Side Hustle", createdAt: "2026-01-01T00:00:00.000Z" }, + ]); + + expect(workspaces.map((workspace) => workspace.icon)).toEqual(["briefcase", "rocket"]); + expect(nextWorkspaceIconId(workspaces)).toBe("palette"); + }); + + test("repairs duplicate saved icons", () => { + const workspaces = workspaceListSchema.parse([ + { + id: "ws-work", + name: "Work", + createdAt: "2026-01-01T00:00:00.000Z", + icon: "rocket", + }, + { + id: "ws-side", + name: "Side Hustle", + createdAt: "2026-01-01T00:00:00.000Z", + icon: "rocket", + }, + ]); + + expect(workspaces.map((workspace) => workspace.icon)).toEqual(["rocket", "briefcase"]); + }); +}); + +describe("isProjectInWorkspace", () => { + test("matches only the active workspace", () => { + expect(isProjectInWorkspace({ workspaceId: "ws-work" }, "ws-work", KNOWN)).toBe(true); + expect(isProjectInWorkspace({ workspaceId: "ws-side" }, "ws-work", KNOWN)).toBe(false); + }); + + test("an unfiled project stays visible in every workspace", () => { + expect(isProjectInWorkspace({}, "ws-work", KNOWN)).toBe(true); + expect(isProjectInWorkspace({ workspaceId: undefined }, "ws-side", KNOWN)).toBe(true); + }); + + test("a dangling workspace id stays visible rather than hiding the project", () => { + // The workspace was deleted on another device; hiding the project would make + // it unreachable from the sidebar with no way to recover it. + expect(isProjectInWorkspace({ workspaceId: "ws-deleted" }, "ws-work", KNOWN)).toBe(true); + }); + + test("no active workspace shows unfiled projects but not filed ones", () => { + expect(isProjectInWorkspace({}, null, KNOWN)).toBe(true); + expect(isProjectInWorkspace({ workspaceId: "ws-work" }, null, KNOWN)).toBe(false); + }); +}); diff --git a/src/shared/contracts/workspace.ts b/src/shared/contracts/workspace.ts new file mode 100644 index 000000000..2a2026618 --- /dev/null +++ b/src/shared/contracts/workspace.ts @@ -0,0 +1,102 @@ +import { z } from "zod"; +import { isHomeProjectId } from "../homeScope"; + +/** + * A user-defined grouping of projects (e.g. "Work", "Side Hustle"). Exactly one + * workspace is active at a time; the sidebar, schedules, and pull-request views + * only surface projects belonging to it. + * + * Distinct from the account/stats "profile" (`./profile.ts`) and from Claude + * auth profiles (`./agentInstance.ts`) — those describe *who* is running the + * agent, a workspace describes *which projects* are in view. + */ +export const WORKSPACE_ICON_IDS = [ + "briefcase", + "rocket", + "palette", + "code", + "building", + "graduation", + "heart", + "sprout", + "globe", + "lightbulb", + "music", + "gamepad", +] as const; + +export const workspaceIconIdSchema = z.enum(WORKSPACE_ICON_IDS); +export type WorkspaceIconId = z.infer; + +export interface Workspace { + id: string; + name: string; + createdAt: string; + icon: WorkspaceIconId; +} + +const workspaceInputSchema = z.object({ + id: z.string().min(1), + name: z.string().min(1), + createdAt: z.string().min(1), + icon: workspaceIconIdSchema.optional(), +}); + +function firstAvailableWorkspaceIcon(used: ReadonlySet): WorkspaceIconId { + return WORKSPACE_ICON_IDS.find((icon) => !used.has(icon)) ?? WORKSPACE_ICON_IDS[0]; +} + +function normalizeWorkspaceIcons( + workspaces: readonly z.input[], +): Workspace[] { + const used = new Set(); + return workspaces.map((workspace) => { + const icon = + workspace.icon && !used.has(workspace.icon) + ? workspace.icon + : firstAvailableWorkspaceIcon(used); + used.add(icon); + return { ...workspace, icon }; + }); +} + +export const workspaceSchema = workspaceInputSchema.transform( + (workspace): Workspace => normalizeWorkspaceIcons([workspace])[0]!, +); + +export const workspaceListSchema = z.array(workspaceInputSchema).transform(normalizeWorkspaceIcons); + +/** Names seeded on first run; the first entry starts active and adopts every existing project. */ +export const DEFAULT_WORKSPACE_NAMES = ["Work", "Side Hustle"] as const; +export const DEFAULT_WORKSPACE_ICONS = ["briefcase", "rocket"] as const satisfies readonly [ + WorkspaceIconId, + WorkspaceIconId, +]; + +/** Pick a distinct identity for a newly-created workspace whenever the palette has room. */ +export function nextWorkspaceIconId(workspaces: readonly Workspace[]): WorkspaceIconId { + return firstAvailableWorkspaceIcon(new Set(workspaces.map((workspace) => workspace.icon))); +} + +/** + * The single membership rule every workspace-scoped surface goes through — the + * sidebar, the schedules view, and the pull-request view must agree on what "in + * this workspace" means, or a project's threads and its PRs disagree about + * whether it exists. + * + * A project whose `workspaceId` is absent or points at a workspace that no + * longer exists stays visible rather than vanishing — losing track of a project + * is far worse than showing it in the wrong group, and the Workspaces settings + * section lets the user file it deliberately. Home is synthetic and never filed, + * so it belongs to every workspace by the same rule. + */ +export function isProjectInWorkspace( + project: { id?: string; workspaceId?: string | undefined }, + activeWorkspaceId: string | null, + knownWorkspaceIds: ReadonlySet, +): boolean { + if (isHomeProjectId(project.id)) return true; + const assigned = project.workspaceId; + if (!assigned || !knownWorkspaceIds.has(assigned)) return true; + return assigned === activeWorkspaceId; +} diff --git a/src/shared/settings.ts b/src/shared/settings.ts index fd7cc49fa..b2c2c621b 100644 --- a/src/shared/settings.ts +++ b/src/shared/settings.ts @@ -19,6 +19,7 @@ import { threadRemoveActionSchema, worktreeStorageModeSchema, mcpServerListSchema, + workspaceListSchema, } from "./contracts"; import { DEFAULT_SEARCH_EXCLUDE } from "./searchExclude"; import { AI_LANGUAGE_VALUES, LOCALE_SETTING_VALUES } from "./locale"; @@ -394,6 +395,16 @@ export const sharedSettingsSchema = z.object({ * WebSocket-connected foreground app still shows full detail. */ remotePushRedactContent: z.boolean(), + /** + * User-defined project groupings ("Work", "Side Hustle", …), newest last. + * Which one is *active* is not stored here but per-window (see the renderer's + * `workspaceStore`), so switching in one window leaves the others alone. + * + * Not part of `remoteSettingsSchema`, so paired clients (mobile PWA) receive no + * workspace list and therefore show every project — add it to that allowlist if + * workspaces should scope remote sessions too. + */ + workspaces: workspaceListSchema, /** User-starred (provider, presentation, model) entries surfaced at the top of the model picker. */ favoriteModels: z.array(modelPickerEntrySchema), /** @@ -546,6 +557,7 @@ export const defaultSharedSettings: SharedSettings = { notifyL2Cli: true, remotePushEnabled: true, remotePushRedactContent: false, + workspaces: [], favoriteModels: [], recentModels: [], disableCliHookPlugin: false,