From 345e708779b4994350d1edba289fcc3686ccb22f Mon Sep 17 00:00:00 2001 From: LectWolf <67421358+LectWolf@users.noreply.github.com> Date: Fri, 18 Sep 2026 05:45:48 +0800 Subject: [PATCH] fix(settings): localize the font picker default and hug its trigger The closed font control showed the English catalog label even when the UI language was Chinese. Use settings.fontSystemDefault for the trigger and search, and size the pill to the current label like language and theme. --- .../src/components/settings/FontFamilyRow.tsx | 23 +++++++++++++------ apps/desktop/src/styles/settings.css | 21 ++++++++++------- .../test/settings-font-picker.test.mjs | 16 +++++++++++++ 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/apps/desktop/src/components/settings/FontFamilyRow.tsx b/apps/desktop/src/components/settings/FontFamilyRow.tsx index 824ed8d52..51122d3c9 100644 --- a/apps/desktop/src/components/settings/FontFamilyRow.tsx +++ b/apps/desktop/src/components/settings/FontFamilyRow.tsx @@ -26,8 +26,10 @@ import { IconCheck, IconChevronDown, IconSearch } from "../icons"; * Global UI font picker (Settings → Basics → Appearance). Offers the * system default, bundled open-licensed families, and installed system * families; the selected stack is persisted as `AppSettings.fontFamily` - * and applied to `--font-sans` by App. Selecting System default persists an - * empty stack, which every consumer treats as the built-in token stack. + * and applied to `--font-sans` by App. Selecting the localized system-default + * option persists an empty stack, which every consumer treats as the built-in + * token stack. The closed trigger and search haystack use `settings.fontSystemDefault` + * so the English catalog label in `fonts.ts` never reaches the UI. */ export function FontFamilyRow({ settings, @@ -153,17 +155,24 @@ export function FontFamilyRow({ const selectedValue = settings.fontFamily ?? ""; const selectedOption = options.find((option) => option.value === selectedValue) ?? null; + const defaultLabel = t("settings.fontSystemDefault"); const selectedLabel = - selectedOption?.label ?? readableFontFamily(settings.fontFamily ?? ""); + selectedOption?.group === "default" || selectedValue === "" + ? defaultLabel + : selectedOption?.label ?? readableFontFamily(settings.fontFamily ?? ""); const selectedFamily = selectedOption?.family ?? readableFontFamily(selectedValue); const filtered = useMemo(() => { const needle = query.trim().toLowerCase(); if (!needle) return options; - return options.filter((option) => - option.label.toLowerCase().includes(needle), - ); - }, [options, query]); + return options.filter((option) => { + const localized = + option.group === "default" ? defaultLabel.toLowerCase() : option.label.toLowerCase(); + return ( + localized.includes(needle) || option.label.toLowerCase().includes(needle) + ); + }); + }, [defaultLabel, options, query]); const groupLabel = useCallback((group: string) => { if (group === "bundled") return t("settings.fontBundled"); diff --git a/apps/desktop/src/styles/settings.css b/apps/desktop/src/styles/settings.css index 4d8f196ce..be03522c9 100644 --- a/apps/desktop/src/styles/settings.css +++ b/apps/desktop/src/styles/settings.css @@ -760,21 +760,26 @@ font-size: var(--text-sm-plus); } -/* Global UI font picker (Appearance card). Pill trigger mirrors the - shared settings select; the menu portals to document.body as a fixed - floating layer so the settings card's overflow cannot clip it. */ +/* Global UI font picker (Appearance card). Pill trigger mirrors language / + theme: hug the current label, capped by the settings control column. The + menu portals to document.body as a fixed floating layer so the settings + card's overflow cannot clip it. */ .settings-font { position: relative; + width: max-content; + max-width: 100%; + min-width: 0; } .settings-font-trigger { - display: flex; + display: inline-flex; align-items: center; gap: 8px; - min-width: 200px; - max-width: 280px; + width: max-content; + max-width: 100%; + min-width: 0; height: 30px; - padding: 0 12px; + padding: 0 10px 0 12px; border-radius: var(--radius-full); background: var(--ds-raised); box-shadow: var(--ds-raised-shadow); @@ -788,7 +793,7 @@ .settings-font-trigger-label { min-width: 0; - flex: 1; + flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/apps/desktop/test/settings-font-picker.test.mjs b/apps/desktop/test/settings-font-picker.test.mjs index d86bfcb91..ccd8f0e49 100644 --- a/apps/desktop/test/settings-font-picker.test.mjs +++ b/apps/desktop/test/settings-font-picker.test.mjs @@ -25,6 +25,22 @@ test("selecting System default persists an empty stack so the override clears", assert.doesNotMatch(rowSource, /fontFamily: undefined/); }); +test("the closed trigger and search use the localized system-default label", () => { + assert.match(rowSource, /const defaultLabel = t\("settings\.fontSystemDefault"\)/); + assert.match( + rowSource, + /selectedOption\?\.group === "default" \|\| selectedValue === ""/, + ); + assert.match(rowSource, /option\.group === "default" \? defaultLabel\.toLowerCase\(\)/); +}); + +test("the font trigger hugs the current label like language and theme", () => { + assert.match(styles, /\.settings-font\s*\{[^}]*width:\s*max-content;/s); + assert.match(styles, /\.settings-font-trigger\s*\{[^}]*width:\s*max-content;/s); + assert.match(styles, /\.settings-font-trigger-label\s*\{[^}]*flex:\s*0 1 auto;/s); + assert.doesNotMatch(styles, /\.settings-font-trigger\s*\{[^}]*min-width:\s*200px;/s); +}); + test("font list windows the rows so only the visible slice is in the DOM", () => { assert.match(rowSource, /visibleRowRange\(layout, scrollTop/); assert.match(rowSource, /layout\.rows\.slice\(start, end\)/);