add translation keys (#246)

* Fix i18n tsc issue

"t" can return null by default.
This was already disabled in the config, but it had no effect on tsc.

* Fix translation in "DefaultNavBar"

key was missing and since this is a constant the translation wouldn't update in case the language got changed after first load.

* Enable typing for i18n translation keys

* Add translation keys
This commit is contained in:
schroda
2023-03-23 13:59:32 +01:00
committed by GitHub
parent 8c129f2e08
commit d58bd6cd92
56 changed files with 1030 additions and 416 deletions

View File

@@ -30,8 +30,8 @@ export default function Browse() {
scrollButtons
allowScrollButtonsMobile
>
<Tab sx={{ textTransform: 'none' }} label={t('screens.Browse.sources')} />
<Tab sx={{ textTransform: 'none' }} label={t('screens.Browse.extensions')} />
<Tab sx={{ textTransform: 'none' }} label={t('source.title')} />
<Tab sx={{ textTransform: 'none' }} label={t('extension.title')} />
</Tabs>
<TabPanel index={0} currentIndex={tabNum}>
<Sources />

View File

@@ -51,7 +51,7 @@ const DownloadQueue: React.FC = () => {
};
useEffect(() => {
setTitle(t('screens.DownloadQueue.download-queue'));
setTitle(t('download.queue.title'));
setAction(null);
}, []);
@@ -59,7 +59,7 @@ const DownloadQueue: React.FC = () => {
const onDragEnd = (result: DropResult) => {};
if (queue.length === 0) {
return <EmptyView message={t('screens.DownloadQueue.no-downloads')} />;
return <EmptyView message={t('download.queue.label.no_downloads')} />;
}
const handleDelete = (chapter: IChapter) => {

View File

@@ -22,6 +22,7 @@ import { useQueryParam, StringParam } from 'use-query-params';
import { Virtuoso } from 'react-virtuoso';
import { Typography, useMediaQuery, useTheme } from '@mui/material';
import { IExtension } from 'typings';
import { useTranslation } from 'react-i18next';
const LANGUAGE = 0;
const EXTENSIONS = 1;
@@ -66,6 +67,8 @@ function groupExtensions(extensions: IExtension[]) {
}
export default function MangaExtensions() {
const { t } = useTranslation();
const inputRef = useRef<HTMLInputElement>(null);
const { setTitle, setAction } = useContext(NavbarContext);
const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownExtensionLangs', extensionDefaultLangs());
@@ -75,7 +78,7 @@ export default function MangaExtensions() {
const [query] = useQueryParam('query', StringParam);
useEffect(() => {
setTitle('Extensions');
setTitle(t('extension.title'));
setAction(
<>
<AppbarSearch />
@@ -120,18 +123,18 @@ export default function MangaExtensions() {
inputRef.current.value = '';
}
makeToast('Installing Extension File....', 'info');
makeToast(t('extension.label.installing_file'), 'info');
client
.post('/api/v1/extension/install', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
.then(() => {
makeToast('Installed extension successfully!', 'success');
makeToast(t('extension.label.installed_successfully'), 'success');
mutate();
})
.catch(() => makeToast('Extension installion failed!', 'error'));
.catch(() => makeToast(t('extension.label.installation_failed'), 'error'));
} else {
makeToast('invalid file type!', 'error');
makeToast(t('global.error.label.invalid_file_type'), 'error');
}
};

View File

@@ -41,7 +41,7 @@ export default function Library() {
const { setTitle, setAction } = useContext(NavbarContext);
useEffect(() => {
setTitle(t('screens.Library.Library'));
setTitle(t('library.title'));
setAction(
<>
<AppbarSearch />
@@ -62,7 +62,7 @@ export default function Library() {
if (tabsError != null) {
return (
<EmptyView
message={t('screens.Library.could-not-load-categories')}
message={t('category.error.label.request_failure')}
messageExtra={tabsError?.message ?? tabsError}
/>
);
@@ -73,7 +73,7 @@ export default function Library() {
}
if (tabs.length === 0) {
return <EmptyView message={t('screens.Library.your-library-is-empty')} />;
return <EmptyView message={t('library.error.label.empty')} />;
}
if (tabs.length === 1) {
@@ -81,7 +81,7 @@ export default function Library() {
<LibraryMangaGrid
mangas={mangas}
lastLibraryUpdate={lastLibraryUpdate}
message={t('screens.Library.your-library-is-empty') as string}
message={t('library.error.label.empty') as string}
isLoading={activeTab != null && mangaLoading}
/>
);
@@ -112,14 +112,14 @@ export default function Library() {
{tab === activeTab &&
(mangaError ? (
<EmptyView
message={t('screens.Library.could-not-load-manga')}
message={t('manga.error.label.request_failure')}
messageExtra={mangaError?.message ?? mangaError}
/>
) : (
<LibraryMangaGrid
mangas={mangas}
lastLibraryUpdate={lastLibraryUpdate}
message={t('screens.Library.category-is-empty') as string}
message={t('library.error.label.empty') as string}
isLoading={mangaLoading}
/>
))}

View File

@@ -17,6 +17,7 @@ import { NavbarToolbar } from 'components/navbar/DefaultNavBar';
import EmptyView from 'components/util/EmptyView';
import LoadingPlaceholder from 'components/util/LoadingPlaceholder';
import React, { useContext, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { useQuery } from 'util/client';
import { IManga } from 'typings';
@@ -24,6 +25,8 @@ import { IManga } from 'typings';
const AUTOFETCH_AGE = 60 * 60 * 24; // 24 hours
const Manga: React.FC = () => {
const { t } = useTranslation();
const { setTitle } = useContext(NavbarContext);
const { id } = useParams<{ id: string }>();
const autofetchedRef = useRef(false);
@@ -58,11 +61,11 @@ const Manga: React.FC = () => {
}, [manga]);
useEffect(() => {
setTitle(manga?.title ?? 'Manga');
setTitle(manga?.title ?? t('manga.title'));
}, [manga?.title]);
if (error && !manga) {
return <EmptyView message="Could not load manga" messageExtra={error.message ?? error} />;
return <EmptyView message={t('manga.error.label.request_failure')} messageExtra={error.message ?? error} />;
}
return (
<Box sx={{ display: { md: 'flex' }, overflow: 'hidden' }}>
@@ -72,7 +75,7 @@ const Manga: React.FC = () => {
<Tooltip
title={
<>
Could not fetch manga data
{t('manga.error.label.request_failure')}
<br />
{error.message ?? error}
</>

View File

@@ -26,6 +26,7 @@ import {
} from 'util/readerSettings';
import makeToast from 'components/util/Toast';
import { IChapter, IManga, IMangaCard, IPartialChapter, IReaderSettings, ReaderType } from 'typings';
import { useTranslation } from 'react-i18next';
const getReaderComponent = (readerType: ReaderType) => {
switch (readerType) {
@@ -62,6 +63,7 @@ const initialChapter = () => ({
});
export default function Reader() {
const { t } = useTranslation();
const history = useHistory();
const [serverAddress] = useLocalStorage<String>('serverBaseURL', '');
@@ -87,13 +89,13 @@ export default function Reader() {
const setSettingValue = (key: keyof IReaderSettings, value: string | boolean) => {
setSettings({ ...settings, [key]: value });
requestUpdateMangaMetadata(manga, [[key, value]]).catch(() =>
makeToast('Failed to save the reader settings to the server', 'warning'),
makeToast(t('reader.settings.error.label.failed_to_save_settings'), 'warning'),
);
};
useEffect(() => {
if (!manga?.title || (chapter as IChapter)?.name === 'Loading...') {
setTitle(`Reader - Manga ${mangaId} Chapter ${chapterIndex}`);
if (!manga?.title || (chapter as IChapter)?.name === t('global.label.loading')) {
setTitle(t('reader.title'));
} else {
setTitle(`${manga.title}: ${(chapter as IChapter).name}`);
}

View File

@@ -19,6 +19,7 @@ import client from 'util/client';
import { langCodeToName, langSortCmp, sourceDefualtLangs, sourceForcedDefaultLangs } from 'util/language';
import useLocalStorage from 'util/useLocalStorage';
import { ISource } from 'typings';
import { useTranslation } from 'react-i18next';
function sourceToLangList(sources: ISource[]) {
const result: string[] = [];
@@ -34,6 +35,8 @@ function sourceToLangList(sources: ISource[]) {
}
const SearchAll: React.FC = () => {
const { t } = useTranslation();
const [query] = useQueryParam('query', StringParam);
const { setTitle, setAction } = useContext(NavbarContext);
const [triggerUpdate, setTriggerUpdate] = useState<number>(2);
@@ -53,7 +56,7 @@ const SearchAll: React.FC = () => {
const limit = new PQueue({ concurrency: 5 });
useEffect(() => {
setTitle('Global Search');
setTitle(t('search.title.global_search'));
setAction(<AppbarSearch />);
}, []);
@@ -146,7 +149,7 @@ const SearchAll: React.FC = () => {
}, []);
useEffect(() => {
setTitle('Sources');
setTitle(t('source.title'));
setAction(
<>
<AppbarSearch autoOpen />
@@ -210,7 +213,7 @@ const SearchAll: React.FC = () => {
setLastPageNum={setLastPageNum}
horizontal
noFaces
message={fetched[id] ? 'No manga was found!' : undefined}
message={fetched[id] ? t('manga.error.label.no_mangas_found') : undefined}
inLibraryIndicator
/>
</>

View File

@@ -36,11 +36,14 @@ import DarkTheme from 'components/context/DarkTheme';
import useLocalStorage from 'util/useLocalStorage';
import ListItemLink from 'components/util/ListItemLink';
import SearchSettings from 'screens/settings/SearchSettings';
import { useTranslation } from 'react-i18next';
export default function Settings() {
const { t } = useTranslation();
const { setTitle, setAction } = useContext(NavbarContext);
useEffect(() => {
setTitle('Settings');
setTitle(t('settings.title'));
setAction(null);
}, []);
@@ -99,25 +102,25 @@ export default function Settings() {
<ListItemIcon>
<ListAltIcon />
</ListItemIcon>
<ListItemText primary="Categories" />
<ListItemText primary={t('category.title.categories')} />
</ListItemLink>
<ListItemLink to="/settings/defaultReaderSettings">
<ListItemIcon>
<AutoStoriesIcon />
</ListItemIcon>
<ListItemText primary="Default Reader Settings" />
<ListItemText primary={t('reader.settings.title.default_reader_settings')} />
</ListItemLink>
<ListItemLink to="/settings/backup">
<ListItemIcon>
<BackupIcon />
</ListItemIcon>
<ListItemText primary="Backup" />
<ListItemText primary={t('settings.backup.title')} />
</ListItemLink>
<ListItem>
<ListItemIcon>
<Brightness6Icon />
</ListItemIcon>
<ListItemText primary="Dark Theme" />
<ListItemText primary={t('settings.label.dark_theme')} />
<ListItemSecondaryAction>
<Switch edge="end" checked={darkTheme} onChange={() => setDarkTheme(!darkTheme)} />
</ListItemSecondaryAction>
@@ -128,7 +131,7 @@ export default function Settings() {
<ViewModuleIcon />
</ListItemIcon>
<ListItemText
primary="Manga Item width"
primary={t('settings.label.manga_item_width')}
secondary={`px:${ItemWidth}`}
onClick={() => {
handleDialogOpenItemWidth();
@@ -139,7 +142,10 @@ export default function Settings() {
<ListItemIcon>
<FavoriteIcon />
</ListItemIcon>
<ListItemText primary="Show NSFW" secondary="Hide NSFW extensions and sources" />
<ListItemText
primary={t('settings.label.show_nsfw')}
secondary={t('settings.label.show_nsfw_description')}
/>
<ListItemSecondaryAction>
<Switch edge="end" checked={showNsfw} onChange={() => setShowNsfw(!showNsfw)} />
</ListItemSecondaryAction>
@@ -149,9 +155,8 @@ export default function Settings() {
<CachedIcon />
</ListItemIcon>
<ListItemText
primary="Use image cache"
secondary="Disabling image cache makes images load faster if you have a slow disk,
but uses it much more internet traffic in turn"
primary={t('settings.label.image_cache')}
secondary={t('settings.label.image_cache_description')}
/>
<ListItemSecondaryAction>
<Switch edge="end" checked={useCache} onChange={() => setUseCache(!useCache)} />
@@ -161,7 +166,7 @@ export default function Settings() {
<ListItemIcon>
<DnsIcon />
</ListItemIcon>
<ListItemText primary="Server Address" secondary={serverAddress} />
<ListItemText primary={t('settings.about.label.server_address')} secondary={serverAddress} />
<ListItemSecondaryAction>
<IconButton
onClick={() => {
@@ -177,18 +182,18 @@ export default function Settings() {
<ListItemIcon>
<InfoIcon />
</ListItemIcon>
<ListItemText primary="About" />
<ListItemText primary={t('settings.about.title')} />
</ListItemLink>
</List>
<Dialog open={dialogOpen} onClose={handleDialogCancel}>
<DialogContent>
<DialogContentText>Enter Server Address</DialogContentText>
<DialogContentText>{t('settings.server_address.dialog.label.enter_address')}</DialogContentText>
<TextField
autoFocus
margin="dense"
id="name"
label="Server Address"
label={t('settings.about.label.server_address')}
type="text"
fullWidth
value={dialogValue}
@@ -198,16 +203,16 @@ export default function Settings() {
</DialogContent>
<DialogActions>
<Button onClick={handleDialogCancel} color="primary">
Cancel
{t('global.button.cancel')}
</Button>
<Button onClick={handleDialogSubmit} color="primary">
Set
{t('global.button.set')}
</Button>
</DialogActions>
</Dialog>
<Dialog open={dialogOpenItemWidth} onClose={handleDialogCancelItemWidth}>
<DialogTitle>Manga Item width</DialogTitle>
<DialogTitle>{t('settings.label.manga_item_width')}</DialogTitle>
<DialogContent
sx={{
width: '98%',
@@ -236,13 +241,13 @@ export default function Settings() {
</DialogContent>
<DialogActions>
<Button onClick={handleDialogResetItemWidth} color="primary">
Reset to Default
{t('global.button.reset_to_default')}
</Button>
<Button onClick={handleDialogCancelItemWidth} color="primary">
Cancel
{t('global.button.cancel')}
</Button>
<Button onClick={handleDialogSubmitItemWidth} color="primary">
OK
{t('global.button.ok')}
</Button>
</DialogActions>
</Dialog>

View File

@@ -16,6 +16,7 @@ import MultiSelectListPreference from 'components/sourceConfiguration/MultiSelec
import List from '@mui/material/List';
import cloneObject from 'util/cloneObject';
import { SourcePreferences } from 'typings';
import { useTranslation } from 'react-i18next';
function getPrefComponent(type: string) {
switch (type) {
@@ -35,10 +36,11 @@ function getPrefComponent(type: string) {
}
export default function SourceConfigure() {
const { t } = useTranslation();
const { setTitle, setAction } = useContext(NavbarContext);
useEffect(() => {
setTitle('Source Configuration');
setTitle(t('source.configuration.title'));
setAction(null);
}, []);

View File

@@ -18,6 +18,7 @@ import { useQueryParam, StringParam } from 'use-query-params';
import SourceGridLayout from 'components/source/GridLayouts';
import { useLibraryOptionsContext } from 'components/context/LibraryOptionsContext';
import { IManga, IMangaCard, ISource, ISourceFilters } from 'typings';
import { useTranslation } from 'react-i18next';
interface IPos {
position: number;
@@ -26,6 +27,7 @@ interface IPos {
}
export default function SourceMangas({ popular }: { popular: boolean }) {
const { t } = useTranslation();
const { setTitle, setAction } = useContext(NavbarContext);
const history = useHistory();
@@ -58,7 +60,7 @@ export default function SourceMangas({ popular }: { popular: boolean }) {
}
useEffect(() => {
setTitle('Source'); // title is later set after a fetch but we set it here once
setTitle(t('source.title')); // title is later set after a fetch but we set it here once
}, []);
useEffect(() => {
@@ -220,12 +222,14 @@ export default function SourceMangas({ popular }: { popular: boolean }) {
let messageExtra;
if (fetched) {
message = 'No manga was found!';
message = t('manga.error.label.no_mangas_found');
if (sourceId === '0') {
messageExtra = (
<>
<span>Check out </span>
<a href="https://github.com/Suwayomi/Tachidesk-Server/wiki/Local-Source">Local source guide</a>
<span>{t('source.local_source.label.checkout')} </span>
<a href="https://github.com/Suwayomi/Tachidesk-Server/wiki/Local-Source">
{t('source.local_source.label.guide')}
</a>
</>
);
}

View File

@@ -17,6 +17,7 @@ import TravelExploreIcon from '@mui/icons-material/TravelExplore';
import { useHistory } from 'react-router-dom';
import { useQuery } from 'util/client';
import { ISource } from 'typings';
import { useTranslation } from 'react-i18next';
function sourceToLangList(sources: ISource[]) {
const result: string[] = [];
@@ -44,6 +45,7 @@ function groupByLang(sources: ISource[]) {
}
export default function Sources() {
const { t } = useTranslation();
const { setTitle, setAction } = useContext(NavbarContext);
const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownSourceLangs', sourceDefualtLangs());
@@ -70,7 +72,7 @@ export default function Sources() {
}, []);
useEffect(() => {
setTitle('Sources');
setTitle(t('source.title'));
setAction(
<>
<IconButton onClick={() => history.push('/sources/all/search/')} size="large">
@@ -89,7 +91,7 @@ export default function Sources() {
if (loading) return <LoadingPlaceholder />;
if (sources?.length === 0) {
return <h3>No sources found. Install Some Extensions first.</h3>;
return <h3>{t('source.error.label.no_sources_found')}</h3>;
}
return (

View File

@@ -22,6 +22,8 @@ import { Link, useHistory } from 'react-router-dom';
import client from 'util/client';
import useLocalStorage from 'util/useLocalStorage';
import { IChapter, IMangaChapter, IQueue, PaginatedList } from 'typings';
import { useTranslation } from 'react-i18next';
import { t as translate } from 'i18next';
function epochToDate(epoch: number) {
const date = new Date(0); // The 0 there is the key, which sets the date to the epoch
@@ -39,11 +41,11 @@ function isTheSameDay(first: Date, second: Date) {
function getDateString(date: Date) {
const today = new Date();
if (isTheSameDay(today, date)) return 'TODAY';
if (isTheSameDay(today, date)) return translate('global.date.label.today');
// calculate yesterday
const yesterday = new Date();
yesterday.setDate(today.getDate() - 1);
if (isTheSameDay(yesterday, date)) return 'YESTERDAY';
if (isTheSameDay(yesterday, date)) return translate('global.date.label.yesterday');
return date.toLocaleDateString();
}
@@ -70,6 +72,7 @@ const initialQueue = {
} as IQueue;
const Updates: React.FC = () => {
const { t } = useTranslation();
const history = useHistory();
const { setTitle, setAction } = useContext(NavbarContext);
@@ -97,7 +100,7 @@ const Updates: React.FC = () => {
}, []);
useEffect(() => {
setTitle('Updates');
setTitle(t('updates.title'));
setAction(null);
}, []);
@@ -136,7 +139,7 @@ const Updates: React.FC = () => {
return <LoadingPlaceholder />;
}
if (fetched && updateEntries.length === 0) {
return <EmptyView message="You don't have any updates yet." />;
return <EmptyView message={t('updates.error.label.no_updates_available')} />;
}
const downloadForChapter = (chapter: IChapter) => {

View File

@@ -7,12 +7,14 @@ import NavbarContext from 'components/context/NavbarContext';
import LoadingPlaceholder from 'components/util/LoadingPlaceholder';
import { useQuery } from 'util/client';
import { IAbout } from 'typings';
import { useTranslation } from 'react-i18next';
export default function About() {
const { t } = useTranslation();
const { setTitle, setAction } = useContext(NavbarContext);
useEffect(() => {
setTitle('About');
setTitle(t('settings.about.title'));
setAction(null);
}, []);
@@ -32,19 +34,22 @@ export default function About() {
return (
<List>
<ListItem>
<ListItemText primary="Server" secondary={`${about.name} ${about.buildType}`} />
<ListItemText
primary={t('settings.about.label.server')}
secondary={`${about.name} ${about.buildType}`}
/>
</ListItem>
<ListItem>
<ListItemText primary="Server version" secondary={version()} />
<ListItemText primary={t('settings.about.label.server_version')} secondary={version()} />
</ListItem>
<ListItem>
<ListItemText primary="Build time" secondary={buildTime()} />
<ListItemText primary={t('settings.about.label.build_time')} secondary={buildTime()} />
</ListItem>
<ListItemLink to={about.github}>
<ListItemText primary="Github" secondary={about.github} />
<ListItemText primary={t('settings.about.label.github')} secondary={about.github} />
</ListItemLink>
<ListItemLink to={about.discord}>
<ListItemText primary="Discord" secondary={about.discord} />
<ListItemText primary={t('settings.about.label.discord')} secondary={about.discord} />
</ListItemLink>
</List>
);

View File

@@ -14,11 +14,13 @@ import client from 'util/client';
import makeToast from 'components/util/Toast';
import ListItemLink from 'components/util/ListItemLink';
import NavbarContext from 'components/context/NavbarContext';
import { useTranslation } from 'react-i18next';
export default function Backup() {
const { t } = useTranslation();
const { setTitle, setAction } = useContext(NavbarContext);
useEffect(() => {
setTitle('Backup');
setTitle(t('settings.backup.title'));
setAction(null);
}, []);
@@ -29,17 +31,17 @@ export default function Backup() {
const formData = new FormData();
formData.append('backup.proto.gz', file);
makeToast('Restoring backup....', 'info');
makeToast(t('settings.backup.label.restoring_backup'), 'info');
client
.post('/api/v1/backup/import/file', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
.then(() => makeToast('Backup restore finished!', 'success'))
.catch(() => makeToast('Backup restore failed!', 'error'));
.then(() => makeToast(t('settings.backup.label.restored_backup'), 'success'))
.catch(() => makeToast(t('settings.backup.label.backup_restore_failed'), 'error'));
} else if (file.name.toLowerCase().endsWith('json')) {
makeToast('legacy backups are not supported!', 'error');
makeToast(t('settings.backup.label.legacy_backup_unsupported'), 'error');
} else {
makeToast('invalid file type!', 'error');
makeToast(t('global.error.label.invalid_file_type'), 'error');
}
};
@@ -74,12 +76,15 @@ export default function Backup() {
<>
<List sx={{ padding: 0 }}>
<ListItemLink to={`${baseURL}/api/v1/backup/export/file`} directLink>
<ListItemText primary="Create Backup" secondary="Backup library as a Tachiyomi backup" />
<ListItemText
primary={t('settings.backup.label.create_backup')}
secondary={t('settings.backup.label.create_backup_info')}
/>
</ListItemLink>
<ListItem button onClick={() => document.getElementById('backup-file')?.click()}>
<ListItemText
primary="Restore Backup"
secondary="You can also drag and drop the backup file here to restore"
primary={t('settings.backup.label.restore_backup')}
secondary={t('settings.backup.label.restore_backup_info')}
/>
</ListItem>
</List>

View File

@@ -34,6 +34,7 @@ import FormControlLabel from '@mui/material/FormControlLabel';
import NavbarContext from 'components/context/NavbarContext';
import client, { useQuery } from 'util/client';
import { ICategory } from 'typings';
import { useTranslation } from 'react-i18next';
const getItemStyle = (
isDragging: boolean,
@@ -49,9 +50,11 @@ const getItemStyle = (
});
export default function Categories() {
const { t } = useTranslation();
const { setTitle, setAction } = useContext(NavbarContext);
useEffect(() => {
setTitle('Categories');
setTitle(t('category.title.categories'));
setAction(null);
}, []);
@@ -196,14 +199,16 @@ export default function Categories() {
</Fab>
<Dialog open={dialogOpen} onClose={handleDialogCancel}>
<DialogTitle id="form-dialog-title">
{categoryToEdit === -1 ? 'New Catalog' : 'Edit Catalog'}
{categoryToEdit === -1
? t('category.dialog.title.new_category')
: t('category.dialog.title.edit_category')}
</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
id="name"
label="Category Name"
label={t('category.label.category_name')}
type="text"
fullWidth
value={dialogName}
@@ -217,15 +222,15 @@ export default function Categories() {
color="default"
/>
}
label="Default category when adding new manga to library"
label={t('category.label.use_as_default_category')}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleDialogCancel} color="primary">
Cancel
{t('global.button.cancel')}
</Button>
<Button onClick={handleDialogSubmit} color="primary">
Submit
{t('global.button.submit')}
</Button>
</DialogActions>
</Dialog>

View File

@@ -21,11 +21,13 @@ import {
} from 'util/readerSettings';
import ReaderSettingsOptions from 'components/reader/ReaderSettingsOptions';
import { IReaderSettings } from 'typings';
import { useTranslation } from 'react-i18next';
export default function DefaultReaderSettings() {
const { t } = useTranslation();
const { setTitle, setAction } = useContext(NavbarContext);
useEffect(() => {
setTitle('Default Reader Settings');
setTitle(t('reader.settings.title.default_reader_settings'));
setAction(null);
}, []);
@@ -33,7 +35,7 @@ export default function DefaultReaderSettings() {
const setSettingValue = (key: keyof IReaderSettings, value: string | boolean) => {
requestUpdateServerMetadata(metadata ?? {}, [[key, value]]).catch(() =>
makeToast('Failed to save the default reader settings to the server', 'warning'),
makeToast(t('reader.settings.error.label.failed_to_save_settings'), 'warning'),
);
};

View File

@@ -7,13 +7,15 @@ import makeToast from 'components/util/Toast';
import ListItemIcon from '@mui/material/ListItemIcon';
import SearchIcon from '@mui/icons-material/Search';
import { SearchMetadataKeys } from 'typings';
import { useTranslation } from 'react-i18next';
export default function SearchSettings() {
const { t } = useTranslation();
const { metadata, settings } = useSearchSettings();
const setSettingValue = (key: SearchMetadataKeys, value: boolean) => {
requestUpdateServerMetadata(metadata ?? {}, [[key, value]]).catch(() =>
makeToast('Failed to save the default search settings to the server', 'warning'),
makeToast(t('search.error.label.failed_to_save_settings'), 'warning'),
);
};
return (
@@ -21,7 +23,7 @@ export default function SearchSettings() {
<ListItemIcon>
<SearchIcon />
</ListItemIcon>
<ListItemText primary="Ignore Filters when Searching" />
<ListItemText primary={t('search.label.ignore_filters')} />
<ListItemSecondaryAction>
<Switch
edge="end"