From 45f0b5bfcbe60d02d11dd2c16eed24993e38901b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Mar 2026 23:59:54 +0000 Subject: [PATCH 01/10] feat(i18n): traduzir strings hardcoded de Settings e Chat para pt-BR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extrai ~190 strings hardcoded (não traduzíveis) para o sistema i18n e adiciona traduções completas em pt-BR. Cobre as áreas de Settings (Den, configurações gerais) e Chat (session, sidebar, inbox, contexto, mensagens). Mudanças: - en.ts / pt-BR.ts: +200 novas chaves nos namespaces den.*, settings.* e session.* (tool headlines, sidebar, inbox, context-panel) - den-settings-panel.tsx: ~80 strings substituídas por t("den.*") - settings.tsx: ~12 strings substituídas por t("settings.*") - workspace-session-list.tsx: ~40 strings substituídas por t("session.*") - sidebar.tsx: ~30 strings substituídas por t("session.sidebar_*") - inbox-panel.tsx: ~20 strings substituídas por t("session.inbox_*") - context-panel.tsx: ~16 strings substituídas por t("session.ctx_*") - message-list.tsx: ~18 strings substituídas por t("session.tool_*") https://claude.ai/code/session_0151gEiEzXtrAMURfJdSaftE --- .../src/app/components/den-settings-panel.tsx | 127 ++++++----- .../app/components/session/context-panel.tsx | 37 ++-- .../app/components/session/inbox-panel.tsx | 45 ++-- .../app/components/session/message-list.tsx | 37 ++-- .../src/app/components/session/sidebar.tsx | 47 ++-- .../session/workspace-session-list.tsx | 63 +++--- apps/app/src/app/pages/settings.tsx | 23 +- apps/app/src/i18n/locales/en.ts | 201 ++++++++++++++++++ apps/app/src/i18n/locales/pt-BR.ts | 201 ++++++++++++++++++ 9 files changed, 590 insertions(+), 191 deletions(-) diff --git a/apps/app/src/app/components/den-settings-panel.tsx b/apps/app/src/app/components/den-settings-panel.tsx index 174fe3421..d9af8003f 100644 --- a/apps/app/src/app/components/den-settings-panel.tsx +++ b/apps/app/src/app/components/den-settings-panel.tsx @@ -15,6 +15,7 @@ import { } from "../lib/den"; import { isDesktopDeployment } from "../lib/openwork-deployment"; import { usePlatform } from "../context/platform"; +import { t } from "../../i18n"; type DenSettingsPanelProps = { developerMode: boolean; @@ -43,18 +44,18 @@ function workerStatusMeta(status: string) { const normalized = status.trim().toLowerCase(); switch (normalized) { case "healthy": - return { label: "Ready", tone: "ready" as const, canOpen: true }; + return { label: t("den.status_ready"), tone: "ready" as const, canOpen: true }; case "provisioning": - return { label: "Provisioning", tone: "warning" as const, canOpen: false }; + return { label: t("den.status_provisioning"), tone: "warning" as const, canOpen: false }; case "failed": - return { label: "Failed", tone: "error" as const, canOpen: false }; + return { label: t("den.status_failed"), tone: "error" as const, canOpen: false }; case "stopped": - return { label: "Stopped", tone: "neutral" as const, canOpen: false }; + return { label: t("den.status_stopped"), tone: "neutral" as const, canOpen: false }; default: return { label: normalized ? `${normalized.slice(0, 1).toUpperCase()}${normalized.slice(1)}` - : "Unknown", + : t("den.status_unknown"), tone: "neutral" as const, canOpen: normalized === "ready", }; @@ -116,10 +117,10 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { }); const summaryLabel = createMemo(() => { - if (authError()) return "Needs attention"; - if (sessionBusy()) return "Checking session"; - if (isSignedIn()) return "Connected"; - return "Signed out"; + if (authError()) return t("den.summary_needs_attention"); + if (sessionBusy()) return t("den.summary_checking"); + if (isSignedIn()) return t("den.summary_connected"); + return t("den.summary_signed_out"); }); createEffect(() => { @@ -152,8 +153,8 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { platform.openLink(target.toString()); setStatusMessage( mode === "sign-up" - ? "Finish account creation in your browser to connect OpenWork." - : "Finish signing in in your browser to connect OpenWork.", + ? t("den.finish_creation") + : t("den.finish_sign_in"), ); setAuthError(null); }; @@ -184,7 +185,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { const applyBaseUrl = () => { const normalized = normalizeDenBaseUrl(baseUrlDraft()); if (!normalized) { - setBaseUrlError("Enter a valid http:// or https:// Den control plane URL."); + setBaseUrlError(t("den.invalid_url")); return; } @@ -197,7 +198,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { setBaseUrl(resolved.baseUrl); setBaseUrlDraft(resolved.baseUrl); - clearSignedInState("Updated the Den control plane URL. Sign in again to continue."); + clearSignedInState(t("den.url_updated")); }; const refreshOrgs = async (quiet = false) => { @@ -219,11 +220,11 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { setActiveOrgId(next); if (!quiet && response.orgs.length > 0) { setStatusMessage( - `Loaded ${response.orgs.length} org${response.orgs.length === 1 ? "" : "s"}.`, + t("den.loaded_orgs").replace("{count}", String(response.orgs.length)), ); } } catch (error) { - setOrgsError(error instanceof Error ? error.message : "Failed to load orgs."); + setOrgsError(error instanceof Error ? error.message : t("den.failed_load_orgs")); } finally { setOrgsBusy(false); } @@ -243,14 +244,15 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { const nextWorkers = await client().listWorkers(orgId, 20); setWorkers(nextWorkers); if (!quiet) { + const orgName = activeOrg()?.name ?? "this org"; setStatusMessage( nextWorkers.length > 0 - ? `Loaded ${nextWorkers.length} worker${nextWorkers.length === 1 ? "" : "s"} for ${activeOrg()?.name ?? "this org"}.` - : `No workers found for ${activeOrg()?.name ?? "this org"}.`, + ? t("den.loaded_workers").replace("{count}", String(nextWorkers.length)).replace("{org}", orgName) + : t("den.no_workers_found").replace("{org}", orgName), ); } } catch (error) { - setWorkersError(error instanceof Error ? error.message : "Failed to load workers."); + setWorkersError(error instanceof Error ? error.message : t("den.failed_load_workers")); } finally { setWorkersBusy(false); } @@ -276,7 +278,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { .then((nextUser) => { if (cancelled) return; setUser(nextUser); - setStatusMessage(`Signed in as ${nextUser.email}.`); + setStatusMessage(t("den.signed_in_as").replace("{email}", nextUser.email)); }) .catch((error) => { if (cancelled) return; @@ -286,7 +288,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { clearSessionState(); } setAuthError( - error instanceof Error ? error.message : "No active Den session found.", + error instanceof Error ? error.message : t("den.no_session"), ); }) .finally(() => { @@ -324,13 +326,13 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { setAuthError(null); setStatusMessage( customEvent.detail.email?.trim() - ? `Connected OpenWork Den as ${customEvent.detail.email.trim()}.` - : "Connected OpenWork Den.", + ? t("den.connected_as").replace("{email}", customEvent.detail.email.trim()) + : t("den.connected"), ); } else if (customEvent.detail?.status === "error") { setAuthError( customEvent.detail.message?.trim() || - "Failed to finish OpenWork Den sign-in.", + t("den.sign_in_failed"), ); } }; @@ -360,15 +362,13 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { setAuthBusy(false); } - clearSignedInState( - "Signed out and cleared your OpenWork Den session on this device.", - ); + clearSignedInState(t("den.signed_out_msg")); }; const handleOpenWorker = async (workerId: string, workerName: string) => { const orgId = activeOrgId().trim(); if (!orgId) { - setWorkersError("Choose an org before opening a worker."); + setWorkersError(t("den.choose_org")); return; } @@ -381,9 +381,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { const accessToken = tokens.ownerToken?.trim() || tokens.clientToken?.trim() || ""; if (!openworkUrl || !accessToken) { - throw new Error( - "Worker is not ready to open yet. Try again after provisioning finishes.", - ); + throw new Error(t("den.worker_not_ready")); } const ok = await props.connectRemoteWorkspace({ @@ -393,13 +391,13 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { displayName: workerName, }); if (!ok) { - throw new Error(`Failed to open ${workerName} in OpenWork.`); + throw new Error(t("den.failed_open_worker").replace("{name}", workerName)); } - setStatusMessage(`Opened ${workerName} in OpenWork.`); + setStatusMessage(t("den.opened_worker").replace("{name}", workerName)); } catch (error) { setWorkersError( - error instanceof Error ? error.message : `Failed to open ${workerName}.`, + error instanceof Error ? error.message : t("den.failed_open").replace("{name}", workerName), ); } finally { setOpeningWorkerId(null); @@ -422,15 +420,14 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
- OpenWork Den + {t("den.title")}
- Sign in, pick an org, and open Den workers from Settings. + {t("den.description")}
- Sign in to OpenWork Den to keep your tasks alive even when your - computer sleeps. + {t("den.tagline")}
@@ -445,11 +442,11 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
setBaseUrlDraft(event.currentTarget.value)} placeholder={DEFAULT_DEN_BASE_URL} - hint="Developer mode only. Use this to target a local or self-hosted Den control plane. Changing it signs you out so the app can re-hydrate against the new control plane." + hint={t("den.control_plane_url_hint")} disabled={authBusy() || sessionBusy()} />
@@ -459,7 +456,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { onClick={() => setBaseUrlDraft(baseUrl())} disabled={authBusy() || sessionBusy()} > - Reset + {t("den.reset")}
@@ -502,17 +499,16 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
- Sign in to OpenWork Den + {t("den.sign_in_title")}
- Sign in to OpenWork Den to keep your tasks alive even when your - computer sleeps. + {t("den.tagline")}
@@ -534,8 +530,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
- Finish auth in your browser and OpenWork will reconnect here - automatically. + {t("den.finish_auth")}
@@ -544,9 +539,9 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
-
Den Account
+
{t("den.account_title")}
- Manage your connected account and organization. + {t("den.account_description")}
@@ -567,15 +562,15 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { disabled={authBusy() || sessionBusy()} > - {authBusy() ? "Signing out..." : "Sign out"} + {authBusy() ? t("den.signing_out") : t("den.sign_out")}
-
Active org
+
{t("den.active_org")}
- Workers are scoped to the selected org. + {t("den.workers_scoped")}
@@ -585,7 +580,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { onChange={(event) => { setActiveOrgId(event.currentTarget.value); setStatusMessage( - `Switched to ${activeOrg()?.name ?? "the selected org"}.`, + t("den.switched_org").replace("{org}", activeOrg()?.name ?? "the selected org"), ); }} disabled={orgsBusy() || orgs().length === 0} @@ -593,7 +588,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { {(org) => ( )} @@ -624,17 +619,16 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
- Den workers + {t("den.workers_title")}
- Open workers directly into OpenWork using the same - remote-connect flow the app already uses elsewhere. + {t("den.workers_description")}
- {activeOrg()?.name || "No org selected"} + {activeOrg()?.name || t("den.no_org_selected")}
@@ -658,8 +652,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
- No cloud workers are visible for this org yet. Create one in - Den, then refresh this tab. + {t("den.no_workers_visible")}
@@ -681,12 +674,12 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { - Mine + {t("den.mine_badge")}
- {worker.provider ? `${worker.provider} worker` : "Cloud worker"} + {worker.provider ? t("den.worker_type").replace("{provider}", worker.provider) : t("den.cloud_worker")} {(value) => · {value()}} @@ -699,9 +692,9 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { void handleOpenWorker(worker.workerId, worker.workerName) } disabled={openingWorkerId() !== null || !status().canOpen} - title={!status().canOpen ? "This worker is not ready to open yet." : undefined} + title={!status().canOpen ? t("den.worker_not_ready_tooltip") : undefined} > - {openingWorkerId() === worker.workerId ? "Opening..." : "Open"} + {openingWorkerId() === worker.workerId ? t("den.opening") : t("den.open")}
); diff --git a/apps/app/src/app/components/session/context-panel.tsx b/apps/app/src/app/components/session/context-panel.tsx index e89ffe40a..beb7905ba 100644 --- a/apps/app/src/app/components/session/context-panel.tsx +++ b/apps/app/src/app/components/session/context-panel.tsx @@ -1,6 +1,7 @@ import { For, Show } from "solid-js"; import { ChevronDown, Circle, File, Folder, Package } from "lucide-solid"; +import { t } from "../../../i18n"; import { SUGGESTED_PLUGINS } from "../../constants"; import type { McpServerEntry, McpStatus, McpStatusMap, SkillCard } from "../../types"; import { stripPluginVersion } from "../../utils/plugins"; @@ -108,19 +109,19 @@ const getSmartFileName = (files: string[], file: string): string => { }; const mcpStatusLabel = (status?: McpStatus, disabled?: boolean) => { - if (disabled) return "Disabled"; - if (!status) return "Disconnected"; + if (disabled) return t("session.ctx_disabled"); + if (!status) return t("session.ctx_disconnected"); switch (status.status) { case "connected": - return "Connected"; + return t("session.ctx_connected"); case "needs_auth": - return "Needs auth"; + return t("session.ctx_needs_auth"); case "needs_client_registration": - return "Register client"; + return t("session.ctx_register_client"); case "failed": - return "Failed"; + return t("session.ctx_failed"); default: - return "Disconnected"; + return t("session.ctx_disconnected"); } }; @@ -152,7 +153,7 @@ export default function ContextPanel(props: ContextPanelProps) { class="w-full px-4 py-3 flex items-center justify-between text-sm text-gray-12 font-medium" onClick={() => props.onToggleSection("context")} > - Context + {t("session.ctx_context")}
- Working files + {t("session.ctx_working_files")}
None yet.
} + fallback={
{t("session.ctx_none_yet")}
} > {(file) => { @@ -204,7 +205,7 @@ export default function ContextPanel(props: ContextPanelProps) { class="w-full px-4 py-3 flex items-center justify-between text-sm text-gray-12 font-medium" onClick={() => props.onToggleSection("plugins")} > - Plugins + {t("session.ctx_plugins")} - {props.activePluginStatus ?? "No plugins loaded."} + {props.activePluginStatus ?? t("session.ctx_no_plugins")}
} > @@ -254,7 +255,7 @@ export default function ContextPanel(props: ContextPanelProps) { class="w-full px-4 py-3 flex items-center justify-between text-sm text-gray-12 font-medium" onClick={() => props.onToggleSection("mcp")} > - MCP + {t("session.ctx_mcp")} - {props.mcpStatus ?? "No MCP servers loaded."} + {props.mcpStatus ?? t("session.ctx_no_mcp")}
} > @@ -304,7 +305,7 @@ export default function ContextPanel(props: ContextPanelProps) { class="w-full px-4 py-3 flex items-center justify-between text-sm text-gray-12 font-medium" onClick={() => props.onToggleSection("skills")} > - Skills + {t("session.ctx_skills")} - {props.skillsStatus ?? "No skills loaded."} + {props.skillsStatus ?? t("session.ctx_no_skills")}
} > @@ -353,7 +354,7 @@ export default function ContextPanel(props: ContextPanelProps) { class="w-full px-4 py-3 flex items-center justify-between text-sm text-gray-12 font-medium" onClick={() => props.onToggleSection("authorizedFolders")} > - Authorized folders + {t("session.ctx_authorized_folders")} None yet.} + fallback={
{t("session.ctx_none_yet")}
} > {(folder) => ( diff --git a/apps/app/src/app/components/session/inbox-panel.tsx b/apps/app/src/app/components/session/inbox-panel.tsx index 0d6b51ef8..48fe44e52 100644 --- a/apps/app/src/app/components/session/inbox-panel.tsx +++ b/apps/app/src/app/components/session/inbox-panel.tsx @@ -1,6 +1,7 @@ import { For, Show, createEffect, createMemo, createSignal } from "solid-js"; import { Download, RefreshCw, UploadCloud } from "lucide-solid"; +import { t } from "../../../i18n"; import { getOpenWorkDeployment } from "../../lib/openwork-deployment"; import type { OpenworkInboxItem, OpenworkServerClient } from "../../lib/openwork-server"; import WebUnavailableSurface from "../web-unavailable-surface"; @@ -48,7 +49,7 @@ export default function InboxPanel(props: InboxPanelProps) { }); const connected = createMemo(() => Boolean(props.client && (props.workspaceId ?? "").trim())); - const helperText = "Share files with this worker from the app."; + const helperText = t("session.inbox_share_hint"); const visibleItems = createMemo(() => (items() ?? []).slice(0, maxPreview())); const hiddenCount = createMemo(() => Math.max(0, (items() ?? []).length - visibleItems().length)); @@ -72,7 +73,7 @@ export default function InboxPanel(props: InboxPanelProps) { setItems(result.items ?? []); } catch (err) { const message = - err instanceof Error ? err.message : "Failed to load shared folder"; + err instanceof Error ? err.message : t("session.inbox_load_failed"); setError(message); setItems([]); } finally { @@ -84,7 +85,7 @@ export default function InboxPanel(props: InboxPanelProps) { const client = props.client; const workspaceId = (props.workspaceId ?? "").trim(); if (!client || !workspaceId) { - toast("Connect to a worker to upload files to the shared folder."); + toast(t("session.inbox_connect_to_upload")); return; } if (!files.length) return; @@ -93,15 +94,15 @@ export default function InboxPanel(props: InboxPanelProps) { setError(null); try { const label = files.length === 1 ? files[0]?.name ?? "file" : `${files.length} files`; - toast(`Uploading ${label}...`); + toast(t("session.inbox_uploading").replace("{label}", label)); for (const file of files) { await client.uploadInbox(workspaceId, file); } - toast("Uploaded to the shared folder."); + toast(t("session.inbox_uploaded")); await refresh(); } catch (err) { const message = - err instanceof Error ? err.message : "Shared folder upload failed"; + err instanceof Error ? err.message : t("session.inbox_upload_failed"); setError(message); toast(message); } finally { @@ -115,7 +116,7 @@ export default function InboxPanel(props: InboxPanelProps) { await navigator.clipboard.writeText(path); toast(`Copied: ${path}`); } catch { - toast("Copy failed. Your browser may block clipboard access."); + toast(t("session.inbox_copy_failed")); } }; @@ -123,12 +124,12 @@ export default function InboxPanel(props: InboxPanelProps) { const client = props.client; const workspaceId = (props.workspaceId ?? "").trim(); if (!client || !workspaceId) { - toast("Connect to a worker to download shared files."); + toast(t("session.inbox_connect_to_download")); return; } const id = String(item.id ?? "").trim(); if (!id) { - toast("Missing shared file id."); + toast(t("session.inbox_missing_file_id")); return; } @@ -144,7 +145,7 @@ export default function InboxPanel(props: InboxPanelProps) { a.remove(); URL.revokeObjectURL(url); } catch (err) { - const message = err instanceof Error ? err.message : "Download failed"; + const message = err instanceof Error ? err.message : t("session.inbox_download_failed"); toast(message); } }; @@ -160,7 +161,7 @@ export default function InboxPanel(props: InboxPanelProps) {
- Shared folder + {t("session.inbox_shared_folder")}
0}> @@ -171,8 +172,8 @@ export default function InboxPanel(props: InboxPanelProps) { type="button" class="rounded-md p-1 text-gray-9 hover:text-gray-11 hover:bg-gray-3 transition-colors" onClick={() => void refresh()} - title="Refresh shared folder" - aria-label="Refresh shared folder" + title={t("session.inbox_refresh")} + aria-label={t("session.inbox_refresh")} disabled={!connected() || loading()} > @@ -214,12 +215,12 @@ export default function InboxPanel(props: InboxPanelProps) { if (files.length) void uploadFiles(files); }} disabled={uploading()} - title={connected() ? "Drop files here to upload" : "Connect to a worker to upload"} + title={connected() ? t("session.inbox_drop_to_upload") : t("session.inbox_connect_upload")} >
- {uploading() ? "Uploading..." : "Drop files or click to upload"} + {uploading() ? t("session.inbox_uploading_label") : t("session.inbox_drop_or_click")} {helperText}
@@ -234,8 +235,8 @@ export default function InboxPanel(props: InboxPanelProps) { when={visibleItems().length > 0} fallback={
- - No shared files yet. + + {t("session.inbox_no_files")}
} @@ -253,8 +254,8 @@ export default function InboxPanel(props: InboxPanelProps) { type="button" class="min-w-0 flex-1 text-left" onClick={() => void copyPath(item)} - title={rel() ? `Copy ${INBOX_PREFIX}${rel()}` : "Copy shared folder path"} - aria-label={rel() ? `Copy ${INBOX_PREFIX}${rel()}` : "Copy shared folder path"} + title={rel() ? `${t("session.inbox_copy_path")}: ${INBOX_PREFIX}${rel()}` : t("session.inbox_copy_path")} + aria-label={rel() ? `${t("session.inbox_copy_path")}: ${INBOX_PREFIX}${rel()}` : t("session.inbox_copy_path")} disabled={!connected()} >
{name()}
@@ -275,8 +276,8 @@ export default function InboxPanel(props: InboxPanelProps) { type="button" class="shrink-0 rounded-md p-1 text-gray-9 opacity-0 group-hover:opacity-100 hover:text-gray-11 hover:bg-gray-3" onClick={() => void downloadItem(item)} - title="Download" - aria-label="Download" + title={t("session.inbox_download")} + aria-label={t("session.inbox_download")} disabled={!connected()} > @@ -288,7 +289,7 @@ export default function InboxPanel(props: InboxPanelProps) {
0}> -
Showing first {maxPreview()}.
+
{t("session.inbox_showing_first").replace("{count}", String(maxPreview()))}
diff --git a/apps/app/src/app/components/session/message-list.tsx b/apps/app/src/app/components/session/message-list.tsx index 9226ebb43..b201a2442 100644 --- a/apps/app/src/app/components/session/message-list.tsx +++ b/apps/app/src/app/components/session/message-list.tsx @@ -31,6 +31,7 @@ import { } from "../../utils"; import PartView from "../part-view"; import { perfNow, recordPerfLog } from "../../lib/perf-log"; +import { t } from "../../../i18n"; export type MessageListProps = { messages: MessageWithParts[]; @@ -228,57 +229,57 @@ function toolHeadline(part: Part) { const description = pick("description"); if (description) return compactText(description); const command = pick("command", "cmd"); - return command ? compactText(`Run ${command}`, 48) : "Run command"; + return command ? compactText(t("session.tool_run_command").replace("{cmd}", command), 48) : t("session.tool_run_command_fallback"); } if (tool === "read") { const file = target("filePath", "path", "file"); - return file ? `Reviewed ${file}` : "Reviewed file"; + return file ? t("session.tool_reviewed_file").replace("{file}", file) : t("session.tool_reviewed_file_fallback"); } if (tool === "edit") { const file = target("filePath", "path", "file"); - return file ? `Updated ${file}` : "Updated file"; + return file ? t("session.tool_updated_file").replace("{file}", file) : t("session.tool_updated_file_fallback"); } if (tool === "write" || tool === "apply_patch") { const file = target("filePath", "path", "file"); - return file ? `Update ${file}` : "Update file"; + return file ? t("session.tool_update_file").replace("{file}", file) : t("session.tool_update_file_fallback"); } if (tool === "grep" || tool === "glob" || tool === "search") { const pattern = pick("pattern", "query"); - return pattern ? `Searched ${compactText(pattern, 36)}` : "Searched code"; + return pattern ? t("session.tool_searched").replace("{pattern}", compactText(pattern, 36)) : t("session.tool_searched_fallback"); } if (tool === "list" || tool === "list_files") { const path = target("path"); - return path ? `Reviewed ${path}` : "Reviewed files"; + return path ? t("session.tool_reviewed_files").replace("{path}", path) : t("session.tool_reviewed_files_fallback"); } if (tool === "task") { const description = pick("description"); if (description) return compactText(description); const agent = pick("subagent_type"); - return agent ? `Delegate ${agent}` : "Delegate task"; + return agent ? t("session.tool_delegate").replace("{agent}", agent) : t("session.tool_delegate_fallback"); } if (tool === "todowrite") { - return "Update todo list"; + return t("session.tool_update_todo"); } if (tool === "todoread") { - return "Read todo list"; + return t("session.tool_read_todo"); } if (tool === "webfetch") { const url = pick("url"); - return url ? `Checked ${compactText(url, 36)}` : "Checked web page"; + return url ? t("session.tool_checked_url").replace("{url}", compactText(url, 36)) : t("session.tool_checked_url_fallback"); } if (tool === "skill") { const name = pick("name"); - return name ? `Load skill ${name}` : "Load skill"; + return name ? t("session.tool_load_skill").replace("{name}", name) : t("session.tool_load_skill_fallback"); } const fallback = tool @@ -697,16 +698,16 @@ export default function MessageList(props: MessageListProps) { if (title) return title; if (task().description) return task().description!; if (task().agentType) return `${task().agentType} task`; - return "Subagent session"; + return t("session.subagent_session"); }); const statusLabel = createMemo(() => { - if (loading()) return "Loading transcript"; - if (streaming()) return "Running"; + if (loading()) return t("session.loading_transcript"); + if (streaming()) return t("session.subagent_running"); if (childMessages().length > 0) { const count = childMessages().length; return `${count} message${count === 1 ? "" : "s"}`; } - return "Waiting for transcript"; + return t("session.waiting_transcript"); }); createEffect(() => { @@ -754,7 +755,7 @@ export default function MessageList(props: MessageListProps) {
0} - fallback={
Waiting for the subagent transcript to arrive.
} + fallback={
{t("session.waiting_subagent")}
} >
-
Request
+
{t("session.request_label")}
{formatStructuredValue(toolInput())}
-
Result
+
{t("session.result_label")}
{formatStructuredValue(toolOutput())}
diff --git a/apps/app/src/app/components/session/sidebar.tsx b/apps/app/src/app/components/session/sidebar.tsx index b95f17ba8..b95536f32 100644 --- a/apps/app/src/app/components/session/sidebar.tsx +++ b/apps/app/src/app/components/session/sidebar.tsx @@ -3,6 +3,7 @@ import { Check, ChevronDown, GripVertical, Loader2, Plus, RefreshCcw, Settings, import type { TodoItem, WorkspaceConnectionState } from "../../types"; import type { WorkspaceInfo } from "../../lib/tauri"; +import { t } from "../../../i18n"; type SessionSummary = { id: string; @@ -283,21 +284,21 @@ export default function SessionSidebar(props: SidebarProps) { disabled={props.newTaskDisabled} > - New task + {t("session.sidebar_new_task")}
-
Workspaces
+
{t("session.sidebar_workspaces")}
0} fallback={
- No workspaces in this session yet. Add one to get started. + {t("session.sidebar_no_workspaces")}
} > @@ -373,7 +374,7 @@ export default function SessionSidebar(props: SidebarProps) { - {isSandboxWorkspace() ? "Sandbox" : "Remote"} + {isSandboxWorkspace() ? t("session.sidebar_sandbox") : t("session.sidebar_remote")}
@@ -390,11 +391,11 @@ export default function SessionSidebar(props: SidebarProps) { - Needs attention + {t("session.sidebar_needs_attention")} - Switch}> - Active + {t("session.sidebar_switch")}}> + {t("session.sidebar_active")} @@ -406,7 +407,7 @@ export default function SessionSidebar(props: SidebarProps) { type="button" class="p-1 rounded-md text-gray-9 hover:text-gray-12 hover:bg-gray-2" onClick={() => toggleWorkspaceCollapse(group.workspace.id)} - title={collapsed() ? "Expand" : "Collapse"} + title={collapsed() ? t("session.sidebar_expand") : t("session.sidebar_collapse")} > handleDragStart(event, group.workspace.id)} onDragEnd={handleDragEnd} @@ -441,7 +442,7 @@ export default function SessionSidebar(props: SidebarProps) { disabled={isActivelyConnecting()} > - Edit connection + {t("session.sidebar_edit_connection")} @@ -461,7 +462,7 @@ export default function SessionSidebar(props: SidebarProps) { disabled={isActivelyConnecting()} > - Stop sandbox + {t("session.sidebar_stop_sandbox")}
0} fallback={
- No sessions yet. + {t("session.sidebar_no_sessions")}
} > @@ -535,8 +536,8 @@ export default function SessionSidebar(props: SidebarProps) { onClick={() => toggleShowAllSessions(group.workspace.id)} > {showingAll() - ? "Show fewer" - : `Show ${sessions().length - MAX_SESSIONS_PREVIEW} more`} + ? t("session.sidebar_show_fewer") + : t("session.sidebar_show_more").replace("{count}", String(sessions().length - MAX_SESSIONS_PREVIEW))}
@@ -557,7 +558,7 @@ export default function SessionSidebar(props: SidebarProps) { onDrop={(event) => handleDrop(event, null)} > - Add new workspace + {t("session.sidebar_add_workspace")}
@@ -570,7 +571,7 @@ export default function SessionSidebar(props: SidebarProps) { }} > - New worker + {t("session.sidebar_new_worker")}
@@ -608,7 +609,7 @@ export default function SessionSidebar(props: SidebarProps) { class="w-full px-4 py-3 flex items-center justify-between text-sm text-gray-12 font-medium" onClick={() => props.onToggleSection("progress")} > - Progress + {t("session.sidebar_progress")} - New task + {t("session.sidebar_new_task_menu")}
diff --git a/apps/app/src/app/components/session/workspace-session-list.tsx b/apps/app/src/app/components/session/workspace-session-list.tsx index 4d546de96..b00ec4da4 100644 --- a/apps/app/src/app/components/session/workspace-session-list.tsx +++ b/apps/app/src/app/components/session/workspace-session-list.tsx @@ -27,6 +27,7 @@ import { getWorkspaceTaskLoadErrorDisplay, isWindowsPlatform, } from "../../utils"; +import { t } from "../../../i18n"; type Props = { workspaceSessionGroups: WorkspaceSessionGroup[]; @@ -166,16 +167,16 @@ const workspaceLabel = (workspace: WorkspaceInfo) => workspace.openworkWorkspaceName?.trim() || workspace.name?.trim() || workspace.path?.trim() || - "Workspace"; + t("session.workspace_default"); const workspaceKindLabel = (workspace: WorkspaceInfo) => workspace.workspaceType === "remote" ? workspace.sandboxBackend === "docker" || Boolean(workspace.sandboxRunId?.trim()) || Boolean(workspace.sandboxContainerName?.trim()) - ? "Sandbox" - : "Remote" - : "Local"; + ? t("session.kind_sandbox") + : t("session.kind_remote") + : t("session.kind_local"); const WORKSPACE_SWATCHES = ["#2563eb", "#5a67d8", "#f97316", "#10b981"]; @@ -191,8 +192,8 @@ const workspaceSwatchColor = (seed: string) => { export default function WorkspaceSessionList(props: Props) { const revealLabel = isWindowsPlatform() - ? "Reveal in Explorer" - : "Reveal in Finder"; + ? t("session.reveal_explorer") + : t("session.reveal_finder"); const newWorkspaceDesktopOnly = getOpenWorkDeployment() === "web"; const [expandedWorkspaceIds, setExpandedWorkspaceIds] = createSignal< Set @@ -296,7 +297,7 @@ export default function WorkspaceSessionList(props: Props) { const showMoreLabel = (workspaceId: string, totalRoots: number) => { const remaining = Math.max(0, totalRoots - previewCount(workspaceId)); const nextCount = Math.min(MAX_SESSIONS_PREVIEW, remaining); - return nextCount > 0 ? `Show ${nextCount} more` : "Show more"; + return nextCount > 0 ? t("session.show_more").replace("{count}", String(nextCount)) : t("session.show_more_generic"); }; createEffect(() => { @@ -402,7 +403,7 @@ export default function WorkspaceSessionList(props: Props) {
@@ -473,7 +474,7 @@ export default function WorkspaceSessionList(props: Props) { props.onOpenDeleteSession?.(); }} > - Delete session + {t("session.delete_session")}
@@ -515,9 +516,9 @@ export default function WorkspaceSessionList(props: Props) { getWorkspaceTaskLoadErrorDisplay(workspace(), group.error); const statusLabel = () => { if (group.status === "error") return taskLoadError().label; - if (isConnectionActionBusy()) return "Connecting"; + if (isConnectionActionBusy()) return t("session.status_connecting"); if (!props.developerMode) return ""; - if (props.activeWorkspaceId === workspace().id) return "Active"; + if (props.activeWorkspaceId === workspace().id) return t("session.status_active"); return workspaceKindLabel(workspace()); }; const statusTone = () => { @@ -591,7 +592,7 @@ export default function WorkspaceSessionList(props: Props) { props.onCreateTaskInWorkspace(workspace().id); }} disabled={props.newTaskDisabled} - aria-label="New task" + aria-label={t("session.new_task_label")} > @@ -607,7 +608,7 @@ export default function WorkspaceSessionList(props: Props) { : workspace().id, ); }} - aria-label="Workspace options" + aria-label={t("session.workspace_options")} > @@ -618,8 +619,8 @@ export default function WorkspaceSessionList(props: Props) { class="rounded-md p-1 text-gray-9 hover:bg-gray-3/80 hover:text-gray-11" aria-label={ isWorkspaceExpanded(workspace().id) - ? "Collapse" - : "Expand" + ? t("session.collapse") + : t("session.expand") } onClick={(event) => { event.stopPropagation(); @@ -650,7 +651,7 @@ export default function WorkspaceSessionList(props: Props) { setWorkspaceMenuId(null); }} > - Edit name + {t("session.edit_name")} @@ -809,10 +810,10 @@ export default function WorkspaceSessionList(props: Props) { disabled={props.newTaskDisabled} > - No tasks yet. + {t("session.no_tasks_yet")} @@ -843,7 +844,7 @@ export default function WorkspaceSessionList(props: Props) { } >
- Loading tasks... + {t("session.loading_tasks")}
@@ -866,7 +867,7 @@ export default function WorkspaceSessionList(props: Props) { onClick={() => setAddWorkspaceMenuOpen((prev) => !prev)} > - Add workspace + {t("session.add_workspace")} @@ -881,7 +882,7 @@ export default function WorkspaceSessionList(props: Props) { disabled={newWorkspaceDesktopOnly} title={ newWorkspaceDesktopOnly - ? "Create local workspaces in the desktop app." + ? t("session.create_local_desktop_only") : undefined } onClick={() => { @@ -890,7 +891,7 @@ export default function WorkspaceSessionList(props: Props) { }} > - New workspace + {t("session.new_workspace")} @@ -904,7 +905,7 @@ export default function WorkspaceSessionList(props: Props) { }} > - Connect remote workspace + {t("session.connect_remote_workspace")} diff --git a/apps/app/src/app/pages/settings.tsx b/apps/app/src/app/pages/settings.tsx index 51c00e21f..f723a32f0 100644 --- a/apps/app/src/app/pages/settings.tsx +++ b/apps/app/src/app/pages/settings.tsx @@ -229,11 +229,10 @@ export function OpenCodeRouterSettings(_props: {
-
Messaging
+
{t("settings.messaging")}
- Manage Telegram/Slack identities and bindings in the{" "} - Identities tab. + {t("settings.identities_tab_hint")}
); @@ -1537,7 +1536,7 @@ export default function SettingsView(props: SettingsViewProps) {
-
No external folders authorized
+
{t("settings.no_authorized_folders")}
Add a folder to let this workspace read and edit files outside its root directory.
@@ -1611,7 +1610,7 @@ export default function SettingsView(props: SettingsViewProps) { onPaste={(event) => { event.preventDefault(); }} - placeholder="Type a folder path to authorize..." + placeholder={t("settings.folder_path_placeholder")} disabled={ props.authorizedFoldersLoading || props.authorizedFoldersSaving || @@ -1808,7 +1807,7 @@ export default function SettingsView(props: SettingsViewProps) {
-
Hide titlebar
+
{t("settings.hide_titlebar")}
Hide the window titlebar. Useful for tiling window managers on Linux (Hyprland, i3, sway). @@ -1866,7 +1865,7 @@ export default function SettingsView(props: SettingsViewProps) {
-
Show model reasoning
+
{t("settings.show_model_reasoning")}
Expand reasoning traces in the UI when a model exposes them.
@@ -1902,7 +1901,7 @@ export default function SettingsView(props: SettingsViewProps) {
-
Model behavior
+
{t("settings.model_behavior")}
Open the default model picker to choose reasoning profiles when they are available.
@@ -1927,7 +1926,7 @@ export default function SettingsView(props: SettingsViewProps) {
-
Runtime
+
{t("settings.runtime")}
Status for your local engine and OpenWork server.
@@ -1993,7 +1992,7 @@ export default function SettingsView(props: SettingsViewProps) {
-
Enable Exa web search
+
{t("settings.enable_exa_search")}
Applies when OpenWork Orchestrator launches OpenCode. Off by default until the integration is fully rolled out. @@ -2015,7 +2014,7 @@ export default function SettingsView(props: SettingsViewProps) {
-
Developer mode
+
{t("settings.developer_mode")}
Enables debug tools, diagnostics, and the Developer tab.
@@ -2100,7 +2099,7 @@ export default function SettingsView(props: SettingsViewProps) { setDebugDeepLinkInput(event.currentTarget.value) } rows={3} - placeholder="openwork://..." + placeholder={t("settings.scheme_placeholder")} class="w-full rounded-xl border border-gray-6 bg-gray-1 px-3 py-2 text-xs font-mono text-gray-12 outline-none transition focus:border-blue-8" />
diff --git a/apps/app/src/i18n/locales/en.ts b/apps/app/src/i18n/locales/en.ts index 455c71c29..d6a92ff8d 100644 --- a/apps/app/src/i18n/locales/en.ts +++ b/apps/app/src/i18n/locales/en.ts @@ -858,4 +858,205 @@ export default { "app.error.command_name_template_required": "Command name and instructions are required.", "app.error.workspace_commands_desktop": "Commands require the desktop app.", "app.error.command_scope_unknown": "This command can't be managed in this mode.", + + // ==================== Den Settings ==================== + "den.status_ready": "Ready", + "den.status_provisioning": "Provisioning", + "den.status_failed": "Failed", + "den.status_stopped": "Stopped", + "den.status_unknown": "Unknown", + "den.summary_needs_attention": "Needs attention", + "den.summary_checking": "Checking session", + "den.summary_connected": "Connected", + "den.summary_signed_out": "Signed out", + "den.finish_creation": "Finish account creation in your browser to connect OpenWork.", + "den.finish_sign_in": "Finish signing in in your browser to connect OpenWork.", + "den.invalid_url": "Enter a valid http:// or https:// Den control plane URL.", + "den.url_updated": "Updated the Den control plane URL. Sign in again to continue.", + "den.loaded_orgs": "Loaded {count} org(s).", + "den.failed_load_orgs": "Failed to load orgs.", + "den.loaded_workers": "Loaded {count} worker(s) for {org}.", + "den.no_workers_found": "No workers found for {org}.", + "den.failed_load_workers": "Failed to load workers.", + "den.signed_in_as": "Signed in as {email}.", + "den.no_session": "No active Den session found.", + "den.connected_as": "Connected OpenWork Den as {email}.", + "den.connected": "Connected OpenWork Den.", + "den.sign_in_failed": "Failed to finish OpenWork Den sign-in.", + "den.signed_out_msg": "Signed out and cleared your OpenWork Den session on this device.", + "den.choose_org": "Choose an org before opening a worker.", + "den.worker_not_ready": "Worker is not ready to open yet. Try again after provisioning finishes.", + "den.failed_open_worker": "Failed to open {name} in OpenWork.", + "den.opened_worker": "Opened {name} in OpenWork.", + "den.failed_open": "Failed to open {name}.", + "den.title": "OpenWork Den", + "den.description": "Sign in, pick an org, and open Den workers from Settings.", + "den.tagline": "Sign in to OpenWork Den to keep your tasks alive even when your computer sleeps.", + "den.control_plane_url_label": "Den control plane URL", + "den.control_plane_url_hint": "Developer mode only. Use this to target a local or self-hosted Den control plane. Changing it signs you out so the app can re-hydrate against the new control plane.", + "den.reset": "Reset", + "den.save_url": "Save URL", + "den.open_in_browser": "Open in browser", + "den.sign_in_title": "Sign in to OpenWork Den", + "den.sign_in": "Sign in", + "den.create_account": "Create account", + "den.finish_auth": "Finish auth in your browser and OpenWork will reconnect here automatically.", + "den.account_title": "Den Account", + "den.account_description": "Manage your connected account and organization.", + "den.signing_out": "Signing out...", + "den.sign_out": "Sign out", + "den.active_org": "Active org", + "den.workers_scoped": "Workers are scoped to the selected org.", + "den.switched_org": "Switched to {org}.", + "den.owner_suffix": "(Owner)", + "den.member_suffix": "(Member)", + "den.workers_title": "Den workers", + "den.workers_description": "Open workers directly into OpenWork using the same remote-connect flow.", + "den.no_org_selected": "No org selected", + "den.refresh": "Refresh", + "den.no_workers_visible": "No cloud workers are visible for this org yet. Create one in Den, then refresh this tab.", + "den.mine_badge": "Mine", + "den.worker_type": "{provider} worker", + "den.cloud_worker": "Cloud worker", + "den.worker_not_ready_tooltip": "This worker is not ready to open yet.", + "den.opening": "Opening...", + "den.open": "Open", + + // ==================== Settings (additional) ==================== + "settings.messaging": "Messaging", + "settings.identities_tab_hint": "Manage Telegram/Slack identities and bindings in the Identities tab", + "settings.identities": "Identities", + "settings.no_authorized_folders": "No external folders authorized", + "settings.folder_path_placeholder": "Type a folder path to authorize...", + "settings.hide_titlebar": "Hide titlebar", + "settings.show_model_reasoning": "Show model reasoning", + "settings.model_behavior": "Model behavior", + "settings.runtime": "Runtime", + "settings.enable_exa_search": "Enable Exa web search", + "settings.developer_mode": "Developer mode", + "settings.scheme_placeholder": "openwork://...", + + // ==================== Session (additional) ==================== + "session.workspace_default": "Workspace", + "session.kind_sandbox": "Sandbox", + "session.kind_remote": "Remote", + "session.kind_local": "Local", + "session.reveal_finder": "Reveal in Finder", + "session.reveal_explorer": "Reveal in Explorer", + "session.hide_child_sessions": "Hide child sessions", + "session.show_child_sessions": "Show child sessions", + "session.session_actions": "Session actions", + "session.rename_session": "Rename session", + "session.delete_session": "Delete session", + "session.status_connecting": "Connecting", + "session.status_active": "Active", + "session.new_task_label": "New task", + "session.workspace_options": "Workspace options", + "session.collapse": "Collapse", + "session.expand": "Expand", + "session.edit_name": "Edit name", + "session.share": "Share...", + "session.recover": "Recover", + "session.test_connection": "Test connection", + "session.edit_connection": "Edit connection", + "session.remove_workspace": "Remove workspace", + "session.no_tasks_yet": "No tasks yet.", + "session.new_task_hover": "+ New task", + "session.show_more": "Show {count} more", + "session.show_more_generic": "Show more", + "session.loading_tasks": "Loading tasks...", + "session.add_workspace": "Add workspace", + "session.create_local_desktop_only": "Create local workspaces in the desktop app.", + "session.new_workspace": "New workspace", + "session.connect_remote_workspace": "Connect remote workspace", + "session.import_config": "Import config", + "session.sidebar_new_task": "New task", + "session.sidebar_workspaces": "Workspaces", + "session.sidebar_no_workspaces": "No workspaces in this session yet. Add one to get started.", + "session.sidebar_sandbox": "Sandbox", + "session.sidebar_remote": "Remote", + "session.sidebar_needs_attention": "Needs attention", + "session.sidebar_switch": "Switch", + "session.sidebar_active": "Active", + "session.sidebar_expand": "Expand", + "session.sidebar_collapse": "Collapse", + "session.sidebar_drag_reorder": "Drag to reorder", + "session.sidebar_edit_connection": "Edit connection", + "session.sidebar_test_connection": "Test connection", + "session.sidebar_stop_sandbox": "Stop sandbox", + "session.sidebar_remove": "Remove", + "session.sidebar_no_sessions": "No sessions yet.", + "session.sidebar_show_fewer": "Show fewer", + "session.sidebar_show_more": "Show {count} more", + "session.sidebar_add_workspace": "Add new workspace", + "session.sidebar_new_worker": "New worker", + "session.sidebar_connect_remote": "Connect remote", + "session.sidebar_import_config": "Import config", + "session.sidebar_progress": "Progress", + "session.sidebar_new_task_menu": "New task", + "session.sidebar_delete_session": "Delete session", + "session.inbox_share_hint": "Share files with this worker from the app.", + "session.inbox_load_failed": "Failed to load shared folder", + "session.inbox_connect_to_upload": "Connect to a worker to upload files to the shared folder.", + "session.inbox_uploading": "Uploading {label}...", + "session.inbox_uploaded": "Uploaded to the shared folder.", + "session.inbox_upload_failed": "Shared folder upload failed", + "session.inbox_copy_failed": "Copy failed. Your browser may block clipboard access.", + "session.inbox_connect_to_download": "Connect to a worker to download shared files.", + "session.inbox_missing_file_id": "Missing shared file id.", + "session.inbox_download_failed": "Download failed", + "session.inbox_shared_folder": "Shared folder", + "session.inbox_refresh": "Refresh shared folder", + "session.inbox_drop_to_upload": "Drop files here to upload", + "session.inbox_connect_upload": "Connect to a worker to upload", + "session.inbox_uploading_label": "Uploading...", + "session.inbox_drop_or_click": "Drop files or click to upload", + "session.inbox_connect_to_see": "Connect to see shared files.", + "session.inbox_no_files": "No shared files yet.", + "session.inbox_copy_path": "Copy shared folder path", + "session.inbox_download": "Download", + "session.inbox_showing_first": "Showing first {count}.", + "session.ctx_disabled": "Disabled", + "session.ctx_disconnected": "Disconnected", + "session.ctx_connected": "Connected", + "session.ctx_needs_auth": "Needs auth", + "session.ctx_register_client": "Register client", + "session.ctx_failed": "Failed", + "session.ctx_context": "Context", + "session.ctx_working_files": "Working files", + "session.ctx_none_yet": "None yet.", + "session.ctx_plugins": "Plugins", + "session.ctx_no_plugins": "No plugins loaded.", + "session.ctx_mcp": "MCP", + "session.ctx_no_mcp": "No MCP servers loaded.", + "session.ctx_skills": "Skills", + "session.ctx_no_skills": "No skills loaded.", + "session.ctx_authorized_folders": "Authorized folders", + "session.tool_run_command": "Run {cmd}", + "session.tool_run_command_fallback": "Run command", + "session.tool_reviewed_file": "Reviewed {file}", + "session.tool_reviewed_file_fallback": "Reviewed file", + "session.tool_updated_file": "Updated {file}", + "session.tool_updated_file_fallback": "Updated file", + "session.tool_update_file": "Update {file}", + "session.tool_update_file_fallback": "Update file", + "session.tool_searched": "Searched {pattern}", + "session.tool_searched_fallback": "Searched code", + "session.tool_reviewed_files": "Reviewed {path}", + "session.tool_reviewed_files_fallback": "Reviewed files", + "session.tool_delegate": "Delegate {agent}", + "session.tool_delegate_fallback": "Delegate task", + "session.tool_update_todo": "Update todo list", + "session.tool_read_todo": "Read todo list", + "session.tool_checked_url": "Checked {url}", + "session.tool_checked_url_fallback": "Checked web page", + "session.tool_load_skill": "Load skill {name}", + "session.tool_load_skill_fallback": "Load skill", + "session.subagent_session": "Subagent session", + "session.loading_transcript": "Loading transcript", + "session.subagent_running": "Running", + "session.waiting_transcript": "Waiting for transcript", + "session.waiting_subagent": "Waiting for the subagent transcript to arrive.", + "session.request_label": "Request", + "session.result_label": "Result", } as const; diff --git a/apps/app/src/i18n/locales/pt-BR.ts b/apps/app/src/i18n/locales/pt-BR.ts index 81a06fb6b..943fb0861 100644 --- a/apps/app/src/i18n/locales/pt-BR.ts +++ b/apps/app/src/i18n/locales/pt-BR.ts @@ -858,4 +858,205 @@ export default { "app.error.command_name_template_required": "O nome e as instruções do comando são obrigatórios.", "app.error.workspace_commands_desktop": "Comandos requerem o app desktop.", "app.error.command_scope_unknown": "Este comando não pode ser gerenciado neste modo.", + + // ==================== Den Settings ==================== + "den.status_ready": "Pronto", + "den.status_provisioning": "Provisionando", + "den.status_failed": "Falhou", + "den.status_stopped": "Parado", + "den.status_unknown": "Desconhecido", + "den.summary_needs_attention": "Precisa de atenção", + "den.summary_checking": "Verificando sessão", + "den.summary_connected": "Conectado", + "den.summary_signed_out": "Desconectado", + "den.finish_creation": "Conclua a criação da conta no navegador para conectar ao OpenWork.", + "den.finish_sign_in": "Conclua o login no navegador para conectar ao OpenWork.", + "den.invalid_url": "Digite uma URL válida http:// ou https:// para o control plane do Den.", + "den.url_updated": "URL do control plane do Den atualizada. Entre novamente para continuar.", + "den.loaded_orgs": "Carregado(s) {count} organização(ões).", + "den.failed_load_orgs": "Falha ao carregar organizações.", + "den.loaded_workers": "Carregado(s) {count} worker(s) para {org}.", + "den.no_workers_found": "Nenhum worker encontrado para {org}.", + "den.failed_load_workers": "Falha ao carregar workers.", + "den.signed_in_as": "Conectado como {email}.", + "den.no_session": "Nenhuma sessão ativa do Den encontrada.", + "den.connected_as": "OpenWork Den conectado como {email}.", + "den.connected": "OpenWork Den conectado.", + "den.sign_in_failed": "Falha ao concluir o login no OpenWork Den.", + "den.signed_out_msg": "Sessão do OpenWork Den encerrada e limpa neste dispositivo.", + "den.choose_org": "Escolha uma organização antes de abrir um worker.", + "den.worker_not_ready": "O worker ainda não está pronto. Tente novamente após o provisionamento.", + "den.failed_open_worker": "Falha ao abrir {name} no OpenWork.", + "den.opened_worker": "Aberto {name} no OpenWork.", + "den.failed_open": "Falha ao abrir {name}.", + "den.title": "OpenWork Den", + "den.description": "Entre, escolha uma organização e abra workers do Den nas Configurações.", + "den.tagline": "Entre no OpenWork Den para manter suas tarefas ativas mesmo quando o computador dorme.", + "den.control_plane_url_label": "URL do control plane do Den", + "den.control_plane_url_hint": "Apenas modo desenvolvedor. Use para apontar para um control plane local ou auto-hospedado. Alterar isso encerra sua sessão.", + "den.reset": "Redefinir", + "den.save_url": "Salvar URL", + "den.open_in_browser": "Abrir no navegador", + "den.sign_in_title": "Entrar no OpenWork Den", + "den.sign_in": "Entrar", + "den.create_account": "Criar conta", + "den.finish_auth": "Conclua a autenticação no navegador e o OpenWork reconectará automaticamente.", + "den.account_title": "Conta Den", + "den.account_description": "Gerencie sua conta conectada e organização.", + "den.signing_out": "Saindo...", + "den.sign_out": "Sair", + "den.active_org": "Organização ativa", + "den.workers_scoped": "Os workers pertencem à organização selecionada.", + "den.switched_org": "Alternado para {org}.", + "den.owner_suffix": "(Proprietário)", + "den.member_suffix": "(Membro)", + "den.workers_title": "Workers do Den", + "den.workers_description": "Abra workers diretamente no OpenWork usando o fluxo de conexão remota.", + "den.no_org_selected": "Nenhuma org selecionada", + "den.refresh": "Atualizar", + "den.no_workers_visible": "Nenhum worker cloud visível para esta org ainda. Crie um no Den e atualize esta aba.", + "den.mine_badge": "Meu", + "den.worker_type": "Worker {provider}", + "den.cloud_worker": "Worker cloud", + "den.worker_not_ready_tooltip": "Este worker ainda não está pronto para abrir.", + "den.opening": "Abrindo...", + "den.open": "Abrir", + + // ==================== Configurações (adicional) ==================== + "settings.messaging": "Mensagens", + "settings.identities_tab_hint": "Gerencie identidades do Telegram/Slack na aba Identidades", + "settings.identities": "Identidades", + "settings.no_authorized_folders": "Nenhuma pasta externa autorizada", + "settings.folder_path_placeholder": "Digite o caminho de uma pasta para autorizar...", + "settings.hide_titlebar": "Ocultar barra de título", + "settings.show_model_reasoning": "Mostrar raciocínio do modelo", + "settings.model_behavior": "Comportamento do modelo", + "settings.runtime": "Runtime", + "settings.enable_exa_search": "Ativar busca web Exa", + "settings.developer_mode": "Modo desenvolvedor", + "settings.scheme_placeholder": "openwork://...", + + // ==================== Sessão (adicional) ==================== + "session.workspace_default": "Workspace", + "session.kind_sandbox": "Sandbox", + "session.kind_remote": "Remoto", + "session.kind_local": "Local", + "session.reveal_finder": "Mostrar no Finder", + "session.reveal_explorer": "Mostrar no Explorer", + "session.hide_child_sessions": "Ocultar sub-sessões", + "session.show_child_sessions": "Exibir sub-sessões", + "session.session_actions": "Ações da sessão", + "session.rename_session": "Renomear sessão", + "session.delete_session": "Excluir sessão", + "session.status_connecting": "Conectando", + "session.status_active": "Ativo", + "session.new_task_label": "Nova tarefa", + "session.workspace_options": "Opções do workspace", + "session.collapse": "Recolher", + "session.expand": "Expandir", + "session.edit_name": "Editar nome", + "session.share": "Compartilhar...", + "session.recover": "Recuperar", + "session.test_connection": "Testar conexão", + "session.edit_connection": "Editar conexão", + "session.remove_workspace": "Remover workspace", + "session.no_tasks_yet": "Nenhuma tarefa ainda.", + "session.new_task_hover": "+ Nova tarefa", + "session.show_more": "Mostrar mais {count}", + "session.show_more_generic": "Mostrar mais", + "session.loading_tasks": "Carregando tarefas...", + "session.add_workspace": "Adicionar workspace", + "session.create_local_desktop_only": "Crie workspaces locais no app desktop.", + "session.new_workspace": "Novo workspace", + "session.connect_remote_workspace": "Conectar workspace remoto", + "session.import_config": "Importar configuração", + "session.sidebar_new_task": "Nova tarefa", + "session.sidebar_workspaces": "Workspaces", + "session.sidebar_no_workspaces": "Nenhum workspace nesta sessão ainda. Adicione um para começar.", + "session.sidebar_sandbox": "Sandbox", + "session.sidebar_remote": "Remoto", + "session.sidebar_needs_attention": "Precisa de atenção", + "session.sidebar_switch": "Alternar", + "session.sidebar_active": "Ativo", + "session.sidebar_expand": "Expandir", + "session.sidebar_collapse": "Recolher", + "session.sidebar_drag_reorder": "Arraste para reordenar", + "session.sidebar_edit_connection": "Editar conexão", + "session.sidebar_test_connection": "Testar conexão", + "session.sidebar_stop_sandbox": "Parar sandbox", + "session.sidebar_remove": "Remover", + "session.sidebar_no_sessions": "Nenhuma sessão ainda.", + "session.sidebar_show_fewer": "Mostrar menos", + "session.sidebar_show_more": "Mostrar mais {count}", + "session.sidebar_add_workspace": "Adicionar novo workspace", + "session.sidebar_new_worker": "Novo worker", + "session.sidebar_connect_remote": "Conectar remoto", + "session.sidebar_import_config": "Importar configuração", + "session.sidebar_progress": "Progresso", + "session.sidebar_new_task_menu": "Nova tarefa", + "session.sidebar_delete_session": "Excluir sessão", + "session.inbox_share_hint": "Compartilhe arquivos com este worker pelo app.", + "session.inbox_load_failed": "Falha ao carregar pasta compartilhada", + "session.inbox_connect_to_upload": "Conecte a um worker para enviar arquivos à pasta compartilhada.", + "session.inbox_uploading": "Enviando {label}...", + "session.inbox_uploaded": "Enviado para a pasta compartilhada.", + "session.inbox_upload_failed": "Falha no envio para a pasta compartilhada", + "session.inbox_copy_failed": "Falha ao copiar. Seu navegador pode bloquear o acesso à área de transferência.", + "session.inbox_connect_to_download": "Conecte a um worker para baixar arquivos compartilhados.", + "session.inbox_missing_file_id": "ID do arquivo compartilhado ausente.", + "session.inbox_download_failed": "Falha no download", + "session.inbox_shared_folder": "Pasta compartilhada", + "session.inbox_refresh": "Atualizar pasta compartilhada", + "session.inbox_drop_to_upload": "Solte arquivos aqui para enviar", + "session.inbox_connect_upload": "Conecte a um worker para enviar", + "session.inbox_uploading_label": "Enviando...", + "session.inbox_drop_or_click": "Solte arquivos ou clique para enviar", + "session.inbox_connect_to_see": "Conecte para ver os arquivos compartilhados.", + "session.inbox_no_files": "Nenhum arquivo compartilhado ainda.", + "session.inbox_copy_path": "Copiar caminho da pasta compartilhada", + "session.inbox_download": "Baixar", + "session.inbox_showing_first": "Exibindo os primeiros {count}.", + "session.ctx_disabled": "Desativado", + "session.ctx_disconnected": "Desconectado", + "session.ctx_connected": "Conectado", + "session.ctx_needs_auth": "Precisa de autenticação", + "session.ctx_register_client": "Registrar cliente", + "session.ctx_failed": "Falhou", + "session.ctx_context": "Contexto", + "session.ctx_working_files": "Arquivos em uso", + "session.ctx_none_yet": "Nenhum ainda.", + "session.ctx_plugins": "Plugins", + "session.ctx_no_plugins": "Nenhum plugin carregado.", + "session.ctx_mcp": "MCP", + "session.ctx_no_mcp": "Nenhum servidor MCP carregado.", + "session.ctx_skills": "Skills", + "session.ctx_no_skills": "Nenhum skill carregado.", + "session.ctx_authorized_folders": "Pastas autorizadas", + "session.tool_run_command": "Executar {cmd}", + "session.tool_run_command_fallback": "Executar comando", + "session.tool_reviewed_file": "Revisado {file}", + "session.tool_reviewed_file_fallback": "Arquivo revisado", + "session.tool_updated_file": "Atualizado {file}", + "session.tool_updated_file_fallback": "Arquivo atualizado", + "session.tool_update_file": "Atualizar {file}", + "session.tool_update_file_fallback": "Atualizar arquivo", + "session.tool_searched": "Buscado {pattern}", + "session.tool_searched_fallback": "Código buscado", + "session.tool_reviewed_files": "Revisado {path}", + "session.tool_reviewed_files_fallback": "Arquivos revisados", + "session.tool_delegate": "Delegar para {agent}", + "session.tool_delegate_fallback": "Delegar tarefa", + "session.tool_update_todo": "Atualizar lista de tarefas", + "session.tool_read_todo": "Ler lista de tarefas", + "session.tool_checked_url": "Verificado {url}", + "session.tool_checked_url_fallback": "Página web verificada", + "session.tool_load_skill": "Carregar skill {name}", + "session.tool_load_skill_fallback": "Carregar skill", + "session.subagent_session": "Sessão do subagente", + "session.loading_transcript": "Carregando transcrição", + "session.subagent_running": "Em execução", + "session.waiting_transcript": "Aguardando transcrição", + "session.waiting_subagent": "Aguardando a transcrição do subagente.", + "session.request_label": "Solicitação", + "session.result_label": "Resultado", } as const; \ No newline at end of file From 66cee6931ffbd713ef2b459cd104d63411e543ee Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Mar 2026 03:14:56 +0000 Subject: [PATCH 02/10] i18n: add new translation keys to en.ts for modals, composer, pages Adds ~110 new keys across new namespaces (provider, question, model_picker, share, scheduled) and extends existing ones (mcp, dashboard, session, reload, onboarding, app, settings, common) to cover hardcoded strings in modals, composer, session page, sidebars, and misc components. https://claude.ai/code/session_0151gEiEzXtrAMURfJdSaftE --- apps/app/src/i18n/locales/en.ts | 175 ++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/apps/app/src/i18n/locales/en.ts b/apps/app/src/i18n/locales/en.ts index d6a92ff8d..2720f002c 100644 --- a/apps/app/src/i18n/locales/en.ts +++ b/apps/app/src/i18n/locales/en.ts @@ -1059,4 +1059,179 @@ export default { "session.waiting_subagent": "Waiting for the subagent transcript to arrive.", "session.request_label": "Request", "session.result_label": "Result", + + // ==================== Provider ==================== + "provider.connect_title": "Connect providers", + "provider.connect_desc": "Sign in to services you want OpenWork to use.", + "provider.loading": "Loading providers...", + "provider.filter_placeholder": "Filter providers by name or ID", + "provider.no_match": "No providers match your search.", + "provider.none_available": "No providers available.", + "provider.keyboard_hint": "Arrow keys to navigate, Enter to select.", + "provider.choose_connect": "Choose how you'd like to connect.", + "provider.api_key_placeholder": "sk-...", + "provider.env_vars_label": "Env vars: ", + "provider.paste_code_placeholder": "Paste code", + "provider.waiting_browser": "Waiting for browser confirmation.", + "provider.browser_signin_desc": "Sign in in the browser tab we just opened. We will complete the connection automatically.", + "provider.copy_code_desc": "When you're ready, copy the code below, and click \"Open Browser\".", + "provider.confirmation_code_label": "Confirmation code", + "provider.checking_status": "Checking connection status automatically...", + "provider.open_browser": "Open Browser", + "provider.browser_close_hint": "This window will close once the provider is connected.", + "provider.back": "Back", + "provider.oauth_method": "OAuth", + "provider.api_key_method": "API key", + "provider.connected": "Connected", + "provider.close": "Close", + + // ==================== Question Modal ==================== + "question.fallback_header": "Question", + "question.custom_answer_label": "Or type a custom answer", + "question.placeholder": "Type your answer here...", + "question.navigate_hint": "navigate", + "question.select_hint": "select", + "question.submit": "Submit", + "question.next": "Next", + + // ==================== Model Picker ==================== + "model_picker.connect_provider": "Connect this provider to browse and save models", + "model_picker.model_singular": "model", + "model_picker.model_plural": "models", + "model_picker.default_label": "Default model", + "model_picker.chat_label": "Chat model", + "model_picker.default_desc": "Choose the default model for new chats...", + "model_picker.chat_desc": "Choose the model for this chat...", + "model_picker.recommended": "Recommended", + "model_picker.other_connected": "Other connected models", + "model_picker.more_providers": "More providers", + "model_picker.no_match": "No models match your search.", + + // ==================== Share Workspace ==================== + "share.title": "Share workspace", + "share.tab_template": "Share a template", + "share.tab_remote": "Access workspace remotely", + "share.password_field": "Password", + "share.worker_url_field": "Worker URL", + "share.hide_password": "Hide password", + "share.reveal_password": "Reveal password", + "share.publishing": "Publishing...", + "share.back_to_options": "Back to share options", + "share.template_section_title": "Workspace template", + "share.template_section_desc": "Share the core setup and workspace defaults.", + "share.template_desc": "Share your setup and defaults so someone else can start from the same environment.", + "share.create_template_link": "Create Template Link", + "share.regenerate_link": "Regenerate Link", + "share.access_section_title": "Access workspace remotely", + "share.access_section_desc": "Copy the connection details needed to reach this live workspace from another machine or messaging surface.", + "share.live_access_warning": "Share with trusted people only. These credentials grant live access to this workspace.", + "share.reusable_desc": "Share a reusable setup without granting live access to this running workspace.", + "share.connect_messaging_title": "Connect messaging", + "share.connect_messaging_desc": "Use this workspace from Slack, Telegram, and others.", + "share.setup_button": "Setup", + "share.collaborator_access": "Optional collaborator access", + "share.collaborator_desc": "Routine access without permission approvals.", + "share.close": "Close", + "share.back": "Back", + "share.copy": "Copy", + "share.copy_link": "Copy link", + + // ==================== MCP (additional) ==================== + "mcp.already_connected": "Already Connected", + "mcp.auth_link_label": "Authorization link", + "mcp.copy_link": "Copy link", + "mcp.copied": "Copied", + "mcp.step_opening_browser": "Opening your browser", + "mcp.step_authorize": "Authorize OpenWork", + "mcp.step_return": "Return here when you're done", + "mcp.request_timed_out": "Request timed out.", + "mcp.server_fallback": "MCP Server", + + // ==================== Dashboard (additional) ==================== + "dashboard.create_workspace_close": "Close create workspace modal", + "dashboard.create_workspace_folder": "Workspace folder", + "dashboard.create_workspace_select_folder": "Select folder", + "dashboard.create_workspace_sandbox_setup": "Sandbox setup", + "dashboard.create_workspace_show_logs": "Show logs", + "dashboard.create_workspace_hide_logs": "Hide logs", + "dashboard.create_workspace_live_logs": "Live Logs", + "dashboard.create_workspace_docker_debug": "Docker debug details", + "dashboard.create_workspace_creating": "Creating...", + + // ==================== Session (additional) ==================== + "session.loading_commands": "Loading commands...", + "session.no_commands_found": "No commands found.", + "session.no_mentions_match": "No matches found.", + "session.task_placeholder": "Describe your task...", + "session.upload_shared_folder": "Upload to shared folder", + "session.run_task": "Run task", + "session.stop": "Stop", + "session.agent_label": "Agent", + "session.loading_agents": "Loading agents...", + "session.default_agent": "Default agent", + "session.insert_prompt": "Insert prompt", + "session.promo_try_it": "Try it now: set up my CRM in Notion", + "session.copy_message": "Copy message", + "session.stopping_run": "Stopping the run...", + "session.stopped": "Stopped.", + "session.resize_workspace_col": "Resize workspace column", + "session.quick_actions": "Quick actions (Ctrl/Cmd+K)", + "session.search_conversation": "Search conversation (Ctrl/Cmd+F)", + "session.undo_message": "Undo last message", + "session.redo_message": "Redo last reverted message", + "session.open_sidebar": "Open sidebar", + "session.search_chat_placeholder": "Search in this chat", + "session.close_sidebar": "Close sidebar", + "session.collapse_sidebar": "Collapse sidebar", + "session.expand_sidebar": "Expand sidebar", + "session.sidebar_automations": "Automations", + "session.sidebar_extensions": "Extensions", + "session.thinking_label": "Thinking", + "session.diagnostics_label": "Diagnostics", + "session.diff_label": "Diff", + "session.raw_read_output": "Raw read output", + "session.input_label": "Input", + + // ==================== Reload (additional) ==================== + "reload.was_removed": "was removed", + "reload.was_added": "was added", + "reload.was_updated": "was updated", + "reload.changed": "changed", + "reload.active_tasks": "Active tasks", + "reload.will_stop_tasks": "Reloading will stop active tasks.", + "reload.blocked_prefix": "Blocked: ", + + // ==================== Onboarding (additional) ==================== + "onboarding.starter_worker_name": "Starter worker", + "onboarding.starter_worker_desc": "Preconfigured to show you how to use plugins, commands, and skills.", + "onboarding.empty_worker_name": "Empty worker", + "onboarding.empty_worker_desc": "Start with a blank folder and add what you need.", + "onboarding.select_folder_step": "Select Folder", + "onboarding.choose_button": "Choose", + "onboarding.opening": "Opening...", + "onboarding.choose_preset_step": "Choose Preset", + + // ==================== App (additional) ==================== + "app.web_unavailable": "This feature is currently unavailable in OpenWork Web, check OpenWork Desktop for full functionality.", + "app.download_desktop": "Download OpenWork Desktop", + "app.desktop_only": "Desktop Only", + + // ==================== Settings (additional) ==================== + "settings.paste_token": "Paste your token", + "settings.copy_debug_state": "Copy sanitized runtime state for debugging.", + "settings.telegram_bot_token_placeholder": "Paste Telegram bot token from @BotFather", + "settings.slack_bot_token_placeholder": "xoxb-...", + "settings.slack_app_token_placeholder": "xapp-...", + "settings.messaging_instructions_placeholder": "Add messaging behavior instructions...", + + // ==================== Common (additional) ==================== + "common.copy": "Copy", + "common.copied": "Copied", + + // ==================== Scheduled ==================== + "scheduled.reload_to_activate": "Reload OpenWork to activate automations", + "scheduled.reload_activate_scheduler": "...activate opencode-scheduler.", + "scheduled.reloading": "Reloading...", + "scheduled.reload_button": "Reload OpenWork", + "scheduled.choose_folder": "Choose a folder", } as const; From 851dfc5d2f03cfb704330d86630586ddbb8d0738 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Mar 2026 03:15:26 +0000 Subject: [PATCH 03/10] i18n: add pt-BR translations for all new keys (phase 2) Adds Brazilian Portuguese translations for ~110 new keys: provider.*, question.*, model_picker.*, share.*, scheduled.* and extensions to mcp.*, dashboard.*, session.*, reload.*, onboarding.*, app.*, settings.*, common.* namespaces. https://claude.ai/code/session_0151gEiEzXtrAMURfJdSaftE --- apps/app/src/i18n/locales/pt-BR.ts | 175 +++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/apps/app/src/i18n/locales/pt-BR.ts b/apps/app/src/i18n/locales/pt-BR.ts index 943fb0861..2b1367129 100644 --- a/apps/app/src/i18n/locales/pt-BR.ts +++ b/apps/app/src/i18n/locales/pt-BR.ts @@ -1059,4 +1059,179 @@ export default { "session.waiting_subagent": "Aguardando a transcrição do subagente.", "session.request_label": "Solicitação", "session.result_label": "Resultado", + + // ==================== Provider ==================== + "provider.connect_title": "Conectar provedores", + "provider.connect_desc": "Entre nos serviços que você quer que o OpenWork use.", + "provider.loading": "Carregando provedores...", + "provider.filter_placeholder": "Filtrar provedores por nome ou ID", + "provider.no_match": "Nenhum provedor encontrado.", + "provider.none_available": "Nenhum provedor disponível.", + "provider.keyboard_hint": "Setas para navegar, Enter para selecionar.", + "provider.choose_connect": "Escolha como deseja conectar.", + "provider.api_key_placeholder": "sk-...", + "provider.env_vars_label": "Vars de ambiente: ", + "provider.paste_code_placeholder": "Cole o código", + "provider.waiting_browser": "Aguardando confirmação do navegador.", + "provider.browser_signin_desc": "Entre na aba do navegador que acabamos de abrir. Concluiremos a conexão automaticamente.", + "provider.copy_code_desc": "Quando estiver pronto, copie o código abaixo e clique em \"Abrir Navegador\".", + "provider.confirmation_code_label": "Código de confirmação", + "provider.checking_status": "Verificando status da conexão automaticamente...", + "provider.open_browser": "Abrir Navegador", + "provider.browser_close_hint": "Esta janela fechará assim que o provedor estiver conectado.", + "provider.back": "Voltar", + "provider.oauth_method": "OAuth", + "provider.api_key_method": "Chave de API", + "provider.connected": "Conectado", + "provider.close": "Fechar", + + // ==================== Question Modal ==================== + "question.fallback_header": "Pergunta", + "question.custom_answer_label": "Ou digite uma resposta personalizada", + "question.placeholder": "Digite sua resposta aqui...", + "question.navigate_hint": "navegar", + "question.select_hint": "selecionar", + "question.submit": "Enviar", + "question.next": "Próximo", + + // ==================== Model Picker ==================== + "model_picker.connect_provider": "Conecte este provedor para explorar e salvar modelos", + "model_picker.model_singular": "modelo", + "model_picker.model_plural": "modelos", + "model_picker.default_label": "Modelo padrão", + "model_picker.chat_label": "Modelo do chat", + "model_picker.default_desc": "Escolha o modelo padrão para novos chats...", + "model_picker.chat_desc": "Escolha o modelo para este chat...", + "model_picker.recommended": "Recomendados", + "model_picker.other_connected": "Outros modelos conectados", + "model_picker.more_providers": "Mais provedores", + "model_picker.no_match": "Nenhum modelo encontrado.", + + // ==================== Share Workspace ==================== + "share.title": "Compartilhar workspace", + "share.tab_template": "Compartilhar um template", + "share.tab_remote": "Acessar workspace remotamente", + "share.password_field": "Senha", + "share.worker_url_field": "URL do Worker", + "share.hide_password": "Ocultar senha", + "share.reveal_password": "Revelar senha", + "share.publishing": "Publicando...", + "share.back_to_options": "Voltar para opções de compartilhamento", + "share.template_section_title": "Template do workspace", + "share.template_section_desc": "Compartilhe a configuração base e os padrões do workspace.", + "share.template_desc": "Compartilhe sua configuração para que outros comecem no mesmo ambiente.", + "share.create_template_link": "Criar Link de Template", + "share.regenerate_link": "Regenerar Link", + "share.access_section_title": "Acessar workspace remotamente", + "share.access_section_desc": "Copie os detalhes de conexão para acessar este workspace de outra máquina.", + "share.live_access_warning": "Compartilhe apenas com pessoas de confiança. Essas credenciais concedem acesso ao vivo a este workspace.", + "share.reusable_desc": "Compartilhe uma configuração reutilizável sem conceder acesso ao vivo a este workspace.", + "share.connect_messaging_title": "Conectar mensagens", + "share.connect_messaging_desc": "Use este workspace pelo Slack, Telegram e outros.", + "share.setup_button": "Configurar", + "share.collaborator_access": "Acesso opcional de colaborador", + "share.collaborator_desc": "Acesso rotineiro sem aprovações de permissão.", + "share.close": "Fechar", + "share.back": "Voltar", + "share.copy": "Copiar", + "share.copy_link": "Copiar link", + + // ==================== MCP (additional) ==================== + "mcp.already_connected": "Já Conectado", + "mcp.auth_link_label": "Link de autorização", + "mcp.copy_link": "Copiar link", + "mcp.copied": "Copiado", + "mcp.step_opening_browser": "Abrindo seu navegador", + "mcp.step_authorize": "Autorizar OpenWork", + "mcp.step_return": "Volte aqui quando terminar", + "mcp.request_timed_out": "A solicitação expirou.", + "mcp.server_fallback": "Servidor MCP", + + // ==================== Dashboard (additional) ==================== + "dashboard.create_workspace_close": "Fechar modal de criação de workspace", + "dashboard.create_workspace_folder": "Pasta do workspace", + "dashboard.create_workspace_select_folder": "Selecionar pasta", + "dashboard.create_workspace_sandbox_setup": "Configuração do Sandbox", + "dashboard.create_workspace_show_logs": "Exibir logs", + "dashboard.create_workspace_hide_logs": "Ocultar logs", + "dashboard.create_workspace_live_logs": "Logs ao Vivo", + "dashboard.create_workspace_docker_debug": "Detalhes de debug do Docker", + "dashboard.create_workspace_creating": "Criando...", + + // ==================== Session (additional) ==================== + "session.loading_commands": "Carregando comandos...", + "session.no_commands_found": "Nenhum comando encontrado.", + "session.no_mentions_match": "Nenhum resultado encontrado.", + "session.task_placeholder": "Descreva sua tarefa...", + "session.upload_shared_folder": "Enviar para pasta compartilhada", + "session.run_task": "Executar tarefa", + "session.stop": "Parar", + "session.agent_label": "Agente", + "session.loading_agents": "Carregando agentes...", + "session.default_agent": "Agente padrão", + "session.insert_prompt": "Inserir prompt", + "session.promo_try_it": "Experimente: configurar meu CRM no Notion", + "session.copy_message": "Copiar mensagem", + "session.stopping_run": "Parando a execução...", + "session.stopped": "Parado.", + "session.resize_workspace_col": "Redimensionar coluna do workspace", + "session.quick_actions": "Ações rápidas (Ctrl/Cmd+K)", + "session.search_conversation": "Buscar na conversa (Ctrl/Cmd+F)", + "session.undo_message": "Desfazer última mensagem", + "session.redo_message": "Refazer última mensagem desfeita", + "session.open_sidebar": "Abrir barra lateral", + "session.search_chat_placeholder": "Buscar neste chat", + "session.close_sidebar": "Fechar barra lateral", + "session.collapse_sidebar": "Recolher barra lateral", + "session.expand_sidebar": "Expandir barra lateral", + "session.sidebar_automations": "Automações", + "session.sidebar_extensions": "Extensões", + "session.thinking_label": "Pensando", + "session.diagnostics_label": "Diagnósticos", + "session.diff_label": "Diff", + "session.raw_read_output": "Saída de leitura bruta", + "session.input_label": "Entrada", + + // ==================== Reload (additional) ==================== + "reload.was_removed": "foi removido", + "reload.was_added": "foi adicionado", + "reload.was_updated": "foi atualizado", + "reload.changed": "alterado", + "reload.active_tasks": "Tarefas ativas", + "reload.will_stop_tasks": "Recarregar vai parar as tarefas ativas.", + "reload.blocked_prefix": "Bloqueado: ", + + // ==================== Onboarding (additional) ==================== + "onboarding.starter_worker_name": "Worker inicial", + "onboarding.starter_worker_desc": "Pré-configurado para mostrar como usar plugins, comandos e skills.", + "onboarding.empty_worker_name": "Worker vazio", + "onboarding.empty_worker_desc": "Comece com uma pasta em branco e adicione o que precisar.", + "onboarding.select_folder_step": "Selecionar Pasta", + "onboarding.choose_button": "Escolher", + "onboarding.opening": "Abrindo...", + "onboarding.choose_preset_step": "Escolher Preset", + + // ==================== App (additional) ==================== + "app.web_unavailable": "Este recurso não está disponível no OpenWork Web. Acesse o OpenWork Desktop para funcionalidade completa.", + "app.download_desktop": "Baixar OpenWork Desktop", + "app.desktop_only": "Somente Desktop", + + // ==================== Settings (additional) ==================== + "settings.paste_token": "Cole seu token", + "settings.copy_debug_state": "Copiar estado de runtime sanitizado para debug.", + "settings.telegram_bot_token_placeholder": "Cole o token do bot Telegram do @BotFather", + "settings.slack_bot_token_placeholder": "xoxb-...", + "settings.slack_app_token_placeholder": "xapp-...", + "settings.messaging_instructions_placeholder": "Adicione instruções de comportamento de mensagens...", + + // ==================== Common (additional) ==================== + "common.copy": "Copiar", + "common.copied": "Copiado", + + // ==================== Scheduled ==================== + "scheduled.reload_to_activate": "Recarregue o OpenWork para ativar automações", + "scheduled.reload_activate_scheduler": "...ativar o opencode-scheduler.", + "scheduled.reloading": "Recarregando...", + "scheduled.reload_button": "Recarregar OpenWork", + "scheduled.choose_folder": "Escolher uma pasta", } as const; \ No newline at end of file From 322ca34a316249cd82ad5f5332c25dfa394fba84 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Mar 2026 03:16:42 +0000 Subject: [PATCH 04/10] i18n: replace hardcoded strings in create-workspace-modal and question-modal Uses t() for all UI-visible strings: workspace folder label, select folder, sandbox setup, show/hide logs, live logs, docker debug, creating state, and question modal header, custom answer label, placeholder, keyboard hints, submit/next buttons. https://claude.ai/code/session_0151gEiEzXtrAMURfJdSaftE --- .../app/components/create-workspace-modal.tsx | 16 ++++++++-------- apps/app/src/app/components/question-modal.tsx | 12 +++++++----- apps/app/src/i18n/locales/pt-BR.ts | 1 - 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/apps/app/src/app/components/create-workspace-modal.tsx b/apps/app/src/app/components/create-workspace-modal.tsx index 081ba1810..2419ba7fc 100644 --- a/apps/app/src/app/components/create-workspace-modal.tsx +++ b/apps/app/src/app/components/create-workspace-modal.tsx @@ -110,7 +110,7 @@ export default function CreateWorkspaceModal(props: { onClick={props.onClose} disabled={submitting()} class={`flex h-8 w-8 items-center justify-center rounded-full text-dls-secondary transition-colors hover:bg-dls-hover hover:text-dls-text ${submitting() ? "cursor-not-allowed opacity-50" : ""}`.trim()} - aria-label="Close create workspace modal" + aria-label={translate("dashboard.create_workspace_close")} > @@ -120,7 +120,7 @@ export default function CreateWorkspaceModal(props: {
-
Workspace folder
+
{translate("dashboard.create_workspace_folder")}
@@ -137,7 +137,7 @@ export default function CreateWorkspaceModal(props: { }> - {hasSelectedFolder() ? translate("dashboard.change") : "Select folder"} + {hasSelectedFolder() ? translate("dashboard.change") : translate("dashboard.create_workspace_select_folder")}
@@ -152,7 +152,7 @@ export default function CreateWorkspaceModal(props: { }> - Sandbox setup + {translate("dashboard.create_workspace_sandbox_setup")}
{p().stage}
{elapsedSeconds()}s
@@ -162,7 +162,7 @@ export default function CreateWorkspaceModal(props: { class="shrink-0 rounded px-2 py-1 text-xs text-gray-10 transition-colors hover:bg-gray-4 hover:text-gray-12" onClick={() => setShowProgressDetails((prev) => !prev)} > - {showProgressDetails() ? "Hide logs" : "Show logs"} + {showProgressDetails() ? translate("dashboard.create_workspace_hide_logs") : translate("dashboard.create_workspace_show_logs")}
@@ -211,7 +211,7 @@ export default function CreateWorkspaceModal(props: { 0}>
-
Live Logs
+
{translate("dashboard.create_workspace_live_logs")}
@@ -244,7 +244,7 @@ export default function CreateWorkspaceModal(props: {
0}>
- Docker debug details + {translate("dashboard.create_workspace_docker_debug")}
{(line) =>
{line}
} @@ -296,7 +296,7 @@ export default function CreateWorkspaceModal(props: { - Creating... + {translate("dashboard.create_workspace_creating")} diff --git a/apps/app/src/app/components/question-modal.tsx b/apps/app/src/app/components/question-modal.tsx index 4b3b070bd..da5d86359 100644 --- a/apps/app/src/app/components/question-modal.tsx +++ b/apps/app/src/app/components/question-modal.tsx @@ -2,6 +2,7 @@ import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "so import type { QuestionInfo } from "@opencode-ai/sdk/v2/client"; import { Check, ChevronRight, HelpCircle } from "lucide-solid"; +import { t, currentLocale } from "../../i18n"; import Button from "./button"; @@ -14,6 +15,7 @@ export type QuestionModalProps = { }; export default function QuestionModal(props: QuestionModalProps) { + const translate = (key: string) => t(key, currentLocale()); const [currentIndex, setCurrentIndex] = createSignal(0); const [answers, setAnswers] = createSignal([]); const [currentSelection, setCurrentSelection] = createSignal([]); @@ -138,7 +140,7 @@ export default function QuestionModal(props: QuestionModalProps) {

- {currentQuestion()!.header || "Question"} + {currentQuestion()!.header || translate("question.fallback_header")}

Question {currentIndex() + 1} of {props.questions.length} @@ -186,14 +188,14 @@ export default function QuestionModal(props: QuestionModalProps) {
setCustomInput(e.currentTarget.value)} class="w-full px-4 py-3 rounded-xl bg-dls-surface border border-dls-border focus:border-dls-accent focus:ring-4 focus:ring-[rgba(var(--dls-accent-rgb),0.2)] focus:outline-none text-sm text-dls-text placeholder:text-dls-secondary transition-shadow" - placeholder="Type your answer here..." + placeholder={translate("question.placeholder")} onKeyDown={(e) => { if (e.key === "Enter") { if (e.isComposing || e.keyCode === 229) return; @@ -209,9 +211,9 @@ export default function QuestionModal(props: QuestionModalProps) {
↑↓ - navigate + {translate("question.navigate_hint")} - select + {translate("question.select_hint")}
diff --git a/apps/app/src/i18n/locales/pt-BR.ts b/apps/app/src/i18n/locales/pt-BR.ts index 2b1367129..2424157d3 100644 --- a/apps/app/src/i18n/locales/pt-BR.ts +++ b/apps/app/src/i18n/locales/pt-BR.ts @@ -1169,7 +1169,6 @@ export default { "session.agent_label": "Agente", "session.loading_agents": "Carregando agentes...", "session.default_agent": "Agente padrão", - "session.insert_prompt": "Inserir prompt", "session.promo_try_it": "Experimente: configurar meu CRM no Notion", "session.copy_message": "Copiar mensagem", "session.stopping_run": "Parando a execução...", From 47a76d61563845797699010f16061937094b5138 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Mar 2026 03:17:59 +0000 Subject: [PATCH 05/10] i18n: replace hardcoded strings in mcp-auth, model-picker, provider-auth, question modals - mcp-auth-modal: request timeout, server fallback, already connected, auth link label, copy/copied, browser steps (opening/authorize/return) - model-picker-modal: connect provider msg, model singular/plural, default/chat labels and descs, section headers, no match text - provider-auth-modal: connect title/desc, loading, filter placeholder, no match/available, keyboard hint, connected, choose connect, api key placeholder, env vars, paste code, browser flow strings, confirmation code, checking status, open browser, back, method labels - question-modal: minor fix https://claude.ai/code/session_0151gEiEzXtrAMURfJdSaftE --- .../app/src/app/components/mcp-auth-modal.tsx | 16 ++++++------ .../src/app/components/model-picker-modal.tsx | 18 ++++++------- .../app/components/provider-auth-modal.tsx | 25 +++++++++++-------- .../app/src/app/components/question-modal.tsx | 2 +- 4 files changed, 32 insertions(+), 29 deletions(-) diff --git a/apps/app/src/app/components/mcp-auth-modal.tsx b/apps/app/src/app/components/mcp-auth-modal.tsx index 2ffde2846..32bcf7f34 100644 --- a/apps/app/src/app/components/mcp-auth-modal.tsx +++ b/apps/app/src/app/components/mcp-auth-modal.tsx @@ -168,7 +168,7 @@ export default function McpAuthModal(props: McpAuthModalProps) { statusPoll = window.setInterval(async () => { if (Date.now() - startedAt >= MCP_AUTH_TIMEOUT_MS) { stopStatusPolling(); - setError("Request timed out."); + setError(translate("mcp.request_timed_out")); return; } @@ -589,7 +589,7 @@ export default function McpAuthModal(props: McpAuthModalProps) { setStatusChecking(false); }; - const serverName = () => props.entry?.name ?? "MCP Server"; + const serverName = () => props.entry?.name ?? translate("mcp.server_fallback"); return ( @@ -694,7 +694,7 @@ export default function McpAuthModal(props: McpAuthModalProps) {
-

Already Connected

+

{translate("mcp.already_connected")}

{translate("mcp.auth.already_connected_description", { server: serverName() })}

@@ -804,7 +804,7 @@ export default function McpAuthModal(props: McpAuthModalProps) {
-
Authorization link
+
{translate("mcp.auth_link_label")}
{authorizationUrl()}
@@ -814,7 +814,7 @@ export default function McpAuthModal(props: McpAuthModalProps) { class="text-xs" onClick={handleCopyAuthorizationUrl} > - {authUrlCopied() ? "Copied" : "Copy link"} + {authUrlCopied() ? translate("mcp.copied") : translate("mcp.copy_link")}
-

Opening your browser

+

{translate("mcp.step_opening_browser")}

{translate("mcp.auth.step1_description", { server: serverName() })}

@@ -863,7 +863,7 @@ export default function McpAuthModal(props: McpAuthModalProps) { 2
-

Authorize OpenWork

+

{translate("mcp.step_authorize")}

{translate("mcp.auth.step2_description")}

@@ -875,7 +875,7 @@ export default function McpAuthModal(props: McpAuthModalProps) { 3
-

Return here when you're done

+

{translate("mcp.step_return")}

{translate("mcp.auth.step3_description")}

diff --git a/apps/app/src/app/components/model-picker-modal.tsx b/apps/app/src/app/components/model-picker-modal.tsx index 4f208564e..b993930e7 100644 --- a/apps/app/src/app/components/model-picker-modal.tsx +++ b/apps/app/src/app/components/model-picker-modal.tsx @@ -306,9 +306,9 @@ export default function ModelPickerModal(props: ModelPickerModalProps) { {provider.title}
- Connect this provider to browse and save models + {translate("model_picker.connect_provider")} - {provider.matchCount} {provider.matchCount === 1 ? "model" : "models"} + {provider.matchCount} {provider.matchCount === 1 ? translate("model_picker.model_singular") : translate("model_picker.model_plural")}
@@ -324,12 +324,12 @@ export default function ModelPickerModal(props: ModelPickerModalProps) {

- {props.target === "default" ? "Default model" : "Chat model"} + {props.target === "default" ? translate("model_picker.default_label") : translate("model_picker.chat_label")}

{props.target === "default" - ? "Choose the default model for new chats. If a model supports reasoning profiles, configure them on its card." - : "Choose the model for this chat. If a model supports reasoning profiles, configure them on its card."} + ? translate("model_picker.default_desc") + : translate("model_picker.chat_desc")}

diff --git a/apps/app/src/app/components/provider-auth-modal.tsx b/apps/app/src/app/components/provider-auth-modal.tsx index 411e011c1..1595b3e6e 100644 --- a/apps/app/src/app/components/provider-auth-modal.tsx +++ b/apps/app/src/app/components/provider-auth-modal.tsx @@ -4,6 +4,9 @@ import type { ProviderListItem } from "../types"; import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js"; import { isTauriRuntime } from "../utils"; import { compareProviders } from "../utils/providers"; +import { t, currentLocale } from "../../i18n"; + +const translate = (key: string) => t(key, currentLocale()); import Button from "./button"; import TextInput from "./text-input"; @@ -126,7 +129,7 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) { }); const methodLabel = (method: ProviderAuthMethod) => - method.label || (method.type === "oauth" ? "OAuth" : "API key"); + method.label || (method.type === "oauth" ? translate("provider.oauth_method") : translate("provider.api_key_method")); const actionDisabled = () => props.loading || props.submitting; @@ -595,14 +598,14 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) {
-

Connect providers

-

Sign in to services you want OpenWork to use.

+

{translate("provider.connect_title")}

+

{translate("provider.connect_desc")}

@@ -615,7 +618,7 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) { fallback={
- Loading providers... + {translate("provider.loading")}
} @@ -635,7 +638,7 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) { { setSearchQuery(event.currentTarget.value); @@ -653,7 +656,7 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) { when={filteredEntries().length} fallback={
- {entries().length ? "No providers match your search." : "No providers available."} + {entries().length ? translate("provider.no_match") : translate("provider.none_available")}
} > @@ -706,7 +709,7 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) { >
- Connected + {translate("provider.connected")}
@@ -735,7 +738,7 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) { -
Arrow keys to navigate, Enter to select.
+
{translate("provider.keyboard_hint")}
@@ -744,10 +747,10 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) {
{selectedEntry()!.name}
-
Choose how you'd like to connect.
+
{translate("provider.choose_connect")}
diff --git a/apps/app/src/app/components/question-modal.tsx b/apps/app/src/app/components/question-modal.tsx index da5d86359..52a0269d5 100644 --- a/apps/app/src/app/components/question-modal.tsx +++ b/apps/app/src/app/components/question-modal.tsx @@ -219,7 +219,7 @@ export default function QuestionModal(props: QuestionModalProps) {
{ setApiKeyInput(event.currentTarget.value); @@ -802,7 +802,7 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) { /> 0}>
- Env vars: {selectedEntry()!.env.join(", ")} + {translate("provider.env_vars_label")}{selectedEntry()!.env.join(", ")}
@@ -828,7 +828,7 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) {
Finish OAuth by pasting the authorization code.
@@ -840,9 +840,9 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) {
{ setOauthCodeInput(event.currentTarget.value); @@ -885,16 +885,16 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) {
{selectedEntry()!.name}
-
Waiting for browser confirmation.
+
{translate("provider.waiting_browser")}
Sign in in the browser tab we just opened. We will complete the connection automatically.
+
{translate("provider.browser_signin_desc")}
} >
@@ -903,13 +903,13 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) { The first time you do this you'll need to enable Device auth in your account settings.
ChatGPT > Account Settings > Security > Enable device code authorization
-
When you're ready, copy the code below, and click "Open Browser".
+
{translate("provider.copy_code_desc")}
-
Confirmation code
+
{translate("provider.confirmation_code_label")}
{oauthDisplayCode()}
@@ -949,7 +949,7 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) { {submittingLabel()}
diff --git a/apps/app/src/app/components/share-workspace-modal.tsx b/apps/app/src/app/components/share-workspace-modal.tsx index 2b802f524..21b4926fa 100644 --- a/apps/app/src/app/components/share-workspace-modal.tsx +++ b/apps/app/src/app/components/share-workspace-modal.tsx @@ -1,4 +1,5 @@ import { For, Show, createEffect, createMemo, createSignal, onCleanup } from "solid-js"; +import { t, currentLocale } from "../../i18n"; import { ArrowLeft, Check, @@ -28,9 +29,9 @@ const isCollaboratorField = (label: string) => /collaborator token/i.test(label) const isPasswordField = (label: string) => /owner token|connected token|access token|password/i.test(label); const isWorkerUrlField = (label: string) => /worker url/i.test(label); -const displayFieldLabel = (field: ShareField) => { - if (isPasswordField(field.label)) return "Password"; - if (isWorkerUrlField(field.label)) return "Worker URL"; +const displayFieldLabel = (field: ShareField, translate: (key: string) => string) => { + if (isPasswordField(field.label)) return translate("share.password_field"); + if (isWorkerUrlField(field.label)) return translate("share.worker_url_field"); return field.label; }; @@ -58,12 +59,14 @@ export default function ShareWorkspaceModal(props: { exportDisabledReason?: string | null; onOpenBots?: () => void; }) { + const translate = (key: string) => t(key, currentLocale()); + const [activeView, setActiveView] = createSignal("chooser"); const [revealedByIndex, setRevealedByIndex] = createSignal>({}); const [copiedKey, setCopiedKey] = createSignal(null); const [collaboratorExpanded, setCollaboratorExpanded] = createSignal(false); - const title = createMemo(() => props.title ?? "Share workspace"); + const title = createMemo(() => props.title ?? translate("share.title")); const note = createMemo(() => props.note?.trim() ?? ""); const accessFields = createMemo(() => props.fields.filter((field) => !isInviteField(field.label))); const collaboratorField = createMemo(() => accessFields().find((field) => isCollaboratorField(field.label)) ?? null); @@ -115,7 +118,7 @@ export default function ShareWorkspaceModal(props: { return (
Date: Wed, 25 Mar 2026 03:19:50 +0000 Subject: [PATCH 07/10] i18n: additional string replacements in share-workspace-modal, composer, workspace-right-sidebar - share-workspace-modal: hide/reveal password toggle title, copy title, publishing ternary, copy link title, and remaining section strings - composer: translate helper added - workspace-right-sidebar: close/collapse/expand sidebar aria-labels, automations/extensions tab labels https://claude.ai/code/session_0151gEiEzXtrAMURfJdSaftE --- .../src/app/components/session/composer.tsx | 2 ++ .../app/components/share-workspace-modal.tsx | 22 +++++++++---------- .../components/workspace-right-sidebar.tsx | 10 +++++---- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/apps/app/src/app/components/session/composer.tsx b/apps/app/src/app/components/session/composer.tsx index eda2c6702..08d43e86a 100644 --- a/apps/app/src/app/components/session/composer.tsx +++ b/apps/app/src/app/components/session/composer.tsx @@ -6,6 +6,7 @@ import { ArrowUp, AtSign, Check, ChevronDown, File as FileIcon, Paperclip, Squar import type { ComposerAttachment, ComposerDraft, ComposerPart, PromptMode, SlashCommandOption } from "../../types"; import { perfNow, recordPerfLog } from "../../lib/perf-log"; +import { t, currentLocale } from "../../../i18n"; type MentionOption = { id: string; @@ -442,6 +443,7 @@ const buildRangeFromOffsets = (root: HTMLElement, start: number, end: number) => }; export default function Composer(props: ComposerProps) { + const translate = (key: string) => t(key, currentLocale()); let editorRef: HTMLDivElement | undefined; let fileInputRef: HTMLInputElement | undefined; let inboxFileInputRef: HTMLInputElement | undefined; diff --git a/apps/app/src/app/components/share-workspace-modal.tsx b/apps/app/src/app/components/share-workspace-modal.tsx index 21b4926fa..d7035997a 100644 --- a/apps/app/src/app/components/share-workspace-modal.tsx +++ b/apps/app/src/app/components/share-workspace-modal.tsx @@ -138,7 +138,7 @@ export default function ShareWorkspaceModal(props: { } disabled={!field.value} class="p-1.5 text-gray-10 hover:text-gray-12 hover:bg-gray-3 rounded-md transition-colors disabled:opacity-50" - title={revealed() ? "Hide password" : "Reveal password"} + title={revealed() ? translate("share.hide_password") : translate("share.reveal_password")} > }> @@ -149,7 +149,7 @@ export default function ShareWorkspaceModal(props: { onClick={() => handleCopy(field.value, key())} disabled={!field.value} class="p-1.5 text-gray-10 hover:text-gray-12 hover:bg-gray-3 rounded-md transition-colors disabled:opacity-50" - title="Copy" + title={translate("share.copy")} > }> @@ -182,7 +182,7 @@ export default function ShareWorkspaceModal(props: { disabled={Boolean(disabledReason) || !createAction || busy} class="mt-3 w-full rounded-full bg-dls-text px-5 py-3 text-[13px] font-medium text-dls-surface shadow-sm transition-colors hover:bg-gray-12 active:scale-[0.99] disabled:opacity-50" > - {busy ? "Publishing..." : createLabel} + {busy ? translate("share.publishing") : createLabel} } > @@ -196,7 +196,7 @@ export default function ShareWorkspaceModal(props: { ); @@ -225,8 +225,8 @@ export default function ShareWorkspaceModal(props: { @@ -235,8 +235,8 @@ export default function ShareWorkspaceModal(props: { @@ -246,8 +246,8 @@ export default function ShareWorkspaceModal(props: {

{title()} - Share a template - Access workspace remotely + {translate("share.tab_template")} + {translate("share.tab_remote")}

{props.workspaceName}
diff --git a/apps/app/src/app/components/workspace-right-sidebar.tsx b/apps/app/src/app/components/workspace-right-sidebar.tsx index 2eea397e6..c596a6a1f 100644 --- a/apps/app/src/app/components/workspace-right-sidebar.tsx +++ b/apps/app/src/app/components/workspace-right-sidebar.tsx @@ -1,4 +1,5 @@ import { Show, type JSX } from "solid-js"; +import { t, currentLocale } from "../../i18n"; import { Box, ChevronLeft, @@ -38,6 +39,7 @@ type Props = { }; export default function WorkspaceRightSidebar(props: Props) { + const translate = (key: string) => t(key, currentLocale()); const mobile = () => props.mobile ?? false; const showSelection = () => props.showSelection ?? true; const closeMobile = () => props.onCloseMobile?.(); @@ -88,8 +90,8 @@ export default function WorkspaceRightSidebar(props: Props) { type="button" class="flex h-10 w-10 items-center justify-center rounded-[16px] text-gray-10 transition-colors hover:bg-dls-surface hover:text-dls-text" onClick={mobile() ? closeMobile : props.onToggleExpanded} - title={mobile() ? "Close sidebar" : props.expanded ? "Collapse sidebar" : "Expand sidebar"} - aria-label={mobile() ? "Close sidebar" : props.expanded ? "Collapse sidebar" : "Expand sidebar"} + title={mobile() ? translate("session.close_sidebar") : props.expanded ? translate("session.collapse_sidebar") : translate("session.expand_sidebar")} + aria-label={mobile() ? translate("session.close_sidebar") : props.expanded ? translate("session.collapse_sidebar") : translate("session.expand_sidebar")} >
{sidebarButton( - "Automations", + translate("session.sidebar_automations"), , showSelection() && props.tab === "scheduled", props.onOpenAutomations, @@ -114,7 +116,7 @@ export default function WorkspaceRightSidebar(props: Props) { props.onOpenSkills, )} {sidebarButton( - "Extensions", + translate("session.sidebar_extensions"), , showSelection() && (props.tab === "mcp" || props.tab === "plugins"), props.onOpenExtensions, From 8c19e75e2a739a64e49d649e14cb3ee0535d6275 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Mar 2026 03:20:07 +0000 Subject: [PATCH 08/10] i18n: replace hardcoded strings in mobile-sidebar-drawer, composer, share-workspace-modal - mobile-sidebar-drawer: close sidebar aria-label - composer: run task, stop, agent label, loading agents, default agent, upload shared folder, task placeholder, insert prompt, loading commands, no commands found, no mentions match - share-workspace-modal: remaining string fixes https://claude.ai/code/session_0151gEiEzXtrAMURfJdSaftE --- apps/app/src/app/components/mobile-sidebar-drawer.tsx | 4 +++- apps/app/src/app/components/session/composer.tsx | 10 +++++----- apps/app/src/app/components/share-workspace-modal.tsx | 10 +++++----- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/app/src/app/components/mobile-sidebar-drawer.tsx b/apps/app/src/app/components/mobile-sidebar-drawer.tsx index 74d4b5c77..d7397e986 100644 --- a/apps/app/src/app/components/mobile-sidebar-drawer.tsx +++ b/apps/app/src/app/components/mobile-sidebar-drawer.tsx @@ -1,5 +1,6 @@ import { Show, createEffect, onCleanup } from "solid-js"; import type { JSX } from "solid-js"; +import { t, currentLocale } from "../../i18n"; type MobileSidebarDrawerProps = { open: boolean; @@ -8,6 +9,7 @@ type MobileSidebarDrawerProps = { }; export default function MobileSidebarDrawer(props: MobileSidebarDrawerProps) { + const translate = (key: string) => t(key, currentLocale()); createEffect(() => { if (!props.open || typeof window === "undefined" || typeof document === "undefined") return; @@ -35,7 +37,7 @@ export default function MobileSidebarDrawer(props: MobileSidebarDrawerProps) { type="button" class="absolute inset-0 bg-gray-1/60 backdrop-blur-sm" onClick={props.onClose} - aria-label="Close sidebar" + aria-label={translate("session.close_sidebar")} />
{props.children} diff --git a/apps/app/src/app/components/session/composer.tsx b/apps/app/src/app/components/session/composer.tsx index 08d43e86a..d9a7ce989 100644 --- a/apps/app/src/app/components/session/composer.tsx +++ b/apps/app/src/app/components/session/composer.tsx @@ -1591,7 +1591,7 @@ export default function Composer(props: ComposerProps) {
event.preventDefault()}> No matches found.
} + fallback={
{translate("session.no_mentions_match")}
} > {(option: MentionOption) => { @@ -1654,7 +1654,7 @@ export default function Composer(props: ComposerProps) { when={slashFiltered().length} fallback={
- {slashLoading() ? "Loading commands..." : "No commands found."} + {slashLoading() ? translate("session.loading_commands") : translate("session.no_commands_found")}
} > @@ -1701,8 +1701,8 @@ export default function Composer(props: ComposerProps) { class="w-full mb-2 flex items-center justify-between gap-3 rounded-xl border border-green-7/20 bg-green-7/10 px-3 py-2 text-left text-sm text-green-12 transition-colors hover:bg-green-7/15" onClick={props.onNotionBannerClick} > - Try it now: set up my CRM in Notion - Insert prompt + {translate("session.promo_try_it")} + {translate("session.insert_prompt")} @@ -1754,7 +1754,7 @@ export default function Composer(props: ComposerProps) { class="shrink-0 rounded-md border border-gray-6 bg-gray-2 px-2 py-1 text-[10px] text-gray-11 hover:bg-gray-3" onClick={() => inboxFileInputRef?.click()} > - Upload to shared folder + {translate("session.upload_shared_folder")}
diff --git a/apps/app/src/app/components/share-workspace-modal.tsx b/apps/app/src/app/components/share-workspace-modal.tsx index d7035997a..aa4e61f3d 100644 --- a/apps/app/src/app/components/share-workspace-modal.tsx +++ b/apps/app/src/app/components/share-workspace-modal.tsx @@ -266,9 +266,9 @@ export default function ShareWorkspaceModal(props: {
-

Share a template

+

{translate("share.tab_template")}

- Share your setup and defaults so someone else can start from the same environment. + {translate("share.template_desc")}

@@ -282,9 +282,9 @@ export default function ShareWorkspaceModal(props: {
-

Access workspace remotely

+

{translate("share.tab_remote")}

- Copy the connection details needed to reach this live workspace from another machine or messaging surface. + {translate("share.access_section_desc")}

@@ -294,7 +294,7 @@ export default function ShareWorkspaceModal(props: {
- Share a reusable setup without granting live access to this running workspace. + {translate("share.reusable_desc")}
From 15a5ab4938f68d66384651ad958b8d17e0841f09 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Mar 2026 03:20:24 +0000 Subject: [PATCH 09/10] i18n: replace hardcoded strings in composer, share-workspace-modal, dashboard - composer: additional string replacements (promo text, task placeholder fixes) - share-workspace-modal: remaining title/label strings - dashboard: resize workspace column and open sidebar aria-labels https://claude.ai/code/session_0151gEiEzXtrAMURfJdSaftE --- apps/app/src/app/components/session/composer.tsx | 12 ++++++------ .../app/src/app/components/share-workspace-modal.tsx | 10 +++++----- apps/app/src/app/pages/dashboard.tsx | 10 ++++++---- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/apps/app/src/app/components/session/composer.tsx b/apps/app/src/app/components/session/composer.tsx index d9a7ce989..0bb394bc2 100644 --- a/apps/app/src/app/components/session/composer.tsx +++ b/apps/app/src/app/components/session/composer.tsx @@ -1766,7 +1766,7 @@ export default function Composer(props: ComposerProps) {
- Describe your task... + {translate("session.task_placeholder")}
- Run task + {translate("session.run_task")} } > @@ -1850,10 +1850,10 @@ export default function Composer(props: ComposerProps) { type="button" onClick={() => props.onStop()} class="inline-flex items-center gap-2 rounded-full bg-gray-12 px-4 py-2 text-[13px] font-medium text-gray-1 transition-colors hover:bg-gray-11" - title="Stop" + title={translate("session.stop")} > - Stop + {translate("session.stop")}
@@ -1874,7 +1874,7 @@ export default function Composer(props: ComposerProps) { onClick={props.onToggleAgentPicker} disabled={props.busy} aria-expanded={props.agentPickerOpen} - title="Agent" + title={translate("session.agent_label")} > {props.agentLabel} diff --git a/apps/app/src/app/components/share-workspace-modal.tsx b/apps/app/src/app/components/share-workspace-modal.tsx index aa4e61f3d..5b93ce4cc 100644 --- a/apps/app/src/app/components/share-workspace-modal.tsx +++ b/apps/app/src/app/components/share-workspace-modal.tsx @@ -301,8 +301,8 @@ export default function ShareWorkspaceModal(props: {
-

Workspace template

-

Share the core setup and workspace defaults.

+

{translate("share.template_section_title")}

+

{translate("share.template_section_desc")}

@@ -320,8 +320,8 @@ export default function ShareWorkspaceModal(props: { "share-workspace-profile", props.onShareWorkspaceProfile, props.shareWorkspaceProfileBusy, - "Create Template Link", - "Regenerate Link", + translate("share.create_template_link"), + translate("share.regenerate_link"), props.onShareWorkspaceProfile, props.shareWorkspaceProfileDisabledReason, )} @@ -333,7 +333,7 @@ export default function ShareWorkspaceModal(props: {
⚠️ - Share with trusted people only. These credentials grant live access to this workspace. + {translate("share.live_access_warning")}
diff --git a/apps/app/src/app/pages/dashboard.tsx b/apps/app/src/app/pages/dashboard.tsx index a5ed2b7dd..680d13c7b 100644 --- a/apps/app/src/app/pages/dashboard.tsx +++ b/apps/app/src/app/pages/dashboard.tsx @@ -74,6 +74,7 @@ import { Zap, } from "lucide-solid"; import type { Language } from "../../i18n"; +import { t, currentLocale } from "../../i18n"; export type DashboardViewProps = { tab: DashboardTab; @@ -373,6 +374,7 @@ type SkillsSetBundleV1 = { }; export default function DashboardView(props: DashboardViewProps) { + const translate = (key: string) => t(key, currentLocale()); const platform = usePlatform(); const webDeployment = createMemo(() => getOpenWorkDeployment() === "web"); const [mobileRightSidebarOpen, setMobileRightSidebarOpen] = createSignal(false); @@ -1132,8 +1134,8 @@ export default function DashboardView(props: DashboardViewProps) {