mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-10 07:25:48 +01:00
✨ Add translation for module, fix language changer
This commit is contained in:
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"states": {
|
|
||||||
"online": "Online {{response}}",
|
|
||||||
"offline": "Offline {{response}}",
|
|
||||||
"loading": "Loading..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
public/locales/en/modules/ping.json
Normal file
11
public/locales/en/modules/ping.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"descriptor": {
|
||||||
|
"name": "Ping",
|
||||||
|
"description": "Allows you to check if the service is up or returns a specific HTTP status code."
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"online": "Online {{response}}",
|
||||||
|
"offline": "Offline {{response}}",
|
||||||
|
"loading": "Loading..."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -126,7 +126,7 @@ const AppShelf = (props: any) => {
|
|||||||
const noCategory = config.services.filter(
|
const noCategory = config.services.filter(
|
||||||
(e) => e.category === undefined || e.category === null
|
(e) => e.category === undefined || e.category === null
|
||||||
);
|
);
|
||||||
const downloadEnabled = config.modules?.[DownloadsModule.title]?.enabled ?? false;
|
const downloadEnabled = config.modules?.[DownloadsModule.id]?.enabled ?? false;
|
||||||
// Create an item with 0: true, 1: true, 2: true... For each category
|
// Create an item with 0: true, 1: true, 2: true... For each category
|
||||||
return (
|
return (
|
||||||
// TODO: Style accordion so that the bar is transparent to the user settings
|
// TODO: Style accordion so that the bar is transparent to the user settings
|
||||||
|
|||||||
@@ -4,14 +4,17 @@ import { showNotification } from '@mantine/notifications';
|
|||||||
import { forwardRef, useState } from 'react';
|
import { forwardRef, useState } from 'react';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
|
import { getCookie, setCookie } from 'cookies-next';
|
||||||
import { getLanguageByCode, Language } from '../../languages/language';
|
import { getLanguageByCode, Language } from '../../languages/language';
|
||||||
|
|
||||||
export default function LanguageSwitch() {
|
export default function LanguageSwitch() {
|
||||||
const { t, i18n } = useTranslation('settings/general/internationalization');
|
const { t, i18n } = useTranslation('settings/general/internationalization');
|
||||||
const { changeLanguage } = i18n;
|
const { changeLanguage } = i18n;
|
||||||
|
const configLocale = getCookie('config-locale');
|
||||||
const { locale, locales } = useRouter();
|
const { locale, locales } = useRouter();
|
||||||
const [selectedLanguage, setSelectedLanguage] = useState<string | null | undefined>(locale);
|
const [selectedLanguage, setSelectedLanguage] = useState<string | undefined>(
|
||||||
|
(configLocale as string) ?? locale
|
||||||
|
);
|
||||||
|
|
||||||
const data = locales
|
const data = locales
|
||||||
? locales.map((localeItem) => ({
|
? locales.map((localeItem) => ({
|
||||||
@@ -26,9 +29,13 @@ export default function LanguageSwitch() {
|
|||||||
setSelectedLanguage(value);
|
setSelectedLanguage(value);
|
||||||
|
|
||||||
const newLanguage = getLanguageByCode(value);
|
const newLanguage = getLanguageByCode(value);
|
||||||
|
|
||||||
changeLanguage(value)
|
changeLanguage(value)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
setCookie('config-locale', value, {
|
||||||
|
maxAge: 60 * 60 * 24 * 30,
|
||||||
|
sameSite: 'strict',
|
||||||
|
});
|
||||||
|
|
||||||
showNotification({
|
showNotification({
|
||||||
title: 'Language changed',
|
title: 'Language changed',
|
||||||
message: `You changed the language to '${newLanguage.originalName}'`,
|
message: `You changed the language to '${newLanguage.originalName}'`,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Checkbox, Popover, SimpleGrid, Stack, Text, Title } from '@mantine/core';
|
import { Checkbox, HoverCard, SimpleGrid, Stack, Text, Title } from '@mantine/core';
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import * as Modules from '../../modules';
|
import * as Modules from '../../modules';
|
||||||
import { IModule } from '../../modules/ModuleTypes';
|
import { IModule } from '../../modules/ModuleTypes';
|
||||||
@@ -11,9 +10,9 @@ export default function ModuleEnabler(props: any) {
|
|||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Title order={4}>{t('title')}</Title>
|
<Title order={4}>{t('title')}</Title>
|
||||||
<SimpleGrid cols={3} spacing="xs">
|
<SimpleGrid cols={3} spacing="sm">
|
||||||
{modules.map((module) => (
|
{modules.map((module) => (
|
||||||
<ModuleToggle module={module} />
|
<ModuleToggle key={module.id} module={module} />
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -22,39 +21,36 @@ export default function ModuleEnabler(props: any) {
|
|||||||
|
|
||||||
const ModuleToggle = ({ module }: { module: IModule }) => {
|
const ModuleToggle = ({ module }: { module: IModule }) => {
|
||||||
const { config, setConfig } = useConfig();
|
const { config, setConfig } = useConfig();
|
||||||
const { t: translationModules } = useTranslation(module.translationNamespace);
|
const { t } = useTranslation(`modules/${module.id}`);
|
||||||
const [opened, { close, open }] = useDisclosure(false);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover opened={opened} withArrow withinPortal width={200}>
|
<HoverCard withArrow withinPortal width={200} shadow="md" openDelay={200}>
|
||||||
<Popover.Target>
|
<HoverCard.Target>
|
||||||
<div onMouseEnter={open} onMouseLeave={close}>
|
<Checkbox
|
||||||
<Checkbox
|
key={module.id}
|
||||||
key={module.title}
|
size="md"
|
||||||
size="md"
|
checked={config.modules?.[module.id]?.enabled ?? false}
|
||||||
checked={config.modules?.[module.title]?.enabled ?? false}
|
label={t('descriptor.name', {
|
||||||
label={translationModules('descriptor.name', {
|
defaultValue: 'Unknown',
|
||||||
defaultValue: 'Unknown',
|
})}
|
||||||
})}
|
onChange={(e) => {
|
||||||
onChange={(e) => {
|
setConfig({
|
||||||
setConfig({
|
...config,
|
||||||
...config,
|
modules: {
|
||||||
modules: {
|
...config.modules,
|
||||||
...config.modules,
|
[module.id]: {
|
||||||
[module.title]: {
|
...config.modules?.[module.id],
|
||||||
...config.modules?.[module.title],
|
enabled: e.currentTarget.checked,
|
||||||
enabled: e.currentTarget.checked,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
}}
|
});
|
||||||
/>
|
}}
|
||||||
</div>
|
/>
|
||||||
</Popover.Target>
|
</HoverCard.Target>
|
||||||
<Popover.Dropdown>
|
<HoverCard.Dropdown>
|
||||||
<Text weight="bold">{translationModules('descriptor.name')}</Text>
|
<Title order={4}>{t('descriptor.name')}</Title>
|
||||||
<Text>{translationModules('descriptor.description')}</Text>
|
<Text size="sm">{t('descriptor.description')}</Text>
|
||||||
</Popover.Dropdown>
|
</HoverCard.Dropdown>
|
||||||
</Popover>
|
</HoverCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
2
src/modules/ModuleTypes.d.ts
vendored
2
src/modules/ModuleTypes.d.ts
vendored
@@ -6,11 +6,11 @@ import { TablerIcon } from '@tabler/icons';
|
|||||||
|
|
||||||
// Note: Maybe use context to keep track of the modules
|
// Note: Maybe use context to keep track of the modules
|
||||||
export interface IModule {
|
export interface IModule {
|
||||||
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
icon: TablerIcon;
|
icon: TablerIcon;
|
||||||
component: React.ComponentType;
|
component: React.ComponentType;
|
||||||
options?: Option;
|
options?: Option;
|
||||||
translationNamespace: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Option {
|
interface Option {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const CalendarModule: IModule = {
|
|||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
translationNamespace: 'modules/calendar-module',
|
id: 'calendar',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CalendarComponent(props: any) {
|
export default function CalendarComponent(props: any) {
|
||||||
@@ -127,7 +127,7 @@ export default function CalendarComponent(props: any) {
|
|||||||
}, [config.services]);
|
}, [config.services]);
|
||||||
|
|
||||||
const weekStartsAtSunday =
|
const weekStartsAtSunday =
|
||||||
(config?.modules?.[CalendarModule.title]?.options?.sundaystart?.value as boolean) ?? false;
|
(config?.modules?.[CalendarModule.id]?.options?.sundaystart?.value as boolean) ?? false;
|
||||||
return (
|
return (
|
||||||
<Calendar
|
<Calendar
|
||||||
firstDayOfWeek={weekStartsAtSunday ? 'sunday' : 'monday'}
|
firstDayOfWeek={weekStartsAtSunday ? 'sunday' : 'monday'}
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ export function SonarrMediaDisplay(props: any) {
|
|||||||
export function MediaDisplay({ media }: { media: IMedia }) {
|
export function MediaDisplay({ media }: { media: IMedia }) {
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
const { secondaryColor } = useColorTheme();
|
const { secondaryColor } = useColorTheme();
|
||||||
const { t } = useTranslation('modules/common-media-cards-module');
|
const { t } = useTranslation('modules/common-media-cards');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group mr="xs" align="stretch" noWrap style={{ maxHeight: 200 }}>
|
<Group mr="xs" align="stretch" noWrap style={{ maxHeight: 200 }}>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const DashdotModule = asModule({
|
|||||||
value: '',
|
value: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
translationNamespace: 'modules/dashdot-module',
|
id: 'dashdot',
|
||||||
});
|
});
|
||||||
|
|
||||||
const useStyles = createStyles((theme, _params) => ({
|
const useStyles = createStyles((theme, _params) => ({
|
||||||
@@ -126,7 +126,7 @@ export function DashdotComponent() {
|
|||||||
const { classes } = useStyles();
|
const { classes } = useStyles();
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
|
||||||
const dashConfig = config.modules?.[DashdotModule.title]
|
const dashConfig = config.modules?.[DashdotModule.id]
|
||||||
.options as typeof DashdotModule['options'];
|
.options as typeof DashdotModule['options'];
|
||||||
const isCompact = dashConfig?.useCompactView?.value ?? false;
|
const isCompact = dashConfig?.useCompactView?.value ?? false;
|
||||||
const dashdotService: serviceItem | undefined = config.services.filter(
|
const dashdotService: serviceItem | undefined = config.services.filter(
|
||||||
@@ -148,7 +148,7 @@ export function DashdotComponent() {
|
|||||||
const totalSize =
|
const totalSize =
|
||||||
(info?.storage?.layout as any[])?.reduce((acc, curr) => (curr.size ?? 0) + acc, 0) ?? 0;
|
(info?.storage?.layout as any[])?.reduce((acc, curr) => (curr.size ?? 0) + acc, 0) ?? 0;
|
||||||
|
|
||||||
const { t } = useTranslation('modules/dashdot-module');
|
const { t } = useTranslation('modules/dashdot');
|
||||||
|
|
||||||
const graphs = [
|
const graphs = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,14 +16,14 @@ export const DateModule: IModule = {
|
|||||||
value: true,
|
value: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
translationNamespace: 'modules/date-module',
|
id: 'date',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function DateComponent(props: any) {
|
export default function DateComponent(props: any) {
|
||||||
const [date, setDate] = useState(new Date());
|
const [date, setDate] = useState(new Date());
|
||||||
const setSafeInterval = useSetSafeInterval();
|
const setSafeInterval = useSetSafeInterval();
|
||||||
const { config } = useConfig();
|
const { config } = useConfig();
|
||||||
const isFullTime = config?.modules?.[DateModule.title]?.options?.full?.value ?? true;
|
const isFullTime = config?.modules?.[DateModule.id]?.options?.full?.value ?? true;
|
||||||
const formatString = isFullTime ? 'HH:mm' : 'h:mm A';
|
const formatString = isFullTime ? 'HH:mm' : 'h:mm A';
|
||||||
// Change date on minute change
|
// Change date on minute change
|
||||||
// Note: Using 10 000ms instead of 1000ms to chill a little :)
|
// Note: Using 10 000ms instead of 1000ms to chill a little :)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function sendDockerCommand(
|
|||||||
containerName: string,
|
containerName: string,
|
||||||
reload: () => void
|
reload: () => void
|
||||||
) {
|
) {
|
||||||
const { t } = useTranslation('modules/docker-module');
|
const { t } = useTranslation('modules/docker');
|
||||||
|
|
||||||
showNotification({
|
showNotification({
|
||||||
id: containerId,
|
id: containerId,
|
||||||
@@ -64,7 +64,7 @@ export interface ContainerActionBarProps {
|
|||||||
|
|
||||||
export default function ContainerActionBar({ selected, reload }: ContainerActionBarProps) {
|
export default function ContainerActionBar({ selected, reload }: ContainerActionBarProps) {
|
||||||
const [opened, setOpened] = useState<boolean>(false);
|
const [opened, setOpened] = useState<boolean>(false);
|
||||||
const { t } = useTranslation('modules/docker-module');
|
const { t } = useTranslation('modules/docker');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export interface ContainerStateProps {
|
|||||||
export default function ContainerState(props: ContainerStateProps) {
|
export default function ContainerState(props: ContainerStateProps) {
|
||||||
const { state } = props;
|
const { state } = props;
|
||||||
|
|
||||||
const { t } = useTranslation('modules/docker-module');
|
const { t } = useTranslation('modules/docker');
|
||||||
|
|
||||||
const options: {
|
const options: {
|
||||||
size: MantineSize;
|
size: MantineSize;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const DockerModule: IModule = {
|
|||||||
title: 'Docker',
|
title: 'Docker',
|
||||||
icon: IconBrandDocker,
|
icon: IconBrandDocker,
|
||||||
component: DockerMenuButton,
|
component: DockerMenuButton,
|
||||||
translationNamespace: 'modules/docker-module',
|
id: 'docker',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function DockerMenuButton(props: any) {
|
export default function DockerMenuButton(props: any) {
|
||||||
@@ -23,9 +23,9 @@ export default function DockerMenuButton(props: any) {
|
|||||||
const [containers, setContainers] = useState<Docker.ContainerInfo[]>([]);
|
const [containers, setContainers] = useState<Docker.ContainerInfo[]>([]);
|
||||||
const [selection, setSelection] = useState<Docker.ContainerInfo[]>([]);
|
const [selection, setSelection] = useState<Docker.ContainerInfo[]>([]);
|
||||||
const { config } = useConfig();
|
const { config } = useConfig();
|
||||||
const moduleEnabled = config.modules?.[DockerModule.title]?.enabled ?? false;
|
const moduleEnabled = config.modules?.[DockerModule.id]?.enabled ?? false;
|
||||||
|
|
||||||
const { t } = useTranslation('modules/docker-module');
|
const { t } = useTranslation('modules/docker');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reload();
|
reload();
|
||||||
@@ -54,7 +54,7 @@ export default function DockerMenuButton(props: any) {
|
|||||||
);
|
);
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
const exists = config.modules?.[DockerModule.title]?.enabled ?? false;
|
const exists = config.modules?.[DockerModule.id]?.enabled ?? false;
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default function DockerTable({
|
|||||||
const { classes, cx } = useStyles();
|
const { classes, cx } = useStyles();
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
const { t } = useTranslation('modules/docker-module');
|
const { t } = useTranslation('modules/docker');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setContainers(containers);
|
setContainers(containers);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
Skeleton,
|
Skeleton,
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
Center,
|
Center,
|
||||||
|
Stack,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconDownload as Download } from '@tabler/icons';
|
import { IconDownload as Download } from '@tabler/icons';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -32,7 +33,7 @@ export const DownloadsModule: IModule = {
|
|||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
translationNamespace: 'modules/downloads-module',
|
id: 'torrents-status',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function DownloadComponent() {
|
export default function DownloadComponent() {
|
||||||
@@ -46,12 +47,12 @@ export default function DownloadComponent() {
|
|||||||
service.type === 'Deluge'
|
service.type === 'Deluge'
|
||||||
) ?? [];
|
) ?? [];
|
||||||
const hideComplete: boolean =
|
const hideComplete: boolean =
|
||||||
(config?.modules?.[DownloadsModule.title]?.options?.hidecomplete?.value as boolean) ?? false;
|
(config?.modules?.[DownloadsModule.id]?.options?.hidecomplete?.value as boolean) ?? false;
|
||||||
const [torrents, setTorrents] = useState<NormalizedTorrent[]>([]);
|
const [torrents, setTorrents] = useState<NormalizedTorrent[]>([]);
|
||||||
const setSafeInterval = useSetSafeInterval();
|
const setSafeInterval = useSetSafeInterval();
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
const { t } = useTranslation('modules/downloads-module');
|
const { t } = useTranslation(`modules/${DownloadsModule.id}`);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@@ -85,13 +86,13 @@ export default function DownloadComponent() {
|
|||||||
|
|
||||||
if (downloadServices.length === 0) {
|
if (downloadServices.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Group>
|
<Stack>
|
||||||
<Title order={3}>{t('card.errors.noDownloadClients.title')}</Title>
|
<Title order={3}>{t('card.errors.noDownloadClients.title')}</Title>
|
||||||
<Group>
|
<Group>
|
||||||
<Text>{t('card.errors.noDownloadClients.text')}</Text>
|
<Text>{t('card.errors.noDownloadClients.text')}</Text>
|
||||||
<AddItemShelfButton />
|
<AddItemShelfButton />
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const TotalDownloadsModule: IModule = {
|
|||||||
title: 'Download Speed',
|
title: 'Download Speed',
|
||||||
icon: Download,
|
icon: Download,
|
||||||
component: TotalDownloadsComponent,
|
component: TotalDownloadsComponent,
|
||||||
translationNamespace: 'modules/total-downloads-module',
|
id: 'dlspeed',
|
||||||
};
|
};
|
||||||
|
|
||||||
interface torrentHistory {
|
interface torrentHistory {
|
||||||
@@ -37,7 +37,7 @@ export default function TotalDownloadsComponent() {
|
|||||||
service.type === 'Transmission' ||
|
service.type === 'Transmission' ||
|
||||||
service.type === 'Deluge'
|
service.type === 'Deluge'
|
||||||
) ?? [];
|
) ?? [];
|
||||||
const { t } = useTranslation('modules/downloads-module');
|
const { t } = useTranslation(`modules/${TotalDownloadsModule.id}`);
|
||||||
|
|
||||||
const [torrentHistory, torrentHistoryHandlers] = useListState<torrentHistory>([]);
|
const [torrentHistory, torrentHistoryHandlers] = useListState<torrentHistory>([]);
|
||||||
const [torrents, setTorrents] = useState<NormalizedTorrent[]>([]);
|
const [torrents, setTorrents] = useState<NormalizedTorrent[]>([]);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { IModule } from './ModuleTypes';
|
|||||||
|
|
||||||
function getItems(module: IModule) {
|
function getItems(module: IModule) {
|
||||||
const { config, setConfig } = useConfig();
|
const { config, setConfig } = useConfig();
|
||||||
const { t } = useTranslation(module.translationNamespace);
|
const { t } = useTranslation([module.id, 'common']);
|
||||||
|
|
||||||
const items: JSX.Element[] = [];
|
const items: JSX.Element[] = [];
|
||||||
if (module.options) {
|
if (module.options) {
|
||||||
@@ -28,8 +28,8 @@ function getItems(module: IModule) {
|
|||||||
const types = values.map((v) => typeof v.value);
|
const types = values.map((v) => typeof v.value);
|
||||||
// Loop over all the types with a for each loop
|
// Loop over all the types with a for each loop
|
||||||
types.forEach((type, index) => {
|
types.forEach((type, index) => {
|
||||||
const optionName = `${module.title}.${keys[index]}`;
|
const optionName = `${module.id}.${keys[index]}`;
|
||||||
const moduleInConfig = config.modules?.[module.title];
|
const moduleInConfig = config.modules?.[module.id];
|
||||||
if (type === 'object') {
|
if (type === 'object') {
|
||||||
items.push(
|
items.push(
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
@@ -46,7 +46,7 @@ function getItems(module: IModule) {
|
|||||||
...config,
|
...config,
|
||||||
modules: {
|
modules: {
|
||||||
...config.modules,
|
...config.modules,
|
||||||
[module.title]: {
|
[module.id]: {
|
||||||
...moduleInConfig,
|
...moduleInConfig,
|
||||||
options: {
|
options: {
|
||||||
...moduleInConfig?.options,
|
...moduleInConfig?.options,
|
||||||
@@ -71,12 +71,12 @@ function getItems(module: IModule) {
|
|||||||
...config,
|
...config,
|
||||||
modules: {
|
modules: {
|
||||||
...config.modules,
|
...config.modules,
|
||||||
[module.title]: {
|
[module.id]: {
|
||||||
...config.modules[module.title],
|
...config.modules[module.id],
|
||||||
options: {
|
options: {
|
||||||
...config.modules[module.title].options,
|
...config.modules[module.id].options,
|
||||||
[keys[index]]: {
|
[keys[index]]: {
|
||||||
...config.modules[module.title].options?.[keys[index]],
|
...config.modules[module.id].options?.[keys[index]],
|
||||||
value: (e.target as any)[0].value,
|
value: (e.target as any)[0].value,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -99,7 +99,7 @@ function getItems(module: IModule) {
|
|||||||
onChange={(e) => {}}
|
onChange={(e) => {}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button type="submit">Save</Button>
|
<Button type="submit">{t('actions.save')}</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
@@ -120,12 +120,12 @@ function getItems(module: IModule) {
|
|||||||
...config,
|
...config,
|
||||||
modules: {
|
modules: {
|
||||||
...config.modules,
|
...config.modules,
|
||||||
[module.title]: {
|
[module.id]: {
|
||||||
...config.modules[module.title],
|
...config.modules[module.id],
|
||||||
options: {
|
options: {
|
||||||
...config.modules[module.title].options,
|
...config.modules[module.id].options,
|
||||||
[keys[index]]: {
|
[keys[index]]: {
|
||||||
...config.modules[module.title].options?.[keys[index]],
|
...config.modules[module.id].options?.[keys[index]],
|
||||||
value: e.currentTarget.checked,
|
value: e.currentTarget.checked,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -148,7 +148,7 @@ export function ModuleWrapper(props: any) {
|
|||||||
const { config, setConfig } = useConfig();
|
const { config, setConfig } = useConfig();
|
||||||
const enabledModules = config.modules ?? {};
|
const enabledModules = config.modules ?? {};
|
||||||
// Remove 'Module' from enabled modules titles
|
// Remove 'Module' from enabled modules titles
|
||||||
const isShown = enabledModules[module.title]?.enabled ?? false;
|
const isShown = enabledModules[module.id]?.enabled ?? false;
|
||||||
//TODO: fix the hover problem
|
//TODO: fix the hover problem
|
||||||
const [hovering, setHovering] = useState(false);
|
const [hovering, setHovering] = useState(false);
|
||||||
const { t } = useTranslation('modules');
|
const { t } = useTranslation('modules');
|
||||||
@@ -160,7 +160,7 @@ export function ModuleWrapper(props: any) {
|
|||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
{...props}
|
{...props}
|
||||||
key={module.title}
|
key={module.id}
|
||||||
hidden={!isShown}
|
hidden={!isShown}
|
||||||
withBorder
|
withBorder
|
||||||
radius="lg"
|
radius="lg"
|
||||||
@@ -195,7 +195,7 @@ export function ModuleMenu(props: any) {
|
|||||||
<>
|
<>
|
||||||
{module.options && (
|
{module.options && (
|
||||||
<Menu
|
<Menu
|
||||||
key={module.title}
|
key={module.id}
|
||||||
withinPortal
|
withinPortal
|
||||||
width="lg"
|
width="lg"
|
||||||
shadow="xl"
|
shadow="xl"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export const OverseerrModule: IModule = {
|
|||||||
title: 'Overseerr',
|
title: 'Overseerr',
|
||||||
icon: IconEyeglass,
|
icon: IconEyeglass,
|
||||||
component: OverseerrMediaDisplay,
|
component: OverseerrMediaDisplay,
|
||||||
translationNamespace: 'modules/overseerr-module',
|
id: 'overseerr',
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface OverseerSearchProps {
|
export interface OverseerSearchProps {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useColorTheme } from '../../tools/color';
|
|||||||
import { MovieResult } from './Movie.d';
|
import { MovieResult } from './Movie.d';
|
||||||
import { MediaType, Result } from './SearchResult.d';
|
import { MediaType, Result } from './SearchResult.d';
|
||||||
import { TvShowResult, TvShowResultSeason } from './TvShow.d';
|
import { TvShowResult, TvShowResultSeason } from './TvShow.d';
|
||||||
|
|
||||||
interface RequestModalProps {
|
interface RequestModalProps {
|
||||||
base: Result;
|
base: Result;
|
||||||
opened: boolean;
|
opened: boolean;
|
||||||
@@ -57,7 +58,7 @@ export function MovieRequestModal({
|
|||||||
setOpened: (opened: boolean) => void;
|
setOpened: (opened: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const { secondaryColor } = useColorTheme();
|
const { secondaryColor } = useColorTheme();
|
||||||
const { t } = useTranslation('modules/overseerr-module');
|
const { t } = useTranslation('modules/overseerr');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -114,7 +115,7 @@ export function TvRequestModal({
|
|||||||
}) {
|
}) {
|
||||||
const [selection, setSelection] = useState<TvShowResultSeason[]>(result.seasons);
|
const [selection, setSelection] = useState<TvShowResultSeason[]>(result.seasons);
|
||||||
const { classes, cx } = useStyles();
|
const { classes, cx } = useStyles();
|
||||||
const { t } = useTranslation('modules/overseerr-module');
|
const { t } = useTranslation('modules/overseerr');
|
||||||
|
|
||||||
const toggleRow = (container: TvShowResultSeason) =>
|
const toggleRow = (container: TvShowResultSeason) =>
|
||||||
setSelection((current: TvShowResultSeason[]) =>
|
setSelection((current: TvShowResultSeason[]) =>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const PingModule: IModule = {
|
|||||||
title: 'Ping Services',
|
title: 'Ping Services',
|
||||||
icon: Plug,
|
icon: Plug,
|
||||||
component: PingComponent,
|
component: PingComponent,
|
||||||
translationNamespace: 'modules/ping-module',
|
id: 'ping',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PingComponent(props: any) {
|
export default function PingComponent(props: any) {
|
||||||
@@ -21,9 +21,9 @@ export default function PingComponent(props: any) {
|
|||||||
const { url }: { url: string } = props;
|
const { url }: { url: string } = props;
|
||||||
const [isOnline, setOnline] = useState<State>('loading');
|
const [isOnline, setOnline] = useState<State>('loading');
|
||||||
const [response, setResponse] = useState(500);
|
const [response, setResponse] = useState(500);
|
||||||
const exists = config.modules?.[PingModule.title]?.enabled ?? false;
|
const exists = config.modules?.[PingModule.id]?.enabled ?? false;
|
||||||
|
|
||||||
const { t } = useTranslation('modules/ping-module');
|
const { t } = useTranslation('modules/ping');
|
||||||
|
|
||||||
function statusCheck(response: AxiosResponse) {
|
function statusCheck(response: AxiosResponse) {
|
||||||
const { status }: { status: string[] } = props;
|
const { status }: { status: string[] } = props;
|
||||||
@@ -54,7 +54,7 @@ export default function PingComponent(props: any) {
|
|||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
statusCheck(error.response);
|
statusCheck(error.response);
|
||||||
});
|
});
|
||||||
}, [config.modules?.[PingModule.title]?.enabled]);
|
}, [config.modules?.[PingModule.id]?.enabled]);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,15 +30,15 @@ export const SearchModule: IModule = {
|
|||||||
title: 'Search',
|
title: 'Search',
|
||||||
icon: Search,
|
icon: Search,
|
||||||
component: SearchBar,
|
component: SearchBar,
|
||||||
translationNamespace: 'modules/search-module',
|
id: 'search',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SearchBar(props: any) {
|
export default function SearchBar(props: any) {
|
||||||
const { classes, cx } = useStyles();
|
const { classes, cx } = useStyles();
|
||||||
// Config
|
// Config
|
||||||
const { config } = useConfig();
|
const { config } = useConfig();
|
||||||
const isModuleEnabled = config.modules?.[SearchModule.title]?.enabled ?? false;
|
const isModuleEnabled = config.modules?.[SearchModule.id]?.enabled ?? false;
|
||||||
const isOverseerrEnabled = config.modules?.[OverseerrModule.title]?.enabled ?? false;
|
const isOverseerrEnabled = config.modules?.[OverseerrModule.id]?.enabled ?? false;
|
||||||
const OverseerrService = config.services.find(
|
const OverseerrService = config.services.find(
|
||||||
(service) => service.type === 'Overseerr' || service.type === 'Jellyseerr'
|
(service) => service.type === 'Overseerr' || service.type === 'Jellyseerr'
|
||||||
);
|
);
|
||||||
@@ -60,7 +60,7 @@ export default function SearchBar(props: any) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const [debounced, cancel] = useDebouncedValue(form.values.query, 250);
|
const [debounced, cancel] = useDebouncedValue(form.values.query, 250);
|
||||||
const { t } = useTranslation('modules/search-module');
|
const { t } = useTranslation('modules/search');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (OverseerrService === undefined && isOverseerrEnabled) {
|
if (OverseerrService === undefined && isOverseerrEnabled) {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const WeatherModule: IModule = {
|
|||||||
value: 'Paris',
|
value: 'Paris',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
translationNamespace: 'modules/weather-module',
|
id: 'weather',
|
||||||
};
|
};
|
||||||
|
|
||||||
// 0 Clear sky
|
// 0 Clear sky
|
||||||
@@ -49,7 +49,7 @@ export const WeatherModule: IModule = {
|
|||||||
// 95 *Thunderstorm: Slight or moderate
|
// 95 *Thunderstorm: Slight or moderate
|
||||||
// 96, 99 *Thunderstorm with slight and heavy hail
|
// 96, 99 *Thunderstorm with slight and heavy hail
|
||||||
export function WeatherIcon(props: any) {
|
export function WeatherIcon(props: any) {
|
||||||
const { t } = useTranslation('modules/weather-module');
|
const { t } = useTranslation('modules/weather');
|
||||||
|
|
||||||
const { code } = props;
|
const { code } = props;
|
||||||
let data: { icon: any; name: string };
|
let data: { icon: any; name: string };
|
||||||
@@ -146,9 +146,9 @@ export default function WeatherComponent(props: any) {
|
|||||||
const { config } = useConfig();
|
const { config } = useConfig();
|
||||||
const [weather, setWeather] = useState({} as WeatherResponse);
|
const [weather, setWeather] = useState({} as WeatherResponse);
|
||||||
const cityInput: string =
|
const cityInput: string =
|
||||||
(config?.modules?.[WeatherModule.title]?.options?.location?.value as string) ?? 'Paris';
|
(config?.modules?.[WeatherModule.id]?.options?.location?.value as string) ?? 'Paris';
|
||||||
const isFahrenheit: boolean =
|
const isFahrenheit: boolean =
|
||||||
(config?.modules?.[WeatherModule.title]?.options?.freedomunit?.value as boolean) ?? false;
|
(config?.modules?.[WeatherModule.id]?.options?.freedomunit?.value as boolean) ?? false;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
axios
|
axios
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { NextLink } from '@mantine/next';
|
import { NextLink } from '@mantine/next';
|
||||||
|
|
||||||
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
paddingTop: 80,
|
paddingTop: 80,
|
||||||
@@ -92,3 +94,12 @@ export default function Custom404() {
|
|||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getStaticProps({ locale }: { locale: string }) {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...(await serverSideTranslations(locale, ['404'])),
|
||||||
|
// Will be passed to the page component as props
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,18 +17,19 @@ export async function getServerSideProps({
|
|||||||
res,
|
res,
|
||||||
locale,
|
locale,
|
||||||
}: GetServerSidePropsContext): Promise<{ props: { config: Config } }> {
|
}: GetServerSidePropsContext): Promise<{ props: { config: Config } }> {
|
||||||
let cookie = getCookie('config-name', { req, res });
|
let configName = getCookie('config-name', { req, res });
|
||||||
if (!cookie) {
|
const configLocale = getCookie('config-locale', { req, res });
|
||||||
|
if (!configName) {
|
||||||
setCookie('config-name', 'default', {
|
setCookie('config-name', 'default', {
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
maxAge: 60 * 60 * 24 * 30,
|
maxAge: 60 * 60 * 24 * 30,
|
||||||
sameSite: 'strict',
|
sameSite: 'strict',
|
||||||
});
|
});
|
||||||
cookie = 'default';
|
configName = 'default';
|
||||||
}
|
}
|
||||||
|
|
||||||
const translations = await serverSideTranslations(locale as string, [
|
const translations = await serverSideTranslations((configLocale ?? locale) as string, [
|
||||||
'common',
|
'common',
|
||||||
'layout/app-shelf',
|
'layout/app-shelf',
|
||||||
'layout/add-service-app-shelf',
|
'layout/add-service-app-shelf',
|
||||||
@@ -46,19 +47,19 @@ export async function getServerSideProps({
|
|||||||
'settings/customization/app-width',
|
'settings/customization/app-width',
|
||||||
'settings/customization/opacity-selector',
|
'settings/customization/opacity-selector',
|
||||||
'modules/common',
|
'modules/common',
|
||||||
'modules/date-module',
|
'modules/date',
|
||||||
'modules/calendar-module',
|
'modules/calendar',
|
||||||
'modules/total-downloads-module',
|
'modules/dlspeed',
|
||||||
'modules/search-module',
|
'modules/search',
|
||||||
'modules/downloads-module',
|
'modules/torrents-status',
|
||||||
'modules/weather-module',
|
'modules/weather',
|
||||||
'modules/ping-module',
|
'modules/ping',
|
||||||
'modules/docker-module',
|
'modules/docker',
|
||||||
'modules/dashdot-module',
|
'modules/dashdot',
|
||||||
'modules/overseerr-module',
|
'modules/overseerr',
|
||||||
'modules/common-media-cards-module',
|
'modules/common-media-cards',
|
||||||
]);
|
]);
|
||||||
return getConfig(cookie as string, translations);
|
return getConfig(configName as string, translations);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HomePage(props: any) {
|
export default function HomePage(props: any) {
|
||||||
|
|||||||
Reference in New Issue
Block a user