summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/pages/error.tsx
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-01-20 10:10:43 -0600
committerAdam <[email protected]>2026-01-20 17:58:06 -0600
commit7e8e4d9938fc473e187a80e0b8012b0d9bed2dc6 (patch)
tree4fc8cb87e996669577f954dd39b9c62ee26d0393 /packages/app/src/pages/error.tsx
parent7a359ff67c7e5cd86b638d8512e023ad2cf16b86 (diff)
downloadopencode-7e8e4d9938fc473e187a80e0b8012b0d9bed2dc6.tar.gz
opencode-7e8e4d9938fc473e187a80e0b8012b0d9bed2dc6.zip
wip(app): i18n
Diffstat (limited to 'packages/app/src/pages/error.tsx')
-rw-r--r--packages/app/src/pages/error.tsx104
1 files changed, 66 insertions, 38 deletions
diff --git a/packages/app/src/pages/error.tsx b/packages/app/src/pages/error.tsx
index 4326206d8..460c87295 100644
--- a/packages/app/src/pages/error.tsx
+++ b/packages/app/src/pages/error.tsx
@@ -4,6 +4,7 @@ import { Button } from "@opencode-ai/ui/button"
import { Component, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { usePlatform } from "@/context/platform"
+import { useLanguage } from "@/context/language"
import { Icon } from "@opencode-ai/ui/icon"
export type InitError = {
@@ -11,6 +12,8 @@ export type InitError = {
data: Record<string, unknown>
}
+type Translator = ReturnType<typeof useLanguage>["t"]
+
function isInitError(error: unknown): error is InitError {
return (
typeof error === "object" &&
@@ -38,30 +41,32 @@ function safeJson(value: unknown): string {
return json ?? String(value)
}
-function formatInitError(error: InitError): string {
+function formatInitError(error: InitError, t: Translator): string {
const data = error.data
switch (error.name) {
- case "MCPFailed":
- return `MCP server "${data.name}" failed. Note, opencode does not support MCP authentication yet.`
+ 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)
- return `Provider authentication failed (${providerID}): ${message}`
+ return t("error.chain.providerAuthFailed", { provider: providerID, message })
}
case "APIError": {
- const message = typeof data.message === "string" ? data.message : "API error"
+ const message = typeof data.message === "string" ? data.message : t("error.chain.apiError")
const lines: string[] = [message]
if (typeof data.statusCode === "number") {
- lines.push(`Status: ${data.statusCode}`)
+ lines.push(t("error.chain.status", { status: data.statusCode }))
}
if (typeof data.isRetryable === "boolean") {
- lines.push(`Retryable: ${data.isRetryable}`)
+ lines.push(t("error.chain.retryable", { retryable: data.isRetryable }))
}
if (typeof data.responseBody === "string" && data.responseBody) {
- lines.push(`Response body:\n${data.responseBody}`)
+ lines.push(t("error.chain.responseBody", { body: data.responseBody }))
}
return lines.join("\n")
@@ -72,24 +77,38 @@ function formatInitError(error: InitError): string {
modelID: string
suggestions?: string[]
}
+
+ const suggestionsLine = Array.isArray(suggestions) && suggestions.length
+ ? [t("error.chain.didYouMean", { suggestions: suggestions.join(", ") })]
+ : []
+
return [
- `Model not found: ${providerID}/${modelID}`,
- ...(Array.isArray(suggestions) && suggestions.length ? ["Did you mean: " + suggestions.join(", ")] : []),
- `Check your config (opencode.json) provider/model names`,
+ t("error.chain.modelNotFound", { provider: providerID, model: modelID }),
+ ...suggestionsLine,
+ t("error.chain.checkConfig"),
].join("\n")
}
case "ProviderInitError": {
const providerID = typeof data.providerID === "string" ? data.providerID : "unknown"
- return `Failed to initialize provider "${providerID}". Check credentials and configuration.`
+ return t("error.chain.providerInitFailed", { provider: providerID })
}
case "ConfigJsonError": {
+ const path = typeof data.path === "string" ? data.path : safeJson(data.path)
const message = typeof data.message === "string" ? data.message : ""
- return `Config file at ${data.path} is not valid JSON(C)` + (message ? `: ${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)
+ 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)
+ return t("error.chain.configFrontmatterError", { path, message })
}
- case "ConfigDirectoryTypoError":
- return `Directory "${data.dir}" in ${data.path} is not valid. Rename the directory to "${data.suggestion}" or remove it. This is a common typo.`
- case "ConfigFrontmatterError":
- return `Failed to parse frontmatter in ${data.path}:\n${data.message}`
case "ConfigInvalidError": {
const issues = Array.isArray(data.issues)
? data.issues.map(
@@ -97,7 +116,13 @@ function formatInitError(error: InitError): string {
)
: []
const message = typeof data.message === "string" ? data.message : ""
- return [`Config file at ${data.path} is invalid` + (message ? `: ${message}` : ""), ...issues].join("\n")
+ const path = typeof data.path === "string" ? data.path : safeJson(data.path)
+
+ const line = message
+ ? t("error.chain.configInvalidWithMessage", { path, message })
+ : t("error.chain.configInvalid", { path })
+
+ return [line, ...issues].join("\n")
}
case "UnknownError":
return typeof data.message === "string" ? data.message : safeJson(data)
@@ -107,20 +132,20 @@ function formatInitError(error: InitError): string {
}
}
-function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): string {
- if (!error) return "Unknown error"
+function formatErrorChain(error: unknown, t: Translator, depth = 0, parentMessage?: string): string {
+ if (!error) return t("error.chain.unknown")
if (isInitError(error)) {
- const message = formatInitError(error)
+ const message = formatInitError(error, t)
if (depth > 0 && parentMessage === message) return ""
- const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
+ const indent = depth > 0 ? `\n${"─".repeat(40)}\n${t("error.chain.causedBy")}\n` : ""
return indent + `${error.name}\n${message}`
}
if (error instanceof Error) {
const isDuplicate = depth > 0 && parentMessage === error.message
const parts: string[] = []
- const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
+ const indent = depth > 0 ? `\n${"─".repeat(40)}\n${t("error.chain.causedBy")}\n` : ""
const header = `${error.name}${error.message ? `: ${error.message}` : ""}`
const stack = error.stack?.trim()
@@ -153,7 +178,7 @@ function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): st
}
if (error.cause) {
- const causeResult = formatErrorChain(error.cause, depth + 1, error.message)
+ const causeResult = formatErrorChain(error.cause, t, depth + 1, error.message)
if (causeResult) {
parts.push(causeResult)
}
@@ -164,16 +189,16 @@ function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): st
if (typeof error === "string") {
if (depth > 0 && parentMessage === error) return ""
- const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
+ const indent = depth > 0 ? `\n${"─".repeat(40)}\n${t("error.chain.causedBy")}\n` : ""
return indent + error
}
- const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
+ const indent = depth > 0 ? `\n${"─".repeat(40)}\n${t("error.chain.causedBy")}\n` : ""
return indent + safeJson(error)
}
-function formatError(error: unknown): string {
- return formatErrorChain(error, 0)
+function formatError(error: unknown, t: Translator): string {
+ return formatErrorChain(error, t, 0)
}
interface ErrorPageProps {
@@ -182,6 +207,7 @@ interface ErrorPageProps {
export const ErrorPage: Component<ErrorPageProps> = (props) => {
const platform = usePlatform()
+ const language = useLanguage()
const [store, setStore] = createStore({
checking: false,
version: undefined as string | undefined,
@@ -206,51 +232,53 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
<div class="w-2/3 max-w-3xl flex flex-col items-center justify-center gap-8">
<Logo class="w-58.5 opacity-12 shrink-0" />
<div class="flex flex-col items-center gap-2 text-center">
- <h1 class="text-lg font-medium text-text-strong">Something went wrong</h1>
- <p class="text-sm text-text-weak">An error occurred while loading the application.</p>
+ <h1 class="text-lg font-medium text-text-strong">{language.t("error.page.title")}</h1>
+ <p class="text-sm text-text-weak">{language.t("error.page.description")}</p>
</div>
<TextField
- value={formatError(props.error)}
+ value={formatError(props.error, language.t)}
readOnly
copyable
multiline
class="max-h-96 w-full font-mono text-xs no-scrollbar"
- label="Error Details"
+ label={language.t("error.page.details.label")}
hideLabel
/>
<div class="flex items-center gap-3">
<Button size="large" onClick={platform.restart}>
- Restart
+ {language.t("error.page.action.restart")}
</Button>
<Show when={platform.checkUpdate}>
<Show
when={store.version}
fallback={
<Button size="large" variant="ghost" onClick={checkForUpdates} disabled={store.checking}>
- {store.checking ? "Checking..." : "Check for updates"}
+ {store.checking ? language.t("error.page.action.checking") : language.t("error.page.action.checkUpdates")}
</Button>
}
>
<Button size="large" onClick={installUpdate}>
- Update to {store.version}
+ {language.t("error.page.action.updateTo", { version: store.version ?? "" })}
</Button>
</Show>
</Show>
</div>
<div class="flex flex-col items-center gap-2">
<div class="flex items-center justify-center gap-1">
- Please report this error to the OpenCode team
+ {language.t("error.page.report.prefix")}
<button
type="button"
class="flex items-center text-text-interactive-base gap-1"
onClick={() => platform.openLink("https://opencode.ai/desktop-feedback")}
>
- <div>on Discord</div>
+ <div>{language.t("error.page.report.discord")}</div>
<Icon name="discord" class="text-text-interactive-base" />
</button>
</div>
<Show when={platform.version}>
- <p class="text-xs text-text-weak">Version: {platform.version}</p>
+ {(version) => (
+ <p class="text-xs text-text-weak">{language.t("error.page.version", { version: version() })}</p>
+ )}
</Show>
</div>
</div>