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,18 +6,18 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { SortBy, SortOrder } from '@/features/migration/Migration.types.ts';
import { TranslationKey } from '@/base/Base.types.ts';
export const sortByToTranslationKey: Record<SortBy, TranslationKey> = {
[SortBy.SOURCE_NAME]: 'migrate.sort.by_source_name',
[SortBy.MANGA_COUNT]: 'migrate.sort.by_manga_count',
export const sortByToTranslation: Record<SortBy, MessageDescriptor> = {
[SortBy.SOURCE_NAME]: msg`By source name`,
[SortBy.MANGA_COUNT]: msg`By manga count`,
};
export const sortOrderToTranslationKey: Record<SortBy, TranslationKey> = {
[SortOrder.ASC]: 'global.sort.label.asc',
[SortOrder.DESC]: 'global.sort.label.desc',
export const sortOrderToTranslation: Record<SortBy, MessageDescriptor> = {
[SortOrder.ASC]: msg`Ascending`,
[SortOrder.DESC]: msg`Descending`,
};
export const DEFAULT_SORT_SETTINGS = {

View File

@@ -11,11 +11,11 @@ import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import Button from '@mui/material/Button';
import { useTranslation } from 'react-i18next';
import Stack from '@mui/material/Stack';
import { Link, useNavigate, useParams } from 'react-router-dom';
import { useState } from 'react';
import FormGroup from '@mui/material/FormGroup';
import { useLingui } from '@lingui/react/macro';
import { CheckboxInput } from '@/base/components/inputs/CheckboxInput.tsx';
import { Mangas } from '@/features/manga/services/Mangas.ts';
import { makeToast } from '@/base/utils/Toast.ts';
@@ -29,7 +29,7 @@ import { MigrateMode } from '@/features/manga/Manga.types.ts';
import { AppRoutes } from '@/base/AppRoute.constants.ts';
export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrateTo: number; onClose: () => void }) => {
const { t } = useTranslation();
const { t } = useLingui();
const navigate = useNavigate();
@@ -51,7 +51,7 @@ export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrat
throw new Error(`MigrateDialog::migrate: unexpected mangaId "${mangaId}"`);
}
makeToast(t('migrate.label.info'), 'info');
makeToast(t`Migrating manga…`, 'info');
setIsMigrationInProcess(true);
@@ -72,30 +72,30 @@ export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrat
return (
<Dialog open fullWidth onClose={onClose}>
<DialogTitle>{t('migrate.dialog.title')}</DialogTitle>
<DialogTitle>{t`Select data to include`}</DialogTitle>
<DialogContent dividers>
<FormGroup>
<CheckboxInput
disabled={isMigrationInProcess}
label={t('chapter.title_one')}
label={t`Chapter`}
checked={migrateChapters}
onChange={(_, checked) => setMigrationFlag('migrateChapters', checked)}
/>
<CheckboxInput
disabled={isMigrationInProcess}
label={t('category.title.category_one')}
label={t`Category`}
checked={migrateCategories}
onChange={(_, checked) => setMigrationFlag('migrateCategories', checked)}
/>
<CheckboxInput
disabled={isMigrationInProcess}
label={t('tracking.title')}
label={t`Tracking`}
checked={migrateTracking}
onChange={(_, checked) => setMigrationFlag('migrateTracking', checked)}
/>
<CheckboxInput
disabled={isMigrationInProcess}
label={t('migrate.dialog.label.delete_downloaded')}
label={t`Delete downloaded`}
checked={deleteChapters}
onChange={(_, checked) => setMigrationFlag('deleteChapters', checked)}
/>
@@ -114,17 +114,17 @@ export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrat
component={Link}
to={AppRoutes.manga.path(mangaIdToMigrateTo)}
>
{t('migrate.dialog.action.button.show_entry')}
{t`Show entry`}
</Button>
<Stack direction="row">
<Button disabled={isMigrationInProcess} onClick={onClose}>
{t('global.button.cancel')}
{t`Cancel`}
</Button>
<Button disabled={isMigrationInProcess} onClick={() => migrate('copy')}>
{t('global.button.copy')}
{t`Copy`}
</Button>
<Button disabled={isMigrationInProcess} onClick={() => migrate('migrate')}>
{t('global.button.migrate')}
{t`Migrate`}
</Button>
</Stack>
</Stack>

View File

@@ -12,7 +12,7 @@ import CardActionArea from '@mui/material/CardActionArea';
import Chip from '@mui/material/Chip';
import Typography from '@mui/material/Typography';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { useLingui } from '@lingui/react/macro';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { GetMigratableSourcesQuery } from '@/lib/graphql/generated/graphql.ts';
import { translateExtensionLanguage } from '@/features/extension/Extensions.utils.ts';
@@ -26,10 +26,10 @@ export type TMigratableSource = NonNullable<GetMigratableSourcesQuery['mangas'][
// TODO - cleanup source/extension components
export const MigrationCard = ({ id, name, lang, iconUrl, mangaCount }: TMigratableSource) => {
const { t } = useTranslation();
const { t } = useLingui();
const isLocalSource = Number(id) === 0;
const sourceName = isLocalSource ? t('source.local_source.title') : name;
const sourceName = isLocalSource ? t`Local source` : name;
return (
<Card>

View File

@@ -8,7 +8,7 @@
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { useLingui } from '@lingui/react/macro';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { TMigratableSource } from '@/features/migration/components/MigrationCard.tsx';
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
@@ -25,7 +25,7 @@ import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import { useAppTitleAndAction } from '@/features/navigation-bar/hooks/useAppTitleAndAction.ts';
export const Migrate = () => {
const { t } = useTranslation();
const { t } = useLingui();
const { sourceId: paramSourceId } = useParams<{ sourceId: string }>();
@@ -68,7 +68,7 @@ export const Migrate = () => {
});
useAppTitleAndAction(
name ?? sourceId ?? t('migrate.title'),
name ?? sourceId ?? t`Migrate`,
<GridLayouts gridLayout={gridLayout} onChange={setGridLayout} />,
[gridLayout],
);
@@ -96,7 +96,7 @@ export const Migrate = () => {
const error = (hasErrorSource ? sourceError : mangasError)!;
return (
<EmptyViewAbsoluteCentered
message={t('global.error.label.failed_to_load_data')}
message={t`Unable to load data`}
messageExtra={getErrorMessage(error)}
retry={() => {
if (hasErrorSource) {

View File

@@ -6,7 +6,6 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useTranslation } from 'react-i18next';
import { useMemo } from 'react';
import List from '@mui/material/List';
import Stack from '@mui/material/Stack';
@@ -15,6 +14,7 @@ import SortByAlphaIcon from '@mui/icons-material/SortByAlpha';
import TagIcon from '@mui/icons-material/Tag';
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
import { useLingui } from '@lingui/react/macro';
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
@@ -23,7 +23,7 @@ import { MigrationCard, TMigratableSource } from '@/features/migration/component
import { StyledGroupItemWrapper } from '@/base/components/virtuoso/StyledGroupItemWrapper.tsx';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { SortBy, SortOrder, SortSettings, TMigratableSourcesResult } from '@/features/migration/Migration.types.ts';
import { sortByToTranslationKey, sortOrderToTranslationKey } from '@/features/migration/Migration.constants.ts';
import { sortByToTranslation, sortOrderToTranslation } from '@/features/migration/Migration.constants.ts';
import {
createUpdateMetadataServerSettings,
useMetadataServerSettings,
@@ -75,14 +75,14 @@ const getMigratableSources = (
};
export const Migration = ({ tabsMenuHeight }: { tabsMenuHeight: number }) => {
const { t } = useTranslation();
const { t } = useLingui();
const { appBarHeight } = useNavBarContext();
const {
settings: { migrateSortSettings },
} = useMetadataServerSettings();
const updateMetadataServerSettings = createUpdateMetadataServerSettings<'migrateSortSettings'>((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)),
);
const { sortBy, sortOrder } = migrateSortSettings;
@@ -101,7 +101,7 @@ export const Migration = ({ 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('Migration::refetch'))}
/>
@@ -123,7 +123,7 @@ export const Migration = ({ tabsMenuHeight }: { tabsMenuHeight: number }) => {
zIndex: 1,
}}
>
<CustomTooltip title={t(sortByToTranslationKey[sortBy])}>
<CustomTooltip title={t(sortByToTranslation[sortBy])}>
<IconButton
color="inherit"
onClick={() =>
@@ -133,7 +133,7 @@ export const Migration = ({ tabsMenuHeight }: { tabsMenuHeight: number }) => {
{sortBy ? <TagIcon /> : <SortByAlphaIcon />}
</IconButton>
</CustomTooltip>
<CustomTooltip title={t(sortOrderToTranslationKey[sortOrder])}>
<CustomTooltip title={t(sortOrderToTranslation[sortOrder])}>
<IconButton
color="inherit"
onClick={() =>