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
+105
View File
@@ -0,0 +1,105 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT
mock_bin="$test_tmp/bin"
test_home="$test_tmp/home"
pkg_log="$test_tmp/pkg-add"
installer_log="$test_tmp/installer"
sudo_log="$test_tmp/sudo"
systemctl_log="$test_tmp/systemctl"
mkdir -p "$mock_bin" "$test_home"
cat >"$mock_bin/omarchy-pkg-add" <<'SH'
#!/bin/bash
printf '%s\n' "$*" >>"$OMARCHY_TEST_PKG_LOG"
SH
cat >"$mock_bin/sudo" <<'SH'
#!/bin/bash
printf '%s\n' "$*" >>"$OMARCHY_TEST_SUDO_LOG"
SH
cat >"$mock_bin/systemctl" <<'SH'
#!/bin/bash
printf '%s\n' "$*" >>"$OMARCHY_TEST_SYSTEMCTL_LOG"
SH
cat >"$mock_bin/rime-ice-installer" <<'SH'
#!/bin/bash
printf 'ran\n' >>"$OMARCHY_TEST_INSTALLER_LOG"
mkdir -p "$HOME/.local/share/fcitx5/rime"
touch "$HOME/.local/share/fcitx5/rime/rime_ice.schema.yaml"
SH
chmod +x "$mock_bin"/*
export HOME="$test_home"
export PATH="$mock_bin:$ROOT/bin:$PATH"
export OMARCHY_TEST_PKG_LOG="$pkg_log"
export OMARCHY_TEST_INSTALLER_LOG="$installer_log"
export OMARCHY_TEST_SUDO_LOG="$sudo_log"
export OMARCHY_TEST_SYSTEMCTL_LOG="$systemctl_log"
export OMARCHY_PATH="$ROOT"
[[ $(omarchy-region) == "World" ]] || fail "region defaults to World before any choice"
pass "region defaults to World before any choice"
mkdir -p "$test_home/.config/omarchy/extensions"
cp "$ROOT/config/omarchy/extensions/omarchy-menu.jsonc" "$test_home/.config/omarchy/extensions/omarchy-menu.jsonc"
omarchy-region china >/dev/null
cmp -s "$ROOT/default/omarchy/omarchy-menu.zh-cn.jsonc" "$test_home/.config/omarchy/extensions/omarchy-menu.jsonc" ||
fail "china installs the Chinese menu labels over the stock sample"
grep -q "locale-gen" "$sudo_log" || fail "china generates the Chinese locale"
grep -Fx "localectl set-locale LANG=zh_CN.UTF-8" "$sudo_log" >/dev/null || fail "china sets the system language"
grep -Fx "pacman -Sy" "$sudo_log" >/dev/null || fail "china syncs the repo databases before installing"
grep -Fx "rime-ice-installer" "$pkg_log" >/dev/null || fail "china installs the Rime Ice installer package"
(( $(wc -l <"$installer_log") == 1 )) || fail "china runs the Rime Ice installer"
grep -A1 '^\[Groups/0/Items/0\]$' "$test_home/.config/fcitx5/profile" | grep -qFx "Name=rime" ||
fail "china makes Rime the first input method"
grep -qFx "DefaultIM=rime" "$test_home/.config/fcitx5/profile" || fail "china makes Rime the group default"
grep -qFx "0=Control+space" "$test_home/.config/fcitx5/config" || fail "china pins the Ctrl+Space toggle"
grep -Fx -- "--user restart omarchy-fcitx5.service" "$systemctl_log" >/dev/null ||
fail "china restarts fcitx5 with the new profile"
[[ $(omarchy-region) == "China" ]] || fail "china is stored as the region"
pass "china sets up the language, menu and input method"
omarchy-region china >/dev/null
(( $(wc -l <"$installer_log") == 1 )) || fail "reapplying china skips the installed input method"
(( $(grep -cFx "pacman -Sy" "$sudo_log") == 1 )) || fail "reapplying china skips the database sync too"
pass "reapplying china is idempotent and leaves the input method alone"
omarchy-region world >/dev/null
cmp -s "$ROOT/config/omarchy/extensions/omarchy-menu.jsonc" "$test_home/.config/omarchy/extensions/omarchy-menu.jsonc" ||
fail "world restores the sample menu extension"
[[ $(omarchy-region) == "World" ]] || fail "world is stored as the region"
[[ -f $test_home/.local/share/fcitx5/rime/rime_ice.schema.yaml ]] || fail "world leaves the input method installed"
grep -qFx "DefaultIM=rime" "$test_home/.config/fcitx5/profile" || fail "world leaves the input method configured"
pass "world restores the menu and keeps the input method"
printf '{"apps": {"label":"Mine"}}\n' >"$test_home/.config/omarchy/extensions/omarchy-menu.jsonc"
if omarchy-region china >/dev/null 2>"$test_tmp/refusal"; then
fail "china refuses a menu extension someone wrote by hand"
fi
grep -q "menu:" "$test_tmp/refusal" || fail "china names the menu extension it refuses to touch"
[[ $(omarchy-region) == "World" ]] || fail "a refused china run keeps the region"
pass "china refuses a hand-written menu extension"
grep -Fx '{"apps": {"label":"Mine"}}' "$test_home/.config/omarchy/extensions/omarchy-menu.jsonc" >/dev/null ||
fail "a refused china run leaves the extension untouched"
omarchy-region world >/dev/null
grep -Fx '{"apps": {"label":"Mine"}}' "$test_home/.config/omarchy/extensions/omarchy-menu.jsonc" >/dev/null ||
fail "world leaves a menu extension someone wrote by hand"
pass "world only restores the extension Omarchy wrote"
if omarchy-region china unexpected >/dev/null 2>&1; then
fail "region rejects extra arguments"
fi
[[ $(omarchy-region) == "World" ]] || fail "extra arguments change nothing"
pass "region rejects extra arguments"