I set KDE to Chinese in System Settings. The UI stayed English.
The setting was not the problem. KDE had written it down correctly in two places, and every translation file was on disk. The problem was one layer down, and after I fixed that one, two more appeared underneath it. Each looked like the previous fix had broken something. None of them had.
Symptom | Actual cause |
|---|---|
UI stays English |
|
Chinese renders, glyphs look wrong | fontconfig picked the Korean variant out of the Noto CJK bundle |
New fonts don't show up in Plasma | Plasma requests |
Whole UI is one weight too heavy | MiSans ships weight metadata that fontconfig reads wrong |
This is one Arch box — Plasma 6.7.4 on Wayland, kernel 7.1.6-zen — but the diagnosis for each layer generalises.
The locale was never generated
KDE's side was fine:
~/.config/plasma-localerc LANG=zh_CN.UTF-8 / LANGUAGE=zh_CN
/var/lib/AccountsService/users/jin Languages=zh_CN.UTF-8;glibc's side was not:
$ locale
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
LANG=zh_CN.UTF-8 # the variable is right
$ locale -a
C C.utf8 POSIX en_US.utf8 # but zh_CN doesn't exist
$ grep -vE '^\s*#|^\s*$' /etc/locale.gen
en_US.UTF-8 UTF-8 # only English was ever enabledThe environment variable pointed at a locale that had never been built, so glibc fell back to C/POSIX and every program rendered in English. Not a missing translation package — those were all there:
$ ls /usr/share/locale/zh_CN/LC_MESSAGES/ | wc -l
533The fix is two steps: uncomment zh_CN.UTF-8 UTF-8 in /etc/locale.gen, then
sudo locale-genAny process started after that gets Chinese immediately — pacman output switched in the same shell. Processes already running inside the KDE session don't, so you still need to log out and back in for the desktop itself.
The diagnostic worth memorising: locale printing Cannot set LC_* to default locale: No such file or directory is the confirmed signature of this specific problem — the variable is set, the locale isn't generated. Don't reach for localectl status here. The System Locale it reports comes from /etc/locale.conf, which is the system-level setting; KDE's per-session language lives somewhere else entirely. Reading one while debugging the other will waste your afternoon.
The Korean variant
With Chinese finally rendering, the glyphs were wrong:
sans-serif:lang=zh-cn -> Noto Sans CJK KR
serif:lang=zh-cn -> Noto Serif CJK KR
monospace:lang=zh-cn -> Noto Sans Mono CJK KRnoto-fonts-cjk packs the SC, TC, JP and KR regional variants into a single .ttc. With no fontconfig rules in play, fontconfig picked KR out of the file's internal order. Chinese, Japanese and Korean share Unicode codepoints but not glyph shapes — 直, 骨, 化, 次 all differ — so the text displays perfectly while looking subtly foreign. It reads as "my fonts are broken," which sends people off reinstalling packages that were never the problem.
~/.config/fontconfig/ didn't exist on this machine at all.
I've written up the full fontconfig setup separately — the short version is: declare the priority explicitly, Latin faces first, CJK right behind them. Latin fonts contain no Chinese, so fontconfig falls through to the first CJK entry on its own, which keeps the config readable and the order correct.
sudo pacman -S --needed noto-fonts-extra inter-font
paru -S --needed otf-misans misans-fontconfig maplemono-nf-cnTwo corrections to that earlier post, verified against the actual repos: Inter isinter-fontinextra, notttf-inter. Andmaplemono-nf-cnis in the AUR, not the official repos.misans-fontconfigturns out to be trivial — it aliasesMi Sans(with a space) to MiSans and nothing else, so it won't fight a hand-writtenfonts.conf.
Check the real family names before you write any config, or your rules will fail silently:
fc-list -f '%{family[0]}\n' | sort -u | grep -iE '^inter|^misans|^maple'The alias block, in ~/.config/fontconfig/fonts.conf:
<alias binding="strong">
<family>sans-serif</family>
<prefer>
<family>Inter</family>
<family>Noto Sans</family>
<family>MiSans</family>
<family>Noto Sans CJK SC</family>
<family>Noto Color Emoji</family>
</prefer>
</alias>
<!-- serif: Noto Serif -> Noto Serif CJK SC -->
<!-- monospace: Maple Mono NF CN -> Noto Sans Mono CJK SC -->One rule I added beyond the template, because applications do ask for the unsuffixed family by name and get whatever the bundle hands them:
<match target="pattern">
<test name="family"><string>Noto Sans CJK</string></test>
<edit name="family" mode="assign" binding="strong">
<string>Noto Sans CJK SC</string>
</edit>
</match>Verifying:
fc-match "sans-serif:lang=zh-cn"
fc-match -s "sans-serif:lang=zh-cn" | head -5 # the real cascade
fc-match "Noto Sans CJK" # the catch-all ruleGeneric families now resolve to Inter and Maple Mono NF CN; Chinese characters land on MiSans and Noto Serif CJK SC; the unsuffixed request resolves to SC. No KR anywhere.
A `fc-match` trap: because of binding="strong", fc-match "sans-serif:charset=4e2d" still returns Inter — which contains no Chinese at all. The single-result form tells you what won the match, not what will draw the character. Use fc-match -s and read the sorted list, or go the other way with fc-list ':charset=4e2d'.
Plasma never asks for sans-serif
Here is the part that cost me the most time. After all that fontconfig work, the Plasma UI looked exactly the same.
Plasma's built-in default UI font is Noto Sans — and that is a request by name. It does not go through the sans-serif generic alias, so nothing in the block above applies to it. Inter and MiSans were only being reached as fallback when a character wasn't covered. The desktop itself never asked.
~/.config/kdeglobals had no font entries at all, meaning it was running on those built-in defaults. Plasma 6 stores fonts as a 16-field string:
UI="MiSans,10,-1,5,400,0,0,0,0,0,0,0,0,0,0,1"
SMALL="MiSans,8,-1,5,400,0,0,0,0,0,0,0,0,0,0,1"
FIXED="Maple Mono NF CN,10,-1,5,400,0,0,0,0,0,0,0,0,0,0,1"
WMF="MiSans,10,-1,5,700,0,0,0,0,0,0,0,0,0,0,1"
kwriteconfig6 --file kdeglobals --group General --key font "$UI"
kwriteconfig6 --file kdeglobals --group General --key menuFont "$UI"
kwriteconfig6 --file kdeglobals --group General --key toolBarFont "$UI"
kwriteconfig6 --file kdeglobals --group General --key smallestReadableFont "$SMALL"
kwriteconfig6 --file kdeglobals --group General --key fixed "$FIXED"
kwriteconfig6 --file kdeglobals --group WM --key activeFont "$WMF"System Settings → Appearance → Fonts does the same thing if you'd rather click. Back up the file first either way.
MiSans lies about its weights
The UI was now in the right language and the right typeface, and everything looked slightly too bold. Checking the actual metadata explained it:
$ fc-list "MiSans" -f '%{file|basename}\t%{style}\tweight=%{weight}\n'
MiSans-Regular.otf Regular weight=53
MiSans-Medium.otf Medium,Regular weight=75
MiSans-Bold.otf Bold weight=186
MiSans-Heavy.otf Heavy,Regular weight=200fontconfig's constants are light=50, demilight=55, book=75, regular=80, medium=100, demibold=180, bold=200, black=210. So MiSans-Regular declares itself at 53 — somewhere between light and demilight — and MiSans-Heavy declares exactly 200, which is fontconfig's bold.
The consequence is systematic. Qt asks for regular (80) and the nearest declared weight is Medium at 75. Qt asks for bold (200) and hits Heavy dead on. Every piece of text in the UI comes out one step heavier than requested.
The fix is a target="scan" rule, which rewrites the property as fontconfig indexes the file:
<match target="scan">
<test name="file" compare="contains"><string>MiSans-Regular.</string></test>
<edit name="weight"><const>regular</const></edit>
</match>One of those per face, mapping Thin→thin, ExtraLight→extralight, Light→light, Normal→demilight, Regular→regular, Medium→medium, Demibold→demibold, Semibold→demibold, Bold→bold, Heavy→black. Rebuild the cache afterwards — target="scan" is only evaluated during the scan, so nothing happens until you do:
fc-cache -f -rDon't match on style
My first attempt tested style instead of the filename, which seemed like the more semantic choice:
<test name="style"><string>Regular</string></test>It collapsed all ten weights onto the same value:
MiSans-Thin.otf weight=80
MiSans-ExtraLight.otf weight=80
MiSans-Light.otf weight=80
MiSans-Normal.otf weight=80
MiSans-Regular.otf weight=80MiSans declares style as a multi-valued list — Thin,Regular, Light,Regular, Medium,Regular — so a test for Regular matches nearly every face in the family, and the ten rules then overwrite each other in file order. The last one to match wins.
Filename matching is reliable here because no MiSans filename is a substring of another: MiSans-Light is not contained in MiSans-ExtraLight, and MiSans-Bold is not contained in MiSans-Semibold. Worth confirming for any family you try this on, since compare="contains" will happily match a prefix.
With that in place, all ten map one-to-one:
thin -> MiSans-Thin.otf medium -> MiSans-Medium.otf
extralight -> MiSans-ExtraLight.otf demibold -> MiSans-Demibold.otf
light -> MiSans-Light.otf bold -> MiSans-Bold.otf
demilight -> MiSans-Normal.otf black -> MiSans-Heavy.otf
regular -> MiSans-Regular.otfWhat I deliberately left alone
`/etc/locale.conf` stays at `LANG=en_US.UTF-8`. It governs the TTY, the SDDM login screen, and system services running outside a session. Switching it to Chinese gets you boxes and blanks on the console, because the console font has no Chinese glyphs. The graphical session is already handled by KDE's own plasma-localerc, so there's nothing to gain. If you do want the login screen in Chinese, it's sudo localectl set-locale LANG=zh_CN.UTF-8 — just know what you're trading.
Japanese will use Chinese glyph shapes. The config points CJK at the simplified variants across the board, so kana render in MiSans and kanji get SC forms. For a Chinese-first desktop that's the right call; if you typeset Japanese, add lang=ja rules.
Korean falls back to `Noto Sans CJK SC`. Hangul has no regional glyph variants, so this costs nothing.
The change list
/etc/locale.gen enable zh_CN.UTF-8 UTF-8, then locale-gen
~/.config/fontconfig/fonts.conf new: generic priority, Noto CJK catch-all,
MiSans weight correction, rendering options
~/.config/kdeglobals UI / menu / toolbar / smallest-readable /
fixed / window-title fonts
packages noto-fonts-extra inter-font otf-misans
misans-fontconfig maplemono-nf-cnLog out and back in for all of it to take effect.
Diagnostics
locale # does it report Cannot set LC_*?
locale -a # which locales actually exist
localectl status # system-level; not the same as the session
fc-match "sans-serif:lang=zh-cn"
fc-match -s "sans-serif:lang=zh-cn" | head # the real cascade
fc-list "MiSans" -f '%{file|basename}\t%{style}\tweight=%{weight}\n'
fc-list ':charset=4e2d' family # which fonts cover a given character
fc-conflist # which config files are loaded, in order
fc-cache -f -r # required after changing target="scan"Reference
- fontconfig user manual — weight constants and
target="scan"semantics - CJK fonts on Arch Linux — the fontconfig groundwork this builds on




No comments yet