mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-09 02:40:21 +00:00
feat(settings): add locale-aware font preferences
Bundle optional Inter and Outfit families, preserve the platform system default, and provide script-aware fallbacks for every supported locale. Refs #31
This commit is contained in:
@@ -194,6 +194,7 @@ function App() {
|
||||
const sidebarPosition = useSettingsStore(state => state.sidebarPosition);
|
||||
const toggleSidebar = useSettingsStore(state => state.toggleSidebar);
|
||||
const activeView = useSettingsStore(state => state.activeView);
|
||||
const fontFamily = useSettingsStore(state => state.fontFamily);
|
||||
const appFontSize = useSettingsStore(state => state.appFontSize);
|
||||
const listRowDensity = useSettingsStore(state => state.listRowDensity);
|
||||
|
||||
@@ -649,6 +650,10 @@ function App() {
|
||||
});
|
||||
}, [addToast, coreReady, showKeychainModal]);
|
||||
|
||||
useEffect(() => {
|
||||
window.document.documentElement.setAttribute('data-font-family', fontFamily);
|
||||
}, [fontFamily]);
|
||||
|
||||
useEffect(() => {
|
||||
window.document.documentElement.setAttribute('data-font-size', appFontSize);
|
||||
}, [appFontSize]);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type FontFamily = "system" | "inter" | "outfit" | "serif" | "monospace";
|
||||
@@ -1,6 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AppFontSize } from "./AppFontSize";
|
||||
import type { CalendarPreference } from "./CalendarPreference";
|
||||
import type { FontFamily } from "./FontFamily";
|
||||
import type { ListRowDensity } from "./ListRowDensity";
|
||||
import type { MediaCookieSource } from "./MediaCookieSource";
|
||||
import type { ProxyMode } from "./ProxyMode";
|
||||
@@ -10,4 +11,4 @@ import type { SiteLogin } from "./SiteLogin";
|
||||
import type { Theme } from "./Theme";
|
||||
import type { WindowControlStyle } from "./WindowControlStyle";
|
||||
|
||||
export type PersistedSettings = { theme: Theme, windowControlStyle: WindowControlStyle, calendarPreference: CalendarPreference, language: string, baseDownloadFolder: string, categorySubfoldersEnabled: boolean, categorySubfolders: { [key in string]: string }, categoryDirectoryOverrides: { [key in string]: string }, approvedDownloadRoots: Array<string>, maxConcurrentDownloads: number, globalSpeedLimit: string, speedLimitPresetValues: Array<number>, logsEnabled: boolean, isSidebarVisible: boolean, sidebarPosition: string, activeSettingsTab: SettingsTab, scheduler: SchedulerSettings, schedulerRunning: boolean, schedulerActiveDownloadIds: Array<string>, schedulerLastStartKey: string, schedulerLastStopKey: string, lastCustomSpeedLimitKiB: number, lastCustomSpeedLimitUnit: string, perServerConnections: number, maxAutomaticRetries: number, showNotifications: boolean, playCompletionSound: boolean, autoAddClipboardLinks: boolean, appFontSize: AppFontSize, listRowDensity: ListRowDensity, showDockBadge: boolean, showMenuBarIcon: boolean, proxyMode: ProxyMode, proxyHost: string, proxyPort: number, customUserAgent: string, askWhereToSaveEachFile: boolean, rememberLastUsedDownloadDirectory: boolean, preventsSleepWhileDownloading: boolean, mediaCookieSource: MediaCookieSource, siteLogins: Array<SiteLogin>, autoCheckUpdates: boolean, keychainAccessGranted: boolean, };
|
||||
export type PersistedSettings = { theme: Theme, fontFamily: FontFamily, windowControlStyle: WindowControlStyle, calendarPreference: CalendarPreference, language: string, baseDownloadFolder: string, categorySubfoldersEnabled: boolean, categorySubfolders: { [key in string]: string }, categoryDirectoryOverrides: { [key in string]: string }, approvedDownloadRoots: Array<string>, maxConcurrentDownloads: number, globalSpeedLimit: string, speedLimitPresetValues: Array<number>, logsEnabled: boolean, isSidebarVisible: boolean, sidebarPosition: string, activeSettingsTab: SettingsTab, scheduler: SchedulerSettings, schedulerRunning: boolean, schedulerActiveDownloadIds: Array<string>, schedulerLastStartKey: string, schedulerLastStopKey: string, lastCustomSpeedLimitKiB: number, lastCustomSpeedLimitUnit: string, perServerConnections: number, maxAutomaticRetries: number, showNotifications: boolean, playCompletionSound: boolean, autoAddClipboardLinks: boolean, appFontSize: AppFontSize, listRowDensity: ListRowDensity, showDockBadge: boolean, showMenuBarIcon: boolean, proxyMode: ProxyMode, proxyHost: string, proxyPort: number, customUserAgent: string, askWhereToSaveEachFile: boolean, rememberLastUsedDownloadDirectory: boolean, preventsSleepWhileDownloading: boolean, mediaCookieSource: MediaCookieSource, siteLogins: Array<SiteLogin>, autoCheckUpdates: boolean, keychainAccessGranted: boolean, };
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useRef, useState, useEffect } from 'react';
|
||||
import {
|
||||
type AppFontSize,
|
||||
type FontFamily,
|
||||
type ListRowDensity,
|
||||
type SidebarPosition,
|
||||
type WindowControlStyle,
|
||||
@@ -706,6 +707,13 @@ runEngineChecks(false);
|
||||
{ value: 'gnome', label: t($ => $.settings.lookAndFeel.windowControlStyleGnome) },
|
||||
{ value: 'minimal', label: t($ => $.settings.lookAndFeel.windowControlStyleMinimal) },
|
||||
];
|
||||
const fontFamilyOptions: Array<{ value: FontFamily; label: string }> = [
|
||||
{ value: 'system', label: t($ => $.settings.lookAndFeel.fontFamilySystem) },
|
||||
{ value: 'inter', label: t($ => $.settings.lookAndFeel.fontFamilyInter) },
|
||||
{ value: 'outfit', label: t($ => $.settings.lookAndFeel.fontFamilyOutfit) },
|
||||
{ value: 'serif', label: t($ => $.settings.lookAndFeel.fontFamilySerif) },
|
||||
{ value: 'monospace', label: t($ => $.settings.lookAndFeel.fontFamilyMonospace) },
|
||||
];
|
||||
|
||||
const TabButton = ({ type, icon: Icon, label }: { type: SettingsTab; icon: typeof Download; label: string }) => {
|
||||
const active = activeTab === type;
|
||||
@@ -903,6 +911,21 @@ runEngineChecks(false);
|
||||
|
||||
<h2 className="settings-section-title">{t($ => $.settings.lookAndFeel.display)}</h2>
|
||||
<div className="mac-settings-group">
|
||||
<div className="mac-settings-row">
|
||||
<div className="settings-row-label">
|
||||
<span>{t($ => $.settings.lookAndFeel.fontFamily)}</span>
|
||||
<small>{t($ => $.settings.lookAndFeel.fontFamilyDescription)}</small>
|
||||
</div>
|
||||
<select
|
||||
value={settings.fontFamily}
|
||||
onChange={(event) => settings.setFontFamily(event.target.value as FontFamily)}
|
||||
className="app-control w-60"
|
||||
>
|
||||
{fontFamilyOptions.map(option => (
|
||||
<option key={option.value} value={option.value}>{option.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="mac-settings-row">
|
||||
<div className="settings-row-label">
|
||||
<span>{t($ => $.settings.lookAndFeel.fontSize)}</span>
|
||||
|
||||
@@ -618,6 +618,13 @@ const common = {
|
||||
nord: 'Nord',
|
||||
systemAppearance: 'System follows the current {{platform}} light or dark appearance.',
|
||||
display: 'Display',
|
||||
fontFamily: 'Font',
|
||||
fontFamilyDescription: 'Choose the interface font. Automatic uses platform and locale-aware system fallbacks.',
|
||||
fontFamilySystem: 'System UI (recommended)',
|
||||
fontFamilyInter: 'Inter',
|
||||
fontFamilyOutfit: 'Outfit',
|
||||
fontFamilySerif: 'Serif',
|
||||
fontFamilyMonospace: 'Monospace',
|
||||
fontSize: 'Font size',
|
||||
fontSizeDescription: 'Scales the download list and compact controls.',
|
||||
small: 'Small',
|
||||
|
||||
@@ -618,6 +618,13 @@ const fa = {
|
||||
nord: 'Nord',
|
||||
systemAppearance: 'برنامه از حالت روشن یا تاریک فعلی {{platform}} پیروی میکند.',
|
||||
display: 'نمایش',
|
||||
fontFamily: 'فونت',
|
||||
fontFamilyDescription: 'فونت رابط کاربری را انتخاب کنید. حالت خودکار از فونتهای سیستمی متناسب با سیستمعامل و زبان استفاده میکند.',
|
||||
fontFamilySystem: 'رابط کاربری سیستم (پیشنهادشده)',
|
||||
fontFamilyInter: 'Inter',
|
||||
fontFamilyOutfit: 'Outfit',
|
||||
fontFamilySerif: 'سریف',
|
||||
fontFamilyMonospace: 'تکفاصله',
|
||||
fontSize: 'اندازه قلم',
|
||||
fontSizeDescription: 'اندازه فهرست دانلود و کنترلهای فشرده را تغییر میدهد.',
|
||||
small: 'کوچک',
|
||||
|
||||
@@ -618,6 +618,13 @@ const he = {
|
||||
nord: 'Nord',
|
||||
systemAppearance: 'מערכת עוקבת אחר המראה הנוכחי (בהיר או כהה) של {{platform}}.',
|
||||
display: 'תצוגה',
|
||||
fontFamily: 'גופן',
|
||||
fontFamilyDescription: 'בחר את גופן הממשק. מצב אוטומטי משתמש בגופני מערכת המתאימים לפלטפורמה ולשפה.',
|
||||
fontFamilySystem: 'ממשק המערכת (מומלץ)',
|
||||
fontFamilyInter: 'Inter',
|
||||
fontFamilyOutfit: 'Outfit',
|
||||
fontFamilySerif: 'סריף',
|
||||
fontFamilyMonospace: 'רוחב קבוע',
|
||||
fontSize: 'גודל גופן',
|
||||
fontSizeDescription: 'משנה את קנה המידה של רשימת ההורדות והפקדים הקומפקטיים.',
|
||||
small: 'קטן',
|
||||
|
||||
@@ -618,6 +618,13 @@ const ru = {
|
||||
nord: 'Nord',
|
||||
systemAppearance: 'Системная тема соответствует текущему светлому или тёмному оформлению {{platform}}.',
|
||||
display: 'Отображение',
|
||||
fontFamily: 'Шрифт',
|
||||
fontFamilyDescription: 'Выберите шрифт интерфейса. Автоматический режим использует системные шрифты с учётом ОС и языка.',
|
||||
fontFamilySystem: 'Системный интерфейс (рекомендуется)',
|
||||
fontFamilyInter: 'Inter',
|
||||
fontFamilyOutfit: 'Outfit',
|
||||
fontFamilySerif: 'С засечками',
|
||||
fontFamilyMonospace: 'Моноширинный',
|
||||
fontSize: 'Размер шрифта',
|
||||
fontSizeDescription: 'Масштабирует список загрузок и компактные элементы управления.',
|
||||
small: 'Мелкий',
|
||||
|
||||
@@ -618,6 +618,13 @@ const uk = {
|
||||
nord: 'Nord',
|
||||
systemAppearance: 'Система слідкує за поточним світлим або темним виглядом {{platform}}.',
|
||||
display: 'Відображення',
|
||||
fontFamily: 'Шрифт',
|
||||
fontFamilyDescription: 'Виберіть шрифт інтерфейсу. Автоматичний режим використовує системні шрифти з урахуванням ОС і мови.',
|
||||
fontFamilySystem: 'Системний інтерфейс (рекомендовано)',
|
||||
fontFamilyInter: 'Inter',
|
||||
fontFamilyOutfit: 'Outfit',
|
||||
fontFamilySerif: 'Із зарубками',
|
||||
fontFamilyMonospace: 'Моноширинний',
|
||||
fontSize: 'Розмір шрифту',
|
||||
fontSizeDescription: 'Масштабує список завантажень та компактні елементи керування.',
|
||||
small: 'Дрібний',
|
||||
|
||||
@@ -618,6 +618,13 @@ const zhCN = {
|
||||
nord: 'Nord',
|
||||
systemAppearance: '系统跟随当前的 {{platform}} 浅色或深色外观。',
|
||||
display: '显示',
|
||||
fontFamily: '字体',
|
||||
fontFamilyDescription: '选择界面字体。“自动”会使用适合操作系统和语言的系统字体及备用字体。',
|
||||
fontFamilySystem: '系统界面字体(推荐)',
|
||||
fontFamilyInter: 'Inter',
|
||||
fontFamilyOutfit: 'Outfit',
|
||||
fontFamilySerif: '衬线',
|
||||
fontFamilyMonospace: '等宽',
|
||||
fontSize: '字体大小',
|
||||
fontSizeDescription: '缩放下载列表和紧凑型控件。',
|
||||
small: '小',
|
||||
|
||||
@@ -94,6 +94,8 @@ describe('translation catalogs', () => {
|
||||
'settings.integrations.chromiumZip',
|
||||
'settings.lookAndFeel.dracula',
|
||||
'settings.lookAndFeel.nord',
|
||||
'settings.lookAndFeel.fontFamilyInter',
|
||||
'settings.lookAndFeel.fontFamilyOutfit',
|
||||
'settings.lookAndFeel.windowControlStyleWindows',
|
||||
'settings.lookAndFeel.windowControlStyleGnome',
|
||||
'settings.network.chromeWindows',
|
||||
|
||||
+49
-2
@@ -223,8 +223,55 @@
|
||||
--color-surface-overlay: hsl(var(--surface-overlay));
|
||||
--color-bg-context-menu: hsl(var(--surface-overlay));
|
||||
|
||||
/* Apple System Fonts */
|
||||
--font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
/*
|
||||
* Keep the default platform UI stack, and let each locale contribute a
|
||||
* script-aware fallback. Optional custom families are bundled locally so
|
||||
* the Tauri app remains usable offline.
|
||||
*/
|
||||
--font-locale-fallback: sans-serif;
|
||||
--font-stack-system: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, var(--font-locale-fallback), "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
--font-stack-inter: "Inter Variable", -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, var(--font-locale-fallback), "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
--font-stack-outfit: "Outfit Variable", -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, var(--font-locale-fallback), "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
--font-stack-serif: Georgia, "Times New Roman", var(--font-locale-fallback), serif;
|
||||
--font-stack-monospace: ui-monospace, "SF Mono", Monaco, "Cascadia Code", "Fira Code", "JetBrains Mono", var(--font-locale-fallback), monospace;
|
||||
--font-sans: var(--font-stack-system);
|
||||
}
|
||||
|
||||
html[lang="fa"] {
|
||||
--font-locale-fallback: "Vazirmatn", "Noto Sans Arabic", "Segoe UI", Tahoma, Arial, sans-serif;
|
||||
}
|
||||
|
||||
html[lang="he"] {
|
||||
--font-locale-fallback: "Arial Hebrew", "Noto Sans Hebrew", "Segoe UI", Arial, sans-serif;
|
||||
}
|
||||
|
||||
html[lang="zh-CN"] {
|
||||
--font-locale-fallback: "PingFang SC", "Microsoft YaHei UI", "Noto Sans CJK SC", "Noto Sans SC", sans-serif;
|
||||
}
|
||||
|
||||
html[lang="ru"],
|
||||
html[lang="uk"] {
|
||||
--font-locale-fallback: "Segoe UI", "Noto Sans", Roboto, Arial, sans-serif;
|
||||
}
|
||||
|
||||
html[data-font-family="system"] {
|
||||
--font-sans: var(--font-stack-system);
|
||||
}
|
||||
|
||||
html[data-font-family="inter"] {
|
||||
--font-sans: var(--font-stack-inter);
|
||||
}
|
||||
|
||||
html[data-font-family="outfit"] {
|
||||
--font-sans: var(--font-stack-outfit);
|
||||
}
|
||||
|
||||
html[data-font-family="serif"] {
|
||||
--font-sans: var(--font-stack-serif);
|
||||
}
|
||||
|
||||
html[data-font-family="monospace"] {
|
||||
--font-sans: var(--font-stack-monospace);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import "@fontsource-variable/inter/wght.css";
|
||||
import "@fontsource-variable/outfit/wght.css";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
import { i18nReady } from "./i18n";
|
||||
|
||||
@@ -4,6 +4,7 @@ import { invokeCommand as invoke } from '../ipc';
|
||||
import { info } from '../utils/logger';
|
||||
import type { ActiveView } from '../bindings/ActiveView';
|
||||
import type { AppFontSize } from '../bindings/AppFontSize';
|
||||
import type { FontFamily } from '../bindings/FontFamily';
|
||||
import type { ListRowDensity } from '../bindings/ListRowDensity';
|
||||
import type { MediaCookieSource } from '../bindings/MediaCookieSource';
|
||||
import type { PostQueueAction } from '../bindings/PostQueueAction';
|
||||
@@ -74,6 +75,7 @@ const notifySettingsPersistenceError = () => {
|
||||
};
|
||||
|
||||
const THEME_VALUES = ['system', 'light', 'dark', 'dracula', 'nord'] as const;
|
||||
const FONT_FAMILY_VALUES = ['system', 'inter', 'outfit', 'serif', 'monospace'] as const;
|
||||
const WINDOW_CONTROL_STYLE_VALUES = ['auto', 'macos', 'windows', 'gnome', 'minimal'] as const;
|
||||
const APP_FONT_SIZE_VALUES = ['small', 'standard', 'large'] as const;
|
||||
const LIST_ROW_DENSITY_VALUES = ['compact', 'standard', 'relaxed'] as const;
|
||||
@@ -162,6 +164,7 @@ const tauriStorage: StateStorage = {
|
||||
export type {
|
||||
ActiveView,
|
||||
AppFontSize,
|
||||
FontFamily,
|
||||
CalendarPreference,
|
||||
ListRowDensity,
|
||||
MediaCookieSource,
|
||||
@@ -178,6 +181,7 @@ export type SidebarPosition = 'auto' | 'left' | 'right';
|
||||
|
||||
export interface SettingsState {
|
||||
theme: Theme;
|
||||
fontFamily: FontFamily;
|
||||
windowControlStyle: WindowControlStyle;
|
||||
calendarPreference: CalendarPreference;
|
||||
language: AppLocalePreference;
|
||||
@@ -235,6 +239,7 @@ export interface SettingsState {
|
||||
showKeychainModal: boolean;
|
||||
|
||||
setTheme: (theme: Theme) => void;
|
||||
setFontFamily: (fontFamily: FontFamily) => void;
|
||||
setWindowControlStyle: (style: WindowControlStyle) => void;
|
||||
setCalendarPreference: (calendarPreference: CalendarPreference) => void;
|
||||
setLanguage: (language: AppLocalePreference) => void;
|
||||
@@ -293,6 +298,7 @@ export const useSettingsStore = create<SettingsState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
theme: 'system',
|
||||
fontFamily: 'system',
|
||||
windowControlStyle: 'auto',
|
||||
calendarPreference: DEFAULT_CALENDAR_PREFERENCE,
|
||||
language: 'system',
|
||||
@@ -357,6 +363,10 @@ export const useSettingsStore = create<SettingsState>()(
|
||||
showKeychainModal: false,
|
||||
|
||||
setTheme: (theme) => { info('Settings updated: theme'); set({ theme }); },
|
||||
setFontFamily: (fontFamily) => {
|
||||
info('Settings updated: fontFamily');
|
||||
set({ fontFamily });
|
||||
},
|
||||
setWindowControlStyle: (windowControlStyle) => {
|
||||
info('Settings updated: windowControlStyle');
|
||||
set({ windowControlStyle });
|
||||
@@ -571,6 +581,7 @@ export const useSettingsStore = create<SettingsState>()(
|
||||
},
|
||||
partialize: (state): PersistedSettingsSnapshot => ({
|
||||
theme: state.theme,
|
||||
fontFamily: state.fontFamily,
|
||||
windowControlStyle: state.windowControlStyle,
|
||||
calendarPreference: state.calendarPreference,
|
||||
language: state.language,
|
||||
@@ -633,6 +644,9 @@ export const useSettingsStore = create<SettingsState>()(
|
||||
theme: isAllowedSetting(THEME_VALUES, persisted.theme)
|
||||
? persisted.theme
|
||||
: currentState.theme,
|
||||
fontFamily: isAllowedSetting(FONT_FAMILY_VALUES, persisted.fontFamily)
|
||||
? persisted.fontFamily
|
||||
: currentState.fontFamily,
|
||||
windowControlStyle: isAllowedSetting(WINDOW_CONTROL_STYLE_VALUES, persisted.windowControlStyle)
|
||||
? persisted.windowControlStyle
|
||||
: currentState.windowControlStyle,
|
||||
|
||||
Reference in New Issue
Block a user