diff --git a/Dockerfile b/Dockerfile
index fd12b82..44805b2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1
# Build commands:
-# - $VERSION=0.6
+# - $VERSION=0.7
# - rm build -r -Force ; rm ./node_modules/.cache/babel-loader -r -Force ; yarn build
# - docker build --pull --rm -t oncharterliz/multi-frigate:latest -t oncharterliz/multi-frigate:$VERSION "."
# - docker image push --all-tags oncharterliz/multi-frigate
diff --git a/package.json b/package.json
index 7d67329..fb984ea 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,8 @@
"date-fns": "^3.3.1",
"dayjs": "^1.11.9",
"embla-carousel-react": "^8.0.0-rc10",
+ "i18next": "^23.10.1",
+ "i18next-browser-languagedetector": "^7.2.0",
"idb-keyval": "^6.2.1",
"jwt-decode": "^4.0.0",
"mantine-react-table": "^1.0.0-beta.25",
@@ -39,6 +41,7 @@
"react": "^18.2.0",
"react-device-detect": "^2.2.3",
"react-dom": "^18.2.0",
+ "react-i18next": "^14.1.0",
"react-oidc-context": "^2.2.2",
"react-router-dom": "^6.14.1",
"react-scripts": "5.0.1",
diff --git a/src/App.tsx b/src/App.tsx
index 15a8de1..df52dc4 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -62,6 +62,7 @@ function App() {
urlParams.delete('state');
urlParams.delete('session_state');
urlParams.delete('code');
+ urlParams.delete('iss');
navigate(`${location.pathname}${urlParams.toString() ? '?' + urlParams.toString() : ''}`, { replace: true })
}
diff --git a/src/AppBody.tsx b/src/AppBody.tsx
index 3da0dc4..d558902 100644
--- a/src/AppBody.tsx
+++ b/src/AppBody.tsx
@@ -1,14 +1,24 @@
-import React, { useContext, useState } from 'react';
-import { AppShell, useMantineTheme, } from "@mantine/core"
-import { HeaderAction } from './widgets/header/HeaderAction';
-import { headerLinks } from './widgets/header/header.links';
-import AppRouter from './router/AppRouter';
-import { Context } from '.';
-import SideBar from './shared/components/SideBar';
+import { AppShell, useMantineTheme, } from "@mantine/core";
import { observer } from 'mobx-react-lite';
+import { useContext, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Context } from '.';
+import AppRouter from './router/AppRouter';
+import { routesPath } from './router/routes.path';
+import SideBar from './shared/components/SideBar';
import { isProduction } from './shared/env.const';
+import { HeaderAction } from './widgets/header/HeaderAction';
const AppBody = () => {
+ const { t } = useTranslation()
+
+ const headerLinks = [
+ { link: routesPath.MAIN_PATH, label: t('header.home') },
+ { link: routesPath.SETTINGS_PATH, label: t('header.settings'), admin: true },
+ { link: routesPath.RECORDINGS_PATH, label: t('header.recordings') },
+ { link: routesPath.HOSTS_PATH, label: t('header.hostsConfig'), admin: true },
+ { link: routesPath.ACCESS_PATH, label: t('header.acessSettings'), admin: true },
+ ]
const { sideBarsStore } = useContext(Context)
diff --git a/src/index.tsx b/src/index.tsx
index b3f0426..6eb45c2 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -6,6 +6,7 @@ import RootStore from './shared/stores/root.store';
import { AuthProvider, AuthProviderProps } from 'react-oidc-context';
import { isProduction, oidpSettings } from './shared/env.const';
import { BrowserRouter } from 'react-router-dom';
+import './services/i18n';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
@@ -23,6 +24,7 @@ export const keycloakConfig: AuthProviderProps = {
params.delete('state');
params.delete('session_state');
params.delete('code');
+ params.delete('iss');
const newUrl = `${window.location.pathname}${params.toString() ? '?' + params.toString() : ''}`
window.history.replaceState({}, document.title, newUrl)
}
diff --git a/src/locales/en.ts b/src/locales/en.ts
new file mode 100644
index 0000000..ce5b35f
--- /dev/null
+++ b/src/locales/en.ts
@@ -0,0 +1,63 @@
+const en = {
+ header: {
+ home: 'Main',
+ settings: 'Settings',
+ recordings: 'Recordings',
+ hostsConfig: 'Frigate servers',
+ acessSettings: 'Access settings',
+ }, hostArr: {
+ host: 'Host',
+ name: 'Host name',
+ url: 'Address',
+ enabled: 'Enabled',
+ },
+ player: {
+ startVideo: 'Enable Video',
+ stopVideo: 'Disable Video',
+ object: 'Object',
+ duration: 'Duration',
+ startTime: 'Start',
+ endTime: 'End',
+ doubleClickToFullHint: 'Double click for fullscreen',
+ rating: 'Rating',
+ },
+ pleaseSelectRole: 'Please select Role',
+ pleaseSelectHost: 'Please select Host',
+ pleaseSelectCamera: 'Please select Camera',
+ pleaseSelectDate: 'Please select Date',
+ nothingHere: 'Nothing here',
+ allowed: 'Allowed',
+ notAllowed: 'Not allowed',
+ camera: 'Camera',
+ camersDoesNotExist: 'No cameras',
+ search: 'Search',
+ recordings: 'Recordings',
+ hour: 'Hour',
+ events: 'Events',
+ notHaveEvents: 'No events',
+ day: 'Day',
+ selectHost: 'Select host',
+ selectCamera: 'Select Camera',
+ selectRange: 'Select period',
+ changeTheme: "Change theme",
+ logout: "Logout",
+ enterQuantity: "Enter quantity:",
+ quantity: "Quantity",
+ tooltipСlose: "Press Enter",
+ hide: "Hide",
+ confirm: "Confirm",
+ save: "Save",
+ discard: "Cancel",
+ next: "Next",
+ back: "Back",
+ goToMainPage: "Return to main page",
+ retry: "Retry",
+ youCanRetryOrGoToMain: "You can retry or return to the main page",
+ errors: {
+ somthingGoesWrong: "Something went wrong",
+ 403: "Sorry, you do not have access",
+ 404: "Sorry, we cannot find that page",
+ }
+}
+
+export default en
\ No newline at end of file
diff --git a/src/locales/ru.ts b/src/locales/ru.ts
new file mode 100644
index 0000000..25e31ab
--- /dev/null
+++ b/src/locales/ru.ts
@@ -0,0 +1,64 @@
+const ru = {
+ header: {
+ home: 'Главная',
+ settings: 'Настройки',
+ recordings: 'Записи',
+ hostsConfig: 'Серверы Frigate',
+ acessSettings: 'Настройка доступа',
+ },
+ hostArr: {
+ host: 'Хост',
+ name: 'Имя хоста',
+ url: 'Адрес',
+ enabled: 'Включен',
+ },
+ player: {
+ startVideo: 'Вкл. Видео',
+ stopVideo: 'Выкл. Видео',
+ object: 'Объект',
+ duration: 'Длительность',
+ startTime: 'Начало',
+ endTime: 'Конец',
+ doubleClickToFullHint: 'Двойное нажатие мышью для полноэкранного просмотра',
+ rating: 'Рейтинг',
+ },
+ pleaseSelectRole: 'Пожалуйста выберите роль',
+ pleaseSelectHost: 'Пожалуйста выберите хост',
+ pleaseSelectCamera: 'Пожалуйста выберите камеру',
+ pleaseSelectDate: 'Пожалуйста выберите дату',
+ nothingHere: 'Ничего нет',
+ allowed: 'Разрешено',
+ notAllowed: 'Не разрешено',
+ camera: 'Камера',
+ camersDoesNotExist: 'Камер нет',
+ search: 'Поиск',
+ recordings: 'Записи',
+ hour: 'Час',
+ events: 'События',
+ notHaveEvents: 'Событий нет',
+ day: 'День',
+ selectHost:'Выбери хост',
+ selectCamera: 'Выбери камеру',
+ selectRange: 'Выбери период',
+ changeTheme: "Изменить тему",
+ logout: "Выйти",
+ enterQuantity: "Введите количество:",
+ quantity: "Количество",
+ tooltipСlose: "Нажмите Enter",
+ hide: "Скрыть",
+ confirm: "Подтвердить",
+ save: "Сохранить",
+ discard: "Отменить",
+ next: "Далее",
+ back: "Назад",
+ goToMainPage: "Вернуться на главную",
+ retry: "Повторить",
+ youCanRetryOrGoToMain: "Вы можете повторить или вернуться на главную",
+ errors: {
+ somthingGoesWrong: "Что-то пошло не так",
+ 403: "Извините, у вас нет доступа",
+ 404: "Извините, мы не можем найти такую страницу",
+ }
+}
+
+export default ru
\ No newline at end of file
diff --git a/src/pages/403.tsx b/src/pages/403.tsx
index 2397137..2952199 100644
--- a/src/pages/403.tsx
+++ b/src/pages/403.tsx
@@ -1,12 +1,13 @@
import { Button, Flex, Text } from '@mantine/core';
-import React, { useContext, useEffect, useRef } from 'react';
-import CogWheelWithText from '../shared/components/loaders/CogWheelWithText';
-import { strings } from '../shared/strings/strings';
-import { routesPath } from '../router/routes.path';
-import { Context } from '..';
import { observer } from 'mobx-react-lite';
+import { useContext, useEffect, useRef } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Context } from '..';
+import { routesPath } from '../router/routes.path';
+import CogWheelWithText from '../shared/components/loaders/CogWheelWithText';
const Forbidden = () => {
+ const { t } = useTranslation()
const executed = useRef(false)
const { sideBarsStore } = useContext(Context)
@@ -25,9 +26,9 @@ const Forbidden = () => {
return (
- {strings.errors[403]}
+ {t('errors.403')}
-
+
);
diff --git a/src/pages/404.tsx b/src/pages/404.tsx
index 94e0f8e..6a35057 100644
--- a/src/pages/404.tsx
+++ b/src/pages/404.tsx
@@ -1,12 +1,13 @@
import { Button, Flex, Text } from '@mantine/core';
import React, { useContext, useEffect, useRef } from 'react';
import CogWheelWithText from '../shared/components/loaders/CogWheelWithText';
-import { strings } from '../shared/strings/strings';
import { routesPath } from '../router/routes.path';
import { Context } from '..';
import { observer } from 'mobx-react-lite';
+import { useTranslation } from 'react-i18next';
const NotFound = () => {
+ const { t } = useTranslation()
const executed = useRef(false)
const { sideBarsStore } = useContext(Context)
@@ -25,9 +26,9 @@ const NotFound = () => {
return (
- {strings.errors[404]}
+ {t('errors.404')}
-
+
);
diff --git a/src/pages/AccessSettingsPage.tsx b/src/pages/AccessSettingsPage.tsx
index 37a9029..18eb94c 100644
--- a/src/pages/AccessSettingsPage.tsx
+++ b/src/pages/AccessSettingsPage.tsx
@@ -1,21 +1,22 @@
-import { useQuery } from '@tanstack/react-query';
-import React, { useContext, useEffect, useRef, useState } from 'react';
-import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
-import CenterLoader from '../shared/components/loaders/CenterLoader';
-import RetryErrorPage from './RetryErrorPage';
import { Flex, Group, Select, Text } from '@mantine/core';
-import { OneSelectItem } from '../shared/components/filters/OneSelectFilter';
import { useMediaQuery } from '@mantine/hooks';
-import { dimensions } from '../shared/dimensions/dimensions';
-import CamerasTransferList from '../shared/components/CamerasTransferList';
-import { Context } from '..';
-import { strings } from '../shared/strings/strings';
-import { useAdminRole } from '../hooks/useAdminRole';
-import Forbidden from './403';
+import { useQuery } from '@tanstack/react-query';
import { observer } from 'mobx-react-lite';
+import { useContext, useEffect, useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Context } from '..';
+import { useAdminRole } from '../hooks/useAdminRole';
+import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
+import CamerasTransferList from '../shared/components/CamerasTransferList';
+import { OneSelectItem } from '../shared/components/filters/OneSelectFilter';
+import CenterLoader from '../shared/components/loaders/CenterLoader';
+import { dimensions } from '../shared/dimensions/dimensions';
import { isProduction } from '../shared/env.const';
+import Forbidden from './403';
+import RetryErrorPage from './RetryErrorPage';
const AccessSettings = () => {
+ const { t } = useTranslation()
const executed = useRef(false)
const { data, isPending, isError, refetch } = useQuery({
queryKey: [frigateQueryKeys.getRoles],
@@ -49,7 +50,7 @@ const AccessSettings = () => {
if (!isProduction) console.log('AccessSettings rendered')
return (
- {strings.pleaseSelectRole}
+ {t('pleaseSelectRole')}
{!isMobile ? : <>>}
diff --git a/src/pages/RecordingsPage.tsx b/src/pages/RecordingsPage.tsx
index e944e4f..4819a45 100644
--- a/src/pages/RecordingsPage.tsx
+++ b/src/pages/RecordingsPage.tsx
@@ -10,6 +10,7 @@ import RecordingsFiltersRightSide from '../widgets/RecordingsFiltersRightSide';
import SelectedCameraList from '../widgets/SelectedCameraList';
import SelectedDayList from '../widgets/SelectedDayList';
import SelectedHostList from '../widgets/SelectedHostList';
+import { useTranslation } from 'react-i18next';
export const recordingsPageQuery = {
@@ -21,6 +22,7 @@ export const recordingsPageQuery = {
}
const RecordingsPage = () => {
+ const { t } = useTranslation()
const executed = useRef(false)
const { sideBarsStore, recordingsStore: recStore } = useContext(Context)
@@ -116,10 +118,10 @@ const RecordingsPage = () => {
return (
{!hostId ?
- Please select host
+ {t('pleaseSelectHost')}
: <>>}
{hostId && !(startDay && endDay) ?
- Please select date
+ {t('pleaseSelectDate')}
: <>>
}
diff --git a/src/pages/RetryErrorPage.tsx b/src/pages/RetryErrorPage.tsx
index 10be194..484449d 100644
--- a/src/pages/RetryErrorPage.tsx
+++ b/src/pages/RetryErrorPage.tsx
@@ -1,11 +1,11 @@
import { Flex, Button, Text } from '@mantine/core';
import React, { useContext, useEffect, useRef } from 'react';
import { routesPath } from '../router/routes.path';
-import { strings } from '../shared/strings/strings';
import { useNavigate } from 'react-router-dom';
import { ExclamationCogWheel } from '../shared/components/svg/ExclamationCogWheel';
import { Context } from '..';
import { observer } from 'mobx-react-lite';
+import { useTranslation } from 'react-i18next';
interface RetryErrorPageProps {
repeatVisible?: boolean
@@ -20,6 +20,7 @@ const RetryErrorPage = ({
mainVisible = true,
onRetry
}: RetryErrorPageProps) => {
+ const { t } = useTranslation()
const executed = useRef(false)
const navigate = useNavigate()
@@ -49,13 +50,13 @@ const RetryErrorPage = ({
return (
- {strings.errors.somthengGoesWrong}
+ {t('errors.somthingGoesWrong')}
{ExclamationCogWheel}
- {strings.youCanRetryOrGoToMain}
+ {t('youCanRetryOrGoToMain')}
- {repeatVisible ? : null}
- { backVisible ? : null }
- { mainVisible ? : null }
+ {repeatVisible ? : null}
+ { backVisible ? : null }
+ { mainVisible ? : null }
);
diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx
index e84f5ec..2a09b98 100644
--- a/src/pages/SettingsPage.tsx
+++ b/src/pages/SettingsPage.tsx
@@ -1,25 +1,26 @@
-import React, { useContext, useEffect, useRef, useState } from 'react';
-import {
- useQuery,
- useMutation,
- useQueryClient,
-} from '@tanstack/react-query'
-import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
-import CenterLoader from '../shared/components/loaders/CenterLoader';
-import RetryErrorPage from './RetryErrorPage';
import { Button, Flex, Space } from '@mantine/core';
-import { FloatingLabelInput } from '../shared/components/inputs/FloatingLabelInput';
-import { strings } from '../shared/strings/strings';
-import { dimensions } from '../shared/dimensions/dimensions';
import { useMediaQuery } from '@mantine/hooks';
-import { GetConfig } from '../services/frigate.proxy/frigate.schema';
+import {
+ useMutation,
+ useQuery,
+ useQueryClient,
+} from '@tanstack/react-query';
+import { observer } from 'mobx-react-lite';
+import React, { useContext, useEffect, useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
import { Context } from '..';
import { useAdminRole } from '../hooks/useAdminRole';
-import Forbidden from './403';
-import { observer } from 'mobx-react-lite';
+import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
+import { GetConfig } from '../services/frigate.proxy/frigate.schema';
+import { FloatingLabelInput } from '../shared/components/inputs/FloatingLabelInput';
+import CenterLoader from '../shared/components/loaders/CenterLoader';
+import { dimensions } from '../shared/dimensions/dimensions';
import { isProduction } from '../shared/env.const';
+import Forbidden from './403';
+import RetryErrorPage from './RetryErrorPage';
const SettingsPage = () => {
+ const { t } = useTranslation()
const executed = useRef(false)
const queryClient = useQueryClient()
const { isPending: configPending, error: configError, data, refetch } = useQuery({
@@ -126,8 +127,8 @@ const SettingsPage = () => {
))}
-
-
+
+
diff --git a/src/services/frigate.proxy/frigate.api.ts b/src/services/frigate.proxy/frigate.api.ts
index 4f334eb..574d35b 100644
--- a/src/services/frigate.proxy/frigate.api.ts
+++ b/src/services/frigate.proxy/frigate.api.ts
@@ -21,7 +21,7 @@ export const getToken = (): string | undefined => {
const instanceApi = axios.create({
baseURL: proxyURL.toString(),
- timeout: 60 * 1000,
+ timeout: 20 * 1000,
})
instanceApi.interceptors.request.use(
diff --git a/src/services/i18n.ts b/src/services/i18n.ts
new file mode 100644
index 0000000..ff9d4d9
--- /dev/null
+++ b/src/services/i18n.ts
@@ -0,0 +1,21 @@
+import i18n from 'i18next'
+import { initReactI18next } from 'react-i18next'
+import LanguageDetector from 'i18next-browser-languagedetector'
+import en from '../locales/en'
+import ru from '../locales/ru'
+
+i18n
+ .use(initReactI18next)
+ .use(LanguageDetector)
+ .init({
+ resources: {
+ en: { translation: en},
+ ru: { translation: ru},
+ },
+ fallbackLng: 'en',
+ interpolation: {
+ escapeValue: false,
+ },
+ })
+
+export default i18n
\ No newline at end of file
diff --git a/src/shared/components/CamerasTransferList.tsx b/src/shared/components/CamerasTransferList.tsx
index b35e8d3..8ff5d92 100644
--- a/src/shared/components/CamerasTransferList.tsx
+++ b/src/shared/components/CamerasTransferList.tsx
@@ -1,12 +1,11 @@
+import { Button, Flex, Text, TransferList, TransferListData, TransferListItem } from '@mantine/core';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
-import React, { useEffect, useState } from 'react';
+import { useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
import { frigateApi, frigateQueryKeys } from '../../services/frigate.proxy/frigate.api';
-import CogwheelLoader from './loaders/CogwheelLoader';
-import RetryError from './RetryError';
-import { TransferList, Text, TransferListData, TransferListProps, TransferListItem, Button, Flex } from '@mantine/core';
-import { OneSelectItem } from './filters/OneSelectFilter';
-import { strings } from '../strings/strings';
import { isProduction } from '../env.const';
+import RetryError from './RetryError';
+import CogwheelLoader from './loaders/CogwheelLoader';
interface CamerasTransferListProps {
roleId: string
@@ -15,6 +14,7 @@ interface CamerasTransferListProps {
const CamerasTransferList = ({
roleId,
}: CamerasTransferListProps) => {
+ const { t } = useTranslation()
const queryClient = useQueryClient()
const { data: cameras, isPending, isError, refetch } = useQuery({
queryKey: [frigateQueryKeys.getCamerasWHost, roleId],
@@ -50,7 +50,7 @@ const CamerasTransferList = ({
if (isPending) return
if (isError || !cameras) return
- if (cameras.length < 1) return {strings.camersDoesNotExist}
+ if (cameras.length < 1) return {t('camersDoesNotExist')}
const handleSave = () => {
@@ -65,8 +65,8 @@ const CamerasTransferList = ({
return (
<>
-
-
+
+
>
diff --git a/src/shared/components/SideBar.tsx b/src/shared/components/SideBar.tsx
index 32df7b7..fab5c86 100644
--- a/src/shared/components/SideBar.tsx
+++ b/src/shared/components/SideBar.tsx
@@ -1,10 +1,10 @@
import { Aside, Button, Navbar, createStyles } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { observer } from 'mobx-react-lite';
-import React, { useContext, useEffect, useRef, useState } from 'react';
+import React, { useContext, useEffect, useState } from 'react';
+import { useTranslation } from "react-i18next";
import { Context } from '../..';
import { dimensions } from '../dimensions/dimensions';
-import { strings } from '../strings/strings';
import { useMantineSize } from '../utils/mantine.size.convertor';
import { SideButton } from './SideButton';
@@ -29,6 +29,7 @@ const useStyles = createStyles((theme,
const SideBar = ({ isHidden, side, children }: SideBarProps) => {
+ const { t } = useTranslation()
const hideSizePx = useMantineSize(dimensions.hideSidebarsSize)
const initialVisible = () => {
const savedVisibility = localStorage.getItem(`sidebarVisible_${side}`);
@@ -97,7 +98,7 @@ const SideBar = ({ isHidden, side, children }: SideBarProps) => {
p={dimensions.hideSidebarsSize}
width={{ sm: 200, lg: 300, }}
>
-
+
{leftChildren}
:
@@ -105,7 +106,7 @@ const SideBar = ({ isHidden, side, children }: SideBarProps) => {
className={classes.aside}
p={dimensions.hideSidebarsSize}
width={{ sm: 200, lg: 300 }}>
-
+
{rightChildren}
}
diff --git a/src/shared/components/UserMenu.tsx b/src/shared/components/UserMenu.tsx
index 9443280..b1c4fe4 100644
--- a/src/shared/components/UserMenu.tsx
+++ b/src/shared/components/UserMenu.tsx
@@ -1,17 +1,25 @@
-import React from 'react';
-import { Avatar, Group, Menu, Text, Button, Flex } from "@mantine/core";
-import { useAuth } from 'react-oidc-context';
-import { strings } from '../strings/strings';
+import { Avatar, Button, Flex, Group, Menu, Text } from "@mantine/core";
import { useMediaQuery } from '@mantine/hooks';
+import { useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useAuth } from 'react-oidc-context';
+import { keycloakConfig } from '../..';
import { dimensions } from '../dimensions/dimensions';
import ColorSchemeToggle from './buttons/ColorSchemeToggle';
-import { keycloakConfig } from '../..';
interface UserMenuProps {
user: { name: string; image: string }
}
const UserMenu = ({ user }: UserMenuProps) => {
+
+ const { t, i18n } = useTranslation()
+
+ const languages = [
+ { lng: 'en', name: 'Eng' },
+ { lng: 'ru', name: 'Rus' },
+ ]
+
const auth = useAuth()
const isMiddleScreen = useMediaQuery(dimensions.middleScreenSize)
@@ -23,6 +31,24 @@ const UserMenu = ({ user }: UserMenuProps) => {
await auth.signoutRedirect({ post_logout_redirect_uri: keycloakConfig.redirect_uri, id_token_hint: id_token_hint })
}
+ const handleChangeLanguage = async (lng: string) => {
+ await i18n.changeLanguage(lng)
+ }
+
+ const languageSelector = useCallback(() => {
+ return languages.map(lang => (
+
+ ))
+ }, [i18n.resolvedLanguage])
+
return (
diff --git a/src/shared/components/accordion/CameraAccordion.tsx b/src/shared/components/accordion/CameraAccordion.tsx
index 401013f..56224ed 100644
--- a/src/shared/components/accordion/CameraAccordion.tsx
+++ b/src/shared/components/accordion/CameraAccordion.tsx
@@ -1,17 +1,18 @@
import { Accordion, Center, Loader } from '@mantine/core';
-import React, { useContext, useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';
-import { frigateQueryKeys, mapHostToHostname, proxyApi } from '../../../services/frigate.proxy/frigate.api';
-import DayAccordion from './DayAccordion';
import { observer } from 'mobx-react-lite';
+import { useContext, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
import { Context } from '../../..';
-import { getResolvedTimeZone, parseQueryDateToDate } from '../../utils/dateUtil';
-import RetryError from '../RetryError';
-import { strings } from '../../strings/strings';
+import { frigateQueryKeys, mapHostToHostname, proxyApi } from '../../../services/frigate.proxy/frigate.api';
import { RecordSummary } from '../../../types/record';
import { isProduction } from '../../env.const';
+import { getResolvedTimeZone, parseQueryDateToDate } from '../../utils/dateUtil';
+import RetryError from '../RetryError';
+import DayAccordion from './DayAccordion';
const CameraAccordion = () => {
+ const { t } = useTranslation()
const { recordingsStore: recStore } = useContext(Context)
const camera = recStore.openedCamera || recStore.filteredCamera
@@ -30,7 +31,7 @@ const CameraAccordion = () => {
const recodItem = (record: RecordSummary) => (
- {strings.day}: {record.day}
+ {t('day')}: {record.day}
diff --git a/src/shared/components/accordion/DayAccordionItem.tsx b/src/shared/components/accordion/DayAccordionItem.tsx
index 67d211f..365cc92 100644
--- a/src/shared/components/accordion/DayAccordionItem.tsx
+++ b/src/shared/components/accordion/DayAccordionItem.tsx
@@ -1,12 +1,12 @@
import { Accordion, Flex, Group, Text } from '@mantine/core';
import { IconExternalLink } from '@tabler/icons-react';
import { useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { routesPath } from '../../../router/routes.path';
import { proxyApi } from '../../../services/frigate.proxy/frigate.api';
import { RecordHour, RecordSummary } from '../../../types/record';
import { isProduction } from '../../env.const';
-import { strings } from '../../strings/strings';
import { getResolvedTimeZone, mapDateHourToUnixTime } from '../../utils/dateUtil';
import AccordionControlButton from '../buttons/AccordionControlButton';
import AccordionShareButton from '../buttons/AccordionShareButton';
@@ -30,6 +30,7 @@ const DayAccordionItem = ({
played,
openPlayer
}: DayAccordionItemProps) => {
+ const { t } = useTranslation()
const navigate = useNavigate()
const [playedURL, setPlayedUrl] = useState()
@@ -72,11 +73,11 @@ const DayAccordionItem = ({
- {strings.hour}: {hour}:00
+ {t('hour')}: {hour}:00
{recordHour.events > 0 ?
- {strings.events}: {recordHour.events}
+ {t('events')}: {recordHour.events}
:
- {strings.notHaveEvents}
+ {t('notHaveEvents')}
}
diff --git a/src/shared/components/accordion/DayEventsAccordion.tsx b/src/shared/components/accordion/DayEventsAccordion.tsx
index c673e4b..a82c73a 100644
--- a/src/shared/components/accordion/DayEventsAccordion.tsx
+++ b/src/shared/components/accordion/DayEventsAccordion.tsx
@@ -1,6 +1,6 @@
import { Accordion, Text } from '@mantine/core';
-import React, { Suspense, lazy, useState } from 'react';
-import { strings } from '../../strings/strings';
+import { Suspense, lazy, useState } from 'react';
+import { useTranslation } from 'react-i18next';
const EventsAccordion = lazy(() => import('./EventsAccordion'))
interface DayEventsAccordionProps {
@@ -14,6 +14,7 @@ const DayEventsAccordion = ({
hour,
qty,
}: DayEventsAccordionProps) => {
+ const { t } = useTranslation()
const [openedItem, setOpenedItem] = useState()
const handleClick = (value: string | null) => {
@@ -22,7 +23,7 @@ const DayEventsAccordion = ({
return (
- {strings.events}: {qty}
+ {t('events')}: {qty}
{openedItem === hour ?
diff --git a/src/shared/components/accordion/DayPanel.tsx b/src/shared/components/accordion/DayPanel.tsx
index f8f11ab..88b4d3f 100644
--- a/src/shared/components/accordion/DayPanel.tsx
+++ b/src/shared/components/accordion/DayPanel.tsx
@@ -1,8 +1,8 @@
import { Accordion, Center, Flex, Text } from '@mantine/core';
import VideoDownloader from '../../../widgets/VideoDownloader';
-import { strings } from '../../strings/strings';
import VideoPlayer from '../players/VideoPlayer';
import DayEventsAccordion from './DayEventsAccordion';
+import { useTranslation } from 'react-i18next';
interface DayPanelProps {
day: string,
@@ -27,6 +27,7 @@ const DayPanel = ({
startUnixTime,
endUnixTime,
}: DayPanelProps) => {
+ const { t } = useTranslation()
return (
{playedURL && playedURL === videoURL ? : <>>}
@@ -43,7 +44,7 @@ const DayPanel = ({
{events > 0 ?
:
- {strings.notHaveEvents}
+ {t('notHaveEvents')}
}
);
diff --git a/src/shared/components/accordion/EventPanel.tsx b/src/shared/components/accordion/EventPanel.tsx
index 47cb5cd..1aa6601 100644
--- a/src/shared/components/accordion/EventPanel.tsx
+++ b/src/shared/components/accordion/EventPanel.tsx
@@ -2,10 +2,10 @@ import { Button, Flex, Text } from '@mantine/core';
import { IconExternalLink } from '@tabler/icons-react';
import { proxyApi } from '../../../services/frigate.proxy/frigate.api';
import { EventFrigate } from '../../../types/event';
-import { strings } from '../../strings/strings';
import { getDurationFromTimestamps, unixTimeToDate } from '../../utils/dateUtil';
import BlobImage from '../images/BlobImage';
import VideoPlayer from '../players/VideoPlayer';
+import { useTranslation } from 'react-i18next';
interface EventPanelProps {
event: EventFrigate
@@ -20,6 +20,7 @@ const EventPanel = ({
videoURL,
playedURL,
}: EventPanelProps) => {
+ const { t } = useTranslation()
return (
<>
@@ -45,12 +46,12 @@ const EventPanel = ({
}
- {strings.camera}: {event.camera}
- {strings.player.object}: {event.label}
- {strings.player.startTime}: {unixTimeToDate(event.start_time)}
- {strings.player.duration}: {getDurationFromTimestamps(event.start_time, event.end_time)}
+ {t('camera')}: {event.camera}
+ {t('player.object')}: {event.label}
+ {t('player.startTime')}: {unixTimeToDate(event.start_time)}
+ {t('player.duration')}: {getDurationFromTimestamps(event.start_time, event.end_time)}
{!event.data?.score? <>> :
- {strings.player.rating}: {(event.data.score * 100).toFixed(2)}%
+ {t('player.rating')}: {(event.data.score * 100).toFixed(2)}%
}
diff --git a/src/shared/components/accordion/EventsAccordionItem.tsx b/src/shared/components/accordion/EventsAccordionItem.tsx
index 1b73df6..b38a8e7 100644
--- a/src/shared/components/accordion/EventsAccordionItem.tsx
+++ b/src/shared/components/accordion/EventsAccordionItem.tsx
@@ -6,11 +6,11 @@ import AccordionShareButton from '../buttons/AccordionShareButton';
import PlayControl from '../buttons/PlayControl';
import EventPanel from './EventPanel';
import { EventFrigate } from '../../../types/event';
-import { strings } from '../../strings/strings';
import { unixTimeToDate, getDurationFromTimestamps } from '../../utils/dateUtil';
import { useNavigate } from 'react-router-dom';
import { routesPath } from '../../../router/routes.path';
import { proxyApi } from '../../../services/frigate.proxy/frigate.api';
+import { useTranslation } from 'react-i18next';
interface EventsAccordionItemProps {
@@ -25,8 +25,8 @@ const EventsAccordionItem = ({
hostName,
played,
openPlayer,
-} : EventsAccordionItemProps) => {
-
+}: EventsAccordionItemProps) => {
+ const { t } = useTranslation()
const [playedURL, setPlayedUrl] = useState()
const navigate = useNavigate()
@@ -44,7 +44,7 @@ const EventsAccordionItem = ({
const duration = getDurationFromTimestamps(event.start_time, event.end_time)
return (
- {strings.player.object}: {event.label}
+ {t('player.object')}: {event.label}
{time}
{duration ?
{duration}
@@ -75,28 +75,28 @@ const EventsAccordionItem = ({
return (
-
-
- {eventLabel(event)}
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ {eventLabel(event)}
+
+
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/src/shared/components/buttons/AccordionControlButton.tsx b/src/shared/components/buttons/AccordionControlButton.tsx
index 6d2db7e..def9168 100644
--- a/src/shared/components/buttons/AccordionControlButton.tsx
+++ b/src/shared/components/buttons/AccordionControlButton.tsx
@@ -1,4 +1,4 @@
-import { Button, ButtonProps, Group, UnstyledButton, createStyles } from '@mantine/core';
+import { ButtonProps, Group, createStyles } from '@mantine/core';
import React from 'react';
const useStyles = createStyles((theme) => ({
diff --git a/src/shared/components/buttons/CloseWithTooltip.tsx b/src/shared/components/buttons/CloseWithTooltip.tsx
index b1ed26b..781221d 100644
--- a/src/shared/components/buttons/CloseWithTooltip.tsx
+++ b/src/shared/components/buttons/CloseWithTooltip.tsx
@@ -1,6 +1,5 @@
import { Tooltip, CloseButton, CloseButtonProps } from '@mantine/core';
import React from 'react';
-import { strings } from '../../strings/strings';
interface CloseWithTooltipProps {
diff --git a/src/shared/components/buttons/PlayControl.tsx b/src/shared/components/buttons/PlayControl.tsx
index af5ec90..4e4c5b7 100644
--- a/src/shared/components/buttons/PlayControl.tsx
+++ b/src/shared/components/buttons/PlayControl.tsx
@@ -1,7 +1,7 @@
import { Flex, createStyles } from '@mantine/core';
import { IconPlayerPlayFilled, IconPlayerStopFilled } from '@tabler/icons-react';
-import { strings } from '../../strings/strings';
import AccordionControlButton from './AccordionControlButton';
+import { useTranslation } from 'react-i18next';
const useStyles = createStyles((theme) => ({
@@ -22,6 +22,7 @@ const PlayControl = ({
played,
onClick
}: PlayControlProps) => {
+ const { t } = useTranslation()
const { classes } = useStyles();
const handleClick = () => {
@@ -32,7 +33,7 @@ const PlayControl = ({
onClick={() => { handleClick() }}
>
- {played ? strings.player.stopVideo : strings.player.startVideo}
+ {played ? t('player.stopVideo') : t('player.startVideo')}
{played ?
diff --git a/src/shared/components/filters/CameraSelectFilter.tsx b/src/shared/components/filters/CameraSelectFilter.tsx
index 24afd76..127665c 100644
--- a/src/shared/components/filters/CameraSelectFilter.tsx
+++ b/src/shared/components/filters/CameraSelectFilter.tsx
@@ -6,9 +6,9 @@ import { frigateApi, frigateQueryKeys } from '../../../services/frigate.proxy/fr
import CogwheelLoader from '../loaders/CogwheelLoader';
import { Center, Loader, Text } from '@mantine/core';
import OneSelectFilter, { OneSelectItem } from './OneSelectFilter';
-import { strings } from '../../strings/strings';
import RetryError from '../RetryError';
import { isProduction } from '../../env.const';
+import { useTranslation } from 'react-i18next';
interface CameraSelectFilterProps {
selectedHostId: string,
@@ -17,6 +17,7 @@ interface CameraSelectFilterProps {
const CameraSelectFilter = ({
selectedHostId,
}: CameraSelectFilterProps) => {
+ const { t } = useTranslation()
const { recordingsStore: recStore } = useContext(Context)
const { data, isError, isPending, isSuccess, refetch } = useQuery({
@@ -53,7 +54,7 @@ const CameraSelectFilter = ({
return (
{
+ const { t } = useTranslation()
const { recordingsStore: recStore } = useContext(Context)
const handlePick = (value: [Date | null, Date | null]) => {
@@ -23,7 +24,7 @@ const DateRangeSelectFilter = ({
- {strings.selectRange}
+ {t('selectRange')}
{
+ const { t } = useTranslation()
const handleOnChange = (value: string[]) => {
if (changedState) {
@@ -34,7 +35,7 @@ const MultiSelectFilter = ({
{label}
{showClose ?
-
+
: null}
{
+ const { t } = useTranslation()
const handleOnChange = (value: string) => {
if (onChange) onChange(value, id,)
@@ -44,7 +44,7 @@ const OneSelectFilter = ({
{!label ? null :
{label}
- {showClose ?
+ {showClose ?
: null}
}
diff --git a/src/shared/components/filters/RangeSliderFilter.tsx b/src/shared/components/filters/RangeSliderFilter.tsx
index 8674323..89dce45 100644
--- a/src/shared/components/filters/RangeSliderFilter.tsx
+++ b/src/shared/components/filters/RangeSliderFilter.tsx
@@ -1,7 +1,7 @@
-import { SystemProp, SpacingValue, SliderProps, Box, RangeSlider, RangeSliderProps, Text, Flex, CloseButton } from '@mantine/core';
-import React, { CSSProperties, useState } from 'react';
+import { Box, Flex, RangeSlider, RangeSliderProps, SpacingValue, SystemProp, Text } from '@mantine/core';
+import { CSSProperties } from 'react';
+import { useTranslation } from 'react-i18next';
import CloseWithTooltip from '../buttons/CloseWithTooltip';
-import { strings } from '../../strings/strings';
interface SliderFilterProps {
id: string
@@ -16,13 +16,14 @@ interface SliderFilterProps {
display?: SystemProp
showClose?: boolean,
changedState?(id: string, value: [number, number]): void
- onClose?():void
+ onClose?(): void
}
const RangeSliderFilter = ({ id, min, max, value, spaceBetween,
label, defaultValue, textClassName,
sliderProps, display, showClose, changedState, onClose }: SliderFilterProps) => {
+ const { t } = useTranslation()
const handleOnChange = (value: [number, number]) => {
if (changedState) {
changedState(id, value)
@@ -33,7 +34,7 @@ const RangeSliderFilter = ({ id, min, max, value, spaceBetween,
{label}
- {showClose? : null}
+ {showClose ? : null}
diff --git a/src/shared/components/filters/RecordingsHostFilter.tsx b/src/shared/components/filters/RecordingsHostFilter.tsx
index 957717f..75576a7 100644
--- a/src/shared/components/filters/RecordingsHostFilter.tsx
+++ b/src/shared/components/filters/RecordingsHostFilter.tsx
@@ -3,10 +3,11 @@ import { observer } from 'mobx-react-lite';
import { useContext } from 'react';
import { Context } from '../../..';
import { frigateApi, frigateQueryKeys } from '../../../services/frigate.proxy/frigate.api';
-import { strings } from '../../strings/strings';
import HostSelect from './HostSelect';
+import { useTranslation } from 'react-i18next';
const RecordingsHostFilter = () => {
+ const { t } = useTranslation()
const { recordingsStore: recStore } = useContext(Context)
const { data: hosts } = useQuery({
@@ -38,7 +39,7 @@ const RecordingsHostFilter = () => {
return (
showClose?: boolean,
changedState?(id: string, value: number): void
- onClose?():void
+ onClose?(): void
}
const SliderFilter = ({ id, min, max, value, spaceBetween, label, defaultValue, textClassName, sliderProps, display, showClose, changedState, onClose }: SliderFilterProps) => {
+ const { t } = useTranslation()
const handleOnChange = (value: number) => {
if (changedState) {
@@ -32,7 +33,7 @@ const SliderFilter = ({ id, min, max, value, spaceBetween, label, defaultValue,
{label}
- {showClose ? : null}
+ {showClose ? : null}
showClose?: boolean
changedState?(id: string, value: boolean): void
- onClose?():void
+ onClose?(): void
}
export interface SwitchChangeState {
@@ -23,6 +22,7 @@ export interface SwitchChangeState {
}
const SwitchFilter = ({ id, value, defaultValue, spaceBetween, label, textClassName, display, showClose, changedState, onClose }: SwitchFilterProps) => {
+ const { t } = useTranslation()
const handleChange = (event: ChangeEvent | undefined) => {
const checked = event?.currentTarget.checked
if (changedState && typeof checked === 'boolean') {
@@ -35,7 +35,7 @@ const SwitchFilter = ({ id, value, defaultValue, spaceBetween, label, textClassN
{label}
- {showClose ? : null}
+ {showClose ? : null}
);
diff --git a/src/shared/components/inputs/ClearableTextInput.tsx b/src/shared/components/inputs/ClearableTextInput.tsx
new file mode 100644
index 0000000..2cb178f
--- /dev/null
+++ b/src/shared/components/inputs/ClearableTextInput.tsx
@@ -0,0 +1,43 @@
+import { CloseButton, TextInput, TextInputProps } from '@mantine/core';
+import React, { useState } from 'react';
+
+interface ClearableTextInputProps extends TextInputProps {
+ clerable?: boolean
+}
+
+
+const ClearableTextInput: React.FC = ({
+ value,
+ onChange,
+ clerable,
+ ...textInputProps
+}) => {
+ const [text, setText] = useState(value)
+
+ const handleClear = (event: React.MouseEvent) => {
+ setText(''); // обновляем локальное состояние
+ if (onChange) {
+ const fakeEvent = {
+ target: { value: '' },
+ currentTarget: { value: '' }
+ } as unknown as React.ChangeEvent;
+ onChange(fakeEvent);
+ }
+ }
+
+ const handleChange = (value: React.ChangeEvent) => {
+ if (onChange) onChange(value)
+ setText(value.currentTarget.value)
+ }
+
+ return (
+ : null}
+ value={text}
+ onChange={handleChange}
+ {...textInputProps}
+ />
+ );
+};
+
+export default ClearableTextInput;
\ No newline at end of file
diff --git a/src/shared/components/modal.windows/InputModal.tsx b/src/shared/components/modal.windows/InputModal.tsx
index fbbb9b3..1f83257 100644
--- a/src/shared/components/modal.windows/InputModal.tsx
+++ b/src/shared/components/modal.windows/InputModal.tsx
@@ -1,9 +1,9 @@
import { ActionIcon, CloseButton, Flex, Modal, NumberInput, TextInput, Tooltip, createStyles, } from '@mantine/core';
import { getHotkeyHandler, useMediaQuery } from '@mantine/hooks';
import React, { ReactEventHandler, useState, FocusEvent, useRef, Ref } from 'react';
-import { strings } from '../../strings/strings';
import { IconAlertCircle, IconX } from '@tabler/icons-react';
import { dimensions } from '../../dimensions/dimensions';
+import { useTranslation } from 'react-i18next';
const useStyles = createStyles((theme) => ({
rightSection: {
@@ -21,6 +21,7 @@ interface InputModalProps {
}
const InputModal = ({ inValue, putValue, opened, open, close }: InputModalProps) => {
+ const { t } = useTranslation()
const { classes } = useStyles()
const [value, setValue] = useState(inValue)
const isMobile = useMediaQuery(dimensions.mobileSize)
@@ -54,7 +55,7 @@ const InputModal = ({ inValue, putValue, opened, open, close }: InputModalProps)
fullScreen={isMobile}
>
- {strings.enterQuantity}
+ {t('enterQuantity')}
0 ? handeClear()}> : null // todo move to textinput
+ rightSection={
-
+
diff --git a/src/shared/components/players/JSMpegPlayer.tsx b/src/shared/components/players/JSMpegPlayer.tsx
index d02f5d4..b880ccb 100644
--- a/src/shared/components/players/JSMpegPlayer.tsx
+++ b/src/shared/components/players/JSMpegPlayer.tsx
@@ -1,9 +1,8 @@
// @ts-ignore we know this doesn't have types
import JSMpeg from "@cycjimmy/jsmpeg-player";
-import { Flex } from "@mantine/core";
import { useViewportSize } from "@mantine/hooks";
import { useEffect, useRef, useState } from "react";
-import { strings } from "../../strings/strings";
+import { useTranslation } from "react-i18next";
type JSMpegPlayerProps = {
wsUrl: string;
@@ -18,6 +17,7 @@ const JSMpegPlayer = (
cameraHeight = 800,
}: JSMpegPlayerProps
) => {
+ const { t } = useTranslation()
const playerRef = useRef(null);
const [playerInitialized, setPlayerInitialized] = useState(false)
@@ -28,7 +28,7 @@ const JSMpegPlayer = (
playerRef.current,
wsUrl,
{},
- {protocols: [], audio: false, videoBufferSize: 1024*1024*4}
+ { protocols: [], audio: false, videoBufferSize: 1024 * 1024 * 4 }
);
const toggleFullscreen = () => {
@@ -54,7 +54,7 @@ const JSMpegPlayer = (
}
};
- video.els.canvas.addEventListener('dblclick',toggleFullscreen);
+ video.els.canvas.addEventListener('dblclick', toggleFullscreen);
return () => {
video.destroy();
@@ -63,11 +63,11 @@ const JSMpegPlayer = (
}, [wsUrl]);
return (
-
+
)
};
diff --git a/src/shared/components/table.aps/ProductsTableHead.tsx b/src/shared/components/table.aps/ProductsTableHead.tsx
deleted file mode 100644
index 8a49432..0000000
--- a/src/shared/components/table.aps/ProductsTableHead.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import React from 'react';
-import { productString } from '../../strings/product.strings';
-import SortedTh from './SortedTh';
-
-interface TableHeadProps {
- reverseSortDirection: boolean
- sortBy: string | null
- setSorting: (title: string) => void
-}
-
-const ProductsTableHead = ({ reverseSortDirection, sortBy, setSorting }: TableHeadProps) => {
-
- return (
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default ProductsTableHead;
\ No newline at end of file
diff --git a/src/shared/strings/header.menu.strings.ts b/src/shared/strings/header.menu.strings.ts
deleted file mode 100644
index c68f3d9..0000000
--- a/src/shared/strings/header.menu.strings.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export const headerMenu = {
- home:"На главную",
- test:"Тест",
- settings:"Настройки",
- recordings:"Записи",
- acessSettings:" Настройка доступа",
- hostsConfig:"Серверы Frigate",
-}
diff --git a/src/shared/strings/product.strings.ts b/src/shared/strings/product.strings.ts
deleted file mode 100644
index 0568737..0000000
--- a/src/shared/strings/product.strings.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-export const productString = {
- name: "Наименование",
- cost: "Цена",
- image: "Изображение",
- qty: "Количество",
- buy: "Купить",
- number: "Артикул",
- manufactory: "Производитель",
- oem: "OEM",
- stock: "Наличие",
- receiptDate: "Дата поступления",
- discount:"Скидка",
- parameters: "Характеристики",
-}
\ No newline at end of file
diff --git a/src/shared/strings/strings.ts b/src/shared/strings/strings.ts
deleted file mode 100644
index a50431a..0000000
--- a/src/shared/strings/strings.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-export const strings = {
- host: 'Хост',
- hostArr: {
- name: 'Имя хоста',
- url: 'Адрес',
- enabled: 'Включен',
- },
- player: {
- startVideo: 'Вкл Видео',
- stopVideo: 'Выкл Видео',
- object: 'Объект',
- duration: 'Длительность',
- startTime: 'Начало',
- endTime: 'Конец',
- doubleClickToFullHint: 'Двойное нажатие мышью для полноэкранного просмотра',
- rating: 'Рейтинг',
- },
-
- empty: 'Пусто',
- pleaseSelectRole: 'Пожалуйста выберите роль',
- nothingHere: 'Ни чего нет',
- allowed: 'Разрешено',
- notAllowed: 'Не разрешено',
- camera: 'Камера',
- camersDoesNotExist: 'Камер нет',
- search: 'Поиск',
- recordings: 'Записи',
- hour: 'Час',
- minute: 'Минута',
- second: 'Секунда',
- events: 'События',
- event: 'Событие',
- notHaveEvents: 'Нет событий',
- date: 'Дата',
- day: 'День',
- selectHost:'Выбери хост',
- selectCamera: 'Выбери камеру',
- selectDate: 'Выбери дату',
- selectRange: 'Выбери период',
- aboutMe: "Обо мне",
- settings: "Настройки",
- changeTheme: "Изменить тему",
- logout: "Выйти",
- userProfile: "Профиль пользователя",
- firstName: "Имя",
- middleName: "Фамилия",
- lastName: "Отчетство",
- taxNumber: "ИНН",
- phone: "Телефон",
- managerName: "Имя менеджера",
- managerPhone: "Телефон менеджера",
- defaultContract: "Договор по умолчанию",
- myShippingAddresses: "Мои адреса доставки:",
- myConditions: "Мои условия:",
- warehouse: "Склад",
- name: "Имя",
- schedule: "Расписание",
- address: "Адрес",
- edit: "Изменить",
- delete: "Удалить",
- error: "Ошибка",
- discounts: "Скидки",
- delivery: "Доставка",
- selectDeliveryMethod: "Выберите метод доставки:",
- pickUpByMyself: "Я заберу самостоятельно",
- courierDelivery: "Доставка курьером",
- deliveryPoint: "Адрес доставки",
- selectYourDeliveryAddress: "Выберите свой адрес доставки:",
- deliveryDate: "Дата доставки",
- selectDeliveryDate: "Выберите дату доставки:",
- enterQuantity: "Введите количество:",
- quantity: "Количество",
- tooltip_close: "нажмите Enter",
- currency: "₽",
- category: "Категории:",
- collapse: "Свернуть",
- hide: "Скрыть",
- show: "Показать",
- showAll: "Показать всё",
- true: "Да",
- false: "Нет",
- // order section
- cart: "Корзина",
- order: "Заказ",
- confirmOrder: "Подтвердить заказ",
- orderParams: "Параметры",
- chooseParams: "Выбрать параметры заказа",
- payment: "Оплата",
- inputPaymentValues: "Ввести платежные данные",
- summary: "Общие итоги",
- positions: "Позиции:",
- weight: "Вес:",
- total: "Итого:",
- confirm: "Подтвердить",
- save: "Сохранить",
- discard: "Отменить",
- next: "Далее",
- back: "Назад",
- paymentMethod: "Метод оплаты",
- selectPaymentMethod: "Выберите метод оплаты",
- cashToCourier: "Наличными курьеру",
- bankTransfer: "Банковским переводом",
- onlineByCard: "Онлайн банковской картой",
- thanksForYourPurchase: "Спасибо за вашу покупку!",
- orderConfirmed: (order: string) => `Ордер ${order} подтвержден!`,
- goToMainPage: "Вернуться на главную",
- goToOrder: "Посмотреть ордер",
- retry: "Повторить",
- youCanRetryOrGoToMain: "Вы можете повторить или вернуться на главную",
- errors: {
- somthengGoesWrong: "Что-то пошло не так",
- cartIsEmpty: "Корзина пуста",
- choosePaymentMethod: "Выберите метод оплаты",
- chooseDeliveryMethod: "Выберите метод доставки",
- chooseDeliveryPoint: "Выберите адрес доставки",
- chooseDate: "Выберите дату",
- 403: "Извините у вас нет доступа",
- 404: "Извините мы не можем найти такую страницу",
-
- }
-}
\ No newline at end of file
diff --git a/src/widgets/CameraCard.tsx b/src/widgets/CameraCard.tsx
index 38844a7..cc3fa9a 100644
--- a/src/widgets/CameraCard.tsx
+++ b/src/widgets/CameraCard.tsx
@@ -7,7 +7,7 @@ import { routesPath } from '../router/routes.path';
import { mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema';
import AutoUpdatedImage from '../shared/components/images/AutoUpdatedImage';
-import { strings } from '../shared/strings/strings';
+import { useTranslation } from 'react-i18next';
const useStyles = createStyles((theme) => ({
mainCard: {
@@ -41,6 +41,7 @@ interface CameraCardProps {
const CameraCard = ({
camera
}: CameraCardProps) => {
+ const { t } = useTranslation()
const [renderImage, setRenderImage] = useState(false)
const { classes } = useStyles();
const { ref, entry } = useIntersection({ threshold: 0.5, })
@@ -71,7 +72,7 @@ const CameraCard = ({
-
+
diff --git a/src/widgets/FrigateHostsTable.tsx b/src/widgets/FrigateHostsTable.tsx
index 89b026d..82d802f 100644
--- a/src/widgets/FrigateHostsTable.tsx
+++ b/src/widgets/FrigateHostsTable.tsx
@@ -7,10 +7,10 @@ import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
import HostSettingsMenu from '../shared/components/menu/HostSettingsMenu';
import SortedTh from '../shared/components/table.aps/SortedTh';
import { isProduction } from '../shared/env.const';
-import { strings } from '../shared/strings/strings';
import StateCell from './hosts.table/StateCell';
import SwitchCell from './hosts.table/SwitchCell';
import TextInputCell from './hosts.table/TextInputCell';
+import { useTranslation } from 'react-i18next';
interface TableProps {
data: T[],
@@ -20,6 +20,8 @@ interface TableProps {
}
const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedCallback }: TableProps) => {
+ const { t } = useTranslation()
+
const [tableData, setTableData] = useState(data)
const [reversed, setReversed] = useState(false)
const [sortedName, setSortedName] = useState(null)
@@ -59,9 +61,9 @@ const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedC
}
const headTitle = [
- { propertyName: 'name', title: strings.hostArr.name },
- { propertyName: 'host', title: strings.hostArr.url },
- { propertyName: 'enabled', title: strings.hostArr.enabled },
+ { propertyName: 'name', title: t('hostArr.host') },
+ { propertyName: 'host', title: t('hostArr.url') },
+ { propertyName: 'enabled', title: t('hostArr.enabled') },
{ title: '', sorting: false },
]
diff --git a/src/widgets/SelectedHostList.tsx b/src/widgets/SelectedHostList.tsx
index 2c184d6..549051f 100644
--- a/src/widgets/SelectedHostList.tsx
+++ b/src/widgets/SelectedHostList.tsx
@@ -5,8 +5,8 @@ import { frigateQueryKeys, frigateApi } from '../services/frigate.proxy/frigate.
import { Context } from '..';
import CenterLoader from '../shared/components/loaders/CenterLoader';
import RetryErrorPage from '../pages/RetryErrorPage';
-import { strings } from '../shared/strings/strings';
import { observer } from 'mobx-react-lite';
+import { useTranslation } from 'react-i18next';
const CameraAccordion = lazy(() => import('../shared/components/accordion/CameraAccordion'));
interface SelectedHostListProps {
@@ -16,7 +16,7 @@ interface SelectedHostListProps {
const SelectedHostList = ({
hostId
}: SelectedHostListProps) => {
-
+ const { t } = useTranslation()
const { recordingsStore: recStore } = useContext(Context)
const [openCameraId, setOpenCameraId] = useState(null)
@@ -47,7 +47,7 @@ const SelectedHostList = ({
const camerasItems = camerasQuery.map(camera => {
return (
- {strings.camera}: {camera.name}
+ {t('camera')}: {camera.name}
{openCameraId === camera.id && (
@@ -61,7 +61,7 @@ const SelectedHostList = ({
return (
- {strings.host}: {camerasQuery[0].frigateHost?.name}
+ {t('hostArr.host')}: {camerasQuery[0].frigateHost?.name}
{
- // clearTimeout(timer)
- // setTimer(undefined)
- // setCreateName(undefined)
- // setLink(undefined)
- // }
-
-
if (startUnixTime === 0 || endUnixTime === 0) return null
if (error) return createVideo.mutate()} />
if (link && progress && !videoSrc) return (
diff --git a/src/widgets/header/header.links.ts b/src/widgets/header/header.links.ts
deleted file mode 100644
index d81faf9..0000000
--- a/src/widgets/header/header.links.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { routesPath } from "../../router/routes.path";
-import { headerMenu } from "../../shared/strings/header.menu.strings";
-import { LinkItem } from "./HeaderAction";
-
-export const headerLinks: LinkItem[] = [
- { link: routesPath.MAIN_PATH, label: headerMenu.home },
- { link: routesPath.SETTINGS_PATH, label: headerMenu.settings, admin: true },
- { link: routesPath.RECORDINGS_PATH, label: headerMenu.recordings },
- { link: routesPath.HOSTS_PATH, label: headerMenu.hostsConfig, admin: true },
- { link: routesPath.ACCESS_PATH, label: headerMenu.acessSettings, admin: true },
-]
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 35660dc..2bb5a8a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1122,7 +1122,7 @@
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
-"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.24.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e"
integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==
@@ -5684,6 +5684,13 @@ html-minifier-terser@^6.0.2:
relateurl "^0.2.7"
terser "^5.10.0"
+html-parse-stringify@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
+ integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
+ dependencies:
+ void-elements "3.1.0"
+
html-webpack-plugin@^5.5.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz#50a8fa6709245608cb00e811eacecb8e0d7b7ea0"
@@ -5778,6 +5785,20 @@ human-signals@^2.1.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
+i18next-browser-languagedetector@^7.2.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.0.tgz#de0321cba6881be37d82e20e4d6f05aa75f6e37f"
+ integrity sha512-U00DbDtFIYD3wkWsr2aVGfXGAj2TgnELzOX9qv8bT0aJtvPV9CRO77h+vgmHFBMe7LAxdwvT/7VkCWGya6L3tA==
+ dependencies:
+ "@babel/runtime" "^7.23.2"
+
+i18next@^23.10.1:
+ version "23.10.1"
+ resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.10.1.tgz#217ce93b75edbe559ac42be00a20566b53937df6"
+ integrity sha512-NDiIzFbcs3O9PXpfhkjyf7WdqFn5Vq6mhzhtkXzj51aOcNuPNcTwuYNuXCpHsanZGHlHKL35G7huoFeVic1hng==
+ dependencies:
+ "@babel/runtime" "^7.23.2"
+
iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -8647,6 +8668,14 @@ react-error-overlay@^6.0.11:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
+react-i18next@^14.1.0:
+ version "14.1.0"
+ resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-14.1.0.tgz#44da74fbffd416f5d0c5307ef31735cf10cc91d9"
+ integrity sha512-3KwX6LHpbvGQ+sBEntjV4sYW3Zovjjl3fpoHbUwSgFHf0uRBcbeCBLR5al6ikncI5+W0EFb71QXZmfop+J6NrQ==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ html-parse-stringify "^3.0.1"
+
react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -10271,6 +10300,11 @@ videojs-vtt.js@0.15.5:
dependencies:
global "^4.3.1"
+void-elements@3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
+ integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
+
vscode-jsonrpc@8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9"