Skip to content

Commit be493e8

Browse files
committed
wip(app): i18n
1 parent 7e8e4d9 commit be493e8

6 files changed

Lines changed: 68 additions & 44 deletions

File tree

packages/app/src/components/session-context-usage.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { AssistantMessage } from "@opencode-ai/sdk/v2/client"
77

88
import { useLayout } from "@/context/layout"
99
import { useSync } from "@/context/sync"
10+
import { useLanguage } from "@/context/language"
1011

1112
interface SessionContextUsageProps {
1213
variant?: "button" | "indicator"
@@ -16,6 +17,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
1617
const sync = useSync()
1718
const params = useParams()
1819
const layout = useLayout()
20+
const language = useLanguage()
1921

2022
const variant = createMemo(() => props.variant ?? "button")
2123
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
@@ -24,14 +26,16 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
2426
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
2527

2628
const cost = createMemo(() => {
29+
const locale = language.locale()
2730
const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0)
28-
return new Intl.NumberFormat("en-US", {
31+
return new Intl.NumberFormat(locale, {
2932
style: "currency",
3033
currency: "USD",
3134
}).format(total)
3235
})
3336

3437
const context = createMemo(() => {
38+
const locale = language.locale()
3539
const last = messages().findLast((x) => {
3640
if (x.role !== "assistant") return false
3741
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) {
4246
last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write
4347
const model = sync.data.provider.all.find((x) => x.id === last.providerID)?.models[last.modelID]
4448
return {
45-
tokens: total.toLocaleString(),
49+
tokens: total.toLocaleString(locale),
4650
percentage: model?.limit.context ? Math.round((total / model.limit.context) * 100) : null,
4751
}
4852
})
@@ -67,21 +71,21 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
6771
<>
6872
<div class="flex items-center gap-2">
6973
<span class="text-text-invert-strong">{ctx().tokens}</span>
70-
<span class="text-text-invert-base">Tokens</span>
74+
<span class="text-text-invert-base">{language.t("context.usage.tokens")}</span>
7175
</div>
7276
<div class="flex items-center gap-2">
7377
<span class="text-text-invert-strong">{ctx().percentage ?? 0}%</span>
74-
<span class="text-text-invert-base">Usage</span>
78+
<span class="text-text-invert-base">{language.t("context.usage.usage")}</span>
7579
</div>
7680
</>
7781
)}
7882
</Show>
7983
<div class="flex items-center gap-2">
8084
<span class="text-text-invert-strong">{cost()}</span>
81-
<span class="text-text-invert-base">Cost</span>
85+
<span class="text-text-invert-base">{language.t("context.usage.cost")}</span>
8286
</div>
8387
<Show when={variant() === "button"}>
84-
<div class="text-11-regular text-text-invert-base mt-1">Click to view context</div>
88+
<div class="text-11-regular text-text-invert-base mt-1">{language.t("context.usage.clickToView")}</div>
8589
</Show>
8690
</div>
8791
)

packages/app/src/components/session/session-context-tab.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ export function SessionContextTab(props: SessionContextTabProps) {
6161
})
6262

6363
const cost = createMemo(() => {
64+
const locale = language.locale()
6465
const total = props.messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0)
65-
return new Intl.NumberFormat("en-US", {
66+
return new Intl.NumberFormat(locale, {
6667
style: "currency",
6768
currency: "USD",
6869
}).format(total)
@@ -91,18 +92,18 @@ export function SessionContextTab(props: SessionContextTabProps) {
9192
const number = (value: number | null | undefined) => {
9293
if (value === undefined) return "—"
9394
if (value === null) return "—"
94-
return value.toLocaleString()
95+
return value.toLocaleString(language.locale())
9596
}
9697

9798
const percent = (value: number | null | undefined) => {
9899
if (value === undefined) return "—"
99100
if (value === null) return "—"
100-
return value.toString() + "%"
101+
return value.toLocaleString(language.locale()) + "%"
101102
}
102103

103104
const time = (value: number | undefined) => {
104105
if (!value) return "—"
105-
return DateTime.fromMillis(value).toLocaleString(DateTime.DATETIME_MED)
106+
return DateTime.fromMillis(value).setLocale(language.locale()).toLocaleString(DateTime.DATETIME_MED)
106107
}
107108

108109
const providerLabel = createMemo(() => {
@@ -246,7 +247,7 @@ export function SessionContextTab(props: SessionContextTabProps) {
246247
const count = counts()
247248
return [
248249
{ label: language.t("context.stats.session"), value: props.info()?.title ?? params.id ?? "—" },
249-
{ label: language.t("context.stats.messages"), value: count.all.toLocaleString() },
250+
{ label: language.t("context.stats.messages"), value: count.all.toLocaleString(language.locale()) },
250251
{ label: language.t("context.stats.provider"), value: providerLabel() },
251252
{ label: language.t("context.stats.model"), value: modelLabel() },
252253
{ label: language.t("context.stats.limit"), value: number(c?.limit) },
@@ -259,8 +260,8 @@ export function SessionContextTab(props: SessionContextTabProps) {
259260
label: language.t("context.stats.cacheTokens"),
260261
value: `${number(c?.cacheRead)} / ${number(c?.cacheWrite)}`,
261262
},
262-
{ label: language.t("context.stats.userMessages"), value: count.user.toLocaleString() },
263-
{ label: language.t("context.stats.assistantMessages"), value: count.assistant.toLocaleString() },
263+
{ label: language.t("context.stats.userMessages"), value: count.user.toLocaleString(language.locale()) },
264+
{ label: language.t("context.stats.assistantMessages"), value: count.assistant.toLocaleString(language.locale()) },
264265
{ label: language.t("context.stats.totalCost"), value: cost() },
265266
{ label: language.t("context.stats.sessionCreated"), value: time(props.info()?.time.created) },
266267
{ label: language.t("context.stats.lastActivity"), value: time(c?.message.time.created) },

packages/app/src/components/session/session-new-view.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Show, createMemo } from "solid-js"
22
import { DateTime } from "luxon"
33
import { useSync } from "@/context/sync"
4+
import { useLanguage } from "@/context/language"
45
import { Icon } from "@opencode-ai/ui/icon"
56
import { getDirectory, getFilename } from "@opencode-ai/util/path"
67
import { Select } from "@opencode-ai/ui/select"
@@ -15,6 +16,7 @@ interface NewSessionViewProps {
1516

1617
export function NewSessionView(props: NewSessionViewProps) {
1718
const sync = useSync()
19+
const language = useLanguage()
1820

1921
const sandboxes = createMemo(() => sync.project?.sandboxes ?? [])
2022
const options = createMemo(() => [MAIN_WORKTREE, ...sandboxes(), CREATE_WORKTREE])
@@ -32,13 +34,13 @@ export function NewSessionView(props: NewSessionViewProps) {
3234

3335
const label = (value: string) => {
3436
if (value === MAIN_WORKTREE) {
35-
if (isWorktree()) return "Main branch"
37+
if (isWorktree()) return language.t("session.new.worktree.main")
3638
const branch = sync.data.vcs?.branch
37-
if (branch) return `Main branch (${branch})`
38-
return "Main branch"
39+
if (branch) return language.t("session.new.worktree.mainWithBranch", { branch })
40+
return language.t("session.new.worktree.main")
3941
}
4042

41-
if (value === CREATE_WORKTREE) return "Create new worktree"
43+
if (value === CREATE_WORKTREE) return language.t("session.new.worktree.create")
4244

4345
return getFilename(value)
4446
}
@@ -48,7 +50,7 @@ export function NewSessionView(props: NewSessionViewProps) {
4850
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"
4951
style={{ "padding-bottom": "calc(var(--prompt-height, 11.25rem) + 64px)" }}
5052
>
51-
<div class="text-20-medium text-text-weaker">New session</div>
53+
<div class="text-20-medium text-text-weaker">{language.t("command.session.new")}</div>
5254
<div class="flex justify-center items-center gap-3">
5355
<Icon name="folder" size="small" />
5456
<div class="text-12-medium text-text-weak">
@@ -76,9 +78,11 @@ export function NewSessionView(props: NewSessionViewProps) {
7678
<div class="flex justify-center items-center gap-3">
7779
<Icon name="pencil-line" size="small" />
7880
<div class="text-12-medium text-text-weak">
79-
Last modified&nbsp;
81+
{language.t("session.new.lastModified")}&nbsp;
8082
<span class="text-text-strong">
81-
{DateTime.fromMillis(project().time.updated ?? project().time.created).toRelative()}
83+
{DateTime.fromMillis(project().time.updated ?? project().time.created)
84+
.setLocale(language.locale())
85+
.toRelative()}
8286
</span>
8387
</div>
8488
</div>

packages/app/src/i18n/en.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,11 @@ export const dict = {
256256
"context.stats.sessionCreated": "Session Created",
257257
"context.stats.lastActivity": "Last Activity",
258258

259+
"context.usage.tokens": "Tokens",
260+
"context.usage.usage": "Usage",
261+
"context.usage.cost": "Cost",
262+
"context.usage.clickToView": "Click to view context",
263+
259264
"language.en": "English",
260265
"language.zh": "Chinese",
261266

@@ -344,6 +349,11 @@ export const dict = {
344349

345350
"session.context.addToContext": "Add {{selection}} to context",
346351

352+
"session.new.worktree.main": "Main branch",
353+
"session.new.worktree.mainWithBranch": "Main branch ({{branch}})",
354+
"session.new.worktree.create": "Create new worktree",
355+
"session.new.lastModified": "Last modified",
356+
347357
"session.header.search.placeholder": "Search {{project}}",
348358

349359
"session.share.popover.title": "Publish on web",

packages/app/src/i18n/zh.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,11 @@ export const dict = {
255255
"context.stats.sessionCreated": "创建时间",
256256
"context.stats.lastActivity": "最后活动",
257257

258+
"context.usage.tokens": "Token",
259+
"context.usage.usage": "使用率",
260+
"context.usage.cost": "成本",
261+
"context.usage.clickToView": "点击查看上下文",
262+
258263
"language.en": "英语",
259264
"language.zh": "中文",
260265

@@ -341,6 +346,11 @@ export const dict = {
341346

342347
"session.context.addToContext": "将 {{selection}} 添加到上下文",
343348

349+
"session.new.worktree.main": "主分支",
350+
"session.new.worktree.mainWithBranch": "主分支 ({{branch}})",
351+
"session.new.worktree.create": "创建新的 worktree",
352+
"session.new.lastModified": "最后修改",
353+
344354
"session.header.search.placeholder": "搜索 {{project}}",
345355

346356
"session.share.popover.title": "发布到网页",

specs/06-app-i18n-audit.md

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ This report documents the remaining user-facing strings in `packages/app/src` th
99
## Current State
1010

1111
- The app uses `useLanguage().t("...")` with dictionaries in `packages/app/src/i18n/en.ts` and `packages/app/src/i18n/zh.ts`.
12-
- 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).
13-
- Dictionary parity check: `en.ts` and `zh.ts` currently contain the same key set (354 keys each; no missing or extra keys).
12+
- 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).
13+
- Dictionary parity check: `en.ts` and `zh.ts` currently contain the same key set (362 keys each; no missing or extra keys).
1414

1515
## Methodology
1616

@@ -76,32 +76,30 @@ Completed (2026-01-20):
7676

7777
File: `packages/app/src/components/session/session-new-view.tsx`
7878

79-
**Untranslated strings**
80-
- "New session"
81-
- "Main branch" / "Main branch ({branch})"
82-
- "Create new worktree"
83-
- "Last modified"
79+
Completed (2026-01-20):
80+
81+
- Reused existing `command.session.new` for the heading.
82+
- Localized worktree labels via `session.new.worktree.*` (main branch, main branch w/ branch name, create worktree).
83+
- Localized "Last modified" via `session.new.lastModified` and used `language.locale()` for Luxon relative time.
8484

8585
### 6) Context Usage Tooltip
8686

8787
File: `packages/app/src/components/session-context-usage.tsx`
8888

89-
**Untranslated tooltip labels**
90-
- "Tokens", "Usage", "Cost"
91-
- "Click to view context"
89+
Completed (2026-01-20):
9290

93-
**Locale formatting issue**
94-
- Uses `new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" })`.
95-
- Recommendation: format using the active locale (e.g. `language.locale()`), or at least `navigator.language`.
91+
- Localized tooltip labels + CTA via `context.usage.*` keys.
92+
- Switched currency and number formatting to the active locale (`language.locale()`).
9693

9794
### 7) Session Context Tab (Formatting)
9895

9996
File: `packages/app/src/components/session/session-context-tab.tsx`
10097

101-
- Already uses many translation keys for labels (e.g. `context.breakdown.system`).
102-
- Still forces `Intl.NumberFormat("en-US", ...)` for currency.
103-
- Has some non-translated fallback symbols like "--" and "-" style output (e.g. "---" / "-" / "--" equivalent "--" is used as "—" in code).
104-
- If you want fully localized punctuation, these should become keys as well.
98+
Completed (2026-01-20):
99+
100+
- Switched currency formatting to the active locale (`language.locale()`).
101+
- Also used `language.locale()` for number/date formatting.
102+
- Note: "—" placeholders remain hardcoded; optional to localize.
105103

106104
### 8) LSP Indicator
107105

@@ -211,18 +209,18 @@ This is only thrown in DEV and is more of a developer diagnostic. Optional to tr
211209

212210
## Prioritized Implementation Plan
213211

214-
1. `packages/app/src/components/session/session-new-view.tsx`
215-
2. `packages/app/src/components/session-context-usage.tsx` + locale formatting improvements (also `packages/app/src/components/session/session-context-tab.tsx`)
216-
3. Small stragglers:
212+
1. Small stragglers:
217213
- `packages/app/src/components/session-lsp-indicator.tsx`
218214
- `packages/app/src/components/session/session-sortable-tab.tsx`
219215
- `packages/app/src/components/titlebar.tsx`
220216
- `packages/app/src/components/dialog-select-model.tsx`
217+
- `packages/app/src/components/dialog-select-server.tsx` (optional URL placeholder)
218+
2. Context modules:
221219
- `packages/app/src/context/notification.tsx`
222220
- `packages/app/src/context/global-sync.tsx`
223221
- `packages/app/src/context/file.tsx` + `packages/app/src/context/local.tsx`
224222
- `packages/app/src/utils/prompt.ts`
225-
4. Decide on the terminal naming approach (`packages/app/src/context/terminal.tsx`).
223+
3. Decide on the terminal naming approach (`packages/app/src/context/terminal.tsx`).
226224

227225
## Suggested Key Naming Conventions
228226

@@ -245,9 +243,6 @@ Pages:
245243
- (none)
246244

247245
Components:
248-
- `packages/app/src/components/session/session-new-view.tsx`
249-
- `packages/app/src/components/session-context-usage.tsx`
250-
- `packages/app/src/components/session/session-context-tab.tsx` (formatting locale)
251246
- `packages/app/src/components/session-lsp-indicator.tsx`
252247
- `packages/app/src/components/session/session-sortable-tab.tsx`
253248
- `packages/app/src/components/titlebar.tsx`

0 commit comments

Comments
 (0)