From 0f767d14bb104abefef43c14df56690d08611320 Mon Sep 17 00:00:00 2001 From: Steven Harris Date: Fri, 27 Mar 2026 18:10:18 -0400 Subject: [PATCH 1/2] Feature: UniFi Drive (UNAS) service widget (#6461) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/unifi-drive.md | 24 ++++++ mkdocs.yml | 1 + public/locales/en/common.json | 5 ++ src/widgets/components.js | 1 + src/widgets/unifi_drive/component.jsx | 58 ++++++++++++++ src/widgets/unifi_drive/component.test.jsx | 92 ++++++++++++++++++++++ src/widgets/unifi_drive/proxy.js | 36 +++++++++ src/widgets/unifi_drive/proxy.test.js | 82 +++++++++++++++++++ src/widgets/unifi_drive/widget.js | 14 ++++ src/widgets/unifi_drive/widget.test.js | 11 +++ src/widgets/widgets.js | 2 + 11 files changed, 326 insertions(+) create mode 100644 docs/widgets/services/unifi-drive.md create mode 100644 src/widgets/unifi_drive/component.jsx create mode 100644 src/widgets/unifi_drive/component.test.jsx create mode 100644 src/widgets/unifi_drive/proxy.js create mode 100644 src/widgets/unifi_drive/proxy.test.js create mode 100644 src/widgets/unifi_drive/widget.js create mode 100644 src/widgets/unifi_drive/widget.test.js diff --git a/docs/widgets/services/unifi-drive.md b/docs/widgets/services/unifi-drive.md new file mode 100644 index 000000000..90843140e --- /dev/null +++ b/docs/widgets/services/unifi-drive.md @@ -0,0 +1,24 @@ +--- +title: UniFi Drive +description: UniFi Drive Widget Configuration +--- + +Learn more about [UniFi Drive](https://ui.com/integrations/network-storage). + +## Configuration + +Displays storage statistics from your UniFi Network Attached Storage (UNAS) device. Requires a local UniFi account with at least read privileges. + +Allowed fields: `["total", "used", "available", "status"]` + +```yaml +widget: + type: unifi_drive + url: https://unifi.host.or.ip + username: your_username + password: your_password +``` + +!!! hint + + If you enter incorrect credentials and receive an "API Error", you may need to recreate the container or restart the service to clear the cache. diff --git a/mkdocs.yml b/mkdocs.yml index b08277808..1933825c5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -171,6 +171,7 @@ nav: - widgets/services/truenas.md - widgets/services/tubearchivist.md - widgets/services/unifi-controller.md + - widgets/services/unifi-drive.md - widgets/services/unmanic.md - widgets/services/unraid.md - widgets/services/uptime-kuma.md diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 66a6a34a7..9528341b8 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Subsystem status unknown" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/src/widgets/components.js b/src/widgets/components.js index 472ddd684..c5f144e39 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -147,6 +147,7 @@ const components = { tubearchivist: dynamic(() => import("./tubearchivist/component")), truenas: dynamic(() => import("./truenas/component")), unifi: dynamic(() => import("./unifi/component")), + unifi_drive: dynamic(() => import("./unifi_drive/component")), unmanic: dynamic(() => import("./unmanic/component")), unraid: dynamic(() => import("./unraid/component")), uptimekuma: dynamic(() => import("./uptimekuma/component")), diff --git a/src/widgets/unifi_drive/component.jsx b/src/widgets/unifi_drive/component.jsx new file mode 100644 index 000000000..a616dbd20 --- /dev/null +++ b/src/widgets/unifi_drive/component.jsx @@ -0,0 +1,58 @@ +import Block from "components/services/widget/block"; +import Container from "components/services/widget/container"; +import { useTranslation } from "next-i18next"; + +import useWidgetAPI from "utils/proxy/use-widget-api"; + +export default function Component({ service }) { + const { t } = useTranslation(); + const { widget } = service; + + const { data: storageData, error: storageError } = useWidgetAPI(widget, "storage"); + + if (storageError) { + return ; + } + + if (!storageData) { + return ( + + + + + + + ); + } + + const { data: storage } = storageData; + + if (!storage) { + return ( + + + + ); + } + + const { totalQuota, usage, status } = storage; + const totalBytes = totalQuota ?? 0; + const usedBytes = (usage?.system || 0) + (usage?.myDrives || 0) + (usage?.sharedDrives || 0); + const availableBytes = Math.max(0, totalBytes - usedBytes); + let statusValue = status; + if (status === "healthy") statusValue = t("unifi_drive.healthy"); + else if (status === "degraded") statusValue = t("unifi_drive.degraded"); + + return ( + + + + + + + ); +} diff --git a/src/widgets/unifi_drive/component.test.jsx b/src/widgets/unifi_drive/component.test.jsx new file mode 100644 index 000000000..46f23ce1e --- /dev/null +++ b/src/widgets/unifi_drive/component.test.jsx @@ -0,0 +1,92 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { renderWithProviders } from "test-utils/render-with-providers"; +import { expectBlockValue } from "test-utils/widget-assertions"; + +const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() })); + +vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI })); + +import Component from "./component"; + +describe("widgets/unifi_drive/component", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders placeholders while loading", () => { + useWidgetAPI.mockReturnValue({ data: undefined, error: undefined }); + + const service = { widget: { type: "unifi_drive" } }; + const { container } = renderWithProviders(, { settings: { hideErrors: false } }); + + expect(container.querySelectorAll(".service-block")).toHaveLength(4); + expect(screen.getByText("resources.total")).toBeInTheDocument(); + expect(screen.getByText("resources.used")).toBeInTheDocument(); + expect(screen.getByText("resources.free")).toBeInTheDocument(); + expect(screen.getByText("widget.status")).toBeInTheDocument(); + }); + + it("renders error when API fails", () => { + useWidgetAPI.mockReturnValue({ data: undefined, error: new Error("fail") }); + + const service = { widget: { type: "unifi_drive" } }; + renderWithProviders(, { settings: { hideErrors: false } }); + + expect(screen.getAllByText("widget.api_error", { exact: false }).length).toBeGreaterThan(0); + }); + + it("renders no_data when storage data is missing", () => { + useWidgetAPI.mockReturnValue({ data: { data: null }, error: undefined }); + + const service = { widget: { type: "unifi_drive" } }; + renderWithProviders(, { settings: { hideErrors: false } }); + + expect(screen.getByText("unifi_drive.no_data")).toBeInTheDocument(); + }); + + it("renders storage statistics when data is loaded", () => { + useWidgetAPI.mockReturnValue({ + data: { + data: { + totalQuota: 1000000000000, + usage: { system: 100000000000, myDrives: 200000000000, sharedDrives: 50000000000 }, + status: "healthy", + }, + }, + error: undefined, + }); + + const service = { widget: { type: "unifi_drive" } }; + const { container } = renderWithProviders(, { settings: { hideErrors: false } }); + + expect(container.querySelectorAll(".service-block")).toHaveLength(4); + expectBlockValue(container, "resources.total", 1000000000000); + expectBlockValue(container, "resources.used", 350000000000); + expectBlockValue(container, "resources.free", 650000000000); + expectBlockValue(container, "widget.status", "unifi_drive.healthy"); + }); + + it("renders degraded status", () => { + useWidgetAPI.mockReturnValue({ + data: { + data: { + totalQuota: 100, + usage: { system: 10, myDrives: 20, sharedDrives: 5 }, + status: "degraded", + }, + }, + error: undefined, + }); + + const service = { widget: { type: "unifi_drive" } }; + const { container } = renderWithProviders(, { settings: { hideErrors: false } }); + + expect(container.querySelectorAll(".service-block")).toHaveLength(4); + expectBlockValue(container, "widget.status", "unifi_drive.degraded"); + expectBlockValue(container, "resources.free", 65); + }); +}); diff --git a/src/widgets/unifi_drive/proxy.js b/src/widgets/unifi_drive/proxy.js new file mode 100644 index 000000000..02dff2ef3 --- /dev/null +++ b/src/widgets/unifi_drive/proxy.js @@ -0,0 +1,36 @@ +import getServiceWidget from "utils/config/service-helpers"; +import createUnifiProxyHandler from "utils/proxy/handlers/unifi"; +import { httpProxy } from "utils/proxy/http"; + +const drivePrefix = "/proxy/drive"; + +async function getWidget(req, logger) { + const { group, service, index } = req.query; + if (!group || !service) return null; + + const widget = await getServiceWidget(group, service, index); + if (!widget) { + logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); + return null; + } + return widget; +} + +async function resolveRequestContext({ cachedPrefix, widget }) { + if (cachedPrefix !== null) { + return { prefix: cachedPrefix }; + } + + const [, , , responseHeaders] = await httpProxy(widget.url); + + return { + prefix: drivePrefix, + csrfToken: responseHeaders?.["x-csrf-token"], + }; +} + +export default createUnifiProxyHandler({ + proxyName: "unifiDriveProxyHandler", + resolveWidget: getWidget, + resolveRequestContext, +}); diff --git a/src/widgets/unifi_drive/proxy.test.js b/src/widgets/unifi_drive/proxy.test.js new file mode 100644 index 000000000..07eef7480 --- /dev/null +++ b/src/widgets/unifi_drive/proxy.test.js @@ -0,0 +1,82 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import createMockRes from "test-utils/create-mock-res"; + +const { httpProxy, getServiceWidget, cache, logger } = vi.hoisted(() => { + const store = new Map(); + return { + httpProxy: vi.fn(), + getServiceWidget: vi.fn(), + cache: { + get: vi.fn((k) => (store.has(k) ? store.get(k) : null)), + put: vi.fn((k, v) => store.set(k, v)), + del: vi.fn((k) => store.delete(k)), + _reset: () => store.clear(), + }, + logger: { debug: vi.fn(), error: vi.fn() }, + }; +}); + +vi.mock("memory-cache", () => ({ default: cache, ...cache })); +vi.mock("utils/logger", () => ({ default: () => logger })); +vi.mock("utils/config/service-helpers", () => ({ default: getServiceWidget })); +vi.mock("utils/proxy/http", () => ({ httpProxy })); +vi.mock("widgets/widgets", () => ({ + default: { unifi_drive: { api: "{url}{prefix}/api/{endpoint}" } }, +})); + +import unifiDriveProxyHandler from "./proxy"; + +const widgetConfig = { type: "unifi_drive", url: "http://unifi", username: "u", password: "p" }; + +describe("widgets/unifi_drive/proxy", () => { + beforeEach(() => { + vi.clearAllMocks(); + cache._reset(); + }); + + it("returns 400 when widget config is missing", async () => { + getServiceWidget.mockResolvedValue(null); + const res = createMockRes(); + await unifiDriveProxyHandler( + { query: { group: "g", service: "s", endpoint: "v1/systems/storage?type=detail" } }, + res, + ); + expect(res.statusCode).toBe(400); + }); + + it("returns 403 when widget type has no API config", async () => { + getServiceWidget.mockResolvedValue({ ...widgetConfig, type: "unknown" }); + const res = createMockRes(); + await unifiDriveProxyHandler({ query: { group: "g", service: "s", endpoint: "storage" } }, res); + expect(res.statusCode).toBe(403); + }); + + it("uses /proxy/drive prefix and returns data on success", async () => { + getServiceWidget.mockResolvedValue({ ...widgetConfig }); + httpProxy + .mockResolvedValueOnce([200, "text/html", Buffer.from(""), {}]) + .mockResolvedValueOnce([200, "application/json", Buffer.from('{"data":{}}'), {}]); + + const res = createMockRes(); + await unifiDriveProxyHandler({ query: { group: "g", service: "s", endpoint: "storage" } }, res); + + expect(httpProxy.mock.calls[0][0]).toBe("http://unifi"); + expect(httpProxy.mock.calls[1][0].toString()).toContain("/proxy/drive/api/"); + expect(cache.put).toHaveBeenCalledWith("unifiDriveProxyHandler__prefix.s", "/proxy/drive"); + expect(res.statusCode).toBe(200); + }); + + it("skips prefix detection when cached", async () => { + getServiceWidget.mockResolvedValue({ ...widgetConfig }); + cache.put("unifiDriveProxyHandler__prefix.s", "/proxy/drive"); + httpProxy.mockResolvedValueOnce([200, "application/json", Buffer.from('{"data":{}}'), {}]); + + const res = createMockRes(); + await unifiDriveProxyHandler({ query: { group: "g", service: "s", endpoint: "storage" } }, res); + + expect(httpProxy).toHaveBeenCalledTimes(1); + expect(httpProxy.mock.calls[0][0].toString()).toContain("/proxy/drive/api/"); + expect(res.statusCode).toBe(200); + }); +}); diff --git a/src/widgets/unifi_drive/widget.js b/src/widgets/unifi_drive/widget.js new file mode 100644 index 000000000..4e78c5a5f --- /dev/null +++ b/src/widgets/unifi_drive/widget.js @@ -0,0 +1,14 @@ +import unifiDriveProxyHandler from "./proxy"; + +const widget = { + api: "{url}{prefix}/api/{endpoint}", + proxyHandler: unifiDriveProxyHandler, + + mappings: { + storage: { + endpoint: "v1/systems/storage?type=detail", + }, + }, +}; + +export default widget; diff --git a/src/widgets/unifi_drive/widget.test.js b/src/widgets/unifi_drive/widget.test.js new file mode 100644 index 000000000..87f60f69e --- /dev/null +++ b/src/widgets/unifi_drive/widget.test.js @@ -0,0 +1,11 @@ +import { describe, it } from "vitest"; + +import { expectWidgetConfigShape } from "test-utils/widget-config"; + +import widget from "./widget"; + +describe("unifi_drive widget config", () => { + it("exports a valid widget config", () => { + expectWidgetConfigShape(widget); + }); +}); diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 533410bdc..be7f685e4 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -137,6 +137,7 @@ import trilium from "./trilium/widget"; import truenas from "./truenas/widget"; import tubearchivist from "./tubearchivist/widget"; import unifi from "./unifi/widget"; +import unifi_drive from "./unifi_drive/widget"; import unmanic from "./unmanic/widget"; import unraid from "./unraid/widget"; import uptimekuma from "./uptimekuma/widget"; @@ -296,6 +297,7 @@ const widgets = { truenas, unifi, unifi_console: unifi, + unifi_drive, unmanic, unraid, uptimekuma, From 7b552f50807b073cbd460ed1e7bbe2d879597305 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:16:31 -0700 Subject: [PATCH 2/2] New Crowdin translations by GitHub Action (#6433) Co-authored-by: Crowdin Bot --- public/locales/af/common.json | 5 ++ public/locales/ar/common.json | 5 ++ public/locales/bg/common.json | 5 ++ public/locales/ca/common.json | 5 ++ public/locales/cs/common.json | 5 ++ public/locales/da/common.json | 5 ++ public/locales/de/common.json | 25 ++++--- public/locales/el/common.json | 5 ++ public/locales/eo/common.json | 5 ++ public/locales/es/common.json | 5 ++ public/locales/eu/common.json | 5 ++ public/locales/fi/common.json | 5 ++ public/locales/fr/common.json | 5 ++ public/locales/he/common.json | 5 ++ public/locales/hi/common.json | 5 ++ public/locales/hr/common.json | 5 ++ public/locales/hu/common.json | 5 ++ public/locales/id/common.json | 5 ++ public/locales/it/common.json | 5 ++ public/locales/ja/common.json | 5 ++ public/locales/ko/common.json | 103 +++++++++++++++-------------- public/locales/lv/common.json | 5 ++ public/locales/ms/common.json | 5 ++ public/locales/nl/common.json | 5 ++ public/locales/no/common.json | 5 ++ public/locales/pl/common.json | 5 ++ public/locales/pt/common.json | 5 ++ public/locales/pt_BR/common.json | 73 ++++++++++---------- public/locales/ro/common.json | 5 ++ public/locales/ru/common.json | 5 ++ public/locales/sk/common.json | 5 ++ public/locales/sl/common.json | 5 ++ public/locales/sr/common.json | 5 ++ public/locales/sv/common.json | 5 ++ public/locales/te/common.json | 5 ++ public/locales/th/common.json | 5 ++ public/locales/tr/common.json | 5 ++ public/locales/uk/common.json | 5 ++ public/locales/vi/common.json | 5 ++ public/locales/yue/common.json | 5 ++ public/locales/zh-Hans/common.json | 5 ++ public/locales/zh-Hant/common.json | 5 ++ 42 files changed, 303 insertions(+), 93 deletions(-) diff --git a/public/locales/af/common.json b/public/locales/af/common.json index 0fc63a043..c591fe4d8 100644 --- a/public/locales/af/common.json +++ b/public/locales/af/common.json @@ -66,6 +66,11 @@ "wait": "Wag asseblief", "empty_data": "Substelsel status onbekend" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/ar/common.json b/public/locales/ar/common.json index 4b61311a1..915a3a4f6 100644 --- a/public/locales/ar/common.json +++ b/public/locales/ar/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "حالة النظام الفرعي غير معروفة" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "استقبال", "tx": "ارسال", diff --git a/public/locales/bg/common.json b/public/locales/bg/common.json index 516fbc93a..6a6fa0a07 100644 --- a/public/locales/bg/common.json +++ b/public/locales/bg/common.json @@ -66,6 +66,11 @@ "wait": "Моля изчакайте", "empty_data": "Неизвестен статус на подсистема" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "ПЧ", "tx": "ИЗ", diff --git a/public/locales/ca/common.json b/public/locales/ca/common.json index 43d2b2830..293a81de7 100644 --- a/public/locales/ca/common.json +++ b/public/locales/ca/common.json @@ -66,6 +66,11 @@ "wait": "Si us plau espera", "empty_data": "Estat del subsistema desconegut" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "Rebut", "tx": "Transmès", diff --git a/public/locales/cs/common.json b/public/locales/cs/common.json index d1e67c0f0..b4f7b2b62 100644 --- a/public/locales/cs/common.json +++ b/public/locales/cs/common.json @@ -66,6 +66,11 @@ "wait": "Čekejte prosím", "empty_data": "Stav podsystému neznámý" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/da/common.json b/public/locales/da/common.json index 0c920cbdb..62ee45db4 100644 --- a/public/locales/da/common.json +++ b/public/locales/da/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Subsystem status ukendt" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/de/common.json b/public/locales/de/common.json index c5fb9a67c..27d4a73c7 100644 --- a/public/locales/de/common.json +++ b/public/locales/de/common.json @@ -66,6 +66,11 @@ "wait": "Bitte warten", "empty_data": "Subsystem-Status unbekannt" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", @@ -115,7 +120,7 @@ "movies": "Filme", "series": "Serien", "episodes": "Episoden", - "songs": "Songs" + "songs": "Titel" }, "esphome": { "offline": "Offline", @@ -185,10 +190,10 @@ "plex_connection_error": "Prüfe Plex-Verbindung" }, "tracearr": { - "no_active": "No Active Streams", + "no_active": "Keine aktiven Streams", "streams": "Streams", - "transcodes": "Transcodes", - "directplay": "Direct Play", + "transcodes": "Transkodieren", + "directplay": "Direkte Wiedergabe", "bitrate": "Bitrate" }, "omada": { @@ -290,10 +295,10 @@ "available": "Verfügbar" }, "seerr": { - "pending": "Pending", - "approved": "Approved", - "available": "Available", - "completed": "Completed", + "pending": "Ausstehend", + "approved": "Bestätigt", + "available": "Verfügbar", + "completed": "Abgeschlossen", "processing": "Processing", "issues": "Open Issues" }, @@ -811,7 +816,7 @@ "series": "Serien" }, "booklore": { - "libraries": "Libraries", + "libraries": "Bibliotheken", "books": "Bücher", "reading": "Reading", "finished": "Finished" @@ -1176,7 +1181,7 @@ "environment_not_found": "Umgebung nicht gefunden" }, "sparkyfitness": { - "eaten": "Eaten", + "eaten": "", "burned": "Burned", "remaining": "Remaining", "steps": "Steps" diff --git a/public/locales/el/common.json b/public/locales/el/common.json index f6f64577f..51ed63e3a 100644 --- a/public/locales/el/common.json +++ b/public/locales/el/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Άγνωστη κατάσταση υποσυστήματος" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/eo/common.json b/public/locales/eo/common.json index 2da22914a..598de6243 100644 --- a/public/locales/eo/common.json +++ b/public/locales/eo/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Subsistemostatuso nekonata" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/es/common.json b/public/locales/es/common.json index 6bd5fb263..c1d95aac4 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -66,6 +66,11 @@ "wait": "Espere, por favor", "empty_data": "Se desconoce el estado del subsistema" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "Recibido", "tx": "Transmitido", diff --git a/public/locales/eu/common.json b/public/locales/eu/common.json index 2d2be6637..5bbf72aa0 100644 --- a/public/locales/eu/common.json +++ b/public/locales/eu/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Subsystem status unknown" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/fi/common.json b/public/locales/fi/common.json index 42434aec7..057ecd95d 100644 --- a/public/locales/fi/common.json +++ b/public/locales/fi/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Subsystem status unknown" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/fr/common.json b/public/locales/fr/common.json index fa5279a01..94c48892b 100644 --- a/public/locales/fr/common.json +++ b/public/locales/fr/common.json @@ -66,6 +66,11 @@ "wait": "Veuillez patienter", "empty_data": "Statut du sous-système inconnu" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "Rx", "tx": "Tx", diff --git a/public/locales/he/common.json b/public/locales/he/common.json index 2897e0ddd..4eadb695f 100644 --- a/public/locales/he/common.json +++ b/public/locales/he/common.json @@ -66,6 +66,11 @@ "wait": "נא להמתין", "empty_data": "מצב תת-מערכת לא ידוע" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/hi/common.json b/public/locales/hi/common.json index 45f0ffc21..a91365abd 100644 --- a/public/locales/hi/common.json +++ b/public/locales/hi/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Subsystem status unknown" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/hr/common.json b/public/locales/hr/common.json index d6b8147d9..4217b2ad8 100644 --- a/public/locales/hr/common.json +++ b/public/locales/hr/common.json @@ -66,6 +66,11 @@ "wait": "Pričekaj", "empty_data": "Stanje podsustava nepoznato" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/hu/common.json b/public/locales/hu/common.json index 9191e2d02..d3475519e 100644 --- a/public/locales/hu/common.json +++ b/public/locales/hu/common.json @@ -66,6 +66,11 @@ "wait": "Kérjük várjon", "empty_data": "Az alrendszer állapota ismeretlen" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/id/common.json b/public/locales/id/common.json index 12a1b1061..753110daf 100644 --- a/public/locales/id/common.json +++ b/public/locales/id/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Status subsistem tdk diketahui" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/it/common.json b/public/locales/it/common.json index 992e2aee7..d7c992812 100644 --- a/public/locales/it/common.json +++ b/public/locales/it/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Stato del sottosistema sconosciuto" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/ja/common.json b/public/locales/ja/common.json index 787598995..d78ee2dac 100644 --- a/public/locales/ja/common.json +++ b/public/locales/ja/common.json @@ -66,6 +66,11 @@ "wait": "お待ちください", "empty_data": "サブシステムの状態は不明" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "受信済み", "tx": "送信済み", diff --git a/public/locales/ko/common.json b/public/locales/ko/common.json index 060cb0996..3f8e3cd3e 100644 --- a/public/locales/ko/common.json +++ b/public/locales/ko/common.json @@ -66,6 +66,11 @@ "wait": "잠시만 기다려주세요", "empty_data": "서브시스템 상태 알 수 없음" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "수신", "tx": "송신", @@ -108,14 +113,14 @@ "songs": "음악" }, "jellyfin": { - "playing": "Playing", - "transcoding": "Transcoding", - "bitrate": "Bitrate", - "no_active": "No Active Streams", - "movies": "Movies", - "series": "Series", - "episodes": "Episodes", - "songs": "Songs" + "playing": "재생 중", + "transcoding": "트랜스코딩 중", + "bitrate": "비트레이트", + "no_active": "활성 스트림 없음", + "movies": "영상", + "series": "시리즈", + "episodes": "에피소드", + "songs": "음악" }, "esphome": { "offline": "오프라인", @@ -185,11 +190,11 @@ "plex_connection_error": "Plex 연결 확인" }, "tracearr": { - "no_active": "No Active Streams", - "streams": "Streams", - "transcodes": "Transcodes", - "directplay": "Direct Play", - "bitrate": "Bitrate" + "no_active": "활성 스트림 없음", + "streams": "스트림", + "transcodes": "트랜스코드", + "directplay": "다이렉트 플레이", + "bitrate": "비트레이트" }, "omada": { "connectedAp": "연결된 AP", @@ -290,12 +295,12 @@ "available": "이용 가능" }, "seerr": { - "pending": "Pending", - "approved": "Approved", - "available": "Available", - "completed": "Completed", - "processing": "Processing", - "issues": "Open Issues" + "pending": "대기 중", + "approved": "승인됨", + "available": "사용 가능", + "completed": "완료됨", + "processing": "처리 중", + "issues": "열린 이슈" }, "netalertx": { "total": "전체", @@ -546,7 +551,7 @@ "up": "업", "pending": "대기 중", "down": "다운", - "ok": "Ok" + "ok": "확인" }, "healthchecks": { "new": "신규", @@ -618,9 +623,9 @@ "sites": "Sites", "resources": "Resources", "targets": "Targets", - "traffic": "Traffic", - "in": "In", - "out": "Out" + "traffic": "트래픽", + "in": "수신", + "out": "송신" }, "peanut": { "battery_charge": "배터리 충전", @@ -719,8 +724,8 @@ "volumeAvailable": "사용 가능" }, "dispatcharr": { - "channels": "Channels", - "streams": "Streams" + "channels": "채널", + "streams": "스트림" }, "mylar": { "series": "시리즈", @@ -787,7 +792,7 @@ "gross_percent_today": "오늘", "gross_percent_1y": "1년", "gross_percent_max": "전체 기간", - "net_worth": "Net Worth" + "net_worth": "순자산" }, "audiobookshelf": { "podcasts": "팟캐스트", @@ -811,10 +816,10 @@ "series": "시리즈" }, "booklore": { - "libraries": "Libraries", - "books": "Books", - "reading": "Reading", - "finished": "Finished" + "libraries": "라이브러리", + "books": "책", + "reading": "읽는 중", + "finished": "완료" }, "jdownloader": { "downloadCount": "대기열", @@ -1150,30 +1155,30 @@ "bytes_added_30": "추가된 용량" }, "yourspotify": { - "songs": "Songs", - "time": "Time", - "artists": "Artists" + "songs": "음악", + "time": "시간", + "artists": "아티스트" }, "arcane": { - "containers": "Containers", - "images": "Images", - "image_updates": "Image Updates", - "images_unused": "Unused", - "environment_required": "Environment ID Required" + "containers": "컨테이너", + "images": "이미지", + "image_updates": "이미지 업데이트", + "images_unused": "미사용", + "environment_required": "환경 ID 필요" }, "dockhand": { - "running": "Running", - "stopped": "Stopped", + "running": "실행 중", + "stopped": "정지됨", "cpu": "CPU", - "memory": "Memory", - "images": "Images", - "volumes": "Volumes", - "events_today": "Events Today", - "pending_updates": "Pending Updates", - "stacks": "Stacks", - "paused": "Paused", - "total": "Total", - "environment_not_found": "Environment Not Found" + "memory": "메모리", + "images": "이미지", + "volumes": "볼륨", + "events_today": "오늘의 이벤트", + "pending_updates": "대기 중인 업데이트", + "stacks": "스택", + "paused": "일시정지됨", + "total": "전체", + "environment_not_found": "환경 없음" }, "sparkyfitness": { "eaten": "Eaten", diff --git a/public/locales/lv/common.json b/public/locales/lv/common.json index 411b95249..fb269367a 100644 --- a/public/locales/lv/common.json +++ b/public/locales/lv/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Subsystem status unknown" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/ms/common.json b/public/locales/ms/common.json index 48af4fc1b..cb4772a86 100644 --- a/public/locales/ms/common.json +++ b/public/locales/ms/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Status subsistem tak diketahui" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/nl/common.json b/public/locales/nl/common.json index b5bcee4ae..8bbbd0b57 100644 --- a/public/locales/nl/common.json +++ b/public/locales/nl/common.json @@ -66,6 +66,11 @@ "wait": "Even geduld", "empty_data": "Subsysteem status onbekend" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/no/common.json b/public/locales/no/common.json index a07e6ff23..4214c6860 100644 --- a/public/locales/no/common.json +++ b/public/locales/no/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Ukjent undersystemstatus" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/pl/common.json b/public/locales/pl/common.json index e11b48569..ec0f54bed 100644 --- a/public/locales/pl/common.json +++ b/public/locales/pl/common.json @@ -66,6 +66,11 @@ "wait": "Proszę czekać", "empty_data": "Status podsystemu nieznany" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "Rx", "tx": "Tx", diff --git a/public/locales/pt/common.json b/public/locales/pt/common.json index 0275558fd..d9d056c87 100644 --- a/public/locales/pt/common.json +++ b/public/locales/pt/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Status de Subsistema Desconhecido" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "Rx", "tx": "Tx", diff --git a/public/locales/pt_BR/common.json b/public/locales/pt_BR/common.json index 67669eb18..c208f3055 100644 --- a/public/locales/pt_BR/common.json +++ b/public/locales/pt_BR/common.json @@ -14,7 +14,7 @@ "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", "duration": "{{value, duration}}", - "months": "M", + "months": "mo", "days": "d", "hours": "h", "minutes": "m", @@ -66,9 +66,14 @@ "wait": "Por favor, aguarde", "empty_data": "Status do Subsistema desconhecido" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { - "rx": "Rx", - "tx": "Tx", + "rx": "RX", + "tx": "TX", "mem": "MEM", "cpu": "CPU", "running": "Executando", @@ -101,21 +106,21 @@ "playing": "A reproduzir", "transcoding": "Transcodificação", "bitrate": "Taxa de bits", - "no_active": "Sem Streams Ativos", + "no_active": "Sem Transmissões Ativas", "movies": "Filmes", "series": "Séries", "episodes": "Episódios", "songs": "Canções" }, "jellyfin": { - "playing": "Playing", + "playing": "Jogando", "transcoding": "Transcoding", "bitrate": "Bitrate", "no_active": "No Active Streams", - "movies": "Movies", - "series": "Series", - "episodes": "Episodes", - "songs": "Songs" + "movies": "Filmes", + "series": "Séries", + "episodes": "Episódios", + "songs": "Músicas" }, "esphome": { "offline": "Offline", @@ -290,12 +295,12 @@ "available": "Disponível" }, "seerr": { - "pending": "Pending", - "approved": "Approved", - "available": "Available", - "completed": "Completed", - "processing": "Processing", - "issues": "Open Issues" + "pending": "Pendente", + "approved": "Aprovado", + "available": "Disponível", + "completed": "Concluído", + "processing": "Processando", + "issues": "Erros pendentes" }, "netalertx": { "total": "Total", @@ -616,7 +621,7 @@ "pangolin": { "orgs": "Orgs", "sites": "Sites", - "resources": "Resources", + "resources": "Recursos", "targets": "Targets", "traffic": "Traffic", "in": "In", @@ -719,8 +724,8 @@ "volumeAvailable": "Disponível" }, "dispatcharr": { - "channels": "Channels", - "streams": "Streams" + "channels": "Canais", + "streams": "Transmissões" }, "mylar": { "series": "Séries", @@ -811,10 +816,10 @@ "series": "Séries" }, "booklore": { - "libraries": "Libraries", - "books": "Books", - "reading": "Reading", - "finished": "Finished" + "libraries": "Bibliotecas", + "books": "Livros", + "reading": "Lendo", + "finished": "Finalizado" }, "jdownloader": { "downloadCount": "Fila de espera", @@ -1155,23 +1160,23 @@ "artists": "Artistas" }, "arcane": { - "containers": "Containers", - "images": "Images", - "image_updates": "Image Updates", - "images_unused": "Unused", + "containers": "Recipientes", + "images": "Imagens", + "image_updates": "Atualizações de Imagem", + "images_unused": "Não utilizado", "environment_required": "Environment ID Required" }, "dockhand": { - "running": "Running", + "running": "Executando", "stopped": "Stopped", "cpu": "CPU", - "memory": "Memory", - "images": "Images", - "volumes": "Volumes", - "events_today": "Events Today", - "pending_updates": "Pending Updates", - "stacks": "Stacks", - "paused": "Paused", + "memory": "Memória", + "images": "Imagens", + "volumes": "Quantidades", + "events_today": "Eventos hoje", + "pending_updates": "Atualizações pendentes", + "stacks": "Pilhas", + "paused": "Pausado", "total": "Total", "environment_not_found": "Environment Not Found" }, diff --git a/public/locales/ro/common.json b/public/locales/ro/common.json index 813dcc45e..1f415d796 100644 --- a/public/locales/ro/common.json +++ b/public/locales/ro/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Starea subsistemului este necunoscut" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json index 0fa464fc7..c58d1f220 100644 --- a/public/locales/ru/common.json +++ b/public/locales/ru/common.json @@ -66,6 +66,11 @@ "wait": "Пожалуйста, подождите", "empty_data": "Статус подсистемы неизвестен" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/sk/common.json b/public/locales/sk/common.json index 917d70923..57359f65d 100644 --- a/public/locales/sk/common.json +++ b/public/locales/sk/common.json @@ -66,6 +66,11 @@ "wait": "Čakajte, prosím", "empty_data": "Stav podsystému neznámy" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "Prijaté", "tx": "Odoslané", diff --git a/public/locales/sl/common.json b/public/locales/sl/common.json index bcc63b1b7..ae1ad2d4a 100644 --- a/public/locales/sl/common.json +++ b/public/locales/sl/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Neznani status podsistema" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/sr/common.json b/public/locales/sr/common.json index 110217cd3..6c9efb6f7 100644 --- a/public/locales/sr/common.json +++ b/public/locales/sr/common.json @@ -66,6 +66,11 @@ "wait": "Молим сачекајте", "empty_data": "Статус подсистема непознат" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/sv/common.json b/public/locales/sv/common.json index ef92e38b0..47ada9e49 100644 --- a/public/locales/sv/common.json +++ b/public/locales/sv/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Subsystem status unknown" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/te/common.json b/public/locales/te/common.json index f027f0f5a..c5194fbc2 100644 --- a/public/locales/te/common.json +++ b/public/locales/te/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Subsystem status unknown" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/th/common.json b/public/locales/th/common.json index b38d9db4a..419c9c247 100644 --- a/public/locales/th/common.json +++ b/public/locales/th/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "Subsystem status unknown" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/tr/common.json b/public/locales/tr/common.json index a48822f63..fb90b25e5 100644 --- a/public/locales/tr/common.json +++ b/public/locales/tr/common.json @@ -66,6 +66,11 @@ "wait": "Lütfen bekleyin", "empty_data": "Alt sistem durumu bilinmiyor" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "Gelen Veri", "tx": "Giden Veri", diff --git a/public/locales/uk/common.json b/public/locales/uk/common.json index 9775de051..f45057d7a 100644 --- a/public/locales/uk/common.json +++ b/public/locales/uk/common.json @@ -66,6 +66,11 @@ "wait": "Будь ласка, зачекайте", "empty_data": "Статус підсистеми невідомий" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/vi/common.json b/public/locales/vi/common.json index 6efa0a65f..d700bfb77 100644 --- a/public/locales/vi/common.json +++ b/public/locales/vi/common.json @@ -66,6 +66,11 @@ "wait": "Vui lòng chờ", "empty_data": "Trạng thái hệ thống phụ không xác định" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/public/locales/yue/common.json b/public/locales/yue/common.json index 4e9ed87a5..d65cb96f0 100644 --- a/public/locales/yue/common.json +++ b/public/locales/yue/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "子系統狀態未知" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "接收", "tx": "發送", diff --git a/public/locales/zh-Hans/common.json b/public/locales/zh-Hans/common.json index dab59e434..295196b22 100644 --- a/public/locales/zh-Hans/common.json +++ b/public/locales/zh-Hans/common.json @@ -66,6 +66,11 @@ "wait": "请稍候", "empty_data": "子系统状态未知" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "接收", "tx": "发送", diff --git a/public/locales/zh-Hant/common.json b/public/locales/zh-Hant/common.json index 32c812862..ef0485202 100644 --- a/public/locales/zh-Hant/common.json +++ b/public/locales/zh-Hant/common.json @@ -66,6 +66,11 @@ "wait": "Please wait", "empty_data": "子系統狀態未知" }, + "unifi_drive": { + "healthy": "Healthy", + "degraded": "Degraded", + "no_data": "No storage data available" + }, "docker": { "rx": "接收", "tx": "傳送",