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:
@@ -6,6 +6,8 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { MangaStatus } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { MangaAction, MangaIdInfo, MangaType } from '@/features/manga/Manga.types.ts';
|
||||
import {
|
||||
@@ -13,7 +15,6 @@ import {
|
||||
CHAPTER_ACTION_TO_TRANSLATION,
|
||||
} from '@/features/chapter/Chapter.constants.ts';
|
||||
import { GqlMetaHolder } from '@/features/metadata/Metadata.types.ts';
|
||||
import { TranslationKey } from '@/base/Base.types.ts';
|
||||
|
||||
export const FALLBACK_MANGA: MangaIdInfo & GqlMetaHolder = { id: -1 };
|
||||
|
||||
@@ -21,14 +22,14 @@ export const GLOBAL_READER_SETTINGS_MANGA: MangaIdInfo = { id: -2 };
|
||||
|
||||
export const MANGA_COVER_ASPECT_RATIO = '1 / 1.5';
|
||||
|
||||
export const MANGA_STATUS_TO_TRANSLATION: Record<MangaStatus, TranslationKey> = {
|
||||
[MangaStatus.Cancelled]: 'manga.status.cancelled',
|
||||
[MangaStatus.Completed]: 'manga.status.completed',
|
||||
[MangaStatus.Licensed]: 'manga.status.licensed',
|
||||
[MangaStatus.Ongoing]: 'manga.status.ongoing',
|
||||
[MangaStatus.OnHiatus]: 'manga.status.hiatus',
|
||||
[MangaStatus.PublishingFinished]: 'manga.status.publishing_finished',
|
||||
[MangaStatus.Unknown]: 'manga.status.unknown',
|
||||
export const MANGA_STATUS_TO_TRANSLATION: Record<MangaStatus, MessageDescriptor> = {
|
||||
[MangaStatus.Cancelled]: msg`Cancelled`,
|
||||
[MangaStatus.Completed]: msg`Completed`,
|
||||
[MangaStatus.Licensed]: msg`Licensed`,
|
||||
[MangaStatus.Ongoing]: msg`Ongoing`,
|
||||
[MangaStatus.OnHiatus]: msg`Hiatus`,
|
||||
[MangaStatus.PublishingFinished]: msg`Publishing finished`,
|
||||
[MangaStatus.Unknown]: msg`Unknown`,
|
||||
};
|
||||
|
||||
export const MANGA_ACTION_TO_CONFIRMATION_REQUIRED: Record<
|
||||
@@ -45,48 +46,48 @@ export const MANGA_ACTION_TO_CONFIRMATION_REQUIRED: Record<
|
||||
export const MANGA_ACTION_TO_TRANSLATION: {
|
||||
[key in MangaAction]: {
|
||||
action: {
|
||||
single: TranslationKey;
|
||||
selected: TranslationKey;
|
||||
single: MessageDescriptor;
|
||||
selected: MessageDescriptor;
|
||||
};
|
||||
confirmation?: TranslationKey;
|
||||
success: TranslationKey;
|
||||
error: TranslationKey;
|
||||
confirmation?: MessageDescriptor;
|
||||
success: MessageDescriptor;
|
||||
error: MessageDescriptor;
|
||||
};
|
||||
} = {
|
||||
...CHAPTER_ACTION_TO_TRANSLATION,
|
||||
remove_from_library: {
|
||||
action: {
|
||||
single: 'manga.action.library.remove.label.action',
|
||||
selected: 'manga.action.library.remove.button.selected',
|
||||
single: msg`Remove from the library`,
|
||||
selected: msg`Remove selected from the library`,
|
||||
},
|
||||
confirmation: 'manga.action.library.remove.label.confirmation',
|
||||
success: 'manga.action.library.remove.label.success',
|
||||
error: 'manga.action.library.remove.label.error',
|
||||
confirmation: msg`{count, plural, one {You are about to remove one entry from your library} other {You are about to remove # entries from your library}}`,
|
||||
success: msg`{count, plural, one {Removed manga from the library} other {Removed # manga from the library}}`,
|
||||
error: msg`{count, plural, one {Could not remove manga from the library} other {Could not remove manga from the library}}`,
|
||||
},
|
||||
change_categories: {
|
||||
action: {
|
||||
single: 'manga.action.category.label.action',
|
||||
selected: 'manga.action.category.button.selected',
|
||||
single: msg`Change categories`,
|
||||
selected: msg`Change categories of selected`,
|
||||
},
|
||||
confirmation: 'manga.action.category.label.confirmation',
|
||||
success: 'manga.action.category.label.success',
|
||||
error: 'manga.action.category.label.error',
|
||||
confirmation: msg`{count, plural, one {You are about to change the category of one entry} other {You are about to change the category of # entries}}`,
|
||||
success: msg`{count, plural, one {Changed categories of manga} other {Changed categories of # manga}}`,
|
||||
error: msg`{count, plural, one {Could not change the categories of the manga} other {Could not change the categories of the manga}}`,
|
||||
},
|
||||
migrate: {
|
||||
action: {
|
||||
single: 'global.button.migrate',
|
||||
selected: 'global.button.migrate', // not supported
|
||||
single: msg`Migrate`,
|
||||
selected: msg`Migrate`, // not supported
|
||||
},
|
||||
success: 'manga.action.migrate.label.success',
|
||||
error: 'manga.action.migrate.label.error',
|
||||
success: msg`Successfully migrated manga`,
|
||||
error: msg`Could not migrate manga`,
|
||||
},
|
||||
track: {
|
||||
action: {
|
||||
single: 'manga.action.track.add.label.action',
|
||||
selected: 'manga.action.track.add.label.action', // not supported
|
||||
single: msg`Track`,
|
||||
selected: msg`Track`, // not supported
|
||||
},
|
||||
success: 'manga.action.track.add.label.success',
|
||||
error: 'manga.action.track.add.label.error',
|
||||
success: msg`Tracked manga`,
|
||||
error: msg`Could not track manga`,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -151,10 +152,10 @@ export const SOURCES_BY_MANGA_TYPE: Record<MangaType, string[]> = {
|
||||
],
|
||||
};
|
||||
|
||||
export const MANGA_TAGS_BY_MANGA_TYPE: Record<MangaType, TranslationKey[]> = {
|
||||
[MangaType.MANGA]: ['manga.type.manga'],
|
||||
[MangaType.COMIC]: ['manga.type.comic'],
|
||||
[MangaType.WEBTOON]: ['manga.type.webtoon', 'manga.type.long_strip'],
|
||||
[MangaType.MANHWA]: ['manga.type.manhwa', 'manga.type.long_strip'],
|
||||
[MangaType.MANHUA]: ['manga.type.manhua', 'manga.type.long_strip'],
|
||||
export const MANGA_TAGS_BY_MANGA_TYPE: Record<MangaType, MessageDescriptor[]> = {
|
||||
[MangaType.MANGA]: [msg`Manga`],
|
||||
[MangaType.COMIC]: [msg`Comic`],
|
||||
[MangaType.WEBTOON]: [msg`Webtoon`, msg`Long strip`],
|
||||
[MangaType.MANHWA]: [msg`Manhwa`, msg`Long strip`],
|
||||
[MangaType.MANHUA]: [msg`Manhua`, msg`Long strip`],
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import { ReactElement } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { CustomTooltip } from '@/base/components/CustomTooltip';
|
||||
import {
|
||||
ChapterNameInfo,
|
||||
@@ -28,7 +28,7 @@ export const ContinueReadingTooltip = ({
|
||||
ChapterScanlatorInfo & {
|
||||
children: ReactElement;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
const isFirstChapter = sourceOrder === 1;
|
||||
|
||||
@@ -41,11 +41,11 @@ export const ContinueReadingTooltip = ({
|
||||
},
|
||||
},
|
||||
}}
|
||||
title={t(isFirstChapter ? 'chapter.action.read.start' : 'chapter.action.read.resume', {
|
||||
chapterNumber,
|
||||
title: name,
|
||||
scanlator,
|
||||
})}
|
||||
title={
|
||||
isFirstChapter
|
||||
? t`Start reading\n#${chapterNumber} — ${name}\n${scanlator}`
|
||||
: t`Continue reading\n#${chapterNumber} — ${name}\n${scanlator}`
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</CustomTooltip>
|
||||
|
||||
@@ -11,7 +11,6 @@ import Delete from '@mui/icons-material/Delete';
|
||||
import Download from '@mui/icons-material/Download';
|
||||
import RemoveDone from '@mui/icons-material/RemoveDone';
|
||||
import Done from '@mui/icons-material/Done';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
|
||||
import Label from '@mui/icons-material/Label';
|
||||
import { useMemo, useState } from 'react';
|
||||
@@ -20,6 +19,7 @@ import { Link } from 'react-router-dom';
|
||||
import SyncIcon from '@mui/icons-material/Sync';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import { AwaitableComponent } from 'awaitable-component';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Mangas } from '@/features/manga/services/Mangas.ts';
|
||||
import { SelectableCollectionReturnType } from '@/base/collection/hooks/useSelectableCollection.ts';
|
||||
import { MenuItem } from '@/base/components/menu/MenuItem.tsx';
|
||||
@@ -60,7 +60,7 @@ export const MangaActionMenuItems = ({
|
||||
onClose,
|
||||
setHideMenu,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
const [isTrackDialogOpen, setIsTrackDialogOpen] = useState(false);
|
||||
|
||||
@@ -108,7 +108,7 @@ export const MangaActionMenuItems = ({
|
||||
return (
|
||||
<>
|
||||
{!!handleSelection && isSingleMode && (
|
||||
<MenuItem onClick={handleSelect} Icon={CheckBoxOutlineBlank} title={t('chapter.action.label.select')} />
|
||||
<MenuItem onClick={handleSelect} Icon={CheckBoxOutlineBlank} title={t`Select`} />
|
||||
)}
|
||||
{shouldShowMenuItem(!isFullyDownloaded) && (
|
||||
<NestedMenuItem
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
*/
|
||||
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Button from '@mui/material/Button';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { MangaCardMode } from '@/features/manga/Manga.types.ts';
|
||||
import { MediaQuery } from '@/base/utils/MediaQuery.tsx';
|
||||
import { useMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts';
|
||||
@@ -42,7 +42,7 @@ export const MangaBadges = ({
|
||||
downloadCount?: number;
|
||||
mode: MangaCardMode;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
const isTouchDevice = MediaQuery.useIsTouchDevice();
|
||||
|
||||
@@ -69,7 +69,7 @@ export const MangaBadges = ({
|
||||
}}
|
||||
color={isInLibrary ? 'error' : 'primary'}
|
||||
>
|
||||
{t(isInLibrary ? 'manga.action.library.remove.label.action' : 'manga.button.add_to_library')}
|
||||
{isInLibrary ? t`Remove from the library` : t`Add To Library`}
|
||||
</Button>
|
||||
)}
|
||||
{inLibraryIndicator && isInLibrary && (
|
||||
@@ -77,7 +77,7 @@ export const MangaBadges = ({
|
||||
className="source-manga-library-state-indicator"
|
||||
sx={{ backgroundColor: 'primary.dark', color: 'primary.contrastText', p: 0.3 }}
|
||||
>
|
||||
{t('manga.button.in_library')}
|
||||
{t`In Library`}
|
||||
</Typography>
|
||||
)}
|
||||
{((showUnreadBadge && mode === 'default') || mode === 'duplicate') && (unread ?? 0) > 0 && (
|
||||
|
||||
@@ -10,7 +10,7 @@ import React, { ForwardedRef, Ref, useCallback, useLayoutEffect, useMemo, useRef
|
||||
import Grid, { GridTypeMap } from '@mui/material/Grid';
|
||||
import Box, { BoxProps } from '@mui/material/Box';
|
||||
import { GridItemProps } from 'react-virtuoso';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
|
||||
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
|
||||
import { MangaCard } from '@/features/manga/components/cards/MangaCard.tsx';
|
||||
@@ -209,7 +209,7 @@ export const MangaGrid: React.FC<IMangaGridProps> = ({
|
||||
retry,
|
||||
gridWrapperProps,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
const { navBarWidth } = useNavBarContext();
|
||||
const {
|
||||
@@ -313,7 +313,7 @@ export const MangaGrid: React.FC<IMangaGridProps> = ({
|
||||
return (
|
||||
<EmptyViewAbsoluteCentered
|
||||
noFaces={noFaces}
|
||||
message={message ?? t('manga.error.label.no_mangas_found')}
|
||||
message={message ?? t`No manga found`}
|
||||
messageExtra={messageExtra}
|
||||
retry={retry}
|
||||
/>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { BaseSyntheticEvent, ChangeEvent, useMemo, ForwardedRef } from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
@@ -14,6 +13,7 @@ import IconButton from '@mui/material/IconButton';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import { PopupState } from 'material-ui-popup-state/hooks';
|
||||
import { bindTrigger } from 'material-ui-popup-state';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||
import { SelectableCollectionReturnType } from '@/base/collection/hooks/useSelectableCollection.ts';
|
||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||
@@ -34,7 +34,7 @@ export const MangaOptionButton = ({
|
||||
popupState: PopupState;
|
||||
ref?: ForwardedRef<HTMLButtonElement | null>;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
const bindTriggerProps = useMemo(() => bindTrigger(popupState), [popupState]);
|
||||
|
||||
@@ -59,7 +59,7 @@ export const MangaOptionButton = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<CustomTooltip title={t(selected ? 'global.button.deselect' : 'global.button.select')}>
|
||||
<CustomTooltip title={selected ? t`Deselect` : t`Select`}>
|
||||
<Checkbox {...MUIUtil.preventRippleProp()} checked={selected} onChange={handleSelectionChange} />
|
||||
</CustomTooltip>
|
||||
);
|
||||
@@ -67,7 +67,7 @@ export const MangaOptionButton = ({
|
||||
|
||||
if (asCheckbox) {
|
||||
return (
|
||||
<CustomTooltip title={t('global.button.options')}>
|
||||
<CustomTooltip title={t`Options`}>
|
||||
<IconButton
|
||||
ref={ref}
|
||||
{...MUIUtil.preventRippleProp(bindTriggerProps, { onClick: preventDefaultAction })}
|
||||
@@ -80,7 +80,7 @@ export const MangaOptionButton = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<CustomTooltip title={t('global.button.options')}>
|
||||
<CustomTooltip title={t`Options`}>
|
||||
<Button
|
||||
ref={ref}
|
||||
{...MUIUtil.preventRippleProp(bindTriggerProps, { onClick: preventDefaultAction })}
|
||||
|
||||
@@ -15,13 +15,13 @@ import ListItemText from '@mui/material/ListItemText';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import SyncAltIcon from '@mui/icons-material/SyncAlt';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||
import { AwaitableComponent } from 'awaitable-component';
|
||||
import ColorLensIcon from '@mui/icons-material/ColorLens';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||
@@ -40,7 +40,7 @@ interface IProps {
|
||||
}
|
||||
|
||||
export const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
const theme = useTheme();
|
||||
const isLargeScreen = useMediaQuery(theme.breakpoints.up('sm'));
|
||||
@@ -78,7 +78,7 @@ export const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
|
||||
<>
|
||||
{isLargeScreen && (
|
||||
<>
|
||||
<CustomTooltip title={t('manga.label.reload_from_source')} disabled={refreshing}>
|
||||
<CustomTooltip title={t`Reload data from source`} disabled={refreshing}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
onRefresh();
|
||||
@@ -90,7 +90,7 @@ export const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
|
||||
</IconButton>
|
||||
</CustomTooltip>
|
||||
{settings.mangaDynamicColorSchemes && (
|
||||
<CustomTooltip title={t('settings.appearance.manga_dynamic_color_schemes.save')}>
|
||||
<CustomTooltip title={t`Save dynamic color theme`}>
|
||||
<IconButton onClick={saveDynamicColorTheme} color="inherit">
|
||||
<ColorLensIcon />
|
||||
</IconButton>
|
||||
@@ -98,7 +98,7 @@ export const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
|
||||
)}
|
||||
{manga.inLibrary && (
|
||||
<>
|
||||
<CustomTooltip title={t('global.button.migrate')}>
|
||||
<CustomTooltip title={t`Migrate`}>
|
||||
<Link
|
||||
to={AppRoutes.migrate.childRoutes.search.path(
|
||||
manga.sourceId,
|
||||
@@ -113,7 +113,7 @@ export const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
|
||||
</IconButton>
|
||||
</Link>
|
||||
</CustomTooltip>
|
||||
<CustomTooltip title={t('manga.label.edit_categories')}>
|
||||
<CustomTooltip title={t`Edit manga categories`}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
openCategorySelection();
|
||||
@@ -158,14 +158,14 @@ export const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
|
||||
<ListItemIcon>
|
||||
<Refresh fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t('manga.label.reload_from_source')}</ListItemText>
|
||||
<ListItemText>{t`Reload data from source`}</ListItemText>
|
||||
</MenuItem>
|
||||
{settings.mangaDynamicColorSchemes && (
|
||||
<MenuItem onClick={saveDynamicColorTheme}>
|
||||
<ListItemIcon>
|
||||
<ColorLensIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t('settings.appearance.manga_dynamic_color_schemes.save')}</ListItemText>
|
||||
<ListItemText>{t`Save dynamic color theme`}</ListItemText>
|
||||
</MenuItem>
|
||||
)}
|
||||
{manga.inLibrary && [
|
||||
@@ -179,7 +179,7 @@ export const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
|
||||
<ListItemIcon>
|
||||
<SyncAltIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t('migrate.title')}</ListItemText>
|
||||
<ListItemText>{t`Migrate`}</ListItemText>
|
||||
</MenuItem>,
|
||||
<MenuItem
|
||||
key="categories"
|
||||
@@ -191,7 +191,7 @@ export const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
|
||||
<ListItemIcon>
|
||||
<Label fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t('manga.label.edit_categories')}</ListItemText>
|
||||
<ListItemText>{t`Edit manga categories`}</ListItemText>
|
||||
</MenuItem>,
|
||||
]}
|
||||
</Menu>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
import PlayArrow from '@mui/icons-material/PlayArrow';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { StyledFab } from '@/base/components/buttons/StyledFab.tsx';
|
||||
import { Chapters } from '@/features/chapter/services/Chapters.ts';
|
||||
import {
|
||||
@@ -31,7 +31,7 @@ export function ResumeFab({
|
||||
ChapterNameInfo &
|
||||
ChapterScanlatorInfo;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
const { sourceOrder, name, chapterNumber, scanlator } = chapter;
|
||||
const isFirstChapter = sourceOrder === 1;
|
||||
@@ -51,7 +51,7 @@ export function ResumeFab({
|
||||
state={Chapters.getReaderOpenChapterLocationState(chapter)}
|
||||
>
|
||||
<PlayArrow />
|
||||
{isFirstChapter ? t('global.button.start') : t('global.button.resume')}
|
||||
{isFirstChapter ? t`Start` : t`Resume`}
|
||||
</StyledFab>
|
||||
</ContinueReadingTooltip>
|
||||
);
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import SyncIcon from '@mui/icons-material/Sync';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PopupState, { bindDialog, bindTrigger } from 'material-ui-popup-state';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { plural } from '@lingui/core/macro';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { makeToast } from '@/base/utils/Toast.ts';
|
||||
import { TrackManga } from '@/features/tracker/components/TrackManga.tsx';
|
||||
@@ -23,7 +24,7 @@ import { MangaTrackRecordInfo } from '@/features/manga/Manga.types.ts';
|
||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||
|
||||
export const TrackMangaButton = ({ manga }: { manga: MangaTrackRecordInfo & Pick<MangaType, 'title'> }) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const trackerList = requestManager.useGetTrackerList<GetTrackersSettingsQuery>(GET_TRACKERS_SETTINGS);
|
||||
@@ -35,7 +36,7 @@ export const TrackMangaButton = ({ manga }: { manga: MangaTrackRecordInfo & Pick
|
||||
|
||||
const handleClick = (openPopup: () => void) => {
|
||||
if (trackerList.error) {
|
||||
makeToast(t('tracking.error.label.could_not_load_track_info'), 'error', trackerList.error?.toString());
|
||||
makeToast(t`Could not load track info`, 'error', trackerList.error?.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -60,8 +61,11 @@ export const TrackMangaButton = ({ manga }: { manga: MangaTrackRecordInfo & Pick
|
||||
>
|
||||
{trackersInUse.length ? <CheckIcon /> : <SyncIcon />}
|
||||
{trackersInUse.length
|
||||
? t('manga.button.track.active', { count: trackersInUse.length })
|
||||
: t('manga.button.track.start')}
|
||||
? plural(trackersInUse.length, {
|
||||
one: '# Tracker',
|
||||
other: '# Tracker',
|
||||
})
|
||||
: t`Tracking`}
|
||||
</CustomButton>
|
||||
{popupState.isOpen && (
|
||||
<Dialog {...bindDialog(popupState)} maxWidth="md" fullWidth scroll="paper">
|
||||
|
||||
@@ -10,14 +10,14 @@ import FavoriteIcon from '@mui/icons-material/Favorite';
|
||||
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { ComponentProps, ReactNode, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { t as translate } from 'i18next';
|
||||
import Link from '@mui/material/Link';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import ButtonGroup from '@mui/material/ButtonGroup';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { t as translate } from '@lingui/core/macro';
|
||||
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||
import { makeToast } from '@/base/utils/Toast.ts';
|
||||
import { Mangas } from '@/features/manga/services/Mangas.ts';
|
||||
@@ -136,11 +136,11 @@ const MangaButtonsContainer = styled('div')(({ theme }) => ({
|
||||
}));
|
||||
|
||||
const OpenSourceButton = ({ url }: { url?: string | null }) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<CustomTooltip title={t('global.button.open_browser')} disabled={!url}>
|
||||
<CustomTooltip title={t`Open in browser`} disabled={!url}>
|
||||
<CustomButtonIcon
|
||||
size="medium"
|
||||
disabled={!url}
|
||||
@@ -153,7 +153,7 @@ const OpenSourceButton = ({ url }: { url?: string | null }) => {
|
||||
<IconBrowser />
|
||||
</CustomButtonIcon>
|
||||
</CustomTooltip>
|
||||
<CustomTooltip title={t('global.button.open_webview')} disabled={!url}>
|
||||
<CustomTooltip title={t`Open in WebView`} disabled={!url}>
|
||||
<CustomButtonIcon
|
||||
size="medium"
|
||||
disabled={!url}
|
||||
@@ -172,11 +172,11 @@ const OpenSourceButton = ({ url }: { url?: string | null }) => {
|
||||
|
||||
function getSourceName(source?: Pick<SourceType, 'id' | 'displayName'> | null): string {
|
||||
if (!source) {
|
||||
return translate('global.label.unknown');
|
||||
return translate`Unknown`;
|
||||
}
|
||||
|
||||
if (Sources.isLocalSource(source)) {
|
||||
return translate('source.local_source.title');
|
||||
return translate`Local source`;
|
||||
}
|
||||
|
||||
return source.displayName ?? source.id;
|
||||
@@ -215,7 +215,7 @@ export const MangaDetails = ({
|
||||
};
|
||||
mode: MangaLocationState['mode'];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
const {
|
||||
settings: { mangaThumbnailBackdrop, mangaDynamicColorSchemes },
|
||||
@@ -223,16 +223,16 @@ export const MangaDetails = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (!manga.source) {
|
||||
makeToast(translate('source.error.label.source_not_found'), 'error');
|
||||
makeToast(t`Could not find source. Check your installed extensions.`, 'error');
|
||||
}
|
||||
}, [manga.source]);
|
||||
}, [manga.source, t]);
|
||||
|
||||
const { updateLibraryState } = useManageMangaLibraryState(manga);
|
||||
|
||||
const copyTitle = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(manga.title);
|
||||
makeToast(t('global.label.copied_clipboard'), 'info');
|
||||
makeToast(t`Copied to clipboard`, 'info');
|
||||
} catch (e) {
|
||||
defaultPromiseErrorHandler('MangaDetails::copyTitleLongPress')(e);
|
||||
}
|
||||
@@ -250,7 +250,7 @@ export const MangaDetails = ({
|
||||
{manga.title}
|
||||
</Typography>
|
||||
</SearchLink>
|
||||
<CustomTooltip title={t('global.button.copy')}>
|
||||
<CustomTooltip title={t`Copy`}>
|
||||
<IconButton onClick={copyTitle} color="inherit">
|
||||
<ContentCopyIcon fontSize="small" />
|
||||
</IconButton>
|
||||
@@ -258,21 +258,18 @@ export const MangaDetails = ({
|
||||
</Stack>
|
||||
{manga.author && (
|
||||
<Metadata
|
||||
title={t('manga.label.author')}
|
||||
title={t`Author`}
|
||||
value={valuesToJoinedSearchLinks(Mangas.getAuthors(manga), manga.source?.id, mode)}
|
||||
/>
|
||||
)}
|
||||
{manga.artist && (
|
||||
<Metadata
|
||||
title={t('manga.label.artist')}
|
||||
title={t`Artist`}
|
||||
value={valuesToJoinedSearchLinks(Mangas.getArtists(manga), manga.source?.id, mode)}
|
||||
/>
|
||||
)}
|
||||
<Metadata
|
||||
title={t('manga.label.status')}
|
||||
value={t(MANGA_STATUS_TO_TRANSLATION[manga.status])}
|
||||
/>
|
||||
<Metadata title={t('source.title_one')} value={getSourceName(manga.source)} />
|
||||
<Metadata title={t`Status`} value={t(MANGA_STATUS_TO_TRANSLATION[manga.status])} />
|
||||
<Metadata title={t`Source`} value={getSourceName(manga.source)} />
|
||||
</MetadataContainer>
|
||||
</ThumbnailMetadataWrapper>
|
||||
<MangaButtonsContainer>
|
||||
@@ -282,7 +279,7 @@ export const MangaDetails = ({
|
||||
variant={manga.inLibrary ? 'contained' : 'outlined'}
|
||||
>
|
||||
{manga.inLibrary ? <FavoriteIcon /> : <FavoriteBorderIcon />}
|
||||
{manga.inLibrary ? t('manga.button.in_library') : t('manga.button.add_to_library')}
|
||||
{manga.inLibrary ? t`In Library` : t`Add To Library`}
|
||||
</CustomButton>
|
||||
<TrackMangaButton manga={manga} />
|
||||
<OpenSourceButton url={manga.realUrl} />
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import gql from 'graphql-tag';
|
||||
import { AwaitableComponent } from 'awaitable-component';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { makeToast } from '@/base/utils/Toast.ts';
|
||||
import { getMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts';
|
||||
@@ -27,7 +27,7 @@ export const useManageMangaLibraryState = (
|
||||
manga: Pick<MangaType, 'id' | 'title'> & Partial<Pick<MangaType, 'inLibrary'>>,
|
||||
confirmRemoval: boolean = false,
|
||||
) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
|
||||
const [isInLibrary, setIsInLibrary] = useState(!!manga.inLibrary);
|
||||
|
||||
@@ -42,10 +42,10 @@ export const useManageMangaLibraryState = (
|
||||
updateManga: { inLibrary: true },
|
||||
updateMangaCategories: { addToCategories, removeFromCategories },
|
||||
})
|
||||
.response.then(() => makeToast(t('library.info.label.added_to_library'), 'success'))
|
||||
.response.then(() => makeToast(t`Added manga to library!`, 'success'))
|
||||
.then(() => setIsInLibrary(true))
|
||||
.catch((e) => {
|
||||
makeToast(t('library.error.label.add_to_library'), 'error', getErrorMessage(e));
|
||||
makeToast(t`Could not add manga to library!`, 'error', getErrorMessage(e));
|
||||
});
|
||||
},
|
||||
[manga.id],
|
||||
@@ -55,10 +55,12 @@ export const useManageMangaLibraryState = (
|
||||
if (confirmRemoval) {
|
||||
await Confirmation.show(
|
||||
{
|
||||
title: t('global.label.are_you_sure'),
|
||||
message: t('manga.action.library.remove.dialog.label.message', { title: manga.title }),
|
||||
title: t`Are you sure?`,
|
||||
message: t`You are about to remove "${manga.title}" from your library`,
|
||||
actions: {
|
||||
confirm: { title: t('global.button.remove') },
|
||||
confirm: {
|
||||
title: t`Remove`,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ id: `manga-library-state-remove-${manga.id}` },
|
||||
@@ -83,7 +85,7 @@ export const useManageMangaLibraryState = (
|
||||
showAddToLibraryCategorySelectDialog = (await getMetadataServerSettings())
|
||||
.showAddToLibraryCategorySelectDialog;
|
||||
} catch (e) {
|
||||
makeToast(t('global.error.label.failed_to_load_data'), 'error', getErrorMessage(e));
|
||||
makeToast(t`Unable to load data`, 'error', getErrorMessage(e));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -98,7 +100,7 @@ export const useManageMangaLibraryState = (
|
||||
GetCategoriesBaseQueryVariables
|
||||
>(GET_CATEGORIES_BASE).response;
|
||||
} catch (e) {
|
||||
makeToast(t('category.error.label.request_failure'), 'error', getErrorMessage(e));
|
||||
makeToast(t`Could not load categories`, 'error', getErrorMessage(e));
|
||||
return;
|
||||
}
|
||||
const userCreatedCategories = Categories.getUserCreated(categories.data.categories.nodes);
|
||||
@@ -109,15 +111,20 @@ export const useManageMangaLibraryState = (
|
||||
try {
|
||||
duplicatedLibraryMangas = await Mangas.getDuplicateLibraryMangas(manga.title).response;
|
||||
} catch (e) {
|
||||
const errorMessage = getErrorMessage(e);
|
||||
await Confirmation.show(
|
||||
{
|
||||
title: t('global.error.label.failed_to_load_data'),
|
||||
message: t('manga.action.library.add.dialog.duplicate.label.failure', {
|
||||
error: getErrorMessage(e),
|
||||
}),
|
||||
title: t`Unable to load data`,
|
||||
message: t`Could not check for duplicated manga in your library.\n\nError: ${errorMessage}`,
|
||||
actions: {
|
||||
extra: { show: true, title: t('global.button.retry'), contain: true },
|
||||
confirm: { title: t('global.button.add') },
|
||||
extra: {
|
||||
show: true,
|
||||
title: t`Retry`,
|
||||
contain: true,
|
||||
},
|
||||
confirm: {
|
||||
title: t`Add`,
|
||||
},
|
||||
},
|
||||
onExtra: () =>
|
||||
update().catch(
|
||||
@@ -132,16 +139,18 @@ export const useManageMangaLibraryState = (
|
||||
if (doDuplicatesExist) {
|
||||
await Confirmation.show(
|
||||
{
|
||||
title: t('global.label.are_you_sure'),
|
||||
message: t('manga.action.library.add.dialog.duplicate.label.info'),
|
||||
title: t`Are you sure?`,
|
||||
message: t`You have an entry in your library with the same name.`,
|
||||
actions: {
|
||||
extra: {
|
||||
show: true,
|
||||
title: t('migrate.dialog.action.button.show_entry'),
|
||||
title: t`Show entry`,
|
||||
contain: true,
|
||||
link: AppRoutes.manga.path(duplicatedLibraryMangas!.data.mangas.nodes[0].id),
|
||||
},
|
||||
confirm: { title: t('global.button.add') },
|
||||
confirm: {
|
||||
title: t`Add`,
|
||||
},
|
||||
},
|
||||
onExtra: () => {},
|
||||
},
|
||||
|
||||
@@ -12,9 +12,9 @@ import IconButton from '@mui/material/IconButton';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Box from '@mui/material/Box';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { isNetworkRequestInFlight } from '@apollo/client/core/networkStatus';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { ChapterList } from '@/features/chapter/components/ChapterList.tsx';
|
||||
@@ -30,7 +30,7 @@ import { useAppTitleAndAction } from '@/features/navigation-bar/hooks/useAppTitl
|
||||
import { MangaLocationState } from '@/features/manga/Manga.types.ts';
|
||||
|
||||
export const Manga: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useLingui();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const { mode } = useLocation<MangaLocationState>().state ?? {};
|
||||
|
||||
@@ -61,7 +61,7 @@ export const Manga: React.FC = () => {
|
||||
}, [manga]);
|
||||
|
||||
useAppTitleAndAction(
|
||||
manga?.title ?? t('manga.title_one'),
|
||||
manga?.title ?? t`Manga`,
|
||||
<Stack
|
||||
direction="row"
|
||||
sx={{
|
||||
@@ -72,7 +72,7 @@ export const Manga: React.FC = () => {
|
||||
<CustomTooltip
|
||||
title={
|
||||
<>
|
||||
{t('manga.error.label.request_failure')}
|
||||
{t`Could not load manga`}
|
||||
<br />
|
||||
{getErrorMessage(error)}
|
||||
</>
|
||||
@@ -94,12 +94,7 @@ export const Manga: React.FC = () => {
|
||||
);
|
||||
|
||||
if (error && !manga) {
|
||||
return (
|
||||
<EmptyViewAbsoluteCentered
|
||||
message={t('manga.error.label.request_failure')}
|
||||
messageExtra={getErrorMessage(error)}
|
||||
/>
|
||||
);
|
||||
return <EmptyViewAbsoluteCentered message={t`Could not load manga`} messageExtra={getErrorMessage(error)} />;
|
||||
}
|
||||
return (
|
||||
<Box sx={{ display: { md: 'flex' }, overflow: 'hidden' }}>
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import i18next, { t as translate } from 'i18next';
|
||||
import { DocumentNode, Unmasked } from '@apollo/client/core';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { i18n } from '@/i18n';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import {
|
||||
ChapterConditionInput,
|
||||
@@ -557,11 +558,12 @@ export class Mangas {
|
||||
|
||||
try {
|
||||
await Confirmation.show({
|
||||
title: translate('global.label.are_you_sure'),
|
||||
message: translate(confirmationMessage, { count: itemCount }),
|
||||
title: t`Are you sure?`,
|
||||
/* lingui-extract-ignore */
|
||||
message: i18n.t({ ...confirmationMessage, values: { count: itemCount } }),
|
||||
actions: {
|
||||
confirm: {
|
||||
title: translate('global.button.ok'),
|
||||
title: t`Ok`,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -571,10 +573,15 @@ export class Mangas {
|
||||
}
|
||||
|
||||
await fnToExecute();
|
||||
makeToast(translate(MANGA_ACTION_TO_TRANSLATION[action].success, { count: itemCount }), 'success');
|
||||
makeToast(
|
||||
/* lingui-extract-ignore */
|
||||
i18n.t({ ...MANGA_ACTION_TO_TRANSLATION[action].success, values: { count: itemCount } }),
|
||||
'success',
|
||||
);
|
||||
} catch (e) {
|
||||
makeToast(
|
||||
translate(MANGA_ACTION_TO_TRANSLATION[action].error, { count: itemCount }),
|
||||
/* lingui-extract-ignore */
|
||||
i18n.t({ ...MANGA_ACTION_TO_TRANSLATION[action].error, values: { count: itemCount } }),
|
||||
'error',
|
||||
getErrorMessage(e),
|
||||
);
|
||||
@@ -650,9 +657,14 @@ export class Mangas {
|
||||
const translateMangaTagsByMangaTypeEntries = Object.entries(MANGA_TAGS_BY_MANGA_TYPE).map(
|
||||
([mangaType, tags]) => [
|
||||
mangaType,
|
||||
['en', i18next.language, manga.source?.lang]
|
||||
['en', i18n.locale, manga.source?.lang]
|
||||
.filter((lng) => !!lng)
|
||||
.flatMap((language) => tags.flatMap((tag) => translate(tag, { lng: language }))),
|
||||
.flatMap((language) =>
|
||||
tags.flatMap((tag) =>
|
||||
/* lingui-extract-ignore */
|
||||
i18n.t({ ...tag, values: { lng: language } }),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
const translatedMangaTagsByMangaType = Object.fromEntries(translateMangaTagsByMangaTypeEntries) as Record<
|
||||
|
||||
Reference in New Issue
Block a user