wip(app): i18n
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user