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:
@@ -14,9 +14,9 @@ import { StringParam, useQueryParam } from 'use-query-params';
|
||||
import Button from '@mui/material/Button';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useWindowEvent } from '@mantine/hooks';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { AppbarSearch } from '@/base/components/AppbarSearch.tsx';
|
||||
@@ -42,7 +42,7 @@ import {
|
||||
ExtensionState,
|
||||
TExtension,
|
||||
} from '@/features/extension/Extensions.types.ts';
|
||||
import { EXTENSION_ACTION_TO_FAILURE_TRANSLATION_KEY_MAP } from '@/features/extension/Extensions.constants.ts';
|
||||
import { EXTENSION_ACTION_TO_FAILURE_TRANSLATION_MAP } from '@/features/extension/Extensions.constants.ts';
|
||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||
import {
|
||||
@@ -52,6 +52,7 @@ import {
|
||||
import { MetadataBrowseSettings } from '@/features/browse/Browse.types.ts';
|
||||
import { useAppAction } from '@/features/navigation-bar/hooks/useAppAction.ts';
|
||||
import { SearchParam } from '@/base/Base.types.ts';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
const LANGUAGE = 0;
|
||||
const EXTENSIONS = 1;
|
||||
@@ -73,7 +74,7 @@ const GroupHeader = ({
|
||||
setUpdatingExtensionIds: (ids: TExtension['pkgName'][]) => void;
|
||||
handleExtensionUpdate: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<StyledGroupHeader
|
||||
@@ -96,8 +97,10 @@ const GroupHeader = ({
|
||||
.response.then(() => handleExtensionUpdate())
|
||||
.catch((e) =>
|
||||
makeToast(
|
||||
t(EXTENSION_ACTION_TO_FAILURE_TRANSLATION_KEY_MAP[ExtensionAction.UPDATE], {
|
||||
count: groupExtensionIds.length,
|
||||
/* lingui-extract-ignore */
|
||||
i18n.t({
|
||||
...EXTENSION_ACTION_TO_FAILURE_TRANSLATION_MAP[ExtensionAction.UPDATE],
|
||||
values: { count: groupExtensionIds.length },
|
||||
}),
|
||||
'error',
|
||||
getErrorMessage(e),
|
||||
@@ -106,7 +109,7 @@ const GroupHeader = ({
|
||||
.finally(() => setUpdatingExtensionIds([]));
|
||||
}}
|
||||
>
|
||||
{t('extension.action.label.update_all')}
|
||||
{t`Update all`}
|
||||
</Button>
|
||||
)}
|
||||
</StyledGroupHeader>
|
||||
@@ -114,7 +117,7 @@ const GroupHeader = ({
|
||||
};
|
||||
|
||||
export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
const {
|
||||
data: serverSettingsData,
|
||||
@@ -130,7 +133,7 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
} = useMetadataServerSettings();
|
||||
const updateMetadataServerSettings = createUpdateMetadataServerSettings<
|
||||
keyof Pick<MetadataBrowseSettings, 'extensionLanguages'>
|
||||
>((e) => makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)));
|
||||
>((e) => makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)));
|
||||
|
||||
const [query] = useQueryParam(SearchParam.QUERY, StringParam);
|
||||
|
||||
@@ -170,18 +173,18 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
|
||||
const submitExternalExtension = (file: File) => {
|
||||
if (!file.name.toLowerCase().endsWith('apk')) {
|
||||
makeToast(t('global.error.label.invalid_file_type'), 'error');
|
||||
makeToast(t`Invalid filetype`, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
makeToast(t('extension.label.installing_file'), 'info');
|
||||
makeToast(t`Installing extension file…`, 'info');
|
||||
requestManager
|
||||
.installExternalExtension(file)
|
||||
.response.then(() => {
|
||||
handleExtensionUpdate();
|
||||
makeToast(t('extension.label.installed_successfully'), 'success');
|
||||
makeToast(t`Extension installed`, 'success');
|
||||
})
|
||||
.catch((e) => makeToast(t('extension.label.installation_failed'), 'error', getErrorMessage(e)));
|
||||
.catch((e) => makeToast(t`Could not install the extension`, 'error', getErrorMessage(e)));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -191,7 +194,7 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
useAppAction(
|
||||
<>
|
||||
<AppbarSearch />
|
||||
<CustomTooltip title={t('extension.action.label.install_external')}>
|
||||
<CustomTooltip title={t`Install external extension`}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
const input = document.createElement('input');
|
||||
@@ -241,7 +244,7 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
if (error) {
|
||||
return (
|
||||
<EmptyViewAbsoluteCentered
|
||||
message={t('global.error.label.failed_to_load_data')}
|
||||
message={t`Unable to load data`}
|
||||
messageExtra={getErrorMessage(error)}
|
||||
retry={() => {
|
||||
if (serverSettingsError) {
|
||||
@@ -267,9 +270,9 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
paddingTop: '20px',
|
||||
}}
|
||||
>
|
||||
<Typography>{t('extension.label.add_repository_info')}</Typography>
|
||||
<Typography>{t`You have to add a extension repository to be able to install extensions`}</Typography>
|
||||
<Button component={Link} variant="contained" to={AppRoutes.settings.childRoutes.browse.path}>
|
||||
{t('settings.title')}
|
||||
{t`Settings`}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -10,10 +10,10 @@ import { useEffect, useState } from 'react';
|
||||
import Card from '@mui/material/Card';
|
||||
import Button from '@mui/material/Button';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||
import {
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
import {
|
||||
EXTENSION_ACTION_TO_NEXT_ACTION_MAP,
|
||||
EXTENSION_ACTION_TO_STATE_MAP,
|
||||
INSTALLED_STATE_TO_TRANSLATION_KEY_MAP,
|
||||
INSTALLED_STATE_TO_TRANSLATION_MAP,
|
||||
} from '@/features/extension/Extensions.constants.ts';
|
||||
import { getInstalledState, updateExtension } from '@/features/extension/Extensions.utils.ts';
|
||||
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||
@@ -45,7 +45,7 @@ interface IProps {
|
||||
}
|
||||
|
||||
export function ExtensionCard(props: IProps) {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
const {
|
||||
extension: { name, lang, versionName, isInstalled, hasUpdate, isObsolete, pkgName, iconUrl, isNsfw, repo },
|
||||
@@ -134,7 +134,7 @@ export function ExtensionCard(props: IProps) {
|
||||
{showSourceRepo && <Typography variant="caption">{repo}</Typography>}
|
||||
</Stack>
|
||||
{isInstalled && (
|
||||
<CustomTooltip title={t('settings.title')}>
|
||||
<CustomTooltip title={t`Settings`}>
|
||||
<IconButton color="inherit" {...MUIUtil.preventRippleProp()}>
|
||||
<SettingsIcon />
|
||||
</IconButton>
|
||||
@@ -151,7 +151,7 @@ export function ExtensionCard(props: IProps) {
|
||||
handleButtonClick();
|
||||
}}
|
||||
>
|
||||
{t(INSTALLED_STATE_TO_TRANSLATION_KEY_MAP[installedState])}
|
||||
{t(INSTALLED_STATE_TO_TRANSLATION_MAP[installedState])}
|
||||
</Button>
|
||||
</ListCardContent>
|
||||
</OptionalCardActionAreaLink>
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import Tab from '@mui/material/Tab';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { StringParam, useQueryParam } from 'use-query-params';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Sources } from '@/features/browse/sources/Sources.tsx';
|
||||
import { Extensions } from '@/features/browse/extensions/Extensions.tsx';
|
||||
import { TabPanel } from '@/base/components/tabs/TabPanel.tsx';
|
||||
@@ -23,8 +23,8 @@ import { GROUPED_VIRTUOSO_Z_INDEX } from '@/lib/virtuoso/Virtuoso.constants.ts';
|
||||
import { SearchParam } from '@/base/Base.types.ts';
|
||||
|
||||
export function Browse() {
|
||||
const { t } = useTranslation();
|
||||
useAppTitle(t('global.label.browse'));
|
||||
const { t } = useLingui();
|
||||
useAppTitle(t`Browse`);
|
||||
|
||||
const tabsMenuRef = useRef<HTMLDivElement | null>(null);
|
||||
const [tabsMenuHeight, setTabsMenuHeight] = useState(0);
|
||||
@@ -49,9 +49,9 @@ export function Browse() {
|
||||
value={tabName}
|
||||
onChange={(_, newTab) => setTabSearchParam(newTab, 'replaceIn')}
|
||||
>
|
||||
<Tab value={BrowseTab.SOURCES} sx={{ textTransform: 'none' }} label={t('source.title_other')} />
|
||||
<Tab value={BrowseTab.EXTENSIONS} sx={{ textTransform: 'none' }} label={t('extension.title_other')} />
|
||||
<Tab value={BrowseTab.MIGRATE} sx={{ textTransform: 'none' }} label={t('migrate.title')} />
|
||||
<Tab value={BrowseTab.SOURCES} sx={{ textTransform: 'none' }} label={t`Source`} />
|
||||
<Tab value={BrowseTab.EXTENSIONS} sx={{ textTransform: 'none' }} label={t`Extension`} />
|
||||
<Tab value={BrowseTab.MIGRATE} sx={{ textTransform: 'none' }} label={t`Migrate`} />
|
||||
</TabsMenu>
|
||||
<TabPanel index={BrowseTab.SOURCE_DEPRECATED} currentIndex={tabName}>
|
||||
<Sources tabsMenuHeight={tabsMenuHeight} />
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Trans, 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 { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { plural } from '@lingui/core/macro';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { NumberSetting } from '@/base/components/settings/NumberSetting.tsx';
|
||||
import { MutableListSetting } from '@/base/components/settings/MutableListSetting.tsx';
|
||||
@@ -31,9 +32,9 @@ import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
|
||||
type ExtensionsSettings = Pick<GqlServerSettings, 'maxSourcesInParallel' | 'localSourcePath' | 'extensionRepos'>;
|
||||
|
||||
export const BrowseSettings = () => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
useAppTitle(t('global.label.browse'));
|
||||
useAppTitle(t`Browse`);
|
||||
|
||||
const { data, loading, error, refetch } = requestManager.useGetServerSettings({
|
||||
notifyOnNetworkStatusChange: true,
|
||||
@@ -45,7 +46,7 @@ export const BrowseSettings = () => {
|
||||
value: ExtensionsSettings[Setting],
|
||||
) => {
|
||||
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)),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -53,7 +54,7 @@ export const BrowseSettings = () => {
|
||||
settings: { hideLibraryEntries, showNsfw },
|
||||
} = useMetadataServerSettings();
|
||||
const updateMetadataServerSettings = createUpdateMetadataServerSettings<keyof MetadataBrowseSettings>((e) =>
|
||||
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
|
||||
makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)),
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
@@ -63,7 +64,7 @@ export const BrowseSettings = () => {
|
||||
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('BrowseSettings::refetch'))}
|
||||
/>
|
||||
@@ -75,7 +76,7 @@ export const BrowseSettings = () => {
|
||||
return (
|
||||
<List sx={{ pt: 0 }}>
|
||||
<ListItem>
|
||||
<ListItemText primary={t('settings.label.hide_library_entries')} />
|
||||
<ListItemText primary={t`Hide entries already in library`} />
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={hideLibraryEntries}
|
||||
@@ -83,10 +84,7 @@ export const BrowseSettings = () => {
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t('settings.label.show_nsfw')}
|
||||
secondary={t('settings.label.show_nsfw_description')}
|
||||
/>
|
||||
<ListItemText primary={t`Show NSFW`} secondary={t`Hide NSFW extensions and sources`} />
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={showNsfw}
|
||||
@@ -94,12 +92,12 @@ export const BrowseSettings = () => {
|
||||
/>
|
||||
</ListItem>
|
||||
<NumberSetting
|
||||
settingTitle={t('settings.server.requests.sources.parallel.label.title')}
|
||||
settingValue={t('settings.server.requests.sources.parallel.label.value', {
|
||||
value: serverSettings.maxSourcesInParallel,
|
||||
count: serverSettings.maxSourcesInParallel,
|
||||
settingTitle={t`Parallel source requests`}
|
||||
settingValue={plural(serverSettings.maxSourcesInParallel, {
|
||||
one: '# Source',
|
||||
other: '# Sources',
|
||||
})}
|
||||
valueUnit={t('source.title_one')}
|
||||
valueUnit={t`Source`}
|
||||
value={serverSettings.maxSourcesInParallel}
|
||||
defaultValue={6}
|
||||
minValue={1}
|
||||
@@ -109,10 +107,10 @@ export const BrowseSettings = () => {
|
||||
handleUpdate={(parallelSources) => updateSetting('maxSourcesInParallel', parallelSources)}
|
||||
/>
|
||||
<MutableListSetting
|
||||
settingName={t('extension.settings.repositories.custom.label.title')}
|
||||
description={t('extension.settings.repositories.custom.label.description')}
|
||||
settingName={t`Extension repositories`}
|
||||
description={t`Add repositories from which extensions can be installed`}
|
||||
dialogDisclaimer={
|
||||
<Trans i18nKey="extension.settings.repositories.custom.label.disclaimer">
|
||||
<Trans>
|
||||
<strong>Suwayomi does not provide any support for 3rd party repositories or extensions!</strong>
|
||||
<br />
|
||||
Use with caution as there could be malicious actors making those repositories.
|
||||
@@ -125,22 +123,20 @@ export const BrowseSettings = () => {
|
||||
requestManager.clearExtensionCache();
|
||||
}}
|
||||
valueInfos={serverSettings.extensionRepos.map((extensionRepo) => [extensionRepo])}
|
||||
addItemButtonTitle={t('extension.settings.repositories.custom.dialog.action.button.add')}
|
||||
addItemButtonTitle={t`Add repository`}
|
||||
placeholder="https://github.com/MY_ACCOUNT/MY_REPO/tree/repo"
|
||||
validateItem={(repo) =>
|
||||
!!repo.match(
|
||||
/https:\/\/(www\.|raw\.)?(github|githubusercontent)\.com\/([^/]+)\/([^/]+)((\/tree|\/blob)?\/([^/\n]*))?(\/([^/\n]*\.json)?)?/g,
|
||||
)
|
||||
}
|
||||
invalidItemError={t('extension.settings.repositories.custom.error.label.invalid_url')}
|
||||
invalidItemError={t`Invalid repository url`}
|
||||
/>
|
||||
<TextSetting
|
||||
settingName={t('settings.server.local_source.path.label.title')}
|
||||
dialogDescription={t('settings.server.local_source.path.label.description')}
|
||||
settingName={t`Local source location`}
|
||||
dialogDescription={t`The path to the directory on the server where local source files are saved in`}
|
||||
value={serverSettings.localSourcePath}
|
||||
settingDescription={
|
||||
serverSettings.localSourcePath.length ? serverSettings.localSourcePath : t('global.label.default')
|
||||
}
|
||||
settingDescription={serverSettings.localSourcePath.length ? serverSettings.localSourcePath : t`Default`}
|
||||
handleChange={(path) => updateSetting('localSourcePath', path)}
|
||||
/>
|
||||
</List>
|
||||
|
||||
@@ -10,8 +10,8 @@ import { useCallback, useMemo } from 'react';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import TravelExploreIcon from '@mui/icons-material/TravelExplore';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { DefaultLanguage } from '@/base/utils/Languages.ts';
|
||||
@@ -32,7 +32,7 @@ import { StyledGroupItemWrapper } from '@/base/components/virtuoso/StyledGroupIt
|
||||
import { SourceLanguageSelect } from '@/features/source/components/SourceLanguageSelect.tsx';
|
||||
|
||||
export function Sources({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
const { languages: shownLangs, setLanguages: setShownLangs } = SourceService.useLanguages();
|
||||
const {
|
||||
@@ -97,7 +97,7 @@ export function Sources({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
|
||||
useAppAction(
|
||||
<>
|
||||
<CustomTooltip title={t('search.title.global_search')}>
|
||||
<CustomTooltip title={t`Global Search`}>
|
||||
<IconButton onClick={() => navigate(AppRoutes.sources.childRoutes.searchAll.path())} color="inherit">
|
||||
<TravelExploreIcon />
|
||||
</IconButton>
|
||||
@@ -117,7 +117,7 @@ export function Sources({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
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('Sources::refetch'))}
|
||||
/>
|
||||
@@ -125,7 +125,7 @@ export function Sources({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
}
|
||||
|
||||
if (sources?.length === 0) {
|
||||
return <EmptyViewAbsoluteCentered message={t('source.error.label.no_sources_found')} />;
|
||||
return <EmptyViewAbsoluteCentered message={t`No sources found. Install Some extensions first.`} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -11,12 +11,12 @@ import Button from '@mui/material/Button';
|
||||
import Card from '@mui/material/Card';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import PushPinIcon from '@mui/icons-material/PushPin';
|
||||
import PushPinOutlinedIcon from '@mui/icons-material/PushPinOutlined';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { SourceContentType } from '@/features/source/browse/screens/SourceMangas.tsx';
|
||||
import { GetSourcesListQuery } from '@/lib/graphql/generated/graphql.ts';
|
||||
@@ -38,7 +38,7 @@ interface IProps {
|
||||
}
|
||||
|
||||
export const SourceCard: React.FC<IProps> = (props: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
const { source, showSourceRepo, showLanguage } = props;
|
||||
const {
|
||||
@@ -53,10 +53,10 @@ export const SourceCard: React.FC<IProps> = (props: IProps) => {
|
||||
|
||||
const { isPinned } = useGetSourceMetadata(source);
|
||||
|
||||
const sourceName = Sources.isLocalSource(source) ? t('source.local_source.title') : name;
|
||||
const sourceName = Sources.isLocalSource(source) ? t`Local source` : name;
|
||||
|
||||
const updateSetting = createUpdateSourceMetadata(source, (e) =>
|
||||
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
|
||||
makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)),
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -105,10 +105,10 @@ export const SourceCard: React.FC<IProps> = (props: IProps) => {
|
||||
to={AppRoutes.sources.childRoutes.browse.path(id)}
|
||||
state={{ contentType: SourceContentType.LATEST, clearCache: true }}
|
||||
>
|
||||
{t('global.button.latest')}
|
||||
{t`Latest`}
|
||||
</Button>
|
||||
)}
|
||||
<CustomTooltip title={t(isPinned ? 'source.pin.remove' : 'source.pin.add')}>
|
||||
<CustomTooltip title={isPinned ? t`Unpin source` : t`Pin source`}>
|
||||
<IconButton
|
||||
{...MUIUtil.preventRippleProp()}
|
||||
onClick={(e) => {
|
||||
|
||||
Reference in New Issue
Block a user