summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/pages
diff options
context:
space:
mode:
Diffstat (limited to 'packages/app/src/pages')
-rw-r--r--packages/app/src/pages/error.tsx32
-rw-r--r--packages/app/src/pages/layout.tsx2
-rw-r--r--packages/app/src/pages/session.tsx8
-rw-r--r--packages/app/src/pages/session/composer/session-composer-region.tsx1
-rw-r--r--packages/app/src/pages/session/composer/session-question-dock.tsx2
-rw-r--r--packages/app/src/pages/session/composer/session-todo-dock.tsx28
-rw-r--r--packages/app/src/pages/session/terminal-label.ts6
7 files changed, 49 insertions, 30 deletions
diff --git a/packages/app/src/pages/error.tsx b/packages/app/src/pages/error.tsx
index a30d86d18..11284b3d2 100644
--- a/packages/app/src/pages/error.tsx
+++ b/packages/app/src/pages/error.tsx
@@ -35,14 +35,14 @@ function isInitError(error: unknown): error is InitError {
)
}
-function safeJson(value: unknown): string {
+function safeJson(value: unknown, circular: string): string {
const seen = new WeakSet<object>()
const json = JSON.stringify(
value,
(_key, val) => {
if (typeof val === "bigint") return val.toString()
if (typeof val === "object" && val) {
- if (seen.has(val)) return "[Circular]"
+ if (seen.has(val)) return circular
seen.add(val)
}
return val
@@ -54,14 +54,15 @@ function safeJson(value: unknown): string {
function formatInitError(error: InitError, t: Translator): string {
const data = error.data
+ const json = (value: unknown) => safeJson(value, t("error.page.circular"))
switch (error.name) {
case "MCPFailed": {
const name = typeof data.name === "string" ? data.name : ""
return t("error.chain.mcpFailed", { name })
}
case "ProviderAuthError": {
- const providerID = typeof data.providerID === "string" ? data.providerID : "unknown"
- const message = typeof data.message === "string" ? data.message : safeJson(data.message)
+ const providerID = typeof data.providerID === "string" ? data.providerID : t("common.unknown")
+ const message = typeof data.message === "string" ? data.message : json(data.message)
return t("error.chain.providerAuthFailed", { provider: providerID, message })
}
case "APIError": {
@@ -101,24 +102,24 @@ function formatInitError(error: InitError, t: Translator): string {
].join("\n")
}
case "ProviderInitError": {
- const providerID = typeof data.providerID === "string" ? data.providerID : "unknown"
+ const providerID = typeof data.providerID === "string" ? data.providerID : t("common.unknown")
return t("error.chain.providerInitFailed", { provider: providerID })
}
case "ConfigJsonError": {
- const path = typeof data.path === "string" ? data.path : safeJson(data.path)
+ const path = typeof data.path === "string" ? data.path : json(data.path)
const message = typeof data.message === "string" ? data.message : ""
if (message) return t("error.chain.configJsonInvalidWithMessage", { path, message })
return t("error.chain.configJsonInvalid", { path })
}
case "ConfigDirectoryTypoError": {
- const path = typeof data.path === "string" ? data.path : safeJson(data.path)
- const dir = typeof data.dir === "string" ? data.dir : safeJson(data.dir)
- const suggestion = typeof data.suggestion === "string" ? data.suggestion : safeJson(data.suggestion)
+ const path = typeof data.path === "string" ? data.path : json(data.path)
+ const dir = typeof data.dir === "string" ? data.dir : json(data.dir)
+ const suggestion = typeof data.suggestion === "string" ? data.suggestion : json(data.suggestion)
return t("error.chain.configDirectoryTypo", { dir, path, suggestion })
}
case "ConfigFrontmatterError": {
- const path = typeof data.path === "string" ? data.path : safeJson(data.path)
- const message = typeof data.message === "string" ? data.message : safeJson(data.message)
+ const path = typeof data.path === "string" ? data.path : json(data.path)
+ const message = typeof data.message === "string" ? data.message : json(data.message)
return t("error.chain.configFrontmatterError", { path, message })
}
case "ConfigInvalidError": {
@@ -126,7 +127,7 @@ function formatInitError(error: InitError, t: Translator): string {
? data.issues.filter(isIssue).map((issue) => "↳ " + issue.message + " " + issue.path.join("."))
: []
const message = typeof data.message === "string" ? data.message : ""
- const path = typeof data.path === "string" ? data.path : safeJson(data.path)
+ const path = typeof data.path === "string" ? data.path : json(data.path)
const line = message
? t("error.chain.configInvalidWithMessage", { path, message })
@@ -135,14 +136,15 @@ function formatInitError(error: InitError, t: Translator): string {
return [line, ...issues].join("\n")
}
case "UnknownError":
- return typeof data.message === "string" ? data.message : safeJson(data)
+ return typeof data.message === "string" ? data.message : json(data)
default:
if (typeof data.message === "string") return data.message
- return safeJson(data)
+ return json(data)
}
}
function formatErrorChain(error: unknown, t: Translator, depth = 0, parentMessage?: string): string {
+ const json = (value: unknown) => safeJson(value, t("error.page.circular"))
if (!error) return t("error.chain.unknown")
if (isInitError(error)) {
@@ -204,7 +206,7 @@ function formatErrorChain(error: unknown, t: Translator, depth = 0, parentMessag
}
const indent = depth > 0 ? `\n${CHAIN_SEPARATOR}${t("error.chain.causedBy")}\n` : ""
- return indent + safeJson(error)
+ return indent + json(error)
}
function formatError(error: unknown, t: Translator): string {
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
index c1088622a..bc04f9ecf 100644
--- a/packages/app/src/pages/layout.tsx
+++ b/packages/app/src/pages/layout.tsx
@@ -2159,7 +2159,7 @@ export default function Layout(props: ParentProps) {
{language.t("command.provider.connect")}
</Button>
<Button size="large" variant="ghost" onClick={() => setStore("gettingStartedDismissed", true)}>
- Not yet
+ {language.t("toast.update.action.notYet")}
</Button>
</div>
</div>
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx
index 57ef1853d..d917ce4c7 100644
--- a/packages/app/src/pages/session.tsx
+++ b/packages/app/src/pages/session.tsx
@@ -956,13 +956,15 @@ export default function Page() {
return (
<div class={input.emptyClass}>
<div class="flex flex-col gap-3">
- <div class="text-14-medium text-text-strong">Create a Git repository</div>
+ <div class="text-14-medium text-text-strong">{language.t("session.review.noVcs.createGit.title")}</div>
<div class="text-14-regular text-text-base max-w-md" style={{ "line-height": "var(--line-height-normal)" }}>
- Track, review, and undo changes in this project
+ {language.t("session.review.noVcs.createGit.description")}
</div>
</div>
<Button size="large" disabled={ui.git} onClick={initGit}>
- {ui.git ? "Creating Git repository..." : "Create Git repository"}
+ {ui.git
+ ? language.t("session.review.noVcs.createGit.actionLoading")
+ : language.t("session.review.noVcs.createGit.action")}
</Button>
</div>
)
diff --git a/packages/app/src/pages/session/composer/session-composer-region.tsx b/packages/app/src/pages/session/composer/session-composer-region.tsx
index 84f77ea4a..a5263cd74 100644
--- a/packages/app/src/pages/session/composer/session-composer-region.tsx
+++ b/packages/app/src/pages/session/composer/session-composer-region.tsx
@@ -196,7 +196,6 @@ export function SessionComposerRegion(props: {
<SessionTodoDock
sessionID={route.params.id}
todos={props.state.todos()}
- title={language.t("session.todo.title")}
collapseLabel={language.t("session.todo.collapse")}
expandLabel={language.t("session.todo.expand")}
dockProgress={value()}
diff --git a/packages/app/src/pages/session/composer/session-question-dock.tsx b/packages/app/src/pages/session/composer/session-question-dock.tsx
index b22a92eb0..b66c27579 100644
--- a/packages/app/src/pages/session/composer/session-question-dock.tsx
+++ b/packages/app/src/pages/session/composer/session-question-dock.tsx
@@ -38,7 +38,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
const summary = createMemo(() => {
const n = Math.min(store.tab + 1, total())
- return `${n} of ${total()} questions`
+ return language.t("session.question.progress", { current: n, total: total() })
})
const last = createMemo(() => store.tab >= total() - 1)
diff --git a/packages/app/src/pages/session/composer/session-todo-dock.tsx b/packages/app/src/pages/session/composer/session-todo-dock.tsx
index 5500de97a..2cd660b39 100644
--- a/packages/app/src/pages/session/composer/session-todo-dock.tsx
+++ b/packages/app/src/pages/session/composer/session-todo-dock.tsx
@@ -9,6 +9,10 @@ import { TextStrikethrough } from "@opencode-ai/ui/text-strikethrough"
import { Index, createEffect, createMemo, on, onCleanup } from "solid-js"
import { createStore } from "solid-js/store"
import { composerEnabled, composerProbe } from "@/testing/session-composer"
+import { useLanguage } from "@/context/language"
+
+const doneToken = "\u0000done\u0000"
+const totalToken = "\u0000total\u0000"
function dot(status: Todo["status"]) {
if (status !== "in_progress") return undefined
@@ -38,11 +42,11 @@ function dot(status: Todo["status"]) {
export function SessionTodoDock(props: {
sessionID?: string
todos: Todo[]
- title: string
collapseLabel: string
expandLabel: string
dockProgress: number
}) {
+ const language = useLanguage()
const [store, setStore] = createStore({
collapsed: false,
height: 320,
@@ -52,7 +56,12 @@ export function SessionTodoDock(props: {
const total = createMemo(() => props.todos.length)
const done = createMemo(() => props.todos.filter((todo) => todo.status === "completed").length)
- const label = createMemo(() => `${done()} of ${total()} ${props.title.toLowerCase()} completed`)
+ const label = createMemo(() => language.t("session.todo.progress", { done: done(), total: total() }))
+ const progress = createMemo(() =>
+ language
+ .t("session.todo.progress", { done: doneToken, total: totalToken })
+ .split(/(\u0000done\u0000|\u0000total\u0000)/),
+ )
const active = createMemo(
() =>
@@ -137,10 +146,17 @@ export function SessionTodoDock(props: {
opacity: `${Math.max(0, Math.min(1, 1 - shut()))}`,
}}
>
- <AnimatedNumber value={done()} />
- <span class="mx-1">of</span>
- <AnimatedNumber value={total()} />
- <span>&nbsp;{props.title.toLowerCase()} completed</span>
+ <Index each={progress()}>
+ {(item) =>
+ item() === doneToken ? (
+ <AnimatedNumber value={done()} />
+ ) : item() === totalToken ? (
+ <AnimatedNumber value={total()} />
+ ) : (
+ <span>{item()}</span>
+ )
+ }
+ </Index>
</span>
<div
data-slot="session-todo-preview"
diff --git a/packages/app/src/pages/session/terminal-label.ts b/packages/app/src/pages/session/terminal-label.ts
index 6d336769b..8a34712e4 100644
--- a/packages/app/src/pages/session/terminal-label.ts
+++ b/packages/app/src/pages/session/terminal-label.ts
@@ -1,3 +1,5 @@
+import { isDefaultTitle as isDefaultTerminalTitle } from "@/context/terminal-title"
+
export const terminalTabLabel = (input: {
title?: string
titleNumber?: number
@@ -5,9 +7,7 @@ export const terminalTabLabel = (input: {
}) => {
const title = input.title ?? ""
const number = input.titleNumber ?? 0
- const match = title.match(/^Terminal (\d+)$/)
- const parsed = match ? Number(match[1]) : undefined
- const isDefaultTitle = Number.isFinite(number) && number > 0 && Number.isFinite(parsed) && parsed === number
+ const isDefaultTitle = Number.isFinite(number) && number > 0 && isDefaultTerminalTitle(title, number)
if (title && !isDefaultTitle) return title
if (number > 0) return input.t("terminal.title.numbered", { number })