feat: add i18n

This commit is contained in:
Sergey Krylov 2024-07-09 07:27:06 +03:00
parent 0e16b4f70a
commit 3516914ad3
43 changed files with 413 additions and 113 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -38,12 +38,16 @@
"@mui/material": "5.15.19", "@mui/material": "5.15.19",
"@react-spring/web": "9.7.3", "@react-spring/web": "9.7.3",
"classnames": "2.5.1", "classnames": "2.5.1",
"i18next": "23.11.5",
"i18next-browser-languagedetector": "8.0.0",
"i18next-http-backend": "2.5.2",
"notistack": "3.0.1", "notistack": "3.0.1",
"prop-types": "15.8.1", "prop-types": "15.8.1",
"react": "18.3.1", "react": "18.3.1",
"react-dnd": "16.0.1", "react-dnd": "16.0.1",
"react-dnd-html5-backend": "16.0.1", "react-dnd-html5-backend": "16.0.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-i18next": "14.1.2",
"react-router-dom": "6.23.1", "react-router-dom": "6.23.1",
"workbox-core": "7.1.0", "workbox-core": "7.1.0",
"workbox-precaching": "7.1.0", "workbox-precaching": "7.1.0",

View File

@ -0,0 +1,4 @@
{
"title": "Analyze Page",
"toolbar": "Analyze Page Toolbar"
}

View File

@ -0,0 +1,5 @@
{
"title": "Something goes wrong",
"toolbar": "Unhandled error",
"404-toolbar": "Page does not exist"
}

View File

@ -0,0 +1,4 @@
{
"title": "Payments Page",
"toolbar": "Payments Page Toolbar"
}

View File

@ -0,0 +1,4 @@
{
"title": "Schedule Page",
"toolbar": "Schedule Page Toolbar"
}

View File

@ -0,0 +1,5 @@
{
"analyze-page-link": "Analyze",
"payments-page-link": "Payments list",
"schedule-page-link": "Schedule"
}

View File

@ -0,0 +1,3 @@
{
"language": "English"
}

View File

@ -0,0 +1,4 @@
{
"title": "Аналитика",
"toolbar": "Доска аналитики"
}

View File

@ -0,0 +1,5 @@
{
"title": "Что-то пошло не так",
"toolbar": "Произошла непредвиденная ошибка",
"404-toolbar": "Такой страницы не существует"
}

View File

@ -0,0 +1,4 @@
{
"title": "Платежи",
"toolbar": "Доска платежей"
}

View File

@ -0,0 +1,4 @@
{
"title": "График платежей",
"toolbar": "Доска графика платежей"
}

View File

@ -0,0 +1,5 @@
{
"analyze-page-link": "Аналитика",
"payments-page-link": "Список платежей",
"schedule-page-link": "График платежей"
}

View File

@ -0,0 +1,3 @@
{
"language": "Русский"
}

View File

@ -1,8 +1,8 @@
import CalendarMonthIcon from '@mui/icons-material/CalendarMonth'; import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted'; import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted';
import PieChartIcon from '@mui/icons-material/PieChart'; import PieChartIcon from '@mui/icons-material/PieChart';
import CircularProgress from '@mui/material/CircularProgress';
import { Suspense } from 'react'; import { Suspense } from 'react';
import { useTranslation } from 'react-i18next';
import { createBrowserRouter, Navigate, RouterProvider as RRDRouterProvider } from 'react-router-dom'; import { createBrowserRouter, Navigate, RouterProvider as RRDRouterProvider } from 'react-router-dom';
import { AnalyzePage, AnalyzePageToolbar } from 'pages/AnalyzePage'; import { AnalyzePage, AnalyzePageToolbar } from 'pages/AnalyzePage';
@ -11,6 +11,7 @@ import { PaymentsPage, PaymentsPageToolbar } from 'pages/PaymentsPage';
import { SchedulePage, SchedulePageToolbar } from 'pages/SchedulePage'; import { SchedulePage, SchedulePageToolbar } from 'pages/SchedulePage';
import { AsideMenuLayout } from 'widgets/AsideMenuLayout'; import { AsideMenuLayout } from 'widgets/AsideMenuLayout';
import { PageLoader } from 'widgets/PageLoader';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import type { RouteObject } from 'react-router-dom'; import type { RouteObject } from 'react-router-dom';
@ -23,76 +24,78 @@ type PageListItem = {
Toolbar?: ReactNode; Toolbar?: ReactNode;
}; };
const pages: PageListItem[] = [ const RouterProvider = () => {
{ const { t } = useTranslation('sidebar');
icon: <PieChartIcon />,
title: 'Аналитика',
path: '/analyze',
Content: (
<Suspense fallback={<CircularProgress />}>
<AnalyzePage />
</Suspense>
),
Toolbar: <AnalyzePageToolbar />,
},
{
icon: <CalendarMonthIcon />,
title: 'График платежей',
path: '/schedule',
Content: (
<Suspense fallback={<CircularProgress />}>
<SchedulePage />
</Suspense>
),
Toolbar: <SchedulePageToolbar />,
},
{
icon: <FormatListBulletedIcon />,
title: 'Список платежей',
path: '/payments',
Content: (
<Suspense fallback={<CircularProgress />}>
<PaymentsPage />
</Suspense>
),
Toolbar: <PaymentsPageToolbar />,
},
];
const routes: RouteObject[] = pages.map((page) => ({ const pages: PageListItem[] = [
path: page.path, {
element: ( icon: <PieChartIcon />,
title: t('analyze-page-link'),
path: '/analyze',
Content: <AnalyzePage />,
Toolbar: <AnalyzePageToolbar />,
},
{
icon: <CalendarMonthIcon />,
title: t('schedule-page-link'),
path: '/schedule',
Content: <SchedulePage />,
Toolbar: <SchedulePageToolbar />,
},
{
icon: <FormatListBulletedIcon />,
title: t('payments-page-link'),
path: '/payments',
Content: <PaymentsPage />,
Toolbar: <PaymentsPageToolbar />,
},
];
const Fallback = (
<AsideMenuLayout <AsideMenuLayout
Content={page.Content} Content={<PageLoader />}
pages={pages} pages={pages}
Toolbar={page.Toolbar}
/> />
), );
errorElement: ( const routes: RouteObject[] = pages.map((page) => ({
<AsideMenuLayout path: page.path,
Content={<ErrorPage />} element: (
pages={pages} <Suspense fallback={Fallback}>
Toolbar={<ErrorPageToolbar />} <AsideMenuLayout
/> Content={page.Content}
), pages={pages}
})); Toolbar={page.Toolbar}
const router = createBrowserRouter([ />
...routes, </Suspense>
{ ),
path: '/',
element: <Navigate replace to="/analyze" />,
errorElement: ( errorElement: (
<AsideMenuLayout <Suspense fallback={Fallback}>
Content={<ErrorPage type="404" />} <AsideMenuLayout
pages={pages} Content={<ErrorPage />}
Toolbar={<ErrorPageToolbar type="404" />} pages={pages}
/> Toolbar={<ErrorPageToolbar />}
/>
</Suspense>
), ),
}, }));
]);
const RouterProvider = () => ( const router = createBrowserRouter([
<RRDRouterProvider router={router} /> ...routes,
); {
path: '/',
element: <Navigate replace to="/analyze" />,
errorElement: (
<AsideMenuLayout
Content={<ErrorPage type="404" />}
pages={pages}
Toolbar={<ErrorPageToolbar type="404" />}
/>
),
},
]);
return (
<RRDRouterProvider router={router} />
);
};
export default RouterProvider; export default RouterProvider;

View File

@ -0,0 +1 @@
export { default as LangSwitcher } from './ui/LangSwitcher.ui';

View File

@ -0,0 +1,30 @@
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import i18next from 'i18n';
import { LANGUAGE } from 'shared/constants';
import type { FC, ChangeEventHandler } from 'react';
const LangSwitcher: FC = () => {
const { i18n } = useTranslation();
const onChangedHandler = useCallback<ChangeEventHandler<HTMLSelectElement>>(async (e) => {
await i18next.changeLanguage(e.target.value);
}, []);
return (
<select value={i18n.language} onChange={onChangedHandler}>
{Object.entries(LANGUAGE).map(([lang, params]) => (
<option key={lang} value={lang}>
{params.emoji}
{params.name}
</option>
))}
</select>
);
};
export default LangSwitcher;

22
src/i18n.ts Normal file
View File

@ -0,0 +1,22 @@
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { initReactI18next } from 'react-i18next';
import type { HttpBackendOptions } from 'i18next-http-backend';
// eslint-disable-next-line @typescript-eslint/no-floating-promises
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init<HttpBackendOptions>({
fallbackLng: 'en',
debug: process.env.NODE_ENV === 'development',
load: 'currentOnly',
backend: {
loadPath: 'locales/{{lng}}/{{ns}}.json',
},
});
export default i18n;

View File

@ -1,3 +1,4 @@
import './i18n';
import 'shared/styles/fonts/fonts.css'; import 'shared/styles/fonts/fonts.css';
import React from 'react'; import React from 'react';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';

View File

@ -1,10 +1,16 @@
import { useTranslation } from 'react-i18next';
import type { DetailedHTMLProps, FC, HTMLAttributes } from 'react'; import type { DetailedHTMLProps, FC, HTMLAttributes } from 'react';
type AnalyzePageProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>; type AnalyzePageProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
const AnalyzePage: FC<AnalyzePageProps> = (props) => ( const AnalyzePage: FC<AnalyzePageProps> = (props) => {
<div {...props}> const { t } = useTranslation('analyze-page');
Analyze page
</div> return (
); <div {...props}>
{t('title')}
</div>
);
};
export default AnalyzePage; export default AnalyzePage;

View File

@ -1,15 +1,20 @@
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { useTranslation } from 'react-i18next';
import type { ToolbarProps } from '@mui/material/Toolbar/Toolbar'; import type { ToolbarProps } from '@mui/material/Toolbar/Toolbar';
import type { FC } from 'react'; import type { FC } from 'react';
type AnalyzePageToolbarProps = ToolbarProps; type AnalyzePageToolbarProps = ToolbarProps;
const AnalyzePageToolbar: FC<AnalyzePageToolbarProps> = (props) => ( const AnalyzePageToolbar: FC<AnalyzePageToolbarProps> = (props) => {
<Toolbar {...props}> const { t } = useTranslation('analyze-page');
<Typography noWrap component="div" variant="h6">
Analyze Page Toolbar return (
</Typography> <Toolbar {...props}>
</Toolbar> <Typography noWrap component="div" variant="h6">
); {t('toolbar')}
</Typography>
</Toolbar>
);
};
export default AnalyzePageToolbar; export default AnalyzePageToolbar;

View File

@ -1,5 +1,6 @@
import { Grid } from '@mui/material'; import { Grid } from '@mui/material';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { useTranslation } from 'react-i18next';
import _404Image from 'public/assets/images/404.png'; import _404Image from 'public/assets/images/404.png';
import OopsImage from 'public/assets/images/oops.png'; import OopsImage from 'public/assets/images/oops.png';
@ -16,6 +17,8 @@ const ErrorPage: FC<ErrorPageProps> = (props) => {
...restProps ...restProps
} = props; } = props;
const { t } = useTranslation('error-page');
if (type === 'ErrorBoundary') { if (type === 'ErrorBoundary') {
return ( return (
<Grid <Grid
@ -28,7 +31,7 @@ const ErrorPage: FC<ErrorPageProps> = (props) => {
> >
<Grid item> <Grid item>
<Typography variant="h5"> <Typography variant="h5">
Что-то пошло не так {t('title')}
</Typography> </Typography>
</Grid> </Grid>

View File

@ -1,5 +1,6 @@
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { useTranslation } from 'react-i18next';
import type { ToolbarProps } from '@mui/material/Toolbar/Toolbar'; import type { ToolbarProps } from '@mui/material/Toolbar/Toolbar';
import type { FC } from 'react'; import type { FC } from 'react';
@ -8,17 +9,19 @@ export type ErrorPageToolbarProps = ToolbarProps & {
type?: '404' | 'ErrorBoundary'; type?: '404' | 'ErrorBoundary';
}; };
const TITLE: Record<NonNullable<ErrorPageToolbarProps['type']>, string> = {
404: 'Страницы не существует',
ErrorBoundary: 'Непредвиденная ошибка',
};
const ErrorPageToolbar: FC<ErrorPageToolbarProps> = (props) => { const ErrorPageToolbar: FC<ErrorPageToolbarProps> = (props) => {
const { const {
type = 'ErrorBoundary', type = 'ErrorBoundary',
...restProps ...restProps
} = props; } = props;
const { t } = useTranslation('error-page');
const TITLE: Record<NonNullable<ErrorPageToolbarProps['type']>, string> = {
404: t('404-toolbar'),
ErrorBoundary: t('toolbar'),
};
return ( return (
<Toolbar {...restProps}> <Toolbar {...restProps}>
<Typography noWrap component="div" variant="h6"> <Typography noWrap component="div" variant="h6">

View File

@ -1,10 +1,16 @@
import { useTranslation } from 'react-i18next';
import type { DetailedHTMLProps, FC, HTMLAttributes } from 'react'; import type { DetailedHTMLProps, FC, HTMLAttributes } from 'react';
export type PaymentsPageProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>; export type PaymentsPageProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
const PaymentsPage: FC<PaymentsPageProps> = (props) => ( const PaymentsPage: FC<PaymentsPageProps> = (props) => {
<div {...props}> const { t } = useTranslation('payments-page');
Payments Page
</div> return (
); <div {...props}>
{t('title')}
</div>
);
};
export default PaymentsPage; export default PaymentsPage;

View File

@ -1,16 +1,21 @@
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { useTranslation } from 'react-i18next';
import type { ToolbarProps } from '@mui/material/Toolbar/Toolbar'; import type { ToolbarProps } from '@mui/material/Toolbar/Toolbar';
import type { FC } from 'react'; import type { FC } from 'react';
export type PaymentsPageToolbarProps = ToolbarProps; export type PaymentsPageToolbarProps = ToolbarProps;
const PaymentsPageToolbar: FC<PaymentsPageToolbarProps> = (props) => ( const PaymentsPageToolbar: FC<PaymentsPageToolbarProps> = (props) => {
<Toolbar {...props}> const { t } = useTranslation('payments-page');
<Typography noWrap component="div" variant="h6">
Payments Page Toolbar return (
</Typography> <Toolbar {...props}>
</Toolbar> <Typography noWrap component="div" variant="h6">
); {t('toolbar')}
</Typography>
</Toolbar>
);
};
export default PaymentsPageToolbar; export default PaymentsPageToolbar;

View File

@ -1,10 +1,16 @@
import { useTranslation } from 'react-i18next';
import type { DetailedHTMLProps, FC, HTMLAttributes } from 'react'; import type { DetailedHTMLProps, FC, HTMLAttributes } from 'react';
export type SchedulePageProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>; export type SchedulePageProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
const SchedulePage: FC<SchedulePageProps> = (props) => ( const SchedulePage: FC<SchedulePageProps> = (props) => {
<div {...props}> const { t } = useTranslation('schedule-page');
Schedule Page
</div> return (
); <div {...props}>
{t('title')}
</div>
);
};
export default SchedulePage; export default SchedulePage;

View File

@ -1,16 +1,21 @@
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { useTranslation } from 'react-i18next';
import type { ToolbarProps } from '@mui/material/Toolbar/Toolbar'; import type { ToolbarProps } from '@mui/material/Toolbar/Toolbar';
import type { FC } from 'react'; import type { FC } from 'react';
type SchedulePageToolbarProps = ToolbarProps; type SchedulePageToolbarProps = ToolbarProps;
const SchedulePageToolbar: FC<SchedulePageToolbarProps> = (props) => ( const SchedulePageToolbar: FC<SchedulePageToolbarProps> = (props) => {
<Toolbar {...props}> const { t } = useTranslation('schedule-page');
<Typography noWrap component="div" variant="h6">
Schedule Page Toolbar return (
</Typography> <Toolbar {...props}>
</Toolbar> <Typography noWrap component="div" variant="h6">
); {t('toolbar')}
</Typography>
</Toolbar>
);
};
export default SchedulePageToolbar; export default SchedulePageToolbar;

View File

@ -1,5 +1,5 @@
export { DAYS_IN_WEEK, MONTHS, WEEK_DAYS } from './date'; export { DAYS_IN_WEEK, MONTHS, WEEK_DAYS } from './date';
export { MONTHS_LABEL, WEEK_DAYS_LABEL } from './locale'; export { MONTHS_LABEL, WEEK_DAYS_LABEL, LANGUAGE } from './locale';
export type { export type {
MonthsKeys, MonthsKeys,
MonthsValues, MonthsValues,

View File

@ -136,3 +136,14 @@ export const WEEK_DAYS_LABEL: Record<WeekDaysValues, WeekDaysLabelLocale> = {
}, },
}, },
}; };
export const LANGUAGE = {
ru: {
name: 'Русский',
emoji: '🇷🇺',
},
en: {
name: 'English',
emoji: '🇬🇧',
},
};

View File

@ -7,6 +7,8 @@ import { animated, useTransition } from '@react-spring/web';
import { useContext } from 'react'; import { useContext } from 'react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { LangSwitcher } from 'features/LangSwitcher';
import { DesktopAppBar, MobileAppBar } from './components/AppBar'; import { DesktopAppBar, MobileAppBar } from './components/AppBar';
import { AsideMenuContext, AsideMenuProvider } from './components/AsideMenuProvider'; import { AsideMenuContext, AsideMenuProvider } from './components/AsideMenuProvider';
import { DesktopDrawer } from './components/DesktopDrawer'; import { DesktopDrawer } from './components/DesktopDrawer';
@ -15,6 +17,7 @@ import { MobileDrawer } from './components/MobileDrawer';
import type { MenuItemProps } from './components/MenuItem/ui/MenuItem.ui'; import type { MenuItemProps } from './components/MenuItem/ui/MenuItem.ui';
import type { BoxProps } from '@mui/material/Box/Box'; import type { BoxProps } from '@mui/material/Box/Box';
// eslint-disable-next-line import/max-dependencies
import type { FC, ReactNode } from 'react'; import type { FC, ReactNode } from 'react';
export const DESKTOP_DRAWER_WIDTH = 250; export const DESKTOP_DRAWER_WIDTH = 250;
@ -32,7 +35,6 @@ const AsideMenuLayout: FC<AsideMenuLayoutProps> = (props) => {
Toolbar, Toolbar,
...restProps ...restProps
} = props; } = props;
const { desktopDrawerOpen, setMobileDrawerOpen } = useContext(AsideMenuContext); const { desktopDrawerOpen, setMobileDrawerOpen } = useContext(AsideMenuContext);
const theme = useTheme(); const theme = useTheme();
const isMobileView = useMediaQuery(theme.breakpoints.down('sm')); const isMobileView = useMediaQuery(theme.breakpoints.down('sm'));
@ -49,7 +51,7 @@ const AsideMenuLayout: FC<AsideMenuLayoutProps> = (props) => {
const AnimatedContent = ( const AnimatedContent = (
<> <>
{transitions((styles) => ( {transitions((styles) => (
<animated.div style={styles}> <animated.div style={{ ...styles, height: '100%' }}>
{Content} {Content}
</animated.div> </animated.div>
))} ))}
@ -85,6 +87,8 @@ const AsideMenuLayout: FC<AsideMenuLayoutProps> = (props) => {
</MobileDrawer> </MobileDrawer>
{AnimatedToolBar} {AnimatedToolBar}
<LangSwitcher />
</MobileAppBar> </MobileAppBar>
) )
} }
@ -113,14 +117,16 @@ const AsideMenuLayout: FC<AsideMenuLayoutProps> = (props) => {
</List> </List>
</DesktopDrawer> </DesktopDrawer>
<Box sx={{ flexGrow: 1 }}> <Box sx={{ flexGrow: 1, height: '100vh' }}>
{ Boolean(Toolbar) && ( { Boolean(Toolbar) && (
<DesktopAppBar position="static"> <DesktopAppBar position="static">
{AnimatedToolBar} {AnimatedToolBar}
<LangSwitcher />
</DesktopAppBar> </DesktopAppBar>
)} )}
<Box component="main" sx={{ p: 3 }}> <Box component="main" sx={{ p: 3, height: '100%' }}>
{AnimatedContent} {AnimatedContent}
</Box> </Box>
</Box> </Box>

View File

@ -0,0 +1 @@
export { default as PageLoader } from './ui/PageLoader.ui';

View File

@ -0,0 +1,20 @@
import CircularProgress from '@mui/material/CircularProgress';
import { styled } from '@mui/material/styles';
const CenteredDiv = styled('div')(
() => ({
width: '100%',
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}),
);
const PageLoader = () => (
<CenteredDiv>
<CircularProgress />
</CenteredDiv>
);
export default PageLoader;

114
yarn.lock
View File

@ -2671,7 +2671,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/runtime@npm:^7.11.2": "@babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.23.2":
version: 7.24.7 version: 7.24.7
resolution: "@babel/runtime@npm:7.24.7" resolution: "@babel/runtime@npm:7.24.7"
dependencies: dependencies:
@ -7769,6 +7769,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"cross-fetch@npm:4.0.0":
version: 4.0.0
resolution: "cross-fetch@npm:4.0.0"
dependencies:
node-fetch: "npm:^2.6.12"
checksum: 10c0/386727dc4c6b044746086aced959ff21101abb85c43df5e1d151547ccb6f338f86dec3f28b9dbddfa8ff5b9ec8662ed2263ad4607a93b2dc354fb7fe3bbb898a
languageName: node
linkType: hard
"cross-spawn@npm:7.0.3, cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": "cross-spawn@npm:7.0.3, cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3":
version: 7.0.3 version: 7.0.3
resolution: "cross-spawn@npm:7.0.3" resolution: "cross-spawn@npm:7.0.3"
@ -10579,6 +10588,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"html-parse-stringify@npm:^3.0.1":
version: 3.0.1
resolution: "html-parse-stringify@npm:3.0.1"
dependencies:
void-elements: "npm:3.1.0"
checksum: 10c0/159292753d48b84d216d61121054ae5a33466b3db5b446e2ffc093ac077a411a99ce6cbe0d18e55b87cf25fa3c5a86c4d8b130b9719ec9b66623259000c72c15
languageName: node
linkType: hard
"html-webpack-plugin@npm:5.6.0": "html-webpack-plugin@npm:5.6.0":
version: 5.6.0 version: 5.6.0
resolution: "html-webpack-plugin@npm:5.6.0" resolution: "html-webpack-plugin@npm:5.6.0"
@ -10751,6 +10769,33 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"i18next-browser-languagedetector@npm:8.0.0":
version: 8.0.0
resolution: "i18next-browser-languagedetector@npm:8.0.0"
dependencies:
"@babel/runtime": "npm:^7.23.2"
checksum: 10c0/08a7c747ec18a0743b54390a0b42836d814b15fb49c92b90c259298e6a6e7e001ff9df99d0fd308283a34fceea53b4d9053aa1695a37f696bc04160017bbb524
languageName: node
linkType: hard
"i18next-http-backend@npm:2.5.2":
version: 2.5.2
resolution: "i18next-http-backend@npm:2.5.2"
dependencies:
cross-fetch: "npm:4.0.0"
checksum: 10c0/57cfbeeea40a751134e1521fac89b14b780c9ad87b74a4f7d7168a88a8b603efed9f5df455dddbb408f7e268eae5aa0a6888e47b9f10cb9482d812d4d6997ffa
languageName: node
linkType: hard
"i18next@npm:23.11.5":
version: 23.11.5
resolution: "i18next@npm:23.11.5"
dependencies:
"@babel/runtime": "npm:^7.23.2"
checksum: 10c0/b0bec64250a3e529d4c51e2fc511406a85c5dde3d005d3aabe919551ca31dfc0a8f5490bf6e44649822e895a1fa91a58092d112367669cd11b2eb89e6ba90d1a
languageName: node
linkType: hard
"iconv-lite@npm:0.4.24": "iconv-lite@npm:0.4.24":
version: 0.4.24 version: 0.4.24
resolution: "iconv-lite@npm:0.4.24" resolution: "iconv-lite@npm:0.4.24"
@ -13039,6 +13084,9 @@ __metadata:
eslint-config-ksv741: "npm:0.0.3" eslint-config-ksv741: "npm:0.0.3"
eslint-import-resolver-webpack: "npm:0.13.8" eslint-import-resolver-webpack: "npm:0.13.8"
husky: "npm:9.0.11" husky: "npm:9.0.11"
i18next: "npm:23.11.5"
i18next-browser-languagedetector: "npm:8.0.0"
i18next-http-backend: "npm:2.5.2"
identity-obj-proxy: "npm:3.0.0" identity-obj-proxy: "npm:3.0.0"
jest: "npm:29.7.0" jest: "npm:29.7.0"
jest-environment-jsdom: "npm:29.7.0" jest-environment-jsdom: "npm:29.7.0"
@ -13050,6 +13098,7 @@ __metadata:
react-dnd: "npm:16.0.1" react-dnd: "npm:16.0.1"
react-dnd-html5-backend: "npm:16.0.1" react-dnd-html5-backend: "npm:16.0.1"
react-dom: "npm:18.3.1" react-dom: "npm:18.3.1"
react-i18next: "npm:14.1.2"
react-router-dom: "npm:6.23.1" react-router-dom: "npm:6.23.1"
standard-version: "npm:9.5.0" standard-version: "npm:9.5.0"
ts-jest: "npm:29.1.4" ts-jest: "npm:29.1.4"
@ -13156,6 +13205,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"node-fetch@npm:^2.6.12":
version: 2.7.0
resolution: "node-fetch@npm:2.7.0"
dependencies:
whatwg-url: "npm:^5.0.0"
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
checksum: 10c0/b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8
languageName: node
linkType: hard
"node-forge@npm:^1": "node-forge@npm:^1":
version: 1.3.1 version: 1.3.1
resolution: "node-forge@npm:1.3.1" resolution: "node-forge@npm:1.3.1"
@ -14857,6 +14920,24 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react-i18next@npm:14.1.2":
version: 14.1.2
resolution: "react-i18next@npm:14.1.2"
dependencies:
"@babel/runtime": "npm:^7.23.9"
html-parse-stringify: "npm:^3.0.1"
peerDependencies:
i18next: ">= 23.2.3"
react: ">= 16.8.0"
peerDependenciesMeta:
react-dom:
optional: true
react-native:
optional: true
checksum: 10c0/cb8a83b3696639f083dc9f770d9d9e0681c0fe56f6b5fe24cd6facce08d363c37bd3440078e9d63abacabd7037b783e6b4e4d0c935de9c8dda7820bd4ef7e329
languageName: node
linkType: hard
"react-is@npm:^16.13.1, react-is@npm:^16.7.0": "react-is@npm:^16.13.1, react-is@npm:^16.7.0":
version: 16.13.1 version: 16.13.1
resolution: "react-is@npm:16.13.1" resolution: "react-is@npm:16.13.1"
@ -16587,6 +16668,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"tr46@npm:~0.0.3":
version: 0.0.3
resolution: "tr46@npm:0.0.3"
checksum: 10c0/047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11
languageName: node
linkType: hard
"trim-newlines@npm:^3.0.0": "trim-newlines@npm:^3.0.0":
version: 3.0.1 version: 3.0.1
resolution: "trim-newlines@npm:3.0.1" resolution: "trim-newlines@npm:3.0.1"
@ -17134,6 +17222,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"void-elements@npm:3.1.0":
version: 3.1.0
resolution: "void-elements@npm:3.1.0"
checksum: 10c0/0b8686f9f9aa44012e9bd5eabf287ae0cde409b9a2854c5a2335cb83920c957668ac5876e3f0d158dd424744ac411a7270e64128556b451ed3bec875ef18534d
languageName: node
linkType: hard
"w3c-xmlserializer@npm:^4.0.0": "w3c-xmlserializer@npm:^4.0.0":
version: 4.0.0 version: 4.0.0
resolution: "w3c-xmlserializer@npm:4.0.0" resolution: "w3c-xmlserializer@npm:4.0.0"
@ -17171,6 +17266,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"webidl-conversions@npm:^3.0.0":
version: 3.0.1
resolution: "webidl-conversions@npm:3.0.1"
checksum: 10c0/5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db
languageName: node
linkType: hard
"webidl-conversions@npm:^4.0.2": "webidl-conversions@npm:^4.0.2":
version: 4.0.2 version: 4.0.2
resolution: "webidl-conversions@npm:4.0.2" resolution: "webidl-conversions@npm:4.0.2"
@ -17415,6 +17517,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"whatwg-url@npm:^5.0.0":
version: 5.0.0
resolution: "whatwg-url@npm:5.0.0"
dependencies:
tr46: "npm:~0.0.3"
webidl-conversions: "npm:^3.0.0"
checksum: 10c0/1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5
languageName: node
linkType: hard
"whatwg-url@npm:^7.0.0": "whatwg-url@npm:^7.0.0":
version: 7.1.0 version: 7.1.0
resolution: "whatwg-url@npm:7.1.0" resolution: "whatwg-url@npm:7.1.0"