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:
schroda
2026-01-10 19:45:02 +01:00
parent c1ac58f4d6
commit 65a9a905be
287 changed files with 120766 additions and 37748 deletions

View File

@@ -6,11 +6,11 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { SxProps, Theme } from '@mui/material/styles';
import { PublishingStatus, PublishingType } from '@/features/tracker/Tracker.types.ts';
import { TranslationKey } from '@/base/Base.types.ts';
export const DIALOG_PADDING: number = 2;
export const CARD_BACKGROUND: SxProps<Theme> = {
@@ -24,25 +24,25 @@ export const CARD_STYLING: SxProps<Theme> = {
...CARD_BACKGROUND,
};
export const PUBLISHING_TYPE_TO_TRANSLATION: Record<PublishingType, TranslationKey> = {
[PublishingType.UNKNOWN]: 'tracking.publishing.type.unknown',
[PublishingType.MANGA]: 'tracking.publishing.type.manga',
[PublishingType.NOVEL]: 'tracking.publishing.type.novel',
[PublishingType.ONE_SHOT]: 'tracking.publishing.type.one_shot',
[PublishingType.DOUJINSHI]: 'tracking.publishing.type.doujinshi',
[PublishingType.MANHWA]: 'tracking.publishing.type.manhwa',
[PublishingType.MANHUA]: 'tracking.publishing.type.manhua',
[PublishingType.OEL]: 'tracking.publishing.type.oel',
export const PUBLISHING_TYPE_TO_TRANSLATION: Record<PublishingType, MessageDescriptor> = {
[PublishingType.UNKNOWN]: msg`Unknown`,
[PublishingType.MANGA]: msg`Manga`,
[PublishingType.NOVEL]: msg`Novel`,
[PublishingType.ONE_SHOT]: msg`Oneshot`,
[PublishingType.DOUJINSHI]: msg`Doujinshi`,
[PublishingType.MANHWA]: msg`Manhwa`,
[PublishingType.MANHUA]: msg`Manhua`,
[PublishingType.OEL]: msg`OEL`,
};
export const PUBLISHING_STATUS_TO_TRANSLATION: Record<PublishingStatus, TranslationKey> = {
[PublishingStatus.FINISHED]: 'tracking.publishing.status.finished',
[PublishingStatus.RELEASING]: 'tracking.publishing.status.releasing',
[PublishingStatus.NOT_YET_RELEASED]: 'tracking.publishing.status.not_yet_released',
[PublishingStatus.CANCELLED]: 'tracking.publishing.status.cancelled',
[PublishingStatus.HIATUS]: 'tracking.publishing.status.hiatus',
[PublishingStatus.CURRENTLY_PUBLISHING]: 'tracking.publishing.status.currently_publishing',
[PublishingStatus.NOT_YET_PUBLISHED]: 'tracking.publishing.status.not_yet_published',
export const PUBLISHING_STATUS_TO_TRANSLATION: Record<PublishingStatus, MessageDescriptor> = {
[PublishingStatus.FINISHED]: msg`Finished`,
[PublishingStatus.RELEASING]: msg`Releasing`,
[PublishingStatus.NOT_YET_RELEASED]: msg`Not yet released`,
[PublishingStatus.CANCELLED]: msg`Cancelled`,
[PublishingStatus.HIATUS]: msg`Hiatus`,
[PublishingStatus.CURRENTLY_PUBLISHING]: msg`Currently publishing`,
[PublishingStatus.NOT_YET_PUBLISHED]: msg`Not yet published`,
};
export const UNSET_DATE = '0';

View File

@@ -10,7 +10,7 @@ import { useNavigate } from 'react-router-dom';
import Box from '@mui/material/Box';
import { useEffect, useMemo, useRef, useState } from 'react';
import DialogContent from '@mui/material/DialogContent';
import { useTranslation } from 'react-i18next';
import { useLingui } from '@lingui/react/macro';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
import { Trackers } from '@/features/tracker/services/Trackers.ts';
@@ -38,7 +38,7 @@ const getTrackerMode = (id: number, trackersInUse: number[], searchModeForTracke
};
export const TrackManga = ({ manga }: { manga: MangaIdInfo & Pick<MangaType, 'title'> }) => {
const { t } = useTranslation();
const { t } = useLingui();
const navigate = useNavigate();
const [searchModeForTracker, setSearchModeForTracker] = useState<number>();
@@ -81,7 +81,7 @@ export const TrackManga = ({ manga }: { manga: MangaIdInfo & Pick<MangaType, 'ti
mangaTrackRecords
.filter((trackRecord) => trackersInUseIds.includes(trackRecord.trackerId))
.map((trackRecord) => requestManager.fetchTrackBind(trackRecord.id).response),
).catch((e) => makeToast(t('tracking.error.label.could_not_fetch_track_info'), 'error', getErrorMessage(e)));
).catch((e) => makeToast(t`Could not load latest track info from tracker`, 'error', getErrorMessage(e)));
}, [mangaTrackRecords]);
const trackerComponents = useMemo(
@@ -112,7 +112,7 @@ export const TrackManga = ({ manga }: { manga: MangaIdInfo & Pick<MangaType, 'ti
if (error) {
return (
<EmptyView
message={t('global.error.label.failed_to_load_data')}
message={t`Unable to load data`}
messageExtra={getErrorMessage(error)}
retry={() => {
if (trackerList.error) {

View File

@@ -7,7 +7,6 @@
*/
import Button from '@mui/material/Button';
import { useTranslation } from 'react-i18next';
import List from '@mui/material/List';
import Stack from '@mui/material/Stack';
import { useEffect, useMemo, useState } from 'react';
@@ -22,6 +21,7 @@ import PopupState, { bindPopover, bindTrigger } from 'material-ui-popup-state';
import Popover from '@mui/material/Popover';
import Typography from '@mui/material/Typography';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import { useLingui } from '@lingui/react/macro';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
@@ -32,8 +32,8 @@ import { DIALOG_PADDING } from '@/features/tracker/Tracker.constants.ts';
import { useGetOptionForDirection } from '@/features/theme/services/ThemeCreator.ts';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
import { MangaIdInfo } from '@/features/manga/Manga.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import { applyStyles } from '@/base/utils/ApplyStyles.ts';
import { Tracker, TrackerIdInfo, TTrackerBind } from '@/features/tracker/Tracker.types.ts';
@@ -53,7 +53,7 @@ const TrackButton = ({
closeSearchMode: () => void;
supportsPrivateTracking: boolean;
}) => {
const { t } = useTranslation();
const { t } = useLingui();
const [bindTracker, bindTrackerMutation] = requestManager.useBindTracker();
const trackManga = (asPrivate: boolean) => {
@@ -65,10 +65,10 @@ const TrackButton = ({
variables: { input: { mangaId, remoteId: selectedTrackerRemoteId, trackerId, private: asPrivate } },
})
.then(() => {
makeToast(t('manga.action.track.add.label.success'), 'success');
makeToast(t`Tracked manga`, 'success');
closeSearchMode();
})
.catch((e) => makeToast(t('manga.action.track.add.label.error'), 'error', getErrorMessage(e)));
.catch((e) => makeToast(t`Could not track manga`, 'error', getErrorMessage(e)));
};
return (
@@ -92,13 +92,10 @@ const TrackButton = ({
onClick={() => trackManga(false)}
sx={{ flexBasis: '65%' }}
>
{t('manga.action.track.add.label.action')}
{t`Track`}
</Button>
{supportsPrivateTracking && (
<CustomTooltip
title={t('tracking.action.button.track_privately')}
disabled={bindTrackerMutation.loading}
>
<CustomTooltip title={t`Track privately`} disabled={bindTrackerMutation.loading}>
<CustomButtonIcon
disabled={bindTrackerMutation.loading}
sx={{ flexBasis: '10%', maxWidth: '100px' }}
@@ -124,7 +121,7 @@ export const TrackerSearch = ({
closeSearchMode: () => void;
trackedId?: string;
}) => {
const { t } = useTranslation();
const { t } = useLingui();
const getOptionForDirection = useGetOptionForDirection();
const [searchString, setSearchString] = useState<string>(manga.title);
@@ -197,7 +194,7 @@ export const TrackerSearch = ({
}}
>
<Typography sx={{ padding: 1, whiteSpace: 'pre-line' }}>
{t('tracking.my_anime_list.search.label.hint')}
{t`Search for a ID via "id:<ID>" (e.g. "id:13")\nLimit search to your lists via "my:<Title>" (e.g. "my:One Piece")`}
</Typography>
</Popover>
</>
@@ -217,11 +214,11 @@ export const TrackerSearch = ({
...applyStyles(hasNoResults || hasError, { position: 'relative' }),
}}
>
{hasNoResults && <EmptyViewAbsoluteCentered message={t('manga.error.label.no_mangas_found')} />}
{hasNoResults && <EmptyViewAbsoluteCentered message={t`No manga found`} />}
{trackerSearch.loading && <LoadingPlaceholder />}
{hasError && (
<EmptyViewAbsoluteCentered
message={t('global.error.label.failed_to_load_data')}
message={t`Unable to load data`}
messageExtra={getErrorMessage(trackerSearch.error)}
retry={() =>
trackerSearch.refetch().catch(defaultPromiseErrorHandler('TrackerSearch::refetch'))

View File

@@ -6,12 +6,12 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useTranslation } from 'react-i18next';
import ListItemButton from '@mui/material/ListItemButton';
import Chip from '@mui/material/Chip';
import ListItemAvatar from '@mui/material/ListItemAvatar';
import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
import ListItemText from '@mui/material/ListItemText';
import { useLingui } from '@lingui/react/macro';
import { makeToast } from '@/base/utils/Toast.ts';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { Trackers } from '@/features/tracker/services/Trackers.ts';
@@ -21,7 +21,7 @@ import { AvatarSpinner } from '@/base/components/AvatarSpinner.tsx';
import { CredentialsLogin } from '@/base/components/modals/LoginDialog.tsx';
export const SettingsTrackerCard = ({ tracker }: { tracker: TTrackerSearch }) => {
const { t } = useTranslation();
const { t } = useLingui();
const isOAuthLogin = !tracker.isLoggedIn && !!tracker.authUrl;
@@ -29,7 +29,7 @@ export const SettingsTrackerCard = ({ tracker }: { tracker: TTrackerSearch }) =>
try {
await requestManager.logoutFromTracker(tracker.id).response;
} catch (e) {
makeToast(t('tracking.action.logout.label.failure', { name: tracker.name }), 'error', getErrorMessage(e));
makeToast(t`Could not log out from ${tracker.name}`, 'error', getErrorMessage(e));
}
};
@@ -49,7 +49,7 @@ export const SettingsTrackerCard = ({ tracker }: { tracker: TTrackerSearch }) =>
try {
await requestManager.loginTrackerCredentials(tracker.id, username, password).response;
} catch (e) {
makeToast(t('tracking.action.login.label.failure', { name: tracker.name }), 'error', getErrorMessage(e));
makeToast(t`Could not log in to ${tracker.name}`, 'error', getErrorMessage(e));
}
};
@@ -68,12 +68,7 @@ export const SettingsTrackerCard = ({ tracker }: { tracker: TTrackerSearch }) =>
const controlled = CredentialsLogin.showControlled(
{
title: t(
Trackers.isLoggedIn(tracker)
? 'tracking.settings.dialog.title.log_out'
: 'tracking.settings.dialog.title.log_in',
{ name: tracker.name },
),
title: Trackers.isLoggedIn(tracker) ? t`Log out from ${tracker.name}` : t`Log in to ${tracker.name}`,
isLoading: false,
isLoggedIn: Trackers.isLoggedIn(tracker),
username: initialUsername,
@@ -90,11 +85,7 @@ export const SettingsTrackerCard = ({ tracker }: { tracker: TTrackerSearch }) =>
controlled.submit();
} catch (e) {
makeToast(
t('tracking.action.logout.label.failure', { name: tracker.name }),
'error',
getErrorMessage(e),
);
makeToast(t`Could not log out from ${tracker.name}`, 'error', getErrorMessage(e));
}
return;
@@ -105,11 +96,7 @@ export const SettingsTrackerCard = ({ tracker }: { tracker: TTrackerSearch }) =>
controlled.submit();
} catch (e) {
makeToast(
t('tracking.action.login.label.failure', { name: tracker.name }),
'error',
getErrorMessage(e),
);
makeToast(t`Could not log in to ${tracker.name}`, 'error', getErrorMessage(e));
const RETRY_KEY = '__retry__';
const retry = await Promise.race([controlled.promise, Promise.resolve(RETRY_KEY)]);
@@ -145,7 +132,7 @@ export const SettingsTrackerCard = ({ tracker }: { tracker: TTrackerSearch }) =>
<ListItemText primary={tracker.name} />
{Trackers.isLoggedIn(tracker) && (
<ListItemSecondaryAction>
<Chip label={t('global.label.logged_in')} color="success" />
<Chip label={t`Logged in`} color="success" />
</ListItemSecondaryAction>
)}
</ListItemButton>

View File

@@ -8,7 +8,6 @@
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import { useTranslation } from 'react-i18next';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
@@ -28,6 +27,7 @@ import PopupState, { bindDialog, bindMenu, bindTrigger } from 'material-ui-popup
import { useMemo, useState } from 'react';
import Badge from '@mui/material/Badge';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import { useLingui } from '@lingui/react/macro';
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { Trackers } from '@/features/tracker/services/Trackers.ts';
@@ -63,7 +63,7 @@ const TrackerActiveRemoveBind = ({
onClick: () => void;
onClose: () => void;
}) => {
const { t } = useTranslation();
const { t } = useLingui();
const [removeRemoteTracking, setRemoveRemoteTracking] = useState(false);
@@ -71,8 +71,8 @@ const TrackerActiveRemoveBind = ({
onClose();
requestManager
.unbindTracker(trackerRecordId, removeRemoteTracking)
.response.then(() => makeToast(t('manga.action.track.remove.label.success'), 'success'))
.catch((e) => makeToast(t('manga.action.track.remove.label.error'), 'error', getErrorMessage(e)));
.response.then(() => makeToast(t`Untracked manga`, 'success'))
.catch((e) => makeToast(t`Could not untrack manga`, 'error', getErrorMessage(e)));
};
return (
@@ -86,7 +86,7 @@ const TrackerActiveRemoveBind = ({
popupState.open();
}}
>
{t('global.button.remove')}
{t`Remove`}
</MenuItem>
<Dialog
{...bindDialog(popupState)}
@@ -95,18 +95,14 @@ const TrackerActiveRemoveBind = ({
popupState.close();
}}
>
<DialogTitle>
{t('manga.action.track.remove.dialog.label.title', { tracker: tracker.name })}
</DialogTitle>
<DialogTitle>{t`Remove ${tracker.name} tracking?`}</DialogTitle>
<DialogContent dividers>
<Typography>{t('manga.action.track.remove.dialog.label.description')}</Typography>
<Typography>{t`This will remove the tracking locally.`}</Typography>
{tracker.supportsTrackDeletion && (
<FormGroup>
<CheckboxInput
disabled={false}
label={t('manga.action.track.remove.dialog.label.delete_remote_track', {
tracker: tracker.name,
})}
label={t`Also remove from ${tracker.name}`}
checked={removeRemoteTracking}
onChange={(_, checked) => setRemoveRemoteTracking(checked)}
/>
@@ -121,7 +117,7 @@ const TrackerActiveRemoveBind = ({
onClose();
}}
>
{t('global.button.cancel')}
{t`Cancel`}
</Button>
<Button
onClick={() => {
@@ -130,7 +126,7 @@ const TrackerActiveRemoveBind = ({
removeBind();
}}
>
{t('global.button.ok')}
{t`Ok`}
</Button>
</DialogActions>
</Dialog>
@@ -151,7 +147,7 @@ const TrackerUpdatePrivateStatus = ({
closeMenu: () => void;
supportsPrivateTracking: TTrackerActive['supportsPrivateTracking'];
}) => {
const { t } = useTranslation();
const { t } = useLingui();
if (!supportsPrivateTracking) {
return null;
@@ -162,13 +158,11 @@ const TrackerUpdatePrivateStatus = ({
onClick={() => {
requestManager
.updateTrackerBind(trackRecordId, { private: !isPrivate })
.response.catch((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
);
.response.catch((e) => makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)));
closeMenu();
}}
>
{t(isPrivate ? 'tracking.action.button.track_publicly' : 'tracking.action.button.track_privately')}
{isPrivate ? t`Track publicly` : t`Track privately`}
</MenuItem>
);
};
@@ -183,7 +177,7 @@ const TrackerActiveHeader = ({
tracker: TTrackerActive;
openSearch: () => void;
}) => {
const { t } = useTranslation();
const { t } = useLingui();
return (
<Stack
@@ -218,7 +212,6 @@ const TrackerActiveHeader = ({
/>
</TrackerActiveLink>
</Badge>
<ListItemButton sx={{ flexGrow: 1 }} onClick={openSearch}>
<CustomTooltip title={trackRecord.title}>
<TypographyMaxLines flexGrow={1} lines={1}>
@@ -243,9 +236,7 @@ const TrackerActiveHeader = ({
key={`tracker-active-menu-item-browser-${tracker.id}`}
url={trackRecord.remoteUrl}
>
<MenuItem onClick={() => onClose()}>
{t('global.label.open_in_browser')}
</MenuItem>
<MenuItem onClick={() => onClose()}>{t`Open in browser`}</MenuItem>
</TrackerActiveLink>,
<TrackerActiveRemoveBind
key={`tracker-active-menu-item-remove-${tracker.id}`}
@@ -287,7 +278,7 @@ export const TrackerActiveCard = ({
tracker: TTrackerBind;
onClick: () => void;
}) => {
const { t } = useTranslation();
const { t } = useLingui();
const isScoreUnset = isUnsetScore(trackRecord.displayScore);
const currentScore = isScoreUnset ? tracker.scores[0] : trackRecord.displayScore;
@@ -306,9 +297,7 @@ export const TrackerActiveCard = ({
const updateTrackerBind = (patch: Parameters<typeof requestManager.updateTrackerBind>[1]) => {
requestManager
.updateTrackerBind(trackRecord.id, patch)
.response.catch((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
);
.response.catch((e) => makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)));
};
return (
@@ -320,7 +309,7 @@ export const TrackerActiveCard = ({
<Box sx={{ padding: 1 }}>
<TrackerActiveCardInfoRow>
<ListPreference
ListPreferenceTitle={t('manga.label.status')}
ListPreferenceTitle={t`Status`}
entries={tracker.statuses.map((status) => status.name)}
key="status"
type="ListPreference"
@@ -331,8 +320,8 @@ export const TrackerActiveCard = ({
/>
<Divider orientation="vertical" flexItem />
<NumberSetting
settingTitle={t('chapter.title_other')}
dialogTitle={t('chapter.title_other')}
settingTitle={t`Chapter`}
dialogTitle={t`Chapter`}
settingValue={`${trackRecord.lastChapterRead}/${trackRecord.totalChapters}`}
value={trackRecord.lastChapterRead}
minValue={0}
@@ -342,7 +331,7 @@ export const TrackerActiveCard = ({
/>
<Divider orientation="vertical" flexItem />
<SelectSetting<string>
settingName={t('tracking.track_record.label.score')}
settingName={t`Score`}
value={currentScore}
values={selectSettingValues}
handleChange={(score) => updateTrackerBind({ scoreString: score })}
@@ -351,7 +340,7 @@ export const TrackerActiveCard = ({
<Divider />
<TrackerActiveCardInfoRow>
<DateSetting
settingName={t('tracking.track_record.label.start_date')}
settingName={t`Start date`}
value={Trackers.getDateString(trackRecord.startDate)}
remove
handleChange={(startDate) =>
@@ -360,7 +349,7 @@ export const TrackerActiveCard = ({
/>
<Divider orientation="vertical" flexItem />
<DateSetting
settingName={t('tracking.track_record.label.finish_date')}
settingName={t`Finish date`}
value={Trackers.getDateString(trackRecord.finishDate)}
remove
handleChange={(finishDate) =>

View File

@@ -9,7 +9,6 @@
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Button from '@mui/material/Button';
import { useTranslation } from 'react-i18next';
import Box from '@mui/material/Box';
import CardActionArea from '@mui/material/CardActionArea';
import CardMedia from '@mui/material/CardMedia';
@@ -19,6 +18,7 @@ import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { useLayoutEffect, useRef, useState } from 'react';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import { useLingui } from '@lingui/react/macro';
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
import { SpinnerImage } from '@/base/components/SpinnerImage.tsx';
import { TypographyMaxLines } from '@/base/components/texts/TypographyMaxLines.tsx';
@@ -52,7 +52,7 @@ const TrackerMangaCardTitle = ({ title, selected }: { title: string; selected: b
const SUMMARY_COLLAPSED_SIZE = 50;
const TrackerMangaCardSummary = ({ summary }: { summary: string }) => {
const { t } = useTranslation();
const { t } = useLingui();
const summaryRef = useRef<HTMLParagraphElement>(null);
@@ -85,7 +85,7 @@ const TrackerMangaCardSummary = ({ summary }: { summary: string }) => {
setIsSummaryExpanded(!isSummaryExpanded);
}}
>
{t(isSummaryExpanded ? 'global.button.show_less' : 'global.button.show_more')}
{isSummaryExpanded ? t`Show less` : t`Show more`}
</Button>
)}
</>
@@ -115,7 +115,7 @@ export const TrackerMangaCard = ({
selected: boolean;
onSelect: () => void;
}) => {
const { t } = useTranslation();
const { t } = useLingui();
const isMobileWidth = MediaQuery.useIsMobileWidth();
return (
@@ -175,25 +175,19 @@ export const TrackerMangaCard = ({
</TrackerMangaCardLink>
{manga.publishingType && (
<Metadata
title={t('global.label.type')}
title={t`Type`}
value={t(PUBLISHING_TYPE_TO_TRANSLATION[Trackers.getPublishingType(manga)])}
/>
)}
{manga.startDate && (
<Metadata title={t('global.label.started')} value={manga.startDate} />
)}
{manga.startDate && <Metadata title={t`Started`} value={manga.startDate} />}
{manga.publishingStatus && (
<Metadata
title={t('manga.label.status')}
title={t`Status`}
value={t(PUBLISHING_STATUS_TO_TRANSLATION[Trackers.getPublishingStatus(manga)])}
/>
)}
{manga.score > 0 && (
<Metadata title={t('tracking.track_record.label.score')} value={manga.score} />
)}
{manga.totalChapters > 0 && (
<Metadata title={t('chapter.title_other')} value={manga.totalChapters} />
)}
{manga.score > 0 && <Metadata title={t`Score`} value={manga.score} />}
{manga.totalChapters > 0 && <Metadata title={t`Chapter`} value={manga.totalChapters} />}
</Stack>
</Stack>
<TrackerMangaCardSummary summary={manga.summary} />

View File

@@ -9,12 +9,12 @@
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Button from '@mui/material/Button';
import { useTranslation } from 'react-i18next';
import Stack from '@mui/material/Stack';
import { useLingui } from '@lingui/react/macro';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { CARD_STYLING } from '@/features/tracker/Tracker.constants.ts';
import { TTrackerBase } from '@/features/tracker/Tracker.types.ts';
import { AvatarSpinner } from '@/base/components/AvatarSpinner.tsx';
export const TrackerUntrackedCard = ({
@@ -24,7 +24,7 @@ export const TrackerUntrackedCard = ({
tracker: Pick<TTrackerBase, 'name' | 'icon'>;
onClick: () => void;
}) => {
const { t } = useTranslation();
const { t } = useLingui();
return (
<Card sx={CARD_STYLING}>
@@ -49,7 +49,7 @@ export const TrackerUntrackedCard = ({
}}
/>
<Button sx={{ flexGrow: '1' }} onClick={onClick}>
{t('tracking.action.button.add_tracking')}
{t`Add tracking`}
</Button>
</Stack>
</CardContent>

View File

@@ -7,15 +7,15 @@
*/
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { useLingui } from '@lingui/react/macro';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { makeToast } from '@/base/utils/Toast.ts';
import { AppRoutes } from '@/base/AppRoute.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
export const TrackerOAuthLogin = () => {
const { t } = useTranslation();
const { t } = useLingui();
const navigate = useNavigate();
const url = new URL(window.location.href);
@@ -28,7 +28,7 @@ export const TrackerOAuthLogin = () => {
try {
await requestManager.loginToTrackerOauth(trackerId, window.location.href).response;
} catch (e) {
makeToast(t('tracking.action.login.label.failure', { name: trackerName }), 'error', getErrorMessage(e));
makeToast(t`Could not log in to ${trackerName}`, 'error', getErrorMessage(e));
}
navigate(AppRoutes.settings.childRoutes.tracking.path, { replace: true });
@@ -37,5 +37,5 @@ export const TrackerOAuthLogin = () => {
login();
}, [trackerId]);
return t('tracking.action.login.label.progress', { name: trackerName });
return t`Logging in to ${trackerName}`;
};

View File

@@ -6,12 +6,12 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useTranslation } from 'react-i18next';
import List from '@mui/material/List';
import ListSubheader from '@mui/material/ListSubheader';
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 { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
@@ -29,9 +29,9 @@ import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
export const TrackingSettings = () => {
const { t } = useTranslation();
const { t } = useLingui();
useAppTitle(t('tracking.title'));
useAppTitle(t`Tracking`);
const {
settings: { updateProgressAfterReading, updateProgressManualMarkRead },
@@ -39,7 +39,7 @@ export const TrackingSettings = () => {
request: { error: metadataServerSettingsError, refetch: refetchServerMetadataSettings },
} = useMetadataServerSettings();
const updateTrackingSettings = createUpdateMetadataServerSettings<keyof MetadataTrackingSettings>((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)),
);
const {
@@ -58,7 +58,7 @@ export const TrackingSettings = () => {
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) {
@@ -85,7 +85,7 @@ export const TrackingSettings = () => {
<>
<List sx={{ pt: 0 }}>
<ListItem>
<ListItemText primary={t('tracking.settings.label.update_progress_reading')} />
<ListItemText primary={t`Update progress after reading`} />
<Switch
edge="end"
checked={updateProgressAfterReading}
@@ -94,8 +94,8 @@ export const TrackingSettings = () => {
</ListItem>
<ListItem>
<ListItemText
primary={t('tracking.settings.label.update_progress_manual')}
secondary={t('tracking.settings.label.update_progress_reading_description')}
primary={t`Update progress after manually marking as read`}
secondary={t`Does not work when marking multiple manga as read at once`}
/>
<Switch
edge="end"
@@ -107,7 +107,7 @@ export const TrackingSettings = () => {
<List
subheader={
<ListSubheader component="div" id="tracking-trackers">
{t('tracking.settings.title.trackers')}
{t`Trackers`}
</ListSubheader>
}
>