Migrate to lingui
Switch to "lingui" for better DX. Tried to persist existing languages as much as possible. Removed "vite-plugin-node-polyfills" because it's incompatible with "lingui"
This commit is contained in:
@@ -9,9 +9,9 @@
|
||||
import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ListSubheader from '@mui/material/ListSubheader';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { ListItemLink } from '@/base/components/lists/ListItemLink.tsx';
|
||||
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
|
||||
@@ -24,9 +24,9 @@ import { epochToDate } from '@/base/utils/DateHelper.ts';
|
||||
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
|
||||
|
||||
export function About() {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
useAppTitle(t('settings.about.title'));
|
||||
useAppTitle(t`About`);
|
||||
|
||||
const { data, loading, error, refetch } = requestManager.useGetAbout({ notifyOnNetworkStatusChange: true });
|
||||
|
||||
@@ -57,7 +57,7 @@ export function About() {
|
||||
if (error) {
|
||||
return (
|
||||
<EmptyViewAbsoluteCentered
|
||||
message={t('global.error.label.failed_to_load_data')}
|
||||
message={t`Unable to load data`}
|
||||
messageExtra={getErrorMessage(error)}
|
||||
retry={() => refetch().catch(defaultPromiseErrorHandler('About::refetch'))}
|
||||
/>
|
||||
@@ -78,19 +78,16 @@ export function About() {
|
||||
sx={{ padding: 0 }}
|
||||
subheader={
|
||||
<ListSubheader component="div" id="about-server-info">
|
||||
{t('settings.server.title.server')}
|
||||
{t`Server`}
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('settings.server.title.server')}
|
||||
secondary={`${aboutServer.name} (${aboutServer.buildType})`}
|
||||
/>
|
||||
<ListItemText primary={t`Server`} secondary={`${aboutServer.name} (${aboutServer.buildType})`} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('settings.about.server.label.version')}
|
||||
primary={t`Server version`}
|
||||
secondary={
|
||||
<VersionInfo
|
||||
version={aboutServer.version}
|
||||
@@ -106,7 +103,7 @@ export function About() {
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('settings.about.server.label.build_time')}
|
||||
primary={t`Build time`}
|
||||
secondary={epochToDate(Number(aboutServer.buildTime)).toString()}
|
||||
/>
|
||||
</ListItem>
|
||||
@@ -116,19 +113,16 @@ export function About() {
|
||||
sx={{ padding: 0 }}
|
||||
subheader={
|
||||
<ListSubheader component="div" id="about-webui-info">
|
||||
{t('settings.webui.title.webui')}
|
||||
{t`WebUI`}
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('settings.about.webui.label.channel')}
|
||||
secondary={aboutWebUI.channel.toLocaleUpperCase()}
|
||||
/>
|
||||
<ListItemText primary={t`WebUI channel`} secondary={aboutWebUI.channel.toLocaleUpperCase()} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('settings.about.webui.label.version')}
|
||||
primary={t`WebUI version`}
|
||||
secondary={
|
||||
<VersionInfo
|
||||
version={aboutWebUI.tag}
|
||||
@@ -152,21 +146,18 @@ export function About() {
|
||||
<List
|
||||
subheader={
|
||||
<ListSubheader component="div" id="about-links">
|
||||
{t('global.label.links')}
|
||||
{t`Links`}
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<ListItemLink to={aboutServer.github} target="_blank" rel="noreferrer">
|
||||
<ListItemText primary={t('settings.about.server.label.github')} secondary={aboutServer.github} />
|
||||
<ListItemText primary={t`GitHub Server`} secondary={aboutServer.github} />
|
||||
</ListItemLink>
|
||||
<ListItemLink to="https://github.com/Suwayomi/Suwayomi-WebUI" target="_blank" rel="noreferrer">
|
||||
<ListItemText
|
||||
primary={t('settings.about.webui.label.github')}
|
||||
secondary="https://github.com/Suwayomi/Suwayomi-WebUI"
|
||||
/>
|
||||
<ListItemText primary={t`GitHub WebUI`} secondary="https://github.com/Suwayomi/Suwayomi-WebUI" />
|
||||
</ListItemLink>
|
||||
<ListItemLink to={aboutServer.discord} target="_blank" rel="noreferrer">
|
||||
<ListItemText primary={t('global.label.discord')} secondary={aboutServer.discord} />
|
||||
<ListItemText primary={t`Discord`} secondary={aboutServer.discord} />
|
||||
</ListItemLink>
|
||||
</List>
|
||||
</List>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
@@ -15,11 +14,12 @@ import ListSubheader from '@mui/material/ListSubheader';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import Link from '@mui/material/Link';
|
||||
import { useColorScheme } from '@mui/material/styles';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useAppThemeContext } from '@/features/theme/AppThemeContext.tsx';
|
||||
import { Select } from '@/base/components/inputs/Select.tsx';
|
||||
import { MediaQuery } from '@/base/utils/MediaQuery.tsx';
|
||||
import { NumberSetting } from '@/base/components/settings/NumberSetting.tsx';
|
||||
import { I18nResourceCode, i18nResources } from '@/i18n';
|
||||
import { I18nResourceCode, i18nResources, loadCatalog } from '@/i18n';
|
||||
import { languageCodeToName } from '@/base/utils/Languages.ts';
|
||||
import { ThemeList } from '@/features/theme/components/ThemeList.tsx';
|
||||
import {
|
||||
@@ -38,19 +38,19 @@ import { MANGA_GRID_WIDTH, SERVER_SETTINGS_METADATA_DEFAULT } from '@/features/s
|
||||
import { MUI_THEME_MODE_KEY } from '@/lib/mui/MUI.constants.ts';
|
||||
|
||||
export const Appearance = () => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const { t, i18n } = useLingui();
|
||||
const { themeMode, setThemeMode, shouldUsePureBlackMode, setShouldUsePureBlackMode } = useAppThemeContext();
|
||||
const { mode, setMode } = useColorScheme();
|
||||
const actualThemeMode = (mode ?? themeMode) as ThemeMode;
|
||||
|
||||
useAppTitle(t('settings.appearance.title'));
|
||||
useAppTitle(t`Appearance`);
|
||||
|
||||
const {
|
||||
settings: { mangaThumbnailBackdrop, mangaDynamicColorSchemes, mangaGridItemWidth },
|
||||
request: { loading, error, refetch },
|
||||
} = useMetadataServerSettings();
|
||||
const updateMetadataSetting = createUpdateMetadataServerSettings<keyof MetadataThemeSettings>((e) =>
|
||||
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
|
||||
makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)),
|
||||
);
|
||||
|
||||
const isDarkMode = MediaQuery.getThemeMode(actualThemeMode) === ThemeMode.DARK;
|
||||
@@ -62,7 +62,7 @@ export const Appearance = () => {
|
||||
if (error) {
|
||||
return (
|
||||
<EmptyViewAbsoluteCentered
|
||||
message={t('global.error.label.failed_to_load_data')}
|
||||
message={t`Unable to load data`}
|
||||
messageExtra={getErrorMessage(error)}
|
||||
retry={() => refetch().catch(defaultPromiseErrorHandler('Appearance::refetch'))}
|
||||
/>
|
||||
@@ -73,12 +73,12 @@ export const Appearance = () => {
|
||||
<List
|
||||
subheader={
|
||||
<ListSubheader component="div" id="appearance-theme">
|
||||
{t('settings.appearance.theme.title')}
|
||||
{t`Theme`}
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<ListItem>
|
||||
<ListItemText primary={t('settings.appearance.theme.mode')} />
|
||||
<ListItemText primary={t`Theme mode`} />
|
||||
<Select<ThemeMode>
|
||||
value={actualThemeMode}
|
||||
onChange={(e) => {
|
||||
@@ -91,20 +91,20 @@ export const Appearance = () => {
|
||||
}}
|
||||
>
|
||||
<MenuItem key={ThemeMode.SYSTEM} value={ThemeMode.SYSTEM}>
|
||||
{t('global.label.system')}
|
||||
{t`System`}
|
||||
</MenuItem>
|
||||
<MenuItem key={ThemeMode.DARK} value={ThemeMode.DARK}>
|
||||
{t('global.label.dark')}
|
||||
{t`Dark`}
|
||||
</MenuItem>
|
||||
<MenuItem key={ThemeMode.LIGHT} value={ThemeMode.LIGHT}>
|
||||
{t('global.label.light')}
|
||||
{t`Light`}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</ListItem>
|
||||
<ThemeList />
|
||||
{isDarkMode && (
|
||||
<ListItem>
|
||||
<ListItemText primary={t('settings.appearance.theme.pure_black_mode')} />
|
||||
<ListItemText primary={t`Pure black dark mode`} />
|
||||
<Switch
|
||||
checked={shouldUsePureBlackMode}
|
||||
onChange={(_, enabled) => setShouldUsePureBlackMode(enabled)}
|
||||
@@ -114,33 +114,31 @@ export const Appearance = () => {
|
||||
<List
|
||||
subheader={
|
||||
<ListSubheader component="div" id="appearance-theme">
|
||||
{t('global.label.display')}
|
||||
{t`Display`}
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('global.language.label.language')}
|
||||
primary={t`Language`}
|
||||
secondary={
|
||||
<>
|
||||
<span>{t('settings.label.language_description')} </span>
|
||||
<span>{t`Feel free to translate the project on`} </span>
|
||||
<Link
|
||||
href="https://hosted.weblate.org/projects/suwayomi/suwayomi-webui"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t('global.language.title.weblate')}
|
||||
{t`Weblate`}
|
||||
</Link>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Select
|
||||
value={i18nResources.includes(i18n.language as I18nResourceCode) ? i18n.language : 'en'}
|
||||
value={i18nResources.includes(i18n.locale as I18nResourceCode) ? i18n.locale : 'en'}
|
||||
onChange={({ target: { value: language } }) =>
|
||||
i18n.changeLanguage(language, (e) => {
|
||||
if (e) {
|
||||
makeToast(t('global.language.error.load'), 'error', getErrorMessage(e));
|
||||
}
|
||||
loadCatalog(language).catch((e: Error) => {
|
||||
makeToast(t`Could not load language`, 'error', getErrorMessage(e));
|
||||
})
|
||||
}
|
||||
>
|
||||
@@ -152,7 +150,7 @@ export const Appearance = () => {
|
||||
</Select>
|
||||
</ListItem>
|
||||
<NumberSetting
|
||||
settingTitle={t('settings.label.manga_item_width')}
|
||||
settingTitle={t`Manga item width`}
|
||||
settingValue={`px: ${mangaGridItemWidth}`}
|
||||
value={mangaGridItemWidth}
|
||||
defaultValue={SERVER_SETTINGS_METADATA_DEFAULT.mangaGridItemWidth}
|
||||
@@ -166,8 +164,8 @@ export const Appearance = () => {
|
||||
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('settings.appearance.manga_thumbnail_backdrop.title')}
|
||||
secondary={t('settings.appearance.manga_thumbnail_backdrop.description')}
|
||||
primary={t`Manga thumbnail as background`}
|
||||
secondary={t`Sets the manga thumbnail as the background image on the manga page`}
|
||||
/>
|
||||
<Switch
|
||||
edge="end"
|
||||
@@ -178,8 +176,8 @@ export const Appearance = () => {
|
||||
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('settings.appearance.manga_dynamic_color_schemes.title')}
|
||||
secondary={t('settings.appearance.manga_dynamic_color_schemes.description')}
|
||||
primary={t`Dynamic theme colors on manga page`}
|
||||
secondary={t`Changes the theme colors on the manga page based on the thumbnail`}
|
||||
/>
|
||||
<Switch
|
||||
edge="end"
|
||||
|
||||
@@ -6,15 +6,14 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useState } from 'react';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Button from '@mui/material/Button';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import {
|
||||
IMAGE_PROCESSING_TYPE_TO_SETTING,
|
||||
IMAGE_PROCESSING_TYPE_TO_TRANSLATION,
|
||||
TARGET_DISABLED,
|
||||
} from '@/features/settings/Settings.constants.ts';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { ImageProcessingType, ServerSettings } from '@/features/settings/Settings.types.ts';
|
||||
@@ -37,7 +36,7 @@ import {
|
||||
import { Processing } from '@/features/settings/components/images/Processing.tsx';
|
||||
|
||||
export const ImageProcessingSetting = ({ type }: { type: ImageProcessingType }) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
useAppTitle(t(IMAGE_PROCESSING_TYPE_TO_TRANSLATION[type]));
|
||||
|
||||
@@ -53,7 +52,7 @@ export const ImageProcessingSetting = ({ type }: { type: ImageProcessingType })
|
||||
if (error) {
|
||||
return (
|
||||
<EmptyViewAbsoluteCentered
|
||||
message={t('global.error.label.failed_to_load_data')}
|
||||
message={t`Unable to load data`}
|
||||
messageExtra={getErrorMessage(error)}
|
||||
retry={() => refetch().catch(defaultPromiseErrorHandler('ImageProcessingSetting::refetch'))}
|
||||
/>
|
||||
@@ -78,7 +77,7 @@ export const ImageProcessingSetting = ({ type }: { type: ImageProcessingType })
|
||||
|
||||
const updateSetting = (value: ServerSettings[typeof settingKey]): Promise<any> => {
|
||||
const mutation = mutateSettings({ variables: { input: { settings: { [settingKey]: value } } } });
|
||||
mutation.catch((e) => makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)));
|
||||
mutation.catch((e) => makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)));
|
||||
|
||||
return mutation;
|
||||
};
|
||||
@@ -93,7 +92,9 @@ export const ImageProcessingSetting = ({ type }: { type: ImageProcessingType })
|
||||
|
||||
return (
|
||||
<Stack sx={{ p: 2, gap: 5 }}>
|
||||
<Typography>{t('download.settings.conversion.description', { value: TARGET_DISABLED })}</Typography>
|
||||
<Typography>
|
||||
{t`In case no MIME-Type is defined, the "default" one will be used for the processing.\nSet the target mode to disabled to prevent processing for a MIME-Type`}
|
||||
</Typography>
|
||||
<Stack sx={{ flexDirection: 'column', gap: 5 }}>
|
||||
{tmpConversions.map((conversion, index) => {
|
||||
const { mimeType } = conversion;
|
||||
@@ -140,10 +141,10 @@ export const ImageProcessingSetting = ({ type }: { type: ImageProcessingType })
|
||||
]);
|
||||
}}
|
||||
>
|
||||
{t('global.button.add')}
|
||||
{t`Add`}
|
||||
</Button>
|
||||
<Button variant="contained" disabled={hasInvalidConversion || !hasChanged} onClick={onSubmit}>
|
||||
{t('global.button.save')}
|
||||
{t`Save`}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import List from '@mui/material/List';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import ListSubheader from '@mui/material/ListSubheader';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
|
||||
import { ListItemLink } from '@/base/components/lists/ListItemLink.tsx';
|
||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||
@@ -20,18 +20,18 @@ import { makeToast } from '@/base/utils/Toast.ts';
|
||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||
|
||||
export const ImagesSettings = () => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
useAppTitle(t('settings.images.title'));
|
||||
useAppTitle(t`Images`);
|
||||
|
||||
const [triggerClearServerCache, { loading: isClearingServerCache }] = requestManager.useClearServerCache();
|
||||
|
||||
const clearCache = async () => {
|
||||
try {
|
||||
await Promise.all([triggerClearServerCache(), ImageCache.clear()]);
|
||||
makeToast(t('settings.clear_cache.label.success'), 'success');
|
||||
makeToast(t`Cleared the cache`, 'success');
|
||||
} catch (e) {
|
||||
makeToast(t('settings.clear_cache.label.failure'), 'error', getErrorMessage(e));
|
||||
makeToast(t`Could not clear the cache`, 'error', getErrorMessage(e));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,22 +39,22 @@ export const ImagesSettings = () => {
|
||||
<List sx={{ pt: 0 }}>
|
||||
<ListItemButton disabled={isClearingServerCache} onClick={clearCache}>
|
||||
<ListItemText
|
||||
primary={t('settings.clear_cache.label.title')}
|
||||
secondary={t('settings.clear_cache.label.description')}
|
||||
primary={t`Clear cache`}
|
||||
secondary={t`The cache of the client (browser, electron) should get cleared alongside it, otherwise, the client cache will keep getting used`}
|
||||
/>
|
||||
</ListItemButton>
|
||||
<List
|
||||
subheader={
|
||||
<ListSubheader component="div" id="image-processing-settings">
|
||||
{t('settings.images.processing.title')}
|
||||
{t`Image processing`}
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.images.childRoutes.processingDownloads.path}>
|
||||
<ListItemText primary={t('download.settings.conversion.title')} />
|
||||
<ListItemText primary={t`Image download processing`} />
|
||||
</ListItemLink>
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.images.childRoutes.processingServe.path}>
|
||||
<ListItemText primary={t('settings.images.processing.serve.title')} />
|
||||
<ListItemText primary={t`Image serve processing`} />
|
||||
</ListItemLink>
|
||||
</List>
|
||||
</List>
|
||||
|
||||
@@ -7,12 +7,13 @@
|
||||
*/
|
||||
|
||||
import { Fragment } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import List from '@mui/material/List';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import ListAltIcon from '@mui/icons-material/ListAlt';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||
import { ListItemLink } from '@/base/components/lists/ListItemLink.tsx';
|
||||
import { NAVIGATION_BAR_ITEMS } from '@/features/navigation-bar/NavigationBar.constants.ts';
|
||||
@@ -23,10 +24,10 @@ import { NavbarItem, NavBarItemMoreGroup } from '@/features/navigation-bar/Navig
|
||||
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
|
||||
|
||||
export const More = () => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
const isMobileWidth = MediaQuery.useIsMobileWidth();
|
||||
|
||||
useAppTitle(t('global.label.more'));
|
||||
useAppTitle(t`More`);
|
||||
|
||||
const {
|
||||
settings: { hideHistory },
|
||||
@@ -46,7 +47,7 @@ export const More = () => {
|
||||
...(hiddenNavBarItemsByMoreGroup[NavBarItemMoreGroup.HIDDEN_ITEM] ?? []),
|
||||
{
|
||||
path: AppRoutes.settings.childRoutes.categories.path,
|
||||
title: 'category.title.category_other',
|
||||
title: msg`Categories`,
|
||||
SelectedIconComponent: ListAltIcon,
|
||||
IconComponent: ListAltIcon,
|
||||
show: 'both',
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useMemo } from 'react';
|
||||
import Link from '@mui/material/Link';
|
||||
import List from '@mui/material/List';
|
||||
@@ -14,8 +13,9 @@ import ListItem from '@mui/material/ListItem';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import ListSubheader from '@mui/material/ListSubheader';
|
||||
import { t as translate } from 'i18next';
|
||||
import { d } from 'koration';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { plural, t as translate } from '@lingui/core/macro';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { TextSetting } from '@/base/components/settings/text/TextSetting.tsx';
|
||||
import { NumberSetting } from '@/base/components/settings/NumberSetting.tsx';
|
||||
@@ -44,16 +44,19 @@ import { KoreaderSyncSettings } from '@/features/settings/components/koreaderSyn
|
||||
|
||||
const getLogFilesCleanupDisplayValue = (ttl: number): string => {
|
||||
if (ttl === 0) {
|
||||
return translate('global.label.never');
|
||||
return translate`Never`;
|
||||
}
|
||||
|
||||
return translate('settings.server.misc.log_files.file_cleanup.value', { days: ttl, count: ttl });
|
||||
return plural(ttl, {
|
||||
one: 'Delete log files that are older than # day',
|
||||
other: 'Delete log files that are older than # days',
|
||||
});
|
||||
};
|
||||
|
||||
export const ServerSettings = () => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
useAppTitle(t('settings.server.title.server'));
|
||||
useAppTitle(t`Server`);
|
||||
|
||||
const {
|
||||
settings: { serverInformAvailableUpdate },
|
||||
@@ -62,7 +65,7 @@ export const ServerSettings = () => {
|
||||
} = useMetadataServerSettings();
|
||||
const updateMetadataServerSettings = createUpdateMetadataServerSettings<
|
||||
keyof Pick<MetadataUpdateSettings, 'serverInformAvailableUpdate'>
|
||||
>((e) => makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)));
|
||||
>((e) => makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)));
|
||||
|
||||
const {
|
||||
data,
|
||||
@@ -85,7 +88,7 @@ export const ServerSettings = () => {
|
||||
await mutateSettings({ variables: { input: { settings: { [setting]: value } } } });
|
||||
onCompletion?.(true);
|
||||
} catch (e) {
|
||||
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e));
|
||||
makeToast(t`Failed to save changes`, 'error', getErrorMessage(e));
|
||||
onCompletion?.(false);
|
||||
}
|
||||
};
|
||||
@@ -95,15 +98,15 @@ export const ServerSettings = () => {
|
||||
<List
|
||||
subheader={
|
||||
<ListSubheader component="div" id="server-settings-client">
|
||||
{t('global.label.client')}
|
||||
{t`Client`}
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<ServerAddressSetting />
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('global.update.settings.inform.label.title')}
|
||||
secondary={t('global.update.settings.inform.label.description')}
|
||||
primary={t`Inform about available update`}
|
||||
secondary={t`Shows a dialog in case a new version is available`}
|
||||
/>
|
||||
<Switch
|
||||
edge="end"
|
||||
@@ -132,7 +135,7 @@ export const ServerSettings = () => {
|
||||
<>
|
||||
{localSettings}
|
||||
<EmptyViewAbsoluteCentered
|
||||
message={t('global.error.label.failed_to_load_data')}
|
||||
message={t`Unable to load data`}
|
||||
messageExtra={getErrorMessage(error)}
|
||||
retry={() => {
|
||||
if (metadataServerSettingsError) {
|
||||
@@ -169,34 +172,34 @@ export const ServerSettings = () => {
|
||||
<List
|
||||
subheader={
|
||||
<ListSubheader component="div" id="server-settings-server-address">
|
||||
{t('settings.server.address.server.title')}
|
||||
{t`Server bindings`}
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<TextSetting
|
||||
settingName={t('settings.server.address.server.label.ip')}
|
||||
settingName={t`IP`}
|
||||
handleChange={(ip) => updateSetting('ip', ip)}
|
||||
value={serverSettings.ip}
|
||||
placeholder="0.0.0.0"
|
||||
/>
|
||||
<NumberSetting
|
||||
settingTitle={t('settings.server.address.server.label.port')}
|
||||
settingTitle={t`Port`}
|
||||
settingValue={serverSettings.port.toString()}
|
||||
handleUpdate={(port) => updateSetting('port', port)}
|
||||
value={serverSettings.port}
|
||||
defaultValue={4567}
|
||||
valueUnit={t('settings.server.address.server.label.port')}
|
||||
valueUnit={t`Port`}
|
||||
/>
|
||||
</List>
|
||||
<List
|
||||
subheader={
|
||||
<ListSubheader component="div" id="server-settings-socks-proxy">
|
||||
{t('settings.server.socks_proxy.title')}
|
||||
{t`SOCKS proxy`}
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<ListItem>
|
||||
<ListItemText primary={t('settings.server.socks_proxy.label.enable')} />
|
||||
<ListItemText primary={t`Use SOCKS proxy`} />
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={serverSettings.socksProxyEnabled}
|
||||
@@ -204,7 +207,7 @@ export const ServerSettings = () => {
|
||||
/>
|
||||
</ListItem>
|
||||
<SelectSetting<number>
|
||||
settingName={t('settings.server.socks_proxy.label.version')}
|
||||
settingName={t`SOCKS version`}
|
||||
value={serverSettings.socksProxyVersion}
|
||||
values={[
|
||||
[4, { text: '4' }],
|
||||
@@ -213,22 +216,22 @@ export const ServerSettings = () => {
|
||||
handleChange={(socksProxyVersion) => updateSetting('socksProxyVersion', socksProxyVersion)}
|
||||
/>
|
||||
<TextSetting
|
||||
settingName={t('settings.server.socks_proxy.label.host')}
|
||||
settingName={t`SOCKS host`}
|
||||
value={serverSettings.socksProxyHost}
|
||||
handleChange={(proxyHost) => updateSetting('socksProxyHost', proxyHost)}
|
||||
/>
|
||||
<TextSetting
|
||||
settingName={t('settings.server.socks_proxy.label.port')}
|
||||
settingName={t`SOCKS port`}
|
||||
value={serverSettings.socksProxyPort}
|
||||
handleChange={(proxyPort) => updateSetting('socksProxyPort', proxyPort)}
|
||||
/>
|
||||
<TextSetting
|
||||
settingName={t('settings.server.socks_proxy.label.username')}
|
||||
settingName={t`SOCKS username`}
|
||||
value={serverSettings.socksProxyUsername}
|
||||
handleChange={(proxyUsername) => updateSetting('socksProxyUsername', proxyUsername)}
|
||||
/>
|
||||
<TextSetting
|
||||
settingName={t('settings.server.socks_proxy.label.password')}
|
||||
settingName={t`SOCKS password`}
|
||||
value={serverSettings.socksProxyPassword}
|
||||
handleChange={(proxyPassword) => updateSetting('socksProxyPassword', proxyPassword)}
|
||||
isPassword
|
||||
@@ -237,12 +240,12 @@ export const ServerSettings = () => {
|
||||
<List
|
||||
subheader={
|
||||
<ListSubheader component="div" id="server-settings-auth">
|
||||
{t('settings.server.auth.title')}
|
||||
{t`Authentication`}
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<SelectSetting<AuthMode>
|
||||
settingName={t('settings.server.auth.label.title')}
|
||||
settingName={t`Authentication Mode`}
|
||||
value={serverSettings.authMode}
|
||||
values={AUTH_MODES_SELECT_VALUES}
|
||||
handleChange={(mode) => {
|
||||
@@ -261,13 +264,13 @@ export const ServerSettings = () => {
|
||||
disabled={authModeDisabled}
|
||||
/>
|
||||
<TextSetting
|
||||
settingName={t('settings.server.auth.label.username')}
|
||||
settingName={t`Username`}
|
||||
value={serverSettings.authUsername}
|
||||
validate={(value) => serverSettings.authMode === AuthMode.None || !!value.trim()}
|
||||
handleChange={(authUsername) => updateSetting('authUsername', authUsername)}
|
||||
/>
|
||||
<TextSetting
|
||||
settingName={t('settings.server.auth.label.password')}
|
||||
settingName={t`Password`}
|
||||
value={serverSettings.authPassword}
|
||||
isPassword
|
||||
validate={(value) => serverSettings.authMode === AuthMode.None || !!value.trim()}
|
||||
@@ -276,15 +279,15 @@ export const ServerSettings = () => {
|
||||
{serverSettings.authMode === AuthMode.UiLogin && (
|
||||
<>
|
||||
<TextSetting
|
||||
settingName={t('settings.server.auth.jwt.audience')}
|
||||
settingName={t`JWT audience claim`}
|
||||
value={serverSettings.jwtAudience}
|
||||
handleChange={(audience) => updateSetting('jwtAudience', audience)}
|
||||
/>
|
||||
<NumberSetting
|
||||
settingTitle={t('settings.server.auth.jwt.access_token_expiry')}
|
||||
settingTitle={t`JWT access token expiry`}
|
||||
settingValue={d(serverSettings.jwtTokenExpiry).minutes.humanize()}
|
||||
value={d(serverSettings.jwtTokenExpiry).minutes.inWholeMinutes}
|
||||
valueUnit={t('global.time.minutes.minute_other')}
|
||||
valueUnit={t`Minute`}
|
||||
defaultValue={JWT_ACCESS_TOKEN_EXPIRY.default}
|
||||
minValue={JWT_ACCESS_TOKEN_EXPIRY.min}
|
||||
maxValue={JWT_ACCESS_TOKEN_EXPIRY.max}
|
||||
@@ -292,10 +295,10 @@ export const ServerSettings = () => {
|
||||
showSlider
|
||||
/>
|
||||
<NumberSetting
|
||||
settingTitle={t('settings.server.auth.jwt.refresh_token_expiry')}
|
||||
settingTitle={t`JWT refresh token expiry`}
|
||||
settingValue={d(serverSettings.jwtRefreshExpiry).days.humanize()}
|
||||
value={d(serverSettings.jwtRefreshExpiry).days.inWholeDays}
|
||||
valueUnit={t('global.time.days.day_other')}
|
||||
valueUnit={t`Day`}
|
||||
defaultValue={JWT_REFRESH_TOKEN_EXPIRY.default}
|
||||
minValue={JWT_REFRESH_TOKEN_EXPIRY.min}
|
||||
maxValue={JWT_REFRESH_TOKEN_EXPIRY.max}
|
||||
@@ -308,15 +311,15 @@ export const ServerSettings = () => {
|
||||
<List
|
||||
subheader={
|
||||
<ListSubheader component="div" id="server-settings-clouadflare-bypass">
|
||||
{t('settings.server.cloudflare.title')}
|
||||
{t`Cloudflare bypass`}
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('settings.server.cloudflare.flaresolverr.enabled.label.title')}
|
||||
primary={t`FlareSolverr enabled`}
|
||||
secondary={
|
||||
<Trans i18nKey="settings.server.cloudflare.flaresolverr.enabled.label.description">
|
||||
<Trans>
|
||||
See{' '}
|
||||
<Link
|
||||
href="https://github.com/FlareSolverr/FlareSolverr?tab=readme-ov-file#installation"
|
||||
@@ -336,46 +339,52 @@ export const ServerSettings = () => {
|
||||
/>
|
||||
</ListItem>
|
||||
<TextSetting
|
||||
settingName={t('settings.server.cloudflare.flaresolverr.url.label.title')}
|
||||
dialogDescription={t('settings.server.cloudflare.flaresolverr.url.label.description')}
|
||||
settingName={t`FlareSolverr server url`}
|
||||
dialogDescription={t`The address of the FlareSolverr server`}
|
||||
value={serverSettings.flareSolverrUrl}
|
||||
handleChange={(url) => updateSetting('flareSolverrUrl', url)}
|
||||
/>
|
||||
<NumberSetting
|
||||
settingTitle={t('settings.server.cloudflare.flaresolverr.timeout.label.title')}
|
||||
settingValue={t('global.time.seconds.value', { count: serverSettings.flareSolverrTimeout })}
|
||||
dialogDescription={t('settings.server.cloudflare.flaresolverr.timeout.label.description')}
|
||||
settingTitle={t`FlareSolverr request timeout`}
|
||||
settingValue={plural(serverSettings.flareSolverrTimeout, {
|
||||
one: '# second',
|
||||
other: '# seconds',
|
||||
})}
|
||||
dialogDescription={t`How much time FlareSolverr has to handle the request`}
|
||||
value={serverSettings.flareSolverrTimeout}
|
||||
defaultValue={60}
|
||||
minValue={20}
|
||||
maxValue={60 * 5}
|
||||
stepSize={1}
|
||||
showSlider
|
||||
valueUnit={t('global.time.seconds.second_other')}
|
||||
valueUnit={t`Second`}
|
||||
handleUpdate={(timeout) => updateSetting('flareSolverrTimeout', timeout)}
|
||||
/>
|
||||
<TextSetting
|
||||
settingName={t('settings.server.cloudflare.flaresolverr.session.name.label.title')}
|
||||
settingName={t`FlareSolverr session name`}
|
||||
value={serverSettings.flareSolverrSessionName}
|
||||
handleChange={(sessionName) => updateSetting('flareSolverrSessionName', sessionName)}
|
||||
/>
|
||||
<NumberSetting
|
||||
settingTitle={t('settings.server.cloudflare.flaresolverr.session.ttl.label.title')}
|
||||
settingValue={t('global.time.minutes.value', { count: serverSettings.flareSolverrSessionTtl })}
|
||||
dialogDescription={t('settings.server.cloudflare.flaresolverr.session.ttl.label.description')}
|
||||
settingTitle={t`FlareSolverr session TTL`}
|
||||
settingValue={plural(serverSettings.flareSolverrSessionTtl, {
|
||||
one: '# minute',
|
||||
other: '# minutes',
|
||||
})}
|
||||
dialogDescription={t`FlareSolverr will automatically rotate expired sessions based on the TTL provided in minutes`}
|
||||
value={serverSettings.flareSolverrSessionTtl}
|
||||
defaultValue={15}
|
||||
minValue={1}
|
||||
maxValue={60}
|
||||
stepSize={1}
|
||||
showSlider
|
||||
valueUnit={t('global.time.minutes.minute_other')}
|
||||
valueUnit={t`Minute`}
|
||||
handleUpdate={(sessionTTL) => updateSetting('flareSolverrSessionTtl', sessionTTL)}
|
||||
/>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('settings.server.cloudflare.flaresolverr.response_fallback.label.title')}
|
||||
secondary={t('settings.server.cloudflare.flaresolverr.response_fallback.label.description')}
|
||||
primary={t`Response fallback`}
|
||||
secondary={t`Use the FlareSolverr response in case the server runs into a Cloudflare challenge while FlareSolverr does not and is therefore unable to solve the challenge (does not work for images)`}
|
||||
/>
|
||||
<Switch
|
||||
edge="end"
|
||||
@@ -387,14 +396,14 @@ export const ServerSettings = () => {
|
||||
<List
|
||||
subheader={
|
||||
<ListSubheader component="div" id="server-settings-opds">
|
||||
{t('settings.server.opds.title')}
|
||||
{t`OPDS`}
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('settings.server.opds.binary_file_sizes.label.title')}
|
||||
secondary={t('settings.server.opds.binary_file_sizes.label.description')}
|
||||
primary={t`Binary file size`}
|
||||
secondary={t`Display file sizes in binary (KiB, MiB, GiB) instead of decimal (KB, MB, GB)`}
|
||||
/>
|
||||
<Switch
|
||||
edge="end"
|
||||
@@ -403,22 +412,22 @@ export const ServerSettings = () => {
|
||||
/>
|
||||
</ListItem>
|
||||
<NumberSetting
|
||||
settingTitle={t('settings.server.opds.items_per_page.label.title')}
|
||||
settingTitle={t`Items per page`}
|
||||
settingValue={serverSettings.opdsItemsPerPage.toString()}
|
||||
dialogDescription={t('settings.server.opds.items_per_page.label.description')}
|
||||
dialogDescription={t`Number of items per page in OPDS feeds (e.g., Library History, Manga Chapters).\nHigher values may affect client performance.`}
|
||||
value={serverSettings.opdsItemsPerPage}
|
||||
defaultValue={50}
|
||||
minValue={10}
|
||||
maxValue={5000}
|
||||
stepSize={10}
|
||||
showSlider
|
||||
valueUnit={t('settings.server.opds.items_per_page.label.unit_other')}
|
||||
valueUnit={t`item`}
|
||||
handleUpdate={(value) => updateSetting('opdsItemsPerPage', value)}
|
||||
/>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('settings.server.opds.enable_page_read_progress.label.title')}
|
||||
secondary={t('settings.server.opds.enable_page_read_progress.label.description')}
|
||||
primary={t`Enable page read progress`}
|
||||
secondary={t`Track and update your reading progress by page for each chapter during page streaming`}
|
||||
/>
|
||||
<Switch
|
||||
edge="end"
|
||||
@@ -428,8 +437,8 @@ export const ServerSettings = () => {
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('settings.server.opds.mark_as_read_on_download.label.title')}
|
||||
secondary={t('settings.server.opds.mark_as_read_on_download.label.description')}
|
||||
primary={t`Mark chapters as read on download`}
|
||||
secondary={t`Automatically mark chapters as read when you download them.`}
|
||||
/>
|
||||
<Switch
|
||||
edge="end"
|
||||
@@ -439,8 +448,8 @@ export const ServerSettings = () => {
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('settings.server.opds.show_only_unread_chapters.label.title')}
|
||||
secondary={t('settings.server.opds.show_only_unread_chapters.label.description')}
|
||||
primary={t`Show only unread chapters`}
|
||||
secondary={t`Filter manga feed to display only chapters you haven’t read yet.`}
|
||||
/>
|
||||
<Switch
|
||||
edge="end"
|
||||
@@ -450,8 +459,8 @@ export const ServerSettings = () => {
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('settings.server.opds.show_only_downloaded_chapters.label.title')}
|
||||
secondary={t('settings.server.opds.show_only_downloaded_chapters.label.description')}
|
||||
primary={t`Show only downloaded chapters`}
|
||||
secondary={t`Filter manga feed to display only chapters you have downloaded.`}
|
||||
/>
|
||||
<Switch
|
||||
edge="end"
|
||||
@@ -460,23 +469,48 @@ export const ServerSettings = () => {
|
||||
/>
|
||||
</ListItem>
|
||||
<SelectSetting<SortOrder>
|
||||
settingName={t('settings.server.opds.chapter_sort_order.label.title')}
|
||||
dialogDescription={t('settings.server.opds.chapter_sort_order.label.description')}
|
||||
settingName={t`Chapter sort order`}
|
||||
dialogDescription={t`Choose the order in which chapters are displayed.`}
|
||||
value={serverSettings.opdsChapterSortOrder}
|
||||
values={[
|
||||
[SortOrder.Asc, { text: t('global.sort.label.asc') }],
|
||||
[SortOrder.Desc, { text: t('global.sort.label.desc') }],
|
||||
[
|
||||
SortOrder.Asc,
|
||||
{
|
||||
text: t`Ascending`,
|
||||
},
|
||||
],
|
||||
[
|
||||
SortOrder.Desc,
|
||||
{
|
||||
text: t`Descending`,
|
||||
},
|
||||
],
|
||||
]}
|
||||
handleChange={(value) => updateSetting('opdsChapterSortOrder', value)}
|
||||
/>
|
||||
<SelectSetting<CbzMediaType>
|
||||
settingName={t('settings.server.opds.cbz_mime_type.title')}
|
||||
dialogDescription={t('settings.server.opds.cbz_mime_type.description')}
|
||||
settingName={t`CBZ MIME-Type`}
|
||||
dialogDescription={t`Controls the MimeType that Suwayomi sends in OPDS entries for CBZ archives. Also affects global CBZ download.\nModern follows recent IANA standard (2017), while LEGACY (deprecated mimetype for .cbz) and COMPATIBLE (deprecated mimetype for all comic archives) might be more compatible with older clients.`}
|
||||
value={serverSettings.opdsCbzMimetype}
|
||||
values={[
|
||||
[CbzMediaType.Legacy, { text: t('settings.server.opds.cbz_mime_type.legacy') }],
|
||||
[CbzMediaType.Modern, { text: t('settings.server.opds.cbz_mime_type.modern') }],
|
||||
[CbzMediaType.Compatible, { text: t('settings.server.opds.cbz_mime_type.compatible') }],
|
||||
[
|
||||
CbzMediaType.Legacy,
|
||||
{
|
||||
text: t`Legacy`,
|
||||
},
|
||||
],
|
||||
[
|
||||
CbzMediaType.Modern,
|
||||
{
|
||||
text: t`Modern`,
|
||||
},
|
||||
],
|
||||
[
|
||||
CbzMediaType.Compatible,
|
||||
{
|
||||
text: t`Compatible`,
|
||||
},
|
||||
],
|
||||
]}
|
||||
handleChange={(value) => updateSetting('opdsCbzMimetype', value)}
|
||||
/>
|
||||
@@ -491,34 +525,44 @@ export const ServerSettings = () => {
|
||||
<List
|
||||
subheader={
|
||||
<ListSubheader component="div" id="server-settings-database">
|
||||
{t('settings.server.database.title')}
|
||||
{t`Database`}
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<SelectSetting<DatabaseType>
|
||||
settingName={t('settings.server.database.type.title')}
|
||||
settingName={t`Type`}
|
||||
value={serverSettings.databaseType}
|
||||
values={[
|
||||
[DatabaseType.H2, { text: t('settings.server.database.type.h2') }],
|
||||
[DatabaseType.Postgresql, { text: t('settings.server.database.type.postgresql') }],
|
||||
[
|
||||
DatabaseType.H2,
|
||||
{
|
||||
text: t`H2`,
|
||||
},
|
||||
],
|
||||
[
|
||||
DatabaseType.Postgresql,
|
||||
{
|
||||
text: t`PostgreSQL`,
|
||||
},
|
||||
],
|
||||
]}
|
||||
handleChange={(value) => updateSetting('databaseType', value)}
|
||||
/>
|
||||
<TextSetting
|
||||
settingName={t('settings.server.database.address')}
|
||||
settingName={t`Database url`}
|
||||
handleChange={(url) => updateSetting('databaseUrl', url)}
|
||||
value={serverSettings.databaseUrl}
|
||||
placeholder="postgresql://localhost:5432/suwayomi"
|
||||
disabled={isH2Database}
|
||||
/>
|
||||
<TextSetting
|
||||
settingName={t('settings.server.database.username')}
|
||||
settingName={t`Username`}
|
||||
value={serverSettings.databaseUsername}
|
||||
handleChange={(username) => updateSetting('databaseUsername', username)}
|
||||
disabled={isH2Database}
|
||||
/>
|
||||
<TextSetting
|
||||
settingName={t('settings.server.database.password')}
|
||||
settingName={t`Password`}
|
||||
value={serverSettings.databasePassword}
|
||||
handleChange={(password) => updateSetting('databasePassword', password)}
|
||||
disabled={isH2Database}
|
||||
@@ -526,8 +570,8 @@ export const ServerSettings = () => {
|
||||
/>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('settings.server.database.hikari_connection_pool.title')}
|
||||
secondary={t('settings.server.database.hikari_connection_pool.description')}
|
||||
primary={t`Hikari connection pool`}
|
||||
secondary={t`Improves performance, but may cause issues for some installations where data cannot be read from the database`}
|
||||
/>
|
||||
<Switch
|
||||
edge="end"
|
||||
@@ -539,12 +583,12 @@ export const ServerSettings = () => {
|
||||
<List
|
||||
subheader={
|
||||
<ListSubheader component="div" id="server-settings-misc">
|
||||
{t('settings.server.misc.title')}
|
||||
{t`Misc`}
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<ListItem>
|
||||
<ListItemText primary={t('settings.server.misc.log_level.label.server')} />
|
||||
<ListItemText primary={t`Enable debug logs`} />
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={serverSettings.debugLogsEnabled}
|
||||
@@ -553,8 +597,8 @@ export const ServerSettings = () => {
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('settings.server.misc.tray_icon.label.title')}
|
||||
secondary={t('settings.server.misc.tray_icon.label.description')}
|
||||
primary={t`Show icon in system tray`}
|
||||
secondary={t`This icon will be shown on the system that is running the server`}
|
||||
/>
|
||||
<Switch
|
||||
edge="end"
|
||||
@@ -563,23 +607,23 @@ export const ServerSettings = () => {
|
||||
/>
|
||||
</ListItem>
|
||||
<NumberSetting
|
||||
settingTitle={t('settings.server.misc.log_files.file_cleanup.title')}
|
||||
settingTitle={t`Log file cleanup`}
|
||||
settingValue={getLogFilesCleanupDisplayValue(serverSettings.maxLogFiles)}
|
||||
value={serverSettings.maxLogFiles}
|
||||
valueUnit={t('global.date.label.day_one')}
|
||||
valueUnit={t`Day`}
|
||||
handleUpdate={(maxFiles) => updateSetting('maxLogFiles', maxFiles)}
|
||||
/>
|
||||
<TextSetting
|
||||
settingName={t('settings.server.misc.log_files.file_size.title')}
|
||||
settingName={t`Maximum log file size`}
|
||||
value={serverSettings.maxLogFileSize}
|
||||
dialogDescription={t('settings.server.misc.log_files.file_size.description')}
|
||||
dialogDescription={t`Example for possible values: 1 (bytes), 1KB (kilobytes), 1MB (megabytes), 1GB (gigabytes)`}
|
||||
validate={(value) => !!value.match(/^[0-9]+(|kb|KB|mb|MB|gb|GB)$/g)}
|
||||
handleChange={(maxLogFileSize) => updateSetting('maxLogFileSize', maxLogFileSize)}
|
||||
/>
|
||||
<TextSetting
|
||||
settingName={t('settings.server.misc.log_files.total_size.title')}
|
||||
settingName={t`Maximum size of all log files`}
|
||||
value={serverSettings.maxLogFolderSize}
|
||||
dialogDescription={t('settings.server.misc.log_files.total_size.description')}
|
||||
dialogDescription={t`Example for possible values: 1 (bytes), 1KB (kilobytes), 1MB (megabytes), 1GB (gigabytes)`}
|
||||
validate={(value) => !!value.match(/^[0-9]+(|kb|KB|mb|MB|gb|GB)$/g)}
|
||||
handleChange={(maxLogFolderSize) => updateSetting('maxLogFolderSize', maxLogFolderSize)}
|
||||
/>
|
||||
|
||||
@@ -11,7 +11,6 @@ import List from '@mui/material/List';
|
||||
import BackupIcon from '@mui/icons-material/Backup';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CollectionsOutlinedBookmarkIcon from '@mui/icons-material/CollectionsBookmarkOutlined';
|
||||
import GetAppOutlinedIcon from '@mui/icons-material/GetAppOutlined';
|
||||
import DnsIcon from '@mui/icons-material/Dns';
|
||||
@@ -22,14 +21,15 @@ import SyncIcon from '@mui/icons-material/Sync';
|
||||
import PaletteIcon from '@mui/icons-material/Palette';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import ImageIcon from '@mui/icons-material/Image';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { ListItemLink } from '@/base/components/lists/ListItemLink.tsx';
|
||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
|
||||
|
||||
export function Settings() {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
useAppTitle(t('settings.title'));
|
||||
useAppTitle(t`Settings`);
|
||||
|
||||
return (
|
||||
<List sx={{ padding: 0 }}>
|
||||
@@ -37,74 +37,73 @@ export function Settings() {
|
||||
<ListItemIcon>
|
||||
<PaletteIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('settings.appearance.title')} />
|
||||
<ListItemText primary={t`Appearance`} />
|
||||
</ListItemLink>
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.reader.path}>
|
||||
<ListItemIcon>
|
||||
<AutoStoriesIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('reader.settings.title.reader')} />
|
||||
<ListItemText primary={t`Reader`} />
|
||||
</ListItemLink>
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.library.path}>
|
||||
<ListItemIcon>
|
||||
<CollectionsOutlinedBookmarkIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('library.title')} />
|
||||
<ListItemText primary={t`Library`} />
|
||||
</ListItemLink>
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.download.path}>
|
||||
<ListItemIcon>
|
||||
<GetAppOutlinedIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('download.title.download')} />
|
||||
<ListItemText primary={t`Downloads`} />
|
||||
</ListItemLink>
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.images.path}>
|
||||
<ListItemIcon>
|
||||
<ImageIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('settings.images.title')} />
|
||||
<ListItemText primary={t`Images`} />
|
||||
</ListItemLink>
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.tracking.path}>
|
||||
<ListItemIcon>
|
||||
<SyncIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('tracking.title')} />
|
||||
<ListItemText primary={t`Tracking`} />
|
||||
</ListItemLink>
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.backup.path}>
|
||||
<ListItemIcon>
|
||||
<BackupIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('settings.backup.title')} />
|
||||
<ListItemText primary={t`Backup`} />
|
||||
</ListItemLink>
|
||||
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.browse.path}>
|
||||
<ListItemIcon>
|
||||
<ExploreOutlinedIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('global.label.browse')} />
|
||||
<ListItemText primary={t`Browse`} />
|
||||
</ListItemLink>
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.history.path}>
|
||||
<ListItemIcon>
|
||||
<HistoryIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('history.title')} />
|
||||
<ListItemText primary={t`History`} />
|
||||
</ListItemLink>
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.device.path}>
|
||||
<ListItemIcon>
|
||||
<DevicesIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('settings.device.title.device')} />
|
||||
<ListItemText primary={t`Device`} />
|
||||
</ListItemLink>
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.webui.path}>
|
||||
<ListItemIcon>
|
||||
<WebIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('settings.webui.title.webui')} />
|
||||
<ListItemText primary={t`WebUI`} />
|
||||
</ListItemLink>
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.server.path}>
|
||||
<ListItemIcon>
|
||||
<DnsIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('settings.server.title.server')} />
|
||||
<ListItemText primary={t`Server`} />
|
||||
</ListItemLink>
|
||||
</List>
|
||||
);
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { WebUIUpdateIntervalSetting } from '@/features/settings/components/webUI/WebUIUpdateIntervalSetting.tsx';
|
||||
import { TextSetting } from '@/base/components/settings/text/TextSetting.tsx';
|
||||
@@ -35,9 +35,9 @@ import {
|
||||
} from '@/features/settings/Settings.constants.ts';
|
||||
|
||||
export const WebUISettings = () => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
useAppTitle(t('settings.webui.title.webui'));
|
||||
useAppTitle(t`WebUI`);
|
||||
|
||||
const {
|
||||
settings: { webUIInformAvailableUpdate },
|
||||
@@ -46,7 +46,7 @@ export const WebUISettings = () => {
|
||||
} = useMetadataServerSettings();
|
||||
const updateMetadataServerSettings = createUpdateMetadataServerSettings<
|
||||
keyof Pick<MetadataUpdateSettings, 'webUIInformAvailableUpdate'>
|
||||
>((e) => makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)));
|
||||
>((e) => makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)));
|
||||
|
||||
const {
|
||||
data,
|
||||
@@ -67,7 +67,7 @@ export const WebUISettings = () => {
|
||||
}
|
||||
|
||||
mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch((e) =>
|
||||
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
|
||||
makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -80,7 +80,7 @@ export const WebUISettings = () => {
|
||||
if (error) {
|
||||
return (
|
||||
<EmptyViewAbsoluteCentered
|
||||
message={t('global.error.label.failed_to_load_data')}
|
||||
message={t`Unable to load data`}
|
||||
messageExtra={getErrorMessage(error)}
|
||||
retry={() => {
|
||||
if (metadataServerSettingsError) {
|
||||
@@ -105,13 +105,13 @@ export const WebUISettings = () => {
|
||||
return (
|
||||
<List sx={{ pt: 0 }}>
|
||||
<SelectSetting<WebUiFlavor>
|
||||
settingName={t('settings.webui.flavor.label.title')}
|
||||
settingName={t`Flavor`}
|
||||
value={webUISettings.webUIFlavor}
|
||||
values={WEB_UI_FLAVOR_SELECT_VALUES}
|
||||
handleChange={(flavor) => updateSetting('webUIFlavor', flavor)}
|
||||
/>
|
||||
<ListItem>
|
||||
<ListItemText primary={t('settings.webui.label.initial_open_browser')} />
|
||||
<ListItemText primary={t`Open the WebUI when starting the server`} />
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={webUISettings.initialOpenInBrowserEnabled}
|
||||
@@ -119,22 +119,20 @@ export const WebUISettings = () => {
|
||||
/>
|
||||
</ListItem>
|
||||
<SelectSetting<WebUiInterface>
|
||||
settingName={t('settings.webui.interface.label.title')}
|
||||
settingName={t`Interface`}
|
||||
value={webUISettings.webUIInterface}
|
||||
values={WEB_UI_INTERFACE_SELECT_VALUES}
|
||||
handleChange={(webUIInterface) => updateSetting('webUIInterface', webUIInterface)}
|
||||
/>
|
||||
<TextSetting
|
||||
settingName={t('settings.webui.electron_path.label.title')}
|
||||
dialogDescription={t('settings.webui.electron_path.label.description')}
|
||||
settingName={t`Electron path`}
|
||||
dialogDescription={t`The path to the electron installation on the server`}
|
||||
value={webUISettings.electronPath}
|
||||
settingDescription={
|
||||
webUISettings.electronPath.length ? webUISettings.electronPath : t('global.label.default')
|
||||
}
|
||||
settingDescription={webUISettings.electronPath.length ? webUISettings.electronPath : t`Default`}
|
||||
handleChange={(path) => updateSetting('electronPath', path)}
|
||||
/>
|
||||
<SelectSetting<WebUiChannel>
|
||||
settingName={t('settings.webui.channel.label.title')}
|
||||
settingName={t`Channel`}
|
||||
value={webUISettings.webUIChannel}
|
||||
values={WEB_UI_CHANNEL_SELECT_VALUES}
|
||||
handleChange={(channel) => updateSetting('webUIChannel', channel)}
|
||||
@@ -147,8 +145,8 @@ export const WebUISettings = () => {
|
||||
{!webUISettings.webUIUpdateCheckInterval && (
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('global.update.settings.inform.label.title')}
|
||||
secondary={t('global.update.settings.inform.label.description')}
|
||||
primary={t`Inform about available update`}
|
||||
secondary={t`Shows a dialog in case a new version is available`}
|
||||
/>
|
||||
<Switch
|
||||
edge="end"
|
||||
|
||||
Reference in New Issue
Block a user