diff --git a/public/locales/de/modules/calendar-module.json b/public/locales/de/modules/calendar.json similarity index 100% rename from public/locales/de/modules/calendar-module.json rename to public/locales/de/modules/calendar.json diff --git a/public/locales/de/modules/common-media-cards-module.json b/public/locales/de/modules/common-media-cards.json similarity index 100% rename from public/locales/de/modules/common-media-cards-module.json rename to public/locales/de/modules/common-media-cards.json diff --git a/public/locales/de/modules/dashdot-module.json b/public/locales/de/modules/dashdot.json similarity index 100% rename from public/locales/de/modules/dashdot-module.json rename to public/locales/de/modules/dashdot.json diff --git a/public/locales/de/modules/overseerr-module.json b/public/locales/de/modules/overseerr.json similarity index 100% rename from public/locales/de/modules/overseerr-module.json rename to public/locales/de/modules/overseerr.json diff --git a/public/locales/de/modules/ping-module.json b/public/locales/de/modules/ping.json similarity index 100% rename from public/locales/de/modules/ping-module.json rename to public/locales/de/modules/ping.json diff --git a/public/locales/de/modules/search-module.json b/public/locales/de/modules/search.json similarity index 100% rename from public/locales/de/modules/search-module.json rename to public/locales/de/modules/search.json diff --git a/public/locales/de/modules/downloads-module.json b/public/locales/de/modules/torrents-status.json similarity index 100% rename from public/locales/de/modules/downloads-module.json rename to public/locales/de/modules/torrents-status.json diff --git a/public/locales/de/modules/weather-module.json b/public/locales/de/modules/weather.json similarity index 100% rename from public/locales/de/modules/weather-module.json rename to public/locales/de/modules/weather.json diff --git a/public/locales/en/modules/calendar-module.json b/public/locales/en/modules/calendar.json similarity index 100% rename from public/locales/en/modules/calendar-module.json rename to public/locales/en/modules/calendar.json diff --git a/public/locales/en/modules/common-media-cards-module.json b/public/locales/en/modules/common-media-cards.json similarity index 100% rename from public/locales/en/modules/common-media-cards-module.json rename to public/locales/en/modules/common-media-cards.json diff --git a/public/locales/en/modules/dashdot-module.json b/public/locales/en/modules/dashdot.json similarity index 100% rename from public/locales/en/modules/dashdot-module.json rename to public/locales/en/modules/dashdot.json diff --git a/public/locales/en/modules/date-module.json b/public/locales/en/modules/date.json similarity index 100% rename from public/locales/en/modules/date-module.json rename to public/locales/en/modules/date.json diff --git a/public/locales/en/modules/total-downloads-module.json b/public/locales/en/modules/dlspeed.json similarity index 100% rename from public/locales/en/modules/total-downloads-module.json rename to public/locales/en/modules/dlspeed.json diff --git a/public/locales/en/modules/docker-module.json b/public/locales/en/modules/docker.json similarity index 100% rename from public/locales/en/modules/docker-module.json rename to public/locales/en/modules/docker.json diff --git a/public/locales/en/modules/overseerr-module.json b/public/locales/en/modules/overseerr.json similarity index 100% rename from public/locales/en/modules/overseerr-module.json rename to public/locales/en/modules/overseerr.json diff --git a/public/locales/en/modules/ping-module.json b/public/locales/en/modules/ping-module.json deleted file mode 100644 index 305985605..000000000 --- a/public/locales/en/modules/ping-module.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "states": { - "online": "Online {{response}}", - "offline": "Offline {{response}}", - "loading": "Loading..." - } -} \ No newline at end of file diff --git a/public/locales/en/modules/ping.json b/public/locales/en/modules/ping.json new file mode 100644 index 000000000..403c8027b --- /dev/null +++ b/public/locales/en/modules/ping.json @@ -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..." + } +} \ No newline at end of file diff --git a/public/locales/en/modules/search-module.json b/public/locales/en/modules/search.json similarity index 100% rename from public/locales/en/modules/search-module.json rename to public/locales/en/modules/search.json diff --git a/public/locales/en/modules/downloads-module.json b/public/locales/en/modules/torrents-status.json similarity index 100% rename from public/locales/en/modules/downloads-module.json rename to public/locales/en/modules/torrents-status.json diff --git a/public/locales/en/modules/weather-module.json b/public/locales/en/modules/weather.json similarity index 100% rename from public/locales/en/modules/weather-module.json rename to public/locales/en/modules/weather.json diff --git a/src/components/AppShelf/AppShelf.tsx b/src/components/AppShelf/AppShelf.tsx index 34b560860..51aed451e 100644 --- a/src/components/AppShelf/AppShelf.tsx +++ b/src/components/AppShelf/AppShelf.tsx @@ -126,7 +126,7 @@ const AppShelf = (props: any) => { const noCategory = config.services.filter( (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 return ( // TODO: Style accordion so that the bar is transparent to the user settings diff --git a/src/components/Settings/LanguageSwitch.tsx b/src/components/Settings/LanguageSwitch.tsx index d670571b3..22b6485c2 100644 --- a/src/components/Settings/LanguageSwitch.tsx +++ b/src/components/Settings/LanguageSwitch.tsx @@ -4,14 +4,17 @@ import { showNotification } from '@mantine/notifications'; import { forwardRef, useState } from 'react'; import { useTranslation } from 'next-i18next'; import { useRouter } from 'next/router'; +import { getCookie, setCookie } from 'cookies-next'; import { getLanguageByCode, Language } from '../../languages/language'; export default function LanguageSwitch() { const { t, i18n } = useTranslation('settings/general/internationalization'); const { changeLanguage } = i18n; - + const configLocale = getCookie('config-locale'); const { locale, locales } = useRouter(); - const [selectedLanguage, setSelectedLanguage] = useState(locale); + const [selectedLanguage, setSelectedLanguage] = useState( + (configLocale as string) ?? locale + ); const data = locales ? locales.map((localeItem) => ({ @@ -26,9 +29,13 @@ export default function LanguageSwitch() { setSelectedLanguage(value); const newLanguage = getLanguageByCode(value); - changeLanguage(value) .then(() => { + setCookie('config-locale', value, { + maxAge: 60 * 60 * 24 * 30, + sameSite: 'strict', + }); + showNotification({ title: 'Language changed', message: `You changed the language to '${newLanguage.originalName}'`, diff --git a/src/components/Settings/ModuleEnabler.tsx b/src/components/Settings/ModuleEnabler.tsx index 8f2aa12d3..dc3820d35 100644 --- a/src/components/Settings/ModuleEnabler.tsx +++ b/src/components/Settings/ModuleEnabler.tsx @@ -1,5 +1,4 @@ -import { Checkbox, Popover, SimpleGrid, Stack, Text, Title } from '@mantine/core'; -import { useDisclosure } from '@mantine/hooks'; +import { Checkbox, HoverCard, SimpleGrid, Stack, Text, Title } from '@mantine/core'; import { useTranslation } from 'next-i18next'; import * as Modules from '../../modules'; import { IModule } from '../../modules/ModuleTypes'; @@ -11,9 +10,9 @@ export default function ModuleEnabler(props: any) { return ( {t('title')} - + {modules.map((module) => ( - + ))} @@ -22,39 +21,36 @@ export default function ModuleEnabler(props: any) { const ModuleToggle = ({ module }: { module: IModule }) => { const { config, setConfig } = useConfig(); - const { t: translationModules } = useTranslation(module.translationNamespace); - const [opened, { close, open }] = useDisclosure(false); + const { t } = useTranslation(`modules/${module.id}`); return ( - - -
- { - setConfig({ - ...config, - modules: { - ...config.modules, - [module.title]: { - ...config.modules?.[module.title], - enabled: e.currentTarget.checked, - }, + + + { + setConfig({ + ...config, + modules: { + ...config.modules, + [module.id]: { + ...config.modules?.[module.id], + enabled: e.currentTarget.checked, }, - }); - }} - /> -
-
- - {translationModules('descriptor.name')} - {translationModules('descriptor.description')} - -
+ }, + }); + }} + /> + + + {t('descriptor.name')} + {t('descriptor.description')} + + ); }; diff --git a/src/modules/ModuleTypes.d.ts b/src/modules/ModuleTypes.d.ts index 8db822d02..8ccf82c9d 100644 --- a/src/modules/ModuleTypes.d.ts +++ b/src/modules/ModuleTypes.d.ts @@ -6,11 +6,11 @@ import { TablerIcon } from '@tabler/icons'; // Note: Maybe use context to keep track of the modules export interface IModule { + id: string; title: string; icon: TablerIcon; component: React.ComponentType; options?: Option; - translationNamespace: string; } interface Option { diff --git a/src/modules/calendar/CalendarModule.tsx b/src/modules/calendar/CalendarModule.tsx index 03b539858..171bd8106 100644 --- a/src/modules/calendar/CalendarModule.tsx +++ b/src/modules/calendar/CalendarModule.tsx @@ -34,7 +34,7 @@ export const CalendarModule: IModule = { value: false, }, }, - translationNamespace: 'modules/calendar-module', + id: 'calendar', }; export default function CalendarComponent(props: any) { @@ -127,7 +127,7 @@ export default function CalendarComponent(props: any) { }, [config.services]); const weekStartsAtSunday = - (config?.modules?.[CalendarModule.title]?.options?.sundaystart?.value as boolean) ?? false; + (config?.modules?.[CalendarModule.id]?.options?.sundaystart?.value as boolean) ?? false; return ( diff --git a/src/modules/dashdot/DashdotModule.tsx b/src/modules/dashdot/DashdotModule.tsx index 22b8da60f..8d49497ad 100644 --- a/src/modules/dashdot/DashdotModule.tsx +++ b/src/modules/dashdot/DashdotModule.tsx @@ -41,7 +41,7 @@ export const DashdotModule = asModule({ value: '', }, }, - translationNamespace: 'modules/dashdot-module', + id: 'dashdot', }); const useStyles = createStyles((theme, _params) => ({ @@ -126,7 +126,7 @@ export function DashdotComponent() { const { classes } = useStyles(); const { colorScheme } = useMantineColorScheme(); - const dashConfig = config.modules?.[DashdotModule.title] + const dashConfig = config.modules?.[DashdotModule.id] .options as typeof DashdotModule['options']; const isCompact = dashConfig?.useCompactView?.value ?? false; const dashdotService: serviceItem | undefined = config.services.filter( @@ -148,7 +148,7 @@ export function DashdotComponent() { const totalSize = (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 = [ { diff --git a/src/modules/date/DateModule.tsx b/src/modules/date/DateModule.tsx index 51697cd0d..3a57491fd 100644 --- a/src/modules/date/DateModule.tsx +++ b/src/modules/date/DateModule.tsx @@ -16,14 +16,14 @@ export const DateModule: IModule = { value: true, }, }, - translationNamespace: 'modules/date-module', + 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 a12731d9d..3e83976c6 100644 --- a/src/modules/docker/ContainerActionBar.tsx +++ b/src/modules/docker/ContainerActionBar.tsx @@ -22,7 +22,7 @@ function sendDockerCommand( containerName: string, reload: () => void ) { - const { t } = useTranslation('modules/docker-module'); + const { t } = useTranslation('modules/docker'); showNotification({ id: containerId, @@ -64,7 +64,7 @@ export interface ContainerActionBarProps { export default function ContainerActionBar({ selected, reload }: ContainerActionBarProps) { const [opened, setOpened] = useState(false); - const { t } = useTranslation('modules/docker-module'); + const { t } = useTranslation('modules/docker'); return ( diff --git a/src/modules/docker/ContainerState.tsx b/src/modules/docker/ContainerState.tsx index 459dcb70f..d124f9f44 100644 --- a/src/modules/docker/ContainerState.tsx +++ b/src/modules/docker/ContainerState.tsx @@ -9,7 +9,7 @@ export interface ContainerStateProps { export default function ContainerState(props: ContainerStateProps) { const { state } = props; - const { t } = useTranslation('modules/docker-module'); + const { t } = useTranslation('modules/docker'); const options: { size: MantineSize; diff --git a/src/modules/docker/DockerModule.tsx b/src/modules/docker/DockerModule.tsx index c279f5f95..3def68f2b 100644 --- a/src/modules/docker/DockerModule.tsx +++ b/src/modules/docker/DockerModule.tsx @@ -15,7 +15,7 @@ export const DockerModule: IModule = { title: 'Docker', icon: IconBrandDocker, component: DockerMenuButton, - translationNamespace: 'modules/docker-module', + id: 'docker', }; export default function DockerMenuButton(props: any) { @@ -23,9 +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-module'); + const { t } = useTranslation('modules/docker'); useEffect(() => { reload(); @@ -54,7 +54,7 @@ export default function DockerMenuButton(props: any) { ); }, 300); } - const exists = config.modules?.[DockerModule.title]?.enabled ?? false; + const exists = config.modules?.[DockerModule.id]?.enabled ?? false; if (!exists) { return null; } diff --git a/src/modules/docker/DockerTable.tsx b/src/modules/docker/DockerTable.tsx index b9e06e038..2b06a1b0e 100644 --- a/src/modules/docker/DockerTable.tsx +++ b/src/modules/docker/DockerTable.tsx @@ -28,7 +28,7 @@ export default function DockerTable({ const { classes, cx } = useStyles(); const [search, setSearch] = useState(''); - const { t } = useTranslation('modules/docker-module'); + const { t } = useTranslation('modules/docker'); useEffect(() => { setContainers(containers); diff --git a/src/modules/downloads/DownloadsModule.tsx b/src/modules/downloads/DownloadsModule.tsx index 38b470890..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'; @@ -32,7 +33,7 @@ export const DownloadsModule: IModule = { value: false, }, }, - translationNamespace: 'modules/downloads-module', + id: 'torrents-status', }; export default function DownloadComponent() { @@ -46,12 +47,12 @@ 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/downloads-module'); + const { t } = useTranslation(`modules/${DownloadsModule.id}`); useEffect(() => { setIsLoading(true); @@ -85,13 +86,13 @@ export default function DownloadComponent() { if (downloadServices.length === 0) { return ( - + {t('card.errors.noDownloadClients.title')} {t('card.errors.noDownloadClients.text')} - + ); } diff --git a/src/modules/downloads/TotalDownloadsModule.tsx b/src/modules/downloads/TotalDownloadsModule.tsx index d3c7ddc2b..0addfd712 100644 --- a/src/modules/downloads/TotalDownloadsModule.tsx +++ b/src/modules/downloads/TotalDownloadsModule.tsx @@ -18,7 +18,7 @@ export const TotalDownloadsModule: IModule = { title: 'Download Speed', icon: Download, component: TotalDownloadsComponent, - translationNamespace: 'modules/total-downloads-module', + id: 'dlspeed', }; interface torrentHistory { @@ -37,7 +37,7 @@ export default function TotalDownloadsComponent() { service.type === 'Transmission' || service.type === 'Deluge' ) ?? []; - const { t } = useTranslation('modules/downloads-module'); + const { t } = useTranslation(`modules/${TotalDownloadsModule.id}`); const [torrentHistory, torrentHistoryHandlers] = useListState([]); const [torrents, setTorrents] = useState([]); diff --git a/src/modules/moduleWrapper.tsx b/src/modules/moduleWrapper.tsx index 17bdb105f..bebb928a1 100644 --- a/src/modules/moduleWrapper.tsx +++ b/src/modules/moduleWrapper.tsx @@ -18,7 +18,7 @@ import { IModule } from './ModuleTypes'; function getItems(module: IModule) { const { config, setConfig } = useConfig(); - const { t } = useTranslation(module.translationNamespace); + const { t } = useTranslation([module.id, 'common']); const items: JSX.Element[] = []; if (module.options) { @@ -28,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( {}} /> - + ); @@ -120,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, }, }, @@ -148,7 +148,7 @@ 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'); @@ -160,7 +160,7 @@ export function ModuleWrapper(props: any) { return (