forked from akai/readest
feat: add language preference options in settings (#686)
This commit is contained in:
@@ -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': '正體中文',
|
||||
};
|
||||
|
||||
@@ -102,6 +102,7 @@ export interface BookFont {
|
||||
|
||||
export interface ViewConfig {
|
||||
sideBarTab: string;
|
||||
uiLanguage: string;
|
||||
}
|
||||
|
||||
export interface TTSConfig {
|
||||
|
||||
Reference in New Issue
Block a user