-
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..3e83976c6 100644
--- a/src/modules/docker/ContainerActionBar.tsx
+++ b/src/modules/docker/ContainerActionBar.tsx
@@ -11,9 +11,10 @@ import {
} from '@tabler/icons';
import axios from 'axios';
import Dockerode from 'dockerode';
+import { useState } from 'react';
+import { useTranslation } from 'next-i18next';
import { tryMatchService } from '../../tools/addToHomarr';
import { AddAppShelfItemForm } from '../../components/AppShelf/AddAppShelfItem';
-import { useState } from 'react';
function sendDockerCommand(
action: string,
@@ -21,6 +22,8 @@ function sendDockerCommand(
containerName: string,
reload: () => void
) {
+ const { t } = useTranslation('modules/docker');
+
showNotification({
id: containerId,
loading: true,
@@ -34,8 +37,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 +47,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,
});
@@ -61,6 +64,8 @@ export interface ContainerActionBarProps {
export default function ContainerActionBar({ selected, reload }: ContainerActionBarProps) {
const [opened, setOpened] = useState
(false);
+ const { t } = useTranslation('modules/docker');
+
return (
setOpened(false)}
- title="Add service"
+ title={t('actionBar.addService.title')}
>
}
@@ -104,7 +109,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
color="red"
radius="md"
>
- Stop
+ {t('actionBar.stop.title')}
}
@@ -119,10 +124,10 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
color="green"
radius="md"
>
- Start
+ {t('actionBar.start.title')}
} onClick={() => reload()} variant="light" radius="md">
- Refresh data
+ {t('actionBar.refreshData.title')}
}
@@ -133,7 +138,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
if (selected.length !== 1) {
showNotification({
autoClose: 5000,
- title: Please only add one service at a time!,
+ title: {t('errors.oneServiceAtATime')},
color: 'red',
message: undefined,
});
@@ -142,7 +147,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
}
}}
>
- Add to Homarr
+ {t('actionBar.addToHomarr.title')}
}
@@ -157,7 +162,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
)
}
>
- Remove
+ {t('actionBar.remove.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..3def68f2b 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();
@@ -42,15 +46,15 @@ export default function DockerMenuButton(props: any) {
// 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 +71,7 @@ export default function DockerMenuButton(props: any) {
>
-
+
{
setContainers(containers);
}, [containers]);
@@ -80,7 +84,9 @@ export default function DockerTable({
))}
{element.Ports.length > 3 && (
- {element.Ports.length - 3} more
+
+ {t('table.body.portCollapse', { ports: element.Ports.length - 3 })}
+
)}
@@ -94,7 +100,7 @@ export default function DockerTable({
return (
}
value={search}
@@ -111,10 +117,10 @@ export default function DockerTable({
transitionDuration={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/downloads/DownloadsModule.tsx b/src/modules/downloads/DownloadsModule.tsx
index d81de9123..51afc5301 100644
--- a/src/modules/downloads/DownloadsModule.tsx
+++ b/src/modules/downloads/DownloadsModule.tsx
@@ -8,6 +8,7 @@ import {
Skeleton,
ScrollArea,
Center,
+ Stack,
} from '@mantine/core';
import { IconDownload as Download } from '@tabler/icons';
import { useEffect, useState } from 'react';
@@ -15,6 +16,7 @@ import axios from 'axios';
import { NormalizedTorrent } from '@ctrl/shared-torrent';
import { useViewportSize } from '@mantine/hooks';
import { showNotification } from '@mantine/notifications';
+import { useTranslation } from 'next-i18next';
import { IModule } from '../ModuleTypes';
import { useConfig } from '../../tools/state';
import { AddItemShelfButton } from '../../components/AppShelf/AddAppShelfItem';
@@ -23,15 +25,15 @@ import { humanFileSize } from '../../tools/humanFileSize';
export const DownloadsModule: IModule = {
title: 'Torrent',
- description: 'Show the current download speed of supported services',
icon: Download,
component: DownloadComponent,
options: {
hidecomplete: {
- name: 'Hide completed torrents',
+ name: 'descriptor.settings.hideComplete',
value: false,
},
},
+ id: 'torrents-status',
};
export default function DownloadComponent() {
@@ -45,10 +47,13 @@ export default function DownloadComponent() {
service.type === 'Deluge'
) ?? [];
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([]);
const setSafeInterval = useSetSafeInterval();
const [isLoading, setIsLoading] = useState(true);
+
+ const { t } = useTranslation(`modules/${DownloadsModule.id}`);
+
useEffect(() => {
setIsLoading(true);
if (downloadServices.length === 0) return;
@@ -81,13 +86,13 @@ export default function DownloadComponent() {
if (downloadServices.length === 0) {
return (
-
- No supported download clients found!
+
+ {t('card.errors.noDownloadClients.title')}
- Add a download service to view your current downloads
+ {t('card.errors.noDownloadClients.text')}
-
+
);
}
@@ -105,12 +110,12 @@ export default function DownloadComponent() {
const DEVICE_WIDTH = 576;
const ths = (
- | Name |
- Size |
- {width > 576 ? Down | : ''}
- {width > 576 ? Up | : ''}
- ETA |
- Progress |
+ {t('card.table.header.name')} |
+ {t('card.table.header.size')} |
+ {width > 576 ? {t('card.table.header.download')} | : ''}
+ {width > 576 ? {t('card.table.header.upload')} | : ''}
+ {t('card.table.header.estimatedTimeOfArrival')} |
+ {t('card.table.header.progress')} |
);
// Convert Seconds to readable format.
@@ -195,7 +200,7 @@ export default function DownloadComponent() {
) : (
- No torrents found
+ {t('card.table.body.nothingFound')}
)}
diff --git a/src/modules/downloads/TotalDownloadsModule.tsx b/src/modules/downloads/TotalDownloadsModule.tsx
index ccf7de7b3..0addfd712 100644
--- a/src/modules/downloads/TotalDownloadsModule.tsx
+++ b/src/modules/downloads/TotalDownloadsModule.tsx
@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
import axios from 'axios';
import { NormalizedTorrent } from '@ctrl/shared-torrent';
import { linearGradientDef } from '@nivo/core';
+import { useTranslation } from 'next-i18next';
import { Datum, ResponsiveLine } from '@nivo/line';
import { useListState } from '@mantine/hooks';
import { showNotification } from '@mantine/notifications';
@@ -15,9 +16,9 @@ import { useSetSafeInterval } from '../../tools/hooks/useSetSafeInterval';
export const TotalDownloadsModule: IModule = {
title: 'Download Speed',
- description: 'Show the current download speed of supported services',
icon: Download,
component: TotalDownloadsComponent,
+ id: 'dlspeed',
};
interface torrentHistory {
@@ -36,6 +37,7 @@ export default function TotalDownloadsComponent() {
service.type === 'Transmission' ||
service.type === 'Deluge'
) ?? [];
+ const { t } = useTranslation(`modules/${TotalDownloadsModule.id}`);
const [torrentHistory, torrentHistoryHandlers] = useListState([]);
const [torrents, setTorrents] = useState([]);
@@ -80,14 +82,14 @@ export default function TotalDownloadsComponent() {
if (downloadServices.length === 0) {
return (
- No supported download clients found!
+ {t('card.errors.noDownloadClients.title')}
- Add a download service to view your current downloads
+ {t('card.errors.noDownloadClients.text')}
);
@@ -107,15 +109,19 @@ export default function TotalDownloadsComponent() {
return (
- Current download speed
+ {t('card.lineChart.title')}
- Download: {humanFileSize(totalDownloadSpeed)}/s
+
+ {t('card.lineChart.totalDownload', { download: humanFileSize(totalDownloadSpeed) })}
+
- Upload: {humanFileSize(totalUploadSpeed)}/s
+
+ {t('card.lineChart.totalUpload', { upload: humanFileSize(totalUploadSpeed) })}
+
- {roundedSeconds} seconds ago
+ {t('card.lineChart.timeSpan', { seconds: roundedSeconds })}
- Download: {humanFileSize(Download)}
+
+ {t('card.lineChart.download', { download: humanFileSize(Download) })}
+
- Upload: {humanFileSize(Upload)}
+
+ {t('card.lineChart.upload', { upload: humanFileSize(Upload) })}
+
diff --git a/src/modules/moduleWrapper.tsx b/src/modules/moduleWrapper.tsx
index 90e958f91..bebb928a1 100644
--- a/src/modules/moduleWrapper.tsx
+++ b/src/modules/moduleWrapper.tsx
@@ -11,12 +11,15 @@ 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([module.id, 'common']);
+
const items: JSX.Element[] = [];
if (module.options) {
const keys = Object.keys(module.options);
@@ -25,8 +28,8 @@ 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 +120,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 +133,7 @@ function getItems(module: IModule) {
},
});
}}
- label={values[index].name}
+ label={t(values[index].name)}
/>
);
}
@@ -145,9 +148,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 +160,7 @@ export function ModuleWrapper(props: any) {
return (
{module.options && (