diff --git a/packages/app/src/components/dialog-release-notes.tsx b/packages/app/src/components/dialog-release-notes.tsx index 2040009a8..d0a35b71b 100644 --- a/packages/app/src/components/dialog-release-notes.tsx +++ b/packages/app/src/components/dialog-release-notes.tsx @@ -2,6 +2,7 @@ import { createSignal } from "solid-js" import { Dialog } from "@opencode-ai/ui/dialog" import { Button } from "@opencode-ai/ui/button" import { useDialog } from "@opencode-ai/ui/context/dialog" +import { useLanguage } from "@/context/language" import { useSettings } from "@/context/settings" export type Highlight = { @@ -16,6 +17,7 @@ export type Highlight = { export function DialogReleaseNotes(props: { highlights: Highlight[] }) { const dialog = useDialog() + const language = useLanguage() const settings = useSettings() const [index, setIndex] = createSignal(0) @@ -83,16 +85,16 @@ export function DialogReleaseNotes(props: { highlights: Highlight[] }) {
{isLast() ? ( ) : ( )}
@@ -128,7 +130,7 @@ export function DialogReleaseNotes(props: { highlights: Highlight[] }) { {feature()!.media!.type === "image" ? ( {feature()!.media!.alt ) : ( diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx index 29a3666c0..b530aff53 100644 --- a/packages/app/src/components/dialog-select-file.tsx +++ b/packages/app/src/components/dialog-select-file.tsx @@ -449,7 +449,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil - {getRelativeTime(new Date(item.updated!).toISOString())} + {getRelativeTime(new Date(item.updated!).toISOString(), language.t)} diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index f3209c354..27b1b9cc0 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -430,7 +430,7 @@ export function SessionHeader() { - Open + {language.t("common.open")}
{ when={canDisconnect(item)} fallback={ - Connected from your environment variables + {language.t("settings.providers.connected.environmentDescription")} } > @@ -229,10 +229,12 @@ export const SettingsProviders: Component = () => {
- Custom provider + {language.t("provider.custom.title")} {language.t("settings.providers.tag.custom")}
- Add an OpenAI-compatible provider by base URL. + + {language.t("settings.providers.custom.description")} +
diff --git a/packages/desktop/src/menu.ts b/packages/desktop/src/menu.ts index 9fcb6115b..de6a1d6a7 100644 --- a/packages/desktop/src/menu.ts +++ b/packages/desktop/src/menu.ts @@ -16,7 +16,7 @@ export async function createMenu(trigger: (id: string) => void) { const menu = await Menu.new({ items: [ await Submenu.new({ - text: "OpenCode", + text: t("desktop.menu.app"), items: [ await PredefinedMenuItem.new({ item: { About: null }, @@ -62,15 +62,15 @@ export async function createMenu(trigger: (id: string) => void) { ].filter(Boolean), }), await Submenu.new({ - text: "File", + text: t("desktop.menu.file"), items: [ await MenuItem.new({ - text: "New Session", + text: t("desktop.menu.file.newSession"), accelerator: "Shift+Cmd+S", action: () => trigger("session.new"), }), await MenuItem.new({ - text: "Open Project...", + text: t("desktop.menu.file.openProject"), accelerator: "Cmd+O", action: () => trigger("project.open"), }), @@ -83,7 +83,7 @@ export async function createMenu(trigger: (id: string) => void) { ], }), await Submenu.new({ - text: "Edit", + text: t("desktop.menu.edit"), items: [ await PredefinedMenuItem.new({ item: "Undo", @@ -109,44 +109,44 @@ export async function createMenu(trigger: (id: string) => void) { ], }), await Submenu.new({ - text: "View", + text: t("desktop.menu.view"), items: [ await MenuItem.new({ action: () => trigger("sidebar.toggle"), - text: "Toggle Sidebar", + text: t("desktop.menu.view.toggleSidebar"), accelerator: "Cmd+B", }), await MenuItem.new({ action: () => trigger("terminal.toggle"), - text: "Toggle Terminal", + text: t("desktop.menu.view.toggleTerminal"), accelerator: "Ctrl+`", }), await MenuItem.new({ action: () => trigger("fileTree.toggle"), - text: "Toggle File Tree", + text: t("desktop.menu.view.toggleFileTree"), }), await PredefinedMenuItem.new({ item: "Separator", }), await MenuItem.new({ action: () => trigger("common.goBack"), - text: "Back", + text: t("desktop.menu.view.back"), }), await MenuItem.new({ action: () => trigger("common.goForward"), - text: "Forward", + text: t("desktop.menu.view.forward"), }), await PredefinedMenuItem.new({ item: "Separator", }), await MenuItem.new({ action: () => trigger("session.previous"), - text: "Previous Session", + text: t("desktop.menu.view.previousSession"), accelerator: "Option+ArrowUp", }), await MenuItem.new({ action: () => trigger("session.next"), - text: "Next Session", + text: t("desktop.menu.view.nextSession"), accelerator: "Option+ArrowDown", }), await PredefinedMenuItem.new({ @@ -155,16 +155,16 @@ export async function createMenu(trigger: (id: string) => void) { ], }), await Submenu.new({ - text: "Help", + text: t("desktop.menu.help"), items: [ // missing native macos search await MenuItem.new({ action: () => openUrl("https://opencode.ai/docs"), - text: "OpenCode Documentation", + text: t("desktop.menu.help.documentation"), }), await MenuItem.new({ action: () => openUrl("https://discord.com/invite/opencode"), - text: "Support Forum", + text: t("desktop.menu.help.supportForum"), }), await PredefinedMenuItem.new({ item: "Separator", @@ -177,11 +177,11 @@ export async function createMenu(trigger: (id: string) => void) { }), await MenuItem.new({ action: () => openUrl("https://github.com/anomalyco/opencode/issues/new?template=feature_request.yml"), - text: "Share Feedback", + text: t("desktop.menu.help.shareFeedback"), }), await MenuItem.new({ action: () => openUrl("https://github.com/anomalyco/opencode/issues/new?template=bug_report.yml"), - text: "Report a Bug", + text: t("desktop.menu.help.reportBug"), }), ], }), diff --git a/packages/enterprise/src/app.tsx b/packages/enterprise/src/app.tsx index 6f9cdcafb..b73888ec5 100644 --- a/packages/enterprise/src/app.tsx +++ b/packages/enterprise/src/app.tsx @@ -56,8 +56,9 @@ function detectLocale() { function UiI18nBridge(props: ParentProps) { const locale = createMemo(() => detectLocale()) + const zh = uiZh as Partial> const t = (key: keyof typeof uiEn, params?: UiI18nParams) => { - const value = locale() === "zh" ? (uiZh[key] ?? uiEn[key]) : uiEn[key] + const value = locale() === "zh" ? (zh[key] ?? uiEn[key]) : uiEn[key] const text = value ?? String(key) return resolveTemplate(text, params) } diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index e877fc725..5c110ccd6 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -463,14 +463,22 @@ function contextToolTrigger(part: ToolPart, i18n: ReturnType) { } } -function contextToolSummary(parts: ToolPart[]) { +function contextToolSummary(parts: ToolPart[], i18n: ReturnType) { const read = parts.filter((part) => part.tool === "read").length const search = parts.filter((part) => part.tool === "glob" || part.tool === "grep").length const list = parts.filter((part) => part.tool === "list").length return [ - read ? `${read} ${read === 1 ? "read" : "reads"}` : undefined, - search ? `${search} ${search === 1 ? "search" : "searches"}` : undefined, - list ? `${list} ${list === 1 ? "list" : "lists"}` : undefined, + read + ? i18n.t(read === 1 ? "ui.messagePart.context.read.one" : "ui.messagePart.context.read.other", { count: read }) + : undefined, + search + ? i18n.t(search === 1 ? "ui.messagePart.context.search.one" : "ui.messagePart.context.search.other", { + count: search, + }) + : undefined, + list + ? i18n.t(list === 1 ? "ui.messagePart.context.list.one" : "ui.messagePart.context.list.other", { count: list }) + : undefined, ].filter((value): value is string => !!value) } @@ -595,7 +603,7 @@ function ContextToolGroup(props: { parts: ToolPart[]; busy?: boolean }) { () => !!props.busy || props.parts.some((part) => part.state.status === "pending" || part.state.status === "running"), ) - const summary = createMemo(() => contextToolSummary(props.parts)) + const summary = createMemo(() => contextToolSummary(props.parts, i18n)) const details = createMemo(() => summary().join(", ")) return ( @@ -979,7 +987,7 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) { return (
- {i18n.t("ui.tool.questions")} dismissed + {i18n.t("ui.messagePart.questions.dismissed")}
) diff --git a/packages/ui/src/components/scroll-view.tsx b/packages/ui/src/components/scroll-view.tsx index acc54c8c3..52ed39a46 100644 --- a/packages/ui/src/components/scroll-view.tsx +++ b/packages/ui/src/components/scroll-view.tsx @@ -1,4 +1,5 @@ import { createSignal, onCleanup, onMount, splitProps, type ComponentProps, Show, mergeProps } from "solid-js" +import { useI18n } from "../context/i18n" export interface ScrollViewProps extends ComponentProps<"div"> { viewportRef?: (el: HTMLDivElement) => void @@ -6,6 +7,7 @@ export interface ScrollViewProps extends ComponentProps<"div"> { } export function ScrollView(props: ScrollViewProps) { + const i18n = useI18n() const merged = mergeProps({ orientation: "vertical" }, props) const [local, events, rest] = splitProps( merged, @@ -188,7 +190,7 @@ export function ScrollView(props: ScrollViewProps) { onClick={events.onClick as any} tabIndex={0} role="region" - aria-label="scrollable content" + aria-label={i18n.t("ui.scrollView.ariaLabel")} onKeyDown={(e) => { onKeyDown(e) if (typeof events.onKeyDown === "function") events.onKeyDown(e as any) diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index 5829401eb..77bd9506d 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -16,18 +16,8 @@ import { useFileComponent } from "../context/file" import { useI18n } from "../context/i18n" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { checksum } from "@opencode-ai/util/encode" -import { - createEffect, - createMemo, - createSignal, - For, - Match, - onCleanup, - Show, - Switch, - untrack, - type JSX, -} from "solid-js" +import { createEffect, createMemo, createSignal, For, Match, Show, Switch, untrack, type JSX } from "solid-js" +import { onCleanup } from "solid-js" import { createStore } from "solid-js/store" import { type FileContent, type FileDiff } from "@opencode-ai/sdk/v2" import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr" @@ -191,6 +181,15 @@ export const SessionReview = (props: SessionReviewProps) => { highlightedFile = undefined } + const openFileLabel = () => i18n.t("ui.sessionReview.openFile") + + const selectionLabel = (range: SelectedLineRange) => { + const start = Math.min(range.start, range.end) + const end = Math.max(range.start, range.end) + if (start === end) return i18n.t("ui.sessionReview.selection.line", { line: start }) + return i18n.t("ui.sessionReview.selection.lines", { start, end }) + } + const focusSearch = () => { if (!hasDiffs()) return setSearchOpen(true) @@ -475,7 +474,8 @@ export const SessionReview = (props: SessionReviewProps) => { const wrapper = anchors.get(focus.file) const anchor = wrapper?.querySelector(`[data-comment-id="${focus.id}"]`) - const ready = anchor instanceof HTMLElement + const ready = + anchor instanceof HTMLElement && anchor.style.pointerEvents !== "none" && anchor.style.opacity !== "0" const target = ready ? anchor : wrapper if (!target) { @@ -751,11 +751,11 @@ export const SessionReview = (props: SessionReviewProps) => { {getFilename(file)} - +