forked from akai/readest
Add i18n support (#46)
This commit is contained in:
@@ -5,6 +5,7 @@ import * as CFI from 'foliate-js/epubcfi.js';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { BookNote } from '@/types/book';
|
||||
import { uniqueId } from '@/utils/misc';
|
||||
@@ -15,6 +16,7 @@ interface BookmarkTogglerProps {
|
||||
}
|
||||
|
||||
const BookmarkToggler: React.FC<BookmarkTogglerProps> = ({ bookKey }) => {
|
||||
const _ = useTranslation();
|
||||
const { envConfig } = useEnv();
|
||||
const { settings } = useSettingsStore();
|
||||
const { getConfig, saveConfig, updateBooknotes } = useBookDataStore();
|
||||
@@ -98,7 +100,7 @@ const BookmarkToggler: React.FC<BookmarkTogglerProps> = ({ bookKey }) => {
|
||||
)
|
||||
}
|
||||
onClick={toggleBookmark}
|
||||
tooltip='Bookmark'
|
||||
tooltip={_('Bookmark')}
|
||||
tooltipDirection='bottom'
|
||||
></Button>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { RiArrowGoBackLine, RiArrowGoForwardLine } from 'react-icons/ri';
|
||||
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useSidebarStore } from '@/store/sidebarStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import Button from '@/components/Button';
|
||||
|
||||
interface FooterBarProps {
|
||||
@@ -14,6 +15,7 @@ interface FooterBarProps {
|
||||
}
|
||||
|
||||
const FooterBar: React.FC<FooterBarProps> = ({ bookKey, pageinfo, isHoveredAnim }) => {
|
||||
const _ = useTranslation();
|
||||
const { hoveredBookKey, setHoveredBookKey, getView } = useReaderStore();
|
||||
const { isSideBarVisible } = useSidebarStore();
|
||||
const view = getView(bookKey);
|
||||
@@ -53,17 +55,21 @@ const FooterBar: React.FC<FooterBarProps> = ({ bookKey, pageinfo, isHoveredAnim
|
||||
onMouseEnter={() => setHoveredBookKey(bookKey)}
|
||||
onMouseLeave={() => setHoveredBookKey('')}
|
||||
>
|
||||
<Button icon={<RiArrowLeftWideLine size={20} />} onClick={handleGoPrev} tooltip='Go Left' />
|
||||
<Button
|
||||
icon={<RiArrowLeftWideLine size={20} />}
|
||||
onClick={handleGoPrev}
|
||||
tooltip={_('Go Left')}
|
||||
/>
|
||||
<Button
|
||||
icon={<RiArrowGoBackLine size={20} />}
|
||||
onClick={handleGoBack}
|
||||
tooltip='Go Back'
|
||||
tooltip={_('Go Back')}
|
||||
disabled={!view?.history.canGoBack}
|
||||
/>
|
||||
<Button
|
||||
icon={<RiArrowGoForwardLine size={20} />}
|
||||
onClick={handleGoForward}
|
||||
tooltip='Go Forward'
|
||||
tooltip={_('Go Forward')}
|
||||
disabled={!view?.history.canGoForward}
|
||||
/>
|
||||
<span className='mx-2 text-center text-sm'>
|
||||
@@ -77,7 +83,11 @@ const FooterBar: React.FC<FooterBarProps> = ({ bookKey, pageinfo, isHoveredAnim
|
||||
value={pageinfoValid ? progressFraction * 100 : 0}
|
||||
onChange={(e) => handleProgressChange(e)}
|
||||
/>
|
||||
<Button icon={<RiArrowRightWideLine size={20} />} onClick={handleGoNext} tooltip='Go Right' />
|
||||
<Button
|
||||
icon={<RiArrowRightWideLine size={20} />}
|
||||
onClick={handleGoNext}
|
||||
tooltip={_('Go Right')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { TbLayoutSidebarRight, TbLayoutSidebarRightFilled } from 'react-icons/tb
|
||||
|
||||
import { useSidebarStore } from '@/store/sidebarStore';
|
||||
import { useNotebookStore } from '@/store/notebookStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import Button from '@/components/Button';
|
||||
|
||||
interface NotebookTogglerProps {
|
||||
@@ -10,6 +11,7 @@ interface NotebookTogglerProps {
|
||||
}
|
||||
|
||||
const NotebookToggler: React.FC<NotebookTogglerProps> = ({ bookKey }) => {
|
||||
const _ = useTranslation();
|
||||
const { sideBarBookKey, setSideBarBookKey } = useSidebarStore();
|
||||
const { isNotebookVisible, toggleNotebook } = useNotebookStore();
|
||||
const handleToggleSidebar = () => {
|
||||
@@ -30,7 +32,7 @@ const NotebookToggler: React.FC<NotebookTogglerProps> = ({ bookKey }) => {
|
||||
)
|
||||
}
|
||||
onClick={handleToggleSidebar}
|
||||
tooltip='Notebook'
|
||||
tooltip={_('Notebook')}
|
||||
tooltipDirection='bottom'
|
||||
></Button>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { PageInfo } from '@/types/book';
|
||||
import React from 'react';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { PageInfo } from '@/types/book';
|
||||
|
||||
interface PageInfoProps {
|
||||
bookFormat: string;
|
||||
@@ -9,13 +10,17 @@ interface PageInfoProps {
|
||||
}
|
||||
|
||||
const PageInfoView: React.FC<PageInfoProps> = ({ bookFormat, section, pageinfo, gapRight }) => {
|
||||
const _ = useTranslation();
|
||||
const pageInfo =
|
||||
bookFormat === 'PDF'
|
||||
? section
|
||||
? `${section.current + 1} / ${section.total}`
|
||||
: ''
|
||||
: pageinfo
|
||||
? `Loc. ${pageinfo.current + 1} / ${pageinfo.total}`
|
||||
? _('Loc. {{currentPage}} / {{totalPage}}', {
|
||||
currentPage: pageinfo.current + 1,
|
||||
totalPage: pageinfo.total,
|
||||
})
|
||||
: '';
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { TbLayoutSidebar, TbLayoutSidebarFilled } from 'react-icons/tb';
|
||||
|
||||
import { useSidebarStore } from '@/store/sidebarStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import Button from '@/components/Button';
|
||||
|
||||
interface SidebarTogglerProps {
|
||||
@@ -9,6 +10,7 @@ interface SidebarTogglerProps {
|
||||
}
|
||||
|
||||
const SidebarToggler: React.FC<SidebarTogglerProps> = ({ bookKey }) => {
|
||||
const _ = useTranslation();
|
||||
const { sideBarBookKey, isSideBarVisible, setSideBarBookKey, toggleSideBar } = useSidebarStore();
|
||||
const handleToggleSidebar = () => {
|
||||
if (sideBarBookKey === bookKey) {
|
||||
@@ -28,7 +30,7 @@ const SidebarToggler: React.FC<SidebarTogglerProps> = ({ bookKey }) => {
|
||||
)
|
||||
}
|
||||
onClick={handleToggleSidebar}
|
||||
tooltip='Sidebar'
|
||||
tooltip={_('Sidebar')}
|
||||
tooltipDirection='bottom'
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { MdZoomOut, MdZoomIn, MdCheck } from 'react-icons/md';
|
||||
import { ONE_COLUMN_MAX_INLINE_SIZE } from '@/services/constants';
|
||||
import MenuItem from '@/components/MenuItem';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useTheme, ThemeMode } from '@/hooks/useTheme';
|
||||
import { getStyles } from '@/utils/style';
|
||||
|
||||
@@ -22,6 +23,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
|
||||
setIsDropdownOpen,
|
||||
onSetSettingsDialogOpen,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const { getView, getViews, getViewSettings, setViewSettings } = useReaderStore();
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
|
||||
@@ -126,10 +128,10 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
|
||||
|
||||
<hr className='border-base-200 my-1' />
|
||||
|
||||
<MenuItem label='Font & Layout' shortcut='Shift+F' onClick={openFontLayoutMenu} />
|
||||
<MenuItem label={_('Font & Layout')} shortcut='Shift+F' onClick={openFontLayoutMenu} />
|
||||
|
||||
<MenuItem
|
||||
label='Scrolled Mode'
|
||||
label={_('Scrolled Mode')}
|
||||
shortcut='Shift+J'
|
||||
icon={isScrolledMode ? <MdCheck size={20} /> : undefined}
|
||||
onClick={toggleScrolledMode}
|
||||
@@ -139,7 +141,11 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
|
||||
|
||||
<MenuItem
|
||||
label={
|
||||
themeMode === 'dark' ? 'Dark Mode' : themeMode === 'light' ? 'Light Mode' : 'Auto Mode'
|
||||
themeMode === 'dark'
|
||||
? _('Dark Mode')
|
||||
: themeMode === 'light'
|
||||
? _('Light Mode')
|
||||
: _('Auto Mode')
|
||||
}
|
||||
icon={
|
||||
themeMode === 'dark' ? (
|
||||
@@ -153,7 +159,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
|
||||
onClick={cycleThemeMode}
|
||||
/>
|
||||
<MenuItem
|
||||
label='Invert Colors in Dark Mode'
|
||||
label={_('Invert Colors in Dark Mode')}
|
||||
icon={isInvertedColors ? <MdCheck size={20} className='text-base-content' /> : undefined}
|
||||
onClick={toggleInvertedColors}
|
||||
disabled={!isDarkMode}
|
||||
|
||||
@@ -2,27 +2,31 @@ import React from 'react';
|
||||
|
||||
import { FiSearch } from 'react-icons/fi';
|
||||
import { MdOutlinePushPin, MdPushPin } from 'react-icons/md';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
|
||||
const NotebookHeader: React.FC<{
|
||||
isPinned: boolean;
|
||||
handleTogglePin: () => void;
|
||||
}> = ({ isPinned, handleTogglePin }) => (
|
||||
<div className='notebook-header relative flex h-11 items-center px-3'>
|
||||
<div className='absolute inset-0 flex items-center justify-center'>
|
||||
<div className='notebook-title text-sm font-medium'>Notebook</div>
|
||||
}> = ({ isPinned, handleTogglePin }) => {
|
||||
const _ = useTranslation();
|
||||
return (
|
||||
<div className='notebook-header relative flex h-11 items-center px-3'>
|
||||
<div className='absolute inset-0 flex items-center justify-center'>
|
||||
<div className='notebook-title text-sm font-medium'>{_('Notebook')}</div>
|
||||
</div>
|
||||
<div className='z-10 flex items-center space-x-3'>
|
||||
<button
|
||||
onClick={handleTogglePin}
|
||||
className={`${isPinned ? 'bg-base-300' : 'bg-base-300/65'} btn btn-ghost btn-circle h-6 min-h-6 w-6`}
|
||||
>
|
||||
{isPinned ? <MdPushPin size={14} /> : <MdOutlinePushPin size={14} />}
|
||||
</button>
|
||||
<button className='btn btn-ghost left-0 h-8 min-h-8 w-8 p-0'>
|
||||
<FiSearch size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='z-10 flex items-center space-x-3'>
|
||||
<button
|
||||
onClick={handleTogglePin}
|
||||
className={`${isPinned ? 'bg-base-300' : 'bg-base-300/65'} btn btn-ghost btn-circle h-6 min-h-6 w-6`}
|
||||
>
|
||||
{isPinned ? <MdPushPin size={14} /> : <MdOutlinePushPin size={14} />}
|
||||
</button>
|
||||
<button className='btn btn-ghost left-0 h-8 min-h-8 w-8 p-0'>
|
||||
<FiSearch size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default NotebookHeader;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useNotebookStore } from '@/store/notebookStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { TextSelection } from '@/utils/sel';
|
||||
import { BookNote } from '@/types/book';
|
||||
|
||||
@@ -10,6 +11,7 @@ interface NoteEditorProps {
|
||||
}
|
||||
|
||||
const NoteEditor: React.FC<NoteEditorProps> = ({ onSave, onEdit }) => {
|
||||
const _ = useTranslation();
|
||||
const { notebookNewAnnotation, notebookEditAnnotation } = useNotebookStore();
|
||||
const editorRef = useRef<HTMLTextAreaElement>(null);
|
||||
const [note, setNote] = React.useState('');
|
||||
@@ -62,7 +64,7 @@ const NoteEditor: React.FC<NoteEditorProps> = ({ onSave, onEdit }) => {
|
||||
rows={1}
|
||||
spellCheck={false}
|
||||
onChange={handleChange}
|
||||
placeholder='Add your notes here...'
|
||||
placeholder={_('Add your notes here...')}
|
||||
></textarea>
|
||||
</div>
|
||||
<button
|
||||
@@ -73,7 +75,7 @@ const NoteEditor: React.FC<NoteEditorProps> = ({ onSave, onEdit }) => {
|
||||
)}
|
||||
onClick={handleSaveNote}
|
||||
>
|
||||
<div className='pr-1 align-bottom text-xs text-blue-400'>Save</div>
|
||||
<div className='pr-1 align-bottom text-xs text-blue-400'>{_('Save')}</div>
|
||||
</button>
|
||||
</div>
|
||||
<div className='flex items-start pt-2'>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useSidebarStore } from '@/store/sidebarStore';
|
||||
import { useNotebookStore } from '@/store/notebookStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import useDragBar from '../../hooks/useDragBar';
|
||||
import NotebookHeader from './Header';
|
||||
@@ -19,6 +20,7 @@ const MIN_NOTEBOOK_WIDTH = 0.15;
|
||||
const MAX_NOTEBOOK_WIDTH = 0.45;
|
||||
|
||||
const Notebook: React.FC = ({}) => {
|
||||
const _ = useTranslation();
|
||||
const { envConfig } = useEnv();
|
||||
const { settings } = useSettingsStore();
|
||||
const { sideBarBookKey } = useSidebarStore();
|
||||
@@ -139,7 +141,7 @@ const Notebook: React.FC = ({}) => {
|
||||
/>
|
||||
<NotebookHeader isPinned={isNotebookPinned} handleTogglePin={handleTogglePin} />
|
||||
<div className='max-h-[calc(100vh-44px)] overflow-y-auto px-3'>
|
||||
<div>{excerptNotes.length > 0 && <p className='pt-1 text-sm'>Excerpts</p>}</div>
|
||||
<div>{excerptNotes.length > 0 && <p className='pt-1 text-sm'>{_('Excerpts')}</p>}</div>
|
||||
<ul className=''>
|
||||
{excerptNotes.map((item, index) => (
|
||||
<li key={`${index}-${item.id}`} className='my-2'>
|
||||
@@ -171,7 +173,7 @@ const Notebook: React.FC = ({}) => {
|
||||
)}
|
||||
onClick={handleEditNote.bind(null, item, true)}
|
||||
>
|
||||
<div className='align-bottom text-xs text-red-400'>Delete</div>
|
||||
<div className='align-bottom text-xs text-red-400'>{_('Delete')}</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -181,7 +183,7 @@ const Notebook: React.FC = ({}) => {
|
||||
</ul>
|
||||
<div>
|
||||
{(notebookNewAnnotation || annotationNotes.length > 0) && (
|
||||
<p className='pt-1 text-sm'>Notes</p>
|
||||
<p className='pt-1 text-sm'>{_('Notes')}</p>
|
||||
)}
|
||||
</div>
|
||||
{(notebookNewAnnotation || notebookEditAnnotation) && (
|
||||
|
||||
@@ -6,9 +6,11 @@ import { TbSunMoon } from 'react-icons/tb';
|
||||
import { useTheme } from '@/hooks/useTheme';
|
||||
import { themes } from '@/styles/themes';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { getStyles } from '@/utils/style';
|
||||
|
||||
const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const _ = useTranslation();
|
||||
const { themeMode, themeColor, themeCode, isDarkMode, updateThemeMode, updateThemeColor } =
|
||||
useTheme();
|
||||
const { getViews, getViewSettings } = useReaderStore();
|
||||
@@ -24,9 +26,9 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
return (
|
||||
<div className='my-4 w-full space-y-6'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<h2 className='font-medium'>Theme Mode</h2>
|
||||
<h2 className='font-medium'>{_('Theme Mode')}</h2>
|
||||
<div className='flex gap-2'>
|
||||
<div className='tooltip tooltip-bottom' data-tip='Auto Mode'>
|
||||
<div className='tooltip tooltip-bottom' data-tip={_('Auto Mode')}>
|
||||
<button
|
||||
className={`btn btn-ghost btn-circle ${themeMode === 'auto' ? 'btn-active bg-base-300' : ''}`}
|
||||
onClick={() => updateThemeMode('auto')}
|
||||
@@ -35,7 +37,7 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className='tooltip tooltip-bottom' data-tip='Light Mode'>
|
||||
<div className='tooltip tooltip-bottom' data-tip={_('Light Mode')}>
|
||||
<button
|
||||
className={`btn btn-ghost btn-circle ${themeMode === 'light' ? 'btn-active bg-base-300' : ''}`}
|
||||
onClick={() => updateThemeMode('light')}
|
||||
@@ -44,7 +46,7 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className='tooltip tooltip-bottom' data-tip='Dark Mode'>
|
||||
<div className='tooltip tooltip-bottom' data-tip={_('Dark Mode')}>
|
||||
<button
|
||||
className={`btn btn-ghost btn-circle ${themeMode === 'dark' ? 'btn-active bg-base-300' : ''}`}
|
||||
onClick={() => updateThemeMode('dark')}
|
||||
@@ -56,7 +58,7 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className='mb-2 font-medium'>Theme Color</h2>
|
||||
<h2 className='mb-2 font-medium'>{_('Theme Color')}</h2>
|
||||
<div className='grid grid-cols-3 gap-4'>
|
||||
{themes.map(({ name, label, colors }) => (
|
||||
<label
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import React from 'react';
|
||||
import { MdCheck } from 'react-icons/md';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
|
||||
interface DialogMenuProps {
|
||||
toggleDropdown?: () => void;
|
||||
}
|
||||
|
||||
const DialogMenu: React.FC<DialogMenuProps> = ({ toggleDropdown }) => {
|
||||
const _ = useTranslation();
|
||||
const { isFontLayoutSettingsGlobal, setFontLayoutSettingsGlobal } = useSettingsStore();
|
||||
|
||||
const handleToggleGlobal = () => {
|
||||
@@ -28,7 +30,7 @@ const DialogMenu: React.FC<DialogMenuProps> = ({ toggleDropdown }) => {
|
||||
{isFontLayoutSettingsGlobal && <MdCheck size={20} className='text-base-content' />}
|
||||
</span>
|
||||
<div className='tooltip' data-tip='Uncheck for current book settings'>
|
||||
<span className='ml-2'>Global Settings</span>
|
||||
<span className='ml-2'>{_('Global Settings')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -6,8 +6,9 @@ import FontDropdown from './FontDropDown';
|
||||
import { MONOSPACE_FONTS, SANS_SERIF_FONTS, SERIF_FONTS } from '@/services/constants';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { getStyles } from '@/utils/style';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useTheme } from '@/hooks/useTheme';
|
||||
import { getStyles } from '@/utils/style';
|
||||
|
||||
interface FontFaceProps {
|
||||
className?: string;
|
||||
@@ -38,6 +39,7 @@ const FontFace = ({ className, family, label, options, selected, onSelect }: Fon
|
||||
);
|
||||
|
||||
const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const _ = useTranslation();
|
||||
const { settings, isFontLayoutSettingsGlobal, setSettings } = useSettingsStore();
|
||||
const { getView, getViewSettings, setViewSettings } = useReaderStore();
|
||||
const view = getView(bookKey);
|
||||
@@ -145,7 +147,7 @@ const FontPanel: 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'>Font Size</h2>
|
||||
<h2 className='mb-2 font-medium'>{_('Font Size')}</h2>
|
||||
<div className='card border-base-200 border shadow'>
|
||||
<div className='divide-base-200 divide-y'>
|
||||
<NumberInput
|
||||
@@ -169,11 +171,11 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
</div>
|
||||
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-2 font-medium'>Font Family</h2>
|
||||
<h2 className='mb-2 font-medium'>{_('Font Family')}</h2>
|
||||
<div className='card border-base-200 border shadow'>
|
||||
<div className='divide-base-200 divide-y'>
|
||||
<div className='config-item config-item-top'>
|
||||
<span className=''>Default Font</span>
|
||||
<span className=''>{_('Default Font')}</span>
|
||||
<FontDropdown
|
||||
options={fontFamilyOptions}
|
||||
selected={defaultFont}
|
||||
@@ -183,7 +185,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
</div>
|
||||
|
||||
<div className='config-item config-item-bottom'>
|
||||
<span className=''>Override Publisher Font</span>
|
||||
<span className=''>{_('Override Publisher Font')}</span>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='toggle'
|
||||
@@ -196,20 +198,20 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
</div>
|
||||
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-2 font-medium'>Font Face</h2>
|
||||
<h2 className='mb-2 font-medium'>{_('Font Face')}</h2>
|
||||
<div className='card border-base-200 border shadow'>
|
||||
<div className='divide-base-200 divide-y'>
|
||||
<FontFace
|
||||
className='config-item-top'
|
||||
family='serif'
|
||||
label='Serif Font'
|
||||
label={_('Serif Font')}
|
||||
options={SERIF_FONTS}
|
||||
selected={serifFont}
|
||||
onSelect={setSerifFont}
|
||||
/>
|
||||
<FontFace
|
||||
family='sans-serif'
|
||||
label='Sans-Serif Font'
|
||||
label={_('Sans-Serif Font')}
|
||||
options={SANS_SERIF_FONTS}
|
||||
selected={sansSerifFont}
|
||||
onSelect={setSansSerifFont}
|
||||
@@ -217,7 +219,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
<FontFace
|
||||
className='config-item-bottom'
|
||||
family='monospace'
|
||||
label='Monospace Font'
|
||||
label={_('Monospace Font')}
|
||||
options={MONOSPACE_FONTS}
|
||||
selected={monospaceFont}
|
||||
onSelect={setMonospaceFont}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { getStyles } from '@/utils/style';
|
||||
import { ONE_COLUMN_MAX_INLINE_SIZE } from '@/services/constants';
|
||||
import NumberInput from './NumberInput';
|
||||
import { useTheme } from '@/hooks/useTheme';
|
||||
|
||||
const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const _ = useTranslation();
|
||||
const { settings, isFontLayoutSettingsGlobal, setSettings } = useSettingsStore();
|
||||
const { getView, getViewSettings, setViewSettings } = useReaderStore();
|
||||
const view = getView(bookKey);
|
||||
@@ -112,7 +114,7 @@ const LayoutPanel: 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'>Paragraph</h2>
|
||||
<h2 className='mb-2 font-medium'>{_('Paragraph')}</h2>
|
||||
<div className='card bg-base-100 border-base-200 border shadow'>
|
||||
<div className='divide-base-200 divide-y'>
|
||||
<NumberInput
|
||||
@@ -125,7 +127,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
step={0.1}
|
||||
/>
|
||||
<div className='config-item config-item-bottom'>
|
||||
<span className=''>Full Justification</span>
|
||||
<span className=''>{_('Full Justification')}</span>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='toggle'
|
||||
@@ -134,7 +136,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
/>
|
||||
</div>
|
||||
<div className='config-item config-item-bottom'>
|
||||
<span className=''>Hyphenation</span>
|
||||
<span className=''>{_('Hyphenation')}</span>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='toggle'
|
||||
@@ -147,12 +149,12 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
</div>
|
||||
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-2 font-medium'>Page</h2>
|
||||
<h2 className='mb-2 font-medium'>{_('Page')}</h2>
|
||||
<div className='card bg-base-100 border-base-200 border shadow'>
|
||||
<div className='divide-base-200 divide-y'>
|
||||
<NumberInput
|
||||
className='config-item-top'
|
||||
label='Margins (px)'
|
||||
label={_('Margins (px)')}
|
||||
value={marginPx}
|
||||
onChange={setMarginPx}
|
||||
min={0}
|
||||
@@ -160,21 +162,21 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
step={4}
|
||||
/>
|
||||
<NumberInput
|
||||
label='Gaps (%)'
|
||||
label={_('Gaps (%)')}
|
||||
value={gapPercent}
|
||||
onChange={setGapPercent}
|
||||
min={0}
|
||||
max={30}
|
||||
/>
|
||||
<NumberInput
|
||||
label='Maximum Number of Columns'
|
||||
label={_('Maximum Number of Columns')}
|
||||
value={maxColumnCount}
|
||||
onChange={setMaxColumnCount}
|
||||
min={1}
|
||||
max={2}
|
||||
/>
|
||||
<NumberInput
|
||||
label='Maximum Inline Size'
|
||||
label={_('Maximum Inline Size')}
|
||||
value={maxInlineSize}
|
||||
onChange={setMaxInlineSize}
|
||||
min={500}
|
||||
@@ -182,7 +184,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
step={100}
|
||||
/>
|
||||
<NumberInput
|
||||
label='Maximum Block Size'
|
||||
label={_('Maximum Block Size')}
|
||||
value={maxBlockSize}
|
||||
onChange={setMaxBlockSize}
|
||||
min={500}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import cssbeautify from 'cssbeautify';
|
||||
import { getStyles } from '@/utils/style';
|
||||
import { useTheme } from '@/hooks/useTheme';
|
||||
@@ -9,6 +10,7 @@ const cssRegex =
|
||||
/((?:\s*)([\w#.@*,:\-.:>+~$$$$\"=(),*\s]+)\s*{(?:[\s]*)((?:[^\}]+[:][^\}]+;?)*)*\s*}(?:\s*))/gim;
|
||||
|
||||
const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const _ = useTranslation();
|
||||
const { settings, isFontLayoutSettingsGlobal, setSettings } = useSettingsStore();
|
||||
const { getView, getViewSettings, setViewSettings } = useReaderStore();
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
@@ -87,11 +89,11 @@ 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'>Animation</h2>
|
||||
<h2 className='mb-2 font-medium'>{_('Animation')}</h2>
|
||||
<div className='card bg-base-100 border shadow'>
|
||||
<div className='divide-y'>
|
||||
<div className='config-item config-item-top config-item-bottom'>
|
||||
<span className='text-gray-700'>Paging Animation</span>
|
||||
<span className='text-gray-700'>{_('Paging Animation')}</span>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='toggle'
|
||||
@@ -104,11 +106,11 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
</div>
|
||||
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-2 font-medium'>Behavior</h2>
|
||||
<h2 className='mb-2 font-medium'>{_('Behavior')}</h2>
|
||||
<div className='card bg-base-100 border shadow'>
|
||||
<div className='divide-y'>
|
||||
<div className='config-item config-item-top config-item-bottom'>
|
||||
<span className='text-gray-700'>Disable Click-to-Flip</span>
|
||||
<span className='text-gray-700'>{_('Disable Click-to-Flip')}</span>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='toggle'
|
||||
@@ -121,7 +123,7 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
</div>
|
||||
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-2 font-medium'>Custom CSS</h2>
|
||||
<h2 className='mb-2 font-medium'>{_('Custom CSS')}</h2>
|
||||
<div className={`card bg-base-100 border shadow ${error ? 'border-red-500' : ''}`}>
|
||||
<div className='divide-y'>
|
||||
<div className='css-text-area config-item-top config-item-bottom p-1'>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { BookConfig } from '@/types/book';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { RiFontSize } from 'react-icons/ri';
|
||||
import { RiDashboardLine } from 'react-icons/ri';
|
||||
import { VscSymbolColor } from 'react-icons/vsc';
|
||||
@@ -15,8 +16,11 @@ import Dropdown from '@/components/Dropdown';
|
||||
import DialogMenu from './DialogMenu';
|
||||
import MiscPanel from './MiscPanel';
|
||||
|
||||
type SettingsPanelType = 'Font' | 'Layout' | 'Color' | 'Misc';
|
||||
|
||||
const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ bookKey }) => {
|
||||
const [activePanel, setActivePanel] = useState('Font');
|
||||
const _ = useTranslation();
|
||||
const [activePanel, setActivePanel] = useState<SettingsPanelType>('Font');
|
||||
const { setFontLayoutSettingsDialogOpen } = useSettingsStore();
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
@@ -43,28 +47,28 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
|
||||
onClick={() => setActivePanel('Font')}
|
||||
>
|
||||
<RiFontSize size={20} className='mr-0' />
|
||||
Font
|
||||
{_('Font')}
|
||||
</button>
|
||||
<button
|
||||
className={`btn btn-ghost text-base-content h-8 min-h-8 px-4 ${activePanel === 'Layout' ? 'btn-active' : ''}`}
|
||||
onClick={() => setActivePanel('Layout')}
|
||||
>
|
||||
<RiDashboardLine size={20} className='mr-0' />
|
||||
Layout
|
||||
{_('Layout')}
|
||||
</button>
|
||||
<button
|
||||
className={`btn btn-ghost text-base-content h-8 min-h-8 px-4 ${activePanel === 'Color' ? 'btn-active' : ''}`}
|
||||
onClick={() => setActivePanel('Color')}
|
||||
>
|
||||
<VscSymbolColor size={20} className='mr-0' />
|
||||
Color
|
||||
{_('Color')}
|
||||
</button>
|
||||
<button
|
||||
className={`btn btn-ghost text-base-content h-8 min-h-8 px-4 ${activePanel === 'Misc' ? 'btn-active' : ''}`}
|
||||
onClick={() => setActivePanel('Misc')}
|
||||
>
|
||||
<IoAccessibilityOutline size={20} className='mr-0' />
|
||||
Misc
|
||||
{_('Misc')}
|
||||
</button>
|
||||
</div>
|
||||
<div className='flex h-full items-center justify-end'>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import Image from 'next/image';
|
||||
import { MdInfoOutline } from 'react-icons/md';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
|
||||
interface BookCardProps {
|
||||
cover: string;
|
||||
@@ -9,11 +10,12 @@ interface BookCardProps {
|
||||
}
|
||||
|
||||
const BookCard: React.FC<BookCardProps> = ({ cover, title, author }) => {
|
||||
const _ = useTranslation();
|
||||
return (
|
||||
<div className='flex h-20 w-full items-center'>
|
||||
<Image
|
||||
src={cover}
|
||||
alt='Book cover'
|
||||
alt={_('Book Cover')}
|
||||
width={56}
|
||||
height={80}
|
||||
className='mr-4 aspect-auto max-h-20 w-[15%] max-w-14 rounded-sm object-cover shadow-md'
|
||||
@@ -27,7 +29,7 @@ const BookCard: React.FC<BookCardProps> = ({ cover, title, author }) => {
|
||||
</div>
|
||||
<button
|
||||
className='btn btn-ghost hover:bg-base-300 h-6 min-h-6 w-6 rounded-full p-0 transition-colors'
|
||||
aria-label='More info'
|
||||
aria-label={_('More Info')}
|
||||
>
|
||||
<MdInfoOutline size={18} className='fill-base-content' />
|
||||
</button>
|
||||
|
||||
@@ -4,6 +4,7 @@ import Image from 'next/image';
|
||||
import MenuItem from '@/components/MenuItem';
|
||||
import { setAboutDialogVisible } from '@/components/AboutWindow';
|
||||
import { useLibraryStore } from '@/store/libraryStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { isWebAppPlatform } from '@/services/environment';
|
||||
import { DOWNLOAD_READEST_URL } from '@/services/constants';
|
||||
import useBooksManager from '../../hooks/useBooksManager';
|
||||
@@ -13,6 +14,7 @@ interface BookMenuProps {
|
||||
}
|
||||
|
||||
const BookMenu: React.FC<BookMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
const _ = useTranslation();
|
||||
const { library } = useLibraryStore();
|
||||
const { openParallelView } = useBooksManager();
|
||||
const handleParallelView = (id: string) => {
|
||||
@@ -39,7 +41,7 @@ const BookMenu: React.FC<BookMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
tabIndex={0}
|
||||
className='book-menu dropdown-content dropdown-center border-base-100 z-20 mt-3 w-60 shadow-2xl'
|
||||
>
|
||||
<MenuItem label='Parallel Read' noIcon>
|
||||
<MenuItem label={_('Parallel Read')} noIcon>
|
||||
<ul className='max-h-60 overflow-y-auto'>
|
||||
{library.slice(0, 20).map((book) => (
|
||||
<MenuItem
|
||||
@@ -62,10 +64,10 @@ const BookMenu: React.FC<BookMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
))}
|
||||
</ul>
|
||||
</MenuItem>
|
||||
<MenuItem label='Reload Page' noIcon shortcut='Shift+R' onClick={handleReloadPage} />
|
||||
<MenuItem label={_('Reload Page')} noIcon shortcut='Shift+R' onClick={handleReloadPage} />
|
||||
<hr className='border-base-200 my-1' />
|
||||
{isWebApp && <MenuItem label='Download Readest' noIcon onClick={downloadReadest} />}
|
||||
<MenuItem label='About Readest' noIcon onClick={showAboutReadest} />
|
||||
<MenuItem label={_('About Readest')} noIcon onClick={showAboutReadest} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,8 +6,9 @@ import { BookNote } from '@/types/book';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useNotebookStore } from '@/store/notebookStore';
|
||||
import useScrollToItem from '../../hooks/useScrollToItem';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import useScrollToItem from '../../hooks/useScrollToItem';
|
||||
|
||||
interface BooknoteItemProps {
|
||||
bookKey: string;
|
||||
@@ -16,6 +17,7 @@ interface BooknoteItemProps {
|
||||
}
|
||||
|
||||
const BooknoteItem: React.FC<BooknoteItemProps> = ({ bookKey, item, editable = false }) => {
|
||||
const _ = useTranslation();
|
||||
const { envConfig } = useEnv();
|
||||
const { settings } = useSettingsStore();
|
||||
const { getConfig, saveConfig, updateBooknotes } = useBookDataStore();
|
||||
@@ -115,7 +117,7 @@ const BooknoteItem: React.FC<BooknoteItemProps> = ({ bookKey, item, editable = f
|
||||
)}
|
||||
onClick={editNote.bind(null, item)}
|
||||
>
|
||||
<div className='align-bottom text-blue-400'>Edit</div>
|
||||
<div className='align-bottom text-blue-400'>{_('Edit')}</div>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
@@ -125,7 +127,7 @@ const BooknoteItem: React.FC<BooknoteItemProps> = ({ bookKey, item, editable = f
|
||||
)}
|
||||
onClick={deleteNote.bind(null, item)}
|
||||
>
|
||||
<div className='align-bottom text-red-400'>Delete</div>
|
||||
<div className='align-bottom text-red-400'>{_('Delete')}</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useEnv } from '@/context/EnvContext';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { BookSearchConfig, BookSearchResult } from '@/types/book';
|
||||
import Dropdown from '@/components/Dropdown';
|
||||
import SearchOptions from './SearchOptions';
|
||||
@@ -24,6 +25,7 @@ const SearchBar: React.FC<SearchBarProps> = ({
|
||||
searchTerm: term,
|
||||
onSearchResultChange,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const { envConfig } = useEnv();
|
||||
const { settings } = useSettingsStore();
|
||||
const { getConfig, saveConfig } = useBookDataStore();
|
||||
@@ -149,7 +151,7 @@ const SearchBar: React.FC<SearchBarProps> = ({
|
||||
value={searchTerm}
|
||||
spellCheck={false}
|
||||
onChange={handleInputChange}
|
||||
placeholder='Search...'
|
||||
placeholder={_('Search...')}
|
||||
className='w-full bg-transparent p-2 font-sans text-sm font-light focus:outline-none'
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { MdCheck } from 'react-icons/md';
|
||||
import { BookSearchConfig } from '@/types/book';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
|
||||
interface SearchOptionsProps {
|
||||
searchConfig: BookSearchConfig;
|
||||
@@ -13,6 +14,7 @@ const SearchOptions: React.FC<SearchOptionsProps> = ({
|
||||
onSearchConfigChanged,
|
||||
setIsDropdownOpen,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const handleSetScope = () => {
|
||||
onSearchConfigChanged({
|
||||
...searchConfig,
|
||||
@@ -46,7 +48,7 @@ const SearchOptions: React.FC<SearchOptionsProps> = ({
|
||||
<span style={{ minWidth: '20px' }}>
|
||||
{searchConfig.scope === 'book' && <MdCheck size={20} className='text-base-content' />}
|
||||
</span>
|
||||
<span className='ml-2'>Book</span>
|
||||
<span className='ml-2'>{_('Book')}</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -60,7 +62,7 @@ const SearchOptions: React.FC<SearchOptionsProps> = ({
|
||||
<MdCheck size={20} className='text-base-content' />
|
||||
)}
|
||||
</span>
|
||||
<span className='ml-2'>Chapter</span>
|
||||
<span className='ml-2'>{_('Chapter')}</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -74,7 +76,7 @@ const SearchOptions: React.FC<SearchOptionsProps> = ({
|
||||
<span style={{ minWidth: '20px' }}>
|
||||
{searchConfig.matchCase && <MdCheck size={20} className='text-base-content' />}
|
||||
</span>
|
||||
<span className='ml-2'>Match Case</span>
|
||||
<span className='ml-2'>{_('Match Case')}</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -86,7 +88,7 @@ const SearchOptions: React.FC<SearchOptionsProps> = ({
|
||||
<span style={{ minWidth: '20px' }}>
|
||||
{searchConfig.matchWholeWords && <MdCheck size={20} className='text-base-content' />}
|
||||
</span>
|
||||
<span className='ml-2'>Match Whole Words</span>
|
||||
<span className='ml-2'>{_('Match Whole Words')}</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -98,7 +100,7 @@ const SearchOptions: React.FC<SearchOptionsProps> = ({
|
||||
<span style={{ minWidth: '20px' }}>
|
||||
{searchConfig.matchDiacritics && <MdCheck size={20} className='text-base-content' />}
|
||||
</span>
|
||||
<span className='ml-2'>Match Diacritics</span>
|
||||
<span className='ml-2'>{_('Match Diacritics')}</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user