feat: add language preference options in settings (#686)

This commit is contained in:
Huang Xin
2025-03-23 23:39:58 +08:00
committed by GitHub
parent 05d086352f
commit a79f301165
24 changed files with 177 additions and 25 deletions
@@ -249,5 +249,7 @@
"Show Header": "عرض الرأس",
"Show Footer": "عرض التذييل",
"Small": "صغير",
"Large": "كبير"
"Large": "كبير",
"Auto": "تلقائي",
"Language": "اللغة"
}
@@ -249,5 +249,7 @@
"Show Header": "Kopfzeile anzeigen",
"Show Footer": "Fußzeile anzeigen",
"Small": "Klein",
"Large": "Groß"
"Large": "Groß",
"Auto": "Automatisch",
"Language": "Sprache"
}
@@ -249,5 +249,7 @@
"Show Header": "Εμφάνιση κεφαλίδας",
"Show Footer": "Εμφάνιση υποσέλιδου",
"Small": "Μικρό",
"Large": "Μεγάλο"
"Large": "Μεγάλο",
"Auto": "Αυτόματο",
"Language": "Γλώσσα"
}
@@ -249,5 +249,7 @@
"Show Header": "Mostrar encabezado",
"Show Footer": "Mostrar pie de página",
"Small": "Pequeño",
"Large": "Grande"
"Large": "Grande",
"Auto": "Automático",
"Language": "Idioma"
}
@@ -249,5 +249,7 @@
"Show Header": "Afficher l'en-tête",
"Show Footer": "Afficher le pied de page",
"Small": "Petit",
"Large": "Grand"
"Large": "Grand",
"Auto": "Automatique",
"Language": "Langue"
}
@@ -249,5 +249,7 @@
"Show Header": "हेडर दिखाएं",
"Show Footer": "फ़ुटर दिखाएं",
"Small": "छोटा",
"Large": "बड़ा"
"Large": "बड़ा",
"Auto": "स्वचालित",
"Language": "भाषा"
}
@@ -249,5 +249,7 @@
"Show Header": "Tampilkan Header",
"Show Footer": "Tampilkan Footer",
"Small": "Kecil",
"Large": "Besar"
"Large": "Besar",
"Auto": "Otomatis",
"Language": "Bahasa"
}
@@ -249,5 +249,7 @@
"Show Header": "Mostra intestazione",
"Show Footer": "Mostra piè di pagina",
"Small": "Piccolo",
"Large": "Grande"
"Large": "Grande",
"Auto": "Automatico",
"Language": "Lingua"
}
@@ -249,5 +249,7 @@
"Show Header": "ヘッダーを表示",
"Show Footer": "フッターを表示",
"Small": "小",
"Large": "大"
"Large": "大",
"Auto": "自動",
"Language": "言語"
}
@@ -249,5 +249,7 @@
"Show Header": "헤더 표시",
"Show Footer": "푸터 표시",
"Small": "작음",
"Large": "큼"
"Large": "큼",
"Auto": "자동",
"Language": "언어"
}
@@ -249,5 +249,7 @@
"Show Header": "Pokaż nagłówek",
"Show Footer": "Pokaż stopkę",
"Small": "Mały",
"Large": "Duży"
"Large": "Duży",
"Auto": "Automatyczny",
"Language": "Język"
}
@@ -249,5 +249,7 @@
"Show Header": "Mostrar Cabeçalho",
"Show Footer": "Mostrar Rodapé",
"Small": "Pequeno",
"Large": "Grande"
"Large": "Grande",
"Auto": "Automático",
"Language": "Idioma"
}
@@ -249,5 +249,7 @@
"Show Header": "Показать заголовок",
"Show Footer": "Показать нижний колонтитул",
"Small": "Маленький",
"Large": "Большой"
"Large": "Большой",
"Auto": "Авто",
"Language": "Язык"
}
@@ -249,5 +249,7 @@
"Show Header": "Başlık Göster",
"Show Footer": "Altbilgi Göster",
"Small": "Küçük",
"Large": "Büyük"
"Large": "Büyük",
"Auto": "Otomatik",
"Language": "Dil"
}
@@ -249,5 +249,7 @@
"Show Header": "Показати заголовок",
"Show Footer": "Показати нижню частину",
"Small": "Малий",
"Large": "Великий"
"Large": "Великий",
"Auto": "Автоматичний",
"Language": "Мова"
}
@@ -249,5 +249,7 @@
"Show Header": "Hiển thị tiêu đề",
"Show Footer": "Hiển thị chân trang",
"Small": "Nhỏ",
"Large": "Lớn"
"Large": "Lớn",
"Auto": "Tự động",
"Language": "Ngôn ngữ"
}
@@ -249,5 +249,7 @@
"Show Header": "显示页眉",
"Show Footer": "显示页脚",
"Small": "小",
"Large": "大"
"Large": "大",
"Auto": "自动",
"Language": "语言"
}
@@ -249,5 +249,7 @@
"Show Header": "顯示頁眉",
"Show Footer": "顯示頁腳",
"Small": "小",
"Large": "大"
"Large": "大",
"Auto": "自動",
"Language": "語言"
}
@@ -141,7 +141,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
<>
<div
className={clsx(
'absolute bottom-0 left-0 z-10 h-12 w-full sm:h-[52px]',
'absolute bottom-0 left-0 z-10 hidden w-full sm:flex sm:h-[52px]',
// show scroll bar when vertical and scrolled in desktop
viewSettings?.vertical && viewSettings?.scrolled && 'sm:!bottom-3 sm:!h-7',
)}
@@ -150,16 +150,15 @@ const FooterBar: React.FC<FooterBarProps> = ({
/>
<div
className={clsx(
'footer-bar shadow-xs absolute bottom-0 z-50 flex w-full flex-col sm:h-[52px]',
'footer-bar shadow-xs absolute bottom-0 z-50 flex w-full flex-col',
'sm:h-[52px] sm:justify-center',
'sm:bg-base-100 border-base-300/50 border sm:border-none',
'transition-[opacity,transform] duration-300',
appService?.hasRoundedWindow && 'rounded-window-bottom-right',
!isSideBarVisible && appService?.hasRoundedWindow && 'rounded-window-bottom-left',
isHoveredAnim && 'hover-bar-anim',
// show scroll bar when vertical and scrolled in desktop
viewSettings?.vertical &&
viewSettings?.scrolled &&
'sm:!bottom-3 sm:!h-7 sm:justify-center',
viewSettings?.vertical && viewSettings?.scrolled && 'sm:!bottom-3 sm:!h-7',
isVisible
? `pointer-events-auto translate-y-0 opacity-100`
: `pointer-events-none translate-y-full opacity-0 sm:translate-y-0`,
@@ -0,0 +1,49 @@
import clsx from 'clsx';
import React from 'react';
import { FiChevronDown } from 'react-icons/fi';
import { MdCheck } from 'react-icons/md';
import { useDefaultIconSize, useResponsiveSize } from '@/hooks/useResponsiveSize';
interface DropDownProps {
selected: { option: string; label: string };
options: { option: string; label: string }[];
onSelect: (option: string) => void;
}
const DropDown: React.FC<DropDownProps> = ({ selected, options, onSelect }) => {
const iconSize16 = useResponsiveSize(16);
const defaultIconSize = useDefaultIconSize();
return (
<div className='dropdown dropdown-bottom'>
<button
tabIndex={0}
className='btn btn-sm flex items-center gap-1 px-[20px] font-normal normal-case'
onClick={(e) => e.currentTarget.focus()}
>
<span>{selected.label}</span>
<FiChevronDown size={iconSize16} />
</button>
<ul
tabIndex={0}
className={clsx(
'dropdown-content bgcolor-base-200 no-triangle menu rounded-box absolute z-[1] shadow',
'menu-vertical right-[-32px] mt-2 inline max-h-80 w-44 overflow-y-scroll sm:right-0',
)}
>
{options.map(({ option, label }) => (
<li key={option} onClick={() => onSelect(option)}>
<div className='flex items-center px-0'>
<span style={{ minWidth: `${defaultIconSize}px` }}>
{selected.option === option && <MdCheck className='text-base-content' />}
</span>
<span>{label || option}</span>
</div>
</li>
))}
</ul>
</div>
);
};
export default DropDown;
@@ -1,8 +1,6 @@
import clsx from 'clsx';
import React, { useEffect, useState } from 'react';
import NumberInput from './NumberInput';
import FontDropdown from './FontDropDown';
import {
ANDROID_FONTS,
IOS_FONTS,
@@ -21,6 +19,8 @@ import { getOSPlatform } from '@/utils/misc';
import { getSysFontsList } from '@/utils/font';
import { isTauriAppPlatform } from '@/services/environment';
import { saveViewSettings } from '../../utils/viewSettingsHelper';
import NumberInput from './NumberInput';
import FontDropdown from './FontDropDown';
interface FontFaceProps {
className?: string;
@@ -1,13 +1,16 @@
import clsx from 'clsx';
import React, { useEffect, useState } from 'react';
import i18n from 'i18next';
import { useEnv } from '@/context/EnvContext';
import { useReaderStore } from '@/store/readerStore';
import { useSettingsStore } from '@/store/settingsStore';
import { useTranslation } from '@/hooks/useTranslation';
import { getStyles } from '@/utils/style';
import { saveViewSettings } from '../../utils/viewSettingsHelper';
import { TRANSLATED_LANGS } from '@/services/constants';
import cssbeautify from 'cssbeautify';
import cssValidate from '@/utils/css';
import DropDown from './DropDown';
const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const _ = useTranslation();
@@ -69,6 +72,31 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
e.nativeEvent.stopImmediatePropagation();
};
const getCurrentUILangOption = () => {
const uiLanguage = viewSettings.uiLanguage;
return {
option: uiLanguage,
label:
uiLanguage === ''
? _('Auto')
: TRANSLATED_LANGS[uiLanguage as keyof typeof TRANSLATED_LANGS],
};
};
const getUILangOptions = () => {
const langs = TRANSLATED_LANGS as Record<string, string>;
const options = Object.entries(langs).map(([option, label]) => ({ option, label }));
options.sort((a, b) => a.label.localeCompare(b.label));
options.unshift({ option: '', label: _('Auto') });
return options;
};
const handleSelectUILang = (option: string) => {
viewSettings.uiLanguage = option;
saveViewSettings(envConfig, bookKey, 'uiLanguage', option);
i18n.changeLanguage(option ? option : navigator.language);
};
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'animated', animated);
if (animated) {
@@ -91,6 +119,22 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
return (
<div className='my-4 w-full space-y-6'>
<div className='w-full'>
<h2 className='mb-2 font-medium'>{_('Language')}</h2>
<div className='card border-base-200 bg-base-100 border shadow'>
<div className='divide-base-200 divide-y'>
<div className='config-item'>
<span className=''>{_('Language')}</span>
<DropDown
selected={getCurrentUILangOption()}
options={getUILangOptions()}
onSelect={handleSelectUILang}
/>
</div>
</div>
</div>
</div>
<div className='w-full'>
<h2 className='mb-2 font-medium'>{_('Animation')}</h2>
<div className='card border-base-200 bg-base-100 border shadow'>
@@ -115,6 +115,7 @@ export const DEFAULT_CJK_VIEW_SETTINGS: Partial<ViewSettings> = {
export const DEFAULT_VIEW_CONFIG: ViewConfig = {
sideBarTab: 'toc',
uiLanguage: '',
};
export const DEFAULT_TTS_CONFIG: TTSConfig = {
@@ -485,3 +486,25 @@ export const MIGHT_BE_RTL_LANGS = [
'yi',
'',
];
export const TRANSLATED_LANGS = {
en: 'English',
fr: 'Français',
de: 'Deutsch',
it: 'Italiano',
ja: '日本語',
ko: '한국어',
es: 'Español',
pt: 'Português',
ru: 'Русский',
ar: 'العربية',
el: 'Ελληνικά',
uk: 'Українська',
pl: 'Polski',
tr: 'Türkçe',
hi: 'हिन्दी',
id: 'Bahasa Indonesia',
vi: 'Tiếng Việt',
'zh-CN': '简体中文',
'zh-TW': '正體中文',
};
+1
View File
@@ -102,6 +102,7 @@ export interface BookFont {
export interface ViewConfig {
sideBarTab: string;
uiLanguage: string;
}
export interface TTSConfig {