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:
138 files changed
+1986
-558
No files matched your search
@@ -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')
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
Executable
+75
@@ -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"
|
||||
Executable
+45
@@ -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"
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in new issue
Block a user