diff options
| author | Adam <[email protected]> | 2026-01-20 10:10:43 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2026-01-20 17:58:06 -0600 |
| commit | 7e8e4d9938fc473e187a80e0b8012b0d9bed2dc6 (patch) | |
| tree | 4fc8cb87e996669577f954dd39b9c62ee26d0393 /packages/app/src/pages/error.tsx | |
| parent | 7a359ff67c7e5cd86b638d8512e023ad2cf16b86 (diff) | |
| download | opencode-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.tsx | 104 |
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> |
