Skip to content

feat(app): translate the desktop menu - #322

Merged
quickbeard merged 2 commits into
mainfrom
menu-localization
Aug 25, 2026
Merged

quickbeard merged 2 commits into
mainfrom
menu-localization

Conversation

@quickbeard

Copy link
Copy Markdown
Owner

Issue for this PR

Closes #

Type of change

  • Bug fix

What does this PR do?

The app menu stayed English no matter what language the rest of the UI was in — File, Edit, View, Go, Window, Help and every item inside them. Reported with a Vietnamese app showing an English menu next to a Vietnamese sidebar.

The cause: DESKTOP_MENU in packages/app/src/desktop-menu.ts carried literal English labels, and it is read by two renderers — the macOS native menu bar, built in the desktop main process, and the Windows/Linux titlebar menu, built in the renderer. The renderer half could have translated all along; the main-process half has no i18n at all, which is presumably why neither did.

Labels are now dictionary keys. label → labelKey, typed as keyof typeof dict against the English dict, so a key that does not exist fails typecheck and a new item cannot be added with a literal.

  • windows-app-menu.tsx resolves them through useLanguage().t.
  • The macOS bar resolves them in the new packages/desktop/src/main/menu-i18n.ts, which dynamic-imports only the selected locale's dict (@opencode-ai/app/i18n/*, a new export mirroring how @opencode-ai/ui already exports its own). createMenu returns before that on Windows and Linux, so those builds load no dictionary at all — confirmed in the build output, where en/es/vi land in separate ~70 KB lazy chunks.
  • The locale comes from the same codev.global.dat language record the renderer's own i18n reads, so the bar and the UI cannot disagree. Parsing lives in the electron-free main/menu-locale.ts so it is unit-testable.
  • menu.ts remembers its last Deps so refreshMenuLanguage() can rebuild the bar; ipc.ts's store-set handler calls it when that record is written, so switching language updates the menu instead of leaving it a launch behind.

45 menu.* keys added to en/es/vi. The Vietnamese follows .codev/glossary/vi.md: sentence casing, and developer terms stay English — Tải session logs lên, Bật/tắt terminal, Bật/tắt developer tools, Tải lại webview, Session mới.

DIVERGENCES.md gets a row for the localized menu; CHANGELOG.md gets an entry under Language.

How did you verify your code works?

  • New test in desktop-menu.test.ts asserts every labelKey in the table resolves in all three locales — deliberately the tripwire for an upstream menu item arriving with a literal label. Checked that it actually fails by deleting a key.
  • New menu-locale.test.ts covers pickMenuLocale against the record shape the app really persists (verified against a live codev.global.dat: "language": "{\"locale\":\"vi\"…"), a bare locale string, a parsed object, the app.getLocale() fallback, and a locale with no dictionary.
  • bun typecheck clean in packages/app and packages/desktop; bun test green (706 app, 159 desktop).
  • electron-vite build clean, with the dicts code-split out of the main entry as intended.

Not verified visually: I have no screenshot tooling on this box to open the menu and photograph it, and the macOS bar I cannot run at all. The renderer path is the same language.t every other label in the app uses, and the parity test covers the keys — but a glance at the menu on macOS before merge would be worth it.

Screenshots / recordings

None — see the note above.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

🤖 Generated with Claude Code

Quickbeard and others added 2 commits August 25, 2026 10:11
The app menu was the last English-only surface in a translated UI: its
labels were literals in DESKTOP_MENU, which is shared by the macOS native
menu bar (built in the desktop main process) and the Windows/Linux
titlebar menu (built in the renderer).

Labels are now dictionary keys, typed against the English dict so a
missing key fails typecheck. windows-app-menu.tsx resolves them through
useLanguage().t. The macOS bar resolves them in main/menu-i18n.ts, which
dynamic-imports the selected locale's dict and reads the locale from the
same codev.global.dat record the renderer's i18n reads — so Windows and
Linux, which never build a native menu, load no dictionary at all.
createMenu is async as a result, and ipc.ts's store-set handler calls
refreshMenuLanguage() so a language change rebuilds the bar rather than
leaving it a launch behind.

45 menu.* keys added to en/es/vi, following .codev/glossary/vi.md — the
Vietnamese keeps session, terminal, webview and developer tools in
English. A parity test asserts every labelKey resolves in all three
locales, and pickMenuLocale is unit-tested against the record shape the
app actually persists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@quickbeard
quickbeard merged commit f47fe64 into main Aug 25, 2026
0 of 5 checks passed
@quickbeard
quickbeard deleted the menu-localization branch August 25, 2026 03:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant