Add a Setup > Region toggle with Chinese language and input method

`omarchy region china` switches the system language to zh_CN, installs the
Rime Ice input method through Andy Stewart's rime-ice-installer, makes Rime
the default with Ctrl+Space as the toggle, and swaps the Omarchy menu to
Chinese labels through the user-extension overlay; `omarchy region world`
puts the language and menu back and leaves the input method installed.
mergeMenuSources now only overrides the fields an extension declares, as
docs/menu.md documents, which the label-only overlay needs.

Co-authored-by: Andy Stewart <lazycat.manatee@gmail.com>
This commit is contained in:
2026-09-01 11:34:07 -04:00
co-authored by Andy Stewart
parent b686ed892d
commit d897cff7bf
8 changed files with 450 additions and 7 deletions
+29 -2
View File
@@ -44,9 +44,10 @@ assertDeepEqual(
aliases: ['theme'],
when: '',
checked: '',
disabled: ''
disabled: '',
declared: ['label', 'aliases', 'description', 'action']
},
'menu normalizes parsed items'
'menu normalizes parsed items and records what the file declared'
)
const user = [
@@ -58,6 +59,16 @@ assertEqual(merged.items['style.theme'].label, 'Theme picker', 'menu user entrie
assertEqual(merged.items['style.theme'].order, 2, 'menu preserves original order on override')
assert(merged.items.root, 'menu injects root when merging sources')
const retitled = menu.mergeMenuSources(
menu.parseMenuJsonc('{"apps": {"icon":"A","label":"Apps","provider":"apps"}, "apps.go": {"label":"Go","action":"run-go"}}'),
menu.parseMenuJsonc('{"apps": {"label":"应用"}, "apps.go": {"label":"出发"}}')
)
assertEqual(retitled.items['apps'].provider, 'apps', 'menu keeps the provider when a user entry only retitles')
assertEqual(retitled.items['apps'].icon, 'A', 'menu keeps the icon when a user entry only retitles')
assertEqual(retitled.items['apps.go'].action, 'run-go', 'menu keeps the action when a user entry only retitles')
assertEqual(retitled.items['apps.go'].kind, 'action', 'menu keeps the kind when a user entry only retitles')
assertEqual(retitled.items['apps.go'].label, '出发', 'menu applies the user retitle')
assertEqual(menu.slugify('Power Saver!'), 'power-saver', 'menu slugifies provider rows')
assertEqual(menu.pathFor(merged.items, 'style.theme'), 'Style › Theme picker', 'menu builds item paths')
assertEqual(menu.parentPathFor(merged.items, 'style.theme'), 'Style', 'menu builds parent paths')
@@ -261,6 +272,22 @@ assert(
'menu always exposes every supported browser, terminal, and editor under Defaults'
)
assert(!defaultById['install.ai.crush'], 'menu removes Crush from Install > AI')
const regionEntries = defaultItems.filter(item => item.parent === 'setup.region')
assert(
regionEntries.map(item => item.label).join('\0') === 'World\0China'
&& regionEntries.every(item => item.checked.includes(`== \"${item.label}\"`) && !item.when)
&& defaultById['setup.region.world'].action.includes('omarchy-region world')
&& defaultById['setup.region.china'].action.includes('omarchy-region china'),
'menu offers World and China under Setup > Region'
)
const zhOverlay = fs.readFileSync(path.join(root, 'default/omarchy/omarchy-menu.zh-cn.jsonc'), 'utf8')
const zhItems = menu.parseMenuJsonc(zhOverlay)
assert(zhItems.length > 100, 'Chinese menu overlay parses')
assert(zhItems.every(item => defaultById[item.id]), 'Chinese menu overlay only retitles shipped entries')
assert(
!/"(action|target|icon|when|checked|disabled|provider|aliases)"/.test(zhOverlay),
'Chinese menu overlay declares labels and titles only'
)
// Software you already have keeps its place in Install, dimmed rather than
// dropped, so the list reads as a catalog of what Omarchy can install.
// Chromium Account is the sole Install row with anything left to hide for, so