-
Storage:
+
{t('card.graphs.storage.label')}
{((100 * totalUsed) / (totalSize || 1)).toFixed(1)}%{'\n'}
{bytePrettyPrint(totalUsed)} / {bytePrettyPrint(totalSize)}
@@ -204,10 +210,12 @@ export function DashdotComponent() {
)}
{networkEnabled && (
-
Network:
+
{t('card.graphs.network.label')}
- {bpsPrettyPrint(info?.network?.speedUp)} Up{'\n'}
- {bpsPrettyPrint(info?.network?.speedDown)} Down
+ {bpsPrettyPrint(info?.network?.speedUp)} {t('card.graphs.network.metrics.upload')}
+ {'\n'}
+ {bpsPrettyPrint(info?.network?.speedDown)}
+ {t('card.graphs.network.metrics.download')}
)}
diff --git a/src/modules/date/DateModule.tsx b/src/modules/date/DateModule.tsx
index 72a83fba8..3a57491fd 100644
--- a/src/modules/date/DateModule.tsx
+++ b/src/modules/date/DateModule.tsx
@@ -8,22 +8,22 @@ import { useSetSafeInterval } from '../../tools/hooks/useSetSafeInterval';
export const DateModule: IModule = {
title: 'Date',
- description: 'Show the current time and date in a card',
icon: Clock,
component: DateComponent,
options: {
full: {
- name: 'Display full time (24-hour)',
+ name: 'descriptor.settings.display24HourFormat.label',
value: true,
},
},
+ id: 'date',
};
export default function DateComponent(props: any) {
const [date, setDate] = useState(new Date());
const setSafeInterval = useSetSafeInterval();
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';
// Change date on minute change
// Note: Using 10 000ms instead of 1000ms to chill a little :)
diff --git a/src/modules/docker/ContainerActionBar.tsx b/src/modules/docker/ContainerActionBar.tsx
index 0ae7e2ed1..125ccd80c 100644
--- a/src/modules/docker/ContainerActionBar.tsx
+++ b/src/modules/docker/ContainerActionBar.tsx
@@ -1,4 +1,6 @@
-import { Button, Group, Modal, Title } from '@mantine/core';
+/* eslint-disable @typescript-eslint/no-non-null-assertion */
+import { Button, Group, TextInput, Title } from '@mantine/core';
+import { closeAllModals, closeModal, openModal } from '@mantine/modals';
import { showNotification, updateNotification } from '@mantine/notifications';
import {
IconCheck,
@@ -11,9 +13,14 @@ import {
} from '@tabler/icons';
import axios from 'axios';
import Dockerode from 'dockerode';
-import { tryMatchService } from '../../tools/addToHomarr';
-import { AddAppShelfItemForm } from '../../components/AppShelf/AddAppShelfItem';
+import { useTranslation } from 'next-i18next';
import { useState } from 'react';
+import { TFunction } from 'react-i18next';
+import { AddAppShelfItemForm } from '../../components/AppShelf/AddAppShelfItem';
+import { tryMatchService } from '../../tools/addToHomarr';
+import { useConfig } from '../../tools/state';
+
+let t: TFunction<'modules/docker', undefined>;
function sendDockerCommand(
action: string,
@@ -34,8 +41,8 @@ function sendDockerCommand(
.then((res) => {
updateNotification({
id: containerId,
- title: `Container ${containerName} ${action}ed`,
- message: `Your container was successfully ${action}ed`,
+ title: t('messages.successfullyExecuted.message', { containerName, action }),
+ message: t('messages.successfullyExecuted.message', { action }),
icon:
,
autoClose: 2000,
});
@@ -44,7 +51,7 @@ function sendDockerCommand(
updateNotification({
id: containerId,
color: 'red',
- title: 'There was an error',
+ title: t('errors.unknownError.title'),
message: err.response.data.reason,
autoClose: 2000,
});
@@ -60,22 +67,28 @@ export interface ContainerActionBarProps {
}
export default function ContainerActionBar({ selected, reload }: ContainerActionBarProps) {
- const [opened, setOpened] = useState
(false);
+ t = useTranslation('modules/docker').t;
+ const [isLoading, setisLoading] = useState(false);
+ const { config, setConfig } = useConfig();
+
return (
- }
+ onClick={() => {
+ setisLoading(true);
+ setTimeout(() => {
+ reload();
+ setisLoading(false);
+ }, 750);
+ }}
+ variant="light"
+ color="violet"
+ loading={isLoading}
radius="md"
- opened={opened}
- onClose={() => setOpened(false)}
- title="Add service"
>
-
-
+ {t('actionBar.refreshData.title')}
+
}
onClick={() =>
@@ -88,8 +101,9 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
variant="light"
color="orange"
radius="md"
+ disabled={selected.length === 0}
>
- Restart
+ {t('actionBar.restart.title')}
}
@@ -103,8 +117,9 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
variant="light"
color="red"
radius="md"
+ disabled={selected.length === 0}
>
- Stop
+ {t('actionBar.stop.title')}
}
@@ -118,31 +133,9 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
variant="light"
color="green"
radius="md"
+ disabled={selected.length === 0}
>
- Start
-
- } onClick={() => reload()} variant="light" radius="md">
- Refresh data
-
- }
- color="indigo"
- variant="light"
- radius="md"
- onClick={() => {
- if (selected.length !== 1) {
- showNotification({
- autoClose: 5000,
- title: Please only add one service at a time!,
- color: 'red',
- message: undefined,
- });
- } else {
- setOpened(true);
- }
- }}
- >
- Add to Homarr
+ {t('actionBar.start.title')}
}
@@ -156,8 +149,36 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
)
)
}
+ disabled={selected.length === 0}
>
- Remove
+ {t('actionBar.remove.title')}
+
+ }
+ color="indigo"
+ variant="light"
+ radius="md"
+ disabled={selected.length === 0 || selected.length > 1}
+ onClick={() => {
+ openModal({
+ size: 'xl',
+ modalId: selected.at(0)!.Id,
+ radius: 'md',
+ title: t('actionBar.addService.title'),
+ zIndex: 500,
+ children: (
+ closeModal(selected.at(0)!.Id)}
+ message={t('actionBar.addService.message')}
+ {...tryMatchService(selected.at(0)!)}
+ />
+ ),
+ });
+ }}
+ >
+ {t('actionBar.addToHomarr.title')}
);
diff --git a/src/modules/docker/ContainerState.tsx b/src/modules/docker/ContainerState.tsx
index d5c6b5077..d124f9f44 100644
--- a/src/modules/docker/ContainerState.tsx
+++ b/src/modules/docker/ContainerState.tsx
@@ -1,4 +1,5 @@
import { Badge, BadgeVariant, MantineSize } from '@mantine/core';
+import { useTranslation } from 'next-i18next';
import Dockerode from 'dockerode';
export interface ContainerStateProps {
@@ -7,6 +8,9 @@ export interface ContainerStateProps {
export default function ContainerState(props: ContainerStateProps) {
const { state } = props;
+
+ const { t } = useTranslation('modules/docker');
+
const options: {
size: MantineSize;
radius: MantineSize;
@@ -20,28 +24,28 @@ export default function ContainerState(props: ContainerStateProps) {
case 'running': {
return (
- Running
+ {t('table.states.running')}
);
}
case 'created': {
return (
- Created
+ {t('table.states.created')}
);
}
case 'exited': {
return (
- Stopped
+ {t('table.states.stopped')}
);
}
default: {
return (
- Unknown
+ {t('table.states.unknown')}
);
}
diff --git a/src/modules/docker/DockerModule.tsx b/src/modules/docker/DockerModule.tsx
index caca75306..61101723e 100644
--- a/src/modules/docker/DockerModule.tsx
+++ b/src/modules/docker/DockerModule.tsx
@@ -1,9 +1,11 @@
-import { ActionIcon, Drawer, Group, LoadingOverlay, Text, Tooltip } from '@mantine/core';
+import { ActionIcon, Drawer, Text, Tooltip } from '@mantine/core';
import axios from 'axios';
import { useEffect, useState } from 'react';
import Docker from 'dockerode';
import { IconBrandDocker, IconX } from '@tabler/icons';
import { showNotification } from '@mantine/notifications';
+import { useTranslation } from 'next-i18next';
+
import ContainerActionBar from './ContainerActionBar';
import DockerTable from './DockerTable';
import { useConfig } from '../../tools/state';
@@ -11,9 +13,9 @@ import { IModule } from '../ModuleTypes';
export const DockerModule: IModule = {
title: 'Docker',
- description: 'Allows you to easily manage your torrents',
icon: IconBrandDocker,
component: DockerMenuButton,
+ id: 'docker',
};
export default function DockerMenuButton(props: any) {
@@ -21,7 +23,9 @@ export default function DockerMenuButton(props: any) {
const [containers, setContainers] = useState([]);
const [selection, setSelection] = useState([]);
const { config } = useConfig();
- const moduleEnabled = config.modules?.[DockerModule.title]?.enabled ?? false;
+ const moduleEnabled = config.modules?.[DockerModule.id]?.enabled ?? false;
+
+ const { t } = useTranslation('modules/docker');
useEffect(() => {
reload();
@@ -38,19 +42,21 @@ export default function DockerMenuButton(props: any) {
setContainers(res.data);
setSelection([]);
})
- .catch(() =>
+ .catch(() => {
+ // Remove containers from the list
+ setContainers([]);
// Send an Error notification
showNotification({
autoClose: 1500,
- title: Docker integration failed,
+ title: {t('errors.integrationFailed.title')},
color: 'red',
icon: ,
- message: 'Did you forget to mount the docker socket ?',
- })
- );
+ message: t('errors.integrationFailed.message'),
+ });
+ });
}, 300);
}
- const exists = config.modules?.[DockerModule.title]?.enabled ?? false;
+ const exists = config.modules?.[DockerModule.id]?.enabled ?? false;
if (!exists) {
return null;
}
@@ -67,7 +73,7 @@ export default function DockerMenuButton(props: any) {
>
-
+
{
setContainers(containers);
}, [containers]);
@@ -80,7 +91,9 @@ export default function DockerTable({
))}
{element.Ports.length > 3 && (
- {element.Ports.length - 3} more
+
+ {t('table.body.portCollapse', { ports: element.Ports.length - 3 })}
+
)}
@@ -94,11 +107,12 @@ export default function DockerTable({
return (
}
value={search}
onChange={handleSearchChange}
+ disabled={usedContainers.length === 0}
/>
@@ -106,15 +120,16 @@ export default function DockerTable({
|
0}
indeterminate={selection.length > 0 && selection.length !== usedContainers.length}
transitionDuration={0}
+ disabled={usedContainers.length === 0}
/>
|
- Name |
- Image |
- Ports |
- State |
+ {t('table.header.name')} |
+ {t('table.header.image')} |
+ {t('table.header.ports')} |
+ {t('table.header.state')} |
{rows}
diff --git a/src/modules/index.ts b/src/modules/index.ts
index 88cb1ad02..f3b7292a7 100644
--- a/src/modules/index.ts
+++ b/src/modules/index.ts
@@ -1,9 +1,10 @@
export * from './calendar';
export * from './dashdot';
export * from './date';
-export * from './downloads';
+export * from './torrents';
export * from './ping';
export * from './search';
export * from './weather';
export * from './docker';
export * from './overseerr';
+export * from './usenet';
diff --git a/src/modules/moduleWrapper.tsx b/src/modules/moduleWrapper.tsx
index 90e958f91..c429e80af 100644
--- a/src/modules/moduleWrapper.tsx
+++ b/src/modules/moduleWrapper.tsx
@@ -11,12 +11,14 @@ import {
} from '@mantine/core';
import { IconAdjustments } from '@tabler/icons';
import { motion } from 'framer-motion';
+import { useTranslation } from 'next-i18next';
import { useState } from 'react';
import { useConfig } from '../tools/state';
import { IModule } from './ModuleTypes';
function getItems(module: IModule) {
const { config, setConfig } = useConfig();
+ const { t } = useTranslation([`modules/${module.id}`, 'common']);
const items: JSX.Element[] = [];
if (module.options) {
const keys = Object.keys(module.options);
@@ -25,12 +27,12 @@ function getItems(module: IModule) {
const types = values.map((v) => typeof v.value);
// Loop over all the types with a for each loop
types.forEach((type, index) => {
- const optionName = `${module.title}.${keys[index]}`;
- const moduleInConfig = config.modules?.[module.title];
+ const optionName = `${module.id}.${keys[index]}`;
+ const moduleInConfig = config.modules?.[module.id];
if (type === 'object') {
items.push(
{}}
/>
-
+
);
@@ -117,12 +119,12 @@ function getItems(module: IModule) {
...config,
modules: {
...config.modules,
- [module.title]: {
- ...config.modules[module.title],
+ [module.id]: {
+ ...config.modules[module.id],
options: {
- ...config.modules[module.title].options,
+ ...config.modules[module.id].options,
[keys[index]]: {
- ...config.modules[module.title].options?.[keys[index]],
+ ...config.modules[module.id].options?.[keys[index]],
value: e.currentTarget.checked,
},
},
@@ -130,7 +132,7 @@ function getItems(module: IModule) {
},
});
}}
- label={values[index].name}
+ label={t(values[index].name)}
/>
);
}
@@ -145,9 +147,10 @@ export function ModuleWrapper(props: any) {
const { config, setConfig } = useConfig();
const enabledModules = config.modules ?? {};
// 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
const [hovering, setHovering] = useState(false);
+ const { t } = useTranslation('modules');
if (!isShown) {
return null;
@@ -156,7 +159,7 @@ export function ModuleWrapper(props: any) {
return (
{module.options && (