diff --git a/configs/airootfs/root/.automated_script.sh b/configs/airootfs/root/.automated_script.sh index ee9137b..606c037 100644 --- a/configs/airootfs/root/.automated_script.sh +++ b/configs/airootfs/root/.automated_script.sh @@ -102,24 +102,35 @@ if /usr/local/bin/omarchy-cidata-load; then echo "Autoinstall configuration found on cidata drive; skipping the configurator." export OMARCHY_UI_INTERACTIVE=no else - # The configurator exits with this after China is picked on the VT, which - # cannot draw Chinese. cage + foot rerun this script in a console that can. + # China moves to cage + foot, which draw Chinese; Global moves back to the VT, which applies keymaps. cjk_relaunch=10 - if [[ -z ${OMARCHY_CJK_CONSOLE:-} ]] && command -v cage >/dev/null; then + vt_return=11 + if [[ -n ${OMARCHY_CJK_CONSOLE:-} ]]; then + export OMARCHY_VT_RETURN=$vt_return + elif command -v cage >/dev/null; then export OMARCHY_CJK_RELAUNCH=$cjk_relaunch fi ./configurator && status=0 || status=$? - unset OMARCHY_CJK_RELAUNCH - if ((status == cjk_relaunch)); then + while ((status == cjk_relaunch)); do cjk_runtime=/run/omarchy-cjk-console mkdir -p -m 700 "$cjk_runtime" - rm -f "$cjk_runtime/cjk-console-started" - status=0 - XDG_RUNTIME_DIR=$cjk_runtime OMARCHY_CJK_CONSOLE=1 OMARCHY_INSTALL_REGION=china LANG=C.UTF-8 WLR_RENDERER=pixman \ - cage -- foot -c /usr/share/omarchy-iso/foot.ini "$0" || status=$? - [[ -f $cjk_runtime/cjk-console-started ]] && exit "$status" - echo "The Chinese console could not start (cage exit $status); continuing in English." - OMARCHY_INSTALL_REGION=china ./configurator && status=0 || status=$? + rm -f "$cjk_runtime/cjk-console-started" "$cjk_runtime/return-to-vt" + cage_status=0 + env -u OMARCHY_CJK_RELAUNCH XDG_RUNTIME_DIR=$cjk_runtime OMARCHY_CJK_CONSOLE=1 OMARCHY_INSTALL_REGION=china \ + LANG=C.UTF-8 WLR_RENDERER=pixman cage -- foot -c /usr/share/omarchy-iso/foot.ini "$0" || cage_status=$? + if [[ -f $cjk_runtime/return-to-vt ]]; then + OMARCHY_INSTALL_REGION=global ./configurator && status=0 || status=$? + elif [[ -f $cjk_runtime/cjk-console-started ]]; then + exit "$cage_status" + else + echo "The Chinese console could not start (cage exit $cage_status); continuing in English." + unset OMARCHY_CJK_RELAUNCH + OMARCHY_INSTALL_REGION=china ./configurator && status=0 || status=$? + fi + done + if [[ -n ${OMARCHY_CJK_CONSOLE:-} ]] && ((status == vt_return)); then + touch "$XDG_RUNTIME_DIR/return-to-vt" + exit 0 fi ((status == 0)) || exit "$status" fi diff --git a/configs/airootfs/root/configurator b/configs/airootfs/root/configurator index 7d7a5f2..5dbd624 100644 --- a/configs/airootfs/root/configurator +++ b/configs/airootfs/root/configurator @@ -271,12 +271,14 @@ notice() { # STEP 1: KEYBOARD LAYOUT -# China reads in Chinese only where the console draws CJK. On the VT, -# .automated_script.sh sets OMARCHY_CJK_RELAUNCH and reruns us in one. +# .automated_script.sh moves China into the CJK console and Global back to the VT, which applies keymaps. apply_region_language() { if [[ $region == "china" && -n ${OMARCHY_CJK_RELAUNCH:-} ]]; then exit "$OMARCHY_CJK_RELAUNCH" fi + if [[ $region == "global" && -n ${OMARCHY_VT_RETURN:-} ]]; then + exit "$OMARCHY_VT_RETURN" + fi if [[ $region == "china" && -n ${OMARCHY_CJK_CONSOLE:-} ]]; then export OMARCHY_INSTALL_LANG=zh else @@ -297,15 +299,16 @@ keyboard_form() { say --foreground 8 "$(omarchy_text "Press Ctrl+C to prepare this machine for another owner.")" echo - if $ask_region; then - omarchy_prompt_region && status=0 || status=$? - else - status=0 - fi - ask_region=true + omarchy_prompt_region && status=0 || status=$? if ((status == 0)); then apply_region_language - omarchy_prompt_keyboard && status=0 || status=$? + # cage keeps its US layout, so the CJK console offers no other. + if [[ -n ${OMARCHY_CJK_CONSOLE:-} ]]; then + keyboard="us" + keyboard_label="English (US)" + else + omarchy_prompt_keyboard && status=0 || status=$? + fi fi ((status == 0)) && break @@ -1128,16 +1131,15 @@ select_installation() { defer_provisioning=false install_target="full_disk" -# A relaunch in the CJK console arrives with the region already chosen. +# A rerun in the other console arrives with the region already chosen. region="${OMARCHY_INSTALL_REGION:-global}" -ask_region=true # Let the live VT reach its settled width before the first screen so the logo # isn't measured against the transient 80-column boot console. wait_for_stable_terminal if [[ -n ${OMARCHY_INSTALL_REGION:-} ]]; then - ask_region=false + apply_region_language else greeter fi diff --git a/configs/airootfs/usr/local/bin/omarchy-install-dashboard b/configs/airootfs/usr/local/bin/omarchy-install-dashboard index 39d87a3..3bb8d22 100755 --- a/configs/airootfs/usr/local/bin/omarchy-install-dashboard +++ b/configs/airootfs/usr/local/bin/omarchy-install-dashboard @@ -112,6 +112,17 @@ OMARCHY_ZH+=( ["Drop to shell"]="进入 shell" ["Reboot"]="重启" ["Power off"]="关机" + ["The install medium was misread"]="安装介质读取出错" + ["This package is intact on this ISO, so the download and the write were fine:"]="这个软件包在 ISO 中完好,下载和写入都没有问题:" + ["Try another USB port or another stick, and test this machine's memory"]="换一个 USB 口或换一个 U 盘,并检测这台机器的内存" + ["The install medium is damaged"]="安装介质已损坏" + ["This package no longer matches the checksum it was built with:"]="这个软件包和构建时的校验和不再一致:" + ["Re-download the ISO, verify it with sha256sum, and write the USB again"]="重新下载 ISO,用 sha256sum 校验,再重新写入 U 盘" + ["If a verified ISO fails here too, the USB stick itself is the problem"]="如果校验过的 ISO 在这里也失败,问题出在 U 盘本身" + ["The install medium is damaged or misread"]="安装介质已损坏或读取出错" + ["pacman rejected this package, read straight off this ISO:"]="pacman 拒绝了这个直接从 ISO 读出的软件包:" + ["Verify your download with sha256sum, then write the USB again"]="用 sha256sum 校验下载的文件,再重新写入 U 盘" + ["If the download verifies, the stick or this machine's memory is at fault"]="如果下载校验无误,问题出在 U 盘或这台机器的内存" ) if [[ ${OMARCHY_INSTALL_LANG:-} == "zh" ]]; then @@ -857,13 +868,18 @@ reboot_prompt() { "" <"$TTY_PATH" >"$TTY_PATH" 2>&1 } +# Columns, not characters: each CJK character takes two. truncate_for_width() { local text="$1" width="$2" - if (( ${#text} > width )); then - printf '%s…' "${text:0:$((width - 1))}" - else + if (( $(printf '%s' "$text" | wc -L) <= width )); then printf '%s' "$text" + return fi + text=${text:0:$((width - 1))} + while (( $(printf '%s' "$text" | wc -L) > width - 1 )); do + text=${text%?} + done + printf '%s…' "$text" } render_failure_log_tail() { @@ -977,6 +993,8 @@ render_media_diagnosis() { blank_line continue fi + # The support log keeps English; the screen shows the translation, and package names pass through. + [[ ${OMARCHY_INSTALL_LANG:-} == "zh" ]] && line=${OMARCHY_ZH[$line]:-$line} if (( first )); then center "${RED}$(truncate_for_width "$line" "$CONTENT_WIDTH")${RESET}" "$CONTENT_WIDTH" first=0 diff --git a/test/unit/install-text-zh-test.sh b/test/unit/install-text-zh-test.sh index 757efe8..b7b8bd5 100644 --- a/test/unit/install-text-zh-test.sh +++ b/test/unit/install-text-zh-test.sh @@ -1,7 +1,6 @@ #!/bin/bash # -# China installs read in Chinese: every text the configurator and the install -# dashboard pass to omarchy_text needs an OMARCHY_ZH entry, or it shows in English. +# Every installer text needs an OMARCHY_ZH entry, or a China install shows it in English. set -euo pipefail @@ -39,6 +38,13 @@ for action in "View full log" "Drop to shell" "Reboot" "Power off" "Upload log f done printf 'ok - labels passed through variables have Chinese entries\n' +mapfile -t diagnosis < <(grep -x ' *echo "[^"$]*"' "$ROOT/configs/airootfs/usr/local/bin/omarchy-install-diagnose-media" | sed 's/^ *echo "//; s/"$//') +((${#diagnosis[@]} >= 10)) || fail "the media diagnosis sentences are found" "found ${#diagnosis[@]}" +for line in "${diagnosis[@]}"; do + [[ -n ${OMARCHY_ZH[$line]:-} ]] || fail "every media diagnosis sentence has a Chinese entry" "missing: $line" +done +printf 'ok - every media diagnosis sentence has a Chinese entry\n' + [[ $(OMARCHY_INSTALL_LANG=zh omarchy_text "Confirm overwriting %s" /dev/vda) == "确认覆盖 /dev/vda" ]] || fail "Chinese text keeps its printf arguments" [[ $(omarchy_text "Confirm overwriting %s" /dev/vda) == "Confirm overwriting /dev/vda" ]] || diff --git a/test/unit/test_region.py b/test/unit/test_region.py index c501ddc..16f5695 100644 --- a/test/unit/test_region.py +++ b/test/unit/test_region.py @@ -74,40 +74,69 @@ class RegionTest(unittest.TestCase): chroot = next(call.args[0] for call in run.call_args_list if call.args[0][0] == "arch-chroot") self.assertIn(f"OMARCHY_REGION={region}", chroot) - def run_keyboard_form(self, script: str) -> str: + def run_keyboard_form(self, script: str, **env: str) -> subprocess.CompletedProcess: configurator = (ROOT / "configs/airootfs/root/configurator").read_text() + language = re.search(r"^apply_region_language\(\) \{\n.*?^\}", configurator, re.M | re.S) form = re.search(r"^keyboard_form\(\) \{\n.*?^\}", configurator, re.M | re.S) - init = re.search(r'^region="global"$', configurator, re.M) + init = re.search(r'^region="\$\{OMARCHY_INSTALL_REGION:-global\}"$', configurator, re.M) + self.assertIsNotNone(language) self.assertIsNotNone(form) self.assertIsNotNone(init) harness = "\n".join([ "set -euo pipefail", "OMARCHY_FORM_BACK=1; OMARCHY_FORM_SIGNAL=130; defer_provisioning=false", "clear_logo() { :; }; say() { :; }; abort() { exit 99; }; tty() { echo /dev/pts/0; }", + "omarchy_text() { printf '%s' \"$1\"; }", "answers=(" + script + "); step=0", "answer() { local a=${answers[step]}; step=$((step + 1)); [[ $a == ok:* ]] && eval \"${a#ok:}\" && return 0; return ${a#rc:}; }", "omarchy_prompt_region() { echo \"region-preselect=$region\"; answer; }", - "omarchy_prompt_keyboard() { answer; }", + "omarchy_prompt_keyboard() { echo keyboard-prompt; answer; }", "confirm_prepare_for_another_owner() { answer; }", init.group(), + language.group(), form.group(), "keyboard_form", - 'echo "region=$region defer=$defer_provisioning"', + 'echo "region=$region defer=$defer_provisioning keyboard=${keyboard:-} lang=${OMARCHY_INSTALL_LANG:-}"', ]) - return subprocess.run(["bash", "-c", harness], check=True, capture_output=True, text=True).stdout + return subprocess.run(["bash", "-c", harness], capture_output=True, text=True, + env={"PATH": os.environ["PATH"], **env}) + + def lines(self, result: subprocess.CompletedProcess) -> list[str]: + return [line for line in result.stdout.splitlines() if line] def test_ctrl_c_before_choosing_defers_as_global(self): out = self.run_keyboard_form("rc:130 ok:true") - self.assertIn("region=global defer=true", out) + self.assertIn("region=global defer=true", out.stdout) def test_back_from_keyboard_keeps_the_chosen_region(self): out = self.run_keyboard_form("ok:region=china rc:1 ok:true ok:keyboard=us") - self.assertEqual([line for line in out.splitlines() if line], [ + self.assertEqual(self.lines(out), [ "region-preselect=global", + "keyboard-prompt", "region-preselect=china", - "region=china defer=false", + "keyboard-prompt", + "region=china defer=false keyboard=us lang=", ]) + def test_china_on_the_vt_hands_over_to_the_cjk_console(self): + out = self.run_keyboard_form("ok:region=china", OMARCHY_CJK_RELAUNCH="10") + self.assertEqual(out.returncode, 10) + self.assertEqual(self.lines(out), ["region-preselect=global"]) + + def test_cjk_console_reads_chinese_with_a_us_keyboard(self): + out = self.run_keyboard_form("ok:true", OMARCHY_CJK_CONSOLE="1", OMARCHY_VT_RETURN="11", + OMARCHY_INSTALL_REGION="china") + self.assertEqual(out.returncode, 0) + self.assertEqual(self.lines(out), [ + "region-preselect=china", + "region=china defer=false keyboard=us lang=zh", + ]) + + def test_global_in_the_cjk_console_returns_to_the_vt(self): + out = self.run_keyboard_form("ok:region=global", OMARCHY_CJK_CONSOLE="1", OMARCHY_VT_RETURN="11", + OMARCHY_INSTALL_REGION="china") + self.assertEqual(out.returncode, 11) + self.assertNotIn("keyboard-prompt", out.stdout) if __name__ == "__main__": unittest.main()