diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx index 680f327130..53148d4169 100644 --- a/packages/app/src/components/session-context-usage.tsx +++ b/packages/app/src/components/session-context-usage.tsx @@ -7,6 +7,7 @@ import { AssistantMessage } from "@opencode-ai/sdk/v2/client" import { useLayout } from "@/context/layout" import { useSync } from "@/context/sync" +import { useLanguage } from "@/context/language" interface SessionContextUsageProps { variant?: "button" | "indicator" @@ -16,6 +17,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) { const sync = useSync() const params = useParams() const layout = useLayout() + const language = useLanguage() const variant = createMemo(() => props.variant ?? "button") const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`) @@ -24,14 +26,16 @@ export function SessionContextUsage(props: SessionContextUsageProps) { const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : [])) const cost = createMemo(() => { + const locale = language.locale() const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0) - return new Intl.NumberFormat("en-US", { + return new Intl.NumberFormat(locale, { style: "currency", currency: "USD", }).format(total) }) const context = createMemo(() => { + const locale = language.locale() const last = messages().findLast((x) => { if (x.role !== "assistant") return false const total = x.tokens.input + x.tokens.output + x.tokens.reasoning + x.tokens.cache.read + x.tokens.cache.write @@ -42,7 +46,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) { last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write const model = sync.data.provider.all.find((x) => x.id === last.providerID)?.models[last.modelID] return { - tokens: total.toLocaleString(), + tokens: total.toLocaleString(locale), percentage: model?.limit.context ? Math.round((total / model.limit.context) * 100) : null, } }) @@ -67,21 +71,21 @@ export function SessionContextUsage(props: SessionContextUsageProps) { <>
{ctx().tokens} - Tokens + {language.t("context.usage.tokens")}
{ctx().percentage ?? 0}% - Usage + {language.t("context.usage.usage")}
)}
{cost()} - Cost + {language.t("context.usage.cost")}
-
Click to view context
+
{language.t("context.usage.clickToView")}
) diff --git a/packages/app/src/components/session/session-context-tab.tsx b/packages/app/src/components/session/session-context-tab.tsx index 030ae6d58c..3758dd6e86 100644 --- a/packages/app/src/components/session/session-context-tab.tsx +++ b/packages/app/src/components/session/session-context-tab.tsx @@ -61,8 +61,9 @@ export function SessionContextTab(props: SessionContextTabProps) { }) const cost = createMemo(() => { + const locale = language.locale() const total = props.messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0) - return new Intl.NumberFormat("en-US", { + return new Intl.NumberFormat(locale, { style: "currency", currency: "USD", }).format(total) @@ -91,18 +92,18 @@ export function SessionContextTab(props: SessionContextTabProps) { const number = (value: number | null | undefined) => { if (value === undefined) return "—" if (value === null) return "—" - return value.toLocaleString() + return value.toLocaleString(language.locale()) } const percent = (value: number | null | undefined) => { if (value === undefined) return "—" if (value === null) return "—" - return value.toString() + "%" + return value.toLocaleString(language.locale()) + "%" } const time = (value: number | undefined) => { if (!value) return "—" - return DateTime.fromMillis(value).toLocaleString(DateTime.DATETIME_MED) + return DateTime.fromMillis(value).setLocale(language.locale()).toLocaleString(DateTime.DATETIME_MED) } const providerLabel = createMemo(() => { @@ -246,7 +247,7 @@ export function SessionContextTab(props: SessionContextTabProps) { const count = counts() return [ { label: language.t("context.stats.session"), value: props.info()?.title ?? params.id ?? "—" }, - { label: language.t("context.stats.messages"), value: count.all.toLocaleString() }, + { label: language.t("context.stats.messages"), value: count.all.toLocaleString(language.locale()) }, { label: language.t("context.stats.provider"), value: providerLabel() }, { label: language.t("context.stats.model"), value: modelLabel() }, { label: language.t("context.stats.limit"), value: number(c?.limit) }, @@ -259,8 +260,8 @@ export function SessionContextTab(props: SessionContextTabProps) { label: language.t("context.stats.cacheTokens"), value: `${number(c?.cacheRead)} / ${number(c?.cacheWrite)}`, }, - { label: language.t("context.stats.userMessages"), value: count.user.toLocaleString() }, - { label: language.t("context.stats.assistantMessages"), value: count.assistant.toLocaleString() }, + { label: language.t("context.stats.userMessages"), value: count.user.toLocaleString(language.locale()) }, + { label: language.t("context.stats.assistantMessages"), value: count.assistant.toLocaleString(language.locale()) }, { label: language.t("context.stats.totalCost"), value: cost() }, { label: language.t("context.stats.sessionCreated"), value: time(props.info()?.time.created) }, { label: language.t("context.stats.lastActivity"), value: time(c?.message.time.created) }, diff --git a/packages/app/src/components/session/session-new-view.tsx b/packages/app/src/components/session/session-new-view.tsx index 68ef0cc1f2..6d6078d403 100644 --- a/packages/app/src/components/session/session-new-view.tsx +++ b/packages/app/src/components/session/session-new-view.tsx @@ -1,6 +1,7 @@ import { Show, createMemo } from "solid-js" import { DateTime } from "luxon" import { useSync } from "@/context/sync" +import { useLanguage } from "@/context/language" import { Icon } from "@opencode-ai/ui/icon" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { Select } from "@opencode-ai/ui/select" @@ -15,6 +16,7 @@ interface NewSessionViewProps { export function NewSessionView(props: NewSessionViewProps) { const sync = useSync() + const language = useLanguage() const sandboxes = createMemo(() => sync.project?.sandboxes ?? []) const options = createMemo(() => [MAIN_WORKTREE, ...sandboxes(), CREATE_WORKTREE]) @@ -32,13 +34,13 @@ export function NewSessionView(props: NewSessionViewProps) { const label = (value: string) => { if (value === MAIN_WORKTREE) { - if (isWorktree()) return "Main branch" + if (isWorktree()) return language.t("session.new.worktree.main") const branch = sync.data.vcs?.branch - if (branch) return `Main branch (${branch})` - return "Main branch" + if (branch) return language.t("session.new.worktree.mainWithBranch", { branch }) + return language.t("session.new.worktree.main") } - if (value === CREATE_WORKTREE) return "Create new worktree" + if (value === CREATE_WORKTREE) return language.t("session.new.worktree.create") return getFilename(value) } @@ -48,7 +50,7 @@ export function NewSessionView(props: NewSessionViewProps) { class="size-full flex flex-col pb-45 justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-200 mx-auto px-6" style={{ "padding-bottom": "calc(var(--prompt-height, 11.25rem) + 64px)" }} > -
New session
+
{language.t("command.session.new")}
@@ -76,9 +78,11 @@ export function NewSessionView(props: NewSessionViewProps) {
- Last modified  + {language.t("session.new.lastModified")}  - {DateTime.fromMillis(project().time.updated ?? project().time.created).toRelative()} + {DateTime.fromMillis(project().time.updated ?? project().time.created) + .setLocale(language.locale()) + .toRelative()}
diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index cab1bd29d2..97ab9e0bfc 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -256,6 +256,11 @@ export const dict = { "context.stats.sessionCreated": "Session Created", "context.stats.lastActivity": "Last Activity", + "context.usage.tokens": "Tokens", + "context.usage.usage": "Usage", + "context.usage.cost": "Cost", + "context.usage.clickToView": "Click to view context", + "language.en": "English", "language.zh": "Chinese", @@ -344,6 +349,11 @@ export const dict = { "session.context.addToContext": "Add {{selection}} to context", + "session.new.worktree.main": "Main branch", + "session.new.worktree.mainWithBranch": "Main branch ({{branch}})", + "session.new.worktree.create": "Create new worktree", + "session.new.lastModified": "Last modified", + "session.header.search.placeholder": "Search {{project}}", "session.share.popover.title": "Publish on web", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 2ded1d6779..014fa53531 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -255,6 +255,11 @@ export const dict = { "context.stats.sessionCreated": "创建时间", "context.stats.lastActivity": "最后活动", + "context.usage.tokens": "Token", + "context.usage.usage": "使用率", + "context.usage.cost": "成本", + "context.usage.clickToView": "点击查看上下文", + "language.en": "英语", "language.zh": "中文", @@ -341,6 +346,11 @@ export const dict = { "session.context.addToContext": "将 {{selection}} 添加到上下文", + "session.new.worktree.main": "主分支", + "session.new.worktree.mainWithBranch": "主分支 ({{branch}})", + "session.new.worktree.create": "创建新的 worktree", + "session.new.lastModified": "最后修改", + "session.header.search.placeholder": "搜索 {{project}}", "session.share.popover.title": "发布到网页", diff --git a/specs/06-app-i18n-audit.md b/specs/06-app-i18n-audit.md index 50110f7c0e..a3b28decad 100644 --- a/specs/06-app-i18n-audit.md +++ b/specs/06-app-i18n-audit.md @@ -9,8 +9,8 @@ This report documents the remaining user-facing strings in `packages/app/src` th ## Current State - The app uses `useLanguage().t("...")` with dictionaries in `packages/app/src/i18n/en.ts` and `packages/app/src/i18n/zh.ts`. -- Recent progress (already translated): `packages/app/src/pages/home.tsx`, `packages/app/src/pages/layout.tsx`, `packages/app/src/pages/session.tsx`, `packages/app/src/components/prompt-input.tsx`, `packages/app/src/components/dialog-connect-provider.tsx`, `packages/app/src/components/session/session-header.tsx`, `packages/app/src/pages/error.tsx` (plus new keys added in both dictionaries). -- Dictionary parity check: `en.ts` and `zh.ts` currently contain the same key set (354 keys each; no missing or extra keys). +- Recent progress (already translated): `packages/app/src/pages/home.tsx`, `packages/app/src/pages/layout.tsx`, `packages/app/src/pages/session.tsx`, `packages/app/src/components/prompt-input.tsx`, `packages/app/src/components/dialog-connect-provider.tsx`, `packages/app/src/components/session/session-header.tsx`, `packages/app/src/pages/error.tsx`, `packages/app/src/components/session/session-new-view.tsx`, `packages/app/src/components/session-context-usage.tsx`, `packages/app/src/components/session/session-context-tab.tsx` (plus new keys added in both dictionaries). +- Dictionary parity check: `en.ts` and `zh.ts` currently contain the same key set (362 keys each; no missing or extra keys). ## Methodology @@ -76,32 +76,30 @@ Completed (2026-01-20): File: `packages/app/src/components/session/session-new-view.tsx` -**Untranslated strings** -- "New session" -- "Main branch" / "Main branch ({branch})" -- "Create new worktree" -- "Last modified" +Completed (2026-01-20): + +- Reused existing `command.session.new` for the heading. +- Localized worktree labels via `session.new.worktree.*` (main branch, main branch w/ branch name, create worktree). +- Localized "Last modified" via `session.new.lastModified` and used `language.locale()` for Luxon relative time. ### 6) Context Usage Tooltip File: `packages/app/src/components/session-context-usage.tsx` -**Untranslated tooltip labels** -- "Tokens", "Usage", "Cost" -- "Click to view context" +Completed (2026-01-20): -**Locale formatting issue** -- Uses `new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" })`. -- Recommendation: format using the active locale (e.g. `language.locale()`), or at least `navigator.language`. +- Localized tooltip labels + CTA via `context.usage.*` keys. +- Switched currency and number formatting to the active locale (`language.locale()`). ### 7) Session Context Tab (Formatting) File: `packages/app/src/components/session/session-context-tab.tsx` -- Already uses many translation keys for labels (e.g. `context.breakdown.system`). -- Still forces `Intl.NumberFormat("en-US", ...)` for currency. -- Has some non-translated fallback symbols like "--" and "-" style output (e.g. "---" / "-" / "--" equivalent "--" is used as "—" in code). - - If you want fully localized punctuation, these should become keys as well. +Completed (2026-01-20): + +- Switched currency formatting to the active locale (`language.locale()`). +- Also used `language.locale()` for number/date formatting. +- Note: "—" placeholders remain hardcoded; optional to localize. ### 8) LSP Indicator @@ -211,18 +209,18 @@ This is only thrown in DEV and is more of a developer diagnostic. Optional to tr ## Prioritized Implementation Plan -1. `packages/app/src/components/session/session-new-view.tsx` -2. `packages/app/src/components/session-context-usage.tsx` + locale formatting improvements (also `packages/app/src/components/session/session-context-tab.tsx`) -3. Small stragglers: +1. Small stragglers: - `packages/app/src/components/session-lsp-indicator.tsx` - `packages/app/src/components/session/session-sortable-tab.tsx` - `packages/app/src/components/titlebar.tsx` - `packages/app/src/components/dialog-select-model.tsx` + - `packages/app/src/components/dialog-select-server.tsx` (optional URL placeholder) +2. Context modules: - `packages/app/src/context/notification.tsx` - `packages/app/src/context/global-sync.tsx` - `packages/app/src/context/file.tsx` + `packages/app/src/context/local.tsx` - `packages/app/src/utils/prompt.ts` -4. Decide on the terminal naming approach (`packages/app/src/context/terminal.tsx`). +3. Decide on the terminal naming approach (`packages/app/src/context/terminal.tsx`). ## Suggested Key Naming Conventions @@ -245,9 +243,6 @@ Pages: - (none) Components: -- `packages/app/src/components/session/session-new-view.tsx` -- `packages/app/src/components/session-context-usage.tsx` -- `packages/app/src/components/session/session-context-tab.tsx` (formatting locale) - `packages/app/src/components/session-lsp-indicator.tsx` - `packages/app/src/components/session/session-sortable-tab.tsx` - `packages/app/src/components/titlebar.tsx`