diff --git a/apps/app/src/app/components/den-settings-panel.tsx b/apps/app/src/app/components/den-settings-panel.tsx index bf33410b9..6dc7a06b2 100644 --- a/apps/app/src/app/components/den-settings-panel.tsx +++ b/apps/app/src/app/components/den-settings-panel.tsx @@ -21,6 +21,7 @@ import { readDenTemplateCacheSnapshot, } from "../lib/den-template-cache"; import { usePlatform } from "../context/platform"; +import { t } from "../../i18n"; type DenSettingsPanelProps = { developerMode: boolean; @@ -55,18 +56,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", }; @@ -145,10 +146,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(() => { @@ -177,8 +178,8 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { platform.openLink(buildDenAuthUrl(baseUrl(), mode)); 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); }; @@ -300,7 +301,11 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { const applyBaseUrl = () => { const normalized = normalizeDenBaseUrl(baseUrlDraft()); if (!normalized) { +<<<<<<< HEAD + setBaseUrlError(t("den.invalid_url")); +======= setBaseUrlError("Enter a valid http:// or https:// Cloud control plane URL."); +>>>>>>> upstream/dev return; } @@ -313,7 +318,11 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { setBaseUrl(resolved.baseUrl); setBaseUrlDraft(resolved.baseUrl); +<<<<<<< HEAD + clearSignedInState(t("den.url_updated")); +======= clearSignedInState("Updated the Cloud control plane URL. Sign in again to continue."); +>>>>>>> upstream/dev }; const refreshOrgs = async (quiet = false) => { @@ -343,11 +352,11 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { }); 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); } @@ -367,14 +376,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); } @@ -431,7 +441,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; @@ -441,7 +451,11 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { clearSessionState(); } setAuthError( +<<<<<<< HEAD + error instanceof Error ? error.message : t("den.no_session"), +======= error instanceof Error ? error.message : "No active Cloud session found.", +>>>>>>> upstream/dev ); }) .finally(() => { @@ -484,13 +498,22 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { setAuthError(null); setStatusMessage( customEvent.detail.email?.trim() +<<<<<<< HEAD + ? t("den.connected_as").replace("{email}", customEvent.detail.email.trim()) + : t("den.connected"), +======= ? `Connected OpenWork Cloud as ${customEvent.detail.email.trim()}.` : "Connected OpenWork Cloud.", +>>>>>>> upstream/dev ); } else if (customEvent.detail?.status === "error") { setAuthError( customEvent.detail.message?.trim() || +<<<<<<< HEAD + t("den.sign_in_failed"), +======= "Failed to finish OpenWork Cloud sign-in.", +>>>>>>> upstream/dev ); } }; @@ -520,15 +543,19 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { setAuthBusy(false); } +<<<<<<< HEAD + clearSignedInState(t("den.signed_out_msg")); +======= clearSignedInState( "Signed out and cleared your OpenWork Cloud session on this device.", ); +>>>>>>> upstream/dev }; 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; } @@ -541,9 +568,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({ @@ -553,13 +578,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); @@ -626,6 +651,16 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
+<<<<<<< HEAD + {t("den.title")} +
+
+
+ {t("den.description")} +
+
+ {t("den.tagline")} +======= OpenWork Cloud
@@ -635,6 +670,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
Sign in to OpenWork Cloud to keep your tasks alive even when your computer sleeps. +>>>>>>> upstream/dev
@@ -649,11 +685,19 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
setBaseUrlDraft(event.currentTarget.value)} + placeholder={DEFAULT_DEN_BASE_URL} + hint={t("den.control_plane_url_hint")} +======= label="Cloud control plane URL" value={baseUrlDraft()} onInput={(event) => setBaseUrlDraft(event.currentTarget.value)} placeholder={DEFAULT_DEN_BASE_URL} hint="Developer mode only. Use this to target a local or self-hosted Cloud control plane. Changing it signs you out so the app can re-hydrate against the new control plane." +>>>>>>> upstream/dev disabled={authBusy() || sessionBusy()} />
@@ -663,7 +707,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { onClick={() => setBaseUrlDraft(baseUrl())} disabled={authBusy() || sessionBusy()} > - Reset + {t("den.reset")}
@@ -704,6 +748,16 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
+<<<<<<< HEAD +
+
+ {t("den.sign_in_title")} +
+
+ {t("den.tagline")} +
+
+=======
Sign in to OpenWork Cloud @@ -713,10 +767,11 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { computer sleeps.
+>>>>>>> upstream/dev
@@ -785,9 +839,13 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
+<<<<<<< HEAD +
{t("den.account_title")}
+=======
Cloud account
+>>>>>>> upstream/dev
- Manage your connected account and organization. + {t("den.account_description")}
@@ -808,15 +866,19 @@ 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")}
+<<<<<<< HEAD + {t("den.workers_scoped")} +======= Cloud workers and team templates are scoped to the selected org. +>>>>>>> upstream/dev
@@ -835,7 +897,11 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { activeOrgName: nextOrg?.name ?? null, }); setStatusMessage( +<<<<<<< HEAD + t("den.switched_org").replace("{org}", activeOrg()?.name ?? "the selected org"), +======= `Switched to ${nextOrg?.name ?? "the selected org"}.`, +>>>>>>> upstream/dev ); }} disabled={orgsBusy() || orgs().length === 0} @@ -843,7 +909,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { {(org) => ( )} @@ -874,17 +940,24 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
+<<<<<<< HEAD + {t("den.workers_title")} +======= Cloud workers +>>>>>>> upstream/dev
- Open workers directly into OpenWork using the same - remote-connect flow the app already uses elsewhere. + {t("den.workers_description")}
+<<<<<<< HEAD + {activeOrg()?.name || t("den.no_org_selected")} +======= {activeOrgName()} +>>>>>>> upstream/dev
@@ -908,8 +981,12 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
+<<<<<<< HEAD + {t("den.no_workers_visible")} +======= No cloud workers are visible for this org yet. Create one in Cloud, then refresh this tab. +>>>>>>> upstream/dev
@@ -930,13 +1007,18 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) { {status().label} +<<<<<<< HEAD + + {t("den.mine_badge")} +======= Mine +>>>>>>> upstream/dev
- {worker.provider ? `${worker.provider} worker` : "Cloud worker"} + {worker.provider ? t("den.worker_type").replace("{provider}", worker.provider) : t("den.cloud_worker")} {(value) => · {value()}} @@ -949,9 +1031,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/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/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/model-picker-modal.tsx b/apps/app/src/app/components/model-picker-modal.tsx index 0ce5df734..e7d10fac0 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,17 @@ 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" +<<<<<<< HEAD + ? translate("model_picker.default_desc") + : translate("model_picker.chat_desc")} +======= ? "Choose the default model for new chats, then fine-tune reasoning profiles on its card before pressing Done." : "Choose the model for this chat. If a model supports reasoning profiles, configure them on its card."} +>>>>>>> upstream/dev

diff --git a/apps/app/src/app/components/question-modal.tsx b/apps/app/src/app/components/question-modal.tsx index 4b3b070bd..52a0269d5 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,15 +211,15 @@ export default function QuestionModal(props: QuestionModalProps) {
↑↓ - navigate + {translate("question.navigate_hint")} - select + {translate("question.select_hint")}
+ + +======= +>>>>>>> upstream/dev
@@ -1671,14 +1687,33 @@ export default function Composer(props: ComposerProps) {
+<<<<<<< HEAD + +
+
+ {props.toast} + + + +
+
+
+======= +>>>>>>> upstream/dev
- Describe your task... + {translate("session.task_placeholder")}
- Run task + {translate("session.run_task")} } > @@ -1762,10 +1797,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")}
@@ -1786,7 +1821,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} @@ -1795,14 +1830,14 @@ export default function Composer(props: ComposerProps) {
- Agent + {translate("session.agent_label")}
event.preventDefault()}> Loading agents...
+
{translate("session.loading_agents")}
} > @@ -1817,7 +1852,7 @@ export default function Composer(props: ComposerProps) { props.onSelectAgent(null); }} > - Default agent + {translate("session.default_agent")} diff --git a/apps/app/src/app/components/session/context-panel.tsx b/apps/app/src/app/components/session/context-panel.tsx index 40633ca7f..5683a991f 100644 --- a/apps/app/src/app/components/session/context-panel.tsx +++ b/apps/app/src/app/components/session/context-panel.tsx @@ -1,7 +1,11 @@ import { For, Show } from "solid-js"; import { ChevronDown, Circle, File, Folder, Package } from "lucide-solid"; +<<<<<<< HEAD +import { t } from "../../../i18n"; +======= import { useConnections } from "../../connections/provider"; +>>>>>>> upstream/dev import { SUGGESTED_PLUGINS } from "../../constants"; import type { McpStatus, SkillCard } from "../../types"; import { stripPluginVersion } from "../../utils/plugins"; @@ -106,19 +110,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"); } }; @@ -151,7 +155,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) => { @@ -203,7 +207,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")}
} > @@ -253,7 +257,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")} +<<<<<<< HEAD + {props.mcpStatus ?? t("session.ctx_no_mcp")} +======= {connections.mcpStatus() ?? "No MCP servers loaded."} +>>>>>>> upstream/dev
} > @@ -303,7 +311,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")}
} > @@ -352,7 +360,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 4c3605304..f4158384e 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 639cc22b5..a007d7ef7 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; @@ -266,21 +267,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")}
} > @@ -356,7 +357,7 @@ export default function SessionSidebar(props: SidebarProps) { - {isSandboxWorkspace() ? "Sandbox" : "Remote"} + {isSandboxWorkspace() ? t("session.sidebar_sandbox") : t("session.sidebar_remote")}
@@ -373,11 +374,11 @@ export default function SessionSidebar(props: SidebarProps) { - Needs attention + {t("session.sidebar_needs_attention")} - Switch}> - Active + {t("session.sidebar_switch")}}> + {t("session.sidebar_active")} @@ -389,7 +390,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} @@ -424,7 +425,7 @@ export default function SessionSidebar(props: SidebarProps) { disabled={isActivelyConnecting()} > - Edit connection + {t("session.sidebar_edit_connection")} @@ -444,7 +445,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")}
} > @@ -520,8 +521,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))}
@@ -542,8 +543,51 @@ export default function SessionSidebar(props: SidebarProps) { onDrop={(event) => handleDrop(event, null)} > +<<<<<<< HEAD + {t("session.sidebar_add_workspace")} + + +
+ + + +
+
+======= Add workspace +>>>>>>> upstream/dev
@@ -555,7 +599,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 0851ac42e..d375964f0 100644 --- a/apps/app/src/app/components/session/workspace-session-list.tsx +++ b/apps/app/src/app/components/session/workspace-session-list.tsx @@ -25,6 +25,7 @@ import { getWorkspaceTaskLoadErrorDisplay, isWindowsPlatform, } from "../../utils"; +import { t } from "../../../i18n"; type Props = { workspaceSessionGroups: WorkspaceSessionGroup[]; @@ -159,16 +160,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"]; @@ -184,8 +185,14 @@ const workspaceSwatchColor = (seed: string) => { export default function WorkspaceSessionList(props: Props) { const revealLabel = isWindowsPlatform() +<<<<<<< HEAD + ? t("session.reveal_explorer") + : t("session.reveal_finder"); + const newWorkspaceDesktopOnly = getOpenWorkDeployment() === "web"; +======= ? "Reveal in Explorer" : "Reveal in Finder"; +>>>>>>> upstream/dev const [expandedWorkspaceIds, setExpandedWorkspaceIds] = createSignal< Set >(new Set()); @@ -286,7 +293,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(() => { @@ -380,7 +387,7 @@ export default function WorkspaceSessionList(props: Props) {
@@ -451,7 +458,7 @@ export default function WorkspaceSessionList(props: Props) { props.onOpenDeleteSession?.(); }} > - Delete session + {t("session.delete_session")}
@@ -493,9 +500,13 @@ 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 ""; +<<<<<<< HEAD + if (props.activeWorkspaceId === workspace().id) return t("session.status_active"); +======= if (props.selectedWorkspaceId === workspace().id) return "Selected"; +>>>>>>> upstream/dev return workspaceKindLabel(workspace()); }; const statusTone = () => { @@ -569,7 +580,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")} > @@ -585,7 +596,7 @@ export default function WorkspaceSessionList(props: Props) { : workspace().id, ); }} - aria-label="Workspace options" + aria-label={t("session.workspace_options")} > @@ -596,8 +607,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(); @@ -628,7 +639,7 @@ export default function WorkspaceSessionList(props: Props) { setWorkspaceMenuId(null); }} > - Edit name + {t("session.edit_name")}
@@ -810,10 +821,10 @@ export default function WorkspaceSessionList(props: Props) { disabled={props.newTaskDisabled} > - No tasks yet. + {t("session.no_tasks_yet")} @@ -844,7 +855,7 @@ export default function WorkspaceSessionList(props: Props) { } >
- Loading tasks... + {t("session.loading_tasks")}
@@ -864,8 +875,63 @@ export default function WorkspaceSessionList(props: Props) { onClick={props.onOpenCreateWorkspace} > - Add workspace + {t("session.add_workspace")} +<<<<<<< HEAD + + +
+ + + +
+
+======= +>>>>>>> upstream/dev
); diff --git a/apps/app/src/app/components/workspace-right-sidebar.tsx b/apps/app/src/app/components/workspace-right-sidebar.tsx index ef661b196..9142748f4 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.settingsTab === "automations", props.onOpenAutomations, @@ -114,7 +116,7 @@ export default function WorkspaceRightSidebar(props: Props) { props.onOpenSkills, )} {sidebarButton( - "Extensions", + translate("session.sidebar_extensions"), , showSelection() && props.settingsTab === "extensions", props.onOpenExtensions, diff --git a/apps/app/src/app/context/providers/provider-auth-modal.tsx b/apps/app/src/app/context/providers/provider-auth-modal.tsx index 3b6248cf3..0fe56c0f8 100644 --- a/apps/app/src/app/context/providers/provider-auth-modal.tsx +++ b/apps/app/src/app/context/providers/provider-auth-modal.tsx @@ -1,5 +1,11 @@ import { CheckCircle2, Loader2, X, Search, ChevronRight } from "lucide-solid"; import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js"; +<<<<<<< HEAD:apps/app/src/app/components/provider-auth-modal.tsx +import { isTauriRuntime } from "../utils"; +import { compareProviders } from "../utils/providers"; +import { t, currentLocale } from "../../i18n"; +======= +>>>>>>> upstream/dev:apps/app/src/app/context/providers/provider-auth-modal.tsx import type { ProviderListItem } from "../../types"; import { isTauriRuntime } from "../../utils"; @@ -9,6 +15,16 @@ import ProviderIcon from "../../components/provider-icon"; import TextInput from "../../components/text-input"; import type { ProviderAuthMethod, ProviderOAuthStartResult } from "./store"; +<<<<<<< HEAD:apps/app/src/app/components/provider-auth-modal.tsx +const translate = (key: string) => t(key, currentLocale()); + +export type ProviderAuthMethod = { + type: "oauth" | "api"; + label: string; + methodIndex?: number; +}; +======= +>>>>>>> upstream/dev:apps/app/src/app/context/providers/provider-auth-modal.tsx type ProviderAuthEntry = { id: string; name: string; @@ -117,7 +133,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; @@ -593,14 +609,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")}

@@ -613,7 +629,7 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) { fallback={
- Loading providers... + {translate("provider.loading")}
} @@ -633,7 +649,7 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) { { setSearchQuery(event.currentTarget.value); @@ -651,7 +667,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")}
} > @@ -690,7 +706,7 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) { >
- Connected + {translate("provider.connected")}
@@ -719,7 +735,7 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) { -
Arrow keys to navigate, Enter to select.
+
{translate("provider.keyboard_hint")}
@@ -728,10 +744,10 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) {
{selectedEntry()!.name}
-
Choose how you'd like to connect.
+
{translate("provider.choose_connect")}
@@ -764,13 +780,13 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) {
Paste your API key to connect.
{ setApiKeyInput(event.currentTarget.value); @@ -783,7 +799,7 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) { /> 0}>
- Env vars: {selectedEntry()!.env.join(", ")} + {translate("provider.env_vars_label")}{selectedEntry()!.env.join(", ")}
@@ -809,7 +825,7 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) {
Finish OAuth by pasting the authorization code.
@@ -821,9 +837,9 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) {
{ setOauthCodeInput(event.currentTarget.value); @@ -865,16 +881,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")}
} >
@@ -883,13 +899,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()}
+<<<<<<< HEAD:apps/app/src/app/components/provider-auth-modal.tsx +
+ + {translate("provider.checking_status")} +
+======= Authorization checks will start after you click Open Browser.
+>>>>>>> upstream/dev:apps/app/src/app/context/providers/provider-auth-modal.tsx
@@ -937,7 +960,7 @@ export default function ProviderAuthModal(props: ProviderAuthModalProps) { {submittingLabel()} diff --git a/apps/app/src/app/pages/settings.tsx b/apps/app/src/app/pages/settings.tsx index 840bcbcce..2af49c1b8 100644 --- a/apps/app/src/app/pages/settings.tsx +++ b/apps/app/src/app/pages/settings.tsx @@ -238,11 +238,10 @@ export function OpenCodeRouterSettings(_props: {
-
Messaging
+
{t("settings.messaging")}
- Manage Telegram/Slack identities and bindings in the{" "} - Identities tab. + {t("settings.identities_tab_hint")}
); @@ -1587,8 +1586,142 @@ export default function SettingsView(props: SettingsViewProps) { onClick={modelControls.openDefaultModelPicker} disabled={props.busy} > +<<<<<<< HEAD +
+ + {(hint) => ( +
+ {hint()} +
+ )} +
+ + 0} + fallback={ +
+
+ +
+
{t("settings.no_authorized_folders")}
+
+ Add a folder to let this workspace read and edit files outside its root directory. +
+
+ } + > +
+ + {(folder) => { + const folderName = folder.split(/[/\\]/).filter(Boolean).pop() || folder; + return ( +
+
+
+ +
+
+ {folderName} + {folder} +
+
+ +
+ ); + }} +
+
+
+ + + {(status) => ( +
+ {status()} +
+ )} +
+ + {(error) => ( +
+ {error()} +
+ )} +
+ +
{ + event.preventDefault(); + void props.addAuthorizedFolder(); + }} + > +
+ + props.setAuthorizedFolderDraft(event.currentTarget.value) + } + onPaste={(event) => { + event.preventDefault(); + }} + placeholder={t("settings.folder_path_placeholder")} + disabled={ + props.authorizedFoldersLoading || + props.authorizedFoldersSaving || + !props.authorizedFoldersEditable + } + /> +
+ + + + + + +
+
+ +======= Change +>>>>>>> upstream/dev
@@ -1867,7 +2000,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). @@ -1888,18 +2021,113 @@ export default function SettingsView(props: SettingsViewProps) { +<<<<<<< HEAD + + + + +
+
+
+
Model
+
+ Pick the default chat model and review how it reasons. +
+
+ +
+
+
+ {props.defaultModelLabel} +
+
+ {props.defaultModelRef} +
+
+ +
+ +
+
+
{t("settings.show_model_reasoning")}
+
+ Expand reasoning traces in the UI when a model exposes them. +
+
+ +
+ +
+
+
+ Auto context compaction +
+
+ Automatically compact after a run completes. +
+
+ +
+ +
+
+
{t("settings.model_behavior")}
+
+ Open the default model picker to choose reasoning profiles when they are available. +
+
+ {props.modelVariantLabel} +
+
+ +
+
+
+======= +>>>>>>> upstream/dev
-
Runtime
+
{t("settings.runtime")}
Status for your local engine and OpenWork server.
@@ -1965,7 +2193,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. @@ -1987,7 +2215,7 @@ export default function SettingsView(props: SettingsViewProps) {
-
Developer mode
+
{t("settings.developer_mode")}
Enables debug tools, diagnostics, and the Developer tab.
@@ -2064,6 +2292,34 @@ export default function SettingsView(props: SettingsViewProps) { debugDeepLinkBusy() || !debugDeepLinkInput().trim() } +<<<<<<< HEAD + rows={3} + 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" + /> +
+ +
+ Accepts openwork://,{" "} + openwork-dev://, or a + raw supported{" "} + + https://share.openwork.software/b/... + {" "} + URL. +
+======= > {debugDeepLinkBusy() ? "Opening..." : "Open deeplink"} @@ -2075,6 +2331,7 @@ export default function SettingsView(props: SettingsViewProps) { https://share.openworklabs.com/b/... {" "} URL. +>>>>>>> upstream/dev
diff --git a/apps/app/src/app/shell/settings-shell.tsx b/apps/app/src/app/shell/settings-shell.tsx index 38255ef56..5a76a204e 100644 --- a/apps/app/src/app/shell/settings-shell.tsx +++ b/apps/app/src/app/shell/settings-shell.tsx @@ -64,6 +64,7 @@ import { Zap, } from "lucide-solid"; import type { Language } from "../../i18n"; +import { t, currentLocale } from "../../i18n"; export type SettingsShellProps = { settingsTab: SettingsTab; @@ -257,9 +258,41 @@ export type SettingsShellProps = { openDebugDeepLink: (rawUrl: string) => Promise<{ ok: boolean; message: string }>; }; +<<<<<<< HEAD:apps/app/src/app/pages/dashboard.tsx +type SharedSkillItem = { + name: string; + description?: string; + content: string; + trigger?: string; +}; + +type WorkspaceProfileBundleV1 = { + schemaVersion: 1; + type: "workspace-profile"; + name: string; + description: string; + workspace: OpenworkWorkspaceExport; +}; + +type SkillsSetBundleV1 = { + schemaVersion: 1; + type: "skills-set"; + name: string; + description: string; + skills: SharedSkillItem[]; + sourceWorkspace?: { + id?: string; + name?: string; + }; +}; + +export default function DashboardView(props: DashboardViewProps) { + const translate = (key: string) => t(key, currentLocale()); +======= export default function SettingsShell(props: SettingsShellProps) { const connections = useConnections(); const extensions = useExtensions(); +>>>>>>> upstream/dev:apps/app/src/app/shell/settings-shell.tsx const platform = usePlatform(); const webDeployment = createMemo(() => getOpenWorkDeployment() === "web"); const title = createMemo(() => { @@ -1060,8 +1093,8 @@ export default function SettingsShell(props: SettingsShellProps) {