Skip to content

[feat] Restore upstream's CJK font and fcitx5, add Traditional Chinese - #188

Open
k5953837 wants to merge 12 commits into
omacom:mainfrom
k5953837:feat/zh-tw-language
Open

[feat] Restore upstream's CJK font and fcitx5, add Traditional Chinese#188
k5953837 wants to merge 12 commits into
omacom:mainfrom
k5953837:feat/zh-tw-language

Conversation

@k5953837

Copy link
Copy Markdown

Fixes #175.

Summary

The factory guest cannot display or type Traditional Chinese. It generates only
en_US.UTF-8, ships no CJK font, and omits fcitx5, so Chinese renders as tofu
boxes with no way to enter it.

Most of what is missing was never Omarchy's decision. At the pinned release
(4.0.2, 346e69e1c), install/omarchy-base.packages already contains:

35: fcitx5
36: fcitx5-gtk
37: fcitx5-qt
88: noto-fonts-cjk

guest/packages.txt — the intentionally trimmed ARM64 transaction — drops all
four. This restores them, adds fcitx5-chewing for Bopomofo, and supplies the
Traditional-Chinese-specific configuration upstream does not: a zh_TW.UTF-8
locale, a fontconfig rule that picks TC Han glyph variants over Simplified or
Japanese ones, and a way to choose the language.

That trim has already cost one bug: #91, where the omitted fcitx5 binary left
upstream's omarchy-fcitx5.service in a 203/EXEC restart loop. The
ConditionPathExists=/usr/bin/fcitx5 drop-in added in #106 is self-healing —
installing the package makes it pass with no change to the guard.

What changes

Concern Change
Packages Restore noto-fonts-cjk, fcitx5, fcitx5-gtk, fcitx5-qt; add fcitx5-chewing
Locale Generate zh_TW.UTF-8 alongside en_US.UTF-8 in configure-rootfs.sh
Glyph variants New etc/fonts/conf.d/30-try-omarchy.conf preferring Noto Sans/Serif/Mono CJK TC for zh-tw and zh-hant
Input method Seed /etc/skel/.config/fcitx5/profile with keyboard-us first, chewing second
XWayland XMODIFIERS=@im=fcitx in environment.d
Chromium Append --enable-wayland-ime to upstream's /etc/skel/.config/chromium-flags.conf
Choosing it A Language row in the start menu, in the shape the other integrations already use

How the choice reaches the guest

The Language row stores a preference the same way Shared folder, Port forwarding
and Resources do, run-qemu-gpu.sh turns it into a tryomarchy.locale= kernel
argument the way it already derives tryomarchy.ssh_access=1, and a guest
oneshot unit writes /etc/locale.conf before either login entry point starts.

Leaving the row alone emits no environment variable and no kernel argument, so
an English launch is byte-identical to today. Both sides validate the value
against the locales the image actually generates; the host fails loudly on an
unknown one because that value comes from our own code, while the guest ignores
it silently because the kernel command line does not deserve that trust.

Beyond #175

#175 proposed the guest-side restore only. The Language row is not in it. I
added it because the guest half alone leaves a user with a working locale and no
way to select it short of editing /etc/locale.conf by hand, and because it
needed no new mechanism — it is the same preference → OMARCHY_QEMU_GPU_*
kernel-argument path three existing settings already take. Happy to split it out
if you would rather see the packages land on their own.

Deliberately excluded

  • fcitx5-chinese-addons — hard-depends on qt6-webengine, which does not
    fit the 6144 MiB image. Add Chinese input and locale to the factory guest #63 hit the same wall. Bopomofo via fcitx5-chewing
    does not need it.
  • fcitx5-configtool — upstream does not ship it either, and it pulls a KDE
    dependency chain for a GUI the seeded profile makes unnecessary.
  • Raising image.sizeMiB — that value is duplicated as a runtime contract in
    run-qemu-gpu.sh and asserted in run-qemu-ssh-contract.test.sh; changing it
    is an architecture discussion, not part of this.
  • Translating Omarchy's own interface — the setup wizard and menus stay
    English, and README says so. Upstream has no translation mechanism; those
    strings are hardcoded in its shell scripts.

On the two earlier attempts

#26 and #63 both proposed Simplified Chinese and were closed by their own
authors. The only maintainer feedback on either was on #26:

Thanks for pushing this! Before merge, please update the package lock so it
includes the new packages and builds don't fail.

That PR bumped requestedFileSha256 without a real resolve. This one's lock is
a genuine refresh: nine resolved entries, no existing version touched.

Validation

Package lock — regenerated with guest/build-container.sh --refresh-package-lock
against current main, not carried over from an earlier branch. The diff is nine
additions, zero removals, and no change to any existing version:

enchant · fcitx5 · fcitx5-chewing · fcitx5-gtk · fcitx5-qt ·
libchewing · noto-fonts-cjk · xcb-imdkit · yoga

Five are requested in packages.txt; the other four are transitive
(libchewing for the Chewing engine, xcb-imdkit for fcitx5-qt's XCB input
method, enchant and yoga via fcitx5).

Tests./guest/test passes: 110 Python tests including eight new
test_locale.py cases (allowlist parity, symlink refusal, shell-metacharacter
payloads, idempotency), plus the native guest contract with the new zh-TW
assertions. swift test passes 254 tests including eleven new pure-model cases
for the preference store and menu presentation.

Imagemake guest completes and the packed root sits at 82.97% of the
6144 MiB budget, inside pack-image.sh's 85% ceiling.

Booted — built the image, chose Traditional Chinese in the start menu, and
ran a disposable VM through first-boot provisioning on an Apple Silicon Mac. In
the guest:

$ echo "LANG=[$LANG]"; cat /etc/locale.conf; systemctl is-active try-omarchy-locale.service
LANG=[zh_TW.UTF-8]
LANG=zh_TW.UTF-8
active

xdg-user-dirs renamed the home directories (桌面, 下載, 文件, 音樂, 圖片, 影片,
專案), eza printed dates as 11 9月 02:09, and every glyph rendered — no tofu.
Ctrl+Space reached Chewing and su3 cl3 committed 你好, after which
Ctrl+Space returned to plain US input:

$ echo 你好 from omarchy zh-TW
你好 from omarchy zh-TW

Leaving the Language row alone emits no kernel argument; /etc/locale.conf then
gets the image default, which is also what makes switching back to English take
effect on a VM that has already run in Chinese.


🤖 Generated with Claude Code

k5953837 and others added 12 commits September 10, 2026 00:49
Generate zh_TW.UTF-8 alongside en_US.UTF-8 without changing the default
LANG or KEYMAP, seed an fcitx5 profile that keeps the US keyboard first
and Chewing second, append --enable-wayland-ime to upstream's Chromium
flags, and prefer the Traditional Chinese Noto CJK face for zh-tw and
zh-hant text.

XMODIFIERS is set for XWayland clients only. GTK_IM_MODULE and
QT_IM_MODULE stay unset so native Wayland GTK4 and Qt6 applications keep
using text-input-v3, which Hyprland implements.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Assert that zh_TW.UTF-8 is generated without displacing the English
default, that the seeded fcitx5 profile keeps the US keyboard first,
that Chromium's upstream flags are appended to rather than replaced,
that Traditional Chinese glyphs win for zh-tw and zh-hant text, and
that GTK_IM_MODULE and QT_IM_MODULE are never assigned.

The last check parses non-comment lines so it keeps passing while the
environment file explains in prose why those variables stay unset.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
configure-rootfs.sh appends to upstream's chromium-flags.conf, so declare
it in authenticity.requiredPaths the way config/hypr/monitors.lua already
is. An upstream rename is then caught during source verification instead
of part way through a build.

Also add the architecture note the other guest overlay additions have,
and condense the fontconfig comment to the two reasons that carry weight.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fcitx5 substitutes the second item when a group's requested default names
the first, and rewrites DefaultIM to that resolved value on save, so
DefaultIM=keyboard-us has no effect here. What keeps an untouched session
on plain US input is keyboard-us occupying item index 0, combined with
input contexts starting inactive.

Assert the section and name pair for both items, and record the reasoning
in the profile fragment so the ordering is not "corrected" later.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Read tryomarchy.locale from the kernel command line and write LANG into
/run/environment.d, the runtime tier of the same mechanism the factory
overlay already uses to deliver XMODIFIERS. Without the token nothing is
written, so an English boot is unchanged.

The value reaches the session environment, so only the two locales
configure-rootfs.sh generates are accepted; anything else is ignored
rather than interpolated. /etc/locale.conf, LC_ALL and the console
keymap are left alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add a Language row beside the other integrations. Choosing Traditional
Chinese stores the locale in the same preference shape the other settings
use, threads OMARCHY_QEMU_GPU_LOCALE through the launch-configuration
chain, and run-qemu-gpu.sh turns it into the tryomarchy.locale kernel
argument the guest generator reads.

The launcher re-validates the value against the locales the guest image
generates rather than trusting the environment, and fails loudly on an
unknown one: unlike the kernel command line, that value comes from our
own code, so a bad one is a bug rather than something to shrug off.

Leaving the setting alone emits no environment variable and no kernel
argument, so an English launch is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Give the feature its own README section alongside the other opt-in
start-menu integrations, and widen the architecture note to cover the
host-side choice and the kernel argument that carries it.

State plainly that Omarchy's own setup wizard and menus remain English:
upstream has no translation mechanism and those strings are hardcoded in
its shell scripts, so a reader should know before switching rather than
after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Derive both sides of the allowlist check from source rather than
restating a locale list, so the generator and configure-rootfs.sh cannot
drift apart and leave a chosen language with no LANG.

Add behavioural tests that run the generator as a subprocess with
/proc/cmdline and its output directory redirected into a temporary
directory: an off-allowlist value, an empty value and a value carrying
shell metacharacters must each write nothing, which string matching
alone cannot establish.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Upstream Omarchy already ships noto-fonts-cjk, fcitx5, fcitx5-gtk and
fcitx5-qt in install/omarchy-base.packages; this fork's trimmed
transaction drops all four, which is why Chinese renders as tofu and
there is no way to type it. Put them back and add fcitx5-chewing for
Bopomofo.

fcitx5-configtool is deliberately left out: upstream does not ship it
either, and it drags in a KDE dependency chain for a GUI the seeded
profile makes unnecessary.

The refreshed lock adds exactly nine packages and changes no existing
version.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The generator wrote LANG into /run/environment.d, and measurement in a
booted guest showed why that never took effect: Omarchy starts its
session through uwsm, which merges the login shell's environment back
into the user manager after the environment.d generators have run. The
login shell takes LANG from /etc/locale.conf via PAM, so English always
won.

Write /etc/locale.conf from a oneshot unit ordered before both session
entry points instead. An absent or unrecognised token writes the image
default rather than nothing, so switching back to English in the start
menu also takes effect on a VM that has already run in Chinese.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both notes still described the systemd generator this branch replaced,
and named /run/environment.d rather than the file the login shell
actually reads.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Arch Linux ARM rotated the kernel and orc while this branch was in
progress, and the versions the previous lock named are no longer served,
so a build from it fails before it starts. These three are what the same
--refresh-package-lock run resolves today; the nine packages this branch
adds are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@k5953837

Copy link
Copy Markdown
Author

CI is sitting at action_required — this is my first PR here, so the workflow
needs your approval before it runs. Nothing to fix on my end; flagging it so it
does not look like a stuck check.

For what it is worth, make test passes locally on macOS 15 / Apple Silicon:
./guest/test is clean (110 Python tests, native guest contract verified), and
swift test passes 254 tests. The one exception is
StartMenuWindowWidthTests.swift:131, which expects content.bounds.width == 600
and gets 601.0 — it fails identically on a clean main checkout in this
environment, and CI has it passing on main, so it reads as a local font-metric
artifact rather than anything this branch touches.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

zh-TW support: restore upstream's fcitx5 and Noto CJK, add Traditional Chinese locale and Bopomofo input

1 participant