feat: add an option to configure default CJK fonts in CJK environments, closes #780 (#836)

This commit is contained in:
Huang Xin
2025-04-08 17:27:59 +08:00
committed by GitHub
parent bfcf89b093
commit 9446df1b9c
24 changed files with 129 additions and 25 deletions
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}} دقيقة",
"{{value}} minutes": "{{value}} دقائق",
"{{value}} hour": "{{value}} ساعة",
"{{value}} hours": "{{value}} ساعات"
"{{value}} hours": "{{value}} ساعات",
"CJK Font": "خط CJK"
}
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}} Minute",
"{{value}} minutes": "{{value}} Minuten",
"{{value}} hour": "{{value}} Stunde",
"{{value}} hours": "{{value}} Stunden"
"{{value}} hours": "{{value}} Stunden",
"CJK Font": "CJK-Schriftart"
}
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}} λεπτό",
"{{value}} minutes": "{{value}} λεπτά",
"{{value}} hour": "{{value}} ώρα",
"{{value}} hours": "{{value}} ώρες"
"{{value}} hours": "{{value}} ώρες",
"CJK Font": "Γραμματοσειρά CJK"
}
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}} minuto",
"{{value}} minutes": "{{value}} minutos",
"{{value}} hour": "{{value}} hora",
"{{value}} hours": "{{value}} horas"
"{{value}} hours": "{{value}} horas",
"CJK Font": "Fuente CJK"
}
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}} minute",
"{{value}} minutes": "{{value}} minutes",
"{{value}} hour": "{{value}} heure",
"{{value}} hours": "{{value}} heures"
"{{value}} hours": "{{value}} heures",
"CJK Font": "Police CJK"
}
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}} मिनट",
"{{value}} minutes": "{{value}} मिनट",
"{{value}} hour": "{{value}} घंटा",
"{{value}} hours": "{{value}} घंटे"
"{{value}} hours": "{{value}} घंटे",
"CJK Font": "CJK फ़ॉन्ट"
}
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}} menit",
"{{value}} minutes": "{{value}} menit",
"{{value}} hour": "{{value}} jam",
"{{value}} hours": "{{value}} jam"
"{{value}} hours": "{{value}} jam",
"CJK Font": "Font CJK"
}
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}} minuto",
"{{value}} minutes": "{{value}} minuti",
"{{value}} hour": "{{value}} ora",
"{{value}} hours": "{{value}} ore"
"{{value}} hours": "{{value}} ore",
"CJK Font": "Font CJK"
}
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}}分",
"{{value}} minutes": "{{value}}分",
"{{value}} hour": "{{value}}時間",
"{{value}} hours": "{{value}}時間"
"{{value}} hours": "{{value}}時間",
"CJK Font": "CJKフォント"
}
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}} 분",
"{{value}} minutes": "{{value}} 분",
"{{value}} hour": "{{value}} 시간",
"{{value}} hours": "{{value}} 시간"
"{{value}} hours": "{{value}} 시간",
"CJK Font": "CJK 글꼴"
}
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}} minuta",
"{{value}} minutes": "{{value}} minut",
"{{value}} hour": "{{value}} godzina",
"{{value}} hours": "{{value}} godziny"
"{{value}} hours": "{{value}} godziny",
"CJK Font": "Czcionka CJK"
}
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}} minuto",
"{{value}} minutes": "{{value}} minutos",
"{{value}} hour": "{{value}} hora",
"{{value}} hours": "{{value}} horas"
"{{value}} hours": "{{value}} horas",
"CJK Font": "Fonte CJK"
}
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}} минута",
"{{value}} minutes": "{{value}} минуты",
"{{value}} hour": "{{value}} час",
"{{value}} hours": "{{value}} часа"
"{{value}} hours": "{{value}} часа",
"CJK Font": "CJK Шрифт"
}
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}} dakika",
"{{value}} minutes": "{{value}} dakika",
"{{value}} hour": "{{value}} saat",
"{{value}} hours": "{{value}} saat"
"{{value}} hours": "{{value}} saat",
"CJK Font": "CJK Yazı Tipi"
}
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}} хвилина",
"{{value}} minutes": "{{value}} хвилин",
"{{value}} hour": "{{value}} година",
"{{value}} hours": "{{value}} години"
"{{value}} hours": "{{value}} години",
"CJK Font": "CJK шрифт"
}
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}} phút",
"{{value}} minutes": "{{value}} phút",
"{{value}} hour": "{{value}} giờ",
"{{value}} hours": "{{value}} giờ"
"{{value}} hours": "{{value}} giờ",
"CJK Font": "Phông chữ CJK"
}
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}}分钟",
"{{value}} minutes": "{{value}}分钟",
"{{value}} hour": "{{value}}小时",
"{{value}} hours": "{{value}}小时"
"{{value}} hours": "{{value}}小时",
"CJK Font": "中文字体"
}
@@ -271,5 +271,6 @@
"{{value}} minute": "{{value}}分鐘",
"{{value}} minutes": "{{value}}分鐘",
"{{value}} hour": "{{value}}小時",
"{{value}} hours": "{{value}}小時"
"{{value}} hours": "{{value}}小時",
"CJK Font": "中文字體"
}
@@ -41,7 +41,10 @@ const FontDropdown: React.FC<DropdownProps> = ({
</button>
<ul
tabIndex={0}
className='dropdown-content bgcolor-base-200 no-triangle menu rounded-box absolute right-[-32px] z-[1] mt-4 w-44 shadow sm:right-0'
className={clsx(
'dropdown-content bgcolor-base-200 no-triangle menu rounded-box absolute right-[-32px] z-[1] mt-4 w-44 shadow sm:right-0',
'inline max-h-80 overflow-y-scroll',
)}
>
{options.map(({ option, label }) => (
<li key={option} onClick={() => onSelect(option)}>
@@ -3,6 +3,8 @@ import React, { useEffect, useState } from 'react';
import {
ANDROID_FONTS,
CJK_FONTS_PATTENS,
CJK_NAMES_PATTENS,
IOS_FONTS,
LINUX_FONTS,
MACOS_FONTS,
@@ -14,7 +16,7 @@ import {
import { useReaderStore } from '@/store/readerStore';
import { useTranslation } from '@/hooks/useTranslation';
import { useEnv } from '@/context/EnvContext';
import { getOSPlatform } from '@/utils/misc';
import { getOSPlatform, isCJKEnv } from '@/utils/misc';
import { getSysFontsList } from '@/utils/font';
import { isTauriAppPlatform } from '@/services/environment';
import { saveViewSettings } from '../../utils/viewSettingsHelper';
@@ -63,8 +65,9 @@ const FontFace = ({
const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const _ = useTranslation();
const { envConfig, appService } = useEnv();
const { getViewSettings } = useReaderStore();
const { getView, getViewSettings } = useReaderStore();
const viewSettings = getViewSettings(bookKey)!;
const view = getView(bookKey)!;
const fontFamilyOptions = [
{
@@ -103,6 +106,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const [minFontSize, setMinFontSize] = useState(viewSettings.minimumFontSize!);
const [overrideFont, setOverrideFont] = useState(viewSettings.overrideFont!);
const [defaultFont, setDefaultFont] = useState(viewSettings.defaultFont!);
const [defaultCJKFont, setDefaultCJKFont] = useState(viewSettings.defaultCJKFont!);
const [serifFont, setSerifFont] = useState(viewSettings.serifFont!);
const [sansSerifFont, setSansSerifFont] = useState(viewSettings.sansSerifFont!);
const [monospaceFont, setMonospaceFont] = useState(viewSettings.monospaceFont!);
@@ -122,6 +126,11 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultFont]);
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'defaultCJKFont', defaultCJKFont);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultCJKFont]);
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'defaultFontSize', defaultFontSize);
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -224,6 +233,19 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
/>
</div>
{(isCJKEnv() || view?.language.isCJK) && (
<FontFace
className='config-item-top'
family='serif'
label={_('CJK Font')}
options={sysFonts.filter(
(font) => CJK_FONTS_PATTENS.test(font) || CJK_NAMES_PATTENS.test(font),
)}
selected={defaultCJKFont}
onSelect={setDefaultCJKFont}
/>
)}
<div className='config-item'>
<span className=''>{_('Override Book Font')}</span>
<input
@@ -66,6 +66,7 @@ export const DEFAULT_BOOK_FONT: BookFont = {
sansSerifFont: 'Roboto',
monospaceFont: 'Consolas',
defaultFont: 'Serif',
defaultCJKFont: 'LXGW WenKai GB Screen',
defaultFontSize: 16,
minimumFontSize: 8,
fontWeight: 400,
@@ -181,6 +182,7 @@ export const WINDOWS_FONTS = [
'Leelawadee UI',
'Lucida Console',
'Lucida Sans Unicode',
'LXGW WenKai GB Screen',
'Malgun Gothic',
'Marlett',
'Microsoft Himalaya',
@@ -275,6 +277,7 @@ export const MACOS_FONTS = [
'Kozuka Mincho Pro',
'Lucida Grande',
'Luminari',
'LXGW WenKai GB Screen',
'Marker Felt',
'Menlo',
'Microsoft Sans Serif',
@@ -331,6 +334,7 @@ export const LINUX_FONTS = [
'Liberation Mono',
'Liberation Sans',
'Liberation Serif',
'LXGW WenKai GB Screen',
'Noto Mono',
'Noto Sans',
'Noto Sans JP',
@@ -367,6 +371,7 @@ export const IOS_FONTS = [
'Hiragino Mincho',
'Hiragino Sans',
'Kaiti',
'LXGW WenKai GB Screen',
'Palatino',
'PingFang SC',
'PingFang TC',
@@ -389,6 +394,7 @@ export const ANDROID_FONTS = [
'Georgia',
'Heiti',
'Kaiti',
'LXGW WenKai GB Screen',
'Noto Sans',
'Noto Sans CJK',
'Noto Sans JP',
@@ -406,6 +412,47 @@ export const ANDROID_FONTS = [
'XiHeiti',
];
export const CJK_NAMES_PATTENS = /[\u3040-\u30FF\u4E00-\u9FFF\uAC00-\uD7AF]/;
export const CJK_FONTS_PATTENS = new RegExp(
[
'CJK',
'TC$',
'SC$',
'HK',
'JP',
'TW',
'Sim',
'Kai',
'Hei',
'Yan',
'Min',
'Yuan',
'Song',
'Ming',
'FZ',
'FangZheng',
'WenQuanYi',
'PingFang',
'Hiragino',
'Meiryo',
'Source\\s?Han',
'Yu\\s?Gothic',
'Yu\\s?Mincho',
'Mincho',
'Gothic',
'Nanum',
'Malgun',
'Gulim',
'Dotum',
'Batang',
'Gungsuh',
'OPPO sans',
'MiSans',
'Fallback',
].join('|'),
'i',
);
export const BOOK_IDS_SEPARATOR = '+';
export const DOWNLOAD_READEST_URL = 'https://readest.com?utm_source=readest_web';
+1
View File
@@ -105,6 +105,7 @@ export interface BookFont {
sansSerifFont: string;
monospaceFont: string;
defaultFont: string;
defaultCJKFont: string;
defaultFontSize: number;
minimumFontSize: number;
fontWeight: number;
+6 -1
View File
@@ -32,7 +32,12 @@ export const makeSafeFilename = (filename: string, replacement = '_') => {
export const getUserLang = () => navigator?.language.split('-')[0] || 'en';
export const isCJKEnv = () => ['zh', 'ja', 'ko'].includes(getUserLang());
export const isCJKEnv = () => {
const isCJKLocale = ['zh', 'ja', 'ko'].includes(getUserLang());
const uiLanguage = localStorage.getItem('i18nextLng') || '';
const isCJKUI = ['zh', 'ja', 'ko'].some((lang) => uiLanguage.startsWith(lang));
return isCJKLocale || isCJKUI;
};
export const getUserLocale = (lang: string): string | undefined => {
const languages =
+11 -3
View File
@@ -21,6 +21,7 @@ const getFontStyles = (
sansSerif: string,
monospace: string,
defaultFont: string,
defaultCJKFont: string,
fontSize: number,
minFontSize: number,
fontWeight: number,
@@ -28,16 +29,22 @@ const getFontStyles = (
themeCode: ThemeCode,
) => {
const { primary } = themeCode;
const lastSerifFonts = ['Georgia', 'Times New Roman'];
const lastSerifFonts = ['LXGW WenKai GB Screen', 'Georgia', 'Times New Roman'];
const serifFonts = [
serif,
...SERIF_FONTS.filter((font) => font !== serif && !lastSerifFonts.includes(font)),
...SERIF_FONTS.filter(
(font) => font !== serif && font !== defaultCJKFont && !lastSerifFonts.includes(font),
),
...(defaultCJKFont !== serif && !lastSerifFonts.includes(defaultCJKFont)
? [defaultCJKFont]
: []),
...lastSerifFonts.filter((font) => SERIF_FONTS.includes(font)),
...FALLBACK_FONTS,
];
const sansSerifFonts = [
sansSerif,
...SANS_SERIF_FONTS.filter((font) => font !== sansSerif),
...SANS_SERIF_FONTS.filter((font) => font !== sansSerif && font !== defaultCJKFont),
...(defaultCJKFont !== sansSerif ? [defaultCJKFont] : []),
...FALLBACK_FONTS,
];
const monospaceFonts = [monospace, ...MONOSPACE_FONTS.filter((font) => font !== monospace)];
@@ -367,6 +374,7 @@ export const getStyles = (viewSettings: ViewSettings, themeCode?: ThemeCode) =>
viewSettings.sansSerifFont!,
viewSettings.monospaceFont!,
viewSettings.defaultFont!,
viewSettings.defaultCJKFont!,
viewSettings.defaultFontSize! * fontScale,
viewSettings.minimumFontSize!,
viewSettings.fontWeight!,