diff options
| author | Adam <[email protected]> | 2026-01-20 15:00:46 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2026-01-20 17:58:06 -0600 |
| commit | b13c269162e6c858acbce6cc792636cd4cb921a9 (patch) | |
| tree | d4e81f96109919ce41b36c175bd8da9aba5506ee /packages/ui/src | |
| parent | ef36af0e55d814dc80893af923886ccff40f46e5 (diff) | |
| download | opencode-b13c269162e6c858acbce6cc792636cd4cb921a9.tar.gz opencode-b13c269162e6c858acbce6cc792636cd4cb921a9.zip | |
wip(app): i18n
Diffstat (limited to 'packages/ui/src')
| -rw-r--r-- | packages/ui/src/components/session-review.tsx | 12 | ||||
| -rw-r--r-- | packages/ui/src/components/session-turn.tsx | 52 | ||||
| -rw-r--r-- | packages/ui/src/context/i18n.tsx | 5 |
3 files changed, 41 insertions, 28 deletions
diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index be5181a98..c4be87a0b 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -6,6 +6,7 @@ import { FileIcon } from "./file-icon" import { Icon } from "./icon" import { StickyAccordionHeader } from "./sticky-accordion-header" import { useDiffComponent } from "../context/diff" +import { useI18n } from "../context/i18n" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { For, Match, Show, Switch, type JSX } from "solid-js" import { createStore } from "solid-js/store" @@ -32,6 +33,7 @@ export interface SessionReviewProps { } export const SessionReview = (props: SessionReviewProps) => { + const i18n = useI18n() const diffComponent = useDiffComponent() const [store, setStore] = createStore({ open: props.diffs.length > 10 ? [] : props.diffs.map((d) => d.file), @@ -68,21 +70,23 @@ export const SessionReview = (props: SessionReviewProps) => { [props.classes?.header ?? ""]: !!props.classes?.header, }} > - <div data-slot="session-review-title">Session changes</div> + <div data-slot="session-review-title">{i18n.t("ui.sessionReview.title")}</div> <div data-slot="session-review-actions"> <Show when={props.onDiffStyleChange}> <RadioGroup options={["unified", "split"] as const} current={diffStyle()} value={(style) => style} - label={(style) => (style === "unified" ? "Unified" : "Split")} + label={(style) => + i18n.t(style === "unified" ? "ui.sessionReview.diffStyle.unified" : "ui.sessionReview.diffStyle.split") + } onSelect={(style) => style && props.onDiffStyleChange?.(style)} /> </Show> <Button size="normal" icon="chevron-grabber-vertical" onClick={handleExpandOrCollapseAll}> <Switch> - <Match when={open().length > 0}>Collapse all</Match> - <Match when={true}>Expand all</Match> + <Match when={open().length > 0}>{i18n.t("ui.sessionReview.collapseAll")}</Match> + <Match when={true}>{i18n.t("ui.sessionReview.expandAll")}</Match> </Switch> </Button> {props.actions} diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index 21d00cf00..737f4f41a 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -9,6 +9,7 @@ import { } from "@opencode-ai/sdk/v2/client" import { useData } from "../context" import { useDiffComponent } from "../context/diff" +import { type UiI18nKey, type UiI18nParams, useI18n } from "../context/i18n" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { Binary } from "@opencode-ai/util/binary" @@ -29,29 +30,31 @@ import { DateTime, DurationUnit, Interval } from "luxon" import { createAutoScroll } from "../hooks" import { createResizeObserver } from "@solid-primitives/resize-observer" -function computeStatusFromPart(part: PartType | undefined): string | undefined { +type Translator = (key: UiI18nKey, params?: UiI18nParams) => string + +function computeStatusFromPart(part: PartType | undefined, t: Translator): string | undefined { if (!part) return undefined if (part.type === "tool") { switch (part.tool) { case "task": - return "Delegating work" + return t("ui.sessionTurn.status.delegating") case "todowrite": case "todoread": - return "Planning next steps" + return t("ui.sessionTurn.status.planning") case "read": - return "Gathering context" + return t("ui.sessionTurn.status.gatheringContext") case "list": case "grep": case "glob": - return "Searching the codebase" + return t("ui.sessionTurn.status.searchingCodebase") case "webfetch": - return "Searching the web" + return t("ui.sessionTurn.status.searchingWeb") case "edit": case "write": - return "Making edits" + return t("ui.sessionTurn.status.makingEdits") case "bash": - return "Running commands" + return t("ui.sessionTurn.status.runningCommands") default: return undefined } @@ -59,11 +62,11 @@ function computeStatusFromPart(part: PartType | undefined): string | undefined { if (part.type === "reasoning") { const text = part.text ?? "" const match = text.trimStart().match(/^\*\*(.+?)\*\*/) - if (match) return `Thinking · ${match[1].trim()}` - return "Thinking" + if (match) return t("ui.sessionTurn.status.thinkingWithTopic", { topic: match[1].trim() }) + return t("ui.sessionTurn.status.thinking") } if (part.type === "text") { - return "Gathering thoughts" + return t("ui.sessionTurn.status.gatheringThoughts") } return undefined } @@ -133,6 +136,7 @@ export function SessionTurn( } }>, ) { + const i18n = useI18n() const data = useData() const diffComponent = useDiffComponent() @@ -328,12 +332,12 @@ export function SessionTurn( const msgParts = data.store.part[msg.id] ?? emptyParts for (let pi = msgParts.length - 1; pi >= 0; pi--) { const part = msgParts[pi] - if (part) return computeStatusFromPart(part) + if (part) return computeStatusFromPart(part, i18n.t) } } } - return computeStatusFromPart(last) + return computeStatusFromPart(last, i18n.t) }) const status = createMemo(() => data.store.session_status[props.sessionID] ?? idle) @@ -368,7 +372,7 @@ export function SessionTurn( const interval = Interval.fromDateTimes(from, to) const unit: DurationUnit[] = interval.length("seconds") > 60 ? ["minutes", "seconds"] : ["seconds"] - return interval.toDuration(unit).normalize().toHuman({ + return interval.toDuration(unit).normalize().reconfigure({ locale: i18n.locale() }).toHuman({ notation: "compact", unitDisplay: "narrow", compactDisplay: "short", @@ -532,13 +536,18 @@ export function SessionTurn( })()} </span> <span data-slot="session-turn-retry-seconds"> - · retrying {store.retrySeconds > 0 ? `in ${store.retrySeconds}s ` : ""} + · {i18n.t("ui.sessionTurn.retry.retrying")} + {store.retrySeconds > 0 + ? " " + i18n.t("ui.sessionTurn.retry.inSeconds", { seconds: store.retrySeconds }) + : ""} </span> <span data-slot="session-turn-retry-attempt">(#{retry()?.attempt})</span> </Match> - <Match when={working()}>{store.status ?? "Considering next steps"}</Match> - <Match when={props.stepsExpanded}>Hide steps</Match> - <Match when={!props.stepsExpanded}>Show steps</Match> + <Match when={working()}> + {store.status ?? i18n.t("ui.sessionTurn.status.consideringNextSteps")} + </Match> + <Match when={props.stepsExpanded}>{i18n.t("ui.sessionTurn.steps.hide")}</Match> + <Match when={!props.stepsExpanded}>{i18n.t("ui.sessionTurn.steps.show")}</Match> </Switch> <span>·</span> <span>{store.duration}</span> @@ -580,7 +589,7 @@ export function SessionTurn( <Show when={!working() && (response() || hasDiffs())}> <div data-slot="session-turn-summary-section"> <div data-slot="session-turn-summary-header"> - <h2 data-slot="session-turn-summary-title">Response</h2> + <h2 data-slot="session-turn-summary-title">{i18n.t("ui.sessionTurn.summary.response")}</h2> <Markdown data-slot="session-turn-markdown" data-diffs={hasDiffs()} @@ -657,8 +666,9 @@ export function SessionTurn( }) }} > - Show more changes ( - {(data.store.session_diff?.[props.sessionID]?.length ?? 0) - store.diffLimit}) + {i18n.t("ui.sessionTurn.diff.showMore", { + count: (data.store.session_diff?.[props.sessionID]?.length ?? 0) - store.diffLimit, + })} </Button> </Show> </div> diff --git a/packages/ui/src/context/i18n.tsx b/packages/ui/src/context/i18n.tsx index fd8b05d3c..a2ff0f37b 100644 --- a/packages/ui/src/context/i18n.tsx +++ b/packages/ui/src/context/i18n.tsx @@ -3,7 +3,7 @@ import { dict as en } from "../i18n/en" export type UiI18nKey = keyof typeof en -export type UiI18nParams = Record<string, string | number | boolean | null | undefined> +export type UiI18nParams = Record<string, string | number | boolean> export type UiI18n = { locale: Accessor<string> @@ -15,8 +15,7 @@ function resolveTemplate(text: string, params?: UiI18nParams) { return text.replace(/{{\s*([^}]+?)\s*}}/g, (_, rawKey) => { const key = String(rawKey) const value = params[key] - if (value === undefined || value === null) return "" - return String(value) + return value === undefined ? "" : String(value) }) } |
