chore(release): automatic release v0.1.0
This commit is contained in:
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* text eol=lf
|
||||
@@ -45,9 +45,9 @@
|
||||
"@million/lint": "1.0.11",
|
||||
"@t3-oss/env-nextjs": "^0.11.1",
|
||||
"@tabler/icons-react": "^3.21.0",
|
||||
"@tanstack/react-query": "^5.59.16",
|
||||
"@tanstack/react-query-devtools": "^5.59.16",
|
||||
"@tanstack/react-query-next-experimental": "5.59.16",
|
||||
"@tanstack/react-query": "^5.59.19",
|
||||
"@tanstack/react-query-devtools": "^5.59.19",
|
||||
"@tanstack/react-query-next-experimental": "5.59.19",
|
||||
"@trpc/client": "next",
|
||||
"@trpc/next": "next",
|
||||
"@trpc/react-query": "next",
|
||||
@@ -70,7 +70,7 @@
|
||||
"react-dom": "^18.3.1",
|
||||
"react-error-boundary": "^4.1.2",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
"sass": "^1.80.5",
|
||||
"sass": "^1.80.6",
|
||||
"superjson": "2.2.1",
|
||||
"swagger-ui-react": "^5.17.14",
|
||||
"use-deep-compare-effect": "^1.8.1"
|
||||
@@ -80,13 +80,13 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/chroma-js": "2.4.4",
|
||||
"@types/node": "^22.8.6",
|
||||
"@types/node": "^22.8.7",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/swagger-ui-react": "^4.18.3",
|
||||
"concurrently": "^9.0.1",
|
||||
"eslint": "^9.13.0",
|
||||
"concurrently": "^9.1.0",
|
||||
"eslint": "^9.14.0",
|
||||
"node-loader": "^2.0.0",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "^5.6.3"
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import type { PropsWithChildren } from "react";
|
||||
|
||||
import { useSuspenseDayJsLocalization } from "@homarr/translation/dayjs";
|
||||
|
||||
export const DayJsLoader = ({ children }: PropsWithChildren) => {
|
||||
// Load the dayjs localization for the current locale with suspense
|
||||
useSuspenseDayJsLocalization();
|
||||
|
||||
return children;
|
||||
};
|
||||
@@ -8,14 +8,18 @@ import dayjs from "dayjs";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useSession } from "@homarr/auth/client";
|
||||
import { parseCookies, setClientCookie } from "@homarr/common";
|
||||
import type { ColorScheme } from "@homarr/definitions";
|
||||
import { colorSchemeCookieKey } from "@homarr/definitions";
|
||||
|
||||
export const CustomMantineProvider = ({ children }: PropsWithChildren) => {
|
||||
export const CustomMantineProvider = ({
|
||||
children,
|
||||
defaultColorScheme,
|
||||
}: PropsWithChildren<{ defaultColorScheme: ColorScheme }>) => {
|
||||
const manager = useColorSchemeManager();
|
||||
|
||||
return (
|
||||
<DirectionProvider>
|
||||
<MantineProvider
|
||||
defaultColorScheme="dark"
|
||||
defaultColorScheme={defaultColorScheme}
|
||||
colorSchemeManager={manager}
|
||||
theme={createTheme({
|
||||
primaryColor: "red",
|
||||
@@ -28,12 +32,11 @@ export const CustomMantineProvider = ({ children }: PropsWithChildren) => {
|
||||
);
|
||||
};
|
||||
|
||||
function useColorSchemeManager(): MantineColorSchemeManager {
|
||||
const key = "homarr-color-scheme";
|
||||
export function useColorSchemeManager(): MantineColorSchemeManager {
|
||||
const { data: session } = useSession();
|
||||
|
||||
const updateCookieValue = (value: Exclude<MantineColorScheme, "auto">) => {
|
||||
setClientCookie(key, value, { expires: dayjs().add(1, "year").toDate(), path: "/" });
|
||||
setClientCookie(colorSchemeCookieKey, value, { expires: dayjs().add(1, "year").toDate(), path: "/" });
|
||||
};
|
||||
|
||||
const { mutate: mutateColorScheme } = clientApi.user.changeColorScheme.useMutation({
|
||||
@@ -50,7 +53,7 @@ function useColorSchemeManager(): MantineColorSchemeManager {
|
||||
|
||||
try {
|
||||
const cookies = parseCookies(document.cookie);
|
||||
return (cookies[key] as MantineColorScheme | undefined) ?? defaultValue;
|
||||
return (cookies[colorSchemeCookieKey] as MantineColorScheme | undefined) ?? defaultValue;
|
||||
} catch {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,17 @@ import type { PropsWithChildren } from "react";
|
||||
import type { MantineColorsTuple } from "@mantine/core";
|
||||
import { createTheme, darken, lighten, MantineProvider } from "@mantine/core";
|
||||
|
||||
import type { ColorScheme } from "@homarr/definitions";
|
||||
|
||||
import { useColorSchemeManager } from "../../_client-providers/mantine";
|
||||
import { useRequiredBoard } from "./_context";
|
||||
|
||||
export const BoardMantineProvider = ({ children }: PropsWithChildren) => {
|
||||
export const BoardMantineProvider = ({
|
||||
children,
|
||||
defaultColorScheme,
|
||||
}: PropsWithChildren<{ defaultColorScheme: ColorScheme }>) => {
|
||||
const board = useRequiredBoard();
|
||||
const colorSchemeManager = useColorSchemeManager();
|
||||
|
||||
const theme = createTheme({
|
||||
colors: {
|
||||
@@ -18,7 +25,11 @@ export const BoardMantineProvider = ({ children }: PropsWithChildren) => {
|
||||
autoContrast: true,
|
||||
});
|
||||
|
||||
return <MantineProvider theme={theme}>{children}</MantineProvider>;
|
||||
return (
|
||||
<MantineProvider defaultColorScheme={defaultColorScheme} theme={theme} colorSchemeManager={colorSchemeManager}>
|
||||
{children}
|
||||
</MantineProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const generateColors = (hex: string) => {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { BoardRenameModal } from "~/components/board/modals/board-rename-modal";
|
||||
import { useRequiredBoard } from "../../(content)/_context";
|
||||
import classes from "./danger.module.css";
|
||||
|
||||
export const DangerZoneSettingsContent = () => {
|
||||
export const DangerZoneSettingsContent = ({ hideVisibility }: { hideVisibility: boolean }) => {
|
||||
const board = useRequiredBoard();
|
||||
const t = useScopedI18n("board.setting");
|
||||
const router = useRouter();
|
||||
@@ -90,14 +90,18 @@ export const DangerZoneSettingsContent = () => {
|
||||
buttonText={t("section.dangerZone.action.rename.button")}
|
||||
onClick={onRenameClick}
|
||||
/>
|
||||
<Divider />
|
||||
<DangerZoneRow
|
||||
label={t("section.dangerZone.action.visibility.label")}
|
||||
description={t(`section.dangerZone.action.visibility.description.${visibility}`)}
|
||||
buttonText={t(`section.dangerZone.action.visibility.button.${visibility}`)}
|
||||
onClick={onVisibilityClick}
|
||||
isPending={isChangeVisibilityPending}
|
||||
/>
|
||||
{hideVisibility ? null : (
|
||||
<>
|
||||
<Divider />
|
||||
<DangerZoneRow
|
||||
label={t("section.dangerZone.action.visibility.label")}
|
||||
description={t(`section.dangerZone.action.visibility.description.${visibility}`)}
|
||||
buttonText={t(`section.dangerZone.action.visibility.button.${visibility}`)}
|
||||
onClick={onVisibilityClick}
|
||||
isPending={isChangeVisibilityPending}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Divider />
|
||||
<DangerZoneRow
|
||||
label={t("section.dangerZone.action.delete.label")}
|
||||
|
||||
@@ -14,6 +14,8 @@ import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { api } from "@homarr/api/server";
|
||||
import { capitalize } from "@homarr/common";
|
||||
import { db } from "@homarr/db";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import type { TranslationObject } from "@homarr/translation";
|
||||
import { getScopedI18n } from "@homarr/translation/server";
|
||||
import type { TablerIcon } from "@homarr/ui";
|
||||
@@ -63,6 +65,7 @@ const getBoardAndPermissionsAsync = async (params: Props["params"]) => {
|
||||
|
||||
export default async function BoardSettingsPage({ params, searchParams }: Props) {
|
||||
const { board, permissions } = await getBoardAndPermissionsAsync(params);
|
||||
const boardSettings = await getServerSettingByKeyAsync(db, "board");
|
||||
const { hasFullAccess } = await getBoardPermissionsAsync(board);
|
||||
const t = await getScopedI18n("board.setting");
|
||||
|
||||
@@ -92,7 +95,7 @@ export default async function BoardSettingsPage({ params, searchParams }: Props)
|
||||
<BoardAccessSettings board={board} initialPermissions={permissions} />
|
||||
</AccordionItemFor>
|
||||
<AccordionItemFor value="dangerZone" icon={IconAlertTriangle} danger noPadding>
|
||||
<DangerZoneSettingsContent />
|
||||
<DangerZoneSettingsContent hideVisibility={boardSettings.defaultBoardId === board.id} />
|
||||
</AccordionItemFor>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { logger } from "@homarr/log";
|
||||
import { MainHeader } from "~/components/layout/header";
|
||||
import { BoardLogoWithTitle } from "~/components/layout/logo/board-logo";
|
||||
import { ClientShell } from "~/components/layout/shell";
|
||||
import { getCurrentColorSchemeAsync } from "~/theme/color-scheme";
|
||||
import type { Board } from "./_types";
|
||||
import { BoardProvider } from "./(content)/_context";
|
||||
import type { Params } from "./(content)/_creator";
|
||||
@@ -37,10 +38,11 @@ export const createBoardLayout = <TParams extends Params>({
|
||||
|
||||
throw error;
|
||||
});
|
||||
const colorScheme = await getCurrentColorSchemeAsync();
|
||||
|
||||
return (
|
||||
<BoardProvider initialBoard={initialBoard}>
|
||||
<BoardMantineProvider>
|
||||
<BoardMantineProvider defaultColorScheme={colorScheme}>
|
||||
<CustomCss />
|
||||
<ClientShell hasNavigation={false}>
|
||||
<MainHeader
|
||||
|
||||
@@ -6,7 +6,6 @@ import "@homarr/spotlight/styles.css";
|
||||
import "@homarr/ui/styles.css";
|
||||
import "~/styles/scroll-area.scss";
|
||||
|
||||
import { cookies } from "next/headers";
|
||||
import { notFound } from "next/navigation";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
|
||||
@@ -14,11 +13,13 @@ import { env } from "@homarr/auth/env.mjs";
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import { ModalProvider } from "@homarr/modals";
|
||||
import { Notifications } from "@homarr/notifications";
|
||||
import { isLocaleSupported } from "@homarr/translation";
|
||||
import { getI18nMessages, getScopedI18n } from "@homarr/translation/server";
|
||||
import { isLocaleRTL, isLocaleSupported } from "@homarr/translation";
|
||||
import { getI18nMessages } from "@homarr/translation/server";
|
||||
|
||||
import { Analytics } from "~/components/layout/analytics";
|
||||
import { SearchEngineOptimization } from "~/components/layout/search-engine-optimization";
|
||||
import { getCurrentColorSchemeAsync } from "~/theme/color-scheme";
|
||||
import { DayJsLoader } from "./_client-providers/dayjs-loader";
|
||||
import { JotaiProvider } from "./_client-providers/jotai";
|
||||
import { CustomMantineProvider } from "./_client-providers/mantine";
|
||||
import { AuthProvider } from "./_client-providers/session";
|
||||
@@ -30,7 +31,8 @@ const fontSans = Inter({
|
||||
variable: "--font-sans",
|
||||
});
|
||||
|
||||
export const generateMetadata = (): Metadata => ({
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export const generateMetadata = async (): Promise<Metadata> => ({
|
||||
title: "Homarr",
|
||||
description:
|
||||
"Simplify the management of your server with Homarr - a sleek, modern dashboard that puts all of your apps and services at your fingertips.",
|
||||
@@ -49,7 +51,7 @@ export const generateMetadata = (): Metadata => ({
|
||||
title: "Homarr",
|
||||
capable: true,
|
||||
startupImage: { url: "/logo/logo.png" },
|
||||
statusBarStyle: getColorScheme() === "dark" ? "black-translucent" : "default",
|
||||
statusBarStyle: (await getCurrentColorSchemeAsync()) === "dark" ? "black-translucent" : "default",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -66,9 +68,8 @@ export default async function Layout(props: { children: React.ReactNode; params:
|
||||
}
|
||||
|
||||
const session = await auth();
|
||||
const colorScheme = getColorScheme();
|
||||
const tCommon = await getScopedI18n("common");
|
||||
const direction = tCommon("direction");
|
||||
const colorScheme = await getCurrentColorSchemeAsync();
|
||||
const direction = isLocaleRTL(props.params.locale) ? "rtl" : "ltr";
|
||||
const i18nMessages = await getI18nMessages();
|
||||
|
||||
const StackedProvider = composeWrappers([
|
||||
@@ -77,8 +78,9 @@ export default async function Layout(props: { children: React.ReactNode; params:
|
||||
},
|
||||
(innerProps) => <JotaiProvider {...innerProps} />,
|
||||
(innerProps) => <TRPCReactProvider {...innerProps} />,
|
||||
(innerProps) => <DayJsLoader {...innerProps} />,
|
||||
(innerProps) => <NextIntlClientProvider {...innerProps} messages={i18nMessages} />,
|
||||
(innerProps) => <CustomMantineProvider {...innerProps} />,
|
||||
(innerProps) => <CustomMantineProvider {...innerProps} defaultColorScheme={colorScheme} />,
|
||||
(innerProps) => <ModalProvider {...innerProps} />,
|
||||
]);
|
||||
|
||||
@@ -106,7 +108,3 @@ export default async function Layout(props: { children: React.ReactNode; params:
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
const getColorScheme = () => {
|
||||
return cookies().get("homarr-color-scheme")?.value ?? "dark";
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { ActionIcon, Avatar, Button, Card, Collapse, Group, Kbd, Stack, Text } from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { IconEye, IconEyeOff } from "@tabler/icons-react";
|
||||
@@ -23,7 +22,6 @@ interface SecretCardProps {
|
||||
}
|
||||
|
||||
export const SecretCard = ({ secret, children, onCancel }: SecretCardProps) => {
|
||||
const params = useParams<{ locale: string }>();
|
||||
const t = useI18n();
|
||||
const { isPublic } = integrationSecretKindObject[secret.kind];
|
||||
const [publicSecretDisplayOpened, { toggle: togglePublicSecretDisplay }] = useDisclosure(false);
|
||||
@@ -45,7 +43,7 @@ export const SecretCard = ({ secret, children, onCancel }: SecretCardProps) => {
|
||||
<Group>
|
||||
<Text c="gray.6" size="sm">
|
||||
{t("integration.secrets.lastUpdated", {
|
||||
date: dayjs().locale(params.locale).to(dayjs(secret.updatedAt)),
|
||||
date: dayjs().to(dayjs(secret.updatedAt)),
|
||||
})}
|
||||
</Text>
|
||||
{isPublic ? (
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
"use client";
|
||||
|
||||
import { Group, Text } from "@mantine/core";
|
||||
import { IconMoon, IconSun } from "@tabler/icons-react";
|
||||
|
||||
import type { ColorScheme } from "@homarr/definitions";
|
||||
import { colorSchemes } from "@homarr/definitions";
|
||||
import type { ServerSettings } from "@homarr/server-settings";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { SelectWithCustomItems } from "@homarr/ui";
|
||||
|
||||
import { CommonSettingsForm } from "./common-form";
|
||||
|
||||
export const AppearanceSettingsForm = ({ defaultValues }: { defaultValues: ServerSettings["appearance"] }) => {
|
||||
const tApperance = useScopedI18n("management.page.settings.section.appearance");
|
||||
|
||||
return (
|
||||
<CommonSettingsForm settingKey="appearance" defaultValues={defaultValues}>
|
||||
{(form) => (
|
||||
<>
|
||||
<SelectWithCustomItems
|
||||
label={tApperance("defaultColorScheme.label")}
|
||||
data={colorSchemes.map((scheme) => ({
|
||||
value: scheme,
|
||||
label: tApperance(`defaultColorScheme.options.${scheme}`),
|
||||
}))}
|
||||
{...form.getInputProps("defaultColorScheme")}
|
||||
SelectOption={ApperanceCustomOption}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</CommonSettingsForm>
|
||||
);
|
||||
};
|
||||
|
||||
const appearanceIcons = {
|
||||
light: IconSun,
|
||||
dark: IconMoon,
|
||||
};
|
||||
|
||||
const ApperanceCustomOption = ({ value, label }: { value: ColorScheme; label: string }) => {
|
||||
const Icon = appearanceIcons[value];
|
||||
|
||||
return (
|
||||
<Group>
|
||||
<Icon size={16} stroke={1.5} />
|
||||
<Text fz="sm" fw={500}>
|
||||
{label}
|
||||
</Text>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
"use client";
|
||||
|
||||
import { Group, Text } from "@mantine/core";
|
||||
import { IconLayoutDashboard } from "@tabler/icons-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import type { ServerSettings } from "@homarr/server-settings";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { SelectWithCustomItems } from "@homarr/ui";
|
||||
|
||||
import { CommonSettingsForm } from "./common-form";
|
||||
|
||||
export const BoardSettingsForm = ({ defaultValues }: { defaultValues: ServerSettings["board"] }) => {
|
||||
const tBoard = useScopedI18n("management.page.settings.section.board");
|
||||
const [selectableBoards] = clientApi.board.getPublicBoards.useSuspenseQuery();
|
||||
|
||||
return (
|
||||
<CommonSettingsForm settingKey="board" defaultValues={defaultValues}>
|
||||
{(form) => (
|
||||
<>
|
||||
<SelectWithCustomItems
|
||||
label={tBoard("defaultBoard.label")}
|
||||
description={tBoard("defaultBoard.description")}
|
||||
data={selectableBoards.map((board) => ({
|
||||
value: board.id,
|
||||
label: board.name,
|
||||
image: board.logoImageUrl,
|
||||
}))}
|
||||
SelectOption={({ label, image }: { value: string; label: string; image: string | null }) => (
|
||||
<Group>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
{image ? <img width={16} height={16} src={image} alt={label} /> : <IconLayoutDashboard size={16} />}
|
||||
<Text fz="sm" fw={500}>
|
||||
{label}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
{...form.getInputProps("defaultBoardId")}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</CommonSettingsForm>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { Button, Group, Stack } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useForm } from "@homarr/form";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import type { ServerSettings } from "@homarr/server-settings";
|
||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
export const CommonSettingsForm = <TKey extends keyof ServerSettings>({
|
||||
settingKey,
|
||||
defaultValues,
|
||||
children,
|
||||
}: {
|
||||
settingKey: TKey;
|
||||
defaultValues: ServerSettings[TKey];
|
||||
children: (form: ReturnType<typeof useForm<ServerSettings[TKey]>>) => React.ReactNode;
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const tSettings = useScopedI18n("management.page.settings");
|
||||
const { mutateAsync, isPending } = clientApi.serverSettings.saveSettings.useMutation({
|
||||
onSuccess() {
|
||||
showSuccessNotification({
|
||||
message: tSettings("notification.success.message"),
|
||||
});
|
||||
},
|
||||
onError() {
|
||||
showErrorNotification({
|
||||
message: tSettings("notification.error.message"),
|
||||
});
|
||||
},
|
||||
});
|
||||
const form = useForm({
|
||||
initialValues: defaultValues,
|
||||
});
|
||||
|
||||
const handleSubmitAsync = async (values: ServerSettings[TKey]) => {
|
||||
await mutateAsync({
|
||||
settingsKey: settingKey,
|
||||
value: values,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit((values) => void handleSubmitAsync(values))}>
|
||||
<Stack gap="sm">
|
||||
{children(form)}
|
||||
<Group justify="end">
|
||||
<Button type="submit" loading={isPending}>
|
||||
{t("common.action.save")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import type { ServerSettings } from "@homarr/server-settings";
|
||||
import type { SupportedLanguage } from "@homarr/translation";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import { LanguageCombobox } from "~/components/language/language-combobox";
|
||||
import { CommonSettingsForm } from "./common-form";
|
||||
|
||||
export const CultureSettingsForm = ({ defaultValues }: { defaultValues: ServerSettings["culture"] }) => {
|
||||
const tCulture = useScopedI18n("management.page.settings.section.culture");
|
||||
|
||||
return (
|
||||
<CommonSettingsForm settingKey="culture" defaultValues={defaultValues}>
|
||||
{(form) => (
|
||||
<>
|
||||
<LanguageCombobox
|
||||
label={tCulture("defaultLocale.label")}
|
||||
value={form.getInputProps("defaultLocale").value as SupportedLanguage}
|
||||
{...form.getInputProps("defaultLocale")}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</CommonSettingsForm>
|
||||
);
|
||||
};
|
||||
@@ -6,6 +6,9 @@ import { getScopedI18n } from "@homarr/translation/server";
|
||||
import { CrawlingAndIndexingSettings } from "~/app/[locale]/manage/settings/_components/crawling-and-indexing.settings";
|
||||
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||
import { AnalyticsSettings } from "./_components/analytics.settings";
|
||||
import { AppearanceSettingsForm } from "./_components/appearance-settings-form";
|
||||
import { BoardSettingsForm } from "./_components/board-settings-form";
|
||||
import { CultureSettingsForm } from "./_components/culture-settings-form";
|
||||
|
||||
export async function generateMetadata() {
|
||||
const t = await getScopedI18n("management");
|
||||
@@ -18,14 +21,26 @@ export async function generateMetadata() {
|
||||
|
||||
export default async function SettingsPage() {
|
||||
const serverSettings = await api.serverSettings.getAll();
|
||||
const t = await getScopedI18n("management.page.settings");
|
||||
const tSettings = await getScopedI18n("management.page.settings");
|
||||
return (
|
||||
<>
|
||||
<DynamicBreadcrumb />
|
||||
<Stack>
|
||||
<Title order={1}>{t("title")}</Title>
|
||||
<Title order={1}>{tSettings("title")}</Title>
|
||||
<AnalyticsSettings initialData={serverSettings.analytics} />
|
||||
<CrawlingAndIndexingSettings initialData={serverSettings.crawlingAndIndexing} />
|
||||
<Stack>
|
||||
<Title order={2}>{tSettings("section.board.title")}</Title>
|
||||
<BoardSettingsForm defaultValues={serverSettings.board} />
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Title order={2}>{tSettings("section.appearance.title")}</Title>
|
||||
<AppearanceSettingsForm defaultValues={serverSettings.appearance} />
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Title order={2}>{tSettings("section.culture.title")}</Title>
|
||||
<CultureSettingsForm defaultValues={serverSettings.culture} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { api } from "@homarr/api/server";
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import { getI18n, getScopedI18n } from "@homarr/translation/server";
|
||||
|
||||
import { LanguageCombobox } from "~/components/language/language-combobox";
|
||||
import { CurrentLanguageCombobox } from "~/components/language/current-language-combobox";
|
||||
import { DangerZoneItem, DangerZoneRoot } from "~/components/manage/danger-zone";
|
||||
import { catchTrpcNotFound } from "~/errors/trpc-not-found";
|
||||
import { createMetaTitle } from "~/metadata";
|
||||
@@ -81,7 +81,7 @@ export default async function EditUserPage({ params }: Props) {
|
||||
|
||||
<Stack mb="lg">
|
||||
<Title order={2}>{tGeneral("item.language")}</Title>
|
||||
<LanguageCombobox />
|
||||
<CurrentLanguageCombobox />
|
||||
</Stack>
|
||||
|
||||
<Stack mb="lg">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Button } from "@mantine/core";
|
||||
import { Button, useMatches } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
@@ -61,8 +61,14 @@ export const DeleteGroup = ({ group }: DeleteGroupProps) => {
|
||||
});
|
||||
}, [tDelete, tRoot, openConfirmModal, group.id, group.name, mutateAsync, router]);
|
||||
|
||||
const fullWidth = useMatches({
|
||||
xs: true,
|
||||
sm: true,
|
||||
md: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<Button variant="subtle" color="red" onClick={handleDeletion}>
|
||||
<Button variant="subtle" color="red" onClick={handleDeletion} fullWidth={fullWidth}>
|
||||
{tDelete("label")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
import { Button } from "@mantine/core";
|
||||
import { Button, useMatches } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useConfirmModal, useModalAction } from "@homarr/modals";
|
||||
@@ -74,8 +74,14 @@ export const TransferGroupOwnership = ({ group }: TransferGroupOwnershipProps) =
|
||||
);
|
||||
}, [group.id, group.name, innerOwnerId, mutateAsync, openConfirmModal, openModal, tRoot, tTransfer]);
|
||||
|
||||
const fullWidth = useMatches({
|
||||
xs: true,
|
||||
sm: true,
|
||||
md: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<Button variant="subtle" color="red" onClick={handleTransfer}>
|
||||
<Button variant="subtle" color="red" onClick={handleTransfer} fullWidth={fullWidth}>
|
||||
{tTransfer("label")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Stack, Title } from "@mantine/core";
|
||||
import { Card, Group, Stack, Text, Title } from "@mantine/core";
|
||||
|
||||
import { api } from "@homarr/api/server";
|
||||
import { everyoneGroup } from "@homarr/definitions";
|
||||
import { getScopedI18n } from "@homarr/translation/server";
|
||||
import { UserAvatar } from "@homarr/ui";
|
||||
|
||||
import { DangerZoneItem, DangerZoneRoot } from "~/components/manage/danger-zone";
|
||||
import { DeleteGroup } from "./_delete-group";
|
||||
@@ -30,6 +31,30 @@ export default async function GroupsDetailPage({ params }: GroupsDetailPageProps
|
||||
|
||||
<RenameGroupForm group={group} disabled={isReserved} />
|
||||
|
||||
<Title order={2}>{tGeneral("owner")}</Title>
|
||||
<Card>
|
||||
{group.owner ? (
|
||||
<Group>
|
||||
<UserAvatar user={{ name: group.owner.name, image: group.owner.image }} size={"lg"} />
|
||||
<Stack align={"start"} gap={3}>
|
||||
<Text fw={"bold"}>{group.owner.name}</Text>
|
||||
<Text>{group.owner.email}</Text>
|
||||
<Text c={"dimmed"} size={"sm"}>
|
||||
{tGeneral("ownerOfGroup")}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
) : (
|
||||
<Group>
|
||||
<Stack align={"start"} gap={3}>
|
||||
<Text c={"dimmed"} size={"sm"}>
|
||||
{tGeneral("ownerOfGroupDeleted")}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{!isReserved && (
|
||||
<DangerZoneRoot>
|
||||
<DangerZoneItem
|
||||
|
||||
@@ -5,10 +5,23 @@ import type { SupportedAuthProvider } from "@homarr/definitions";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
export const GET = async (req: NextRequest) => {
|
||||
return await createHandlers(extractProvider(req)).handlers.GET(reqWithTrustedOrigin(req));
|
||||
return await createHandlers(extractProvider(req), isSecureCookieEnabled(req)).handlers.GET(reqWithTrustedOrigin(req));
|
||||
};
|
||||
export const POST = async (req: NextRequest) => {
|
||||
return await createHandlers(extractProvider(req)).handlers.POST(reqWithTrustedOrigin(req));
|
||||
return await createHandlers(extractProvider(req), isSecureCookieEnabled(req)).handlers.POST(
|
||||
reqWithTrustedOrigin(req),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* wheter to use secure cookies or not, is only supported for https.
|
||||
* For http it will not add the cookie as it is not considered secure.
|
||||
* @param req request containing the url
|
||||
* @returns true if the request is https, false otherwise
|
||||
*/
|
||||
const isSecureCookieEnabled = (req: NextRequest): boolean => {
|
||||
const url = new URL(req.url);
|
||||
return url.protocol === "https:";
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useChangeLocale, useCurrentLocale } from "@homarr/translation/client";
|
||||
|
||||
import { LanguageCombobox } from "./language-combobox";
|
||||
|
||||
export const CurrentLanguageCombobox = () => {
|
||||
const currentLocale = useCurrentLocale();
|
||||
const { changeLocale, isPending } = useChangeLocale();
|
||||
|
||||
return <LanguageCombobox value={currentLocale} onChange={changeLocale} isPending={isPending} />;
|
||||
};
|
||||
@@ -1,31 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Combobox, Group, InputBase, Loader, Text, useCombobox } from "@mantine/core";
|
||||
import { Combobox, Group, InputBase, Loader, ScrollArea, Text, useCombobox } from "@mantine/core";
|
||||
import { IconCheck } from "@tabler/icons-react";
|
||||
|
||||
import type { SupportedLanguage } from "@homarr/translation";
|
||||
import { localeConfigurations, supportedLanguages } from "@homarr/translation";
|
||||
import { useChangeLocale, useCurrentLocale } from "@homarr/translation/client";
|
||||
|
||||
import classes from "./language-combobox.module.css";
|
||||
|
||||
export const LanguageCombobox = () => {
|
||||
interface LanguageComboboxProps {
|
||||
label?: string;
|
||||
value: SupportedLanguage;
|
||||
onChange: (value: SupportedLanguage) => void;
|
||||
isPending?: boolean;
|
||||
}
|
||||
|
||||
export const LanguageCombobox = ({ label, value, onChange, isPending }: LanguageComboboxProps) => {
|
||||
const combobox = useCombobox({
|
||||
onDropdownClose: () => combobox.resetSelectedOption(),
|
||||
});
|
||||
const currentLocale = useCurrentLocale();
|
||||
const { changeLocale, isPending } = useChangeLocale();
|
||||
|
||||
const handleOnOptionSubmit = React.useCallback(
|
||||
(value: string) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
changeLocale(value as SupportedLanguage);
|
||||
onChange(value as SupportedLanguage);
|
||||
combobox.closeDropdown();
|
||||
},
|
||||
[changeLocale, combobox],
|
||||
[onChange, combobox],
|
||||
);
|
||||
|
||||
const handleOnClick = React.useCallback(() => {
|
||||
@@ -39,23 +43,26 @@ export const LanguageCombobox = () => {
|
||||
component="button"
|
||||
type="button"
|
||||
pointer
|
||||
label={label}
|
||||
leftSection={isPending ? <Loader size={16} /> : null}
|
||||
rightSection={<Combobox.Chevron />}
|
||||
rightSectionPointerEvents="none"
|
||||
onClick={handleOnClick}
|
||||
variant="filled"
|
||||
>
|
||||
<OptionItem currentLocale={currentLocale} localeKey={currentLocale} />
|
||||
<OptionItem currentLocale={value} localeKey={value} />
|
||||
</InputBase>
|
||||
</Combobox.Target>
|
||||
<Combobox.Dropdown>
|
||||
<Combobox.Options>
|
||||
{supportedLanguages.map((languageKey) => (
|
||||
<Combobox.Option value={languageKey} key={languageKey}>
|
||||
<OptionItem currentLocale={currentLocale} localeKey={languageKey} showCheck />
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
<ScrollArea h={300}>
|
||||
<Combobox.Options>
|
||||
{supportedLanguages.map((languageKey) => (
|
||||
<Combobox.Option value={languageKey} key={languageKey}>
|
||||
<OptionItem currentLocale={value} localeKey={languageKey} showCheck />
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
</ScrollArea>
|
||||
</Combobox.Dropdown>
|
||||
</Combobox>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import Script from "next/script";
|
||||
|
||||
import { UMAMI_WEBSITE_ID } from "@homarr/analytics";
|
||||
import { api } from "@homarr/api/server";
|
||||
import { db } from "@homarr/db";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
|
||||
export const Analytics = async () => {
|
||||
// For static pages it will not find any analytics data so we do not include the script on them
|
||||
const analytics = await api.serverSettings.getAnalytics().catch(() => null);
|
||||
const analytics = await getServerSettingByKeyAsync(db, "analytics").catch(() => null);
|
||||
|
||||
if (analytics?.enableGeneral) {
|
||||
return <Script src="https://umami.homarr.dev/script.js" data-website-id={UMAMI_WEBSITE_ID} defer />;
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import { db, eq } from "@homarr/db";
|
||||
import { serverSettings } from "@homarr/db/schema/sqlite";
|
||||
import type { defaultServerSettings } from "@homarr/server-settings";
|
||||
import { db } from "@homarr/db";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
|
||||
export const SearchEngineOptimization = async () => {
|
||||
const crawlingAndIndexingSetting = await db.query.serverSettings.findFirst({
|
||||
where: eq(serverSettings.settingKey, "crawlingAndIndexing"),
|
||||
});
|
||||
const crawlingAndIndexingSetting = await getServerSettingByKeyAsync(db, "crawlingAndIndexing");
|
||||
|
||||
if (!crawlingAndIndexingSetting) {
|
||||
return null;
|
||||
const robotsAttributes: string[] = [];
|
||||
|
||||
if (crawlingAndIndexingSetting.noIndex) {
|
||||
robotsAttributes.push("noindex");
|
||||
}
|
||||
|
||||
const value = SuperJSON.parse<(typeof defaultServerSettings)["crawlingAndIndexing"]>(
|
||||
crawlingAndIndexingSetting.value,
|
||||
);
|
||||
if (crawlingAndIndexingSetting.noFollow) {
|
||||
robotsAttributes.push("nofollow");
|
||||
}
|
||||
|
||||
const robotsAttributes = [...(value.noIndex ? ["noindex"] : []), ...(value.noIndex ? ["nofollow"] : [])];
|
||||
const googleAttributes: string[] = [];
|
||||
|
||||
const googleAttributes = [
|
||||
...(value.noSiteLinksSearchBox ? ["nositelinkssearchbox"] : []),
|
||||
...(value.noTranslate ? ["notranslate"] : []),
|
||||
];
|
||||
if (crawlingAndIndexingSetting.noSiteLinksSearchBox) {
|
||||
googleAttributes.push("nositelinkssearchbox");
|
||||
}
|
||||
|
||||
if (crawlingAndIndexingSetting.noTranslate) {
|
||||
googleAttributes.push("notranslate");
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -15,7 +15,7 @@ export const DangerZoneRoot = async ({ children }: DangerZoneRootProps) => {
|
||||
<Title c="red.8" order={2}>
|
||||
{t("common.dangerZone")}
|
||||
</Title>
|
||||
<Card withBorder style={{ borderColor: "var(--mantine-color-red-8)" }}>
|
||||
<Card withBorder style={{ borderColor: "var(--mantine-color-red-8)", borderWidth: 3 }}>
|
||||
<Stack gap="sm">
|
||||
{Array.isArray(children)
|
||||
? children.map((child, index) => (
|
||||
@@ -43,14 +43,14 @@ interface DangerZoneItemProps {
|
||||
|
||||
export const DangerZoneItem = ({ label, description, action }: DangerZoneItemProps) => {
|
||||
return (
|
||||
<Group justify="space-between" px="md">
|
||||
<Group justify="space-between" px="md" w={"100%"}>
|
||||
<Stack gap={0}>
|
||||
<Text fw="bold" size="sm">
|
||||
{label}
|
||||
</Text>
|
||||
<Text size="sm">{description}</Text>
|
||||
</Stack>
|
||||
<Group justify="end" w={{ base: "100%", xs: "auto" }}>
|
||||
<Group justify="end" w={{ xs: "100%", sm: "100%", md: "auto" }}>
|
||||
{action}
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
@@ -24,7 +24,7 @@ import { useScopedI18n } from "@homarr/translation/client";
|
||||
import "flag-icons/css/flag-icons.min.css";
|
||||
|
||||
import { useAuthContext } from "~/app/[locale]/_client-providers/session";
|
||||
import { LanguageCombobox } from "./language/language-combobox";
|
||||
import { CurrentLanguageCombobox } from "./language/current-language-combobox";
|
||||
|
||||
interface UserAvatarMenuProps {
|
||||
children: ReactNode;
|
||||
@@ -72,7 +72,7 @@ export const UserAvatarMenu = ({ children }: UserAvatarMenuProps) => {
|
||||
<Menu.Divider />
|
||||
|
||||
<Menu.Item p={0} closeMenuOnClick={false}>
|
||||
<LanguageCombobox />
|
||||
<CurrentLanguageCombobox />
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
{Boolean(session.data) && (
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
import { I18nMiddleware } from "@homarr/translation/middleware";
|
||||
import { fetchApi } from "@homarr/api/client";
|
||||
import { createI18nMiddleware } from "@homarr/translation/middleware";
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
return I18nMiddleware(request);
|
||||
export async function middleware(request: NextRequest) {
|
||||
const culture = await fetchApi.serverSettings.getCulture.query();
|
||||
|
||||
// We don't want to fallback to accept-language header so we clear it
|
||||
request.headers.set("accept-language", "");
|
||||
const next = createI18nMiddleware(culture.defaultLocale);
|
||||
return next(request);
|
||||
}
|
||||
|
||||
export const config = {
|
||||
|
||||
18
apps/nextjs/src/theme/color-scheme.ts
Normal file
18
apps/nextjs/src/theme/color-scheme.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { cache } from "react";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
import { db } from "@homarr/db";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import type { ColorScheme } from "@homarr/definitions";
|
||||
import { colorSchemeCookieKey } from "@homarr/definitions";
|
||||
|
||||
export const getCurrentColorSchemeAsync = cache(async () => {
|
||||
const cookieValue = cookies().get(colorSchemeCookieKey)?.value;
|
||||
|
||||
if (cookieValue) {
|
||||
return cookieValue as ColorScheme;
|
||||
}
|
||||
|
||||
const appearanceSettings = await getServerSettingByKeyAsync(db, "appearance");
|
||||
return appearanceSettings.defaultColorScheme;
|
||||
});
|
||||
@@ -44,9 +44,9 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "^22.8.6",
|
||||
"@types/node": "^22.8.7",
|
||||
"dotenv-cli": "^7.4.2",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"prettier": "^3.3.3",
|
||||
"tsx": "4.19.2",
|
||||
"typescript": "^5.6.3"
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/ws": "^8.5.12",
|
||||
"eslint": "^9.13.0",
|
||||
"@types/ws": "^8.5.13",
|
||||
"eslint": "^9.14.0",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"testcontainers": "^10.13.2",
|
||||
"turbo": "^2.2.3",
|
||||
"typescript": "^5.6.3",
|
||||
"vite-tsconfig-paths": "^5.0.1",
|
||||
"vite-tsconfig-paths": "^5.1.0",
|
||||
"vitest": "^2.1.4"
|
||||
},
|
||||
"packageManager": "pnpm@9.12.3",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { UmamiEventData } from "@umami/node";
|
||||
import { Umami } from "@umami/node";
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import { count, db, eq } from "@homarr/db";
|
||||
import { integrations, items, serverSettings, users } from "@homarr/db/schema/sqlite";
|
||||
import { count, db } from "@homarr/db";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import { integrations, items, users } from "@homarr/db/schema/sqlite";
|
||||
import { logger } from "@homarr/log";
|
||||
import type { defaultServerSettings } from "@homarr/server-settings";
|
||||
|
||||
@@ -12,18 +12,7 @@ import { UMAMI_HOST_URL, UMAMI_WEBSITE_ID } from "./constants";
|
||||
|
||||
export const sendServerAnalyticsAsync = async () => {
|
||||
const stopWatch = new Stopwatch();
|
||||
const setting = await db.query.serverSettings.findFirst({
|
||||
where: eq(serverSettings.settingKey, "analytics"),
|
||||
});
|
||||
|
||||
if (!setting) {
|
||||
logger.info(
|
||||
"Server does not know the configured state of analytics. No data will be sent. Enable analytics in the settings",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const analyticsSettings = SuperJSON.parse<typeof defaultServerSettings.analytics>(setting.value);
|
||||
const analyticsSettings = await getServerSettingByKeyAsync(db, "analytics");
|
||||
|
||||
if (!analyticsSettings.enableGeneral) {
|
||||
logger.info("Analytics are disabled. No data will be sent. Enable analytics in the settings");
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/dockerode": "^3.3.31",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { asc, createId, eq, like } from "@homarr/db";
|
||||
import { asc, createId, eq, inArray, like } from "@homarr/db";
|
||||
import { apps } from "@homarr/db/schema/sqlite";
|
||||
import { validation, z } from "@homarr/validation";
|
||||
|
||||
@@ -55,6 +55,8 @@ export const appRouter = createTRPCRouter({
|
||||
name: z.string(),
|
||||
id: z.string(),
|
||||
iconUrl: z.string(),
|
||||
description: z.string().nullable(),
|
||||
href: z.string().nullable(),
|
||||
}),
|
||||
),
|
||||
)
|
||||
@@ -72,6 +74,8 @@ export const appRouter = createTRPCRouter({
|
||||
id: true,
|
||||
name: true,
|
||||
iconUrl: true,
|
||||
description: true,
|
||||
href: true,
|
||||
},
|
||||
orderBy: asc(apps.name),
|
||||
});
|
||||
@@ -102,6 +106,11 @@ export const appRouter = createTRPCRouter({
|
||||
|
||||
return app;
|
||||
}),
|
||||
byIds: publicProcedure.input(z.array(z.string())).query(async ({ ctx, input }) => {
|
||||
return await ctx.db.query.apps.findMany({
|
||||
where: inArray(apps.id, input),
|
||||
});
|
||||
}),
|
||||
create: protectedProcedure
|
||||
.input(validation.app.manage)
|
||||
.output(z.void())
|
||||
|
||||
@@ -4,6 +4,7 @@ import superjson from "superjson";
|
||||
import { constructBoardPermissions } from "@homarr/auth/shared";
|
||||
import type { Database, SQL } from "@homarr/db";
|
||||
import { and, createId, eq, inArray, like, or } from "@homarr/db";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import {
|
||||
boardGroupPermissions,
|
||||
boards,
|
||||
@@ -41,6 +42,16 @@ export const boardRouter = createTRPCRouter({
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
getPublicBoards: publicProcedure.query(async ({ ctx }) => {
|
||||
return await ctx.db.query.boards.findMany({
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
logoImageUrl: true,
|
||||
},
|
||||
where: eq(boards.isPublic, true),
|
||||
});
|
||||
}),
|
||||
getAllBoards: publicProcedure.query(async ({ ctx }) => {
|
||||
const userId = ctx.session?.user.id;
|
||||
const permissionsOfCurrentUserWhenPresent = await ctx.db.query.boardUserPermissions.findMany({
|
||||
@@ -216,6 +227,14 @@ export const boardRouter = createTRPCRouter({
|
||||
.input(validation.board.changeVisibility)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
|
||||
const boardSettings = await getServerSettingByKeyAsync(ctx.db, "board");
|
||||
|
||||
if (input.visibility !== "public" && boardSettings.defaultBoardId === input.id) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Cannot make default board private",
|
||||
});
|
||||
}
|
||||
|
||||
await ctx.db
|
||||
.update(boards)
|
||||
@@ -240,7 +259,22 @@ export const boardRouter = createTRPCRouter({
|
||||
})
|
||||
: null;
|
||||
|
||||
const boardWhere = user?.homeBoardId ? eq(boards.id, user.homeBoardId) : eq(boards.name, "home");
|
||||
// 1. user home board, 2. default board, 3. not found
|
||||
let boardWhere: SQL<unknown> | null = null;
|
||||
if (user?.homeBoardId) {
|
||||
boardWhere = eq(boards.id, user.homeBoardId);
|
||||
} else {
|
||||
const boardSettings = await getServerSettingByKeyAsync(ctx.db, "board");
|
||||
boardWhere = boardSettings.defaultBoardId ? eq(boards.id, boardSettings.defaultBoardId) : null;
|
||||
}
|
||||
|
||||
if (!boardWhere) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "No home board found",
|
||||
});
|
||||
}
|
||||
|
||||
await throwIfActionForbiddenAsync(ctx, boardWhere, "view");
|
||||
|
||||
return await getFullBoardWithWhereAsync(ctx.db, boardWhere, ctx.session?.user.id ?? null);
|
||||
|
||||
@@ -75,6 +75,14 @@ export const groupRouter = createTRPCRouter({
|
||||
permission: true,
|
||||
},
|
||||
},
|
||||
owner: {
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
image: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,47 +1,16 @@
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import { eq } from "@homarr/db";
|
||||
import { serverSettings } from "@homarr/db/schema/sqlite";
|
||||
import { logger } from "@homarr/log";
|
||||
import type { defaultServerSettings, ServerSettings } from "@homarr/server-settings";
|
||||
import { getServerSettingByKeyAsync, getServerSettingsAsync, updateServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import type { ServerSettings } from "@homarr/server-settings";
|
||||
import { defaultServerSettingsKeys } from "@homarr/server-settings";
|
||||
import { z } from "@homarr/validation";
|
||||
|
||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
|
||||
|
||||
export const serverSettingsRouter = createTRPCRouter({
|
||||
// this must be public so anonymous users also get analytics
|
||||
getAnalytics: publicProcedure.query(async ({ ctx }) => {
|
||||
const setting = await ctx.db.query.serverSettings.findFirst({
|
||||
where: eq(serverSettings.settingKey, "analytics"),
|
||||
});
|
||||
|
||||
if (!setting) {
|
||||
logger.info(
|
||||
"Server settings for analytics is currently undefined. Using default values instead. If this persists, there may be an issue with the server settings",
|
||||
);
|
||||
return {
|
||||
enableGeneral: true,
|
||||
enableIntegrationData: false,
|
||||
enableUserData: false,
|
||||
enableWidgetData: false,
|
||||
} as (typeof defaultServerSettings)["analytics"];
|
||||
}
|
||||
|
||||
return SuperJSON.parse<(typeof defaultServerSettings)["analytics"]>(setting.value);
|
||||
getCulture: publicProcedure.query(async ({ ctx }) => {
|
||||
return await getServerSettingByKeyAsync(ctx.db, "culture");
|
||||
}),
|
||||
getAll: protectedProcedure.query(async ({ ctx }) => {
|
||||
const settings = await ctx.db.query.serverSettings.findMany();
|
||||
|
||||
const data = {} as ServerSettings;
|
||||
defaultServerSettingsKeys.forEach((key) => {
|
||||
const settingValue = settings.find((setting) => setting.settingKey === key)?.value;
|
||||
if (!settingValue) {
|
||||
return;
|
||||
}
|
||||
data[key] = SuperJSON.parse(settingValue);
|
||||
});
|
||||
return data;
|
||||
return await getServerSettingsAsync(ctx.db);
|
||||
}),
|
||||
saveSettings: protectedProcedure
|
||||
.input(
|
||||
@@ -51,12 +20,10 @@ export const serverSettingsRouter = createTRPCRouter({
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const databaseRunResult = await ctx.db
|
||||
.update(serverSettings)
|
||||
.set({
|
||||
value: SuperJSON.stringify(input.value),
|
||||
})
|
||||
.where(eq(serverSettings.settingKey, input.settingsKey));
|
||||
return databaseRunResult.changes === 1;
|
||||
await updateServerSettingByKeyAsync(
|
||||
ctx.db,
|
||||
input.settingsKey,
|
||||
input.value as ServerSettings[keyof ServerSettings],
|
||||
);
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
integrations,
|
||||
items,
|
||||
sections,
|
||||
serverSettings,
|
||||
users,
|
||||
} from "@homarr/db/schema/sqlite";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
@@ -473,13 +474,19 @@ describe("deleteBoard should delete board", () => {
|
||||
});
|
||||
|
||||
describe("getHomeBoard should return home board", () => {
|
||||
it("should return home board", async () => {
|
||||
test("should return user home board when user has one", async () => {
|
||||
// Arrange
|
||||
const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");
|
||||
const db = createDb();
|
||||
const caller = boardRouter.createCaller({ db, session: defaultSession });
|
||||
|
||||
const fullBoardProps = await createFullBoardAsync(db, "home");
|
||||
await db
|
||||
.update(users)
|
||||
.set({
|
||||
homeBoardId: fullBoardProps.boardId,
|
||||
})
|
||||
.where(eq(users.id, defaultCreatorId));
|
||||
|
||||
// Act
|
||||
const result = await caller.getHomeBoard();
|
||||
@@ -491,6 +498,40 @@ describe("getHomeBoard should return home board", () => {
|
||||
});
|
||||
expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "view");
|
||||
});
|
||||
test("should return global home board when user doesn't have one", async () => {
|
||||
// Arrange
|
||||
const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");
|
||||
const db = createDb();
|
||||
const caller = boardRouter.createCaller({ db, session: defaultSession });
|
||||
|
||||
const fullBoardProps = await createFullBoardAsync(db, "home");
|
||||
await db.insert(serverSettings).values({
|
||||
settingKey: "board",
|
||||
value: SuperJSON.stringify({ defaultBoardId: fullBoardProps.boardId }),
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await caller.getHomeBoard();
|
||||
|
||||
// Assert
|
||||
expectInputToBeFullBoardWithName(result, {
|
||||
name: "home",
|
||||
...fullBoardProps,
|
||||
});
|
||||
expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "view");
|
||||
});
|
||||
test("should throw error when home board not configured in serverSettings", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
const caller = boardRouter.createCaller({ db, session: defaultSession });
|
||||
await createFullBoardAsync(db, "home");
|
||||
|
||||
// Act
|
||||
const actAsync = async () => await caller.getHomeBoard();
|
||||
|
||||
// Assert
|
||||
await expect(actAsync()).rejects.toThrowError("No home board found");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getBoardByName should return board by name", () => {
|
||||
|
||||
@@ -40,56 +40,20 @@ describe("getAll server settings", () => {
|
||||
|
||||
await expect(actAsync()).rejects.toThrow();
|
||||
});
|
||||
test("getAll should return server", async () => {
|
||||
test("getAll should return default server settings when nothing in database", async () => {
|
||||
const db = createDb();
|
||||
const caller = serverSettingsRouter.createCaller({
|
||||
db,
|
||||
session: defaultSession,
|
||||
});
|
||||
|
||||
await db.insert(serverSettings).values([
|
||||
{
|
||||
settingKey: defaultServerSettingsKeys[0],
|
||||
value: SuperJSON.stringify(defaultServerSettings.analytics),
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await caller.getAll();
|
||||
|
||||
expect(result).toStrictEqual({
|
||||
analytics: {
|
||||
enableGeneral: true,
|
||||
enableWidgetData: false,
|
||||
enableIntegrationData: false,
|
||||
enableUserData: false,
|
||||
},
|
||||
});
|
||||
expect(result).toStrictEqual(defaultServerSettings);
|
||||
});
|
||||
});
|
||||
|
||||
describe("saveSettings", () => {
|
||||
test("saveSettings should return false when it did not update one", async () => {
|
||||
const db = createDb();
|
||||
const caller = serverSettingsRouter.createCaller({
|
||||
db,
|
||||
session: defaultSession,
|
||||
});
|
||||
|
||||
const result = await caller.saveSettings({
|
||||
settingsKey: "analytics",
|
||||
value: {
|
||||
enableGeneral: true,
|
||||
enableWidgetData: true,
|
||||
enableIntegrationData: true,
|
||||
enableUserData: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
|
||||
const dbSettings = await db.select().from(serverSettings);
|
||||
expect(dbSettings.length).toBe(0);
|
||||
});
|
||||
test("saveSettings should update settings and return true when it updated only one", async () => {
|
||||
const db = createDb();
|
||||
const caller = serverSettingsRouter.createCaller({
|
||||
@@ -104,7 +68,7 @@ describe("saveSettings", () => {
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await caller.saveSettings({
|
||||
await caller.saveSettings({
|
||||
settingsKey: "analytics",
|
||||
value: {
|
||||
enableGeneral: true,
|
||||
@@ -114,8 +78,6 @@ describe("saveSettings", () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
|
||||
const dbSettings = await db.select().from(serverSettings);
|
||||
expect(dbSettings).toStrictEqual([
|
||||
{
|
||||
|
||||
@@ -18,7 +18,11 @@ import { createRedirectUri } from "./redirect";
|
||||
import { expireDateAfter, generateSessionToken, sessionTokenCookieName } from "./session";
|
||||
|
||||
// See why it's unknown in the [...nextauth]/route.ts file
|
||||
export const createConfiguration = (provider: SupportedAuthProvider | "unknown", headers: ReadonlyHeaders | null) => {
|
||||
export const createConfiguration = (
|
||||
provider: SupportedAuthProvider | "unknown",
|
||||
headers: ReadonlyHeaders | null,
|
||||
useSecureCookies: boolean,
|
||||
) => {
|
||||
const adapter = createAdapter(db, provider);
|
||||
return NextAuth({
|
||||
logger: {
|
||||
@@ -37,12 +41,6 @@ export const createConfiguration = (provider: SupportedAuthProvider | "unknown",
|
||||
cookies: {
|
||||
sessionToken: {
|
||||
name: sessionTokenCookieName,
|
||||
options: {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
secure: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
adapter,
|
||||
@@ -81,7 +79,7 @@ export const createConfiguration = (provider: SupportedAuthProvider | "unknown",
|
||||
expires: expires,
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
secure: true,
|
||||
secure: useSecureCookies,
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { NextAuthConfig } from "next-auth";
|
||||
import { and, eq, inArray } from "@homarr/db";
|
||||
import type { Database } from "@homarr/db";
|
||||
import { groupMembers, groups, users } from "@homarr/db/schema/sqlite";
|
||||
import { everyoneGroup } from "@homarr/definitions";
|
||||
import { colorSchemeCookieKey, everyoneGroup } from "@homarr/definitions";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { env } from "./env.mjs";
|
||||
@@ -51,8 +51,10 @@ export const createSignInEventHandler = (db: Database): Exclude<NextAuthConfig["
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`User '${dbUser.name}' logged in at ${dayjs().format()}`);
|
||||
|
||||
// We use a cookie as localStorage is not shared with server (otherwise flickering would occur)
|
||||
cookies().set("homarr-color-scheme", dbUser.colorScheme, {
|
||||
cookies().set(colorSchemeCookieKey, dbUser.colorScheme, {
|
||||
path: "/",
|
||||
expires: dayjs().add(1, "year").toDate(),
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ declare module "next-auth" {
|
||||
export * from "./security";
|
||||
|
||||
// See why it's unknown in the [...nextauth]/route.ts file
|
||||
export const createHandlers = (provider: SupportedAuthProvider | "unknown") => createConfiguration(provider, headers());
|
||||
export const createHandlers = (provider: SupportedAuthProvider | "unknown", useSecureCookies: boolean) =>
|
||||
createConfiguration(provider, headers(), useSecureCookies);
|
||||
|
||||
export { getSessionFromTokenAsync as getSessionFromToken, sessionTokenCookieName } from "./session";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { cache } from "react";
|
||||
|
||||
import { createConfiguration } from "./configuration";
|
||||
|
||||
const { auth: defaultAuth } = createConfiguration("unknown", null);
|
||||
const { auth: defaultAuth } = createConfiguration("unknown", null, false);
|
||||
|
||||
/**
|
||||
* This is the main way to get session data for your RSCs.
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@auth/core": "^0.37.2",
|
||||
"@auth/drizzle-adapter": "^1.7.2",
|
||||
"@auth/core": "^0.37.3",
|
||||
"@auth/drizzle-adapter": "^1.7.3",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
@@ -45,7 +45,7 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/bcrypt": "5.0.2",
|
||||
"@types/cookies": "0.9.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { eq } from "@homarr/db";
|
||||
import type { Database } from "@homarr/db";
|
||||
import { groupMembers, groups, users } from "@homarr/db/schema/sqlite";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
import { everyoneGroup } from "@homarr/definitions";
|
||||
import { colorSchemeCookieKey, everyoneGroup } from "@homarr/definitions";
|
||||
|
||||
import { createSignInEventHandler } from "../events";
|
||||
|
||||
@@ -224,7 +224,7 @@ describe("createSignInEventHandler should create signInEventHandler", () => {
|
||||
});
|
||||
expect(dbUser?.name).toBe("test-new");
|
||||
});
|
||||
test("signInEventHandler should set homarr-color-scheme cookie", async () => {
|
||||
test("signInEventHandler should set color-scheme cookie", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
await createUserAsync(db);
|
||||
@@ -239,7 +239,7 @@ describe("createSignInEventHandler should create signInEventHandler", () => {
|
||||
|
||||
// Assert
|
||||
expect(cookies().set).toHaveBeenCalledWith(
|
||||
"homarr-color-scheme",
|
||||
colorSchemeCookieKey,
|
||||
"dark",
|
||||
expect.objectContaining({
|
||||
path: "/",
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,13 +29,13 @@
|
||||
"dayjs": "^1.11.13",
|
||||
"next": "^14.2.16",
|
||||
"react": "^18.3.1",
|
||||
"tldts": "^6.1.57"
|
||||
"tldts": "^6.1.58"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "next/navigation";
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
const calculateTimeAgo = (timestamp: Date, locale: string) => {
|
||||
return dayjs().locale(locale).to(timestamp);
|
||||
const calculateTimeAgo = (timestamp: Date) => {
|
||||
return dayjs().to(timestamp);
|
||||
};
|
||||
|
||||
export const useTimeAgo = (timestamp: Date) => {
|
||||
const { locale } = useParams<{ locale: string }>();
|
||||
const [timeAgo, setTimeAgo] = useState(calculateTimeAgo(timestamp, locale));
|
||||
const [timeAgo, setTimeAgo] = useState(calculateTimeAgo(timestamp));
|
||||
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(() => setTimeAgo(calculateTimeAgo(timestamp, locale)), 1000); // update every second
|
||||
const intervalId = setInterval(() => setTimeAgo(calculateTimeAgo(timestamp)), 1000); // update every second
|
||||
|
||||
return () => clearInterval(intervalId); // clear interval on hook unmount
|
||||
}, [timestamp, locale]);
|
||||
}, [timestamp]);
|
||||
|
||||
return timeAgo;
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,16 @@
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import { sendServerAnalyticsAsync } from "@homarr/analytics";
|
||||
import { EVERY_WEEK } from "@homarr/cron-jobs-core/expressions";
|
||||
import { db, eq } from "@homarr/db";
|
||||
import { serverSettings } from "@homarr/db/schema/sqlite";
|
||||
import type { defaultServerSettings } from "@homarr/server-settings";
|
||||
import { db } from "@homarr/db";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
|
||||
import { createCronJob } from "../lib";
|
||||
|
||||
export const analyticsJob = createCronJob("analytics", EVERY_WEEK, {
|
||||
runOnStart: true,
|
||||
}).withCallback(async () => {
|
||||
const analyticSetting = await db.query.serverSettings.findFirst({
|
||||
where: eq(serverSettings.settingKey, "analytics"),
|
||||
});
|
||||
const analyticSetting = await getServerSettingByKeyAsync(db, "analytics");
|
||||
|
||||
if (!analyticSetting) {
|
||||
return;
|
||||
}
|
||||
|
||||
const value = SuperJSON.parse<(typeof defaultServerSettings)["analytics"]>(analyticSetting.value);
|
||||
|
||||
if (!value.enableGeneral) {
|
||||
if (!analyticSetting.enableGeneral) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ export const iconsUpdaterJob = createCronJob("iconsUpdater", EVERY_WEEK, {
|
||||
id: createId(),
|
||||
checksum: icon.checksum,
|
||||
name: icon.fileNameWithExtension,
|
||||
url: icon.imageUrl.href,
|
||||
url: icon.imageUrl,
|
||||
iconRepositoryId: repositoryIconGroupId,
|
||||
});
|
||||
countInserted++;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import { objectKeys } from "@homarr/common";
|
||||
import { everyoneGroup } from "@homarr/definitions";
|
||||
import { defaultServerSettings, defaultServerSettingsKeys } from "@homarr/server-settings";
|
||||
|
||||
@@ -32,21 +33,33 @@ const seedEveryoneGroupAsync = async (db: Database) => {
|
||||
|
||||
const seedServerSettingsAsync = async (db: Database) => {
|
||||
const serverSettingsData = await db.query.serverSettings.findMany();
|
||||
let insertedSettingsCount = 0;
|
||||
|
||||
for (const settingsKey of defaultServerSettingsKeys) {
|
||||
if (serverSettingsData.some((setting) => setting.settingKey === settingsKey)) {
|
||||
return;
|
||||
const currentDbEntry = serverSettingsData.find((setting) => setting.settingKey === settingsKey);
|
||||
if (!currentDbEntry) {
|
||||
await db.insert(serverSettings).values({
|
||||
settingKey: settingsKey,
|
||||
value: SuperJSON.stringify(defaultServerSettings[settingsKey]),
|
||||
});
|
||||
console.log(`Created serverSetting through seed key=${settingsKey}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await db.insert(serverSettings).values({
|
||||
settingKey: settingsKey,
|
||||
value: SuperJSON.stringify(defaultServerSettings[settingsKey]),
|
||||
});
|
||||
insertedSettingsCount++;
|
||||
}
|
||||
const currentSettings = SuperJSON.parse<Record<string, unknown>>(currentDbEntry.value);
|
||||
const defaultSettings = defaultServerSettings[settingsKey];
|
||||
const missingKeys = objectKeys(defaultSettings).filter((key) => !(key in currentSettings));
|
||||
|
||||
if (insertedSettingsCount > 0) {
|
||||
console.info(`Inserted ${insertedSettingsCount} missing settings`);
|
||||
if (missingKeys.length === 0) {
|
||||
console.info(`Skipping seeding for serverSetting as it already exists key=${settingsKey}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await db
|
||||
.update(serverSettings)
|
||||
.set({
|
||||
value: SuperJSON.stringify({ ...defaultSettings, ...currentSettings }), // Add missing keys
|
||||
})
|
||||
.where(eq(serverSettings.settingKey, settingsKey));
|
||||
console.log(`Updated serverSetting through seed key=${settingsKey}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@auth/core": "^0.37.2",
|
||||
"@auth/core": "^0.37.3",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
@@ -54,7 +54,7 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/better-sqlite3": "7.6.11",
|
||||
"dotenv-cli": "^7.4.2",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"prettier": "^3.3.3",
|
||||
"tsx": "4.19.2",
|
||||
"typescript": "^5.6.3"
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./item";
|
||||
export * from "./server-setting";
|
||||
|
||||
52
packages/db/queries/server-setting.ts
Normal file
52
packages/db/queries/server-setting.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import type { ServerSettings } from "@homarr/server-settings";
|
||||
import { defaultServerSettings, defaultServerSettingsKeys } from "@homarr/server-settings";
|
||||
|
||||
import type { Database } from "..";
|
||||
import { eq } from "..";
|
||||
import { serverSettings } from "../schema/sqlite";
|
||||
|
||||
export const getServerSettingsAsync = async (db: Database) => {
|
||||
const settings = await db.query.serverSettings.findMany();
|
||||
|
||||
return defaultServerSettingsKeys.reduce((acc, settingKey) => {
|
||||
const setting = settings.find((setting) => setting.settingKey === settingKey);
|
||||
if (!setting) {
|
||||
// Typescript is not happy because the key is a union and it does not know that they are the same
|
||||
acc[settingKey] = defaultServerSettings[settingKey] as never;
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc[settingKey] = {
|
||||
...defaultServerSettings[settingKey],
|
||||
...SuperJSON.parse(setting.value),
|
||||
};
|
||||
return acc;
|
||||
}, {} as ServerSettings);
|
||||
};
|
||||
|
||||
export const getServerSettingByKeyAsync = async <TKey extends keyof ServerSettings>(db: Database, key: TKey) => {
|
||||
const dbSettings = await db.query.serverSettings.findFirst({
|
||||
where: eq(serverSettings.settingKey, key),
|
||||
});
|
||||
|
||||
if (!dbSettings) {
|
||||
return defaultServerSettings[key];
|
||||
}
|
||||
|
||||
return SuperJSON.parse<ServerSettings[TKey]>(dbSettings.value);
|
||||
};
|
||||
|
||||
export const updateServerSettingByKeyAsync = async <TKey extends keyof ServerSettings>(
|
||||
db: Database,
|
||||
key: TKey,
|
||||
value: ServerSettings[TKey],
|
||||
) => {
|
||||
await db
|
||||
.update(serverSettings)
|
||||
.set({
|
||||
value: SuperJSON.stringify(value),
|
||||
})
|
||||
.where(eq(serverSettings.settingKey, key));
|
||||
};
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
2
packages/definitions/src/cookie.ts
Normal file
2
packages/definitions/src/cookie.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const colorSchemeCookieKey = "homarr.color-scheme";
|
||||
export const localeCookieKey = "homarr.locale";
|
||||
@@ -8,3 +8,4 @@ export * from "./auth";
|
||||
export * from "./user";
|
||||
export * from "./group";
|
||||
export * from "./docs";
|
||||
export * from "./cookie";
|
||||
|
||||
@@ -15,6 +15,7 @@ export const widgetKinds = [
|
||||
"mediaRequests-requestList",
|
||||
"mediaRequests-requestStats",
|
||||
"rssFeed",
|
||||
"bookmarks",
|
||||
"indexerManager",
|
||||
"healthMonitoring",
|
||||
] as const;
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,13 +23,14 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GitHubIconRepository } from "./repositories/github.icon-repository";
|
||||
import { JsdelivrIconRepository } from "./repositories/jsdelivr.icon-repository";
|
||||
import { LocalIconRepository } from "./repositories/local.icon-repository";
|
||||
import type { RepositoryIconGroup } from "./types";
|
||||
|
||||
const repositories = [
|
||||
@@ -43,6 +44,7 @@ const repositories = [
|
||||
new URL("https://data.jsdelivr.com/v1/packages/gh/loganmarchione/homelab-svg-assets@main?structure=flat"),
|
||||
"https://cdn.jsdelivr.net/gh/loganmarchione/homelab-svg-assets/{0}",
|
||||
),
|
||||
new LocalIconRepository(),
|
||||
];
|
||||
|
||||
export const fetchIconsAsync = async (): Promise<RepositoryIconGroup[]> => {
|
||||
|
||||
@@ -35,11 +35,8 @@ export class GitHubIconRepository extends IconRepository {
|
||||
.map(({ path, size: sizeInBytes, sha: checksum }) => {
|
||||
const file = parse(path);
|
||||
const fileNameWithExtension = file.base;
|
||||
const imageUrl = new URL(
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this.repositoryBlobUrlTemplate!.replace("{0}", path).replace("{1}", file.name),
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const imageUrl = this.repositoryBlobUrlTemplate!.replace("{0}", path).replace("{1}", file.name);
|
||||
return {
|
||||
imageUrl,
|
||||
fileNameWithExtension,
|
||||
|
||||
@@ -33,7 +33,7 @@ export class JsdelivrIconRepository extends IconRepository {
|
||||
const fileNameWithExtension = file.base;
|
||||
|
||||
return {
|
||||
imageUrl: new URL(this.repositoryBlobUrlTemplate.replace("{0}", path).replace("{1}", file.name)),
|
||||
imageUrl: this.repositoryBlobUrlTemplate.replace("{0}", path).replace("{1}", file.name),
|
||||
fileNameWithExtension,
|
||||
local: false,
|
||||
sizeInBytes,
|
||||
|
||||
26
packages/icons/src/repositories/local.icon-repository.ts
Normal file
26
packages/icons/src/repositories/local.icon-repository.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createHash } from "crypto";
|
||||
|
||||
import { db } from "@homarr/db";
|
||||
|
||||
import type { RepositoryIconGroup } from "../types";
|
||||
import { IconRepository } from "./icon-repository";
|
||||
|
||||
export class LocalIconRepository extends IconRepository {
|
||||
constructor() {
|
||||
super("Local", "local", undefined, undefined, undefined, undefined);
|
||||
}
|
||||
protected async getAllIconsInternalAsync(): Promise<RepositoryIconGroup> {
|
||||
const medias = await db.query.medias.findMany();
|
||||
return {
|
||||
success: true,
|
||||
icons: medias.map((media) => ({
|
||||
local: true,
|
||||
fileNameWithExtension: media.name,
|
||||
imageUrl: `/api/user-medias/${media.id}`,
|
||||
checksum: createHash("md5").update(media.content).digest("hex"),
|
||||
sizeInBytes: media.size,
|
||||
})),
|
||||
slug: "local",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
export interface RepositoryIcon {
|
||||
fileNameWithExtension: string;
|
||||
sizeInBytes?: number;
|
||||
imageUrl: URL;
|
||||
imageUrl: string;
|
||||
local: boolean;
|
||||
checksum: string;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,13 +28,13 @@
|
||||
"dependencies": {
|
||||
"ioredis": "5.4.1",
|
||||
"superjson": "2.2.1",
|
||||
"winston": "3.15.0"
|
||||
"winston": "3.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"prettier": "@homarr/prettier-config"
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"prettier": "@homarr/prettier-config"
|
||||
|
||||
@@ -1,49 +1,57 @@
|
||||
import { createId, inArray } from "@homarr/db";
|
||||
import type { Database, InferInsertModel } from "@homarr/db";
|
||||
import type { Database, InferInsertModel, InferSelectModel } from "@homarr/db";
|
||||
import { apps as appsTable } from "@homarr/db/schema/sqlite";
|
||||
import { logger } from "@homarr/log";
|
||||
import type { OldmarrApp } from "@homarr/old-schema";
|
||||
|
||||
import type { BookmarkApp } from "./widgets/definitions/bookmark";
|
||||
|
||||
type DbAppWithoutId = Omit<InferSelectModel<typeof appsTable>, "id">;
|
||||
|
||||
interface AppMapping extends DbAppWithoutId {
|
||||
ids: string[];
|
||||
newId: string;
|
||||
exists: boolean;
|
||||
}
|
||||
|
||||
export const insertAppsAsync = async (
|
||||
db: Database,
|
||||
apps: OldmarrApp[],
|
||||
bookmarkApps: BookmarkApp[],
|
||||
distinctAppsByHref: boolean,
|
||||
configName: string,
|
||||
) => {
|
||||
logger.info(
|
||||
`Importing old homarr apps configuration=${configName} distinctAppsByHref=${distinctAppsByHref} apps=${apps.length}`,
|
||||
);
|
||||
|
||||
const existingAppsWithHref = distinctAppsByHref
|
||||
? await db.query.apps.findMany({
|
||||
where: inArray(appsTable.href, [...new Set(apps.map((app) => app.url))]),
|
||||
where: inArray(appsTable.href, [
|
||||
...new Set(apps.map((app) => app.url).concat(bookmarkApps.map((app) => app.href))),
|
||||
]),
|
||||
})
|
||||
: [];
|
||||
|
||||
logger.debug(`Found existing apps with href count=${existingAppsWithHref.length}`);
|
||||
|
||||
const mappedApps = apps.map((app) => ({
|
||||
// Use id of existing app when it has the same href and distinctAppsByHref is true
|
||||
newId: distinctAppsByHref
|
||||
? (existingAppsWithHref.find(
|
||||
(existingApp) =>
|
||||
existingApp.href === (app.behaviour.externalUrl === "" ? app.url : app.behaviour.externalUrl) &&
|
||||
existingApp.name === app.name &&
|
||||
existingApp.iconUrl === app.appearance.iconUrl,
|
||||
)?.id ?? createId())
|
||||
: createId(),
|
||||
...app,
|
||||
}));
|
||||
// Generate mappings for all apps from old to new ids
|
||||
const appMappings: AppMapping[] = [];
|
||||
addMappingFor(apps, appMappings, existingAppsWithHref, convertApp);
|
||||
addMappingFor(bookmarkApps, appMappings, existingAppsWithHref, convertBookmarkApp);
|
||||
|
||||
const appsToCreate = mappedApps
|
||||
.filter((app) => !existingAppsWithHref.some((existingApp) => existingApp.id === app.newId))
|
||||
logger.debug(`Mapping apps count=${appMappings.length}`);
|
||||
|
||||
const appsToCreate = appMappings
|
||||
.filter((app) => !app.exists)
|
||||
.map(
|
||||
(app) =>
|
||||
({
|
||||
id: app.newId,
|
||||
name: app.name,
|
||||
iconUrl: app.appearance.iconUrl,
|
||||
href: app.behaviour.externalUrl === "" ? app.url : app.behaviour.externalUrl,
|
||||
description: app.behaviour.tooltipDescription,
|
||||
iconUrl: app.iconUrl,
|
||||
href: app.href,
|
||||
description: app.description,
|
||||
}) satisfies InferInsertModel<typeof appsTable>,
|
||||
);
|
||||
|
||||
@@ -55,5 +63,95 @@ export const insertAppsAsync = async (
|
||||
|
||||
logger.info(`Imported apps count=${appsToCreate.length}`);
|
||||
|
||||
return mappedApps;
|
||||
// Generates a map from old key to new key for all apps
|
||||
return new Map(
|
||||
appMappings
|
||||
.map((app) => app.ids.map((id) => ({ id, newId: app.newId })))
|
||||
.flat()
|
||||
.map(({ id, newId }) => [id, newId]),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a callback to be used in a find method that compares the old app with the new app
|
||||
* @param app either an oldmarr app or a bookmark app
|
||||
* @param convertApp a function that converts the app to a new app
|
||||
* @returns a callback that compares the old app with the new app and returns true if they are the same
|
||||
*/
|
||||
const createFindCallback = <TApp extends OldmarrApp | BookmarkApp>(
|
||||
app: TApp,
|
||||
convertApp: (app: TApp) => DbAppWithoutId,
|
||||
) => {
|
||||
const oldApp = convertApp(app);
|
||||
|
||||
return (dbApp: DbAppWithoutId) =>
|
||||
oldApp.href === dbApp.href &&
|
||||
oldApp.name === dbApp.name &&
|
||||
oldApp.iconUrl === dbApp.iconUrl &&
|
||||
oldApp.description === dbApp.description;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds mappings for the given apps to the appMappings array
|
||||
* @param apps apps to add mappings for
|
||||
* @param appMappings existing app mappings
|
||||
* @param existingAppsWithHref existing apps with href
|
||||
* @param convertApp a function that converts the app to a new app
|
||||
*/
|
||||
const addMappingFor = <TApp extends OldmarrApp | BookmarkApp>(
|
||||
apps: TApp[],
|
||||
appMappings: AppMapping[],
|
||||
existingAppsWithHref: InferSelectModel<typeof appsTable>[],
|
||||
convertApp: (app: TApp) => DbAppWithoutId,
|
||||
) => {
|
||||
for (const app of apps) {
|
||||
const previous = appMappings.find(createFindCallback(app, convertApp));
|
||||
if (previous) {
|
||||
previous.ids.push(app.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const existing = existingAppsWithHref.find(createFindCallback(app, convertApp));
|
||||
if (existing) {
|
||||
appMappings.push({
|
||||
ids: [app.id],
|
||||
newId: existing.id,
|
||||
name: existing.name,
|
||||
href: existing.href,
|
||||
iconUrl: existing.iconUrl,
|
||||
description: existing.description,
|
||||
exists: true,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
appMappings.push({
|
||||
ids: [app.id],
|
||||
newId: createId(),
|
||||
...convertApp(app),
|
||||
exists: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts an oldmarr app to a new app
|
||||
* @param app oldmarr app
|
||||
* @returns new app
|
||||
*/
|
||||
const convertApp = (app: OldmarrApp): DbAppWithoutId => ({
|
||||
name: app.name,
|
||||
href: app.behaviour.externalUrl === "" ? app.url : app.behaviour.externalUrl,
|
||||
iconUrl: app.appearance.iconUrl,
|
||||
description: app.behaviour.tooltipDescription ?? null,
|
||||
});
|
||||
|
||||
/**
|
||||
* Converts a bookmark app to a new app
|
||||
* @param app bookmark app
|
||||
* @returns new app
|
||||
*/
|
||||
const convertBookmarkApp = (app: BookmarkApp): DbAppWithoutId => ({
|
||||
...app,
|
||||
description: null,
|
||||
});
|
||||
|
||||
@@ -15,11 +15,12 @@ import { mapOptions } from "./widgets/options";
|
||||
export const insertItemsAsync = async (
|
||||
db: Database,
|
||||
widgets: OldmarrWidget[],
|
||||
mappedApps: (OldmarrApp & { newId: string })[],
|
||||
apps: OldmarrApp[],
|
||||
appsMap: Map<string, string>,
|
||||
sectionIdMaps: Map<string, string>,
|
||||
configuration: OldmarrImportConfiguration,
|
||||
) => {
|
||||
logger.info(`Importing old homarr items widgets=${widgets.length} apps=${mappedApps.length}`);
|
||||
logger.info(`Importing old homarr items widgets=${widgets.length} apps=${apps.length}`);
|
||||
|
||||
for (const widget of widgets) {
|
||||
// All items should have been moved to the last wrapper
|
||||
@@ -54,13 +55,13 @@ export const insertItemsAsync = async (
|
||||
xOffset: screenSizeShape.location.x,
|
||||
yOffset: screenSizeShape.location.y,
|
||||
kind,
|
||||
options: SuperJSON.stringify(mapOptions(kind, widget.properties)),
|
||||
options: SuperJSON.stringify(mapOptions(kind, widget.properties, appsMap)),
|
||||
});
|
||||
|
||||
logger.debug(`Inserted widget id=${widget.id} sectionId=${sectionId}`);
|
||||
}
|
||||
|
||||
for (const app of mappedApps) {
|
||||
for (const app of apps) {
|
||||
// All items should have been moved to the last wrapper
|
||||
if (app.area.type === "sidebar") {
|
||||
continue;
|
||||
@@ -85,7 +86,9 @@ export const insertItemsAsync = async (
|
||||
yOffset: screenSizeShape.location.y,
|
||||
kind: "app",
|
||||
options: SuperJSON.stringify({
|
||||
appId: app.newId,
|
||||
// it's safe to assume that the app exists in the map
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
appId: appsMap.get(app.id)!,
|
||||
openInNewTab: app.behaviour.isOpeningNewTab,
|
||||
pingEnabled: app.network.enabledStatusChecker,
|
||||
showDescriptionTooltip: app.behaviour.tooltipDescription !== "",
|
||||
|
||||
@@ -9,12 +9,24 @@ import { OldHomarrImportError, OldHomarrScreenSizeError } from "./import-error";
|
||||
import { insertItemsAsync } from "./import-items";
|
||||
import { insertSectionsAsync } from "./import-sections";
|
||||
import { moveWidgetsAndAppsIfMerge } from "./move-widgets-and-apps-merge";
|
||||
import type { BookmarkApp } from "./widgets/definitions/bookmark";
|
||||
|
||||
export const importAsync = async (db: Database, old: OldmarrConfig, configuration: OldmarrImportConfiguration) => {
|
||||
const bookmarkApps = old.widgets
|
||||
.filter((widget) => widget.type === "bookmark")
|
||||
.map((widget) => widget.properties.items)
|
||||
.flat() as BookmarkApp[];
|
||||
|
||||
if (configuration.onlyImportApps) {
|
||||
await db
|
||||
.transaction(async (trasaction) => {
|
||||
await insertAppsAsync(trasaction, old.apps, configuration.distinctAppsByHref, old.configProperties.name);
|
||||
await insertAppsAsync(
|
||||
trasaction,
|
||||
old.apps,
|
||||
bookmarkApps,
|
||||
configuration.distinctAppsByHref,
|
||||
old.configProperties.name,
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
throw new OldHomarrImportError(old, error);
|
||||
@@ -29,13 +41,14 @@ export const importAsync = async (db: Database, old: OldmarrConfig, configuratio
|
||||
|
||||
const boardId = await insertBoardAsync(trasaction, old, configuration);
|
||||
const sectionIdMaps = await insertSectionsAsync(trasaction, categories, wrappers, boardId);
|
||||
const mappedApps = await insertAppsAsync(
|
||||
const appsMap = await insertAppsAsync(
|
||||
trasaction,
|
||||
apps,
|
||||
bookmarkApps,
|
||||
configuration.distinctAppsByHref,
|
||||
old.configProperties.name,
|
||||
);
|
||||
await insertItemsAsync(trasaction, widgets, mappedApps, sectionIdMaps, configuration);
|
||||
await insertItemsAsync(trasaction, widgets, apps, appsMap, sectionIdMaps, configuration);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error instanceof OldHomarrScreenSizeError) {
|
||||
|
||||
@@ -16,3 +16,5 @@ export type OldmarrBookmarkDefinition = CommonOldmarrWidgetDefinition<
|
||||
layout: "autoGrid" | "horizontal" | "vertical";
|
||||
}
|
||||
>;
|
||||
|
||||
export type BookmarkApp = OldmarrBookmarkDefinition["options"]["items"][number];
|
||||
|
||||
@@ -66,6 +66,7 @@ export const widgetKindMapping = {
|
||||
"mediaRequests-requestList": "media-requests-list",
|
||||
"mediaRequests-requestStats": "media-requests-stats",
|
||||
indexerManager: "indexer-manager",
|
||||
bookmarks: "bookmark",
|
||||
healthMonitoring: "health-monitoring",
|
||||
} satisfies Record<WidgetKind, OldmarrWidgetDefinitions["id"] | null>;
|
||||
// Use null for widgets that did not exist in oldmarr
|
||||
|
||||
@@ -13,6 +13,7 @@ type OptionMapping = {
|
||||
: {
|
||||
[OptionsKey in keyof WidgetComponentProps<WidgetKey>["options"]]: (
|
||||
oldOptions: Extract<OldmarrWidgetDefinitions, { id: WidgetMapping[WidgetKey] }>["options"],
|
||||
appsMap: Map<string, string>,
|
||||
) => WidgetComponentProps<WidgetKey>["options"][OptionsKey] | undefined;
|
||||
};
|
||||
};
|
||||
@@ -22,6 +23,22 @@ const optionMapping: OptionMapping = {
|
||||
linksTargetNewTab: (oldOptions) => oldOptions.openInNewTab,
|
||||
},
|
||||
"mediaRequests-requestStats": {},
|
||||
bookmarks: {
|
||||
title: (oldOptions) => oldOptions.name,
|
||||
// It's safe to assume that the app exists, because the app is always created before the widget
|
||||
// And the mapping is created in insertAppsAsync
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
items: (oldOptions, appsMap) => oldOptions.items.map((item) => appsMap.get(item.id)!),
|
||||
layout: (oldOptions) => {
|
||||
const mappedLayouts: Record<typeof oldOptions.layout, WidgetComponentProps<"bookmarks">["options"]["layout"]> = {
|
||||
autoGrid: "grid",
|
||||
horizontal: "row",
|
||||
vertical: "column",
|
||||
};
|
||||
|
||||
return mappedLayouts[oldOptions.layout];
|
||||
},
|
||||
},
|
||||
calendar: {
|
||||
releaseType: (oldOptions) => [oldOptions.radarrReleaseType],
|
||||
filterFutureMonths: () => undefined,
|
||||
@@ -118,11 +135,13 @@ const optionMapping: OptionMapping = {
|
||||
* Maps the oldmarr options to the newmarr options
|
||||
* @param kind item kind to map
|
||||
* @param oldOptions oldmarr options for this item
|
||||
* @param appsMap map of old app ids to new app ids
|
||||
* @returns newmarr options for this item or null if the item did not exist in oldmarr
|
||||
*/
|
||||
export const mapOptions = <K extends WidgetKind>(
|
||||
kind: K,
|
||||
oldOptions: Extract<OldmarrWidgetDefinitions, { id: WidgetMapping[K] }>["options"],
|
||||
appsMap: Map<string, string>,
|
||||
) => {
|
||||
logger.debug(`Mapping old homarr options for widget kind=${kind} options=${JSON.stringify(oldOptions)}`);
|
||||
if (optionMapping[kind] === null) {
|
||||
@@ -132,7 +151,7 @@ export const mapOptions = <K extends WidgetKind>(
|
||||
const mapping = optionMapping[kind];
|
||||
return objectEntries(mapping).reduce(
|
||||
(acc, [key, value]) => {
|
||||
const newValue = value(oldOptions as never);
|
||||
const newValue = value(oldOptions as never, appsMap);
|
||||
logger.debug(`Mapping old homarr option kind=${kind} key=${key as string} newValue=${newValue as string}`);
|
||||
if (newValue !== undefined) {
|
||||
acc[key as string] = newValue;
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"prettier": "@homarr/prettier-config"
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,15 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
export const defaultServerSettingsKeys = ["analytics", "crawlingAndIndexing"] as const;
|
||||
import type { ColorScheme } from "@homarr/definitions";
|
||||
import type { SupportedLanguage } from "@homarr/translation";
|
||||
|
||||
export const defaultServerSettingsKeys = [
|
||||
"analytics",
|
||||
"crawlingAndIndexing",
|
||||
"board",
|
||||
"appearance",
|
||||
"culture",
|
||||
] as const;
|
||||
|
||||
export type ServerSettingsRecord = Record<(typeof defaultServerSettingsKeys)[number], Record<string, unknown>>;
|
||||
|
||||
@@ -15,6 +24,15 @@ export const defaultServerSettings = {
|
||||
noTranslate: true,
|
||||
noSiteLinksSearchBox: false,
|
||||
},
|
||||
board: {
|
||||
defaultBoardId: null as string | null,
|
||||
},
|
||||
appearance: {
|
||||
defaultColorScheme: "light" as ColorScheme,
|
||||
},
|
||||
culture: {
|
||||
defaultLocale: "en" as SupportedLanguage,
|
||||
},
|
||||
} satisfies ServerSettingsRecord;
|
||||
|
||||
export type ServerSettings = typeof defaultServerSettings;
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"prettier": "@homarr/prettier-config"
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"./client": "./src/client/index.ts",
|
||||
"./server": "./src/server.ts",
|
||||
"./middleware": "./src/middleware.ts",
|
||||
"./request": "./src/request.ts"
|
||||
"./request": "./src/request.ts",
|
||||
"./dayjs": "./src/dayjs.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
@@ -27,7 +28,9 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"deepmerge": "4.3.1",
|
||||
"mantine-react-table": "2.0.0-beta.7",
|
||||
"next": "^14.2.16",
|
||||
"next-intl": "3.24.0",
|
||||
@@ -37,7 +40,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,342 @@
|
||||
import type { MRT_Localization } from "mantine-react-table";
|
||||
|
||||
import { objectKeys } from "@homarr/common";
|
||||
|
||||
export const localeConfigurations = {
|
||||
cn: {
|
||||
name: "中文",
|
||||
translatedName: "Chinese (Simplified)",
|
||||
flagIcon: "cn",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/zh-Hans/index.esm.mjs").then(
|
||||
(module) => module.MRT_Localization_ZH_HANS,
|
||||
);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/zh-cn").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
cs: {
|
||||
name: "Čeština",
|
||||
translatedName: "Czech",
|
||||
flagIcon: "cz",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/cs/index.esm.mjs").then((module) => module.MRT_Localization_CS);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/cs").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
da: {
|
||||
name: "Dansk",
|
||||
translatedName: "Danish",
|
||||
flagIcon: "dk",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/da/index.esm.mjs").then((module) => module.MRT_Localization_DA);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/da").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
de: {
|
||||
name: "Deutsch",
|
||||
translatedName: "German",
|
||||
flagIcon: "de",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/de/index.esm.mjs").then((module) => module.MRT_Localization_DE);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/de").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
en: {
|
||||
name: "English",
|
||||
translatedName: "English",
|
||||
flagIcon: "us",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/en/index.esm.mjs").then((module) => module.MRT_Localization_EN);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/en").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
el: {
|
||||
name: "Ελληνικά",
|
||||
translatedName: "Greek",
|
||||
flagIcon: "gr",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/el/index.esm.mjs").then((module) => module.MRT_Localization_EL);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/el").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
es: {
|
||||
name: "Español",
|
||||
translatedName: "Spanish",
|
||||
flagIcon: "es",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/es/index.esm.mjs").then((module) => module.MRT_Localization_ES);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/es").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
et: {
|
||||
name: "Eesti",
|
||||
translatedName: "Estonian",
|
||||
flagIcon: "ee",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/et/index.esm.mjs").then((module) => module.MRT_Localization_ET);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/et").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
fr: {
|
||||
name: "Français",
|
||||
translatedName: "French",
|
||||
flagIcon: "fr",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/fr/index.esm.mjs").then((module) => module.MRT_Localization_FR);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/fr").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
he: {
|
||||
name: "עברית",
|
||||
translatedName: "Hebrew",
|
||||
flagIcon: "il",
|
||||
isRTL: true,
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/he/index.esm.mjs").then((module) => module.MRT_Localization_HE);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/he").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
hr: {
|
||||
name: "Hrvatski",
|
||||
translatedName: "Croatian",
|
||||
flagIcon: "hr",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/hr/index.esm.mjs").then((module) => module.MRT_Localization_HR);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/hr").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
hu: {
|
||||
name: "Magyar",
|
||||
translatedName: "Hungarian",
|
||||
flagIcon: "hu",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/hu/index.esm.mjs").then((module) => module.MRT_Localization_HU);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/hu").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
it: {
|
||||
name: "Italiano",
|
||||
translatedName: "Italian",
|
||||
flagIcon: "it",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/it/index.esm.mjs").then((module) => module.MRT_Localization_IT);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/it").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
ja: {
|
||||
name: "日本語",
|
||||
translatedName: "Japanese",
|
||||
flagIcon: "jp",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/ja/index.esm.mjs").then((module) => module.MRT_Localization_JA);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/ja").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
ko: {
|
||||
name: "한국어",
|
||||
translatedName: "Korean",
|
||||
flagIcon: "kr",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/ko/index.esm.mjs").then((module) => module.MRT_Localization_KO);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/ko").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
lt: {
|
||||
name: "Lietuvių",
|
||||
translatedName: "Lithuanian",
|
||||
flagIcon: "lt",
|
||||
importMrtLocalization() {
|
||||
return import("./mantine-react-table/lt.json");
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/lt").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
lv: {
|
||||
name: "Latviešu",
|
||||
translatedName: "Latvian",
|
||||
flagIcon: "lv",
|
||||
importMrtLocalization() {
|
||||
return import("./mantine-react-table/lv.json");
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/lv").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
nl: {
|
||||
name: "Nederlands",
|
||||
translatedName: "Dutch",
|
||||
flagIcon: "nl",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/nl/index.esm.mjs").then((module) => module.MRT_Localization_NL);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/nl").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
no: {
|
||||
name: "Norsk",
|
||||
translatedName: "Norwegian",
|
||||
flagIcon: "no",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/no/index.esm.mjs").then((module) => module.MRT_Localization_NO);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/nb").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
pl: {
|
||||
name: "Polski",
|
||||
translatedName: "Polish",
|
||||
flagIcon: "pl",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/pl/index.esm.mjs").then((module) => module.MRT_Localization_PL);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/pl").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
pt: {
|
||||
name: "Português",
|
||||
translatedName: "Portuguese",
|
||||
flagIcon: "pt",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/pt/index.esm.mjs").then((module) => module.MRT_Localization_PT);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/pt").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
ro: {
|
||||
name: "Românesc",
|
||||
translatedName: "Romanian",
|
||||
flagIcon: "ro",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/ro/index.esm.mjs").then((module) => module.MRT_Localization_RO);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/ro").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
ru: {
|
||||
name: "Русский",
|
||||
translatedName: "Russian",
|
||||
flagIcon: "ru",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/ru/index.esm.mjs").then((module) => module.MRT_Localization_RU);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/ru").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
sk: {
|
||||
name: "Slovenčina",
|
||||
translatedName: "Slovak",
|
||||
flagIcon: "sk",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/sk/index.esm.mjs").then((module) => module.MRT_Localization_SK);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/sk").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
sl: {
|
||||
name: "Slovenščina",
|
||||
translatedName: "Slovenian",
|
||||
flagIcon: "si",
|
||||
importMrtLocalization() {
|
||||
return import("./mantine-react-table/sl.json");
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/sl").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
sv: {
|
||||
name: "Svenska",
|
||||
translatedName: "Swedish",
|
||||
flagIcon: "se",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/sv/index.esm.mjs").then((module) => module.MRT_Localization_SV);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/sv").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
tr: {
|
||||
name: "Türkçe",
|
||||
translatedName: "Turkish",
|
||||
flagIcon: "tr",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/tr/index.esm.mjs").then((module) => module.MRT_Localization_TR);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/tr").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
tw: {
|
||||
name: "中文",
|
||||
translatedName: "Chinese (Traditional)",
|
||||
flagIcon: "tw",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/zh-Hant/index.esm.mjs").then(
|
||||
(module) => module.MRT_Localization_ZH_HANT,
|
||||
);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/zh-tw").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
uk: {
|
||||
name: "Українська",
|
||||
translatedName: "Ukrainian",
|
||||
flagIcon: "ua",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/uk/index.esm.mjs").then((module) => module.MRT_Localization_UK);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/uk").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
vi: {
|
||||
name: "Tiếng Việt",
|
||||
translatedName: "Vietnamese",
|
||||
flagIcon: "vn",
|
||||
importMrtLocalization() {
|
||||
return import("mantine-react-table/locales/vi/index.esm.mjs").then((module) => module.MRT_Localization_VI);
|
||||
},
|
||||
importDayJsLocale() {
|
||||
return import("dayjs/locale/vi").then((module) => module.default);
|
||||
},
|
||||
},
|
||||
} satisfies Record<
|
||||
string,
|
||||
@@ -17,10 +344,16 @@ export const localeConfigurations = {
|
||||
name: string;
|
||||
translatedName: string;
|
||||
flagIcon: string;
|
||||
importMrtLocalization: () => Promise<MRT_Localization>;
|
||||
importDayJsLocale: () => Promise<ILocale>;
|
||||
isRTL?: boolean;
|
||||
}
|
||||
>;
|
||||
|
||||
export const supportedLanguages = objectKeys(localeConfigurations);
|
||||
export type SupportedLanguage = (typeof supportedLanguages)[number];
|
||||
|
||||
export const defaultLocale = "en" satisfies SupportedLanguage;
|
||||
export const fallbackLocale = "en" satisfies SupportedLanguage;
|
||||
|
||||
export const isLocaleRTL = (locale: SupportedLanguage) =>
|
||||
"isRTL" in localeConfigurations[locale] && localeConfigurations[locale].isRTL;
|
||||
|
||||
48
packages/translation/src/dayjs.ts
Normal file
48
packages/translation/src/dayjs.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { useParams } from "next/navigation";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import type { SupportedLanguage } from "./config";
|
||||
import { localeConfigurations } from "./config";
|
||||
|
||||
let promise: Promise<void> | null = null;
|
||||
let loading = true;
|
||||
let previousLocale: SupportedLanguage | null = null;
|
||||
const load = () => {
|
||||
if (loading) {
|
||||
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
||||
throw promise;
|
||||
}
|
||||
};
|
||||
const dayJsLocalization = (locale: SupportedLanguage) => {
|
||||
if (promise && previousLocale === locale) {
|
||||
return {
|
||||
load,
|
||||
};
|
||||
}
|
||||
promise = localeConfigurations[locale]
|
||||
.importDayJsLocale()
|
||||
.then((dayJsLocale) => {
|
||||
dayjs.locale(dayJsLocale);
|
||||
loading = false;
|
||||
previousLocale = locale;
|
||||
})
|
||||
.catch(() => {
|
||||
loading = false;
|
||||
});
|
||||
|
||||
return {
|
||||
load,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Load the dayjs localization for the current locale with suspense
|
||||
* This allows us to have the loading spinner shown until the localization is loaded and applied.
|
||||
* Suspense works by throwing a promise, which is caught by the nearest Suspense boundary.
|
||||
*/
|
||||
export const useSuspenseDayJsLocalization = () => {
|
||||
const { locale } = useParams<{ locale: SupportedLanguage }>();
|
||||
const resource = dayJsLocalization(locale);
|
||||
|
||||
resource.load();
|
||||
};
|
||||
911
packages/translation/src/lang/cn.json
Normal file
911
packages/translation/src/lang/cn.json
Normal file
@@ -0,0 +1,911 @@
|
||||
{
|
||||
"user": {
|
||||
"title": "用户",
|
||||
"name": "用户",
|
||||
"field": {
|
||||
"email": {
|
||||
"label": "邮箱"
|
||||
},
|
||||
"username": {
|
||||
"label": "用户名"
|
||||
},
|
||||
"password": {
|
||||
"label": "密码",
|
||||
"requirement": {
|
||||
"lowercase": "包括小写字母",
|
||||
"uppercase": "包含大写字母",
|
||||
"number": "包含数字"
|
||||
}
|
||||
},
|
||||
"passwordConfirm": {
|
||||
"label": "确认密码"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"login": {
|
||||
"label": "登录"
|
||||
},
|
||||
"register": {
|
||||
"label": "创建账号",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "账号已创建"
|
||||
}
|
||||
}
|
||||
},
|
||||
"create": "创建用户"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"field": {
|
||||
"name": "名称"
|
||||
},
|
||||
"permission": {
|
||||
"admin": {
|
||||
"title": "管理员"
|
||||
},
|
||||
"board": {
|
||||
"title": "面板"
|
||||
}
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"page": {
|
||||
"list": {
|
||||
"title": "应用"
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "名称"
|
||||
}
|
||||
}
|
||||
},
|
||||
"integration": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "名称"
|
||||
}
|
||||
},
|
||||
"testConnection": {
|
||||
"notification": {
|
||||
"invalidUrl": {
|
||||
"title": "无效链接"
|
||||
}
|
||||
}
|
||||
},
|
||||
"secrets": {
|
||||
"kind": {
|
||||
"username": {
|
||||
"label": "用户名"
|
||||
},
|
||||
"password": {
|
||||
"label": "密码",
|
||||
"newLabel": "新密码"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"field": {
|
||||
"name": "名称",
|
||||
"size": "大小",
|
||||
"creator": "创建者"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"error": "错误",
|
||||
"action": {
|
||||
"add": "添加",
|
||||
"apply": "应用",
|
||||
"create": "创建",
|
||||
"edit": "编辑",
|
||||
"insert": "插入",
|
||||
"remove": "删除",
|
||||
"save": "保存",
|
||||
"saveChanges": "保存更改",
|
||||
"cancel": "取消",
|
||||
"delete": "删除",
|
||||
"confirm": "确认",
|
||||
"previous": "上一步",
|
||||
"next": "下一步",
|
||||
"tryAgain": "请再试一次"
|
||||
},
|
||||
"information": {
|
||||
"hours": "时",
|
||||
"minutes": "分"
|
||||
},
|
||||
"userAvatar": {
|
||||
"menu": {
|
||||
"preferences": "您的首选项",
|
||||
"login": "登录"
|
||||
}
|
||||
},
|
||||
"dangerZone": "危险",
|
||||
"noResults": "未找到结果",
|
||||
"zod": {
|
||||
"errors": {
|
||||
"default": "该字段无效",
|
||||
"required": "此字段为必填",
|
||||
"string": {
|
||||
"startsWith": "该字段必须以 {startsWith} 开头",
|
||||
"endsWith": "该字段必须以 {endsWith} 结尾",
|
||||
"includes": "该字段必须包含 {includes}"
|
||||
},
|
||||
"tooSmall": {
|
||||
"string": "该字段的长度必须至少为 {minimum} 个字符",
|
||||
"number": "该字段必须大于或等于 {minimum}"
|
||||
},
|
||||
"tooBig": {
|
||||
"string": "该字段的长度不得超过 {maximum} 个字符",
|
||||
"number": "该字段必须小于或等于 {maximum}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"section": {
|
||||
"category": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "名称"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"moveUp": "上移",
|
||||
"moveDown": "下移"
|
||||
},
|
||||
"menu": {
|
||||
"label": {
|
||||
"changePosition": "换位"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"menu": {
|
||||
"label": {
|
||||
"settings": "设置"
|
||||
}
|
||||
},
|
||||
"moveResize": {
|
||||
"field": {
|
||||
"width": {
|
||||
"label": "宽度"
|
||||
},
|
||||
"height": {
|
||||
"label": "高度"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"app": {
|
||||
"option": {
|
||||
"openInNewTab": {
|
||||
"label": "在新标签页中打开"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "显示布局",
|
||||
"option": {
|
||||
"row": {
|
||||
"label": "横向"
|
||||
},
|
||||
"column": {
|
||||
"label": "垂直"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"adsBlockedToday": "今日屏蔽",
|
||||
"adsBlockedTodayPercentage": "今日屏蔽",
|
||||
"dnsQueriesToday": "今日查询"
|
||||
}
|
||||
},
|
||||
"dnsHoleControls": {
|
||||
"description": "从您的面板控制 PiHole 或 AdGuard",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "显示布局",
|
||||
"option": {
|
||||
"row": {
|
||||
"label": "横向"
|
||||
},
|
||||
"column": {
|
||||
"label": "垂直"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
"set": "设置",
|
||||
"enabled": "已启用",
|
||||
"disabled": "已禁用",
|
||||
"hours": "时",
|
||||
"minutes": "分"
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
"description": "显示当前的日期和时间。",
|
||||
"option": {
|
||||
"timezone": {
|
||||
"label": "时区"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "笔记本",
|
||||
"option": {
|
||||
"showToolbar": {
|
||||
"label": "显示帮助您写下 Markdown 的工具栏"
|
||||
},
|
||||
"allowReadOnlyCheck": {
|
||||
"label": "允许在只读模式中检查"
|
||||
},
|
||||
"content": {
|
||||
"label": "笔记本的内容"
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
"bold": "粗体",
|
||||
"italic": "斜体",
|
||||
"strikethrough": "删除线",
|
||||
"underline": "下划线",
|
||||
"colorText": "文字颜色",
|
||||
"colorHighlight": "彩色高亮文本",
|
||||
"code": "代码",
|
||||
"clear": "清除格式",
|
||||
"heading": "标题 {level}",
|
||||
"align": "对齐文本: {position}",
|
||||
"blockquote": "引用",
|
||||
"horizontalLine": "横线",
|
||||
"bulletList": "符号列表",
|
||||
"orderedList": "顺序列表",
|
||||
"checkList": "检查列表",
|
||||
"increaseIndent": "增加缩进",
|
||||
"decreaseIndent": "减小缩进",
|
||||
"link": "链接",
|
||||
"unlink": "删除链接",
|
||||
"image": "嵌入图片",
|
||||
"addTable": "添加表格",
|
||||
"deleteTable": "删除表格",
|
||||
"colorCell": "单元格颜色",
|
||||
"mergeCell": "切换单元格合并",
|
||||
"addColumnLeft": "在前面添加列",
|
||||
"addColumnRight": "在后面添加列",
|
||||
"deleteColumn": "删除整列",
|
||||
"addRowTop": "在前面添加行",
|
||||
"addRowBelow": "在后面添加行",
|
||||
"deleteRow": "删除整行"
|
||||
},
|
||||
"align": {
|
||||
"left": "左边",
|
||||
"center": "居中",
|
||||
"right": "右边"
|
||||
},
|
||||
"popover": {
|
||||
"clearColor": "清除颜色",
|
||||
"source": "来源",
|
||||
"widthPlaceholder": "百分比或像素值",
|
||||
"columns": "列数",
|
||||
"rows": "行数",
|
||||
"width": "宽度",
|
||||
"height": "高度"
|
||||
}
|
||||
},
|
||||
"iframe": {
|
||||
"name": "iFrame",
|
||||
"description": "嵌入互联网上的任何内容。某些网站可能限制访问。",
|
||||
"option": {
|
||||
"embedUrl": {
|
||||
"label": "嵌入地址"
|
||||
},
|
||||
"allowFullScreen": {
|
||||
"label": "允许全屏"
|
||||
},
|
||||
"allowTransparency": {
|
||||
"label": "允许透明"
|
||||
},
|
||||
"allowScrolling": {
|
||||
"label": "允许滚动"
|
||||
},
|
||||
"allowPayment": {
|
||||
"label": "允许支付"
|
||||
},
|
||||
"allowAutoPlay": {
|
||||
"label": "允许自动播放"
|
||||
},
|
||||
"allowMicrophone": {
|
||||
"label": "允许麦克风"
|
||||
},
|
||||
"allowCamera": {
|
||||
"label": "允许摄像头"
|
||||
},
|
||||
"allowGeolocation": {
|
||||
"label": "允许地理位置"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"noBrowerSupport": "您的浏览器不支持 iframe。请更新您的浏览器。"
|
||||
}
|
||||
},
|
||||
"smartHome-entityState": {
|
||||
"option": {
|
||||
"entityId": {
|
||||
"label": "实体 ID"
|
||||
}
|
||||
}
|
||||
},
|
||||
"smartHome-executeAutomation": {
|
||||
"option": {
|
||||
"displayName": {
|
||||
"label": "显示名称"
|
||||
},
|
||||
"automationId": {
|
||||
"label": "自动化 ID"
|
||||
}
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
"name": "日历",
|
||||
"option": {
|
||||
"releaseType": {
|
||||
"label": "Radarr发布类型"
|
||||
}
|
||||
}
|
||||
},
|
||||
"weather": {
|
||||
"name": "天气",
|
||||
"description": "显示指定位置的当前天气信息。",
|
||||
"option": {
|
||||
"location": {
|
||||
"label": "天气位置"
|
||||
}
|
||||
},
|
||||
"kind": {
|
||||
"clear": "晴朗",
|
||||
"mainlyClear": "晴朗为主",
|
||||
"fog": "雾",
|
||||
"drizzle": "细雨",
|
||||
"freezingDrizzle": "冻毛毛雨",
|
||||
"rain": "雨",
|
||||
"freezingRain": "冻雨",
|
||||
"snowFall": "降雪",
|
||||
"snowGrains": "霰",
|
||||
"rainShowers": "阵雨",
|
||||
"snowShowers": "阵雪",
|
||||
"thunderstorm": "雷暴",
|
||||
"thunderstormWithHail": "雷暴夹冰雹",
|
||||
"unknown": "未知"
|
||||
}
|
||||
},
|
||||
"indexerManager": {
|
||||
"name": "索引器管理状态",
|
||||
"title": "索引器管理",
|
||||
"testAll": "测试全部"
|
||||
},
|
||||
"healthMonitoring": {
|
||||
"name": "系统健康监测",
|
||||
"description": "显示系统运行状况和状态的信息。",
|
||||
"option": {
|
||||
"fahrenheit": {
|
||||
"label": "CPU 温度(华氏度)"
|
||||
},
|
||||
"cpu": {
|
||||
"label": "显示CPU信息"
|
||||
},
|
||||
"memory": {
|
||||
"label": "显示内存信息"
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "显示文件系统信息"
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"available": "可用"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"search": "搜索",
|
||||
"table": {
|
||||
"header": {},
|
||||
"action": {},
|
||||
"population": {
|
||||
"fallback": "未知"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"video": {
|
||||
"name": "视频流",
|
||||
"description": "嵌入来自相机或网站的视频流或视频",
|
||||
"option": {
|
||||
"feedUrl": {
|
||||
"label": "订阅网址"
|
||||
},
|
||||
"hasAutoPlay": {
|
||||
"label": "自动播放"
|
||||
}
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"items": {
|
||||
"added": {
|
||||
"detailsTitle": "日期已添加"
|
||||
},
|
||||
"downSpeed": {
|
||||
"columnTitle": "下载",
|
||||
"detailsTitle": "下载速度"
|
||||
},
|
||||
"integration": {
|
||||
"columnTitle": "集成"
|
||||
},
|
||||
"progress": {
|
||||
"columnTitle": "进度"
|
||||
},
|
||||
"ratio": {
|
||||
"columnTitle": "分享率"
|
||||
},
|
||||
"state": {
|
||||
"columnTitle": "状态"
|
||||
},
|
||||
"upSpeed": {
|
||||
"columnTitle": "上传"
|
||||
}
|
||||
},
|
||||
"states": {
|
||||
"downloading": "正在下载",
|
||||
"queued": "排队中",
|
||||
"paused": "已暂停",
|
||||
"completed": "已完成",
|
||||
"unknown": "未知"
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"description": "查看 Overr 或 Jellyseerr 实例中的所有媒体请求列表",
|
||||
"option": {
|
||||
"linksTargetNewTab": {
|
||||
"label": "在新标签页中打开链接"
|
||||
}
|
||||
},
|
||||
"availability": {
|
||||
"unknown": "未知",
|
||||
"partiallyAvailable": "部分",
|
||||
"available": "可用"
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestStats": {
|
||||
"description": "您的媒体请求统计",
|
||||
"titles": {
|
||||
"stats": {
|
||||
"main": "媒体统计",
|
||||
"approved": "已经批准",
|
||||
"pending": "等待批准",
|
||||
"tv": "电视请求",
|
||||
"movie": "电影请求",
|
||||
"total": "请求总计"
|
||||
},
|
||||
"users": {
|
||||
"main": "用户排行"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"action": {
|
||||
"oldImport": {
|
||||
"form": {
|
||||
"apps": {
|
||||
"label": "应用"
|
||||
},
|
||||
"screenSize": {
|
||||
"option": {
|
||||
"sm": "小号",
|
||||
"md": "中号",
|
||||
"lg": "大号"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"backgroundImageAttachment": {
|
||||
"label": "背景图片附件"
|
||||
},
|
||||
"backgroundImageSize": {
|
||||
"label": "背景图像大小"
|
||||
},
|
||||
"primaryColor": {
|
||||
"label": "主体色"
|
||||
},
|
||||
"secondaryColor": {
|
||||
"label": "辅助色"
|
||||
},
|
||||
"customCss": {
|
||||
"description": "只推荐有经验的用户使用 CSS 自定义面板"
|
||||
},
|
||||
"name": {
|
||||
"label": "名称"
|
||||
},
|
||||
"isPublic": {
|
||||
"label": "公开"
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
"section": {
|
||||
"general": {
|
||||
"title": "通用"
|
||||
},
|
||||
"layout": {
|
||||
"title": "显示布局"
|
||||
},
|
||||
"background": {
|
||||
"title": "背景"
|
||||
},
|
||||
"access": {
|
||||
"permission": {
|
||||
"item": {
|
||||
"view": {
|
||||
"label": "查看面板"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dangerZone": {
|
||||
"title": "危险",
|
||||
"action": {
|
||||
"delete": {
|
||||
"confirm": {
|
||||
"title": "删除面板"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"management": {
|
||||
"navbar": {
|
||||
"items": {
|
||||
"home": "首页",
|
||||
"boards": "面板",
|
||||
"apps": "应用",
|
||||
"users": {
|
||||
"label": "用户",
|
||||
"items": {
|
||||
"manage": "管理中心",
|
||||
"invites": "邀请"
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"label": "工具",
|
||||
"items": {
|
||||
"docker": "Docker",
|
||||
"api": "API"
|
||||
}
|
||||
},
|
||||
"settings": "设置",
|
||||
"help": {
|
||||
"label": "帮助",
|
||||
"items": {
|
||||
"documentation": "文档",
|
||||
"discord": "Discord 社区"
|
||||
}
|
||||
},
|
||||
"about": "关于"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"home": {
|
||||
"statistic": {
|
||||
"board": "面板",
|
||||
"user": "用户",
|
||||
"invite": "邀请",
|
||||
"app": "应用"
|
||||
},
|
||||
"statisticLabel": {
|
||||
"boards": "面板"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"title": "您的面板",
|
||||
"action": {
|
||||
"settings": {
|
||||
"label": "设置"
|
||||
},
|
||||
"setHomeBoard": {
|
||||
"badge": {
|
||||
"label": "首页"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"label": "永久删除",
|
||||
"confirm": {
|
||||
"title": "删除面板"
|
||||
}
|
||||
}
|
||||
},
|
||||
"modal": {
|
||||
"createBoard": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "名称"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"setting": {
|
||||
"general": {
|
||||
"title": "通用",
|
||||
"item": {
|
||||
"firstDayOfWeek": "一周的第一天",
|
||||
"accessibility": "无障碍服务"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"title": "安全"
|
||||
},
|
||||
"board": {
|
||||
"title": "面板"
|
||||
}
|
||||
},
|
||||
"list": {
|
||||
"metaTitle": "管理用户",
|
||||
"title": "用户"
|
||||
},
|
||||
"create": {
|
||||
"metaTitle": "创建用户",
|
||||
"step": {
|
||||
"security": {
|
||||
"label": "安全"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invite": {
|
||||
"title": "管理用户邀请",
|
||||
"action": {
|
||||
"new": {
|
||||
"description": "过期后,邀请会失效,被邀请的收件人将无法创建账号。"
|
||||
},
|
||||
"copy": {
|
||||
"link": "邀请链接"
|
||||
},
|
||||
"delete": {
|
||||
"title": "删除邀请",
|
||||
"description": "你确定要删除这个邀请吗? 使用此链接的用户将不能再使用该链接创建账号。"
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"id": {
|
||||
"label": "ID"
|
||||
},
|
||||
"creator": {
|
||||
"label": "创建者"
|
||||
},
|
||||
"expirationDate": {
|
||||
"label": "过期时间"
|
||||
},
|
||||
"token": {
|
||||
"label": "Token"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"setting": {
|
||||
"general": {
|
||||
"title": "通用"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "设置"
|
||||
},
|
||||
"tool": {
|
||||
"tasks": {
|
||||
"status": {
|
||||
"running": "运行中",
|
||||
"error": "错误"
|
||||
},
|
||||
"job": {
|
||||
"mediaServer": {
|
||||
"label": "媒体服务"
|
||||
},
|
||||
"mediaRequests": {
|
||||
"label": "媒体请求"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api": {
|
||||
"title": "API",
|
||||
"tab": {
|
||||
"documentation": {
|
||||
"label": "文档"
|
||||
},
|
||||
"apiKey": {
|
||||
"table": {
|
||||
"header": {
|
||||
"id": "ID"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"docker": {
|
||||
"title": "容器",
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "名称"
|
||||
},
|
||||
"state": {
|
||||
"label": "状态",
|
||||
"option": {
|
||||
"created": "已创建",
|
||||
"running": "运行中",
|
||||
"paused": "已暂停",
|
||||
"restarting": "正在重启",
|
||||
"removing": "删除中"
|
||||
}
|
||||
},
|
||||
"containerImage": {
|
||||
"label": "镜像"
|
||||
},
|
||||
"ports": {
|
||||
"label": "端口"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"start": {
|
||||
"label": "开始"
|
||||
},
|
||||
"stop": {
|
||||
"label": "停止"
|
||||
},
|
||||
"restart": {
|
||||
"label": "重启"
|
||||
},
|
||||
"remove": {
|
||||
"label": "删除"
|
||||
}
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"tab": {
|
||||
"user": "用户"
|
||||
},
|
||||
"field": {
|
||||
"user": {
|
||||
"label": "用户"
|
||||
}
|
||||
}
|
||||
},
|
||||
"navigationStructure": {
|
||||
"manage": {
|
||||
"label": "管理中心",
|
||||
"boards": {
|
||||
"label": "面板"
|
||||
},
|
||||
"integrations": {
|
||||
"edit": {
|
||||
"label": "编辑"
|
||||
}
|
||||
},
|
||||
"search-engines": {
|
||||
"edit": {
|
||||
"label": "编辑"
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"label": "应用",
|
||||
"edit": {
|
||||
"label": "编辑"
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"label": "用户",
|
||||
"create": {
|
||||
"label": "创建"
|
||||
},
|
||||
"general": "通用",
|
||||
"security": "安全",
|
||||
"board": "面板",
|
||||
"invites": {
|
||||
"label": "邀请"
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"label": "工具",
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"label": "设置"
|
||||
},
|
||||
"about": {
|
||||
"label": "关于"
|
||||
}
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"mode": {
|
||||
"appIntegrationBoard": {
|
||||
"group": {
|
||||
"app": {
|
||||
"title": "应用"
|
||||
},
|
||||
"board": {
|
||||
"title": "面板"
|
||||
}
|
||||
}
|
||||
},
|
||||
"external": {
|
||||
"group": {
|
||||
"searchEngine": {
|
||||
"option": {
|
||||
"torrent": {
|
||||
"name": "Torrents"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"help": {
|
||||
"group": {
|
||||
"help": {
|
||||
"title": "帮助",
|
||||
"option": {
|
||||
"documentation": {
|
||||
"label": "文档"
|
||||
},
|
||||
"discord": {
|
||||
"label": "Discord 社区"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"group": {
|
||||
"page": {
|
||||
"option": {
|
||||
"manageUser": {
|
||||
"label": "管理用户"
|
||||
},
|
||||
"about": {
|
||||
"label": "关于"
|
||||
},
|
||||
"preferences": {
|
||||
"label": "您的首选项"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"userGroup": {
|
||||
"group": {
|
||||
"user": {
|
||||
"title": "用户"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"engine": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "名称"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
911
packages/translation/src/lang/cs.json
Normal file
911
packages/translation/src/lang/cs.json
Normal file
@@ -0,0 +1,911 @@
|
||||
{
|
||||
"user": {
|
||||
"title": "Uživatelé",
|
||||
"name": "Uživatel",
|
||||
"field": {
|
||||
"email": {
|
||||
"label": "E-mail"
|
||||
},
|
||||
"username": {
|
||||
"label": "Uživatelské jméno"
|
||||
},
|
||||
"password": {
|
||||
"label": "Heslo",
|
||||
"requirement": {
|
||||
"lowercase": "Obsahuje malé písmeno",
|
||||
"uppercase": "Obsahuje velké písmeno",
|
||||
"number": "Obsahuje číslo"
|
||||
}
|
||||
},
|
||||
"passwordConfirm": {
|
||||
"label": "Potvrďte heslo"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"login": {
|
||||
"label": "Přihlásit se"
|
||||
},
|
||||
"register": {
|
||||
"label": "Vytvořit účet",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "Účet byl vytvořen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"create": "Vytvořit uživatele"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"field": {
|
||||
"name": "Název"
|
||||
},
|
||||
"permission": {
|
||||
"admin": {
|
||||
"title": "Administrátor"
|
||||
},
|
||||
"board": {
|
||||
"title": "Plochy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"page": {
|
||||
"list": {
|
||||
"title": "Aplikace"
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Název"
|
||||
}
|
||||
}
|
||||
},
|
||||
"integration": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Název"
|
||||
}
|
||||
},
|
||||
"testConnection": {
|
||||
"notification": {
|
||||
"invalidUrl": {
|
||||
"title": "Neplatná URL adresa"
|
||||
}
|
||||
}
|
||||
},
|
||||
"secrets": {
|
||||
"kind": {
|
||||
"username": {
|
||||
"label": "Uživatelské jméno"
|
||||
},
|
||||
"password": {
|
||||
"label": "Heslo",
|
||||
"newLabel": "Nové heslo"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"field": {
|
||||
"name": "Název",
|
||||
"size": "Velikost",
|
||||
"creator": "Vytvořil/a"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"error": "Chyba",
|
||||
"action": {
|
||||
"add": "Přidat",
|
||||
"apply": "Použít",
|
||||
"create": "Vytvořit",
|
||||
"edit": "Upravit",
|
||||
"insert": "Vložit",
|
||||
"remove": "Odstranit",
|
||||
"save": "Uložit",
|
||||
"saveChanges": "Uložit změny",
|
||||
"cancel": "Zrušit",
|
||||
"delete": "Odstranit",
|
||||
"confirm": "Potvrdit",
|
||||
"previous": "Zpět",
|
||||
"next": "Další",
|
||||
"tryAgain": "Zkusit znovu"
|
||||
},
|
||||
"information": {
|
||||
"hours": "Hodin",
|
||||
"minutes": "Minut"
|
||||
},
|
||||
"userAvatar": {
|
||||
"menu": {
|
||||
"preferences": "Vaše předvolby",
|
||||
"login": "Přihlásit se"
|
||||
}
|
||||
},
|
||||
"dangerZone": "Nebezpečná zóna",
|
||||
"noResults": "Nebyly nalezeny žádné výsledky",
|
||||
"zod": {
|
||||
"errors": {
|
||||
"default": "Toto pole je neplatné",
|
||||
"required": "Toto pole je nutné vyplnit",
|
||||
"string": {
|
||||
"startsWith": "Toto pole musí začínat {startsWith}",
|
||||
"endsWith": "Toto pole musí končit {endsWith}",
|
||||
"includes": "Toto pole musí obsahovat {includes}"
|
||||
},
|
||||
"tooSmall": {
|
||||
"string": "Toto pole musí obsahovat alespoň {minimum} znaků",
|
||||
"number": "Toto pole musí být větší nebo rovno {minimum}"
|
||||
},
|
||||
"tooBig": {
|
||||
"string": "Toto pole může být maximálně {maximum} znaků dlouhé",
|
||||
"number": "Toto pole musí být menší nebo rovno {maximum}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"section": {
|
||||
"category": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Název"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"moveUp": "Posunout nahoru",
|
||||
"moveDown": "Posunout dolů"
|
||||
},
|
||||
"menu": {
|
||||
"label": {
|
||||
"changePosition": "Změnit pozici"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"menu": {
|
||||
"label": {
|
||||
"settings": "Nastavení"
|
||||
}
|
||||
},
|
||||
"moveResize": {
|
||||
"field": {
|
||||
"width": {
|
||||
"label": "Šířka"
|
||||
},
|
||||
"height": {
|
||||
"label": "Výška"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"app": {
|
||||
"option": {
|
||||
"openInNewTab": {
|
||||
"label": "Otevřít na nové kartě"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "Rozložení",
|
||||
"option": {
|
||||
"row": {
|
||||
"label": "Horizontální"
|
||||
},
|
||||
"column": {
|
||||
"label": "Vertikální"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"adsBlockedToday": "Zablokováno dnes",
|
||||
"adsBlockedTodayPercentage": "Zablokováno dnes",
|
||||
"dnsQueriesToday": "Dotazů dnes"
|
||||
}
|
||||
},
|
||||
"dnsHoleControls": {
|
||||
"description": "Ovládejte PiHole nebo AdGuard z plochy",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "Rozložení",
|
||||
"option": {
|
||||
"row": {
|
||||
"label": "Horizontální"
|
||||
},
|
||||
"column": {
|
||||
"label": "Vertikální"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
"set": "Nastavit",
|
||||
"enabled": "Zapnuto",
|
||||
"disabled": "Vypnuto",
|
||||
"hours": "Hodin",
|
||||
"minutes": "Minut"
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
"description": "Zobrazuje aktuální datum a čas.",
|
||||
"option": {
|
||||
"timezone": {
|
||||
"label": "Časové pásmo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Zápisník",
|
||||
"option": {
|
||||
"showToolbar": {
|
||||
"label": "Zobrazovat panel nástroju, který Vám pomůže s formátováním textu"
|
||||
},
|
||||
"allowReadOnlyCheck": {
|
||||
"label": "Povolit kontrolu v režimu pouze pro čtení"
|
||||
},
|
||||
"content": {
|
||||
"label": "Obsah zápisníku"
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
"bold": "Tučné",
|
||||
"italic": "Kurzíva",
|
||||
"strikethrough": "Přeškrtnuté",
|
||||
"underline": "Podtržení",
|
||||
"colorText": "Barva písma",
|
||||
"colorHighlight": "Barevné zvýraznění textu",
|
||||
"code": "Kód",
|
||||
"clear": "Vymazat formátování",
|
||||
"heading": "Nadpis {level}",
|
||||
"align": "Zarovnat text: {position}",
|
||||
"blockquote": "Citace",
|
||||
"horizontalLine": "Vodorovná čára",
|
||||
"bulletList": "Odrážkový seznam",
|
||||
"orderedList": "Číslovaný seznam",
|
||||
"checkList": "Zaškrtávací seznam",
|
||||
"increaseIndent": "Zvětšit odsazení",
|
||||
"decreaseIndent": "Zmenšit odsazení",
|
||||
"link": "Vložit odkaz",
|
||||
"unlink": "Odstranit odkaz",
|
||||
"image": "Vložit obrázek",
|
||||
"addTable": "Přidat tabulku",
|
||||
"deleteTable": "Odstranit tabulku",
|
||||
"colorCell": "Barva výplně",
|
||||
"mergeCell": "Sloučit buňky",
|
||||
"addColumnLeft": "Přidat sloupec před",
|
||||
"addColumnRight": "Přidat sloupec za",
|
||||
"deleteColumn": "Odstranit sloupec",
|
||||
"addRowTop": "Přidat řádek nad",
|
||||
"addRowBelow": "Přidat řádek pod",
|
||||
"deleteRow": "Odstranit řádek"
|
||||
},
|
||||
"align": {
|
||||
"left": "Vlevo",
|
||||
"center": "Střed",
|
||||
"right": "Vpravo"
|
||||
},
|
||||
"popover": {
|
||||
"clearColor": "Odstranit barvu",
|
||||
"source": "Zdroj",
|
||||
"widthPlaceholder": "Hodnota v % nebo pixelech",
|
||||
"columns": "Sloupce",
|
||||
"rows": "Řádky",
|
||||
"width": "Šířka",
|
||||
"height": "Výška"
|
||||
}
|
||||
},
|
||||
"iframe": {
|
||||
"name": "iFrame",
|
||||
"description": "Vložte jakýkoli obsah z internetu. Některé webové stránky mohou omezit přístup.",
|
||||
"option": {
|
||||
"embedUrl": {
|
||||
"label": "Embed URL"
|
||||
},
|
||||
"allowFullScreen": {
|
||||
"label": "Povolit celou obrazovku"
|
||||
},
|
||||
"allowTransparency": {
|
||||
"label": "Povolit průhlednost"
|
||||
},
|
||||
"allowScrolling": {
|
||||
"label": "Povolit posouvání"
|
||||
},
|
||||
"allowPayment": {
|
||||
"label": "Povolit platbu"
|
||||
},
|
||||
"allowAutoPlay": {
|
||||
"label": "Povolit automatické přehrávání"
|
||||
},
|
||||
"allowMicrophone": {
|
||||
"label": "Povolit mikrofon"
|
||||
},
|
||||
"allowCamera": {
|
||||
"label": "Povolit kameru"
|
||||
},
|
||||
"allowGeolocation": {
|
||||
"label": "Povolit polohu"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"noBrowerSupport": "Váš prohlížeč nepodporuje iFrame. Aktualizujte, prosím, svůj prohlížeč."
|
||||
}
|
||||
},
|
||||
"smartHome-entityState": {
|
||||
"option": {
|
||||
"entityId": {
|
||||
"label": "ID entity"
|
||||
}
|
||||
}
|
||||
},
|
||||
"smartHome-executeAutomation": {
|
||||
"option": {
|
||||
"displayName": {
|
||||
"label": "Zobrazovaný název"
|
||||
},
|
||||
"automationId": {
|
||||
"label": "ID automatizace"
|
||||
}
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
"name": "Kalendář",
|
||||
"option": {
|
||||
"releaseType": {
|
||||
"label": "Typ vydání filmu pro Radarr"
|
||||
}
|
||||
}
|
||||
},
|
||||
"weather": {
|
||||
"name": "Počasí",
|
||||
"description": "Zobrazuje aktuální informace o počasí na nastaveném místě.",
|
||||
"option": {
|
||||
"location": {
|
||||
"label": "Lokalita pro počasí"
|
||||
}
|
||||
},
|
||||
"kind": {
|
||||
"clear": "Jasno",
|
||||
"mainlyClear": "Převážně jasno",
|
||||
"fog": "Mlha",
|
||||
"drizzle": "Mrholení",
|
||||
"freezingDrizzle": "Mrznoucí mrholení",
|
||||
"rain": "Déšť",
|
||||
"freezingRain": "Mrznoucí déšť",
|
||||
"snowFall": "Sněžení",
|
||||
"snowGrains": "Sněhová zrna",
|
||||
"rainShowers": "Dešťové přeháňky",
|
||||
"snowShowers": "Sněhové přeháňky",
|
||||
"thunderstorm": "Bouřka",
|
||||
"thunderstormWithHail": "Bouřka s krupobitím",
|
||||
"unknown": "Neznámý"
|
||||
}
|
||||
},
|
||||
"indexerManager": {
|
||||
"name": "Stav správce indexeru",
|
||||
"title": "Správce indexeru",
|
||||
"testAll": "Otestovat vše"
|
||||
},
|
||||
"healthMonitoring": {
|
||||
"name": "Monitorování stavu systému",
|
||||
"description": "Zobrazuje informace o stavu a kondici Vašeho systému (systémů).",
|
||||
"option": {
|
||||
"fahrenheit": {
|
||||
"label": "Teplota CPU ve stupních Fahrenheit"
|
||||
},
|
||||
"cpu": {
|
||||
"label": "Zobrazit info o CPU"
|
||||
},
|
||||
"memory": {
|
||||
"label": "Zobrazit informace o paměti"
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Zobrazit informace o souborovém systému"
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"available": "K dispozici"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"search": "Vyhledat",
|
||||
"table": {
|
||||
"header": {},
|
||||
"action": {},
|
||||
"population": {
|
||||
"fallback": "Neznámý"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"video": {
|
||||
"name": "Streamování videa",
|
||||
"description": "Vložte video stream nebo video z kamery nebo webové stránky",
|
||||
"option": {
|
||||
"feedUrl": {
|
||||
"label": "URL zdroje"
|
||||
},
|
||||
"hasAutoPlay": {
|
||||
"label": "Automatické přehrávání"
|
||||
}
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"items": {
|
||||
"added": {
|
||||
"detailsTitle": "Datum přidání"
|
||||
},
|
||||
"downSpeed": {
|
||||
"columnTitle": "Stahování",
|
||||
"detailsTitle": "Rychlost stahování"
|
||||
},
|
||||
"integration": {
|
||||
"columnTitle": "Integrace"
|
||||
},
|
||||
"progress": {
|
||||
"columnTitle": "Postup"
|
||||
},
|
||||
"ratio": {
|
||||
"columnTitle": "Poměr"
|
||||
},
|
||||
"state": {
|
||||
"columnTitle": "Status"
|
||||
},
|
||||
"upSpeed": {
|
||||
"columnTitle": "Nahrávání"
|
||||
}
|
||||
},
|
||||
"states": {
|
||||
"downloading": "Stahování",
|
||||
"queued": "Ve frontě",
|
||||
"paused": "Pozastaveno",
|
||||
"completed": "Hotovo",
|
||||
"unknown": "Neznámý"
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"description": "Podívejte se na seznam všech požadavků na média z vaší instance Overseerr nebo Jellyseerr",
|
||||
"option": {
|
||||
"linksTargetNewTab": {
|
||||
"label": "Otevírat odkazy v nové kartě"
|
||||
}
|
||||
},
|
||||
"availability": {
|
||||
"unknown": "Neznámý",
|
||||
"partiallyAvailable": "Částečně dostupné",
|
||||
"available": "K dispozici"
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestStats": {
|
||||
"description": "Statistiky vašich požadavků na média",
|
||||
"titles": {
|
||||
"stats": {
|
||||
"main": "Statistiky médií",
|
||||
"approved": "Již schváleno",
|
||||
"pending": "Čeká na schválení",
|
||||
"tv": "Požadavky seriálů",
|
||||
"movie": "Požadavky filmů",
|
||||
"total": "Celkem"
|
||||
},
|
||||
"users": {
|
||||
"main": "Top uživatelé"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"action": {
|
||||
"oldImport": {
|
||||
"form": {
|
||||
"apps": {
|
||||
"label": "Aplikace"
|
||||
},
|
||||
"screenSize": {
|
||||
"option": {
|
||||
"sm": "Malé",
|
||||
"md": "Střední",
|
||||
"lg": "Velké"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"backgroundImageAttachment": {
|
||||
"label": "Příloha obrázku na pozadí"
|
||||
},
|
||||
"backgroundImageSize": {
|
||||
"label": "Velikost obrázku na pozadí"
|
||||
},
|
||||
"primaryColor": {
|
||||
"label": "Primární barva"
|
||||
},
|
||||
"secondaryColor": {
|
||||
"label": "Doplňková barva"
|
||||
},
|
||||
"customCss": {
|
||||
"description": "Dále si můžete přizpůsobit ovládací panel pomocí CSS, doporučujeme pouze zkušeným uživatelům"
|
||||
},
|
||||
"name": {
|
||||
"label": "Název"
|
||||
},
|
||||
"isPublic": {
|
||||
"label": "Veřejné"
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
"section": {
|
||||
"general": {
|
||||
"title": "Obecné"
|
||||
},
|
||||
"layout": {
|
||||
"title": "Rozložení"
|
||||
},
|
||||
"background": {
|
||||
"title": "Pozadí"
|
||||
},
|
||||
"access": {
|
||||
"permission": {
|
||||
"item": {
|
||||
"view": {
|
||||
"label": "Zobrazit plochu"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dangerZone": {
|
||||
"title": "Nebezpečná zóna",
|
||||
"action": {
|
||||
"delete": {
|
||||
"confirm": {
|
||||
"title": "Odstranit plochu"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"management": {
|
||||
"navbar": {
|
||||
"items": {
|
||||
"home": "Domovská stránka",
|
||||
"boards": "Plochy",
|
||||
"apps": "Aplikace",
|
||||
"users": {
|
||||
"label": "Uživatelé",
|
||||
"items": {
|
||||
"manage": "Spravovat",
|
||||
"invites": "Pozvánky"
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"label": "Nástroje",
|
||||
"items": {
|
||||
"docker": "Docker",
|
||||
"api": "API"
|
||||
}
|
||||
},
|
||||
"settings": "Nastavení",
|
||||
"help": {
|
||||
"label": "Nápověda",
|
||||
"items": {
|
||||
"documentation": "Dokumentace",
|
||||
"discord": "Komunitní Discord"
|
||||
}
|
||||
},
|
||||
"about": "O aplikaci"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"home": {
|
||||
"statistic": {
|
||||
"board": "Plochy",
|
||||
"user": "Uživatelé",
|
||||
"invite": "Pozvánky",
|
||||
"app": "Aplikace"
|
||||
},
|
||||
"statisticLabel": {
|
||||
"boards": "Plochy"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"title": "Vaše plochy",
|
||||
"action": {
|
||||
"settings": {
|
||||
"label": "Nastavení"
|
||||
},
|
||||
"setHomeBoard": {
|
||||
"badge": {
|
||||
"label": "Domovská stránka"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"label": "Trvale smazat",
|
||||
"confirm": {
|
||||
"title": "Odstranit plochu"
|
||||
}
|
||||
}
|
||||
},
|
||||
"modal": {
|
||||
"createBoard": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Název"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"setting": {
|
||||
"general": {
|
||||
"title": "Obecné",
|
||||
"item": {
|
||||
"firstDayOfWeek": "První den v týdnu",
|
||||
"accessibility": "Přístupnost"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"title": "Bezpečnost"
|
||||
},
|
||||
"board": {
|
||||
"title": "Plochy"
|
||||
}
|
||||
},
|
||||
"list": {
|
||||
"metaTitle": "Správa uživatelů",
|
||||
"title": "Uživatelé"
|
||||
},
|
||||
"create": {
|
||||
"metaTitle": "Vytvořit uživatele",
|
||||
"step": {
|
||||
"security": {
|
||||
"label": "Bezpečnost"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invite": {
|
||||
"title": "Správa pozvánek uživatelů",
|
||||
"action": {
|
||||
"new": {
|
||||
"description": "Po vypršení platnosti pozvánka přestane být platná a příjemce pozvánky si nebude moci vytvořit účet."
|
||||
},
|
||||
"copy": {
|
||||
"link": "Odkaz na pozvánku"
|
||||
},
|
||||
"delete": {
|
||||
"title": "Odstranit pozvánku",
|
||||
"description": "Jste si jisti, že chcete tuto pozvánku smazat? Uživatelé s tímto odkazem již nebudou moci pomocí tohoto odkazu vytvořit účet."
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"id": {
|
||||
"label": "ID"
|
||||
},
|
||||
"creator": {
|
||||
"label": "Vytvořil/a"
|
||||
},
|
||||
"expirationDate": {
|
||||
"label": "Datum konce platnosti"
|
||||
},
|
||||
"token": {
|
||||
"label": "Token"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"setting": {
|
||||
"general": {
|
||||
"title": "Obecné"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Nastavení"
|
||||
},
|
||||
"tool": {
|
||||
"tasks": {
|
||||
"status": {
|
||||
"running": "Běží",
|
||||
"error": "Chyba"
|
||||
},
|
||||
"job": {
|
||||
"mediaServer": {
|
||||
"label": "Mediální server"
|
||||
},
|
||||
"mediaRequests": {
|
||||
"label": "Žádosti o média"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api": {
|
||||
"title": "API",
|
||||
"tab": {
|
||||
"documentation": {
|
||||
"label": "Dokumentace"
|
||||
},
|
||||
"apiKey": {
|
||||
"table": {
|
||||
"header": {
|
||||
"id": "ID"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"docker": {
|
||||
"title": "Kontejnery",
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Název"
|
||||
},
|
||||
"state": {
|
||||
"label": "Status",
|
||||
"option": {
|
||||
"created": "Vytvořený",
|
||||
"running": "Běží",
|
||||
"paused": "Pozastaveno",
|
||||
"restarting": "Restartování",
|
||||
"removing": "Odstraňování"
|
||||
}
|
||||
},
|
||||
"containerImage": {
|
||||
"label": "Obraz"
|
||||
},
|
||||
"ports": {
|
||||
"label": "Porty"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"start": {
|
||||
"label": "Spustit"
|
||||
},
|
||||
"stop": {
|
||||
"label": "Zastavit"
|
||||
},
|
||||
"restart": {
|
||||
"label": "Restartovat"
|
||||
},
|
||||
"remove": {
|
||||
"label": "Odstranit"
|
||||
}
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"tab": {
|
||||
"user": "Uživatelé"
|
||||
},
|
||||
"field": {
|
||||
"user": {
|
||||
"label": "Uživatel"
|
||||
}
|
||||
}
|
||||
},
|
||||
"navigationStructure": {
|
||||
"manage": {
|
||||
"label": "Spravovat",
|
||||
"boards": {
|
||||
"label": "Plochy"
|
||||
},
|
||||
"integrations": {
|
||||
"edit": {
|
||||
"label": "Upravit"
|
||||
}
|
||||
},
|
||||
"search-engines": {
|
||||
"edit": {
|
||||
"label": "Upravit"
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"label": "Aplikace",
|
||||
"edit": {
|
||||
"label": "Upravit"
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"label": "Uživatelé",
|
||||
"create": {
|
||||
"label": "Vytvořit"
|
||||
},
|
||||
"general": "Obecné",
|
||||
"security": "Bezpečnost",
|
||||
"board": "Plochy",
|
||||
"invites": {
|
||||
"label": "Pozvánky"
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"label": "Nástroje",
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"label": "Nastavení"
|
||||
},
|
||||
"about": {
|
||||
"label": "O aplikaci"
|
||||
}
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"mode": {
|
||||
"appIntegrationBoard": {
|
||||
"group": {
|
||||
"app": {
|
||||
"title": "Aplikace"
|
||||
},
|
||||
"board": {
|
||||
"title": "Plochy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"external": {
|
||||
"group": {
|
||||
"searchEngine": {
|
||||
"option": {
|
||||
"torrent": {
|
||||
"name": "Torrenty"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"help": {
|
||||
"group": {
|
||||
"help": {
|
||||
"title": "Nápověda",
|
||||
"option": {
|
||||
"documentation": {
|
||||
"label": "Dokumentace"
|
||||
},
|
||||
"discord": {
|
||||
"label": "Komunitní Discord"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"group": {
|
||||
"page": {
|
||||
"option": {
|
||||
"manageUser": {
|
||||
"label": "Správa uživatelů"
|
||||
},
|
||||
"about": {
|
||||
"label": "O aplikaci"
|
||||
},
|
||||
"preferences": {
|
||||
"label": "Vaše předvolby"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"userGroup": {
|
||||
"group": {
|
||||
"user": {
|
||||
"title": "Uživatelé"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"engine": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Název"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
911
packages/translation/src/lang/da.json
Normal file
911
packages/translation/src/lang/da.json
Normal file
@@ -0,0 +1,911 @@
|
||||
{
|
||||
"user": {
|
||||
"title": "Brugere",
|
||||
"name": "Bruger",
|
||||
"field": {
|
||||
"email": {
|
||||
"label": "E-mail"
|
||||
},
|
||||
"username": {
|
||||
"label": "Brugernavn"
|
||||
},
|
||||
"password": {
|
||||
"label": "Adgangskode",
|
||||
"requirement": {
|
||||
"lowercase": "Inkluderer små bogstaver",
|
||||
"uppercase": "Inkluderer store bogstaver",
|
||||
"number": "Inkluderer nummer"
|
||||
}
|
||||
},
|
||||
"passwordConfirm": {
|
||||
"label": "Bekræft kodeord"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"login": {
|
||||
"label": "Log ind"
|
||||
},
|
||||
"register": {
|
||||
"label": "Opret konto",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "Konto oprettet"
|
||||
}
|
||||
}
|
||||
},
|
||||
"create": "Opret bruger"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"field": {
|
||||
"name": "Navn"
|
||||
},
|
||||
"permission": {
|
||||
"admin": {
|
||||
"title": "Admin"
|
||||
},
|
||||
"board": {
|
||||
"title": "Boards"
|
||||
}
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"page": {
|
||||
"list": {
|
||||
"title": "Apps"
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Navn"
|
||||
}
|
||||
}
|
||||
},
|
||||
"integration": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Navn"
|
||||
}
|
||||
},
|
||||
"testConnection": {
|
||||
"notification": {
|
||||
"invalidUrl": {
|
||||
"title": "Ugyldig URL"
|
||||
}
|
||||
}
|
||||
},
|
||||
"secrets": {
|
||||
"kind": {
|
||||
"username": {
|
||||
"label": "Brugernavn"
|
||||
},
|
||||
"password": {
|
||||
"label": "Adgangskode",
|
||||
"newLabel": "Nyt kodeord"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"field": {
|
||||
"name": "Navn",
|
||||
"size": "Størrelse",
|
||||
"creator": "Skaber"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"error": "Fejl",
|
||||
"action": {
|
||||
"add": "Tilføj",
|
||||
"apply": "Anvend",
|
||||
"create": "Opret",
|
||||
"edit": "Rediger",
|
||||
"insert": "Indsæt",
|
||||
"remove": "Fjern",
|
||||
"save": "Gem",
|
||||
"saveChanges": "Gem ændringer",
|
||||
"cancel": "Annuller",
|
||||
"delete": "Slet",
|
||||
"confirm": "Bekræft",
|
||||
"previous": "Forrige",
|
||||
"next": "Næste",
|
||||
"tryAgain": "Prøv igen"
|
||||
},
|
||||
"information": {
|
||||
"hours": "Timer",
|
||||
"minutes": "Minutter"
|
||||
},
|
||||
"userAvatar": {
|
||||
"menu": {
|
||||
"preferences": "Dine indstillinger",
|
||||
"login": "Log ind"
|
||||
}
|
||||
},
|
||||
"dangerZone": "Farezone",
|
||||
"noResults": "Ingen resultater fundet",
|
||||
"zod": {
|
||||
"errors": {
|
||||
"default": "Dette felt er ugyldigt",
|
||||
"required": "Dette felt er påkrævet",
|
||||
"string": {
|
||||
"startsWith": "Dette felt skal starte med {startsWith}",
|
||||
"endsWith": "Dette felt skal slutte med {endsWith}",
|
||||
"includes": "Dette felt skal indeholde {includes}"
|
||||
},
|
||||
"tooSmall": {
|
||||
"string": "Dette felt skal være mindst {minimum} tegn langt",
|
||||
"number": "Dette felt skal være større end eller lig med {minimum}"
|
||||
},
|
||||
"tooBig": {
|
||||
"string": "Dette felt må højst være på {maximum} tegn",
|
||||
"number": "Dette felt skal være mindre end eller lig med {maximum}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"section": {
|
||||
"category": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Navn"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"moveUp": "Flyt op",
|
||||
"moveDown": "Flyt ned"
|
||||
},
|
||||
"menu": {
|
||||
"label": {
|
||||
"changePosition": "Ændre placering"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"menu": {
|
||||
"label": {
|
||||
"settings": "Indstillinger"
|
||||
}
|
||||
},
|
||||
"moveResize": {
|
||||
"field": {
|
||||
"width": {
|
||||
"label": "Bredde"
|
||||
},
|
||||
"height": {
|
||||
"label": "Højde"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"app": {
|
||||
"option": {
|
||||
"openInNewTab": {
|
||||
"label": "Åbn i nyt faneblad"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "Layout",
|
||||
"option": {
|
||||
"row": {
|
||||
"label": "Horisontal"
|
||||
},
|
||||
"column": {
|
||||
"label": "Vertikal"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"adsBlockedToday": "Blokeret i dag",
|
||||
"adsBlockedTodayPercentage": "Blokeret i dag",
|
||||
"dnsQueriesToday": "Forespørgsler i dag"
|
||||
}
|
||||
},
|
||||
"dnsHoleControls": {
|
||||
"description": "Kontroller PiHole eller AdGuard fra dit dashboard",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "Layout",
|
||||
"option": {
|
||||
"row": {
|
||||
"label": "Horisontal"
|
||||
},
|
||||
"column": {
|
||||
"label": "Vertikal"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
"set": "Indstil",
|
||||
"enabled": "Aktiveret",
|
||||
"disabled": "Deaktiveret",
|
||||
"hours": "Timer",
|
||||
"minutes": "Minutter"
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
"description": "Viser aktuel dag og klokkeslæt.",
|
||||
"option": {
|
||||
"timezone": {
|
||||
"label": "Tidszone"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Notesbog",
|
||||
"option": {
|
||||
"showToolbar": {
|
||||
"label": "Vis værktøjslinjen, der hjælper dig med at skrive markdown"
|
||||
},
|
||||
"allowReadOnlyCheck": {
|
||||
"label": "Tillad tjek i skrivebeskyttet tilstand"
|
||||
},
|
||||
"content": {
|
||||
"label": "Indholdet af notesbogen"
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
"bold": "Fed",
|
||||
"italic": "Kursiv",
|
||||
"strikethrough": "Gennemstreget",
|
||||
"underline": "Understreget",
|
||||
"colorText": "Tekst i farver",
|
||||
"colorHighlight": "Farvet fremhævning af tekst",
|
||||
"code": "Kode",
|
||||
"clear": "Ryd formatering",
|
||||
"heading": "Overskrift {level}",
|
||||
"align": "Juster tekst: {position}",
|
||||
"blockquote": "Citatblok",
|
||||
"horizontalLine": "Horisontal linje",
|
||||
"bulletList": "Punktopstillet liste",
|
||||
"orderedList": "Sorteret liste",
|
||||
"checkList": "Tjekliste",
|
||||
"increaseIndent": "Forøg indrykning",
|
||||
"decreaseIndent": "Formindsk indrykning",
|
||||
"link": "Link",
|
||||
"unlink": "Fjern link",
|
||||
"image": "Integrer billede",
|
||||
"addTable": "Tilføj tabel",
|
||||
"deleteTable": "Slet tabel",
|
||||
"colorCell": "Farvecelle",
|
||||
"mergeCell": "Slå cellefletning til/fra",
|
||||
"addColumnLeft": "Tilføj kolonne før",
|
||||
"addColumnRight": "Tilføj kolonne efter",
|
||||
"deleteColumn": "Slet kolonne",
|
||||
"addRowTop": "Tilføj række før",
|
||||
"addRowBelow": "Tilføj række efter",
|
||||
"deleteRow": "Slet række"
|
||||
},
|
||||
"align": {
|
||||
"left": "Venstre",
|
||||
"center": "Centrer",
|
||||
"right": "Højre"
|
||||
},
|
||||
"popover": {
|
||||
"clearColor": "Ryd farve",
|
||||
"source": "Kilde",
|
||||
"widthPlaceholder": "Værdi i % eller pixels",
|
||||
"columns": "Kolonner",
|
||||
"rows": "Rækker",
|
||||
"width": "Bredde",
|
||||
"height": "Højde"
|
||||
}
|
||||
},
|
||||
"iframe": {
|
||||
"name": "indlejret dokument (iframe)",
|
||||
"description": "Indlejr ethvert indhold fra internettet. Nogle websteder kan begrænse adgang.",
|
||||
"option": {
|
||||
"embedUrl": {
|
||||
"label": "Indlejr URL"
|
||||
},
|
||||
"allowFullScreen": {
|
||||
"label": "Tillad fuld skærm"
|
||||
},
|
||||
"allowTransparency": {
|
||||
"label": "Tillad gennemsigtighed"
|
||||
},
|
||||
"allowScrolling": {
|
||||
"label": "Tillad rulning"
|
||||
},
|
||||
"allowPayment": {
|
||||
"label": "Tillad betaling"
|
||||
},
|
||||
"allowAutoPlay": {
|
||||
"label": "Tillad automatisk afspilning"
|
||||
},
|
||||
"allowMicrophone": {
|
||||
"label": "Tillad mikrofon"
|
||||
},
|
||||
"allowCamera": {
|
||||
"label": "Tillad kamera"
|
||||
},
|
||||
"allowGeolocation": {
|
||||
"label": "Tillad geolokalisering"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"noBrowerSupport": "Din browser understøtter ikke iframes. Opdater venligst din browser."
|
||||
}
|
||||
},
|
||||
"smartHome-entityState": {
|
||||
"option": {
|
||||
"entityId": {
|
||||
"label": "Entitet ID"
|
||||
}
|
||||
}
|
||||
},
|
||||
"smartHome-executeAutomation": {
|
||||
"option": {
|
||||
"displayName": {
|
||||
"label": "Visningsnavn"
|
||||
},
|
||||
"automationId": {
|
||||
"label": "Automatiserings ID"
|
||||
}
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
"name": "Kalender",
|
||||
"option": {
|
||||
"releaseType": {
|
||||
"label": "Radarr udgivelsestype"
|
||||
}
|
||||
}
|
||||
},
|
||||
"weather": {
|
||||
"name": "Vejr",
|
||||
"description": "Viser de aktuelle vejroplysninger for en bestemt placering.",
|
||||
"option": {
|
||||
"location": {
|
||||
"label": "Vejr lokation"
|
||||
}
|
||||
},
|
||||
"kind": {
|
||||
"clear": "Skyfrit",
|
||||
"mainlyClear": "Hovedsageligt skyfrit",
|
||||
"fog": "Tåge",
|
||||
"drizzle": "Støvregn",
|
||||
"freezingDrizzle": "Støvregn med isslag",
|
||||
"rain": "Regn",
|
||||
"freezingRain": "Isslag",
|
||||
"snowFall": "Snefald",
|
||||
"snowGrains": "Mildt snefald",
|
||||
"rainShowers": "Regnbyger",
|
||||
"snowShowers": "Snebyger",
|
||||
"thunderstorm": "Tordenvejr",
|
||||
"thunderstormWithHail": "Tordenvejr med hagl",
|
||||
"unknown": "Ukendt"
|
||||
}
|
||||
},
|
||||
"indexerManager": {
|
||||
"name": "Indekserings manager status",
|
||||
"title": "Indexer-manager",
|
||||
"testAll": "Test alle"
|
||||
},
|
||||
"healthMonitoring": {
|
||||
"name": "Systemsundhedsovervågning",
|
||||
"description": "Viser oplysninger om dit/dine systems tilstand og status.",
|
||||
"option": {
|
||||
"fahrenheit": {
|
||||
"label": "CPU-temperatur i Fahrenheit"
|
||||
},
|
||||
"cpu": {
|
||||
"label": "Vis CPU-info"
|
||||
},
|
||||
"memory": {
|
||||
"label": "Vis hukommelsesoplysninger"
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Vis information om filsystemet"
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"available": "Tilgængelig"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"search": "Søg",
|
||||
"table": {
|
||||
"header": {},
|
||||
"action": {},
|
||||
"population": {
|
||||
"fallback": "Ukendt"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"video": {
|
||||
"name": "Video Stream",
|
||||
"description": "Indlejr en video stream eller video fra et kamera eller et website",
|
||||
"option": {
|
||||
"feedUrl": {
|
||||
"label": "Feed URL"
|
||||
},
|
||||
"hasAutoPlay": {
|
||||
"label": "Auto-afspilning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"items": {
|
||||
"added": {
|
||||
"detailsTitle": "Dato tilføjet"
|
||||
},
|
||||
"downSpeed": {
|
||||
"columnTitle": "Down",
|
||||
"detailsTitle": "Download hastighed"
|
||||
},
|
||||
"integration": {
|
||||
"columnTitle": "Integration"
|
||||
},
|
||||
"progress": {
|
||||
"columnTitle": "Fremskridt"
|
||||
},
|
||||
"ratio": {
|
||||
"columnTitle": "Delingsforhold"
|
||||
},
|
||||
"state": {
|
||||
"columnTitle": "Tilstand"
|
||||
},
|
||||
"upSpeed": {
|
||||
"columnTitle": "Up"
|
||||
}
|
||||
},
|
||||
"states": {
|
||||
"downloading": "Downloader",
|
||||
"queued": "I kø",
|
||||
"paused": "På pause",
|
||||
"completed": "Fuldført",
|
||||
"unknown": "Ukendt"
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"description": "Se en liste over alle medieforespørgsler fra din Overseerr eller Jellyseerr instans",
|
||||
"option": {
|
||||
"linksTargetNewTab": {
|
||||
"label": "Åbn links i ny fane"
|
||||
}
|
||||
},
|
||||
"availability": {
|
||||
"unknown": "Ukendt",
|
||||
"partiallyAvailable": "Delvis",
|
||||
"available": "Tilgængelig"
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestStats": {
|
||||
"description": "Statistik over dine medieanmodninger",
|
||||
"titles": {
|
||||
"stats": {
|
||||
"main": "Mediestatistik",
|
||||
"approved": "Allerede godkendt",
|
||||
"pending": "Afventer godkendelse",
|
||||
"tv": "TV-anmodninger",
|
||||
"movie": "Film anmodninger",
|
||||
"total": "Total"
|
||||
},
|
||||
"users": {
|
||||
"main": "Topbrugere"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"action": {
|
||||
"oldImport": {
|
||||
"form": {
|
||||
"apps": {
|
||||
"label": "Apps"
|
||||
},
|
||||
"screenSize": {
|
||||
"option": {
|
||||
"sm": "Lille",
|
||||
"md": "Mellem",
|
||||
"lg": "Stor"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"backgroundImageAttachment": {
|
||||
"label": "Vedhæftning af baggrundsbillede"
|
||||
},
|
||||
"backgroundImageSize": {
|
||||
"label": "Baggrundsbilledets størrelse"
|
||||
},
|
||||
"primaryColor": {
|
||||
"label": "Primær farve"
|
||||
},
|
||||
"secondaryColor": {
|
||||
"label": "Sekundær farve"
|
||||
},
|
||||
"customCss": {
|
||||
"description": "Yderligere, tilpasse dit dashboard ved hjælp af CSS, anbefales kun til erfarne brugere"
|
||||
},
|
||||
"name": {
|
||||
"label": "Navn"
|
||||
},
|
||||
"isPublic": {
|
||||
"label": "Offentlig"
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
"section": {
|
||||
"general": {
|
||||
"title": "Generelt"
|
||||
},
|
||||
"layout": {
|
||||
"title": "Layout"
|
||||
},
|
||||
"background": {
|
||||
"title": "Baggrund"
|
||||
},
|
||||
"access": {
|
||||
"permission": {
|
||||
"item": {
|
||||
"view": {
|
||||
"label": "Se board"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dangerZone": {
|
||||
"title": "Farezone",
|
||||
"action": {
|
||||
"delete": {
|
||||
"confirm": {
|
||||
"title": "Slet board"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"management": {
|
||||
"navbar": {
|
||||
"items": {
|
||||
"home": "Hjem",
|
||||
"boards": "Boards",
|
||||
"apps": "Apps",
|
||||
"users": {
|
||||
"label": "Brugere",
|
||||
"items": {
|
||||
"manage": "Administrer",
|
||||
"invites": "Invitationer"
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"label": "Værktøjer",
|
||||
"items": {
|
||||
"docker": "Docker",
|
||||
"api": "API"
|
||||
}
|
||||
},
|
||||
"settings": "Indstillinger",
|
||||
"help": {
|
||||
"label": "Hjælp",
|
||||
"items": {
|
||||
"documentation": "Dokumentation",
|
||||
"discord": "Discordfællesskab"
|
||||
}
|
||||
},
|
||||
"about": "Om"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"home": {
|
||||
"statistic": {
|
||||
"board": "Boards",
|
||||
"user": "Brugere",
|
||||
"invite": "Invitationer",
|
||||
"app": "Apps"
|
||||
},
|
||||
"statisticLabel": {
|
||||
"boards": "Boards"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"title": "Dine boards",
|
||||
"action": {
|
||||
"settings": {
|
||||
"label": "Indstillinger"
|
||||
},
|
||||
"setHomeBoard": {
|
||||
"badge": {
|
||||
"label": "Hjem"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"label": "Slet permanent",
|
||||
"confirm": {
|
||||
"title": "Slet board"
|
||||
}
|
||||
}
|
||||
},
|
||||
"modal": {
|
||||
"createBoard": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Navn"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"setting": {
|
||||
"general": {
|
||||
"title": "Generelt",
|
||||
"item": {
|
||||
"firstDayOfWeek": "Første ugedag",
|
||||
"accessibility": "Hjælpefunktioner"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"title": "Sikkerhed"
|
||||
},
|
||||
"board": {
|
||||
"title": "Boards"
|
||||
}
|
||||
},
|
||||
"list": {
|
||||
"metaTitle": "Administrér brugere",
|
||||
"title": "Brugere"
|
||||
},
|
||||
"create": {
|
||||
"metaTitle": "Opret bruger",
|
||||
"step": {
|
||||
"security": {
|
||||
"label": "Sikkerhed"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invite": {
|
||||
"title": "Administrer brugerinvitationer",
|
||||
"action": {
|
||||
"new": {
|
||||
"description": "Efter udløb vil en invitation ikke længere være gyldig, og modtageren af invitationen vil ikke være i stand til at oprette en konto."
|
||||
},
|
||||
"copy": {
|
||||
"link": "Invitationslink"
|
||||
},
|
||||
"delete": {
|
||||
"title": "Slet invitation",
|
||||
"description": "Er du sikker på, at du vil slette denne invitation? Brugere med dette link vil ikke længere kunne oprette en konto ved hjælp af dette link."
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"id": {
|
||||
"label": "ID"
|
||||
},
|
||||
"creator": {
|
||||
"label": "Skaber"
|
||||
},
|
||||
"expirationDate": {
|
||||
"label": "Udløbsdato"
|
||||
},
|
||||
"token": {
|
||||
"label": "Token"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"setting": {
|
||||
"general": {
|
||||
"title": "Generelt"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Indstillinger"
|
||||
},
|
||||
"tool": {
|
||||
"tasks": {
|
||||
"status": {
|
||||
"running": "Kører",
|
||||
"error": "Fejl"
|
||||
},
|
||||
"job": {
|
||||
"mediaServer": {
|
||||
"label": "Medieserver"
|
||||
},
|
||||
"mediaRequests": {
|
||||
"label": "Medieforespørgsler"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api": {
|
||||
"title": "API",
|
||||
"tab": {
|
||||
"documentation": {
|
||||
"label": "Dokumentation"
|
||||
},
|
||||
"apiKey": {
|
||||
"table": {
|
||||
"header": {
|
||||
"id": "ID"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"docker": {
|
||||
"title": "Containere",
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Navn"
|
||||
},
|
||||
"state": {
|
||||
"label": "Tilstand",
|
||||
"option": {
|
||||
"created": "Oprettet",
|
||||
"running": "Kører",
|
||||
"paused": "På pause",
|
||||
"restarting": "Genstarter",
|
||||
"removing": "Fjerner"
|
||||
}
|
||||
},
|
||||
"containerImage": {
|
||||
"label": "Image"
|
||||
},
|
||||
"ports": {
|
||||
"label": "Porte"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"start": {
|
||||
"label": "Start"
|
||||
},
|
||||
"stop": {
|
||||
"label": "Stop"
|
||||
},
|
||||
"restart": {
|
||||
"label": "Genstart"
|
||||
},
|
||||
"remove": {
|
||||
"label": "Fjern"
|
||||
}
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"tab": {
|
||||
"user": "Brugere"
|
||||
},
|
||||
"field": {
|
||||
"user": {
|
||||
"label": "Bruger"
|
||||
}
|
||||
}
|
||||
},
|
||||
"navigationStructure": {
|
||||
"manage": {
|
||||
"label": "Administrer",
|
||||
"boards": {
|
||||
"label": "Boards"
|
||||
},
|
||||
"integrations": {
|
||||
"edit": {
|
||||
"label": "Rediger"
|
||||
}
|
||||
},
|
||||
"search-engines": {
|
||||
"edit": {
|
||||
"label": "Rediger"
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"label": "Apps",
|
||||
"edit": {
|
||||
"label": "Rediger"
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"label": "Brugere",
|
||||
"create": {
|
||||
"label": "Opret"
|
||||
},
|
||||
"general": "Generelt",
|
||||
"security": "Sikkerhed",
|
||||
"board": "Boards",
|
||||
"invites": {
|
||||
"label": "Invitationer"
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"label": "Værktøjer",
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"label": "Indstillinger"
|
||||
},
|
||||
"about": {
|
||||
"label": "Om"
|
||||
}
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"mode": {
|
||||
"appIntegrationBoard": {
|
||||
"group": {
|
||||
"app": {
|
||||
"title": "Apps"
|
||||
},
|
||||
"board": {
|
||||
"title": "Boards"
|
||||
}
|
||||
}
|
||||
},
|
||||
"external": {
|
||||
"group": {
|
||||
"searchEngine": {
|
||||
"option": {
|
||||
"torrent": {
|
||||
"name": "Torrents"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"help": {
|
||||
"group": {
|
||||
"help": {
|
||||
"title": "Hjælp",
|
||||
"option": {
|
||||
"documentation": {
|
||||
"label": "Dokumentation"
|
||||
},
|
||||
"discord": {
|
||||
"label": "Discordfællesskab"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"group": {
|
||||
"page": {
|
||||
"option": {
|
||||
"manageUser": {
|
||||
"label": "Administrér brugere"
|
||||
},
|
||||
"about": {
|
||||
"label": "Om"
|
||||
},
|
||||
"preferences": {
|
||||
"label": "Dine indstillinger"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"userGroup": {
|
||||
"group": {
|
||||
"user": {
|
||||
"title": "Brugere"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"engine": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Navn"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
911
packages/translation/src/lang/de.json
Normal file
911
packages/translation/src/lang/de.json
Normal file
@@ -0,0 +1,911 @@
|
||||
{
|
||||
"user": {
|
||||
"title": "Benutzer",
|
||||
"name": "Benutzer",
|
||||
"field": {
|
||||
"email": {
|
||||
"label": "E-Mail"
|
||||
},
|
||||
"username": {
|
||||
"label": "Benutzername"
|
||||
},
|
||||
"password": {
|
||||
"label": "Passwort",
|
||||
"requirement": {
|
||||
"lowercase": "Enthält Kleinbuchstaben",
|
||||
"uppercase": "Enthält Großbuchstaben",
|
||||
"number": "Enthält Ziffern"
|
||||
}
|
||||
},
|
||||
"passwordConfirm": {
|
||||
"label": "Passwort bestätigen"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"login": {
|
||||
"label": "Anmelden"
|
||||
},
|
||||
"register": {
|
||||
"label": "Account erstellen",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "Account erstellt"
|
||||
}
|
||||
}
|
||||
},
|
||||
"create": "Benutzer erstellen"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"field": {
|
||||
"name": "Name"
|
||||
},
|
||||
"permission": {
|
||||
"admin": {
|
||||
"title": "Admin"
|
||||
},
|
||||
"board": {
|
||||
"title": "Boards"
|
||||
}
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"page": {
|
||||
"list": {
|
||||
"title": "Apps"
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Name"
|
||||
}
|
||||
}
|
||||
},
|
||||
"integration": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Name"
|
||||
}
|
||||
},
|
||||
"testConnection": {
|
||||
"notification": {
|
||||
"invalidUrl": {
|
||||
"title": "Ungültige URL"
|
||||
}
|
||||
}
|
||||
},
|
||||
"secrets": {
|
||||
"kind": {
|
||||
"username": {
|
||||
"label": "Benutzername"
|
||||
},
|
||||
"password": {
|
||||
"label": "Passwort",
|
||||
"newLabel": "Neues Passwort"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"field": {
|
||||
"name": "Name",
|
||||
"size": "Größe",
|
||||
"creator": "Ersteller"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"error": "Fehler",
|
||||
"action": {
|
||||
"add": "Hinzufügen",
|
||||
"apply": "Übernehmen",
|
||||
"create": "Erstellen",
|
||||
"edit": "Bearbeiten",
|
||||
"insert": "Einfügen",
|
||||
"remove": "Entfernen",
|
||||
"save": "Speichern",
|
||||
"saveChanges": "Änderungen speichern",
|
||||
"cancel": "Abbrechen",
|
||||
"delete": "Löschen",
|
||||
"confirm": "Bestätigen",
|
||||
"previous": "Zurück",
|
||||
"next": "Weiter",
|
||||
"tryAgain": "Erneut versuchen"
|
||||
},
|
||||
"information": {
|
||||
"hours": "Stunden",
|
||||
"minutes": "Minuten"
|
||||
},
|
||||
"userAvatar": {
|
||||
"menu": {
|
||||
"preferences": "Ihre Einstellungen",
|
||||
"login": "Anmelden"
|
||||
}
|
||||
},
|
||||
"dangerZone": "Gefahrenzone",
|
||||
"noResults": "Die Suche ergab keine Treffer",
|
||||
"zod": {
|
||||
"errors": {
|
||||
"default": "Dieses Feld ist ungültig",
|
||||
"required": "Dieses Feld ist erforderlich",
|
||||
"string": {
|
||||
"startsWith": "Dieses Feld muss mit {startsWith} beginnen",
|
||||
"endsWith": "Dieses Feld muss mit {endsWith} enden",
|
||||
"includes": "Dieses Feld muss {includes} beinhalten"
|
||||
},
|
||||
"tooSmall": {
|
||||
"string": "Dieses Feld muss mindestens {minimum} Zeichen lang sein",
|
||||
"number": "Dieses Feld muss größer oder gleich {minimum} sein"
|
||||
},
|
||||
"tooBig": {
|
||||
"string": "Dieses Feld muss mindestens {maximum} Zeichen lang sein",
|
||||
"number": "Dieses Feld muss größer oder gleich {maximum} sein"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"section": {
|
||||
"category": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Name"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"moveUp": "Nach oben bewegen",
|
||||
"moveDown": "Nach unten bewegen"
|
||||
},
|
||||
"menu": {
|
||||
"label": {
|
||||
"changePosition": "Position wechseln"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"menu": {
|
||||
"label": {
|
||||
"settings": "Einstellungen"
|
||||
}
|
||||
},
|
||||
"moveResize": {
|
||||
"field": {
|
||||
"width": {
|
||||
"label": "Breite"
|
||||
},
|
||||
"height": {
|
||||
"label": "Höhe"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"app": {
|
||||
"option": {
|
||||
"openInNewTab": {
|
||||
"label": "In neuem Tab öffnen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "Ansicht",
|
||||
"option": {
|
||||
"row": {
|
||||
"label": "Horizontal"
|
||||
},
|
||||
"column": {
|
||||
"label": "Vertikal"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"adsBlockedToday": "Heute blockiert",
|
||||
"adsBlockedTodayPercentage": "Heute blockiert",
|
||||
"dnsQueriesToday": "Heutige Anfragen"
|
||||
}
|
||||
},
|
||||
"dnsHoleControls": {
|
||||
"description": "Steuern Sie PiHole oder AdGuard von Ihrem Dashboard aus",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "Ansicht",
|
||||
"option": {
|
||||
"row": {
|
||||
"label": "Horizontal"
|
||||
},
|
||||
"column": {
|
||||
"label": "Vertikal"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
"set": "Speichern",
|
||||
"enabled": "Aktiviert",
|
||||
"disabled": "Deaktiviert",
|
||||
"hours": "Stunden",
|
||||
"minutes": "Minuten"
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
"description": "Zeigt das aktuelle Datum und die Uhrzeit an.",
|
||||
"option": {
|
||||
"timezone": {
|
||||
"label": "Zeitzone"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Notizbuch",
|
||||
"option": {
|
||||
"showToolbar": {
|
||||
"label": "Zeigt die Symbolleiste an, um Ihnen beim Schreiben der Markdown zu assistieren"
|
||||
},
|
||||
"allowReadOnlyCheck": {
|
||||
"label": "Prüfung im Nur-Lese-Modus zulassen"
|
||||
},
|
||||
"content": {
|
||||
"label": "Der Inhalt des Notizbuchs"
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
"bold": "Fett",
|
||||
"italic": "Kursiv",
|
||||
"strikethrough": "Durchgestrichen",
|
||||
"underline": "Unterstrichen",
|
||||
"colorText": "Farbiger Text",
|
||||
"colorHighlight": "Farbig hervorgehobener Text",
|
||||
"code": "Code",
|
||||
"clear": "Formatierung entfernen",
|
||||
"heading": "Überschrift {level}",
|
||||
"align": "Text ausrichten: {position}",
|
||||
"blockquote": "Blockzitat",
|
||||
"horizontalLine": "Horizontale Linie",
|
||||
"bulletList": "Aufzählung",
|
||||
"orderedList": "Geordnete Liste",
|
||||
"checkList": "Checkliste",
|
||||
"increaseIndent": "Einzug vergrößern",
|
||||
"decreaseIndent": "Einzug verkleinern",
|
||||
"link": "Link",
|
||||
"unlink": "Link entfernen",
|
||||
"image": "Bild einbetten",
|
||||
"addTable": "Tabelle hinzufügen",
|
||||
"deleteTable": "Tabelle entfernen",
|
||||
"colorCell": "Farbe der Tabellen Zelle",
|
||||
"mergeCell": "Zellen-Zusammenführung umschalten",
|
||||
"addColumnLeft": "Spalte davor hinzufügen",
|
||||
"addColumnRight": "Spalte danach hinzufügen",
|
||||
"deleteColumn": "Spalte löschen",
|
||||
"addRowTop": "Zeile davor hinzufügen",
|
||||
"addRowBelow": "Zeile danach hinzufügen",
|
||||
"deleteRow": "Zeile löschen"
|
||||
},
|
||||
"align": {
|
||||
"left": "Links",
|
||||
"center": "Mittig",
|
||||
"right": "Rechts"
|
||||
},
|
||||
"popover": {
|
||||
"clearColor": "Farbe entfernen",
|
||||
"source": "Quelle",
|
||||
"widthPlaceholder": "Wert in % oder Pixel",
|
||||
"columns": "Spalten",
|
||||
"rows": "Zeilen",
|
||||
"width": "Breite",
|
||||
"height": "Höhe"
|
||||
}
|
||||
},
|
||||
"iframe": {
|
||||
"name": "iFrame",
|
||||
"description": "Einbetten von Inhalten aus dem Internet. Einige Websites können den Zugriff einschränken.",
|
||||
"option": {
|
||||
"embedUrl": {
|
||||
"label": "URL einbetten"
|
||||
},
|
||||
"allowFullScreen": {
|
||||
"label": "Vollbildmodus zulassen"
|
||||
},
|
||||
"allowTransparency": {
|
||||
"label": "Erlaube Transparenz"
|
||||
},
|
||||
"allowScrolling": {
|
||||
"label": "Scrollen zulassen"
|
||||
},
|
||||
"allowPayment": {
|
||||
"label": "Zahlung zulassen"
|
||||
},
|
||||
"allowAutoPlay": {
|
||||
"label": "Automatische Wiedergabe zulassen"
|
||||
},
|
||||
"allowMicrophone": {
|
||||
"label": "Mikrofonzugriff erlauben"
|
||||
},
|
||||
"allowCamera": {
|
||||
"label": "Kamera freigeben"
|
||||
},
|
||||
"allowGeolocation": {
|
||||
"label": "Geolokalisierung zulassen"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"noBrowerSupport": "Ihr Browser unterstützt keine iframes. Bitte aktualisieren Sie Ihren Browser."
|
||||
}
|
||||
},
|
||||
"smartHome-entityState": {
|
||||
"option": {
|
||||
"entityId": {
|
||||
"label": "Eintrag-ID"
|
||||
}
|
||||
}
|
||||
},
|
||||
"smartHome-executeAutomation": {
|
||||
"option": {
|
||||
"displayName": {
|
||||
"label": "Anzeigename"
|
||||
},
|
||||
"automationId": {
|
||||
"label": "Automatisierungs-ID"
|
||||
}
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
"name": "Kalender",
|
||||
"option": {
|
||||
"releaseType": {
|
||||
"label": "Radarr Veröffentlichungs Typ"
|
||||
}
|
||||
}
|
||||
},
|
||||
"weather": {
|
||||
"name": "Wetter",
|
||||
"description": "Zeigt die aktuellen Wetterinformationen für einen bestimmten Ort an.",
|
||||
"option": {
|
||||
"location": {
|
||||
"label": "Wetterstandort"
|
||||
}
|
||||
},
|
||||
"kind": {
|
||||
"clear": "Klar",
|
||||
"mainlyClear": "Überwiegend klar",
|
||||
"fog": "Nebel",
|
||||
"drizzle": "Niesel",
|
||||
"freezingDrizzle": "Eisiger Nieselregen",
|
||||
"rain": "Regen",
|
||||
"freezingRain": "Eisiger Regen",
|
||||
"snowFall": "Schneefall",
|
||||
"snowGrains": "Schneekörner",
|
||||
"rainShowers": "Regenschauer",
|
||||
"snowShowers": "Schneeschauer",
|
||||
"thunderstorm": "Gewitter",
|
||||
"thunderstormWithHail": "Gewitter mit Hagel",
|
||||
"unknown": "Unbekannt"
|
||||
}
|
||||
},
|
||||
"indexerManager": {
|
||||
"name": "Status des Indexer-Managers",
|
||||
"title": "Indexer-Manager",
|
||||
"testAll": "Alle testen"
|
||||
},
|
||||
"healthMonitoring": {
|
||||
"name": "Überwachung des Systemzustands",
|
||||
"description": "Zeigt Informationen zum Zustand und Status Ihres/Ihrer Systeme(s) an.",
|
||||
"option": {
|
||||
"fahrenheit": {
|
||||
"label": "CPU-Temperatur in Fahrenheit"
|
||||
},
|
||||
"cpu": {
|
||||
"label": "CPU-Info anzeigen"
|
||||
},
|
||||
"memory": {
|
||||
"label": "Speicher-Info anzeigen"
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Dateisystem Info anzeigen"
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"available": "Verfügbar"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"search": "Suchen",
|
||||
"table": {
|
||||
"header": {},
|
||||
"action": {},
|
||||
"population": {
|
||||
"fallback": "Unbekannt"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"video": {
|
||||
"name": "Videostream",
|
||||
"description": "Einbetten eines Videostreams oder eines Videos von einer Kamera oder einer Website",
|
||||
"option": {
|
||||
"feedUrl": {
|
||||
"label": "Feed-URL"
|
||||
},
|
||||
"hasAutoPlay": {
|
||||
"label": "Automatische Wiedergabe"
|
||||
}
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"items": {
|
||||
"added": {
|
||||
"detailsTitle": "Hinzugefügt am"
|
||||
},
|
||||
"downSpeed": {
|
||||
"columnTitle": "Down",
|
||||
"detailsTitle": "Download Geschwindigkeit"
|
||||
},
|
||||
"integration": {
|
||||
"columnTitle": "Integration"
|
||||
},
|
||||
"progress": {
|
||||
"columnTitle": "Fortschritt"
|
||||
},
|
||||
"ratio": {
|
||||
"columnTitle": "Verhältnis"
|
||||
},
|
||||
"state": {
|
||||
"columnTitle": "Staat"
|
||||
},
|
||||
"upSpeed": {
|
||||
"columnTitle": "Up"
|
||||
}
|
||||
},
|
||||
"states": {
|
||||
"downloading": "Herunterladen",
|
||||
"queued": "In der Warteschlange",
|
||||
"paused": "Pausiert",
|
||||
"completed": "Abgeschlossen",
|
||||
"unknown": "Unbekannt"
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"description": "Sehen Sie eine Liste aller Medienanfragen von Ihrer Overseerr- oder Jellyseerr-Instanz",
|
||||
"option": {
|
||||
"linksTargetNewTab": {
|
||||
"label": "Links in neuem Tab öffnen"
|
||||
}
|
||||
},
|
||||
"availability": {
|
||||
"unknown": "Unbekannt",
|
||||
"partiallyAvailable": "Teilweise",
|
||||
"available": "Verfügbar"
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestStats": {
|
||||
"description": "Statistiken über Ihre Medienanfragen",
|
||||
"titles": {
|
||||
"stats": {
|
||||
"main": "Medien-Statistiken",
|
||||
"approved": "Bereits genehmigt",
|
||||
"pending": "Ausstehende Freigaben",
|
||||
"tv": "TV-Anfragen",
|
||||
"movie": "Film-Anfragen",
|
||||
"total": "Gesamt"
|
||||
},
|
||||
"users": {
|
||||
"main": "Top-Nutzer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"action": {
|
||||
"oldImport": {
|
||||
"form": {
|
||||
"apps": {
|
||||
"label": "Apps"
|
||||
},
|
||||
"screenSize": {
|
||||
"option": {
|
||||
"sm": "Klein",
|
||||
"md": "Mittel",
|
||||
"lg": "Groß"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"backgroundImageAttachment": {
|
||||
"label": "Anhang des Hintergrundbildes"
|
||||
},
|
||||
"backgroundImageSize": {
|
||||
"label": "Hintergrundbild-Größe"
|
||||
},
|
||||
"primaryColor": {
|
||||
"label": "Primärfarbe"
|
||||
},
|
||||
"secondaryColor": {
|
||||
"label": "Sekundärfarbe"
|
||||
},
|
||||
"customCss": {
|
||||
"description": "Außerdem können Sie Ihr Dashboard mittels CSS anpassen, dies wird nur für erfahrene Benutzer empfohlen"
|
||||
},
|
||||
"name": {
|
||||
"label": "Name"
|
||||
},
|
||||
"isPublic": {
|
||||
"label": "Öffentlich sichtbar"
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
"section": {
|
||||
"general": {
|
||||
"title": "Allgemein"
|
||||
},
|
||||
"layout": {
|
||||
"title": "Ansicht"
|
||||
},
|
||||
"background": {
|
||||
"title": "Hintergrund"
|
||||
},
|
||||
"access": {
|
||||
"permission": {
|
||||
"item": {
|
||||
"view": {
|
||||
"label": "Board anzeigen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dangerZone": {
|
||||
"title": "Gefahrenzone",
|
||||
"action": {
|
||||
"delete": {
|
||||
"confirm": {
|
||||
"title": "Board löschen"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"management": {
|
||||
"navbar": {
|
||||
"items": {
|
||||
"home": "Startseite",
|
||||
"boards": "Boards",
|
||||
"apps": "Apps",
|
||||
"users": {
|
||||
"label": "Benutzer",
|
||||
"items": {
|
||||
"manage": "Verwalten",
|
||||
"invites": "Einladungen"
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"label": "Werkzeuge",
|
||||
"items": {
|
||||
"docker": "Docker",
|
||||
"api": "API"
|
||||
}
|
||||
},
|
||||
"settings": "Einstellungen",
|
||||
"help": {
|
||||
"label": "Hilfe",
|
||||
"items": {
|
||||
"documentation": "Dokumentation",
|
||||
"discord": "Community Discord"
|
||||
}
|
||||
},
|
||||
"about": "Über"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"home": {
|
||||
"statistic": {
|
||||
"board": "Boards",
|
||||
"user": "Benutzer",
|
||||
"invite": "Einladungen",
|
||||
"app": "Apps"
|
||||
},
|
||||
"statisticLabel": {
|
||||
"boards": "Boards"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"title": "Deine Boards",
|
||||
"action": {
|
||||
"settings": {
|
||||
"label": "Einstellungen"
|
||||
},
|
||||
"setHomeBoard": {
|
||||
"badge": {
|
||||
"label": "Startseite"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"label": "Dauerhaft löschen",
|
||||
"confirm": {
|
||||
"title": "Board löschen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"modal": {
|
||||
"createBoard": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"setting": {
|
||||
"general": {
|
||||
"title": "Allgemein",
|
||||
"item": {
|
||||
"firstDayOfWeek": "Erster Tag der Woche",
|
||||
"accessibility": "Barrierefreiheit"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"title": "Sicherheit"
|
||||
},
|
||||
"board": {
|
||||
"title": "Boards"
|
||||
}
|
||||
},
|
||||
"list": {
|
||||
"metaTitle": "Verwaltung von Benutzern",
|
||||
"title": "Benutzer"
|
||||
},
|
||||
"create": {
|
||||
"metaTitle": "Benutzer erstellen",
|
||||
"step": {
|
||||
"security": {
|
||||
"label": "Sicherheit"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invite": {
|
||||
"title": "Verwalten von Benutzereinladungen",
|
||||
"action": {
|
||||
"new": {
|
||||
"description": "Nach Ablauf der Frist ist eine Einladung nicht mehr gültig und der Empfänger der Einladung kann kein Konto erstellen."
|
||||
},
|
||||
"copy": {
|
||||
"link": "Link zur Einladung"
|
||||
},
|
||||
"delete": {
|
||||
"title": "Einladung löschen",
|
||||
"description": "Sind Sie sicher, dass Sie diese Einladung löschen möchten? Benutzer mit diesem Link können dann kein Konto mehr über diesen Link erstellen."
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"id": {
|
||||
"label": "ID"
|
||||
},
|
||||
"creator": {
|
||||
"label": "Ersteller"
|
||||
},
|
||||
"expirationDate": {
|
||||
"label": "Ablaufdatum"
|
||||
},
|
||||
"token": {
|
||||
"label": "Token"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"setting": {
|
||||
"general": {
|
||||
"title": "Allgemein"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Einstellungen"
|
||||
},
|
||||
"tool": {
|
||||
"tasks": {
|
||||
"status": {
|
||||
"running": "Aktiv",
|
||||
"error": "Fehler"
|
||||
},
|
||||
"job": {
|
||||
"mediaServer": {
|
||||
"label": "Medien Server"
|
||||
},
|
||||
"mediaRequests": {
|
||||
"label": "Medienanfragen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api": {
|
||||
"title": "API",
|
||||
"tab": {
|
||||
"documentation": {
|
||||
"label": "Dokumentation"
|
||||
},
|
||||
"apiKey": {
|
||||
"table": {
|
||||
"header": {
|
||||
"id": "ID"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"docker": {
|
||||
"title": "Container",
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Name"
|
||||
},
|
||||
"state": {
|
||||
"label": "Staat",
|
||||
"option": {
|
||||
"created": "Erstellt",
|
||||
"running": "Aktiv",
|
||||
"paused": "Pausiert",
|
||||
"restarting": "Startet neu",
|
||||
"removing": "Wird entfernt"
|
||||
}
|
||||
},
|
||||
"containerImage": {
|
||||
"label": "Image"
|
||||
},
|
||||
"ports": {
|
||||
"label": "Ports"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"start": {
|
||||
"label": "Starten"
|
||||
},
|
||||
"stop": {
|
||||
"label": "Stopp"
|
||||
},
|
||||
"restart": {
|
||||
"label": "Neustarten"
|
||||
},
|
||||
"remove": {
|
||||
"label": "Entfernen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"tab": {
|
||||
"user": "Benutzer"
|
||||
},
|
||||
"field": {
|
||||
"user": {
|
||||
"label": "Benutzer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"navigationStructure": {
|
||||
"manage": {
|
||||
"label": "Verwalten",
|
||||
"boards": {
|
||||
"label": "Boards"
|
||||
},
|
||||
"integrations": {
|
||||
"edit": {
|
||||
"label": "Bearbeiten"
|
||||
}
|
||||
},
|
||||
"search-engines": {
|
||||
"edit": {
|
||||
"label": "Bearbeiten"
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"label": "Apps",
|
||||
"edit": {
|
||||
"label": "Bearbeiten"
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"label": "Benutzer",
|
||||
"create": {
|
||||
"label": "Erstellen"
|
||||
},
|
||||
"general": "Allgemein",
|
||||
"security": "Sicherheit",
|
||||
"board": "Boards",
|
||||
"invites": {
|
||||
"label": "Einladungen"
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"label": "Werkzeuge",
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"label": "Einstellungen"
|
||||
},
|
||||
"about": {
|
||||
"label": "Über"
|
||||
}
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"mode": {
|
||||
"appIntegrationBoard": {
|
||||
"group": {
|
||||
"app": {
|
||||
"title": "Apps"
|
||||
},
|
||||
"board": {
|
||||
"title": "Boards"
|
||||
}
|
||||
}
|
||||
},
|
||||
"external": {
|
||||
"group": {
|
||||
"searchEngine": {
|
||||
"option": {
|
||||
"torrent": {
|
||||
"name": "Torrents"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"help": {
|
||||
"group": {
|
||||
"help": {
|
||||
"title": "Hilfe",
|
||||
"option": {
|
||||
"documentation": {
|
||||
"label": "Dokumentation"
|
||||
},
|
||||
"discord": {
|
||||
"label": "Community Discord"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"group": {
|
||||
"page": {
|
||||
"option": {
|
||||
"manageUser": {
|
||||
"label": "Verwaltung von Benutzern"
|
||||
},
|
||||
"about": {
|
||||
"label": "Über"
|
||||
},
|
||||
"preferences": {
|
||||
"label": "Ihre Einstellungen"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"userGroup": {
|
||||
"group": {
|
||||
"user": {
|
||||
"title": "Benutzer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"engine": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
import "dayjs/locale/de";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import { MRT_Localization_DE } from "mantine-react-table/locales/de/index.cjs";
|
||||
|
||||
dayjs.locale("de");
|
||||
|
||||
export default {
|
||||
user: {
|
||||
page: {
|
||||
login: {
|
||||
title: "Melde dich bei deinem Konto an",
|
||||
subtitle: "Willkommen zurück! Bitte gib deine Zugangsdaten ein",
|
||||
},
|
||||
init: {
|
||||
title: "Neue Homarr Installation",
|
||||
subtitle: "Bitte erstelle den initialen Administrator Benutzer",
|
||||
},
|
||||
},
|
||||
field: {
|
||||
username: {
|
||||
label: "Benutzername",
|
||||
},
|
||||
password: {
|
||||
label: "Passwort",
|
||||
},
|
||||
passwordConfirm: {
|
||||
label: "Passwort bestätigen",
|
||||
},
|
||||
},
|
||||
action: {
|
||||
login: "Anmelden",
|
||||
create: "Benutzer erstellen",
|
||||
},
|
||||
},
|
||||
integration: {
|
||||
page: {
|
||||
list: {
|
||||
title: "Integrationen",
|
||||
search: "Integration suchen",
|
||||
empty: "Keine Integrationen gefunden",
|
||||
},
|
||||
create: {
|
||||
title: "Neue {name} Integration erstellen",
|
||||
notification: {
|
||||
success: {
|
||||
title: "Erstellung erfolgreich",
|
||||
message: "Die Integration wurde erfolgreich erstellt",
|
||||
},
|
||||
error: {
|
||||
title: "Erstellung fehlgeschlagen",
|
||||
message: "Die Integration konnte nicht erstellt werden",
|
||||
},
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
title: "{name} Integration bearbeiten",
|
||||
notification: {
|
||||
success: {
|
||||
title: "Änderungen erfolgreich angewendet",
|
||||
message: "Die Integration wurde erfolgreich gespeichert",
|
||||
},
|
||||
error: {
|
||||
title: "Änderungen konnten nicht angewendet werden",
|
||||
message: "Die Integration konnte nicht gespeichert werden",
|
||||
},
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
title: "Integration entfernen",
|
||||
message: "Möchtest du die Integration {name} wirklich entfernen?",
|
||||
notification: {
|
||||
success: {
|
||||
title: "Entfernen erfolgreich",
|
||||
message: "Die Integration wurde erfolgreich entfernt",
|
||||
},
|
||||
error: {
|
||||
title: "Entfernen fehlgeschlagen",
|
||||
message: "Die Integration konnte nicht entfernt werden",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
field: {
|
||||
name: {
|
||||
label: "Name",
|
||||
},
|
||||
url: {
|
||||
label: "Url",
|
||||
},
|
||||
},
|
||||
action: {
|
||||
create: "Neue Integration",
|
||||
},
|
||||
testConnection: {
|
||||
action: "Verbindung überprüfen",
|
||||
alertNotice: "Der Button zum Speichern wird aktiviert, sobald die Verbindung erfolgreich überprüft wurde",
|
||||
notification: {
|
||||
success: {
|
||||
title: "Verbindung erfolgreich",
|
||||
message: "Die Verbindung wurde erfolgreich hergestellt",
|
||||
},
|
||||
invalidUrl: {
|
||||
title: "Ungültige URL",
|
||||
message: "Die URL ist ungültig",
|
||||
},
|
||||
notAllSecretsProvided: {
|
||||
title: "Fehlende Zugangsdaten",
|
||||
message: "Es wurden nicht alle Zugangsdaten angegeben",
|
||||
},
|
||||
invalidCredentials: {
|
||||
title: "Ungültige Zugangsdaten",
|
||||
message: "Die Zugangsdaten sind ungültig",
|
||||
},
|
||||
commonError: {
|
||||
title: "Verbindung fehlgeschlagen",
|
||||
message: "Die Verbindung konnte nicht hergestellt werden",
|
||||
},
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
title: "Zugangsdaten",
|
||||
lastUpdated: "Zuletzt geändert {date}",
|
||||
secureNotice: "Diese Zugangsdaten können nach der Erstellung nicht mehr ausgelesen werden",
|
||||
reset: {
|
||||
title: "Zugangsdaten zurücksetzen",
|
||||
message: "Möchtest du diese Zugangsdaten wirklich zurücksetzen?",
|
||||
},
|
||||
kind: {
|
||||
username: {
|
||||
label: "Benutzername",
|
||||
newLabel: "Neuer Benutzername",
|
||||
},
|
||||
apiKey: {
|
||||
label: "API Key",
|
||||
newLabel: "Neuer API Key",
|
||||
},
|
||||
password: {
|
||||
label: "Passwort",
|
||||
newLabel: "Neues Passwort",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
common: {
|
||||
rtl: "{value}{symbol}",
|
||||
action: {
|
||||
backToOverview: "Zurück zur Übersicht",
|
||||
create: "Erstellen",
|
||||
edit: "Bearbeiten",
|
||||
save: "Speichern",
|
||||
cancel: "Abbrechen",
|
||||
confirm: "Bestätigen",
|
||||
},
|
||||
multiSelect: {
|
||||
placeholder: "Wähle eine oder mehrere Optionen aus",
|
||||
},
|
||||
noResults: "Keine Ergebnisse gefunden",
|
||||
mantineReactTable: MRT_Localization_DE,
|
||||
},
|
||||
widget: {
|
||||
editModal: {
|
||||
integrations: {
|
||||
label: "Integrationen",
|
||||
},
|
||||
},
|
||||
clock: {
|
||||
option: {
|
||||
is24HourFormat: {
|
||||
label: "24-Stunden Format",
|
||||
description: "Verwende das 24-Stunden Format anstelle des 12-Stunden Formats",
|
||||
},
|
||||
isLocaleTime: {
|
||||
label: "Lokale Zeit verwenden",
|
||||
},
|
||||
timezone: {
|
||||
label: "Zeitzone",
|
||||
},
|
||||
},
|
||||
},
|
||||
weather: {
|
||||
option: {
|
||||
location: {
|
||||
label: "Standort",
|
||||
},
|
||||
showCity: {
|
||||
label: "Stadt anzeigen",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
search: {
|
||||
placeholder: "Suche nach etwas",
|
||||
nothingFound: "Nichts gefunden",
|
||||
},
|
||||
} as const;
|
||||
911
packages/translation/src/lang/el.json
Normal file
911
packages/translation/src/lang/el.json
Normal file
@@ -0,0 +1,911 @@
|
||||
{
|
||||
"user": {
|
||||
"title": "Χρήστες",
|
||||
"name": "Χρήστης",
|
||||
"field": {
|
||||
"email": {
|
||||
"label": "E-Mail"
|
||||
},
|
||||
"username": {
|
||||
"label": "Όνομα Χρήστη"
|
||||
},
|
||||
"password": {
|
||||
"label": "Κωδικός",
|
||||
"requirement": {
|
||||
"lowercase": "Περιλαμβάνει πεζό γράμμα",
|
||||
"uppercase": "Περιλαμβάνει κεφαλαίο γράμμα",
|
||||
"number": "Περιλαμβάνει αριθμό"
|
||||
}
|
||||
},
|
||||
"passwordConfirm": {
|
||||
"label": "Επιβεβαίωση κωδικού"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"login": {
|
||||
"label": "Σύνδεση"
|
||||
},
|
||||
"register": {
|
||||
"label": "Δημιουργία λογαριασμού",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "Ο λογαριασμός δημιουργήθηκε"
|
||||
}
|
||||
}
|
||||
},
|
||||
"create": "Δημιουργία χρήστη"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"field": {
|
||||
"name": "Όνομα"
|
||||
},
|
||||
"permission": {
|
||||
"admin": {
|
||||
"title": "Διαχειριστής"
|
||||
},
|
||||
"board": {
|
||||
"title": "Πίνακες"
|
||||
}
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"page": {
|
||||
"list": {
|
||||
"title": "Εφαρμογές"
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Όνομα"
|
||||
}
|
||||
}
|
||||
},
|
||||
"integration": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Όνομα"
|
||||
}
|
||||
},
|
||||
"testConnection": {
|
||||
"notification": {
|
||||
"invalidUrl": {
|
||||
"title": "Μη Έγκυρος Σύνδεσμος"
|
||||
}
|
||||
}
|
||||
},
|
||||
"secrets": {
|
||||
"kind": {
|
||||
"username": {
|
||||
"label": "Όνομα Χρήστη"
|
||||
},
|
||||
"password": {
|
||||
"label": "Κωδικός",
|
||||
"newLabel": "Νέος κωδικός"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"field": {
|
||||
"name": "Όνομα",
|
||||
"size": "Μέγεθος",
|
||||
"creator": "Δημιουργός"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"error": "Σφάλμα",
|
||||
"action": {
|
||||
"add": "Προσθήκη",
|
||||
"apply": "Εφαρμογή",
|
||||
"create": "Δημιουργία",
|
||||
"edit": "Επεξεργασία",
|
||||
"insert": "Εισαγωγή",
|
||||
"remove": "Αφαίρεση",
|
||||
"save": "Αποθήκευση",
|
||||
"saveChanges": "Αποθήκευση αλλαγών",
|
||||
"cancel": "Ακύρωση",
|
||||
"delete": "Διαγραφή",
|
||||
"confirm": "Επιβεβαίωση",
|
||||
"previous": "Προηγούμενο",
|
||||
"next": "Επόμενο",
|
||||
"tryAgain": "Προσπαθήστε ξανά"
|
||||
},
|
||||
"information": {
|
||||
"hours": "Ώρες",
|
||||
"minutes": "Λεπτά"
|
||||
},
|
||||
"userAvatar": {
|
||||
"menu": {
|
||||
"preferences": "Οι ρυθμίσεις σας",
|
||||
"login": "Σύνδεση"
|
||||
}
|
||||
},
|
||||
"dangerZone": "Επικίνδυνη Περιοχή",
|
||||
"noResults": "Δεν βρέθηκαν αποτελέσματα",
|
||||
"zod": {
|
||||
"errors": {
|
||||
"default": "Το πεδίο δεν είναι έγκυρο",
|
||||
"required": "Αυτό το πεδίο είναι υποχρεωτικό",
|
||||
"string": {
|
||||
"startsWith": "Αυτό το πεδίο πρέπει να ξεκινά με {startsWith}",
|
||||
"endsWith": "Το πεδίο αυτό πρέπει να τελειώνει με {endsWith}",
|
||||
"includes": "Το πεδίο αυτό πρέπει να περιλαμβάνει το {includes}"
|
||||
},
|
||||
"tooSmall": {
|
||||
"string": "Το πεδίο αυτό πρέπει να έχει μήκος τουλάχιστον {minimum} χαρακτήρες",
|
||||
"number": "Το πεδίο αυτό πρέπει να είναι μεγαλύτερο ή ίσο του {minimum}"
|
||||
},
|
||||
"tooBig": {
|
||||
"string": "Το πεδίο αυτό πρέπει να έχει μήκος το πολύ {maximum} χαρακτήρες",
|
||||
"number": "Το πεδίο αυτό πρέπει να είναι μικρότερο ή ίσο του {maximum}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"section": {
|
||||
"category": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Όνομα"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"moveUp": "Μετακίνηση επάνω",
|
||||
"moveDown": "Μετακίνηση κάτω"
|
||||
},
|
||||
"menu": {
|
||||
"label": {
|
||||
"changePosition": "Αλλαγή θέσης"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"menu": {
|
||||
"label": {
|
||||
"settings": "Ρυθμίσεις"
|
||||
}
|
||||
},
|
||||
"moveResize": {
|
||||
"field": {
|
||||
"width": {
|
||||
"label": "Πλάτος"
|
||||
},
|
||||
"height": {
|
||||
"label": "Ύψος"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"app": {
|
||||
"option": {
|
||||
"openInNewTab": {
|
||||
"label": "Άνοιγμα σε νέα καρτέλα"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "Διάταξη",
|
||||
"option": {
|
||||
"row": {
|
||||
"label": "Οριζόντια"
|
||||
},
|
||||
"column": {
|
||||
"label": "Κατακόρυφα"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"adsBlockedToday": "Σημερινοί αποκλεισμοί",
|
||||
"adsBlockedTodayPercentage": "Σημερινοί αποκλεισμοί",
|
||||
"dnsQueriesToday": "Σημερινά queries"
|
||||
}
|
||||
},
|
||||
"dnsHoleControls": {
|
||||
"description": "Ελέγξτε το PiHole ή το AdGuard από το dashboard σας",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "Διάταξη",
|
||||
"option": {
|
||||
"row": {
|
||||
"label": "Οριζόντια"
|
||||
},
|
||||
"column": {
|
||||
"label": "Κατακόρυφα"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
"set": "Ορισμός",
|
||||
"enabled": "Ενεργοποιημένο",
|
||||
"disabled": "Απενεργοποιημένο",
|
||||
"hours": "Ώρες",
|
||||
"minutes": "Λεπτά"
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
"description": "Εμφανίζει την τρέχουσα ημερομηνία και ώρα.",
|
||||
"option": {
|
||||
"timezone": {
|
||||
"label": "Ζώνη ώρας"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Σημειωματάριο",
|
||||
"option": {
|
||||
"showToolbar": {
|
||||
"label": "Εμφάνιση γραμμής εργαλείων για να σας βοηθήσει να γράψετε σημάνσεις"
|
||||
},
|
||||
"allowReadOnlyCheck": {
|
||||
"label": "Να επιτρέπεται η επιλογή σε λειτουργία μόνο ανάγνωσης"
|
||||
},
|
||||
"content": {
|
||||
"label": "Το περιεχόμενο του σημειωματάριου"
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
"bold": "Έντονη γραφή",
|
||||
"italic": "Πλάγια γραφή",
|
||||
"strikethrough": "Διαγραμμισμένο Κείμενο",
|
||||
"underline": "Υπογραμμισμένο Κείμενο",
|
||||
"colorText": "Έγχρωμο κείμενο",
|
||||
"colorHighlight": "Έγχρωμο κείμενο επισήμανσης",
|
||||
"code": "Κωδικός",
|
||||
"clear": "Εκκαθάριση μορφοποίησης",
|
||||
"heading": "Επικεφαλίδα {level}",
|
||||
"align": "Στοίχιση κειμένου: {position}",
|
||||
"blockquote": "Μπλοκ κειμένου παράθεσης",
|
||||
"horizontalLine": "Οριζόντια γραμμή",
|
||||
"bulletList": "Λίστα με κουκκίδες",
|
||||
"orderedList": "Ταξινομημένη λίστα",
|
||||
"checkList": "Λίστα ελέγχου",
|
||||
"increaseIndent": "Αύξηση εσοχής",
|
||||
"decreaseIndent": "Μείωση εσοχής",
|
||||
"link": "Σύνδεσμος",
|
||||
"unlink": "Αφαίρεση συνδέσμου",
|
||||
"image": "Ενσωμάτωση εικόνας",
|
||||
"addTable": "Προσθήκη πίνακα",
|
||||
"deleteTable": "Διαγραφή πίνακα",
|
||||
"colorCell": "Χρώμα κελιού",
|
||||
"mergeCell": "Εναλλαγή συγχώνευσης κελιού",
|
||||
"addColumnLeft": "Προσθήκη στήλης πριν",
|
||||
"addColumnRight": "Προσθήκη στήλης μετά",
|
||||
"deleteColumn": "Διαγραφή στήλης",
|
||||
"addRowTop": "Προσθήκη γραμμής πριν",
|
||||
"addRowBelow": "Προσθήκη γραμμής μετά",
|
||||
"deleteRow": "Διαγραφή γραμμής"
|
||||
},
|
||||
"align": {
|
||||
"left": "Αριστερά",
|
||||
"center": "Κέντρο",
|
||||
"right": "Δεξιά"
|
||||
},
|
||||
"popover": {
|
||||
"clearColor": "Καθαρισμός χρώματος",
|
||||
"source": "Πηγή",
|
||||
"widthPlaceholder": "Τιμή σε % ή εικονοστοιχεία",
|
||||
"columns": "Στήλες",
|
||||
"rows": "Γραμμές",
|
||||
"width": "Πλάτος",
|
||||
"height": "Ύψος"
|
||||
}
|
||||
},
|
||||
"iframe": {
|
||||
"name": "iframe",
|
||||
"description": "Ενσωματώστε οποιοδήποτε περιεχόμενο από το διαδίκτυο. Ορισμένοι ιστότοποι ενδέχεται να περιορίζουν την πρόσβαση.",
|
||||
"option": {
|
||||
"embedUrl": {
|
||||
"label": "URL ενσωμάτωσης"
|
||||
},
|
||||
"allowFullScreen": {
|
||||
"label": "Επιτρέψτε την πλήρη οθόνη"
|
||||
},
|
||||
"allowTransparency": {
|
||||
"label": "Να επιτρέπεται η διαφάνεια"
|
||||
},
|
||||
"allowScrolling": {
|
||||
"label": "Επιτρέπεται η κύλιση"
|
||||
},
|
||||
"allowPayment": {
|
||||
"label": "Επιτρέπονται πληρωμές"
|
||||
},
|
||||
"allowAutoPlay": {
|
||||
"label": "Επιτρέπεται η αυτόματη αναπαραγωγή"
|
||||
},
|
||||
"allowMicrophone": {
|
||||
"label": "Πρόσβαση στο μικρόφωνο"
|
||||
},
|
||||
"allowCamera": {
|
||||
"label": "Πρόσβαση στην κάμερα"
|
||||
},
|
||||
"allowGeolocation": {
|
||||
"label": "Επιτρέπεται ο γεωεντοπισμός"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"noBrowerSupport": "Ο περιηγητής σας δεν υποστηρίζει iframes. Παρακαλούμε ενημερώστε το πρόγραμμα περιήγησης."
|
||||
}
|
||||
},
|
||||
"smartHome-entityState": {
|
||||
"option": {
|
||||
"entityId": {
|
||||
"label": "Αναγνωριστικό οντότητας"
|
||||
}
|
||||
}
|
||||
},
|
||||
"smartHome-executeAutomation": {
|
||||
"option": {
|
||||
"displayName": {
|
||||
"label": "Εμφανιζόμενο όνομα"
|
||||
},
|
||||
"automationId": {
|
||||
"label": "Αναγνωριστικό αυτοματισμού"
|
||||
}
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
"name": "Ημερολόγιο",
|
||||
"option": {
|
||||
"releaseType": {
|
||||
"label": "Τύπος κυκλοφορίας Radarr"
|
||||
}
|
||||
}
|
||||
},
|
||||
"weather": {
|
||||
"name": "Καιρός",
|
||||
"description": "Εμφανίζει τις τρέχουσες πληροφορίες καιρού μιας καθορισμένης τοποθεσίας.",
|
||||
"option": {
|
||||
"location": {
|
||||
"label": "Τοποθεσία καιρού"
|
||||
}
|
||||
},
|
||||
"kind": {
|
||||
"clear": "Καθαρός",
|
||||
"mainlyClear": "Κυρίως καθαρός",
|
||||
"fog": "Ομίχλη",
|
||||
"drizzle": "Ψιχάλες",
|
||||
"freezingDrizzle": "Παγωμένο ψιλόβροχο",
|
||||
"rain": "Βροχή",
|
||||
"freezingRain": "Παγωμένη βροχή",
|
||||
"snowFall": "Χιονόπτωση",
|
||||
"snowGrains": "Κόκκοι χιονιού",
|
||||
"rainShowers": "Βροχοπτώσεις",
|
||||
"snowShowers": "Χιονοπτώσεις",
|
||||
"thunderstorm": "Καταιγίδα",
|
||||
"thunderstormWithHail": "Καταιγίδα με χαλάζι",
|
||||
"unknown": "Άγνωστο"
|
||||
}
|
||||
},
|
||||
"indexerManager": {
|
||||
"name": "Κατάσταση διαχειριστή indexer",
|
||||
"title": "Διαχειριστής indexer",
|
||||
"testAll": "Δοκιμή όλων"
|
||||
},
|
||||
"healthMonitoring": {
|
||||
"name": "Παρακολούθηση της υγείας του συστήματος",
|
||||
"description": "Εμφανίζει πληροφορίες που δείχνουν την κατάσταση και την υγεία του/ων συστήματος/ων σας.",
|
||||
"option": {
|
||||
"fahrenheit": {
|
||||
"label": "Θερμοκρασία CPU σε Φαρενάιτ"
|
||||
},
|
||||
"cpu": {
|
||||
"label": "Εμφάνιση πληροφοριών επεξεργαστή"
|
||||
},
|
||||
"memory": {
|
||||
"label": "Εμφάνιση Πληροφοριών Μνήμης"
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Εμφάνιση Πληροφοριών Συστήματος Αρχείων"
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"available": "Διαθέσιμο"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"search": "Αναζήτηση",
|
||||
"table": {
|
||||
"header": {},
|
||||
"action": {},
|
||||
"population": {
|
||||
"fallback": "Άγνωστο"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"video": {
|
||||
"name": "Ροή Βίντεο",
|
||||
"description": "Ενσωματώστε μια ροή βίντεο ή βίντεο από μια κάμερα ή έναν ιστότοπο",
|
||||
"option": {
|
||||
"feedUrl": {
|
||||
"label": "URL τροφοδοσίας"
|
||||
},
|
||||
"hasAutoPlay": {
|
||||
"label": "Αυτόματη αναπαραγωγή"
|
||||
}
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"items": {
|
||||
"added": {
|
||||
"detailsTitle": "Ημερομηνία Προσθήκης"
|
||||
},
|
||||
"downSpeed": {
|
||||
"columnTitle": "Κάτω",
|
||||
"detailsTitle": "Ταχύτητα Λήψης"
|
||||
},
|
||||
"integration": {
|
||||
"columnTitle": "Ενσωμάτωση"
|
||||
},
|
||||
"progress": {
|
||||
"columnTitle": "Πρόοδος"
|
||||
},
|
||||
"ratio": {
|
||||
"columnTitle": "Αναλογία"
|
||||
},
|
||||
"state": {
|
||||
"columnTitle": "Κατάσταση"
|
||||
},
|
||||
"upSpeed": {
|
||||
"columnTitle": "Πάνω"
|
||||
}
|
||||
},
|
||||
"states": {
|
||||
"downloading": "Λήψη",
|
||||
"queued": "Στην ουρά",
|
||||
"paused": "Σε παύση",
|
||||
"completed": "Ολοκληρώθηκε",
|
||||
"unknown": "Άγνωστο"
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"description": "Δείτε μια λίστα με όλα τα αιτήματα μέσων ενημέρωσης από την περίπτωση Overseerr ή Jellyseerr",
|
||||
"option": {
|
||||
"linksTargetNewTab": {
|
||||
"label": "Άνοιγμα συνδέσμων σε νέα καρτέλα"
|
||||
}
|
||||
},
|
||||
"availability": {
|
||||
"unknown": "Άγνωστο",
|
||||
"partiallyAvailable": "Μερικώς",
|
||||
"available": "Διαθέσιμο"
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestStats": {
|
||||
"description": "Στατιστικά στοιχεία σχετικά με τα αιτήματά σας για τα μέσα ενημέρωσης",
|
||||
"titles": {
|
||||
"stats": {
|
||||
"main": "Στατιστικά Πολυμέσων",
|
||||
"approved": "Ήδη εγκεκριμένα",
|
||||
"pending": "Εκκρεμείς εγκρίσεις",
|
||||
"tv": "Αιτήσεις TV",
|
||||
"movie": "Αιτήσεις ταινιών",
|
||||
"total": "Σύνολο"
|
||||
},
|
||||
"users": {
|
||||
"main": "Κορυφαίοι Χρήστες"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"action": {
|
||||
"oldImport": {
|
||||
"form": {
|
||||
"apps": {
|
||||
"label": "Εφαρμογές"
|
||||
},
|
||||
"screenSize": {
|
||||
"option": {
|
||||
"sm": "Μικρό",
|
||||
"md": "Μεσαίο",
|
||||
"lg": "Μεγάλο"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"backgroundImageAttachment": {
|
||||
"label": "Συνημμένη εικόνα φόντου"
|
||||
},
|
||||
"backgroundImageSize": {
|
||||
"label": "Μέγεθος εικόνας φόντου"
|
||||
},
|
||||
"primaryColor": {
|
||||
"label": "Βασικό χρώμα"
|
||||
},
|
||||
"secondaryColor": {
|
||||
"label": "Δευτερεύον χρώμα"
|
||||
},
|
||||
"customCss": {
|
||||
"description": "Περαιτέρω, προσαρμόστε τον πίνακα ελέγχου σας χρησιμοποιώντας CSS, συνιστάται μόνο για έμπειρους χρήστες"
|
||||
},
|
||||
"name": {
|
||||
"label": "Όνομα"
|
||||
},
|
||||
"isPublic": {
|
||||
"label": "Δημόσιο"
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
"section": {
|
||||
"general": {
|
||||
"title": "Γενικά"
|
||||
},
|
||||
"layout": {
|
||||
"title": "Διάταξη"
|
||||
},
|
||||
"background": {
|
||||
"title": "Φόντο"
|
||||
},
|
||||
"access": {
|
||||
"permission": {
|
||||
"item": {
|
||||
"view": {
|
||||
"label": "Προβολή πίνακα"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dangerZone": {
|
||||
"title": "Επικίνδυνη Περιοχή",
|
||||
"action": {
|
||||
"delete": {
|
||||
"confirm": {
|
||||
"title": "Διαγραφή πίνακα"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"management": {
|
||||
"navbar": {
|
||||
"items": {
|
||||
"home": "Αρχική",
|
||||
"boards": "Πίνακες",
|
||||
"apps": "Εφαρμογές",
|
||||
"users": {
|
||||
"label": "Χρήστες",
|
||||
"items": {
|
||||
"manage": "Διαχείριση",
|
||||
"invites": "Προσκλήσεις"
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"label": "Εργαλεία",
|
||||
"items": {
|
||||
"docker": "Docker",
|
||||
"api": "API"
|
||||
}
|
||||
},
|
||||
"settings": "Ρυθμίσεις",
|
||||
"help": {
|
||||
"label": "Βοήθεια",
|
||||
"items": {
|
||||
"documentation": "Τεκμηρίωση",
|
||||
"discord": "Κοινότητα Discord"
|
||||
}
|
||||
},
|
||||
"about": "Σχετικά"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"home": {
|
||||
"statistic": {
|
||||
"board": "Πίνακες",
|
||||
"user": "Χρήστες",
|
||||
"invite": "Προσκλήσεις",
|
||||
"app": "Εφαρμογές"
|
||||
},
|
||||
"statisticLabel": {
|
||||
"boards": "Πίνακες"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"title": "Οι πίνακές σας",
|
||||
"action": {
|
||||
"settings": {
|
||||
"label": "Ρυθμίσεις"
|
||||
},
|
||||
"setHomeBoard": {
|
||||
"badge": {
|
||||
"label": "Αρχική"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"label": "Οριστική διαγραφή",
|
||||
"confirm": {
|
||||
"title": "Διαγραφή πίνακα"
|
||||
}
|
||||
}
|
||||
},
|
||||
"modal": {
|
||||
"createBoard": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Όνομα"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"setting": {
|
||||
"general": {
|
||||
"title": "Γενικά",
|
||||
"item": {
|
||||
"firstDayOfWeek": "Πρώτη ημέρα της εβδομάδας",
|
||||
"accessibility": "Προσβασιμότητα"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"title": "Ασφάλεια"
|
||||
},
|
||||
"board": {
|
||||
"title": "Πίνακες"
|
||||
}
|
||||
},
|
||||
"list": {
|
||||
"metaTitle": "Διαχείριση χρηστών",
|
||||
"title": "Χρήστες"
|
||||
},
|
||||
"create": {
|
||||
"metaTitle": "Δημιουργία χρήστη",
|
||||
"step": {
|
||||
"security": {
|
||||
"label": "Ασφάλεια"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invite": {
|
||||
"title": "Διαχείριση προσκλήσεων χρηστών",
|
||||
"action": {
|
||||
"new": {
|
||||
"description": "Μετά τη λήξη, μια πρόσκληση δε θα είναι πλέον έγκυρη και ο παραλήπτης της πρόσκλησης δε θα είναι σε θέση να δημιουργήσει λογαριασμό."
|
||||
},
|
||||
"copy": {
|
||||
"link": "Σύνδεσμος πρόσκλησης"
|
||||
},
|
||||
"delete": {
|
||||
"title": "Διαγραφή πρόσκλησης",
|
||||
"description": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτή την πρόσκληση; Οι χρήστες με αυτόν τον σύνδεσμο δεν θα μπορούν πλέον να δημιουργήσουν λογαριασμό χρησιμοποιώντας αυτόν τον σύνδεσμο."
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"id": {
|
||||
"label": "Αναγνωριστικό (ID)"
|
||||
},
|
||||
"creator": {
|
||||
"label": "Δημιουργός"
|
||||
},
|
||||
"expirationDate": {
|
||||
"label": "Ημερομηνία λήξης"
|
||||
},
|
||||
"token": {
|
||||
"label": "Token"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"setting": {
|
||||
"general": {
|
||||
"title": "Γενικά"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Ρυθμίσεις"
|
||||
},
|
||||
"tool": {
|
||||
"tasks": {
|
||||
"status": {
|
||||
"running": "Εκτελείται",
|
||||
"error": "Σφάλμα"
|
||||
},
|
||||
"job": {
|
||||
"mediaServer": {
|
||||
"label": "Διακομιστής πολυμέσων"
|
||||
},
|
||||
"mediaRequests": {
|
||||
"label": "Αιτήματα μέσων ενημέρωσης"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api": {
|
||||
"title": "API",
|
||||
"tab": {
|
||||
"documentation": {
|
||||
"label": "Τεκμηρίωση"
|
||||
},
|
||||
"apiKey": {
|
||||
"table": {
|
||||
"header": {
|
||||
"id": "Αναγνωριστικό (ID)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"docker": {
|
||||
"title": "Containers",
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Όνομα"
|
||||
},
|
||||
"state": {
|
||||
"label": "Κατάσταση",
|
||||
"option": {
|
||||
"created": "Δημιουργήθηκε",
|
||||
"running": "Εκτελείται",
|
||||
"paused": "Σε παύση",
|
||||
"restarting": "Γίνεται επανεκκίνηση",
|
||||
"removing": "Αφαιρείται"
|
||||
}
|
||||
},
|
||||
"containerImage": {
|
||||
"label": "Εικόνα"
|
||||
},
|
||||
"ports": {
|
||||
"label": "Θύρες"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"start": {
|
||||
"label": "Έναρξη"
|
||||
},
|
||||
"stop": {
|
||||
"label": "Διακοπή"
|
||||
},
|
||||
"restart": {
|
||||
"label": "Επανεκκίνηση"
|
||||
},
|
||||
"remove": {
|
||||
"label": "Αφαίρεση"
|
||||
}
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"tab": {
|
||||
"user": "Χρήστες"
|
||||
},
|
||||
"field": {
|
||||
"user": {
|
||||
"label": "Χρήστης"
|
||||
}
|
||||
}
|
||||
},
|
||||
"navigationStructure": {
|
||||
"manage": {
|
||||
"label": "Διαχείριση",
|
||||
"boards": {
|
||||
"label": "Πίνακες"
|
||||
},
|
||||
"integrations": {
|
||||
"edit": {
|
||||
"label": "Επεξεργασία"
|
||||
}
|
||||
},
|
||||
"search-engines": {
|
||||
"edit": {
|
||||
"label": "Επεξεργασία"
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"label": "Εφαρμογές",
|
||||
"edit": {
|
||||
"label": "Επεξεργασία"
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"label": "Χρήστες",
|
||||
"create": {
|
||||
"label": "Δημιουργία"
|
||||
},
|
||||
"general": "Γενικά",
|
||||
"security": "Ασφάλεια",
|
||||
"board": "Πίνακες",
|
||||
"invites": {
|
||||
"label": "Προσκλήσεις"
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"label": "Εργαλεία",
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"label": "Ρυθμίσεις"
|
||||
},
|
||||
"about": {
|
||||
"label": "Σχετικά"
|
||||
}
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"mode": {
|
||||
"appIntegrationBoard": {
|
||||
"group": {
|
||||
"app": {
|
||||
"title": "Εφαρμογές"
|
||||
},
|
||||
"board": {
|
||||
"title": "Πίνακες"
|
||||
}
|
||||
}
|
||||
},
|
||||
"external": {
|
||||
"group": {
|
||||
"searchEngine": {
|
||||
"option": {
|
||||
"torrent": {
|
||||
"name": "Τόρρεντ"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"help": {
|
||||
"group": {
|
||||
"help": {
|
||||
"title": "Βοήθεια",
|
||||
"option": {
|
||||
"documentation": {
|
||||
"label": "Τεκμηρίωση"
|
||||
},
|
||||
"discord": {
|
||||
"label": "Κοινότητα Discord"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"group": {
|
||||
"page": {
|
||||
"option": {
|
||||
"manageUser": {
|
||||
"label": "Διαχείριση χρηστών"
|
||||
},
|
||||
"about": {
|
||||
"label": "Σχετικά"
|
||||
},
|
||||
"preferences": {
|
||||
"label": "Οι ρυθμίσεις σας"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"userGroup": {
|
||||
"group": {
|
||||
"user": {
|
||||
"title": "Χρήστες"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"engine": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Όνομα"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2644
packages/translation/src/lang/en.json
Normal file
2644
packages/translation/src/lang/en.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
911
packages/translation/src/lang/es.json
Normal file
911
packages/translation/src/lang/es.json
Normal file
@@ -0,0 +1,911 @@
|
||||
{
|
||||
"user": {
|
||||
"title": "Usuarios",
|
||||
"name": "Usuario",
|
||||
"field": {
|
||||
"email": {
|
||||
"label": "Correo electrónico"
|
||||
},
|
||||
"username": {
|
||||
"label": "Nombre de usuario"
|
||||
},
|
||||
"password": {
|
||||
"label": "Contraseña",
|
||||
"requirement": {
|
||||
"lowercase": "Incluye letra minúscula",
|
||||
"uppercase": "Incluye letra mayúscula",
|
||||
"number": "Incluye número"
|
||||
}
|
||||
},
|
||||
"passwordConfirm": {
|
||||
"label": "Confirmar contraseña"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"login": {
|
||||
"label": "Iniciar sesión"
|
||||
},
|
||||
"register": {
|
||||
"label": "Crear cuenta",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "Cuenta creada"
|
||||
}
|
||||
}
|
||||
},
|
||||
"create": "Crear usuario"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"field": {
|
||||
"name": "Nombre"
|
||||
},
|
||||
"permission": {
|
||||
"admin": {
|
||||
"title": "Administrador"
|
||||
},
|
||||
"board": {
|
||||
"title": "Tableros"
|
||||
}
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"page": {
|
||||
"list": {
|
||||
"title": "Aplicaciones"
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Nombre"
|
||||
}
|
||||
}
|
||||
},
|
||||
"integration": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Nombre"
|
||||
}
|
||||
},
|
||||
"testConnection": {
|
||||
"notification": {
|
||||
"invalidUrl": {
|
||||
"title": "URL invalida"
|
||||
}
|
||||
}
|
||||
},
|
||||
"secrets": {
|
||||
"kind": {
|
||||
"username": {
|
||||
"label": "Nombre de usuario"
|
||||
},
|
||||
"password": {
|
||||
"label": "Contraseña",
|
||||
"newLabel": "Nueva contraseña"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"field": {
|
||||
"name": "Nombre",
|
||||
"size": "Tamaño",
|
||||
"creator": "Creador"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"error": "Error",
|
||||
"action": {
|
||||
"add": "Añadir",
|
||||
"apply": "Aplicar",
|
||||
"create": "Crear",
|
||||
"edit": "Editar",
|
||||
"insert": "Insertar",
|
||||
"remove": "Eliminar",
|
||||
"save": "Guardar",
|
||||
"saveChanges": "Guardar cambios",
|
||||
"cancel": "Cancelar",
|
||||
"delete": "Eliminar",
|
||||
"confirm": "Confirmar",
|
||||
"previous": "Anterior",
|
||||
"next": "Siguiente",
|
||||
"tryAgain": "Inténtalo de nuevo"
|
||||
},
|
||||
"information": {
|
||||
"hours": "",
|
||||
"minutes": ""
|
||||
},
|
||||
"userAvatar": {
|
||||
"menu": {
|
||||
"preferences": "Tus preferencias",
|
||||
"login": "Iniciar sesión"
|
||||
}
|
||||
},
|
||||
"dangerZone": "Zona de riesgo",
|
||||
"noResults": "No se han encontrado resultados",
|
||||
"zod": {
|
||||
"errors": {
|
||||
"default": "Este campo no es válido",
|
||||
"required": "Este campo es obligatorio",
|
||||
"string": {
|
||||
"startsWith": "Este campo debe empezar con {startsWith}",
|
||||
"endsWith": "Este campo debe terminar con {endsWith}",
|
||||
"includes": "Este campo debe incluir {includes}"
|
||||
},
|
||||
"tooSmall": {
|
||||
"string": "Este campo debe tener al menos {minimum} caracteres",
|
||||
"number": "Este campo debe ser mayor o igual a {minimum}"
|
||||
},
|
||||
"tooBig": {
|
||||
"string": "Este campo debe tener como máximo {maximum} caracteres",
|
||||
"number": "Este campo debe ser menor o igual a {maximum}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"section": {
|
||||
"category": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Nombre"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"moveUp": "Mover hacia arriba",
|
||||
"moveDown": "Mover hacia abajo"
|
||||
},
|
||||
"menu": {
|
||||
"label": {
|
||||
"changePosition": "Cambiar posición"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"menu": {
|
||||
"label": {
|
||||
"settings": "Ajustes"
|
||||
}
|
||||
},
|
||||
"moveResize": {
|
||||
"field": {
|
||||
"width": {
|
||||
"label": "Ancho"
|
||||
},
|
||||
"height": {
|
||||
"label": "Alto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"app": {
|
||||
"option": {
|
||||
"openInNewTab": {
|
||||
"label": "Abrir en una pestaña nueva"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "Diseño",
|
||||
"option": {
|
||||
"row": {
|
||||
"label": "Horizontal"
|
||||
},
|
||||
"column": {
|
||||
"label": "Vertical"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"adsBlockedToday": "Bloqueados hoy",
|
||||
"adsBlockedTodayPercentage": "Bloqueados hoy",
|
||||
"dnsQueriesToday": "Consultas de hoy"
|
||||
}
|
||||
},
|
||||
"dnsHoleControls": {
|
||||
"description": "Controla Pihole o AdGuard desde tu panel",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "Diseño",
|
||||
"option": {
|
||||
"row": {
|
||||
"label": "Horizontal"
|
||||
},
|
||||
"column": {
|
||||
"label": "Vertical"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
"set": "",
|
||||
"enabled": "Activado",
|
||||
"disabled": "Desactivado",
|
||||
"hours": "",
|
||||
"minutes": ""
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
"description": "Muestra la fecha y hora actual.",
|
||||
"option": {
|
||||
"timezone": {
|
||||
"label": "Zona horaria"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Bloc de notas",
|
||||
"option": {
|
||||
"showToolbar": {
|
||||
"label": "Muestra la barra de herramientas para ayudarte a escribir Markdown"
|
||||
},
|
||||
"allowReadOnlyCheck": {
|
||||
"label": "Permitir verificación en modo solo lectura"
|
||||
},
|
||||
"content": {
|
||||
"label": "El contenido del Bloc de notas"
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
"bold": "Negrita",
|
||||
"italic": "Cursiva",
|
||||
"strikethrough": "Tachado",
|
||||
"underline": "Subrayado",
|
||||
"colorText": "Color de texto",
|
||||
"colorHighlight": "Texto resaltado en color",
|
||||
"code": "Código",
|
||||
"clear": "Borrar formato",
|
||||
"heading": "Encabezado {level}",
|
||||
"align": "Alinear texto: {position}",
|
||||
"blockquote": "Cita",
|
||||
"horizontalLine": "Línea horizontal",
|
||||
"bulletList": "Lista sin ordenar",
|
||||
"orderedList": "Lista ordenada",
|
||||
"checkList": "Lista de control",
|
||||
"increaseIndent": "Aumentar sangría",
|
||||
"decreaseIndent": "Disminuir sangría",
|
||||
"link": "Enlace",
|
||||
"unlink": "Eliminar enlace",
|
||||
"image": "Insertar imagen",
|
||||
"addTable": "Añadir tabla",
|
||||
"deleteTable": "Eliminar tabla",
|
||||
"colorCell": "Color de celda",
|
||||
"mergeCell": "Alternar combinación de celdas",
|
||||
"addColumnLeft": "Añadir columna a la izquierda",
|
||||
"addColumnRight": "Añadir columna a la derecha",
|
||||
"deleteColumn": "Eliminar columna",
|
||||
"addRowTop": "Añadir fila encima",
|
||||
"addRowBelow": "Añadir fila debajo",
|
||||
"deleteRow": "Eliminar fila"
|
||||
},
|
||||
"align": {
|
||||
"left": "Izquierda",
|
||||
"center": "Centrar",
|
||||
"right": "Derecha"
|
||||
},
|
||||
"popover": {
|
||||
"clearColor": "Eliminar color",
|
||||
"source": "Fuente",
|
||||
"widthPlaceholder": "Valor en % o píxeles",
|
||||
"columns": "Columnas",
|
||||
"rows": "Filas",
|
||||
"width": "Ancho",
|
||||
"height": "Alto"
|
||||
}
|
||||
},
|
||||
"iframe": {
|
||||
"name": "iFrame",
|
||||
"description": "Incrusta cualquier contenido de Internet. Algunos sitios web pueden restringir el acceso.",
|
||||
"option": {
|
||||
"embedUrl": {
|
||||
"label": "URL incrustada"
|
||||
},
|
||||
"allowFullScreen": {
|
||||
"label": "Permitir pantalla completa"
|
||||
},
|
||||
"allowTransparency": {
|
||||
"label": "Permitir transparencia"
|
||||
},
|
||||
"allowScrolling": {
|
||||
"label": "Permitir desplazamiento"
|
||||
},
|
||||
"allowPayment": {
|
||||
"label": "Permitir pago"
|
||||
},
|
||||
"allowAutoPlay": {
|
||||
"label": "Permitir reproducción automática"
|
||||
},
|
||||
"allowMicrophone": {
|
||||
"label": "Permitir micrófono"
|
||||
},
|
||||
"allowCamera": {
|
||||
"label": "Permitir cámara"
|
||||
},
|
||||
"allowGeolocation": {
|
||||
"label": "Permitir geolocalización"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"noBrowerSupport": "Tu navegador no soporta iframes. Por favor, actualice tu navegador."
|
||||
}
|
||||
},
|
||||
"smartHome-entityState": {
|
||||
"option": {
|
||||
"entityId": {
|
||||
"label": "ID de la entidad"
|
||||
}
|
||||
}
|
||||
},
|
||||
"smartHome-executeAutomation": {
|
||||
"option": {
|
||||
"displayName": {
|
||||
"label": "Nombre a mostrar"
|
||||
},
|
||||
"automationId": {
|
||||
"label": "ID de automatización"
|
||||
}
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
"name": "Calendario",
|
||||
"option": {
|
||||
"releaseType": {
|
||||
"label": "Tipo de lanzamiento de Radarr"
|
||||
}
|
||||
}
|
||||
},
|
||||
"weather": {
|
||||
"name": "El Tiempo",
|
||||
"description": "Muestra la información meteorológica actual de la ubicación establecida.",
|
||||
"option": {
|
||||
"location": {
|
||||
"label": "Ubicación"
|
||||
}
|
||||
},
|
||||
"kind": {
|
||||
"clear": "Despejado",
|
||||
"mainlyClear": "Mayormente despejado",
|
||||
"fog": "Niebla",
|
||||
"drizzle": "Llovizna",
|
||||
"freezingDrizzle": "Llovizna helada",
|
||||
"rain": "Lluvia",
|
||||
"freezingRain": "Lluvia helada",
|
||||
"snowFall": "Nevada",
|
||||
"snowGrains": "Granos de nieve",
|
||||
"rainShowers": "Chubascos",
|
||||
"snowShowers": "Chubascos de nieve",
|
||||
"thunderstorm": "Tormenta eléctrica",
|
||||
"thunderstormWithHail": "Tormenta con granizo",
|
||||
"unknown": "Desconocido"
|
||||
}
|
||||
},
|
||||
"indexerManager": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"testAll": ""
|
||||
},
|
||||
"healthMonitoring": {
|
||||
"name": "Monitorización de Salud del Sistema",
|
||||
"description": "Muestra información sobre la salud y el estado de tu(s) sistema(s).",
|
||||
"option": {
|
||||
"fahrenheit": {
|
||||
"label": "Temperatura de la CPU en grados Fahrenheit"
|
||||
},
|
||||
"cpu": {
|
||||
"label": "Mostrar información de la CPU"
|
||||
},
|
||||
"memory": {
|
||||
"label": "Mostrar información de la memoria"
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Mostrar información del sistema de archivos"
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"available": "Disponible"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"search": "Buscar",
|
||||
"table": {
|
||||
"header": {},
|
||||
"action": {},
|
||||
"population": {
|
||||
"fallback": "Desconocido"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"video": {
|
||||
"name": "Video en directo",
|
||||
"description": "Incrusta una transmisión de video o un video de una cámara o un sitio web",
|
||||
"option": {
|
||||
"feedUrl": {
|
||||
"label": "Fuente URL"
|
||||
},
|
||||
"hasAutoPlay": {
|
||||
"label": "Auto-reproducción"
|
||||
}
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"items": {
|
||||
"added": {
|
||||
"detailsTitle": ""
|
||||
},
|
||||
"downSpeed": {
|
||||
"columnTitle": "Descarga",
|
||||
"detailsTitle": "Velocidad de Descarga"
|
||||
},
|
||||
"integration": {
|
||||
"columnTitle": "Integración"
|
||||
},
|
||||
"progress": {
|
||||
"columnTitle": "Completado %"
|
||||
},
|
||||
"ratio": {
|
||||
"columnTitle": "Ratio"
|
||||
},
|
||||
"state": {
|
||||
"columnTitle": "Estado"
|
||||
},
|
||||
"upSpeed": {
|
||||
"columnTitle": "Subida"
|
||||
}
|
||||
},
|
||||
"states": {
|
||||
"downloading": "Descargando",
|
||||
"queued": "",
|
||||
"paused": "Pausado",
|
||||
"completed": "Completado",
|
||||
"unknown": "Desconocido"
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"description": "Mostrar una lista de todas las solicitudes multimedia de tu instancia de Overseerr o Jellyseerr",
|
||||
"option": {
|
||||
"linksTargetNewTab": {
|
||||
"label": "Abrir enlaces en una pestaña nueva"
|
||||
}
|
||||
},
|
||||
"availability": {
|
||||
"unknown": "Desconocido",
|
||||
"partiallyAvailable": "Parcial",
|
||||
"available": "Disponible"
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestStats": {
|
||||
"description": "Estadísticas sobre tus solicitudes multimedia",
|
||||
"titles": {
|
||||
"stats": {
|
||||
"main": "Estadísticas Multimedia",
|
||||
"approved": "Ya aprobado",
|
||||
"pending": "Aprobaciones pendientes",
|
||||
"tv": "Solicitudes de TV",
|
||||
"movie": "Solicitudes de películas",
|
||||
"total": "Total"
|
||||
},
|
||||
"users": {
|
||||
"main": "Mejores usuarios"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"action": {
|
||||
"oldImport": {
|
||||
"form": {
|
||||
"apps": {
|
||||
"label": "Aplicaciones"
|
||||
},
|
||||
"screenSize": {
|
||||
"option": {
|
||||
"sm": "Pequeño",
|
||||
"md": "Mediano",
|
||||
"lg": "Grande"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"backgroundImageAttachment": {
|
||||
"label": "Adjuntar imagen de fondo"
|
||||
},
|
||||
"backgroundImageSize": {
|
||||
"label": "Tamaño de la imagen de fondo"
|
||||
},
|
||||
"primaryColor": {
|
||||
"label": "Color primario"
|
||||
},
|
||||
"secondaryColor": {
|
||||
"label": "Color secundario"
|
||||
},
|
||||
"customCss": {
|
||||
"description": "Además, personaliza tu panel usando CSS, solo recomendado para usuarios avanzados"
|
||||
},
|
||||
"name": {
|
||||
"label": "Nombre"
|
||||
},
|
||||
"isPublic": {
|
||||
"label": "Pública"
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
"section": {
|
||||
"general": {
|
||||
"title": "General"
|
||||
},
|
||||
"layout": {
|
||||
"title": "Diseño"
|
||||
},
|
||||
"background": {
|
||||
"title": "Fondo"
|
||||
},
|
||||
"access": {
|
||||
"permission": {
|
||||
"item": {
|
||||
"view": {
|
||||
"label": "Ver tablero"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dangerZone": {
|
||||
"title": "Zona de riesgo",
|
||||
"action": {
|
||||
"delete": {
|
||||
"confirm": {
|
||||
"title": "Eliminar tablero"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"management": {
|
||||
"navbar": {
|
||||
"items": {
|
||||
"home": "Inicio",
|
||||
"boards": "Tableros",
|
||||
"apps": "Aplicaciones",
|
||||
"users": {
|
||||
"label": "Usuarios",
|
||||
"items": {
|
||||
"manage": "Administrar",
|
||||
"invites": "Invitaciones"
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"label": "Herramientas",
|
||||
"items": {
|
||||
"docker": "Docker",
|
||||
"api": "API"
|
||||
}
|
||||
},
|
||||
"settings": "Ajustes",
|
||||
"help": {
|
||||
"label": "Ayuda",
|
||||
"items": {
|
||||
"documentation": "Documentación",
|
||||
"discord": "Comunidad Discord"
|
||||
}
|
||||
},
|
||||
"about": "Acerca de"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"home": {
|
||||
"statistic": {
|
||||
"board": "Tableros",
|
||||
"user": "Usuarios",
|
||||
"invite": "Invitaciones",
|
||||
"app": "Aplicaciones"
|
||||
},
|
||||
"statisticLabel": {
|
||||
"boards": "Tableros"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"title": "Tus tableros",
|
||||
"action": {
|
||||
"settings": {
|
||||
"label": "Ajustes"
|
||||
},
|
||||
"setHomeBoard": {
|
||||
"badge": {
|
||||
"label": "Inicio"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"label": "Eliminar permanentemente",
|
||||
"confirm": {
|
||||
"title": "Eliminar tablero"
|
||||
}
|
||||
}
|
||||
},
|
||||
"modal": {
|
||||
"createBoard": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Nombre"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"setting": {
|
||||
"general": {
|
||||
"title": "General",
|
||||
"item": {
|
||||
"firstDayOfWeek": "Primer día de la semana",
|
||||
"accessibility": "Accesibilidad"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"title": "Seguridad"
|
||||
},
|
||||
"board": {
|
||||
"title": "Tableros"
|
||||
}
|
||||
},
|
||||
"list": {
|
||||
"metaTitle": "Administrar usuarios",
|
||||
"title": "Usuarios"
|
||||
},
|
||||
"create": {
|
||||
"metaTitle": "Crear usuario",
|
||||
"step": {
|
||||
"security": {
|
||||
"label": "Seguridad"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invite": {
|
||||
"title": "Administrar invitaciones de usuario",
|
||||
"action": {
|
||||
"new": {
|
||||
"description": "Después de la caducidad, una invitación ya no será válida y el destinatario de la invitación no podrá crear una cuenta."
|
||||
},
|
||||
"copy": {
|
||||
"link": "Link de invitación"
|
||||
},
|
||||
"delete": {
|
||||
"title": "Eliminar invitación",
|
||||
"description": "¿Estás seguro de que deseas eliminar esta invitación? Los usuarios con este enlace ya no podrán crear una cuenta usando ese enlace."
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"id": {
|
||||
"label": "ID"
|
||||
},
|
||||
"creator": {
|
||||
"label": "Creador"
|
||||
},
|
||||
"expirationDate": {
|
||||
"label": "Fecha de caducidad"
|
||||
},
|
||||
"token": {
|
||||
"label": "Token"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"setting": {
|
||||
"general": {
|
||||
"title": "General"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Ajustes"
|
||||
},
|
||||
"tool": {
|
||||
"tasks": {
|
||||
"status": {
|
||||
"running": "En ejecución",
|
||||
"error": "Error"
|
||||
},
|
||||
"job": {
|
||||
"mediaServer": {
|
||||
"label": "Servidor Multimedia"
|
||||
},
|
||||
"mediaRequests": {
|
||||
"label": "Solicitudes multimedia"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api": {
|
||||
"title": "API",
|
||||
"tab": {
|
||||
"documentation": {
|
||||
"label": "Documentación"
|
||||
},
|
||||
"apiKey": {
|
||||
"table": {
|
||||
"header": {
|
||||
"id": "ID"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"docker": {
|
||||
"title": "",
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Nombre"
|
||||
},
|
||||
"state": {
|
||||
"label": "Estado",
|
||||
"option": {
|
||||
"created": "Creado",
|
||||
"running": "En ejecución",
|
||||
"paused": "Pausado",
|
||||
"restarting": "Reiniciando",
|
||||
"removing": "Eliminando"
|
||||
}
|
||||
},
|
||||
"containerImage": {
|
||||
"label": "Imagen"
|
||||
},
|
||||
"ports": {
|
||||
"label": "Puertos"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"start": {
|
||||
"label": "Iniciar"
|
||||
},
|
||||
"stop": {
|
||||
"label": "Detener"
|
||||
},
|
||||
"restart": {
|
||||
"label": "Reiniciar"
|
||||
},
|
||||
"remove": {
|
||||
"label": "Eliminar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"tab": {
|
||||
"user": "Usuarios"
|
||||
},
|
||||
"field": {
|
||||
"user": {
|
||||
"label": "Usuario"
|
||||
}
|
||||
}
|
||||
},
|
||||
"navigationStructure": {
|
||||
"manage": {
|
||||
"label": "Administrar",
|
||||
"boards": {
|
||||
"label": "Tableros"
|
||||
},
|
||||
"integrations": {
|
||||
"edit": {
|
||||
"label": "Editar"
|
||||
}
|
||||
},
|
||||
"search-engines": {
|
||||
"edit": {
|
||||
"label": "Editar"
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"label": "Aplicaciones",
|
||||
"edit": {
|
||||
"label": "Editar"
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"label": "Usuarios",
|
||||
"create": {
|
||||
"label": "Crear"
|
||||
},
|
||||
"general": "General",
|
||||
"security": "Seguridad",
|
||||
"board": "Tableros",
|
||||
"invites": {
|
||||
"label": "Invitaciones"
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"label": "Herramientas",
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"label": "Ajustes"
|
||||
},
|
||||
"about": {
|
||||
"label": "Acerca de"
|
||||
}
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"mode": {
|
||||
"appIntegrationBoard": {
|
||||
"group": {
|
||||
"app": {
|
||||
"title": "Aplicaciones"
|
||||
},
|
||||
"board": {
|
||||
"title": "Tableros"
|
||||
}
|
||||
}
|
||||
},
|
||||
"external": {
|
||||
"group": {
|
||||
"searchEngine": {
|
||||
"option": {
|
||||
"torrent": {
|
||||
"name": "Torrents"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"help": {
|
||||
"group": {
|
||||
"help": {
|
||||
"title": "Ayuda",
|
||||
"option": {
|
||||
"documentation": {
|
||||
"label": "Documentación"
|
||||
},
|
||||
"discord": {
|
||||
"label": "Comunidad Discord"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"group": {
|
||||
"page": {
|
||||
"option": {
|
||||
"manageUser": {
|
||||
"label": "Administrar usuarios"
|
||||
},
|
||||
"about": {
|
||||
"label": "Acerca de"
|
||||
},
|
||||
"preferences": {
|
||||
"label": "Tus preferencias"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"userGroup": {
|
||||
"group": {
|
||||
"user": {
|
||||
"title": "Usuarios"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"engine": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Nombre"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user