Skip to content

i18n: shortcut action titles and group names are not localizable (hardcoded in the keybinding registry) #25048

Open

Description

@taranttoola-create

Problem or use case

The Shortcuts settings page (and every surface that shows a shortcut's action name) stays English in every language, including the built-in zh / ko / ja / es / fr catalogs. A language pack cannot fix it, and neither can the built-in locales.

Concretely, with a full pack installed, the table still reads:

  • Go to File, Open Settings, Force Reload, Switch worktree, Previous worktree, Next worktree, …
  • group headers Global, Terminal Panes, Tabs, Editors, Tab Navigation, Browser, File Explorer, Settings, Quick Commands

Only the chrome around them localizes today ({{value0}} shortcut, Modified, Disabled, Find shortcuts, Status), which is why the pane looks half-translated.

Root cause

Action titles and group names are plain English strings in the shared registry, and render translate()-less:

  • src/shared/keybindings/definitions-core-1..4.ts — 88 entries of the form { id: 'worktree.quickOpen', title: 'Go to File', group: 'Global', … }
  • src/shared/keybindings/definitions.ts — buildAgentTabKeybindingDefinitions() generates title: \New ${name} tab`` per agent (a second, dynamic family)
  • rendered verbatim at src/renderer/src/components/settings/ShortcutCommandBlock.tsx (title={item.title}, {item.title}, and inside several aria-label {{value0}}), ShortcutRowsList.tsx ({group.title}), shortcuts-search.ts (title: item.title), ShortcutFilterRail.tsx (title: row.item.title), ShortcutsPane.tsx (conflict message labels)

I verified none of these strings reach a catalog: of the 88 titles, 73 do not appear in en.json at all, and the remaining 15 only match by coincidence (e.g. Open Settings exists as browser.userAgentMigration.openSettings). There are zero keys derived from action ids (worktree.quickOpen, …). So there is nothing a pack could target, and there is no exempt-path mechanism like plugin-translatable-chrome.ts for shortcuts either.

This affects built-in locales too — it is a catalog gap independent of the plugin-pack mechanism.

Proposed solution

Route the title/group through translate() with the registry string as the fallback, keyed by the stable action id rather than the English text. The registry stays the single source of truth, nothing changes for users without a translation, and packs get a stable key that never drifts when copy is reworded.

A small helper plus call-site swaps — a patch against v1.4.218 (applies cleanly) is in the gist below:

// src/renderer/src/components/settings/keybinding-labels.ts
function keySegment(id: string): string {
  return id.replace(/[^A-Za-z0-9]+/g, '_')
}

export function localizedKeybindingTitle(definition: KeybindingDefinition): string {
  return translate(`keybinding.title.${keySegment(definition.id)}`, definition.title)
}
export function localizedKeybindingGroup(group: string): string {
  return translate(`keybinding.group.${keySegment(group)}`, group)
}

and at each render site item.title → localizedKeybindingTitle(item), group.title → localizedKeybindingGroup(group.title). Since translate(key, fallback) returns the fallback when no translation exists, this is a no-op for English.

Why the id is encoded rather than used raw. The obvious keybinding.title.${definition.id} breaks on the dynamic agent family: buildAgentTabKeybindingDefinitions() emits ids like tab.newAgent.claude, so keybinding.title.tab.newAgent would have to be both a string and an object at once — invalid JSON, and a catalog that cannot be built. Collapsing non-alphanumeric runs to _ gives keybinding.title.tab_newAgent (the default-agent action) and keybinding.title.tab_newAgent_claude side by side. I checked the full id set for this: with encoding there are no prefix collisions across all 130 ids (88 static + 42 agent). Any other injective mapping works equally well — this is just the simplest one.

Artifacts (all in the gist, MIT, no strings attached):

  • shortcuts-i18n.patch — the full change, 6 files, verified git apply --check clean on 75ea5027 (v1.4.218). Includes search-index and conflict-label sites so search and messages match the visible rows.
  • translations-table.md — all 88 action ids with their key suffix and EN → RU, ready to drop into a pack or a built-in catalog.
  • groups-table.md — the 9 group names.
  • ru-keybinding-catalog.json — the 139 keybinding.* keys (88 titles + 9 groups + 42 agent tabs) as a nested catalog, already valid for contributes.languagePacks.

Gist: https://gist.github.com/taranttoola-create/8e52e4cd2286cbbe77eeeed87e2ae121

I confirmed the shape against the runtime: translate() is src/renderer/src/i18n/i18n.ts:85 and already does i18n.t(key, { defaultValue: fallback }), and the lane that renders the built-in locales falls back to the bundled English catalog, so a missing keybinding.* key is exactly today's behavior.

If a keybinding.title.* namespace is not what you want, the same seam works with any keying you prefer (hash keys, a dedicated en.json section, raw ids with a different separator) — the helper is the only place the scheme is named.

Context

I maintain a community Russian pack (taranttoola-create.russian, ~14.7k entries, submitted in stablyai/orca-plugins#9) and hit this while checking what stays English after a full pack is loaded. This is the same class of gap tracked in #17913, and the Shortcuts table was flagged there too by a zh-TW pack author (mark22013333). Between the two it is the largest single user-visible English block left on a fully localized build.

Happy to open the PR myself against main if the direction looks right — I did not want to assume a key naming scheme you may already have in mind.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions