Add Simplified Chinese as a system language

The installer asks for the language before the keyboard, and
Setup > Language (omarchy language en|zh) switches it later. Under
zh_CN the menu reads its labels from omarchy-menu.zh-cn.jsonc, the
shell from I18nZh.js through I18n.tr, and notifications are worded
by their sender through omarchy-text, which reads the same table.
User text is never translated.
This commit is contained in:
ZacharyZhang-NY committed 2026-10-12 02:56:40 -04:00
1 parent defbf90e89
commit a9b39710a2
138 files changed
+1986 -558

No files matched your search

+10 -2
View File
@@ -5,6 +5,12 @@ source "$(dirname "$0")/base-test.sh"
require_command jq
require_command python3
# The collector words its diagnostics through omarchy-text.
text_bin=$(mktemp -d)
trap 'rm -rf "$text_bin"' EXIT
ln -s "$ROOT/bin/omarchy-text" "$text_bin/omarchy-text"
export PATH="$text_bin:$PATH"
# probe_limits reaches Anthropic, so the reader that interprets its answer is
# exercised on its own: the collector loads as a module, and a recorded payload
# stands in for the response.
@@ -112,7 +118,7 @@ pass "Claude collector keeps Max multipliers and plain plan names"
# is still good. Drive collect_limits over a planted cache with the network
# unreachable, so nothing but the credential state decides the answer.
CACHE_HOME=$(mktemp -d)
trap 'rm -rf "$CACHE_HOME"' EXIT
trap 'rm -rf "$CACHE_HOME" "$text_bin"' EXIT
collect_limits() {
COLLECTOR="$ROOT/bin/omarchy-agent-usage-claude" TOKEN="$1" EXPIRES_AT="$2" \
@@ -320,10 +326,12 @@ pass "Claude collector caches a successful probe"
# The panel reads a window out of a label, and that guess cannot survive a
# model name — "Opus 5 (1M context)" parses as a one-minute window. A collector
# that states the title outright is taken at its word.
run_node_test <<'JS'
run_node_test <<'JS' || exit 1
const fs = require('fs')
const source = fs.readFileSync(root + '/shell/plugins/agents/Panel.qml', 'utf8')
const panelRoot = { nowMs: Date.now() }
// The panel's text goes through I18n; English here, with its %1 arguments filled.
const I18n = { tr: (text, ...args) => text.replace(/%(\d)/g, (m, n) => (n > 0 && n <= args.length ? String(args[n - 1]) : m)) }
const start = source.indexOf('function windowIsLong')
const end = source.indexOf('// The window that decides')
assert(start > 0 && end > start, 'agents panel exposes its limit-window helpers')
+2 -2
View File
@@ -27,11 +27,11 @@ assert(/target\.kind === "signin"\) signInAgain\(/.test(panelSource) && /target\
assert(/target\.kind === "autoswitch"\) setSwitchMode\(/.test(panelSource), 'Enter on Autoswitch flips the switch mode')
assert(/keyColumn = use >= 0 \? use : /.test(panelSource), 'moving up or down onto an account lands on Use')
assert(/Qt\.callLater\(function\(\) \{ if \(picking\) pointAt\("choice", 0\) \}\)/.test(panelSource), 'picking an agent to add starts with the first one focused')
assert(!/opacity: stale \?/.test(panelSource) && /"Last updated " \+ root\.formatDuration/.test(panelSource), 'stale limits say how old they are only on hover')
assert(!/opacity: stale \?/.test(panelSource) && /I18n\.tr\("Last updated %1 ago", root\.formatDuration/.test(panelSource), 'stale limits say how old they are only on hover')
assert(/onPickingChanged: resetKeys\(\)/.test(panelSource), 'the cursor starts over when the agent list comes or goes')
assert(!/t === "a" \|\| t === "A"/.test(panelSource), 'adding an account has no hotkey; the + is the way in')
assert(/if \(!accounts\[a\]\.active\) \{/.test(panelSource) && /if \(row\.length > 0\) rows\.push\(row\)/.test(panelSource), 'the active account with nothing to fix is not a keyboard stop')
assert(/text: section\.provider \? String\(section\.provider\.authHelpText \|\| section\.provider\.usageStatusText \|\| ""\) : ""/.test(panelSource), 'the trouble line falls back to the usage status when a record has no auth help')
assert(/text: section\.provider \? I18n\.tr\(String\(section\.provider\.authHelpText \|\| section\.provider\.usageStatusText \|\| ""\)\) : ""/.test(panelSource), 'the trouble line falls back to the usage status when a record has no auth help')
const mainSource = fs.readFileSync(root + '/shell/plugins/agents/Main.qml', 'utf8')
assert(/var from = Math\.min\(0\.8, \(threshold - 15\) \/ 100\)/.test(mainSource), 'faster checks start 15 points below the switch threshold')
assert(/rows\.push\(\[\{ kind: "provider", index: p \}\]\)/.test(panelSource), "every agent's header is a keyboard stop")
+1 -1
View File
@@ -276,7 +276,7 @@ assert(/readonly property int lifeExpectancy: Model\.parseLifeExpectancy\(settin
assert(/id: expectancyField/.test(panelSource) && /id: bornField/.test(panelSource), 'calendar offers both inputs')
assert(/visible: root\.editingLife\s*\n\s*anchors\.horizontalCenter: parent\.horizontalCenter/.test(panelSource), 'calendar centers the inputs over the bar they replace')
assert(/visible: root\.birthYear > 0/.test(panelSource), 'calendar hides the life bar until a birth year is known')
assert(/text: "LIFE"/.test(panelSource) && /root\.lifeDone/.test(panelSource), 'calendar shows the life bar')
assert(/text: I18n\.tr\("LIFE"\)/.test(panelSource) && /root\.lifeDone/.test(panelSource), 'calendar shows the life bar')
assert(/text: "Memento Mori"/.test(panelSource), 'calendar names the life bar on hover')
assert(/onDoubleTapped: root\.clearLife\(\)/.test(panelSource), 'calendar puts the life bar away when it is double-tapped')
assert(/persistSettings\(\{ birthYear: 0 \}\)/.test(panelSource), 'calendar clears the birth year to hide the life bar')
+5
View File
@@ -40,6 +40,11 @@ run omarchy-hyprland-corners theme
[[ ! -e $toggles_dir/corners-sharp.lua ]] || fail "theme corners drop the override"
pass "theme corners drop the override"
LANG=zh_CN.UTF-8 run omarchy-hyprland-corners sharp
grep -qx 'omarchy-notification-send -g 󰘇 所有主题使用直角' "$tmpdir/commands.log" || fail "a Chinese session announces the corners in Chinese" "$(<"$tmpdir/commands.log")"
run omarchy-hyprland-corners theme
pass "a Chinese session announces the corners in Chinese"
if run omarchy-hyprland-corners square 2>/dev/null; then
fail "unknown corners are refused"
fi
+75
View File
@@ -0,0 +1,75 @@
#!/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
root="$test_tmp/root" sandbox="$test_tmp/omarchy" calls="$test_tmp/calls"
mkdir -p "$root/etc" "$sandbox/bin" "$sandbox/shell/Commons" "$test_tmp/bin"
cp "$ROOT/shell/Commons/I18nZh.js" "$sandbox/shell/Commons/"
sed "s|/etc/|$root/etc/|g" "$ROOT/bin/omarchy-language" >"$sandbox/bin/omarchy-language"
for command in locale-gen omarchy-system-logout; do
printf '#!/bin/bash\necho "%s $*" >>"%s"\n' "$command" "$calls" >"$test_tmp/bin/$command"
done
printf '#!/bin/bash\nexec "$@"\n' >"$test_tmp/bin/sudo"
printf '#!/bin/bash\necho "gum $*" >>"%s"\nexit "${GUM_ANSWER:-1}"\n' "$calls" >"$test_tmp/bin/gum"
chmod +x "$test_tmp/bin"/* "$sandbox/bin/omarchy-language"
ln -s "$ROOT/bin/omarchy-text" "$test_tmp/bin/omarchy-text"
export PATH="$test_tmp/bin:$sandbox/bin:$PATH" OMARCHY_PATH="$sandbox" LANG=en_US.UTF-8
printf '#zh_CN.UTF-8 UTF-8\nen_US.UTF-8 UTF-8\n' >"$root/etc/locale.gen"
printf 'LANG=en_US.UTF-8\nLC_TIME=en_DK.UTF-8\n' >"$root/etc/locale.conf"
[[ $(omarchy-language) == "en_US.UTF-8" ]] || fail "omarchy-language prints the system LANG"
pass "omarchy-language prints the system LANG"
: >"$calls"
output=$(omarchy-language zh)
[[ $(<"$root/etc/locale.gen") == $'zh_CN.UTF-8 UTF-8\nen_US.UTF-8 UTF-8' ]] || fail "Chinese enables zh_CN in locale.gen"
[[ $(<"$root/etc/locale.conf") == $'LANG=zh_CN.UTF-8\nLC_TIME=en_DK.UTF-8' ]] || fail "Chinese sets LANG and keeps other settings" "$(<"$root/etc/locale.conf")"
[[ $(<"$calls") == "locale-gen " ]] || fail "Chinese generates the locale and asks nothing without a terminal" "$(<"$calls")"
[[ $output == "重新登录后切换语言。" ]] || fail "Chinese reminds to log back in, in Chinese" "$output"
pass "switching to Chinese generates zh_CN, sets LANG, and reminds in Chinese"
sed -i 's/^en_US/#en_US/' "$root/etc/locale.gen"
output=$(omarchy-language en)
[[ $(<"$root/etc/locale.gen") == $'zh_CN.UTF-8 UTF-8\nen_US.UTF-8 UTF-8' ]] || fail "English enables en_US in locale.gen"
[[ $(<"$root/etc/locale.conf") == $'LANG=en_US.UTF-8\nLC_TIME=en_DK.UTF-8' ]] || fail "English sets LANG back" "$(<"$root/etc/locale.conf")"
[[ $output == "Log back in to switch the language." ]] || fail "English reminds in English" "$output"
pass "switching to English generates en_US and sets LANG"
printf 'LC_TIME=en_DK.UTF-8' >"$root/etc/locale.conf"
omarchy-language zh >/dev/null
[[ $(<"$root/etc/locale.conf") == $'LC_TIME=en_DK.UTF-8\nLANG=zh_CN.UTF-8' ]] || fail "a locale.conf without LANG or a final newline gains LANG on its own line" "$(<"$root/etc/locale.conf")"
: >"$root/etc/locale.conf"
omarchy-language en >/dev/null
[[ $(<"$root/etc/locale.conf") == "LANG=en_US.UTF-8" ]] || fail "an empty locale.conf gains LANG" "$(<"$root/etc/locale.conf")"
pass "a locale.conf without LANG gains one on its own line"
printf 'LANG="zh_CN.UTF-8"\n' >"$root/etc/locale.conf"
[[ $(omarchy-language) == "zh_CN.UTF-8" ]] || fail "a double-quoted LANG reads unquoted" "$(omarchy-language)"
printf "LANG='en_US.UTF-8'\n" >"$root/etc/locale.conf"
[[ $(omarchy-language) == "en_US.UTF-8" ]] || fail "a single-quoted LANG reads unquoted" "$(omarchy-language)"
printf 'LC_TIME=en_DK.UTF-8\n' >"$root/etc/locale.conf"
[[ -z $(LANG=de_DE.UTF-8 omarchy-language) ]] || fail "a locale.conf without LANG reads empty, not the caller's LANG"
pass "omarchy-language reads quoted values and ignores the caller's LANG"
before=$(<"$root/etc/locale.conf")
if output=$(omarchy-language fr 2>&1); then
fail "an unknown language is refused" "$output"
fi
[[ $output == "Usage: omarchy-language [en|zh]" && $(<"$root/etc/locale.conf") == "$before" ]] || fail "a refused language changes nothing" "$output"
pass "an unknown language is refused and changes nothing"
: >"$calls"
GUM_ANSWER=0 script -qec "omarchy-language en" /dev/null >/dev/null
grep -qxF 'gum confirm --affirmative Log out now --negative Later Log out now to switch the language?' "$calls" &&
grep -qx 'omarchy-system-logout ' "$calls" || fail "a terminal switch offers to log out and logs out on yes" "$(<"$calls")"
: >"$calls"
GUM_ANSWER=1 script -qec "omarchy-language zh" /dev/null >/dev/null
grep -qxF 'gum confirm --affirmative 立即注销 --negative 稍后 立即注销以切换语言?' "$calls" && ! grep -q '^omarchy-system-logout' "$calls" ||
fail "a terminal switch asks in the new language and keeps the session on no" "$(<"$calls")"
pass "a terminal switch offers to log out in the new language"
+45
View File
@@ -0,0 +1,45 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
require_command node
node - "$ROOT" <<'JS'
const fs = require('fs')
const root = process.argv[2]
const model = require(`${root}/shell/plugins/menu/MenuModel.js`)
const read = name => model.parseMenuJsonc(fs.readFileSync(`${root}/default/omarchy/${name}`, 'utf8'))
const fail = message => { console.error(`not ok - ${message}`); process.exit(1) }
const defaults = read('omarchy-menu.jsonc')
const chinese = read('omarchy-menu.zh-cn.jsonc')
const byId = Object.fromEntries(defaults.map(item => [item.id, item]))
const unknown = chinese.filter(item => !byId[item.id]).map(item => item.id)
if (unknown.length) fail(`every Chinese label names a shipped menu id: ${unknown}`)
const extra = chinese.filter(item => item.declared.some(key => key !== 'label' && key !== 'title')).map(item => item.id)
if (extra.length) fail(`the Chinese overlay only retitles rows: ${extra}`)
console.log('ok - the Chinese overlay only retitles shipped menu rows')
const user = model.parseMenuJsonc('{"system.lock": {"label": "Mine"}}')
const merged = model.mergeMenuSources(defaults, chinese.concat(user)).items
for (const item of defaults) {
const result = merged[item.id]
const zh = chinese.find(entry => entry.id === item.id)
for (const key of ['action', 'when', 'checked', 'disabled', 'target', 'provider', 'kind', 'icon', 'parent']) {
if (JSON.stringify(result[key]) !== JSON.stringify(item[key])) fail(`${item.id} keeps its ${key} under the Chinese overlay`)
}
if (item.id !== 'system.lock' && zh && zh.declared.includes('label') && result.label !== zh.label) fail(`${item.id} shows its Chinese label`)
}
if (merged['system.lock'].label !== 'Mine') fail('a user extension still overrides the Chinese label')
if (merged['system.reboot'].label !== '重启' || merged['setup.language'].label !== '语言') fail('Chinese labels reach the merged menu')
console.log('ok - Chinese labels replace only the label, and user extensions still win')
JS
grep -qF 'root.localeMenuItems.concat(root.userMenuItems)' "$ROOT/shell/plugins/menu/Menu.qml" &&
grep -qF 'I18n.chinese ? omarchyPath + "/default/omarchy/omarchy-menu.zh-cn.jsonc"' "$ROOT/shell/plugins/menu/Menu.qml" &&
grep -qF 'var chinese = Qt.locale().name.indexOf("zh_CN") === 0' "$ROOT/shell/Commons/Tr.js" ||
fail "the menu loads the Chinese overlay under zh_CN, beneath the user extension"
pass "the menu loads the Chinese overlay under zh_CN, beneath the user extension"
+1
View File
@@ -45,6 +45,7 @@ printf 'terminal: %s\n' "$*" >>"$FAKE_CALLS"
STUB
chmod +x "$STUB_DIR"/*
ln -s "$ROOT/bin/omarchy-text" "$STUB_DIR/omarchy-text"
# Runs the picker against a plugin list, answering its prompt with $2. Leaves
# the rows it offered in $ROWS and what it called in $CALLS.
+22 -2
View File
@@ -62,9 +62,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 = [
@@ -76,6 +77,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')
@@ -284,6 +295,15 @@ assert(
'menu always exposes every supported browser, terminal, and editor under Defaults'
)
assert(!defaultById['install.ai.crush'], 'menu removes Crush from Install > AI')
const languageEntries = defaultItems.filter(item => item.parent === 'setup.language')
assert(
languageEntries.map(item => item.id).join('\0') === 'setup.language.en\0setup.language.zh'
&& defaultById['setup.language.en'].checked.includes('== \"en_US.UTF-8\"')
&& defaultById['setup.language.zh'].checked.includes('== \"zh_CN.UTF-8\"')
&& defaultById['setup.language.en'].action.includes('omarchy-language en')
&& defaultById['setup.language.zh'].action.includes('omarchy-language zh'),
'menu offers English and Simplified Chinese under Setup > Language'
)
// 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. The
@@ -41,6 +41,7 @@ echo "notify $*" >>"$TMP/notes"
echo "noise on stdout"
SH
chmod +x "$tmp/bin"/*
ln -s "$ROOT/bin/omarchy-text" "$tmp/bin/omarchy-text"
device() {
rm -f "$tmp/calls" "$tmp/notes" "$tmp/next" "$tmp/modules"
+55 -1
View File
@@ -53,14 +53,17 @@ source "$ROOT/install/provisioning/setup-form.sh"
notice() { printf '%s\n' "$1" >>"$NOTICES"; }
language=${FORM_LANGUAGE:-en}
if [[ -n ${TAKEN_USERS:-} ]]; then
omarchy_username_taken() { [[ " $TAKEN_USERS " == *" $1 "* ]]; }
fi
trap 'if [[ ${FUNCNAME[0]:-} == "$PROMPT_FN" ]]; then printf "returned\n" >>"$MARKER"; fi' RETURN
"$PROMPT_FN"
"$PROMPT_FN" ${PROMPT_ARG:+"$PROMPT_ARG"}
printf 'language=%s\n' "${language:-}"
printf 'keyboard=%s\n' "${keyboard:-}"
printf 'keyboard_label=%s\n' "${keyboard_label:-}"
printf 'keyboard_input_method=%s\n' "${keyboard_input_method:-}"
@@ -125,6 +128,37 @@ source "$ROOT/install/provisioning/setup-form.sh"
fail "English (US) leads the keyboard layouts so gum choose opens on the default"
pass "the form publishes the 0/1/130 status contract and leads with English (US)"
# Language
run_prompt omarchy_prompt_language "0:Chinese"
assert_status 0 "language prompt succeeds"
[[ $(field language) == "zh" ]] || fail "language prompt stores Chinese as zh"
[[ $(<"$tmp_dir/stdin.1") == $'English\nChinese' ]] || fail "language prompt offers English then Chinese"
grep -qxF -- 'choose --selected English --header Select language' "$GUM_ARGS" || fail "language prompt preselects English on a fresh form"
FORM_LANGUAGE=zh run_prompt omarchy_prompt_language "0:English"
[[ $(field language) == "en" ]] || fail "language prompt stores English as en"
grep -qxF -- 'choose --selected Chinese --header Select language' "$GUM_ARGS" || fail "language prompt preselects the language chosen on an earlier visit"
pass "language prompt maps English and Chinese and preselects the current language"
run_prompt omarchy_prompt_language "1:"
assert_status "$OMARCHY_FORM_BACK" "language prompt reports Esc as back"
assert_returned "language prompt survives Esc under set -e"
run_prompt omarchy_prompt_language "130:"
assert_status "$OMARCHY_FORM_SIGNAL" "language prompt reports Ctrl+C as the caller's signal"
assert_returned "language prompt survives Ctrl+C under set -e"
pass "language prompt propagates Esc and Ctrl+C without dying under set -e"
OMARCHY_CJK_CONSOLE=1 run_prompt omarchy_prompt_language "0:简体中文 Chinese"
assert_status 0 "language prompt succeeds in a CJK console"
[[ $(field language) == "zh" ]] || fail "language prompt stores 简体中文 as zh"
[[ $(<"$tmp_dir/stdin.1") == $'English\n简体中文 Chinese' ]] || fail "a CJK console offers the Chinese name"
grep -qxF -- 'choose --selected English --header Select language · 选择语言' "$GUM_ARGS" || fail "a CJK console asks in both languages"
OMARCHY_CJK_CONSOLE=1 FORM_LANGUAGE=zh run_prompt omarchy_prompt_language "0:English"
[[ $(field language) == "en" ]] || fail "language prompt stores English as en in a CJK console"
grep -qxF -- 'choose --selected 简体中文 Chinese --header Select language · 选择语言' "$GUM_ARGS" || fail "a CJK console preselects Chinese by its shown name"
pass "a CJK console names Chinese in Chinese and maps it back to zh"
# Keyboard
run_prompt omarchy_prompt_keyboard "0:German"
@@ -135,6 +169,11 @@ assert_status 0 "keyboard prompt succeeds"
grep -qF -- '--selected English (US)' "$GUM_ARGS" || fail "keyboard prompt preselects English (US)"
pass "keyboard prompt maps the chosen label to its keymap"
PROMPT_ARG="Chinese (Simplified, Pinyin)" run_prompt omarchy_prompt_keyboard "0:Chinese (Simplified, Pinyin)"
grep -qF -- '--selected Chinese (Simplified\, Pinyin)' "$GUM_ARGS" && [[ $(field keyboard_input_method) == "pinyin" ]] ||
fail "keyboard prompt preselects the layout its caller names, escaping the comma gum splits --selected on"
pass "keyboard prompt preselects the layout its caller names"
for selection in 'Japanese|jp106|mozc|' 'Japanese (US keyboard)|us|mozc|' 'Korean|us|hangul|kr' 'Lao|us|none|la' 'Chinese (Simplified, Pinyin)|us|pinyin|' 'Chinese (Traditional, Zhuyin)|us|chewing|'; do
IFS='|' read -r label keymap method layout <<< "$selection"
run_prompt omarchy_prompt_keyboard "0:$label"
@@ -224,6 +263,21 @@ assert_status "$OMARCHY_FORM_BACK" "hostname prompt reports Esc as back"
assert_returned "hostname prompt survives Esc under set -e"
pass "hostname prompt propagates Esc without dying under set -e"
# Chinese text
OMARCHY_INSTALL_LANG=zh run_prompt omarchy_prompt_username "0:Bad Name" "0:dhh"
assert_status 0 "username prompt succeeds in Chinese"
grep -qF -- '--prompt 用户名> ' "$GUM_ARGS" || fail "OMARCHY_INSTALL_LANG=zh asks for the username in Chinese"
assert_notices "OMARCHY_INSTALL_LANG=zh explains a bad username in Chinese" "用户名只能用字母和数字,不含空格"
run_prompt omarchy_prompt_username "0:dhh"
grep -qF -- '--prompt Username> ' "$GUM_ARGS" || fail "the form stays English without OMARCHY_INSTALL_LANG"
mapfile -t texts < <(grep -o 'omarchy_text "[^"]*"' "$ROOT/install/provisioning/setup-form.sh" | sed 's/^omarchy_text "//; s/"$//')
((${#texts[@]} > 15)) || fail "the form routes its text through omarchy_text"
for text in "${texts[@]}"; do
[[ -n ${OMARCHY_ZH[$text]:-} ]] || fail "every form text has a Chinese entry" "missing: $text"
done
pass "OMARCHY_INSTALL_LANG=zh puts every form text in Chinese"
# Timezone
TZ_GUESS=Europe/Copenhagen run_prompt omarchy_prompt_timezone "0:Europe/Copenhagen"
+73
View File
@@ -0,0 +1,73 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
run_node_test <<'JS'
const fs = require('fs')
const vm = require('vm')
const source = fs.readFileSync(path.join(root, 'shell/Commons/I18nZh.js'), 'utf8').replace('.pragma library', '')
const context = {}
vm.runInNewContext(source + '\nthis.table = table', context)
const table = context.table
const files = []
const walk = dir => {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name)
if (entry.isDirectory()) walk(full)
else if (/\.(qml|js)$/.test(entry.name)) files.push(full)
}
}
walk(path.join(root, 'shell'))
const missing = []
let calls = 0
for (const file of files) {
const text = fs.readFileSync(file, 'utf8')
for (const list of text.matchAll(/I18n\.trMatch\([^\[]*\[([^\]]*)\]/g)) {
for (const item of list[1].matchAll(/"((?:[^"\\]|\\.)*)"/g)) {
const key = JSON.parse('"' + item[1] + '"')
if (!(key in table)) missing.push(`${path.relative(root, file)}: ${key}`)
}
}
for (const match of text.matchAll(/I18n\.tr\(\s*"((?:[^"\\]|\\.)*)"/g)) {
calls++
const key = JSON.parse('"' + match[1] + '"')
if (!(key in table)) missing.push(`${path.relative(root, file)}: ${key}`)
}
}
assert(calls > 300, 'the shell routes its text through I18n.tr', `found ${calls} calls`)
// Senders translate messages that carry a name or number with omarchy-text, or text() in Python.
const senders = []
for (const dir of ['bin', 'install/helpers', 'default/input-methods'])
for (const entry of fs.readdirSync(path.join(root, dir), { withFileTypes: true })) {
if (!entry.isFile()) continue
const name = path.join(dir, entry.name)
const text = fs.readFileSync(path.join(root, name), 'utf8')
for (const match of text.matchAll(/(?:omarchy-text\s+|\btext\(\s*)"((?:[^"\\]|\\.)*)"/g)) {
if (match[1].includes('$')) continue
senders.push([name, JSON.parse('"' + match[1] + '"')])
}
}
assert(senders.length > 40, 'senders translate their parameterized messages', `found ${senders.length}`)
const unsent = senders.filter(([, key]) => !(key in table)).map(([file, key]) => `${file}: ${key}`)
assert(unsent.length === 0, 'every sender message has a Chinese entry', unsent.join('\n'))
assert(missing.length === 0, 'every I18n.tr text has a Chinese entry', missing.join('\n'))
const placeholders = s => (s.match(/%\d/g) || []).sort().join(',')
const mismatched = Object.keys(table).filter(key => placeholders(key) !== placeholders(table[key]))
assert(mismatched.length === 0, 'every Chinese entry keeps its English placeholders', mismatched.join('\n'))
JS
text=$ROOT/bin/omarchy-text
[[ $(OMARCHY_PATH=$ROOT LANG=zh_CN.UTF-8 "$text" "Sent to %1" "phone %2") == "已发送给 phone %2" ]] ||
fail "omarchy-text translates and fills %1 in one pass"
[[ $(OMARCHY_PATH=$ROOT LANG=en_US.UTF-8 "$text" "Battery is down to %1%" 9) == "Battery is down to 9%" ]] ||
fail "omarchy-text keeps English and a literal % outside zh_CN"
[[ $(OMARCHY_PATH=$ROOT LANG=zh_CN.UTF-8 "$text" "Unknown 50% of %1" x) == "Unknown 50% of x" ]] ||
fail "omarchy-text passes an unknown message through"
pass "omarchy-text translates by exact key and fills arguments in one pass"
+1
View File
@@ -18,6 +18,7 @@ setup_scenario() {
notify_log="$scenario_dir/notifications"
journal_log="$scenario_dir/journal"
mkdir -p "$mock_bin" "$state_dir"
ln -s "$ROOT/bin/omarchy-text" "$mock_bin/omarchy-text"
: >"$notify_log"
: >"$journal_log"
+1
View File
@@ -74,6 +74,7 @@ esac
SH
done
chmod +x "$mock_bin"/*
ln -s "$ROOT/bin/omarchy-text" "$mock_bin/omarchy-text"
run_power_command() {
local action="$1"
+1
View File
@@ -28,6 +28,7 @@ printf '%s\n' "\$*" >>"$WORKDIR/notifications"
SH
chmod +x "$WORKDIR/bin/"*
ln -s "$ROOT/bin/omarchy-text" "$WORKDIR/bin/omarchy-text"
receive() {
local expected="$1"
+2 -1
View File
@@ -74,7 +74,8 @@ class TypingSetupTest(unittest.TestCase):
def test_an_uninstalled_engine_names_its_package_without_restarting(self):
absent = subprocess.CompletedProcess([], 1)
with patch.object(typing.setup, "live_group", return_value=("Default", "us", [["keyboard-us", ""]])), patch.object(typing.setup, "available_methods", return_value=[]), patch.object(typing.subprocess, "run", return_value=absent), patch.object(typing.setup, "run") as run, patch.object(typing, "set_inputs") as save:
english = lambda text, *args: text.replace("%1", str(args[0]) if args else "").replace("%2", str(args[1]) if len(args) > 1 else "")
with patch.object(typing.setup, "live_group", return_value=("Default", "us", [["keyboard-us", ""]])), patch.object(typing.setup, "available_methods", return_value=[]), patch.object(typing.subprocess, "run", return_value=absent), patch.object(typing, "text", side_effect=english), patch.object(typing.setup, "run") as run, patch.object(typing, "set_inputs") as save:
with self.assertRaisesRegex(RuntimeError, "Install fcitx5-mozc to use Japanese"):
typing.save_inputs(["mozc"])
run.assert_not_called()
+5 -1
View File
@@ -3,7 +3,11 @@
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/base-test.sh"
OMARCHY_PATH="$ROOT" python "$ROOT/test/shell.d/typing-setup-checks.py"
PATH="$ROOT/bin:$PATH" OMARCHY_PATH="$ROOT" python "$ROOT/test/shell.d/typing-setup-checks.py"
zh=$(cd "$ROOT/default/input-methods" && PATH="$ROOT/bin:$PATH" OMARCHY_PATH="$ROOT" LANG=zh_CN.UTF-8 python -c 'import typing_setup; print(typing_setup.text("Install %1 to use %2", "fcitx5-mozc", "Mozc"))')
[[ $zh == "安装 fcitx5-mozc 后才能使用 Mozc" ]] || fail "a Chinese session words the input setup errors in Chinese" "$zh"
pass "a Chinese session words the input setup errors in Chinese"
run_node_test <<'JS'
const menu = requireFromRoot('shell/plugins/menu/MenuModel.js')