Kaynağa Gözat

Merge remote-tracking branch 'forkorigin/main' into features/basic-docker-swarm

Vinay Dawani 2 yıl önce
ebeveyn
işleme
f51e755216
100 değiştirilmiş dosya ile 6100 ekleme ve 797 silme
  1. 9 0
      .all-contributorsrc
  2. 6 0
      .devcontainer/Dockerfile
  3. 27 0
      .devcontainer/devcontainer.json
  4. 11 0
      .devcontainer/setup.sh
  5. 10 0
      .github/ISSUE_TEMPLATE/bug_report.yml
  6. 9 9
      .github/workflows/docker-publish.yml
  7. 3 3
      Dockerfile
  8. 17 47
      README.md
  9. 1 1
      next-i18next.config.js
  10. 365 0
      public/locales/ar/common.json
  11. 83 3
      public/locales/bg/common.json
  12. 83 3
      public/locales/ca/common.json
  13. 365 0
      public/locales/cs/common.json
  14. 365 0
      public/locales/da/common.json
  15. 151 71
      public/locales/de/common.json
  16. 83 3
      public/locales/en/common.json
  17. 365 0
      public/locales/eo/common.json
  18. 91 11
      public/locales/es/common.json
  19. 83 3
      public/locales/fi/common.json
  20. 87 7
      public/locales/fr/common.json
  21. 83 3
      public/locales/he/common.json
  22. 365 0
      public/locales/hi/common.json
  23. 181 101
      public/locales/hr/common.json
  24. 83 3
      public/locales/hu/common.json
  25. 119 39
      public/locales/it/common.json
  26. 365 0
      public/locales/ms/common.json
  27. 83 3
      public/locales/nb-NO/common.json
  28. 83 3
      public/locales/nl/common.json
  29. 177 97
      public/locales/pl/common.json
  30. 85 5
      public/locales/pt-BR/common.json
  31. 98 18
      public/locales/pt/common.json
  32. 83 3
      public/locales/ro/common.json
  33. 96 16
      public/locales/ru/common.json
  34. 83 3
      public/locales/sr/common.json
  35. 83 3
      public/locales/sv/common.json
  36. 148 68
      public/locales/te/common.json
  37. 146 66
      public/locales/tr/common.json
  38. 83 3
      public/locales/vi/common.json
  39. 83 3
      public/locales/yue/common.json
  40. 107 27
      public/locales/zh-CN/common.json
  41. 83 3
      public/locales/zh-Hant/common.json
  42. 1 1
      src/components/bookmarks/group.jsx
  43. 7 1
      src/components/bookmarks/item.jsx
  44. 7 6
      src/components/quicklaunch.jsx
  45. 37 0
      src/components/resolvedicon.jsx
  46. 9 1
      src/components/services/group.jsx
  47. 27 49
      src/components/services/item.jsx
  48. 44 0
      src/components/services/ping.jsx
  49. 38 5
      src/components/services/status.jsx
  50. 1 1
      src/components/services/widget/block.jsx
  51. 3 5
      src/components/services/widget/container.jsx
  52. 50 0
      src/components/services/widget/error.jsx
  53. 11 14
      src/components/version.jsx
  54. 11 10
      src/components/widgets/datetime/datetime.jsx
  55. 1 1
      src/components/widgets/openmeteo/icon.jsx
  56. 1 1
      src/components/widgets/openweathermap/weather.jsx
  57. 2 2
      src/components/widgets/resources/memory.jsx
  58. 1 1
      src/components/widgets/unifi_console/unifi_console.jsx
  59. 0 3
      src/pages/_document.jsx
  60. 1 1
      src/pages/api/docker/stats/[...service].js
  61. 2 0
      src/pages/api/docker/status/[...service].js
  62. 35 0
      src/pages/api/ping.js
  63. 3 2
      src/pages/api/widgets/openmeteo.js
  64. 12 4
      src/pages/index.jsx
  65. 1 1
      src/skeleton/bookmarks.yaml
  66. 1 1
      src/skeleton/docker.yaml
  67. 1 1
      src/skeleton/services.yaml
  68. 1 1
      src/skeleton/settings.yaml
  69. 1 1
      src/skeleton/widgets.yaml
  70. 4 0
      src/styles/globals.css
  71. 21 4
      src/utils/config/api-response.js
  72. 1 1
      src/utils/config/config.js
  73. 2 0
      src/utils/config/service-helpers.js
  74. 6 1
      src/utils/logger.js
  75. 10 1
      src/utils/proxy/handlers/credentialed.js
  76. 7 0
      src/utils/proxy/handlers/generic.js
  77. 82 0
      src/utils/proxy/handlers/jsonrpc.js
  78. 15 30
      src/utils/proxy/http.js
  79. 7 1
      src/utils/proxy/use-widget-api.js
  80. 22 0
      src/utils/proxy/validate-widget-data.js
  81. 211 0
      src/utils/weather/openmeteo-condition-map.js
  82. 2 2
      src/widgets/adguard/component.jsx
  83. 2 1
      src/widgets/authentik/component.jsx
  84. 40 0
      src/widgets/autobrr/component.jsx
  85. 24 0
      src/widgets/autobrr/widget.js
  86. 3 2
      src/widgets/bazarr/component.jsx
  87. 6 2
      src/widgets/changedetectionio/component.jsx
  88. 4 3
      src/widgets/coinmarketcap/component.jsx
  89. 13 0
      src/widgets/components.js
  90. 52 0
      src/widgets/deluge/component.jsx
  91. 63 0
      src/widgets/deluge/proxy.js
  92. 8 0
      src/widgets/deluge/widget.js
  93. 41 0
      src/widgets/diskstation/component.jsx
  94. 70 0
      src/widgets/diskstation/proxy.js
  95. 14 0
      src/widgets/diskstation/widget.js
  96. 6 3
      src/widgets/docker/component.jsx
  97. 4 4
      src/widgets/emby/component.jsx
  98. 1 1
      src/widgets/emby/widget.js
  99. 53 0
      src/widgets/flood/component.jsx
  100. 66 0
      src/widgets/flood/proxy.js

+ 9 - 0
.all-contributorsrc

@@ -0,0 +1,9 @@
+{
+  "projectName": "homepage",
+  "projectOwner": "benphelps",
+  "files": [
+    "README.md"
+  ],
+  "imageSize": 100,
+  "contributors": []
+}

+ 6 - 0
.devcontainer/Dockerfile

@@ -0,0 +1,6 @@
+ARG VARIANT="16-buster"
+FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:${VARIANT}
+
+RUN npm install -g pnpm
+
+ENV PATH="${PATH}:./node_modules/.bin"

+ 27 - 0
.devcontainer/devcontainer.json

@@ -0,0 +1,27 @@
+{
+	"name": "homepage",
+	"build": {
+		"dockerfile": "Dockerfile",
+		"args": {
+			"VARIANT": "18-buster"
+		}
+	},
+	"customizations": {
+		"vscode": {
+			"extensions": [
+				"dbaeumer.vscode-eslint",
+				"mhutchie.git-graph",
+				"streetsidesoftware.code-spell-checker",
+			],
+			"settings": {
+				"eslint.format.enable": true,
+				"eslint.lintTask.enable": true,
+				"eslint.packageManager": "pnpm"
+			}
+		}
+	},
+	"postCreateCommand": ".devcontainer/setup.sh",
+	"forwardPorts": [
+		3000
+	]
+}

+ 11 - 0
.devcontainer/setup.sh

@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+# Install Node packages
+pnpm install
+
+# Copy in skeleton configuration if there is no existing configuration
+if [ ! -d "config/" ]; then
+  echo "Adding skeleton config"
+  mkdir config/
+  cp -r src/skeleton/* config
+fi

+ 10 - 0
.github/ISSUE_TEMPLATE/bug_report.yml

@@ -59,6 +59,16 @@ body:
       label: Configuration
       description: Please provide any relevant service, widget or otherwise related configuration here
       render: yaml
+  - type: textarea
+    id: container-logs
+    attributes:
+      label: Container Logs
+      description: Please review and provide any logs from the container, if relevant
+  - type: textarea
+    id: browser-logs
+    attributes:
+      label: Browser Logs
+      description: Please review and provide any relevant logs from the browser, if relevant
   - type: textarea
     id: other
     attributes:

+ 9 - 9
.github/workflows/docker-publish.yml

@@ -43,7 +43,7 @@ jobs:
         if: github.event_name != 'pull_request'
         uses: sigstore/cosign-installer@main
         with:
-          cosign-release: 'v1.11.0' # optional
+          cosign-release: 'v1.13.1' # optional
 
       # Setup QEMU
       # https://github.com/marketplace/actions/docker-setup-buildx#with-qemu
@@ -100,7 +100,7 @@ jobs:
             REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
           # https://github.com/docker/setup-qemu-action#about
           # platforms: linux/amd64,linux/arm64,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6
-          platforms: linux/amd64,linux/arm64,linux/arm/v7
+          platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
           cache-from: type=local,src=/tmp/.buildx-cache
           cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
 
@@ -109,13 +109,13 @@ jobs:
       # repository is public to avoid leaking data.  If you would like to publish
       # transparency data even for private images, pass --force to cosign below.
       # https://github.com/sigstore/cosign
-      - name: Sign the published Docker image
-        if: ${{ github.event_name != 'pull_request' }}
-        env:
-          COSIGN_EXPERIMENTAL: "true"
-        # This step uses the identity token to provision an ephemeral certificate
-        # against the sigstore community Fulcio instance.
-        run: echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign {}@${{ steps.build-and-push.outputs.digest }}
+#       - name: Sign the published Docker image
+#         if: ${{ github.event_name != 'pull_request' }}
+#         env:
+#           COSIGN_EXPERIMENTAL: "true"
+#         # This step uses the identity token to provision an ephemeral certificate
+#         # against the sigstore community Fulcio instance.
+#         run: echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign {}@${{ steps.build-and-push.outputs.digest }}
 
       # Temp fix
       # https://github.com/docker/build-push-action/issues/252

+ 3 - 3
Dockerfile

@@ -1,7 +1,7 @@
 # syntax = docker/dockerfile:latest
 
 # Install dependencies only when needed
-FROM node:current-alpine AS deps
+FROM docker.io/node:18-alpine AS deps
 
 WORKDIR /app
 
@@ -19,7 +19,7 @@ RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store pnpm f
 RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store pnpm install -r --offline
 
 # Rebuild the source code only when needed
-FROM node:current-alpine AS builder
+FROM docker.io/node:18-alpine AS builder
 WORKDIR /app
 
 ARG BUILDTIME
@@ -37,7 +37,7 @@ RUN <<EOF
 EOF
 
 # Production image, copy all the files and run next
-FROM node:current-alpine AS runner
+FROM docker.io/node:18-alpine AS runner
 LABEL org.opencontainers.image.title "Homepage"
 LABEL org.opencontainers.image.description "A self-hosted services landing page, with docker and service integrations."
 LABEL org.opencontainers.image.url="https://github.com/benphelps/homepage"

+ 17 - 47
README.md

@@ -38,7 +38,7 @@
 - Images built for AMD64 (x86_64), ARM64, ARMv7 and ARMv6
   - Supports all Raspberry Pi's, most SBCs & Apple Silicon
 - Full i18n support with automatic language detection
-  - Translations for Catalan, Chinese, Dutch, Finnish, French, German, Hebrew, Hungarian, Norwegian Bokmål, Polish, Portuguese, Portuguese (Brazil), Romanian, Russian, Spanish, Swedish and Yue
+  - Translations for Catalan, Chinese, Dutch, Finnish, French, German, Hebrew, Hungarian, Malay, Norwegian Bokmål, Polish, Portuguese, Portuguese (Brazil), Romanian, Russian, Spanish, Swedish and Yue
   - Want to help translate? [Join the Weblate project](https://hosted.weblate.org/engage/homepage/)
 - Service & Web Bookmarks
 - Docker Integration
@@ -47,12 +47,12 @@
 - Service Integration
   - Sonarr, Radarr, Readarr, Prowlarr, Bazarr, Lidarr, Emby, Jellyfin, Tautulli (Plex)
   - Ombi, Overseerr, Jellyseerr, Jackett, NZBGet, SABnzbd, ruTorrent, Transmission, qBittorrent
-  - Portainer, Traefik, Speedtest Tracker, PiHole, AdGuard Home, Nginx Proxy Manager, Gotify, Syncthing Relay Server, Authentic, Proxmox
+  - Portainer, Traefik, Speedtest Tracker, PiHole, AdGuard Home, Nginx Proxy Manager, Gotify, Syncthing Relay Server, Authentik, Proxmox
 - Information Providers
   - Coin Market Cap, Mastodon
 - Information & Utility Widgets
   - System Stats (Disk, CPU, Memory)
-  - Weather via WeatherAPI.com or OpenWeatherMap
+  - Weather via [OpenWeatherMap](https://openweathermap.org/) or [Open-Meteo](https://open-meteo.com/)
   - Search Bar
 - Customizable
   - 21 theme colors with light and dark mode support
@@ -144,47 +144,17 @@ This is a [Next.js](https://nextjs.org/) application, see their doucmentation fo
 
 ## Contributors
 
-Huge thanks to the all the contributors who have helped make this project what it is today! In alphabetical order:
-
-- [aidenpwnz](https://github.com/benphelps/homepage/commits?author=aidenpwnz) - Nginx Proxy Manager, Search Bar Widget
-- [AlexFullmoon](https://github.com/benphelps/homepage/commits?author=AlexFullmoon) - OpenWeatherMap Widget
-- [andrii-kryvoviaz](https://github.com/benphelps/homepage/commits?author=andrii-kryvoviaz) - Background opacity option
-- [DevPGSV](https://github.com/benphelps/homepage/commits?author=DevPGSV) - Syncthing Relay Server & Mastodon widgets
-- [ilusi0n](https://github.com/benphelps/homepage/commits?author=ilusi0n) - Jellyseerr Integration
-- [ItsJustMeChris](https://github.com/benphelps/homepage/commits?author=ItsJustMeChris) - Coin Market Cap Widget
-- [JazzFisch](https://github.com/benphelps/homepage/commits?author=JazzFisch) - Readarr, Bazarr, Lidarr, SABnzbd, Transmission, qBittorrent, Proxmox Integrations & countless more improvements
-- [josways](https://github.com/benphelps/homepage/commits?author=josways) - Baidu search provider
-- [mauricio-kalil](https://github.com/benphelps/homepage/commits?author=mauricio-kalil) - Portuguese (Brazil)
-- [modem7](https://github.com/benphelps/homepage/commits?author=modem7) - Impvoed Docker Image
-- [MountainGod2](https://github.com/benphelps/homepage/discussions/243) - Homepage Logo
-- [quod](https://github.com/benphelps/homepage/commits?author=quod) - Fixed Typos
-- [schklom](https://github.com/benphelps/homepage/commits?author=schklom) - ARM64, ARMv7 and ARMv6
-- [xicopitz](https://github.com/benphelps/homepage/commits?author=xicopitz) - Gotify & Prowlarr Integration
-
-### Translators
-
-- [3vilson](https://github.com/benphelps/homepage/commits?author=3vilson) - German
-- [4lenz1](https://github.com/benphelps/homepage/commits?author=4lenz1) - Chinese
-- [AmadeusGraves](https://github.com/benphelps/homepage/commits?author=AmadeusGraves) - Spanish
-- [boerniee](https://github.com/benphelps/homepage/commits?author=boerniee) - German
-- [brunoccr](https://github.com/benphelps/homepage/commits?author=brunoccr) - Portuguese (Brazil)
-- [C8opmBM](https://github.com/benphelps/homepage/commits?author=C8opmBM) - Romainian
-- [comradekingu](https://github.com/benphelps/homepage/commits?author=comradekingu) - Norwegian Bokmål
-- Daniel Varga - German & Hungarian
-- [deffcolony](https://github.com/benphelps/homepage/commits?author=deffcolony) - Dutch
-- [desolaris](https://github.com/benphelps/homepage/commits?author=desolaris) - Russian
-- [ericlokz](https://github.com/benphelps/homepage/commits?author=ericlokz) - Yue
-- [FunsKiTo](https://github.com/benphelps/homepage/commits?author=FunsKiTo) - Spanish
-- [jackblk](https://github.com/benphelps/homepage/commits?author=jackblk) - Vietnamese
-- [juanmanuelbc](https://github.com/benphelps/homepage/commits?author=juanmanuelbc) - Spanish and Catalan
-- [ling0412](https://github.com/benphelps/homepage/commits?author=ling0412) - Chinese
-- [milotype](https://github.com/benphelps/homepage/commits?author=milotype) - Croatian
-- [nicedc](https://github.com/benphelps/homepage/commits?author=nicedc) - Chinese
-- [Nonoss117](https://github.com/benphelps/homepage/commits?author=Nonoss117) - French
-- [pacoculebras](https://github.com/benphelps/homepage/commits?author=pacoculebras) - Catalan
-- [Prilann](https://github.com/benphelps/homepage/commits?author=Prilann) - German
-- [psychodracon](https://github.com/benphelps/homepage/commits?author=psychodracon) - Polish
-- Sascha Jelinek - German
-- [ShlomiPorush](https://github.com/benphelps/homepage/commits?author=ShlomiPorush) - Hebrew
-- [SuperDOS](https://github.com/benphelps/homepage/commits?author=SuperDOS) - Swedish
-- [kaihu](https://github.com/benphelps/homepage/commits?author=kaihu) - Finnish
+<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
+<!-- prettier-ignore-start -->
+<!-- markdownlint-disable -->
+
+<!-- markdownlint-restore -->
+<!-- prettier-ignore-end -->
+
+<!-- ALL-CONTRIBUTORS-LIST:END -->
+
+
+
+<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
+[![All Contributors](https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square)](#contributors)
+<!-- ALL-CONTRIBUTORS-BADGE:END -->

+ 1 - 1
next-i18next.config.js

@@ -103,7 +103,7 @@ module.exports = {
           const bits = options.bits ? value : value / 8;
           const k = 1024;
           const dm = options.decimals ? options.decimals : 0;
-          const sizes = ["Bps", "Kbps", "Mbps", "Gbps", "Tbps", "Pbps", "Ebps", "Zbps", "Ybps"];
+          const sizes = ["Bps", "KiBps", "MiBps", "GiBps", "TiBps", "PiBps", "EiBps", "ZiBps", "YiBps"];
 
           const i = Math.floor(Math.log(bits) / Math.log(k));
 

+ 365 - 0
public/locales/ar/common.json

@@ -0,0 +1,365 @@
+{
+    "widget": {
+        "missing_type": "نوع القطعة مفقود: {{type}}",
+        "api_error": "API خطأ",
+        "status": "الحالة",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
+    },
+    "weather": {
+        "current": "الموقع الحالي",
+        "allow": "اضغط للسماح",
+        "updating": "جاري التحديث",
+        "wait": "الرجاء الانتظار"
+    },
+    "search": {
+        "placeholder": "بحث …"
+    },
+    "resources": {
+        "cpu": "المعالج",
+        "total": "المجموع",
+        "free": "متاح",
+        "used": "مستخدم",
+        "load": "الضغط"
+    },
+    "unifi": {
+        "users": "المستخدمون",
+        "uptime": "مدة تشغيل النظام",
+        "days": "ايام",
+        "wan": "WAN",
+        "lan": "LAN",
+        "wlan": "WLAN",
+        "devices": "الاجهزة",
+        "lan_devices": "LAN اجهزة",
+        "wlan_devices": "WLAN احهزة",
+        "lan_users": "LAN مستخدمين",
+        "wlan_users": "WLAN مستخدمين",
+        "up": "اعلي",
+        "down": "اسفل",
+        "wait": "الرجاء الانتظار"
+    },
+    "wmo": {
+        "73-day": "Snow",
+        "0-day": "Sunny",
+        "0-night": "Clear",
+        "1-day": "Mainly Sunny",
+        "1-night": "Mainly Clear",
+        "2-day": "Partly Cloudy",
+        "2-night": "Partly Cloudy",
+        "3-day": "Cloudy",
+        "3-night": "Cloudy",
+        "45-day": "Foggy",
+        "45-night": "Foggy",
+        "48-day": "Foggy",
+        "48-night": "Foggy",
+        "51-day": "Light Drizzle",
+        "51-night": "Light Drizzle",
+        "53-day": "Drizzle",
+        "53-night": "Drizzle",
+        "55-day": "Heavy Drizzle",
+        "55-night": "Heavy Drizzle",
+        "56-day": "Light Freezing Drizzle",
+        "56-night": "Light Freezing Drizzle",
+        "57-day": "Freezing Drizzle",
+        "57-night": "Freezing Drizzle",
+        "61-day": "Light Rain",
+        "61-night": "Light Rain",
+        "63-day": "Rain",
+        "63-night": "Rain",
+        "65-day": "Heavy Rain",
+        "65-night": "Heavy Rain",
+        "66-day": "Freezing Rain",
+        "66-night": "Freezing Rain",
+        "67-day": "Freezing Rain",
+        "67-night": "Freezing Rain",
+        "71-day": "Light Snow",
+        "71-night": "Light Snow",
+        "73-night": "Snow",
+        "75-day": "Heavy Snow",
+        "75-night": "Heavy Snow",
+        "77-day": "Snow Grains",
+        "77-night": "Snow Grains",
+        "80-day": "Light Showers",
+        "80-night": "Light Showers",
+        "81-day": "Showers",
+        "81-night": "Showers",
+        "82-day": "Heavy Showers",
+        "82-night": "Heavy Showers",
+        "85-day": "Snow Showers",
+        "85-night": "Snow Showers",
+        "86-day": "Snow Showers",
+        "86-night": "Snow Showers",
+        "95-day": "Thunderstorm",
+        "95-night": "Thunderstorm",
+        "96-day": "Thunderstorm With Hail",
+        "96-night": "Thunderstorm With Hail",
+        "99-day": "Thunderstorm With Hail",
+        "99-night": "Thunderstorm With Hail"
+    },
+    "docker": {
+        "rx": "RX",
+        "tx": "TX",
+        "mem": "الرام",
+        "cpu": "المعالج",
+        "offline": "غير متصل",
+        "error": "Error",
+        "unknown": "Unknown"
+    },
+    "emby": {
+        "playing": "يعمل الان",
+        "transcoding": "التحويل",
+        "bitrate": "معدل البت",
+        "no_active": "No Active Streams"
+    },
+    "changedetectionio": {
+        "totalObserved": "Total Observed",
+        "diffsDetected": "Diffs Detected"
+    },
+    "tautulli": {
+        "playing": "Playing",
+        "transcoding": "Transcoding",
+        "bitrate": "Bitrate",
+        "no_active": "No Active Streams"
+    },
+    "nzbget": {
+        "rate": "Rate",
+        "remaining": "Remaining",
+        "downloaded": "Downloaded"
+    },
+    "plex": {
+        "streams": "Active Streams",
+        "movies": "Movies",
+        "tv": "TV Shows"
+    },
+    "sabnzbd": {
+        "rate": "Rate",
+        "queue": "Queue",
+        "timeleft": "Time Left"
+    },
+    "rutorrent": {
+        "active": "Active",
+        "upload": "Upload",
+        "download": "Download"
+    },
+    "transmission": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "qbittorrent": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "sonarr": {
+        "wanted": "Wanted",
+        "queued": "Queued",
+        "series": "Series"
+    },
+    "radarr": {
+        "wanted": "Wanted",
+        "missing": "Missing",
+        "queued": "Queued",
+        "movies": "Movies"
+    },
+    "lidarr": {
+        "wanted": "Wanted",
+        "queued": "Queued",
+        "albums": "Albums"
+    },
+    "readarr": {
+        "wanted": "Wanted",
+        "queued": "Queued",
+        "books": "Books"
+    },
+    "bazarr": {
+        "missingEpisodes": "Missing Episodes",
+        "missingMovies": "Missing Movies"
+    },
+    "ombi": {
+        "pending": "Pending",
+        "approved": "Approved",
+        "available": "Available"
+    },
+    "jellyseerr": {
+        "pending": "Pending",
+        "approved": "Approved",
+        "available": "Available"
+    },
+    "overseerr": {
+        "pending": "Pending",
+        "approved": "Approved",
+        "available": "Available",
+        "processing": "Processing"
+    },
+    "pihole": {
+        "queries": "Queries",
+        "blocked": "Blocked",
+        "gravity": "Gravity"
+    },
+    "adguard": {
+        "queries": "Queries",
+        "blocked": "Blocked",
+        "filtered": "Filtered",
+        "latency": "Latency"
+    },
+    "speedtest": {
+        "upload": "Upload",
+        "download": "Download",
+        "ping": "Ping"
+    },
+    "portainer": {
+        "running": "Running",
+        "stopped": "Stopped",
+        "total": "Total"
+    },
+    "traefik": {
+        "routers": "Routers",
+        "services": "Services",
+        "middleware": "Middleware"
+    },
+    "npm": {
+        "enabled": "Enabled",
+        "disabled": "Disabled",
+        "total": "Total"
+    },
+    "coinmarketcap": {
+        "configure": "Configure one or more crypto currencies to track",
+        "1hour": "1 Hour",
+        "1day": "1 Day",
+        "7days": "7 Days",
+        "30days": "30 Days"
+    },
+    "gotify": {
+        "apps": "Applications",
+        "clients": "Clients",
+        "messages": "Messages"
+    },
+    "prowlarr": {
+        "enableIndexers": "Indexers",
+        "numberOfGrabs": "Grabs",
+        "numberOfQueries": "Queries",
+        "numberOfFailGrabs": "Fail Grabs",
+        "numberOfFailQueries": "Fail Queries"
+    },
+    "jackett": {
+        "configured": "Configured",
+        "errored": "Errored"
+    },
+    "strelaysrv": {
+        "numActiveSessions": "Sessions",
+        "numConnections": "Connections",
+        "dataRelayed": "Relayed",
+        "transferRate": "Rate"
+    },
+    "mastodon": {
+        "user_count": "Users",
+        "status_count": "Posts",
+        "domain_count": "Domains"
+    },
+    "authentik": {
+        "users": "Users",
+        "loginsLast24H": "Logins (24h)",
+        "failedLoginsLast24H": "Failed Logins (24h)"
+    },
+    "proxmox": {
+        "mem": "MEM",
+        "cpu": "CPU",
+        "lxc": "LXC",
+        "vms": "VMs"
+    },
+    "glances": {
+        "cpu": "CPU",
+        "mem": "MEM",
+        "wait": "Please wait"
+    },
+    "quicklaunch": {
+        "bookmark": "Bookmark",
+        "service": "Service"
+    },
+    "homebridge": {
+        "available_update": "System",
+        "updates": "Updates",
+        "update_available": "Update Available",
+        "up_to_date": "Up to Date",
+        "child_bridges": "Child Bridges",
+        "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "leech": "Leech",
+        "seed": "Seed",
+        "download": "Download",
+        "upload": "Upload"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    }
+}

+ 83 - 3
public/locales/bg/common.json

@@ -12,7 +12,11 @@
     "widget": {
         "missing_type": "Липсваща приставка: {{type}}",
         "api_error": "API Грешка",
-        "status": "Статус"
+        "status": "Статус",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "weather": {
         "current": "Текущо местоположение",
@@ -51,7 +55,9 @@
         "rx": "RX",
         "tx": "TX",
         "mem": "MEM",
-        "cpu": "CPU"
+        "cpu": "CPU",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "emby": {
         "playing": "Възпроизвежда",
@@ -125,7 +131,8 @@
     "overseerr": {
         "pending": "Pending",
         "approved": "Approved",
-        "available": "Available"
+        "available": "Available",
+        "processing": "Processing"
     },
     "pihole": {
         "queries": "Queries",
@@ -281,5 +288,78 @@
         "up_to_date": "Up to Date",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers",
+        "approvedPushes": "Approved"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "ping": "Ping",
+        "error": "Error"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "seed": "Seed",
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech"
+    },
+    "flood": {
+        "leech": "Leech",
+        "seed": "Seed",
+        "download": "Download",
+        "upload": "Upload"
     }
 }

+ 83 - 3
public/locales/ca/common.json

@@ -2,7 +2,11 @@
     "widget": {
         "missing_type": "Falta el tipus de widget: {{type}}",
         "api_error": "Error d'API",
-        "status": "Estat"
+        "status": "Estat",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "weather": {
         "allow": "Feu clic per permetre",
@@ -41,7 +45,9 @@
         "tx": "Transmès",
         "mem": "Memòria",
         "cpu": "Processador",
-        "offline": "Fora de línia"
+        "offline": "Fora de línia",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "emby": {
         "playing": "Reproduint",
@@ -94,7 +100,8 @@
     "overseerr": {
         "pending": "Pendent",
         "approved": "Aprovat",
-        "available": "Disponible"
+        "available": "Disponible",
+        "processing": "Processing"
     },
     "pihole": {
         "queries": "Consultes",
@@ -281,5 +288,78 @@
         "up_to_date": "Up to Date",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "seed": "Seed",
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 365 - 0
public/locales/cs/common.json

@@ -0,0 +1,365 @@
+{
+    "tubearchivist": {
+        "videos": "Videa",
+        "channels": "Kanály",
+        "playlists": "Playlisty",
+        "downloads": "Fronta"
+    },
+    "truenas": {
+        "load": "Vytížení systému",
+        "uptime": "Doba spuštění",
+        "alerts": "Upozornění",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "widget": {
+        "missing_type": "Chybí typ widgetu: {{type}}",
+        "api_error": "Chyba API",
+        "status": "Status",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
+    },
+    "weather": {
+        "current": "Aktuální poloha",
+        "allow": "Klikni pro povolení",
+        "updating": "Probíhá aktualizace",
+        "wait": "Počkejte prosím"
+    },
+    "search": {
+        "placeholder": "Hledat…"
+    },
+    "resources": {
+        "cpu": "CPU",
+        "total": "Celkem",
+        "free": "Volné",
+        "used": "Využité",
+        "load": "Vytížení"
+    },
+    "unifi": {
+        "users": "Uživatelé",
+        "uptime": "Čas od startu systému",
+        "days": "Dnů",
+        "wan": "WAN",
+        "lan": "LAN",
+        "wlan": "WLAN",
+        "devices": "Zařízení",
+        "lan_devices": "LAN Zařízení",
+        "wlan_devices": "WLAN Zařízení",
+        "lan_users": "LAN Uživatelé",
+        "wlan_users": "WLAN Uživatelé",
+        "up": "BĚŽÍ",
+        "down": "NEBĚŽÍ",
+        "wait": "Počkejte prosím"
+    },
+    "docker": {
+        "rx": "RX",
+        "tx": "TX",
+        "mem": "RAM",
+        "cpu": "CPU",
+        "offline": "Offline",
+        "error": "Error",
+        "unknown": "Unknown"
+    },
+    "emby": {
+        "playing": "Přehrává",
+        "transcoding": "Transkódování",
+        "bitrate": "Bitrate",
+        "no_active": "Žádný aktivní stream"
+    },
+    "changedetectionio": {
+        "totalObserved": "Celkem zjištěno",
+        "diffsDetected": "Rozdíly detekovány"
+    },
+    "tautulli": {
+        "playing": "Přehrává",
+        "transcoding": "Transkódování",
+        "bitrate": "Bitrate",
+        "no_active": "Žádný aktivní stream"
+    },
+    "nzbget": {
+        "rate": "Rychlost",
+        "remaining": "Zbývá",
+        "downloaded": "Staženo"
+    },
+    "plex": {
+        "streams": "Aktivní streamy",
+        "movies": "Filmy",
+        "tv": "Seriály"
+    },
+    "sabnzbd": {
+        "rate": "Rychlost",
+        "queue": "Fronta",
+        "timeleft": "Zbývající čas"
+    },
+    "rutorrent": {
+        "active": "Aktivní",
+        "upload": "Nahrávání",
+        "download": "Stahování"
+    },
+    "transmission": {
+        "download": "Stahování",
+        "upload": "Nahrávání",
+        "leech": "Leecher",
+        "seed": "Seeder"
+    },
+    "qbittorrent": {
+        "download": "Stahování",
+        "upload": "Nahrávání",
+        "leech": "Leecher",
+        "seed": "Seeder"
+    },
+    "sonarr": {
+        "wanted": "Hledaný",
+        "queued": "Ve frontě",
+        "series": "Seriály"
+    },
+    "radarr": {
+        "wanted": "Hledaný",
+        "missing": "Chybějící",
+        "queued": "Ve frontě",
+        "movies": "Filmy"
+    },
+    "lidarr": {
+        "wanted": "Hledaný",
+        "queued": "Ve frontě",
+        "albums": "Alba"
+    },
+    "readarr": {
+        "wanted": "Hledaný",
+        "queued": "Ve frontě",
+        "books": "Knihy"
+    },
+    "bazarr": {
+        "missingEpisodes": "Chybějící epizody",
+        "missingMovies": "Chybějící filmy"
+    },
+    "ombi": {
+        "pending": "Čeká",
+        "approved": "Schváleno",
+        "available": "Dostupný"
+    },
+    "jellyseerr": {
+        "pending": "Čeká",
+        "approved": "Schváleno",
+        "available": "Dostupný"
+    },
+    "overseerr": {
+        "pending": "Čeká",
+        "approved": "Schváleno",
+        "available": "Dostupný",
+        "processing": "Processing"
+    },
+    "pihole": {
+        "queries": "Dotazy",
+        "blocked": "Blokováno",
+        "gravity": "Gravitace"
+    },
+    "adguard": {
+        "queries": "Dotazy",
+        "blocked": "Blokováno",
+        "filtered": "Filtrováno",
+        "latency": "Odezva"
+    },
+    "speedtest": {
+        "upload": "Nahrávání",
+        "download": "Stahování",
+        "ping": "Ping"
+    },
+    "portainer": {
+        "running": "Běží",
+        "stopped": "Zastaveno",
+        "total": "Celkově"
+    },
+    "traefik": {
+        "routers": "Routery",
+        "services": "Služby",
+        "middleware": "Prostředník"
+    },
+    "npm": {
+        "enabled": "Povoleno",
+        "disabled": "Zakázáno",
+        "total": "Celkově"
+    },
+    "coinmarketcap": {
+        "configure": "Nakonfigurujte alespoň jednu crypto měnu ke sledování",
+        "1hour": "1 Hodina",
+        "1day": "1 Den",
+        "7days": "7 Dní",
+        "30days": "30 Dní"
+    },
+    "wmo": {
+        "1-night": "Převážně jasno",
+        "2-day": "Polojasno",
+        "0-day": "Slunečno",
+        "0-night": "Jasno",
+        "1-day": "Převážně slunečno",
+        "2-night": "Polojasno",
+        "3-day": "Oblačno",
+        "3-night": "Oblačno",
+        "45-day": "Mlha",
+        "45-night": "Mlha",
+        "48-day": "Mlha",
+        "48-night": "Mlha",
+        "51-day": "Lehké mrholení",
+        "53-day": "Mrholení",
+        "53-night": "Mrholení",
+        "55-day": "Silné mrholení",
+        "55-night": "Silné mrholení",
+        "56-day": "Mírné mrznoucí mrholení",
+        "56-night": "Mírné mrznoucí mrholení",
+        "57-day": "Mrznoucí mrholení",
+        "57-night": "Mrznoucí mrholení",
+        "61-day": "Slabý déšť",
+        "61-night": "Slabý déšť",
+        "51-night": "Lehké mrholení",
+        "63-day": "Déšť",
+        "63-night": "Déšť",
+        "65-day": "Silný déšť",
+        "65-night": "Silný déšť",
+        "66-day": "Mrznoucí déšť",
+        "66-night": "Mrznoucí déšť",
+        "67-day": "Mrznoucí déšť",
+        "67-night": "Mrznoucí déšť",
+        "71-day": "Slabé sněžení",
+        "73-night": "Sněžení",
+        "75-day": "Silné sněžení",
+        "75-night": "Silné sněžení",
+        "77-day": "Sněhová zrna",
+        "71-night": "Slabé sněžení",
+        "73-day": "Sněžení",
+        "77-night": "Sněhová zrna",
+        "80-day": "Lehké přeháňky",
+        "80-night": "Lehké přeháňky",
+        "81-day": "Přeháňky",
+        "81-night": "Přeháňky",
+        "82-day": "Silné přeháňky",
+        "82-night": "Silné přeháňky",
+        "85-day": "Déšť se sněhem",
+        "85-night": "Déšť se sněhem",
+        "86-day": "Déšť se sněhem",
+        "86-night": "Déšť se sněhem",
+        "95-day": "Bouřka",
+        "95-night": "Bouřka",
+        "96-day": "Bouřka s krupobitím",
+        "96-night": "Bouřka s krupobitím",
+        "99-day": "Bouřka s krupobitím",
+        "99-night": "Bouřka s krupobitím"
+    },
+    "gotify": {
+        "apps": "Aplikace",
+        "clients": "Klienti",
+        "messages": "Zprávy"
+    },
+    "prowlarr": {
+        "enableIndexers": "Indexery",
+        "numberOfGrabs": "Uchopení",
+        "numberOfQueries": "Dotazy",
+        "numberOfFailGrabs": "Neúspěšné uchopení",
+        "numberOfFailQueries": "Neúspěšné dotazy"
+    },
+    "jackett": {
+        "configured": "Konfigurováno",
+        "errored": "Chybné"
+    },
+    "strelaysrv": {
+        "numActiveSessions": "Sezení",
+        "numConnections": "Připojení",
+        "dataRelayed": "Přenášení",
+        "transferRate": "Tempo"
+    },
+    "mastodon": {
+        "user_count": "Uživatelé",
+        "status_count": "Příspěvky",
+        "domain_count": "Domény"
+    },
+    "authentik": {
+        "users": "Uživatelé",
+        "loginsLast24H": "Příhlášení (24h)",
+        "failedLoginsLast24H": "Neúspěšná přihlášení (24h)"
+    },
+    "proxmox": {
+        "mem": "RAM",
+        "cpu": "CPU",
+        "lxc": "LXC",
+        "vms": "Virtuální Stroje"
+    },
+    "glances": {
+        "cpu": "CPU",
+        "mem": "RAM",
+        "wait": "Prosím počkejte"
+    },
+    "quicklaunch": {
+        "bookmark": "Záložka",
+        "service": "Služba"
+    },
+    "homebridge": {
+        "update_available": "Dostupná aktualizace",
+        "up_to_date": "Aktuální",
+        "available_update": "Systém",
+        "updates": "Aktualizace",
+        "child_bridges": "Podřadné můstky",
+        "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "watchtower": {
+        "containers_scanned": "Naskenováno",
+        "containers_updated": "Aktualizováno",
+        "containers_failed": "Chyba"
+    },
+    "autobrr": {
+        "approvedPushes": "Schváleno",
+        "rejectedPushes": "Zamítnuto",
+        "filters": "Filtry",
+        "indexers": "Indexery"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed",
+        "download": "Download"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    }
+}

+ 365 - 0
public/locales/da/common.json

@@ -0,0 +1,365 @@
+{
+    "plex": {
+        "movies": "Film",
+        "tv": "TV-Shows",
+        "streams": "Aktive Streams"
+    },
+    "radarr": {
+        "queued": "I Kø",
+        "movies": "Film",
+        "wanted": "Ønskede",
+        "missing": "Mangler"
+    },
+    "lidarr": {
+        "wanted": "Ønsket",
+        "queued": "I Kø",
+        "albums": "Albums"
+    },
+    "jellyseerr": {
+        "available": "Tilgængelig",
+        "pending": "Afventer",
+        "approved": "Godkendt"
+    },
+    "overseerr": {
+        "pending": "Afventer",
+        "approved": "Godkendt",
+        "available": "Tilgængelig",
+        "processing": "Processing"
+    },
+    "adguard": {
+        "queries": "Forespørgsler",
+        "blocked": "Blokerede",
+        "filtered": "Filtreret",
+        "latency": "Latency"
+    },
+    "speedtest": {
+        "upload": "Upload",
+        "download": "Download",
+        "ping": "Ping"
+    },
+    "npm": {
+        "total": "Total",
+        "enabled": "Aktiveret",
+        "disabled": "Deaktiveret"
+    },
+    "coinmarketcap": {
+        "30days": "30 Dage",
+        "1day": "1 Dag",
+        "configure": "Konfigurer en eller flere crypto valutaer til  tracking",
+        "7days": "7 Dage",
+        "1hour": "1 time"
+    },
+    "strelaysrv": {
+        "numActiveSessions": "Sessioner",
+        "dataRelayed": "Videresendt",
+        "numConnections": "Forbindelser",
+        "transferRate": "Rate"
+    },
+    "mastodon": {
+        "domain_count": "Domæner",
+        "status_count": "Indlæg",
+        "user_count": "Brugere"
+    },
+    "authentik": {
+        "users": "Brugere",
+        "loginsLast24H": "Login (24 timer)",
+        "failedLoginsLast24H": "Mislykkede logins (24 timer)"
+    },
+    "glances": {
+        "cpu": "CPU",
+        "mem": "RAM",
+        "wait": "Vent venligst"
+    },
+    "wmo": {
+        "1-day": "Hovedsageligt solrigt",
+        "48-day": "Tåget",
+        "48-night": "Tåget",
+        "51-day": "Let støvregn",
+        "51-night": "Let støvregn",
+        "66-night": "Frysende regn",
+        "67-day": "Frysende regn",
+        "67-night": "Frysende regn",
+        "71-day": "Let Sne",
+        "75-night": "Kraftig Sne",
+        "86-day": "Snebyger",
+        "86-night": "Snebyger",
+        "95-day": "Tordenvejr",
+        "99-day": "Tordenvejr med hagl",
+        "99-night": "Tordenvejr med hagl",
+        "0-day": "Solrig",
+        "0-night": "Klart",
+        "1-night": "Hovedsageligt klart",
+        "2-day": "Delvist skyet",
+        "2-night": "Delvist skyet",
+        "3-day": "Skyet",
+        "3-night": "Skyet",
+        "45-day": "Tåget",
+        "65-day": "Kraftig regn",
+        "65-night": "Kraftig regn",
+        "45-night": "Tåget",
+        "53-day": "Støvregn",
+        "53-night": "Støvregn",
+        "55-day": "Kraftig støvregn",
+        "55-night": "Kraftig støvregn",
+        "56-day": "Let frysende støvregn",
+        "56-night": "Let frysende støvregn",
+        "57-day": "Frysende støvregn",
+        "57-night": "Frysende støvregn",
+        "61-day": "Let Regn",
+        "61-night": "Let Regn",
+        "63-day": "Regn",
+        "63-night": "Regn",
+        "66-day": "Frysende regn",
+        "71-night": "Let Sne",
+        "73-day": "Sne",
+        "73-night": "Sne",
+        "75-day": "Kraftig Sne",
+        "77-day": "Snekorn",
+        "80-day": "Lette byger",
+        "80-night": "Lette byger",
+        "81-day": "Byger",
+        "77-night": "Snekorn",
+        "81-night": "Byger",
+        "82-day": "Kraftige Byger",
+        "82-night": "Kraftige Byger",
+        "85-day": "Snebyger",
+        "85-night": "Snebyger",
+        "95-night": "Tordenvejr",
+        "96-day": "Tordenvejr med hagl",
+        "96-night": "Tordenvejr med hagl"
+    },
+    "homebridge": {
+        "available_update": "System",
+        "updates": "Opdateringer",
+        "update_available": "Opdateringer tilgængelige",
+        "up_to_date": "Opdateret",
+        "child_bridges": "Child Bridges",
+        "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "widget": {
+        "missing_type": "Manglende Widget Type: {{type}}",
+        "api_error": "API fejl",
+        "status": "Status",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
+    },
+    "weather": {
+        "current": "Nuværende lokation",
+        "allow": "Klik for at tillade",
+        "updating": "Opdaterer",
+        "wait": "Vent venligst"
+    },
+    "search": {
+        "placeholder": "Søg…"
+    },
+    "resources": {
+        "cpu": "CPU",
+        "total": "Total",
+        "free": "Fri",
+        "used": "Brugt",
+        "load": "Belastning"
+    },
+    "unifi": {
+        "users": "Brugere",
+        "uptime": "System Oppetid",
+        "days": "Dage",
+        "wan": "WAN",
+        "lan": "LAN",
+        "wlan": "Wifi",
+        "devices": "Enheder",
+        "lan_devices": "LAN Enheder",
+        "wlan_devices": "WLAN Enheder",
+        "lan_users": "LAN Brugere",
+        "wlan_users": "WLAN Brugere",
+        "up": "Oppe",
+        "down": "NED",
+        "wait": "Vent venligst"
+    },
+    "docker": {
+        "cpu": "CPU",
+        "rx": "RX",
+        "tx": "TX",
+        "mem": "RAM",
+        "offline": "Offline",
+        "error": "Error",
+        "unknown": "Unknown"
+    },
+    "emby": {
+        "playing": "Afspiller",
+        "transcoding": "Transcoder",
+        "bitrate": "Bitrate",
+        "no_active": "Ingen Aktive Streams"
+    },
+    "changedetectionio": {
+        "totalObserved": "Total Observeret",
+        "diffsDetected": "Forskelle Detekteret"
+    },
+    "tautulli": {
+        "playing": "Afspiller",
+        "transcoding": "Transcoder",
+        "bitrate": "Bitrate",
+        "no_active": "Ingen Aktive Streams"
+    },
+    "nzbget": {
+        "rate": "Rate",
+        "remaining": "Manglende",
+        "downloaded": "Hentet"
+    },
+    "sabnzbd": {
+        "rate": "Rate",
+        "queue": "Kø",
+        "timeleft": "Resterende tid"
+    },
+    "rutorrent": {
+        "active": "Aktive",
+        "upload": "Upload",
+        "download": "Download"
+    },
+    "transmission": {
+        "upload": "Upload",
+        "download": "Download",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "qbittorrent": {
+        "upload": "Upload",
+        "download": "Download",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "sonarr": {
+        "wanted": "Ønsket",
+        "queued": "I Kø",
+        "series": "Serier"
+    },
+    "readarr": {
+        "wanted": "Ønskede",
+        "queued": "I Kø",
+        "books": "Bøger"
+    },
+    "bazarr": {
+        "missingEpisodes": "Manglende Afsnit",
+        "missingMovies": "Manglende Film"
+    },
+    "ombi": {
+        "pending": "Afventer",
+        "approved": "Godkendt",
+        "available": "Tilgængelig"
+    },
+    "pihole": {
+        "blocked": "Blokerede",
+        "gravity": "Gravity",
+        "queries": "Forespørgsler"
+    },
+    "portainer": {
+        "running": "Kørende",
+        "stopped": "Stoppede",
+        "total": "Total"
+    },
+    "traefik": {
+        "routers": "Routere",
+        "services": "Services",
+        "middleware": "Middleware"
+    },
+    "gotify": {
+        "apps": "Applikationer",
+        "clients": "Klienter",
+        "messages": "Beskeder"
+    },
+    "prowlarr": {
+        "enableIndexers": "Indeksører",
+        "numberOfGrabs": "Grabs",
+        "numberOfQueries": "Forespørgsler",
+        "numberOfFailGrabs": "Fail Grabs",
+        "numberOfFailQueries": "Fejl forespørgsler"
+    },
+    "jackett": {
+        "configured": "Konfigureret",
+        "errored": "Fejlede"
+    },
+    "proxmox": {
+        "mem": "RAM",
+        "cpu": "CPU",
+        "lxc": "LXC",
+        "vms": "VMs"
+    },
+    "quicklaunch": {
+        "bookmark": "Bogmærker",
+        "service": "Service"
+    },
+    "watchtower": {
+        "containers_scanned": "Scannet",
+        "containers_updated": "Opdateret",
+        "containers_failed": "Fejlet"
+    },
+    "autobrr": {
+        "indexers": "Indeksører",
+        "approvedPushes": "Godkendte",
+        "rejectedPushes": "Afviste",
+        "filters": "Filtre"
+    },
+    "tubearchivist": {
+        "downloads": "Kø",
+        "videos": "Videoer",
+        "channels": "Kanaler",
+        "playlists": "Afspilningslister"
+    },
+    "truenas": {
+        "load": "Systembelastning",
+        "uptime": "Oppetid",
+        "alerts": "Advarsler",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "Ingen Aktive Streams",
+        "please_wait": "Vent venligst"
+    },
+    "pyload": {
+        "speed": "Hastighed",
+        "active": "Aktive",
+        "queue": "Kø",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "leech": "Leech",
+        "download": "Download",
+        "upload": "Upload",
+        "seed": "Seed"
+    }
+}

+ 151 - 71
public/locales/de/common.json

@@ -2,7 +2,11 @@
     "widget": {
         "missing_type": "Fehlender Widget-Typ: {{type}}",
         "api_error": "API-Fehler",
-        "status": "Status"
+        "status": "Status",
+        "url": "URL",
+        "information": "Information",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "search": {
         "placeholder": "Suche…"
@@ -19,7 +23,9 @@
         "tx": "Tx",
         "mem": "Mem",
         "cpu": "Prozessor",
-        "offline": "Offline"
+        "offline": "Offline",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "emby": {
         "playing": "Spielen",
@@ -47,7 +53,7 @@
         "wanted": "Gesucht",
         "queued": "In Warteschlange",
         "movies": "Filme",
-        "missing": "Missing"
+        "missing": "Fehlt"
     },
     "readarr": {
         "wanted": "Gesucht",
@@ -98,7 +104,8 @@
     "overseerr": {
         "pending": "Ausstehend",
         "approved": "Genehmigt",
-        "available": "Verfügbar"
+        "available": "Verfügbar",
+        "processing": "Processing"
     },
     "sabnzbd": {
         "rate": "Geschwindigkeit",
@@ -194,9 +201,9 @@
         "wait": "Bitte warten",
         "lan": "LAN",
         "wlan": "WLAN",
-        "devices": "Devices",
-        "lan_devices": "LAN Devices",
-        "wlan_devices": "WLAN Devices"
+        "devices": "Geräte",
+        "lan_devices": "LAN-Geräte",
+        "wlan_devices": "WLAN Geräte"
     },
     "plex": {
         "streams": "Aktive Streams",
@@ -204,82 +211,155 @@
         "tv": "TV Sendungen"
     },
     "glances": {
-        "cpu": "CPU",
+        "cpu": "Prozessor",
         "mem": "RAM",
         "wait": "Bitte warten"
     },
     "changedetectionio": {
-        "totalObserved": "Total Observed",
-        "diffsDetected": "Diffs Detected"
+        "totalObserved": "Gesamt beobachtet",
+        "diffsDetected": "Erkannte Differenzen"
     },
     "wmo": {
-        "0-day": "Sunny",
-        "0-night": "Clear",
-        "1-day": "Mainly Sunny",
-        "1-night": "Mainly Clear",
-        "2-day": "Partly Cloudy",
-        "2-night": "Partly Cloudy",
-        "3-day": "Cloudy",
-        "57-day": "Freezing Drizzle",
-        "61-day": "Light Rain",
-        "65-night": "Heavy Rain",
-        "66-day": "Freezing Rain",
-        "66-night": "Freezing Rain",
-        "3-night": "Cloudy",
-        "45-day": "Foggy",
-        "45-night": "Foggy",
-        "48-day": "Foggy",
-        "48-night": "Foggy",
-        "51-day": "Light Drizzle",
-        "51-night": "Light Drizzle",
-        "55-day": "Heavy Drizzle",
-        "53-day": "Drizzle",
-        "53-night": "Drizzle",
-        "55-night": "Heavy Drizzle",
-        "56-day": "Light Freezing Drizzle",
-        "56-night": "Light Freezing Drizzle",
-        "57-night": "Freezing Drizzle",
-        "61-night": "Light Rain",
-        "63-day": "Rain",
-        "63-night": "Rain",
-        "65-day": "Heavy Rain",
-        "67-day": "Freezing Rain",
-        "67-night": "Freezing Rain",
-        "71-day": "Light Snow",
-        "71-night": "Light Snow",
-        "73-day": "Snow",
-        "73-night": "Snow",
-        "75-day": "Heavy Snow",
-        "75-night": "Heavy Snow",
-        "77-day": "Snow Grains",
-        "77-night": "Snow Grains",
-        "80-day": "Light Showers",
-        "80-night": "Light Showers",
-        "81-day": "Showers",
-        "81-night": "Showers",
-        "82-day": "Heavy Showers",
-        "82-night": "Heavy Showers",
-        "85-day": "Snow Showers",
-        "85-night": "Snow Showers",
-        "86-day": "Snow Showers",
-        "86-night": "Snow Showers",
-        "95-day": "Thunderstorm",
-        "95-night": "Thunderstorm",
-        "96-day": "Thunderstorm With Hail",
-        "96-night": "Thunderstorm With Hail",
-        "99-day": "Thunderstorm With Hail",
-        "99-night": "Thunderstorm With Hail"
+        "0-day": "Sonnig",
+        "0-night": "Klar",
+        "1-day": "Überwiegend sonnig",
+        "1-night": "Überwiegend klar",
+        "2-day": "Teilweise bewölkt",
+        "2-night": "Teilweise bewölkt",
+        "3-day": "bewölkt",
+        "57-day": "Gefrierender Nieselregen",
+        "61-day": "Leichter Regen",
+        "65-night": "Starker Regen",
+        "66-day": "Gefrierender Regen",
+        "66-night": "Gefrierender Regen",
+        "3-night": "Bewölkt",
+        "45-day": "Neblig",
+        "45-night": "Neblig",
+        "48-day": "Neblig",
+        "48-night": "Neblig",
+        "51-day": "Leichter Nieselregen",
+        "51-night": "Leichter Nieselregen",
+        "55-day": "Starker Nieselregen",
+        "53-day": "Nieselregen",
+        "53-night": "Nieselregen",
+        "55-night": "Starker Nieselregen",
+        "56-day": "Leichter gefrierender Nieselregen",
+        "56-night": "Leichter eisiger Nieselregen",
+        "57-night": "Gefrierender Nieselregen",
+        "61-night": "Leichter Regen",
+        "63-day": "Regen",
+        "63-night": "Regen",
+        "65-day": "Starker Regen",
+        "67-day": "Gefrierender Regen",
+        "67-night": "Gefrierender Regen",
+        "71-day": "Leichter Schneefall",
+        "71-night": "Leichter Schnee",
+        "73-day": "Schnee",
+        "73-night": "Schnee",
+        "75-day": "Schwerer Schnee",
+        "75-night": "Schwerer Schnee",
+        "77-day": "Schneegriesel",
+        "77-night": "Schneegriesel",
+        "80-day": "Leichter Schauer",
+        "80-night": "Leichter Schauer",
+        "81-day": "Schauer",
+        "81-night": "Schauer",
+        "82-day": "Starke Regenschauer",
+        "82-night": "Starke Regenschauer",
+        "85-day": "Schneeschauer",
+        "85-night": "Schneeregen",
+        "86-day": "Schneeregen",
+        "86-night": "Schneeregen",
+        "95-day": "Gewitter",
+        "95-night": "Gewitter",
+        "96-day": "Gewitter mit Hagel",
+        "96-night": "Gewitter mit Hagel",
+        "99-day": "Gewitter mit Hagel",
+        "99-night": "Gewitter mit Hagel"
     },
     "quicklaunch": {
-        "bookmark": "Bookmark",
-        "service": "Service"
+        "bookmark": "Lesezeichen",
+        "service": "Dienst"
     },
     "homebridge": {
         "available_update": "System",
-        "updates": "Updates",
-        "update_available": "Update Available",
-        "up_to_date": "Up to Date",
+        "updates": "Aktualisierungen",
+        "update_available": "Aktualisierung verfügbar",
+        "up_to_date": "Aktuell",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Genehmigt",
+        "rejectedPushes": "Abgelehnt",
+        "filters": "Filter",
+        "indexers": "Indexer"
+    },
+    "watchtower": {
+        "containers_scanned": "Überprüft",
+        "containers_updated": "Aktualisiert",
+        "containers_failed": "Fehlgeschlagen"
+    },
+    "tubearchivist": {
+        "downloads": "Warteschlange",
+        "videos": "Videos",
+        "channels": "Kanäle",
+        "playlists": "Wiedergabelisten"
+    },
+    "truenas": {
+        "load": "Systembelastung",
+        "uptime": "Betriebszeit",
+        "alerts": "Warnungen",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "ping": "Ping",
+        "error": "Error"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 83 - 3
public/locales/en/common.json

@@ -13,7 +13,11 @@
     "widget": {
         "missing_type": "Missing Widget Type: {{type}}",
         "api_error": "API Error",
-        "status": "Status"
+        "information": "Information",
+        "status": "Status",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "weather": {
         "current": "Current Location",
@@ -52,7 +56,13 @@
         "tx": "TX",
         "mem": "MEM",
         "cpu": "CPU",
-        "offline": "Offline"
+        "offline": "Offline",
+        "error": "Error",
+        "unknown": "Unknown"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
     },
     "emby": {
         "playing": "Playing",
@@ -60,6 +70,12 @@
         "bitrate": "Bitrate",
         "no_active": "No Active Streams"
     },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
     "changedetectionio": {
         "totalObserved": "Total Observed",
         "diffsDetected": "Diffs Detected"
@@ -102,6 +118,18 @@
         "leech": "Leech",
         "seed": "Seed"
     },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
     "sonarr": {
         "wanted": "Wanted",
         "queued": "Queued",
@@ -139,6 +167,7 @@
     },
     "overseerr": {
         "pending": "Pending",
+        "processing": "Processing",
         "approved": "Approved",
         "available": "Available"
     },
@@ -168,6 +197,10 @@
         "services": "Services",
         "middleware": "Middleware"
     },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
     "npm": {
         "enabled": "Enabled",
         "disabled": "Disabled",
@@ -292,5 +325,52 @@
         "up_to_date": "Up to Date",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
     }
-}
+}

+ 365 - 0
public/locales/eo/common.json

@@ -0,0 +1,365 @@
+{
+    "widget": {
+        "missing_type": "Missing Widget Type: {{type}}",
+        "api_error": "API Error",
+        "information": "Informo",
+        "status": "Stato",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
+    },
+    "weather": {
+        "current": "Aktuala loko",
+        "allow": "Click to allow",
+        "updating": "Updating",
+        "wait": "Please wait"
+    },
+    "search": {
+        "placeholder": "Serĉi…"
+    },
+    "resources": {
+        "cpu": "Ĉefprocesoro",
+        "total": "Totalo",
+        "free": "Libera",
+        "used": "Uzata",
+        "load": "Ŝarĝo"
+    },
+    "unifi": {
+        "users": "Uzantoj",
+        "uptime": "System Uptime",
+        "days": "Tagoj",
+        "wan": "WAN",
+        "lan": "LAN",
+        "wlan": "WLAN",
+        "devices": "Aparatoj",
+        "lan_devices": "LAN Devices",
+        "wlan_devices": "WLAN Devices",
+        "lan_users": "LAN Users",
+        "wlan_users": "WLAN Users",
+        "up": "UP",
+        "down": "DOWN",
+        "wait": "Please wait"
+    },
+    "docker": {
+        "rx": "RX",
+        "tx": "TX",
+        "mem": "Memoro",
+        "cpu": "Ĉefprocesoro",
+        "offline": "Offline",
+        "error": "Eraro",
+        "unknown": "Nekonata"
+    },
+    "ping": {
+        "error": "Eraro",
+        "ping": "Ping"
+    },
+    "emby": {
+        "playing": "Ludante",
+        "transcoding": "Transcoding",
+        "bitrate": "Bitrate",
+        "no_active": "No Active Streams"
+    },
+    "changedetectionio": {
+        "totalObserved": "Total Observed",
+        "diffsDetected": "Diffs Detected"
+    },
+    "tautulli": {
+        "playing": "Playing",
+        "transcoding": "Transcoding",
+        "bitrate": "Bitrate",
+        "no_active": "No Active Streams"
+    },
+    "nzbget": {
+        "rate": "Rate",
+        "remaining": "Remaining",
+        "downloaded": "Downloaded"
+    },
+    "plex": {
+        "streams": "Active Streams",
+        "movies": "Filmoj",
+        "tv": "Televidprogramoj"
+    },
+    "sabnzbd": {
+        "rate": "Rate",
+        "queue": "Queue",
+        "timeleft": "Time Left"
+    },
+    "rutorrent": {
+        "active": "Active",
+        "upload": "Alŝuto",
+        "download": "Elŝuto"
+    },
+    "transmission": {
+        "download": "Elŝuto",
+        "upload": "Alŝuto",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "qbittorrent": {
+        "download": "Elŝuto",
+        "upload": "Alŝuto",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "deluge": {
+        "download": "Elŝuto",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "sonarr": {
+        "wanted": "Wanted",
+        "queued": "Queued",
+        "series": "Serio"
+    },
+    "radarr": {
+        "wanted": "Wanted",
+        "missing": "Missing",
+        "queued": "Queued",
+        "movies": "Filmoj"
+    },
+    "lidarr": {
+        "wanted": "Wanted",
+        "queued": "Queued",
+        "albums": "Albumoj"
+    },
+    "readarr": {
+        "wanted": "Wanted",
+        "queued": "Queued",
+        "books": "Libroj"
+    },
+    "bazarr": {
+        "missingEpisodes": "Missing Episodes",
+        "missingMovies": "Missing Movies"
+    },
+    "ombi": {
+        "pending": "Pending",
+        "approved": "Aprobita",
+        "available": "Havebla"
+    },
+    "jellyseerr": {
+        "pending": "Pending",
+        "approved": "Aprobita",
+        "available": "Havebla"
+    },
+    "overseerr": {
+        "pending": "Pending",
+        "processing": "Processing",
+        "approved": "Aprobita",
+        "available": "Havebla"
+    },
+    "pihole": {
+        "queries": "Queries",
+        "blocked": "Blocked",
+        "gravity": "Gravity"
+    },
+    "adguard": {
+        "queries": "Queries",
+        "blocked": "Blokitaj",
+        "filtered": "Filtritaj",
+        "latency": "Latency"
+    },
+    "speedtest": {
+        "upload": "Upload",
+        "download": "Download",
+        "ping": "Ping"
+    },
+    "portainer": {
+        "running": "Running",
+        "stopped": "Stopped",
+        "total": "Totalo"
+    },
+    "traefik": {
+        "routers": "Routers",
+        "services": "Servoj",
+        "middleware": "Middleware"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "npm": {
+        "enabled": "Enabled",
+        "disabled": "Disabled",
+        "total": "Total"
+    },
+    "coinmarketcap": {
+        "configure": "Configure one or more crypto currencies to track",
+        "1hour": "1 horo",
+        "1day": "1 tago",
+        "7days": "7 tagoj",
+        "30days": "30 tagoj"
+    },
+    "gotify": {
+        "apps": "Applications",
+        "clients": "Klientoj",
+        "messages": "Mesaĝoj"
+    },
+    "prowlarr": {
+        "enableIndexers": "Indexers",
+        "numberOfGrabs": "Grabs",
+        "numberOfQueries": "Queries",
+        "numberOfFailGrabs": "Fail Grabs",
+        "numberOfFailQueries": "Fail Queries"
+    },
+    "jackett": {
+        "configured": "Configured",
+        "errored": "Errored"
+    },
+    "strelaysrv": {
+        "numActiveSessions": "Seancoj",
+        "numConnections": "Konektoj",
+        "dataRelayed": "Relayed",
+        "transferRate": "Rate"
+    },
+    "mastodon": {
+        "user_count": "Uzantoj",
+        "status_count": "Afiŝoj",
+        "domain_count": "Domains"
+    },
+    "authentik": {
+        "users": "Users",
+        "loginsLast24H": "Logins (24h)",
+        "failedLoginsLast24H": "Failed Logins (24h)"
+    },
+    "proxmox": {
+        "mem": "Memoro",
+        "cpu": "Ĉefprocesoro",
+        "lxc": "LXC",
+        "vms": "VMs"
+    },
+    "glances": {
+        "cpu": "Ĉefprocesoro",
+        "mem": "Memoro",
+        "wait": "Bonvolu atendi"
+    },
+    "quicklaunch": {
+        "bookmark": "Bookmark",
+        "service": "Servo"
+    },
+    "wmo": {
+        "0-day": "Suna",
+        "0-night": "Sennuba",
+        "1-day": "Mainly Sunny",
+        "1-night": "Mainly Clear",
+        "2-day": "Nubeta",
+        "2-night": "Nubeta",
+        "3-day": "Nuba",
+        "3-night": "Nuba",
+        "45-day": "Nebula",
+        "45-night": "Nebula",
+        "48-day": "Nebula",
+        "48-night": "Nebula",
+        "51-day": "Light Drizzle",
+        "51-night": "Light Drizzle",
+        "53-day": "Drizzle",
+        "53-night": "Drizzle",
+        "55-day": "Heavy Drizzle",
+        "55-night": "Heavy Drizzle",
+        "56-day": "Light Freezing Drizzle",
+        "56-night": "Light Freezing Drizzle",
+        "57-day": "Freezing Drizzle",
+        "57-night": "Freezing Drizzle",
+        "61-day": "Light Rain",
+        "61-night": "Light Rain",
+        "63-day": "Pluvo",
+        "63-night": "Pluvo",
+        "65-day": "Pluvego",
+        "65-night": "Pluvego",
+        "66-day": "Frosta pluvo",
+        "66-night": "Frosta pluvo",
+        "67-day": "Frosta pluvo",
+        "67-night": "Frosta pluvo",
+        "71-day": "Light Snow",
+        "71-night": "Light Snow",
+        "73-day": "Neĝo",
+        "73-night": "Neĝo",
+        "75-day": "Neĝego",
+        "75-night": "Neĝego",
+        "77-day": "Snow Grains",
+        "77-night": "Snow Grains",
+        "80-day": "Light Showers",
+        "80-night": "Light Showers",
+        "81-day": "Showers",
+        "81-night": "Showers",
+        "82-day": "Heavy Showers",
+        "82-night": "Heavy Showers",
+        "85-day": "Snow Showers",
+        "85-night": "Snow Showers",
+        "86-day": "Snow Showers",
+        "86-night": "Snow Showers",
+        "95-day": "Fulmotondro",
+        "95-night": "Fulmotondro",
+        "96-day": "Fulmotondro kun hajlo",
+        "96-night": "Fulmotondro kun hajlo",
+        "99-day": "Fulmotondro kun hajlo",
+        "99-night": "Fulmotondro kun hajlo"
+    },
+    "homebridge": {
+        "available_update": "Sistemo",
+        "updates": "Updates",
+        "update_available": "Update Available",
+        "up_to_date": "Up to Date",
+        "child_bridges": "Child Bridges",
+        "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filtriloj",
+        "indexers": "Indexers"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Kanaloj",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Aktiva",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Regiono",
+        "country": "Lando"
+    },
+    "hdhomerun": {
+        "channels": "Kanaloj",
+        "hd": "HD"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Totalo"
+    },
+    "diskstation": {
+        "download": "Download",
+        "leech": "Leech",
+        "upload": "Upload",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    }
+}

+ 91 - 11
public/locales/es/common.json

@@ -2,7 +2,11 @@
     "widget": {
         "missing_type": "Falta el tipo de widget: {{type}}",
         "api_error": "Error de API",
-        "status": "Estado"
+        "status": "Estado",
+        "information": "Información",
+        "url": "URL",
+        "raw_error": "Error sin procesar",
+        "response_data": "Datos de respuesta"
     },
     "search": {
         "placeholder": "Buscar…"
@@ -19,7 +23,9 @@
         "tx": "Transmitido",
         "mem": "Memoria",
         "cpu": "Procesador",
-        "offline": "Desconectado"
+        "offline": "Desconectado",
+        "error": "Fallo",
+        "unknown": "Desconocido"
     },
     "emby": {
         "playing": "Reproduciendo",
@@ -47,7 +53,7 @@
         "wanted": "Buscando",
         "queued": "En cola",
         "movies": "Películas",
-        "missing": "No Encontrado"
+        "missing": "Faltan"
     },
     "readarr": {
         "wanted": "Buscando",
@@ -82,7 +88,7 @@
     "traefik": {
         "routers": "Enrutadores",
         "services": "Servicios",
-        "middleware": "Middleware"
+        "middleware": "Software intermedio"
     },
     "npm": {
         "enabled": "Activado",
@@ -98,7 +104,8 @@
     "overseerr": {
         "pending": "Pendiente",
         "approved": "Aprobado",
-        "available": "Disponible"
+        "available": "Disponible",
+        "processing": "Procesando"
     },
     "sabnzbd": {
         "rate": "Tasa",
@@ -132,7 +139,7 @@
     "transmission": {
         "download": "Bajada",
         "upload": "Subida",
-        "leech": "Compañeros",
+        "leech": "Leech",
         "seed": "Semillas"
     },
     "jackett": {
@@ -157,7 +164,7 @@
     "qbittorrent": {
         "download": "Bajada",
         "upload": "Subida",
-        "leech": "Compañeros",
+        "leech": "Leech",
         "seed": "Semillas"
     },
     "mastodon": {
@@ -275,11 +282,84 @@
         "service": "Servicio"
     },
     "homebridge": {
-        "available_update": "System",
-        "updates": "Updates",
-        "update_available": "Update Available",
-        "up_to_date": "Up to Date",
+        "available_update": "Sistema",
+        "updates": "Actualizaciones",
+        "update_available": "Actualización disponible",
+        "up_to_date": "Actualizado",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Aprobado",
+        "rejectedPushes": "Rechazado",
+        "filters": "Filtros",
+        "indexers": "Indexadores"
+    },
+    "watchtower": {
+        "containers_scanned": "Escaneado",
+        "containers_updated": "Actualizado",
+        "containers_failed": "Fallido"
+    },
+    "tubearchivist": {
+        "downloads": "Cola",
+        "videos": "Vídeos",
+        "channels": "Canales",
+        "playlists": "Listas de reproducción"
+    },
+    "truenas": {
+        "load": "Carga del sistema",
+        "uptime": "Tiempo de la actividad",
+        "alerts": "Alertas",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "Sin transmisiones activas",
+        "please_wait": "Espere por favor"
+    },
+    "pyload": {
+        "speed": "Velocidad",
+        "active": "Activo",
+        "queue": "Cola",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "IP pública",
+        "region": "Región",
+        "country": "País"
+    },
+    "hdhomerun": {
+        "channels": "Canales",
+        "hd": "Alta definición"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Aprobado",
+        "failed": "Fallido",
+        "unknown": "Desconocido"
+    },
+    "paperlessngx": {
+        "inbox": "Bandeja de entrada",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Descarga",
+        "upload": "Subida",
+        "leech": "Leech",
+        "seed": "Semilla"
+    },
+    "diskstation": {
+        "download": "Descargar",
+        "upload": "Cargar",
+        "leech": "Leech",
+        "seed": "Semilla"
+    },
+    "flood": {
+        "download": "Descargar",
+        "upload": "Subir",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 83 - 3
public/locales/fi/common.json

@@ -2,7 +2,11 @@
     "widget": {
         "missing_type": "Puuttuva härpäkkeen tyyppi: {{type}}",
         "api_error": "API-virhe",
-        "status": "Tila"
+        "status": "Tila",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "weather": {
         "current": "Nykyinen sijainti",
@@ -25,7 +29,9 @@
         "tx": "TX",
         "mem": "RAM",
         "cpu": "CPU",
-        "offline": "Offline"
+        "offline": "Offline",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "emby": {
         "playing": "Toistaa",
@@ -104,7 +110,8 @@
     "overseerr": {
         "pending": "Vireillä",
         "approved": "Hyväksytty",
-        "available": "Saatavilla"
+        "available": "Saatavilla",
+        "processing": "Processing"
     },
     "pihole": {
         "queries": "Kyselyjä",
@@ -281,5 +288,78 @@
         "up_to_date": "Up to Date",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed",
+        "download": "Download"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 87 - 7
public/locales/fr/common.json

@@ -2,7 +2,11 @@
     "widget": {
         "missing_type": "Type de widget manquant: {{type}}",
         "api_error": "Erreur de l'API",
-        "status": "Statut"
+        "status": "Statut",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "search": {
         "placeholder": "Recherche…"
@@ -19,7 +23,9 @@
         "tx": "Tx",
         "mem": "Mém",
         "cpu": "Cpu",
-        "offline": "Hors ligne"
+        "offline": "Hors ligne",
+        "error": "Erreur",
+        "unknown": "Inconnu"
     },
     "emby": {
         "playing": "En lecture",
@@ -98,7 +104,8 @@
     "overseerr": {
         "pending": "En attente",
         "approved": "Demande",
-        "available": "Disponible"
+        "available": "Disponible",
+        "processing": "En traitement"
     },
     "sabnzbd": {
         "rate": "Débit",
@@ -275,11 +282,84 @@
         "service": "Service"
     },
     "homebridge": {
-        "available_update": "System",
-        "updates": "Updates",
-        "update_available": "Update Available",
-        "up_to_date": "Up to Date",
+        "available_update": "Système",
+        "updates": "Mises à jour",
+        "update_available": "Mise à jour disponible",
+        "up_to_date": "À jour",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Approuvé",
+        "rejectedPushes": "Rejeté",
+        "filters": "Filtres",
+        "indexers": "Indexeur"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanné",
+        "containers_updated": "Mis à jour",
+        "containers_failed": "Échoué"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Vidéos",
+        "channels": "Chaînes",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "Charge Système",
+        "uptime": "Démarré depuis",
+        "alerts": "Alertes",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "Aucun flux actif",
+        "please_wait": "Merci de patienter"
+    },
+    "pyload": {
+        "speed": "Débit",
+        "active": "Actif",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "IP Publique",
+        "region": "Région",
+        "country": "Pays"
+    },
+    "hdhomerun": {
+        "channels": "Canaux",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Erreur",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Réussi",
+        "failed": "Échoué",
+        "unknown": "Inconnu"
+    },
+    "paperlessngx": {
+        "inbox": "Boîte de réception",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Récep.",
+        "upload": "Envoi",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Réception",
+        "upload": "Envoi",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Récep.",
+        "upload": "Envoi",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 83 - 3
public/locales/he/common.json

@@ -2,7 +2,11 @@
     "widget": {
         "missing_type": "סוג ווידג'ט חסר: {{type}}",
         "api_error": "שגיאת API",
-        "status": "סטטוס"
+        "status": "סטטוס",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "weather": {
         "current": "מיקום נוכחי",
@@ -25,7 +29,9 @@
         "tx": "TX",
         "mem": "זיכרון",
         "cpu": "מעבד",
-        "offline": "כבוי"
+        "offline": "כבוי",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "emby": {
         "playing": "מנגן",
@@ -104,7 +110,8 @@
     "overseerr": {
         "pending": "ממתין",
         "approved": "מאושר",
-        "available": "זמין"
+        "available": "זמין",
+        "processing": "Processing"
     },
     "pihole": {
         "queries": "שאילתות",
@@ -281,5 +288,78 @@
         "up_to_date": "Up to Date",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "upload": "Upload",
+        "leech": "Leech",
+        "download": "Download",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 365 - 0
public/locales/hi/common.json

@@ -0,0 +1,365 @@
+{
+    "widget": {
+        "missing_type": "Missing Widget Type: {{type}}",
+        "api_error": "API Error",
+        "status": "Status",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
+    },
+    "weather": {
+        "current": "Current Location",
+        "allow": "Click to allow",
+        "updating": "Updating",
+        "wait": "Please wait"
+    },
+    "readarr": {
+        "queued": "Queued",
+        "books": "Books",
+        "wanted": "Wanted"
+    },
+    "bazarr": {
+        "missingEpisodes": "Missing Episodes",
+        "missingMovies": "Missing Movies"
+    },
+    "ombi": {
+        "pending": "Pending",
+        "approved": "Approved",
+        "available": "Available"
+    },
+    "jellyseerr": {
+        "pending": "Pending",
+        "approved": "Approved",
+        "available": "Available"
+    },
+    "traefik": {
+        "services": "Services",
+        "middleware": "Middleware",
+        "routers": "Routers"
+    },
+    "mastodon": {
+        "domain_count": "Domains",
+        "user_count": "Users",
+        "status_count": "Posts"
+    },
+    "authentik": {
+        "users": "Users",
+        "loginsLast24H": "Logins (24h)",
+        "failedLoginsLast24H": "Failed Logins (24h)"
+    },
+    "search": {
+        "placeholder": "Search…"
+    },
+    "resources": {
+        "cpu": "CPU",
+        "total": "Total",
+        "free": "Free",
+        "used": "Used",
+        "load": "Load"
+    },
+    "unifi": {
+        "users": "Users",
+        "uptime": "System Uptime",
+        "days": "Days",
+        "wan": "WAN",
+        "lan": "LAN",
+        "wlan": "WLAN",
+        "devices": "Devices",
+        "lan_devices": "LAN Devices",
+        "wlan_devices": "WLAN Devices",
+        "lan_users": "LAN Users",
+        "wlan_users": "WLAN Users",
+        "up": "UP",
+        "down": "DOWN",
+        "wait": "Please wait"
+    },
+    "docker": {
+        "rx": "RX",
+        "tx": "TX",
+        "mem": "MEM",
+        "cpu": "CPU",
+        "offline": "Offline",
+        "error": "Error",
+        "unknown": "Unknown"
+    },
+    "emby": {
+        "playing": "Playing",
+        "transcoding": "Transcoding",
+        "bitrate": "Bitrate",
+        "no_active": "No Active Streams"
+    },
+    "changedetectionio": {
+        "totalObserved": "Total Observed",
+        "diffsDetected": "Diffs Detected"
+    },
+    "tautulli": {
+        "playing": "Playing",
+        "transcoding": "Transcoding",
+        "bitrate": "Bitrate",
+        "no_active": "No Active Streams"
+    },
+    "nzbget": {
+        "rate": "Rate",
+        "remaining": "Remaining",
+        "downloaded": "Downloaded"
+    },
+    "plex": {
+        "streams": "Active Streams",
+        "movies": "Movies",
+        "tv": "TV Shows"
+    },
+    "sabnzbd": {
+        "rate": "Rate",
+        "queue": "Queue",
+        "timeleft": "Time Left"
+    },
+    "rutorrent": {
+        "active": "Active",
+        "upload": "Upload",
+        "download": "Download"
+    },
+    "transmission": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "qbittorrent": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "sonarr": {
+        "wanted": "Wanted",
+        "queued": "Queued",
+        "series": "Series"
+    },
+    "radarr": {
+        "wanted": "Wanted",
+        "missing": "Missing",
+        "queued": "Queued",
+        "movies": "Movies"
+    },
+    "lidarr": {
+        "wanted": "Wanted",
+        "queued": "Queued",
+        "albums": "Albums"
+    },
+    "overseerr": {
+        "pending": "Pending",
+        "approved": "Approved",
+        "available": "Available",
+        "processing": "Processing"
+    },
+    "pihole": {
+        "queries": "Queries",
+        "blocked": "Blocked",
+        "gravity": "Gravity"
+    },
+    "adguard": {
+        "queries": "Queries",
+        "blocked": "Blocked",
+        "filtered": "Filtered",
+        "latency": "Latency"
+    },
+    "speedtest": {
+        "upload": "Upload",
+        "download": "Download",
+        "ping": "Ping"
+    },
+    "portainer": {
+        "running": "Running",
+        "stopped": "Stopped",
+        "total": "Total"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "npm": {
+        "enabled": "Enabled",
+        "disabled": "Disabled",
+        "total": "Total"
+    },
+    "coinmarketcap": {
+        "configure": "Configure one or more crypto currencies to track",
+        "1hour": "1 Hour",
+        "1day": "1 Day",
+        "7days": "7 Days",
+        "30days": "30 Days"
+    },
+    "gotify": {
+        "apps": "Applications",
+        "clients": "Clients",
+        "messages": "Messages"
+    },
+    "prowlarr": {
+        "enableIndexers": "Indexers",
+        "numberOfGrabs": "Grabs",
+        "numberOfQueries": "Queries",
+        "numberOfFailGrabs": "Fail Grabs",
+        "numberOfFailQueries": "Fail Queries"
+    },
+    "jackett": {
+        "configured": "Configured",
+        "errored": "Errored"
+    },
+    "strelaysrv": {
+        "numActiveSessions": "Sessions",
+        "numConnections": "Connections",
+        "dataRelayed": "Relayed",
+        "transferRate": "Rate"
+    },
+    "proxmox": {
+        "mem": "MEM",
+        "cpu": "CPU",
+        "lxc": "LXC",
+        "vms": "VMs"
+    },
+    "glances": {
+        "cpu": "CPU",
+        "mem": "MEM",
+        "wait": "Please wait"
+    },
+    "quicklaunch": {
+        "bookmark": "Bookmark",
+        "service": "Service"
+    },
+    "wmo": {
+        "0-day": "Sunny",
+        "0-night": "Clear",
+        "1-day": "Mainly Sunny",
+        "1-night": "Mainly Clear",
+        "2-day": "Partly Cloudy",
+        "2-night": "Partly Cloudy",
+        "3-day": "Cloudy",
+        "3-night": "Cloudy",
+        "45-day": "Foggy",
+        "45-night": "Foggy",
+        "48-day": "Foggy",
+        "48-night": "Foggy",
+        "51-day": "Light Drizzle",
+        "51-night": "Light Drizzle",
+        "53-day": "Drizzle",
+        "53-night": "Drizzle",
+        "55-day": "Heavy Drizzle",
+        "55-night": "Heavy Drizzle",
+        "56-day": "Light Freezing Drizzle",
+        "56-night": "Light Freezing Drizzle",
+        "57-day": "Freezing Drizzle",
+        "57-night": "Freezing Drizzle",
+        "61-day": "Light Rain",
+        "61-night": "Light Rain",
+        "63-day": "Rain",
+        "63-night": "Rain",
+        "65-day": "Heavy Rain",
+        "65-night": "Heavy Rain",
+        "66-day": "Freezing Rain",
+        "66-night": "Freezing Rain",
+        "67-day": "Freezing Rain",
+        "67-night": "Freezing Rain",
+        "71-day": "Light Snow",
+        "71-night": "Light Snow",
+        "73-day": "Snow",
+        "73-night": "Snow",
+        "75-day": "Heavy Snow",
+        "75-night": "Heavy Snow",
+        "77-day": "Snow Grains",
+        "77-night": "Snow Grains",
+        "80-day": "Light Showers",
+        "80-night": "Light Showers",
+        "81-day": "Showers",
+        "81-night": "Showers",
+        "82-day": "Heavy Showers",
+        "82-night": "Heavy Showers",
+        "85-day": "Snow Showers",
+        "85-night": "Snow Showers",
+        "86-day": "Snow Showers",
+        "86-night": "Snow Showers",
+        "95-day": "Thunderstorm",
+        "95-night": "Thunderstorm",
+        "96-day": "Thunderstorm With Hail",
+        "96-night": "Thunderstorm With Hail",
+        "99-day": "Thunderstorm With Hail",
+        "99-night": "Thunderstorm With Hail"
+    },
+    "homebridge": {
+        "available_update": "System",
+        "updates": "Updates",
+        "update_available": "Update Available",
+        "up_to_date": "Up to Date",
+        "child_bridges": "Child Bridges",
+        "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}",
+        "alerts": "Alerts"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    }
+}

+ 181 - 101
public/locales/hr/common.json

@@ -1,12 +1,12 @@
 {
     "weather": {
-        "current": "Trenutna lokacija",
+        "current": "Trenutna lokacija",
         "allow": "Pritisni za dozvoljavanje",
         "updating": "Aktualiziranje",
-        "wait": "Molimo pričekajte"
+        "wait": "Pričekaj"
     },
     "search": {
-        "placeholder": "Traži…"
+        "placeholder": "Traži …"
     },
     "resources": {
         "total": "Ukupno",
@@ -17,18 +17,19 @@
     },
     "sabnzbd": {
         "rate": "Stopa",
-        "queue": "Red",
+        "queue": "Red čekanja",
         "timeleft": "Preostalo vrijeme"
     },
     "overseerr": {
         "available": "Dostupno",
         "pending": "Predstoji",
-        "approved": "Odobreno"
+        "approved": "Odobreno",
+        "processing": "Obrada"
     },
     "pihole": {
         "queries": "Upiti",
         "blocked": "Blokirano",
-        "gravity": "Ozbiljnost"
+        "gravity": "Čuvanje podataka"
     },
     "adguard": {
         "latency": "Kašnjenje",
@@ -58,14 +59,20 @@
     "widget": {
         "missing_type": "Nedostajuća vrsta widgeta: {{type}}",
         "api_error": "API greška",
-        "status": "Stanje"
+        "status": "Stanje",
+        "information": "Informacije",
+        "url": "URL",
+        "raw_error": "Raw greška",
+        "response_data": "Podaci odgovora"
     },
     "docker": {
         "rx": "RX",
         "tx": "TX",
         "mem": "MEM",
         "cpu": "CPU",
-        "offline": "Izvan mreže"
+        "offline": "Nepovezan",
+        "error": "Greška",
+        "unknown": "Nepoznato"
     },
     "emby": {
         "playing": "Reprodukcija",
@@ -92,27 +99,27 @@
     "transmission": {
         "download": "Preuzimanje",
         "upload": "Prijenos",
-        "leech": "Krvopija",
-        "seed": "Prijenos preuzetog sadržaja"
+        "leech": "Leecher",
+        "seed": "Seeder"
     },
     "sonarr": {
-        "wanted": "Željeno",
+        "wanted": "Zatraženo",
         "queued": "U redu čekanja",
         "series": "Serije"
     },
     "radarr": {
-        "wanted": "Željeno",
+        "wanted": "Zatraženo",
         "queued": "U redu čekanja",
         "movies": "Filmovi",
-        "missing": "Missing"
+        "missing": "Nedostaje"
     },
     "lidarr": {
-        "wanted": "Željeno",
+        "wanted": "Zatraženo",
         "queued": "U redu čekanja",
         "albums": "Albumi"
     },
     "readarr": {
-        "wanted": "Željeno",
+        "wanted": "Zatraženo",
         "queued": "U redu čekanja",
         "books": "Knjige"
     },
@@ -143,7 +150,7 @@
     "traefik": {
         "routers": "Ruteri",
         "services": "Usluge",
-        "middleware": "Middleware"
+        "middleware": "Posrednički softver"
     },
     "gotify": {
         "clients": "Klijenti",
@@ -157,8 +164,8 @@
     "qbittorrent": {
         "download": "Preuzimanje",
         "upload": "Prijenos",
-        "leech": "Krvopija",
-        "seed": "Prijenos preuzetog sadržaja"
+        "leech": "Leecher",
+        "seed": "Seeder"
     },
     "mastodon": {
         "user_count": "Korisnici",
@@ -172,114 +179,187 @@
         "transferRate": "Stopa"
     },
     "authentik": {
-        "users": "Users",
-        "loginsLast24H": "Logins (24h)",
-        "failedLoginsLast24H": "Failed Logins (24h)"
+        "users": "Korisnici",
+        "loginsLast24H": "Prijave (24 h)",
+        "failedLoginsLast24H": "Neuspjele prijave (24 h)"
     },
     "proxmox": {
         "mem": "MEM",
         "cpu": "CPU",
-        "lxc": "LXC",
-        "vms": "VMs"
+        "lxc": "Linux kontejner",
+        "vms": "Virtualni uređaji"
     },
     "unifi": {
         "users": "Korisnici",
-        "uptime": "Vrijeme rada sustava",
+        "uptime": "Radno vrijeme sustava",
         "days": "Dani",
         "wan": "WAN",
-        "lan_users": "LAN Korisnici",
-        "wlan_users": "WLAN Korisnici",
-        "up": "Upaljen",
-        "down": "Ugašen",
-        "wait": "Molimo pričekajte",
+        "lan_users": "LAN korisnici",
+        "wlan_users": "WLAN korisnici",
+        "up": "SLANJE",
+        "down": "PRIMANJE",
+        "wait": "Pričekaj",
         "lan": "LAN",
         "wlan": "WLAN",
-        "devices": "Devices",
-        "lan_devices": "LAN Devices",
-        "wlan_devices": "WLAN Devices"
+        "devices": "Uređaji",
+        "lan_devices": "LAN uređaji",
+        "wlan_devices": "WLAN uređaji"
     },
     "plex": {
-        "streams": "Active Streams",
-        "movies": "Movies",
-        "tv": "TV Shows"
+        "streams": "Aktivni prijenosi",
+        "movies": "Filmovi",
+        "tv": "TV emisije"
     },
     "glances": {
         "cpu": "CPU",
         "mem": "MEM",
-        "wait": "Please wait"
+        "wait": "Pričekaj"
     },
     "changedetectionio": {
-        "totalObserved": "Total Observed",
-        "diffsDetected": "Diffs Detected"
+        "totalObserved": "Ukupno promatrano",
+        "diffsDetected": "Otkrivene razlike"
     },
     "wmo": {
-        "0-day": "Sunny",
-        "0-night": "Clear",
-        "1-day": "Mainly Sunny",
-        "1-night": "Mainly Clear",
-        "2-day": "Partly Cloudy",
-        "45-day": "Foggy",
-        "45-night": "Foggy",
-        "48-day": "Foggy",
-        "2-night": "Partly Cloudy",
-        "3-day": "Cloudy",
-        "3-night": "Cloudy",
-        "48-night": "Foggy",
-        "51-day": "Light Drizzle",
-        "51-night": "Light Drizzle",
-        "53-day": "Drizzle",
-        "53-night": "Drizzle",
-        "55-day": "Heavy Drizzle",
-        "55-night": "Heavy Drizzle",
-        "56-day": "Light Freezing Drizzle",
-        "56-night": "Light Freezing Drizzle",
-        "57-day": "Freezing Drizzle",
-        "57-night": "Freezing Drizzle",
-        "61-day": "Light Rain",
-        "61-night": "Light Rain",
-        "63-day": "Rain",
-        "63-night": "Rain",
-        "65-day": "Heavy Rain",
-        "65-night": "Heavy Rain",
-        "66-day": "Freezing Rain",
-        "66-night": "Freezing Rain",
-        "67-day": "Freezing Rain",
-        "67-night": "Freezing Rain",
-        "75-night": "Heavy Snow",
-        "77-day": "Snow Grains",
-        "71-day": "Light Snow",
-        "71-night": "Light Snow",
-        "73-day": "Snow",
-        "73-night": "Snow",
-        "75-day": "Heavy Snow",
-        "77-night": "Snow Grains",
-        "80-day": "Light Showers",
-        "80-night": "Light Showers",
-        "81-day": "Showers",
-        "81-night": "Showers",
-        "82-day": "Heavy Showers",
-        "82-night": "Heavy Showers",
-        "85-day": "Snow Showers",
-        "85-night": "Snow Showers",
-        "86-day": "Snow Showers",
-        "86-night": "Snow Showers",
-        "95-day": "Thunderstorm",
-        "95-night": "Thunderstorm",
-        "96-day": "Thunderstorm With Hail",
-        "96-night": "Thunderstorm With Hail",
-        "99-day": "Thunderstorm With Hail",
-        "99-night": "Thunderstorm With Hail"
+        "0-day": "Sunčano",
+        "0-night": "Vedro",
+        "1-day": "Pretežno sunčano",
+        "1-night": "Pretežno verdo",
+        "2-day": "Djelimično oblačno",
+        "45-day": "Maglovito",
+        "45-night": "Maglovito",
+        "48-day": "Maglovito",
+        "2-night": "Djelimično oblačno",
+        "3-day": "Oblačno",
+        "3-night": "Oblačno",
+        "48-night": "Maglovito",
+        "51-day": "Laka rosulja",
+        "51-night": "Laka rosulja",
+        "53-day": "Rosulja",
+        "53-night": "Rosulja",
+        "55-day": "Jaka rosulja",
+        "55-night": "Jaka rosulja",
+        "56-day": "Laka ledena rosulja",
+        "56-night": "Laka ledena rosulja",
+        "57-day": "Ledena rosulja",
+        "57-night": "Ledena rosulja",
+        "61-day": "Laka kiša",
+        "61-night": "Laka kiša",
+        "63-day": "Kiša",
+        "63-night": "Kiša",
+        "65-day": "Jaka kiša",
+        "65-night": "Jaka kiša",
+        "66-day": "Ledena kiša",
+        "66-night": "Ledena kiša",
+        "67-day": "Ledena kiša",
+        "67-night": "Ledena kiša",
+        "75-night": "Jaki snijeg",
+        "77-day": "Zrnati snijeg",
+        "71-day": "Laki snijeg",
+        "71-night": "Laki snijeg",
+        "73-day": "Snijeg",
+        "73-night": "Snijeg",
+        "75-day": "Jaki snijeg",
+        "77-night": "Zrnati snijeg",
+        "80-day": "Laki pljuskovi",
+        "80-night": "Laki pljuskovi",
+        "81-day": "Pljuskovi",
+        "81-night": "Pljuskovi",
+        "82-day": "Jaki pljuskovi",
+        "82-night": "Jaki pljuskovi",
+        "85-day": "Snježni pljuskovi",
+        "85-night": "Snježni pljuskovi",
+        "86-day": "Snježni pljuskovi",
+        "86-night": "Snježni pljuskovi",
+        "95-day": "Oluja",
+        "95-night": "Oluja",
+        "96-day": "Oluja s tučom",
+        "96-night": "Oluja s tučom",
+        "99-day": "Oluja s tučom",
+        "99-night": "Oluja s tučom"
     },
     "quicklaunch": {
-        "bookmark": "Bookmark",
-        "service": "Service"
+        "bookmark": "Straničnik",
+        "service": "Usluga"
     },
     "homebridge": {
-        "available_update": "System",
-        "updates": "Updates",
-        "update_available": "Update Available",
-        "up_to_date": "Up to Date",
-        "child_bridges": "Child Bridges",
+        "available_update": "Sustav",
+        "updates": "Aktualiziranja",
+        "update_available": "Dostupna je nova verzija",
+        "up_to_date": "Aktualno",
+        "child_bridges": "Podređeni mosotvi",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "rejectedPushes": "Odbijeno",
+        "approvedPushes": "Odobreno",
+        "filters": "Filtri",
+        "indexers": "Indeksatori"
+    },
+    "watchtower": {
+        "containers_scanned": "Skenirano",
+        "containers_updated": "Aktualizirano",
+        "containers_failed": "Neuspjelo"
+    },
+    "tubearchivist": {
+        "downloads": "Red čekanja",
+        "videos": "Videa",
+        "channels": "Kanali",
+        "playlists": "Playliste"
+    },
+    "truenas": {
+        "load": "Opterećenje sustava",
+        "uptime": "Radno vrijeme",
+        "alerts": "Upozorenja",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "Nema aktivnih prijenosa",
+        "please_wait": "Pričekaj"
+    },
+    "pyload": {
+        "speed": "Brzina",
+        "active": "Aktivno",
+        "queue": "Red čekanja",
+        "total": "Ukupno"
+    },
+    "gluetun": {
+        "public_ip": "Javni IP",
+        "region": "Regija",
+        "country": "Zemlja"
+    },
+    "hdhomerun": {
+        "channels": "Kanali",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Greška",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Prošlo",
+        "failed": "Neuspjelo",
+        "unknown": "Nepoznato"
+    },
+    "paperlessngx": {
+        "inbox": "Ulazni sandučić",
+        "total": "Ukupno"
+    },
+    "deluge": {
+        "download": "Preuzimanje",
+        "upload": "Prijenos",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Preuzimanje",
+        "upload": "Prijenos",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Preuzimanje",
+        "upload": "Prijenos",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 83 - 3
public/locales/hu/common.json

@@ -11,7 +11,9 @@
         "tx": "TX",
         "mem": "MEM",
         "cpu": "CPU",
-        "offline": "Offline"
+        "offline": "Offline",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "lidarr": {
         "albums": "Albumok",
@@ -30,7 +32,11 @@
     "widget": {
         "missing_type": "Hiányzó Widget Típus: {{type}}",
         "api_error": "API Hiba",
-        "status": "Státusz"
+        "status": "Státusz",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "weather": {
         "current": "Aktuális hely",
@@ -104,7 +110,8 @@
     "overseerr": {
         "pending": "Függőben",
         "approved": "Engedélyezett",
-        "available": "Elérhető"
+        "available": "Elérhető",
+        "processing": "Processing"
     },
     "pihole": {
         "queries": "Lekérdezések",
@@ -281,5 +288,78 @@
         "up_to_date": "Up to Date",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "seed": "Seed",
+        "upload": "Upload",
+        "leech": "Leech"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 119 - 39
public/locales/it/common.json

@@ -4,7 +4,9 @@
         "mem": "MEM",
         "cpu": "CPU",
         "offline": "Offline",
-        "rx": "RX"
+        "rx": "RX",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "emby": {
         "playing": "In riproduzione",
@@ -36,7 +38,11 @@
     "widget": {
         "missing_type": "Tipo del Widget Mancante: {{type}}",
         "api_error": "Errore API",
-        "status": "Stato"
+        "status": "Stato",
+        "url": "URL",
+        "information": "Information",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "search": {
         "placeholder": "Cerca…"
@@ -98,7 +104,8 @@
     "overseerr": {
         "pending": "In attesa",
         "approved": "Approvati",
-        "available": "Disponibili"
+        "available": "Disponibili",
+        "processing": "Processing"
     },
     "sabnzbd": {
         "rate": "Rapporto",
@@ -123,11 +130,11 @@
         "messages": "Messaggi"
     },
     "prowlarr": {
-        "enableIndexers": "Indexers",
+        "enableIndexers": "Indicizzatori",
         "numberOfGrabs": "Grabs",
         "numberOfQueries": "Queries",
-        "numberOfFailGrabs": "Fail Grabs",
-        "numberOfFailQueries": "Fail Queries"
+        "numberOfFailGrabs": "Grabs Falliti",
+        "numberOfFailQueries": "Queries Fallite"
     },
     "transmission": {
         "download": "Download",
@@ -184,7 +191,7 @@
     },
     "unifi": {
         "users": "Utenti",
-        "uptime": "System Uptime",
+        "uptime": "Uptime di Sistema",
         "days": "Giorni",
         "wan": "WAN",
         "lan_users": "Utenti LAN",
@@ -209,16 +216,16 @@
         "wait": "Attendere prego"
     },
     "changedetectionio": {
-        "totalObserved": "Total Observed",
-        "diffsDetected": "Diffs Detected"
+        "totalObserved": "Totale Osservato",
+        "diffsDetected": "Differenze Rilevate"
     },
     "wmo": {
-        "65-day": "Heavy Rain",
+        "65-day": "Pioggia Intensa",
         "2-night": "Parzialmente Nuvoloso",
         "0-day": "Solleggiato",
-        "0-night": "Clear",
-        "1-day": "Mainly Sunny",
-        "1-night": "Mainly Clear",
+        "0-night": "Pulisci",
+        "1-day": "Principalmente Soleggiato",
+        "1-night": "Principalmente Sereno",
         "2-day": "Parzialmente Nuvoloso",
         "3-day": "Nuvoloso",
         "3-night": "Nuvoloso",
@@ -232,37 +239,37 @@
         "53-night": "Pioggerella",
         "55-day": "Pioggerella Pesante",
         "55-night": "Pioggerella Pesante",
-        "56-day": "Light Freezing Drizzle",
-        "56-night": "Light Freezing Drizzle",
-        "57-day": "Freezing Drizzle",
-        "57-night": "Freezing Drizzle",
+        "56-day": "Leggera Pioggia Gelata",
+        "56-night": "Leggera Pioggia Gelata",
+        "57-day": "Pioggerella Gelata",
+        "57-night": "Pioggerella Gelata",
         "61-day": "Pioggia Leggera",
         "61-night": "Pioggia Leggera",
         "63-day": "Pioggia",
         "63-night": "Pioggia",
-        "65-night": "Heavy Rain",
+        "65-night": "Pioggia Intensa",
         "66-day": "Grandine",
         "66-night": "Grandine",
         "67-day": "Grandine",
         "67-night": "Grandine",
-        "71-day": "Light Snow",
-        "71-night": "Light Snow",
+        "71-day": "Leggera Nevicata",
+        "71-night": "Leggera Nevicata",
         "73-day": "Neve",
         "73-night": "Neve",
-        "75-day": "Heavy Snow",
-        "75-night": "Heavy Snow",
-        "77-day": "Snow Grains",
-        "77-night": "Snow Grains",
-        "80-day": "Light Showers",
-        "80-night": "Light Showers",
-        "81-day": "Showers",
-        "81-night": "Showers",
-        "82-day": "Heavy Showers",
-        "82-night": "Heavy Showers",
-        "85-day": "Snow Showers",
-        "85-night": "Snow Showers",
-        "86-day": "Snow Showers",
-        "86-night": "Snow Showers",
+        "75-day": "Nevicata Intensa",
+        "75-night": "Nevicata Intensa",
+        "77-day": "Fiocchi di Neve",
+        "77-night": "Fiocchi di Neve",
+        "80-day": "Leggeri Rovesci",
+        "80-night": "Leggeri Rovesci",
+        "81-day": "Rovesci",
+        "81-night": "Rovesci",
+        "82-day": "Intensi Rovesci",
+        "82-night": "Intensi Rovesci",
+        "85-day": "Rovesci di Neve",
+        "85-night": "Rovesci di Neve",
+        "86-day": "Rovesci di Neve",
+        "86-night": "Rovesci di Neve",
         "95-day": "Temporale",
         "95-night": "Temporale",
         "96-day": "Temporale con grandine",
@@ -271,15 +278,88 @@
         "99-night": "Temporale con grandine"
     },
     "quicklaunch": {
-        "bookmark": "Bookmark",
+        "bookmark": "Segnalibro",
         "service": "Servizio"
     },
     "homebridge": {
-        "available_update": "System",
-        "updates": "Updates",
-        "update_available": "Update Available",
-        "up_to_date": "Up to Date",
+        "available_update": "Sistema",
+        "updates": "Aggiornamenti",
+        "update_available": "Aggiornamento Disponibile",
+        "up_to_date": "Aggiornato",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Approvato",
+        "rejectedPushes": "Rifiutato",
+        "filters": "Filtri",
+        "indexers": "Indicizzatori"
+    },
+    "watchtower": {
+        "containers_scanned": "Scansionato",
+        "containers_updated": "Aggiornato",
+        "containers_failed": "Fallito"
+    },
+    "tubearchivist": {
+        "downloads": "Coda",
+        "videos": "Video",
+        "channels": "Canali",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "Carico di Sistema",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "Nessun Sistema Attivo",
+        "please_wait": "Attendere, Prego"
+    },
+    "pyload": {
+        "speed": "Velocità",
+        "active": "Attivo",
+        "queue": "Coda",
+        "total": "Totale"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 365 - 0
public/locales/ms/common.json

@@ -0,0 +1,365 @@
+{
+    "resources": {
+        "cpu": "CPU",
+        "total": "Jumlah",
+        "free": "Bebas",
+        "used": "Telah diguna",
+        "load": "Beban"
+    },
+    "unifi": {
+        "uptime": "Masa Operasi Sistem",
+        "users": "Pengguna",
+        "days": "Hari",
+        "wan": "WAN",
+        "lan": "LAN",
+        "wlan": "WLAN",
+        "devices": "Peranti",
+        "lan_devices": "Peranti LAN",
+        "wlan_devices": "Peranti WLAN",
+        "lan_users": "Pengguna LAN",
+        "wlan_users": "Pengguna WLAN",
+        "up": "HIDUP",
+        "down": "MATI",
+        "wait": "Sila tunggu"
+    },
+    "lidarr": {
+        "queued": "Dibaris Gilir",
+        "albums": "Album",
+        "wanted": "Mahu"
+    },
+    "readarr": {
+        "wanted": "Mahu",
+        "queued": "Dibaris Gilir",
+        "books": "Buku"
+    },
+    "jellyseerr": {
+        "pending": "Tertangguh",
+        "approved": "Lulus",
+        "available": "Sudah Ada"
+    },
+    "coinmarketcap": {
+        "30days": "30 Hari",
+        "configure": "Konfigurasikan satu atau lebih matawang crypto untuk dipantau",
+        "1hour": "1 Jam",
+        "1day": "1 Hari",
+        "7days": "7 Hari"
+    },
+    "gotify": {
+        "apps": "Aplikasi",
+        "clients": "Klien",
+        "messages": "Mesej"
+    },
+    "proxmox": {
+        "mem": "MEM",
+        "cpu": "CPU",
+        "lxc": "LXC",
+        "vms": "Mesin Maya"
+    },
+    "glances": {
+        "cpu": "CPU",
+        "mem": "MEM",
+        "wait": "Sila tunggu"
+    },
+    "quicklaunch": {
+        "bookmark": "Tandabuku",
+        "service": "Servis"
+    },
+    "wmo": {
+        "0-day": "Terik",
+        "0-night": "Cerah",
+        "1-day": "Sebahagian Besar Terik",
+        "1-night": "Sebahagian Besar Cerah",
+        "63-day": "Hujan",
+        "63-night": "Hujan",
+        "2-day": "Sebahagian Mendung",
+        "2-night": "Sebahagian Mendung",
+        "3-day": "Mendung",
+        "3-night": "Mendung",
+        "45-day": "Berkabus",
+        "45-night": "Berkabus",
+        "48-day": "Berkabus",
+        "48-night": "Berkabus",
+        "51-day": "Gerimis",
+        "51-night": "Gerimis",
+        "53-day": "Renyai",
+        "53-night": "Renyai",
+        "55-day": "Renyai Kuat",
+        "55-night": "Renyai Kuat",
+        "56-day": "Gerimis Sejuk Ringan",
+        "56-night": "Gerimis Sejuk Ringan",
+        "57-day": "Gerimis Sejuk",
+        "57-night": "Gerimis Sejuk",
+        "61-day": "Hujan Renyai",
+        "61-night": "Hujan Renyai",
+        "65-day": "Hujan Lebat",
+        "65-night": "Hujan Lebat",
+        "66-day": "Hujan Sejuk",
+        "66-night": "Hujan Sejuk",
+        "67-day": "Hujan Sejuk",
+        "67-night": "Hujan Sejuk",
+        "71-day": "Salji Ringan",
+        "71-night": "Salji Ringan",
+        "73-day": "Salji",
+        "73-night": "Salji",
+        "75-day": "Salji Lebat",
+        "75-night": "Salji Lebat",
+        "81-day": "Rintik",
+        "77-day": "Butiran Salji",
+        "77-night": "Butiran Salji",
+        "80-day": "Rintik Ringan",
+        "80-night": "Rintik Ringan",
+        "81-night": "Rintik",
+        "82-day": "Rintik Lebat",
+        "82-night": "Rintik Lebat",
+        "85-day": "Rintik Salji",
+        "85-night": "Rintik Salji",
+        "86-day": "Rintik Salji",
+        "86-night": "Rintik Salji",
+        "95-day": "Ribut",
+        "95-night": "Ribut",
+        "96-day": "Ribut Hujan Batu",
+        "96-night": "Ribut Hujan Batu",
+        "99-day": "Ribut Hujan Batu",
+        "99-night": "Ribut Hujan Batu"
+    },
+    "widget": {
+        "missing_type": "Jenis Widget Hilang: {{type}}",
+        "api_error": "Masalah API",
+        "status": "Status",
+        "information": "Informasi",
+        "url": "URL",
+        "raw_error": "Ralat Mentah",
+        "response_data": "Data Respon"
+    },
+    "weather": {
+        "current": "Lokasi Sekarang",
+        "allow": "Klik untuk benarkan",
+        "updating": "Mengemas kini",
+        "wait": "Sila tunggu"
+    },
+    "search": {
+        "placeholder": "Carian…"
+    },
+    "nzbget": {
+        "remaining": "Baki",
+        "downloaded": "Telah Muat Turun",
+        "rate": "Kadar"
+    },
+    "docker": {
+        "rx": "RX",
+        "tx": "TX",
+        "mem": "MEM",
+        "cpu": "CPU",
+        "offline": "Luar talian",
+        "error": "Ralat",
+        "unknown": "Tidak Diketahui"
+    },
+    "changedetectionio": {
+        "totalObserved": "Jumlah Diperhatikan",
+        "diffsDetected": "Perbezaan Dikesan"
+    },
+    "emby": {
+        "playing": "Sedang dimainkan",
+        "transcoding": "Transkoding",
+        "bitrate": "Kadar bit",
+        "no_active": "Tiada Strim Aktif"
+    },
+    "tautulli": {
+        "playing": "Sedang Dimainkan",
+        "transcoding": "Transkoding",
+        "bitrate": "Kadar bit",
+        "no_active": "Tiada Strim Aktif"
+    },
+    "plex": {
+        "streams": "Strim Aktif",
+        "movies": "Filem",
+        "tv": "Rancangan TV"
+    },
+    "sabnzbd": {
+        "rate": "Kadar",
+        "queue": "Barisan",
+        "timeleft": "Masa Tinggal"
+    },
+    "rutorrent": {
+        "active": "Aktif",
+        "upload": "Muat Naik",
+        "download": "Muat Turun"
+    },
+    "transmission": {
+        "leech": "Leech",
+        "download": "Muat Turun",
+        "upload": "Muat Naik",
+        "seed": "Seed"
+    },
+    "qbittorrent": {
+        "download": "Muat Turun",
+        "upload": "Muat Naik",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "sonarr": {
+        "wanted": "Mahu",
+        "queued": "Dibaris Gilir",
+        "series": "Bersiri"
+    },
+    "radarr": {
+        "wanted": "Mahu",
+        "missing": "Hilang",
+        "queued": "Dibaris Gilir",
+        "movies": "Filem"
+    },
+    "bazarr": {
+        "missingEpisodes": "Episod Yang Hilang",
+        "missingMovies": "Filem Yang Hilang"
+    },
+    "ombi": {
+        "pending": "Tertunda",
+        "approved": "Lulus",
+        "available": "Sudah Ada"
+    },
+    "overseerr": {
+        "pending": "Tertangguh",
+        "approved": "Lulus",
+        "available": "Sudah Ada",
+        "processing": "Processing"
+    },
+    "pihole": {
+        "queries": "Permintaan",
+        "blocked": "Disekat",
+        "gravity": "Gravity"
+    },
+    "adguard": {
+        "queries": "Permintaan",
+        "blocked": "Disekat",
+        "filtered": "Ditapis",
+        "latency": "Kependaman"
+    },
+    "speedtest": {
+        "upload": "Muat Naik",
+        "download": "Muat Turun",
+        "ping": "Ping"
+    },
+    "portainer": {
+        "running": "Sedang Berjalan",
+        "stopped": "Terhenti",
+        "total": "Jumlah"
+    },
+    "traefik": {
+        "routers": "Router",
+        "services": "Servis",
+        "middleware": "Perisian Tengah"
+    },
+    "npm": {
+        "enabled": "Didayakan",
+        "disabled": "Dinyahdayakan",
+        "total": "Jumlah"
+    },
+    "prowlarr": {
+        "enableIndexers": "Pengindeks",
+        "numberOfGrabs": "Capai",
+        "numberOfQueries": "Permintaan",
+        "numberOfFailGrabs": "Capai Yang Ggagal",
+        "numberOfFailQueries": "Permintaan Yang Gagal"
+    },
+    "jackett": {
+        "configured": "Telah Dikonfigurasi",
+        "errored": "Telah Tersalah"
+    },
+    "strelaysrv": {
+        "numActiveSessions": "Sesi",
+        "numConnections": "Penyambungan",
+        "dataRelayed": "Disalurkan",
+        "transferRate": "Kadar"
+    },
+    "mastodon": {
+        "user_count": "Pengguna",
+        "status_count": "Pos",
+        "domain_count": "Domain"
+    },
+    "authentik": {
+        "users": "Pengguna",
+        "loginsLast24H": "Logmasuk (24j)",
+        "failedLoginsLast24H": "Logmasuk Gagal (24j)"
+    },
+    "homebridge": {
+        "child_bridges_status": "{{ok}}/{{total}}",
+        "available_update": "Sistem",
+        "updates": "Kemaskini",
+        "update_available": "Kemaskini Tersedia",
+        "up_to_date": "Terkemaskini",
+        "child_bridges": "Jambatan Anak"
+    },
+    "watchtower": {
+        "containers_scanned": "Terimbas",
+        "containers_updated": "Dikemaskini",
+        "containers_failed": "Gagal"
+    },
+    "autobrr": {
+        "approvedPushes": "Lulus",
+        "rejectedPushes": "Ditolak",
+        "filters": "Tapisan",
+        "indexers": "Pengindeks"
+    },
+    "tubearchivist": {
+        "downloads": "Baris Gilir",
+        "videos": "Video",
+        "channels": "Saluran",
+        "playlists": "Senarai Siar"
+    },
+    "truenas": {
+        "load": "Beban Sistem",
+        "uptime": "Masa Hidup",
+        "alerts": "Amaran",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "Tiada Strim Aktif",
+        "please_wait": "Sila tunggu"
+    },
+    "pyload": {
+        "speed": "Kelajuan",
+        "active": "Aktif",
+        "queue": "Baris Gilir",
+        "total": "Jumlah"
+    },
+    "gluetun": {
+        "public_ip": "IP Awam",
+        "region": "Rantau",
+        "country": "Negara"
+    },
+    "hdhomerun": {
+        "channels": "Saluran",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Ralat",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Lulus",
+        "failed": "Gagal",
+        "unknown": "Tidak Diketahui"
+    },
+    "paperlessngx": {
+        "inbox": "Peti Masuk",
+        "total": "Jumlah"
+    },
+    "deluge": {
+        "download": "Muat Turun",
+        "upload": "Muat Naik",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "upload": "Upload",
+        "download": "Download",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    }
+}

+ 83 - 3
public/locales/nb-NO/common.json

@@ -2,7 +2,11 @@
     "widget": {
         "missing_type": "Manglende miniprogramstype: {{type}}",
         "api_error": "API-feil",
-        "status": "Status"
+        "status": "Status",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "search": {
         "placeholder": "Søk …"
@@ -19,7 +23,9 @@
         "tx": "Sendt",
         "mem": "Minne",
         "cpu": "Prosessor",
-        "offline": "Frakoblet"
+        "offline": "Frakoblet",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "emby": {
         "playing": "Spiller",
@@ -98,7 +104,8 @@
     "overseerr": {
         "pending": "Venter",
         "approved": "Godkjent",
-        "available": "Tilgjengelig"
+        "available": "Tilgjengelig",
+        "processing": "Processing"
     },
     "sabnzbd": {
         "rate": "Takt",
@@ -281,5 +288,78 @@
         "up_to_date": "Up to Date",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "leech": "Leech",
+        "download": "Download",
+        "upload": "Upload",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "leech": "Leech",
+        "download": "Download",
+        "upload": "Upload",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 83 - 3
public/locales/nl/common.json

@@ -2,7 +2,11 @@
     "widget": {
         "missing_type": "Missing Widget Type: {{type}}",
         "api_error": "API Error",
-        "status": "Status"
+        "status": "Status",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "resources": {
         "total": "Totaal",
@@ -16,7 +20,9 @@
         "tx": "TX",
         "mem": "MEM",
         "cpu": "CPU",
-        "offline": "Offline"
+        "offline": "Offline",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "speedtest": {
         "upload": "Upload",
@@ -98,7 +104,8 @@
     "overseerr": {
         "pending": "Pending",
         "approved": "Approved",
-        "available": "Available"
+        "available": "Available",
+        "processing": "Processing"
     },
     "sabnzbd": {
         "rate": "Rate",
@@ -281,5 +288,78 @@
         "up_to_date": "Up to Date",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "total": "Total",
+        "queue": "Queue"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 177 - 97
public/locales/pl/common.json

@@ -52,14 +52,20 @@
     "widget": {
         "missing_type": "Brakujący typ widżetu: {{type}}",
         "api_error": "Błąd API",
-        "status": "Stan"
+        "status": "Stan",
+        "url": "Adres URL",
+        "information": "Informacje",
+        "raw_error": "Niesformatowany błąd",
+        "response_data": "Dane odpowiedzi"
     },
     "docker": {
-        "rx": "RX",
-        "tx": "TX",
-        "mem": "MEM",
-        "cpu": "CPU",
-        "offline": "Offline"
+        "rx": "Rx",
+        "tx": "Tx",
+        "mem": "Pamięć",
+        "cpu": "Procesor",
+        "offline": "Offline",
+        "error": "Błąd",
+        "unknown": "Nieznany"
     },
     "nzbget": {
         "rate": "Szybkość",
@@ -91,7 +97,7 @@
         "wanted": "Poszukiwane",
         "queued": "W kolejce",
         "movies": "Filmy",
-        "missing": "Missing"
+        "missing": "Brakujące"
     },
     "lidarr": {
         "wanted": "Poszukiwane",
@@ -120,7 +126,8 @@
     "overseerr": {
         "pending": "Oczekiwane",
         "approved": "Zaakceptowane",
-        "available": "Dostępne"
+        "available": "Dostępne",
+        "processing": "Przetwarzane"
     },
     "pihole": {
         "queries": "Zapytania",
@@ -149,27 +156,27 @@
         "errored": "Błędne"
     },
     "adguard": {
-        "queries": "Queries",
-        "blocked": "Blocked",
-        "filtered": "Filtered",
-        "latency": "Latency"
+        "queries": "Zapytania",
+        "blocked": "Zablokowane",
+        "filtered": "Przefiltrowane",
+        "latency": "Opóźnienia"
     },
     "qbittorrent": {
-        "download": "Download",
-        "upload": "Upload",
+        "download": "Pobieranie",
+        "upload": "Wysyłanie",
         "leech": "Leech",
         "seed": "Seed"
     },
     "mastodon": {
         "user_count": "Użytkownicy",
-        "status_count": "Posts",
+        "status_count": "Posty",
         "domain_count": "Domeny"
     },
     "strelaysrv": {
         "numActiveSessions": "Sesje",
         "numConnections": "Połączenia",
-        "dataRelayed": "Relayed",
-        "transferRate": "Rate"
+        "dataRelayed": "Przekazane",
+        "transferRate": "Przesył"
     },
     "authentik": {
         "users": "Użytkownicy",
@@ -177,109 +184,182 @@
         "failedLoginsLast24H": "Nieudane logowania (24h)"
     },
     "proxmox": {
-        "mem": "MEM",
-        "cpu": "CPU",
-        "lxc": "LXC",
-        "vms": "VMs"
+        "mem": "Pamięć",
+        "cpu": "Procesor",
+        "lxc": "Kontenery LXC",
+        "vms": "Maszyn wirtualnych"
     },
     "unifi": {
         "users": "Użytkownicy",
         "uptime": "Czas pracy systemu",
         "days": "Dni",
         "wan": "WAN",
-        "lan_users": "LAN Users",
-        "wlan_users": "WLAN Users",
-        "up": "UP",
-        "down": "DOWN",
-        "wait": "Please wait",
+        "lan_users": "Użytkownicy LAN",
+        "wlan_users": "Użytkownicy WLAN",
+        "up": "Wysyłanie",
+        "down": "Pobieranie",
+        "wait": "Proszę czekać",
         "lan": "LAN",
         "wlan": "WLAN",
-        "devices": "Devices",
-        "lan_devices": "LAN Devices",
-        "wlan_devices": "WLAN Devices"
+        "devices": "Urządzenia",
+        "lan_devices": "Urządzenia LAN",
+        "wlan_devices": "Urządzenia WLAN"
     },
     "plex": {
-        "streams": "Active Streams",
-        "movies": "Movies",
-        "tv": "TV Shows"
+        "streams": "Aktywne strumienie",
+        "movies": "Filmy",
+        "tv": "Seriale"
     },
     "glances": {
-        "cpu": "CPU",
-        "mem": "MEM",
-        "wait": "Please wait"
+        "cpu": "Procesor",
+        "mem": "Pamięć",
+        "wait": "Proszę czekać"
     },
     "changedetectionio": {
-        "diffsDetected": "Diffs Detected",
-        "totalObserved": "Total Observed"
+        "diffsDetected": "Wykryto różnic",
+        "totalObserved": "Obserwowanych ogółem"
     },
     "wmo": {
-        "77-day": "Snow Grains",
+        "77-day": "Ziarnisty śnieg",
         "0-day": "Słoneczny",
-        "0-night": "Clear",
-        "1-day": "Mainly Sunny",
-        "1-night": "Mainly Clear",
-        "2-day": "Partly Cloudy",
-        "2-night": "Partly Cloudy",
-        "3-day": "Cloudy",
-        "3-night": "Cloudy",
-        "45-day": "Foggy",
-        "45-night": "Foggy",
-        "48-day": "Foggy",
-        "48-night": "Foggy",
-        "51-day": "Light Drizzle",
-        "51-night": "Light Drizzle",
-        "53-day": "Drizzle",
-        "53-night": "Drizzle",
-        "55-day": "Heavy Drizzle",
-        "55-night": "Heavy Drizzle",
-        "56-day": "Light Freezing Drizzle",
-        "56-night": "Light Freezing Drizzle",
-        "57-day": "Freezing Drizzle",
-        "57-night": "Freezing Drizzle",
-        "61-day": "Light Rain",
-        "61-night": "Light Rain",
-        "63-day": "Rain",
-        "63-night": "Rain",
-        "65-day": "Heavy Rain",
-        "65-night": "Heavy Rain",
-        "66-day": "Freezing Rain",
-        "66-night": "Freezing Rain",
-        "67-day": "Freezing Rain",
-        "67-night": "Freezing Rain",
-        "71-day": "Light Snow",
-        "71-night": "Light Snow",
-        "73-day": "Snow",
-        "73-night": "Snow",
-        "75-day": "Heavy Snow",
-        "75-night": "Heavy Snow",
-        "77-night": "Snow Grains",
-        "80-day": "Light Showers",
-        "80-night": "Light Showers",
-        "81-day": "Showers",
-        "81-night": "Showers",
-        "82-day": "Heavy Showers",
-        "82-night": "Heavy Showers",
-        "85-day": "Snow Showers",
-        "85-night": "Snow Showers",
-        "86-day": "Snow Showers",
-        "86-night": "Snow Showers",
-        "95-day": "Thunderstorm",
-        "95-night": "Thunderstorm",
-        "96-day": "Thunderstorm With Hail",
-        "96-night": "Thunderstorm With Hail",
-        "99-day": "Thunderstorm With Hail",
-        "99-night": "Thunderstorm With Hail"
+        "0-night": "Bezchmurny",
+        "1-day": "Głównie słoneczny",
+        "1-night": "Głównie bezchmurny",
+        "2-day": "Częściowo pochmurnie",
+        "2-night": "Częściowo pochmurnie",
+        "3-day": "Pochmurnie",
+        "3-night": "Pochmurnie",
+        "45-day": "Mgliście",
+        "45-night": "Mgliście",
+        "48-day": "Mgliście",
+        "48-night": "Mgliście",
+        "51-day": "Lekka mżawka",
+        "51-night": "Lekka mżawka",
+        "53-day": "Mżawka",
+        "53-night": "Mżawka",
+        "55-day": "Ciężka mżawka",
+        "55-night": "Ciężka mżawka",
+        "56-day": "Lekko chłodna mżawka",
+        "56-night": "Lekko chłodna mżawka",
+        "57-day": "Chłodna mżawka",
+        "57-night": "Chłodna mżawka",
+        "61-day": "Lekki deszcz",
+        "61-night": "Lekki deszcz",
+        "63-day": "Deszcz",
+        "63-night": "Deszcz",
+        "65-day": "Ciężki deszcz",
+        "65-night": "Ciężki deszcz",
+        "66-day": "Mroźny deszcz",
+        "66-night": "Mroźny deszcz",
+        "67-day": "Mroźny deszcz",
+        "67-night": "Mroźny deszcz",
+        "71-day": "Lekki śnieg",
+        "71-night": "Lekki śnieg",
+        "73-day": "Śnieg",
+        "73-night": "Śnieg",
+        "75-day": "Ciężki śnieg",
+        "75-night": "Ciężki śnieg",
+        "77-night": "Ziarnisty śnieg",
+        "80-day": "Lekkie opady",
+        "80-night": "Lekkie opady",
+        "81-day": "Opady",
+        "81-night": "Opady",
+        "82-day": "Ciężkie opady",
+        "82-night": "Ciężkie opady",
+        "85-day": "Opady śniegu",
+        "85-night": "Opady śniegu",
+        "86-day": "Opady śniegu",
+        "86-night": "Opady śniegu",
+        "95-day": "Burze z piorunami",
+        "95-night": "Burze z piorunami",
+        "96-day": "Burza z gradobiciem",
+        "96-night": "Burza z gradobiciem",
+        "99-day": "Burza z gradobiciem",
+        "99-night": "Burza z gradobiciem"
     },
     "quicklaunch": {
-        "bookmark": "Bookmark",
-        "service": "Service"
+        "bookmark": "Zakładka",
+        "service": "Usługi"
     },
     "homebridge": {
         "available_update": "System",
-        "updates": "Updates",
-        "update_available": "Update Available",
-        "up_to_date": "Up to Date",
+        "updates": "Aktualizacje",
+        "update_available": "Dostępna aktualizacja",
+        "up_to_date": "Aktualny",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Zaakceptowane",
+        "rejectedPushes": "Odrzucone",
+        "filters": "Filtry",
+        "indexers": "Indeksery"
+    },
+    "watchtower": {
+        "containers_scanned": "Zeskanowane",
+        "containers_updated": "Zaktualizowane",
+        "containers_failed": "Niepowodzenie"
+    },
+    "tubearchivist": {
+        "downloads": "Kolejka",
+        "videos": "Pliki wideo",
+        "channels": "Kanały",
+        "playlists": "Playlisty"
+    },
+    "truenas": {
+        "load": "Obciążenie systemu",
+        "uptime": "Czas działania",
+        "alerts": "Ostrzeżenia",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "please_wait": "Proszę czekać",
+        "nothing_streaming": "Brak aktywnych strumieni"
+    },
+    "pyload": {
+        "speed": "Prędkość",
+        "active": "Aktywne",
+        "queue": "Kolejka",
+        "total": "Razem"
+    },
+    "gluetun": {
+        "public_ip": "Adres publiczny",
+        "region": "Region",
+        "country": "Państwo"
+    },
+    "hdhomerun": {
+        "channels": "Kanały",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Błąd",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Powodzenie",
+        "failed": "Niepowodzenie",
+        "unknown": "Nieznane"
+    },
+    "paperlessngx": {
+        "inbox": "Skrzynka odbiorcza",
+        "total": "W sumie"
+    },
+    "deluge": {
+        "download": "Pobieranie",
+        "upload": "Wysyłanie",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Pobieranie",
+        "upload": "Wysyłanie",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Pobieranie",
+        "upload": "Wysyłanie",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 85 - 5
public/locales/pt-BR/common.json

@@ -30,7 +30,11 @@
     "widget": {
         "missing_type": "Tipo de Widget ausente: {{type}}",
         "api_error": "Erro da API",
-        "status": "Status"
+        "status": "Status",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "weather": {
         "current": "Localização atual",
@@ -53,7 +57,9 @@
         "tx": "Tx",
         "mem": "Mem",
         "cpu": "CPU",
-        "offline": "Desligado"
+        "offline": "Desligado",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "emby": {
         "playing": "Reproduzindo",
@@ -122,7 +128,8 @@
     "overseerr": {
         "pending": "Pendente",
         "approved": "Aprovado",
-        "available": "Disponível"
+        "available": "Disponível",
+        "processing": "Processing"
     },
     "pihole": {
         "queries": "Consultas",
@@ -209,8 +216,8 @@
         "wait": "Please wait"
     },
     "changedetectionio": {
-        "totalObserved": "Total Observed",
-        "diffsDetected": "Diffs Detected"
+        "totalObserved": "Observados",
+        "diffsDetected": "Mudanças"
     },
     "wmo": {
         "1-night": "Mainly Clear",
@@ -281,5 +288,78 @@
         "up_to_date": "Up to Date",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 98 - 18
public/locales/pt/common.json

@@ -2,7 +2,11 @@
     "widget": {
         "missing_type": "Widget ausente: {{type}}",
         "api_error": "Erro da API",
-        "status": "Status"
+        "status": "Status",
+        "information": "Informação",
+        "url": "Endereço URL",
+        "raw_error": "Raw Error",
+        "response_data": "Dados da Resposta"
     },
     "search": {
         "placeholder": "Pesquisar…"
@@ -19,7 +23,9 @@
         "tx": "Tx",
         "mem": "Mem",
         "cpu": "CPU",
-        "offline": "Desligado"
+        "offline": "Desligado",
+        "error": "Erro",
+        "unknown": "Desconhecido"
     },
     "emby": {
         "playing": "A reproduzir",
@@ -109,7 +115,8 @@
     "overseerr": {
         "pending": "Pendente",
         "approved": "Aprovado",
-        "available": "Disponível"
+        "available": "Disponível",
+        "processing": "Processing"
     },
     "sabnzbd": {
         "rate": "Taxa",
@@ -143,7 +150,7 @@
     "transmission": {
         "download": "Baixando",
         "upload": "Enviando",
-        "leech": "Sanguessugas",
+        "leech": "Leech",
         "seed": "Semeadores"
     },
     "jackett": {
@@ -194,25 +201,25 @@
         "vms": "VMs"
     },
     "unifi": {
-        "users": "Users",
-        "uptime": "System Uptime",
-        "days": "Days",
+        "users": "Utilizadores",
+        "uptime": "Tempo de Atividade do Sistema",
+        "days": "Dias",
         "wan": "WAN",
-        "lan_users": "LAN Users",
-        "wlan_users": "WLAN Users",
+        "lan_users": "Utilizadores LAN",
+        "wlan_users": "Utilizadores WLAN",
         "up": "UP",
         "down": "DOWN",
-        "wait": "Please wait",
+        "wait": "Por favor aguarde",
         "lan": "LAN",
         "wlan": "WLAN",
-        "devices": "Devices",
-        "lan_devices": "LAN Devices",
-        "wlan_devices": "WLAN Devices"
+        "devices": "Dispositivos",
+        "lan_devices": "Dispositivos LAN",
+        "wlan_devices": "Dispositivos WLAN"
     },
     "plex": {
-        "streams": "Active Streams",
-        "movies": "Movies",
-        "tv": "TV Shows"
+        "streams": "Streams Ativas",
+        "movies": "Filmes",
+        "tv": "Series de TV"
     },
     "glances": {
         "cpu": "CPU",
@@ -220,8 +227,8 @@
         "wait": "Please wait"
     },
     "changedetectionio": {
-        "totalObserved": "Total Observed",
-        "diffsDetected": "Diffs Detected"
+        "totalObserved": "Total Observado",
+        "diffsDetected": "Diferenças Detetadas"
     },
     "wmo": {
         "0-day": "Sunny",
@@ -292,5 +299,78 @@
         "up_to_date": "Up to Date",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "queue": "Queue",
+        "total": "Total",
+        "speed": "Speed",
+        "active": "Active"
+    },
+    "gluetun": {
+        "region": "Region",
+        "country": "Country",
+        "public_ip": "Public IP"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Erro",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Descarregar",
+        "upload": "Carregar",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 83 - 3
public/locales/ro/common.json

@@ -11,7 +11,9 @@
         "tx": "TX",
         "mem": "MEM",
         "cpu": "CPU",
-        "offline": "Offline"
+        "offline": "Offline",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "jellyseerr": {
         "approved": "Aprobate",
@@ -21,7 +23,8 @@
     "overseerr": {
         "pending": "În așteptare",
         "approved": "Aprobate",
-        "available": "Disponibile"
+        "available": "Disponibile",
+        "processing": "Processing"
     },
     "pihole": {
         "queries": "Cereri",
@@ -60,7 +63,11 @@
     "widget": {
         "missing_type": "Lipsește Tipul de Widget: {{type}}",
         "api_error": "Eroare API",
-        "status": "Status"
+        "status": "Status",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "search": {
         "placeholder": "Caută…"
@@ -281,5 +288,78 @@
         "available_update": "System",
         "updates": "Updates",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 96 - 16
public/locales/ru/common.json

@@ -2,7 +2,11 @@
     "widget": {
         "missing_type": "Отсутствует тип виджета: {{type}}",
         "api_error": "Ошибка API",
-        "status": "Статус"
+        "status": "Статус",
+        "information": "Информация",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Данные ответа"
     },
     "search": {
         "placeholder": "Поиск…"
@@ -11,21 +15,23 @@
         "total": "Всего",
         "free": "Свободно",
         "used": "Использовано",
-        "load": "Load",
-        "cpu": "CPU"
+        "load": "Загрузка",
+        "cpu": "Процессор"
     },
     "docker": {
         "rx": "Rx",
         "tx": "Тx",
         "mem": "Память",
         "cpu": "Процессор",
-        "offline": "Не в сети"
+        "offline": "Не в сети",
+        "error": "Ошибка",
+        "unknown": "Неизвестный"
     },
     "emby": {
         "playing": "Воспроизведение",
         "transcoding": "Транскодирование",
         "bitrate": "Битрейт",
-        "no_active": "No Active Streams"
+        "no_active": "Нет активных потоков"
     },
     "tautulli": {
         "playing": "Воспроизведение",
@@ -98,7 +104,8 @@
     "overseerr": {
         "pending": "Pending",
         "approved": "Approved",
-        "available": "Available"
+        "available": "Available",
+        "processing": "Processing"
     },
     "sabnzbd": {
         "rate": "Rate",
@@ -183,19 +190,19 @@
         "vms": "VMs"
     },
     "unifi": {
-        "users": "Users",
-        "uptime": "System Uptime",
-        "days": "Days",
+        "users": "Пользователи",
+        "uptime": "Время работы системы",
+        "days": "Дней",
         "wan": "WAN",
-        "lan_users": "LAN Users",
-        "wlan_users": "WLAN Users",
+        "lan_users": "Пользователи LAN",
+        "wlan_users": "Пользователи WLAN",
         "up": "UP",
         "down": "DOWN",
-        "wait": "Please wait",
+        "wait": "Подождите",
         "lan": "LAN",
         "wlan": "WLAN",
-        "devices": "Devices",
-        "lan_devices": "LAN Devices",
+        "devices": "Устройства",
+        "lan_devices": "Устройства подключённые по LAN",
         "wlan_devices": "WLAN Devices"
     },
     "plex": {
@@ -209,8 +216,8 @@
         "wait": "Please wait"
     },
     "changedetectionio": {
-        "totalObserved": "Total Observed",
-        "diffsDetected": "Diffs Detected"
+        "totalObserved": "Всего наблюдаемых",
+        "diffsDetected": "Обнаружены различия"
     },
     "wmo": {
         "0-day": "Sunny",
@@ -281,5 +288,78 @@
         "up_to_date": "Up to Date",
         "child_bridges_status": "{{ok}}/{{total}}",
         "child_bridges": "Child Bridges"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Ошибка",
+        "ping": "Пинг"
+    },
+    "scrutiny": {
+        "failed": "Failed",
+        "unknown": "Unknown",
+        "passed": "Passed"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "seed": "Seed",
+        "leech": "Leech"
+    },
+    "flood": {
+        "upload": "Upload",
+        "download": "Download",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 83 - 3
public/locales/sr/common.json

@@ -2,7 +2,11 @@
     "widget": {
         "missing_type": "Missing Widget Type: {{type}}",
         "api_error": "API Error",
-        "status": "Status"
+        "status": "Status",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "weather": {
         "current": "Current Location",
@@ -41,7 +45,9 @@
         "tx": "TX",
         "mem": "MEM",
         "cpu": "CPU",
-        "offline": "Offline"
+        "offline": "Offline",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "emby": {
         "playing": "Playing",
@@ -125,7 +131,8 @@
     "overseerr": {
         "pending": "Pending",
         "approved": "Approved",
-        "available": "Available"
+        "available": "Available",
+        "processing": "Processing"
     },
     "pihole": {
         "queries": "Queries",
@@ -281,5 +288,78 @@
         "up_to_date": "Up to Date",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "seed": "Seed",
+        "upload": "Upload",
+        "leech": "Leech"
     }
 }

+ 83 - 3
public/locales/sv/common.json

@@ -2,7 +2,11 @@
     "widget": {
         "missing_type": "Saknar Widget-typ: {{type}}",
         "api_error": "API-fel",
-        "status": "Status"
+        "status": "Status",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "weather": {
         "current": "Nuvarande plats",
@@ -22,7 +26,9 @@
         "tx": "TX",
         "mem": "MEM",
         "cpu": "CPU",
-        "offline": "Offline"
+        "offline": "Offline",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "search": {
         "placeholder": "Sök…"
@@ -98,7 +104,8 @@
     "overseerr": {
         "pending": "Avvaktar",
         "approved": "Godkända",
-        "available": "Tillgänglig"
+        "available": "Tillgänglig",
+        "processing": "Processing"
     },
     "pihole": {
         "blocked": "Blockerad",
@@ -281,5 +288,78 @@
         "up_to_date": "Up to Date",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "leech": "Leech",
+        "upload": "Upload",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 148 - 68
public/locales/te/common.json

@@ -19,7 +19,11 @@
     "widget": {
         "missing_type": "విడ్జెట్ లేదు: {{type}}",
         "api_error": "API లోపం",
-        "status": "హోదా"
+        "status": "హోదా",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "weather": {
         "current": "ప్రస్తుత స్తలం",
@@ -42,7 +46,9 @@
         "tx": "TX",
         "mem": "MEM",
         "cpu": "సీపియూ",
-        "offline": "ఆఫ్‌లైన్"
+        "offline": "ఆఫ్‌లైన్",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "emby": {
         "playing": "ఆడుతున్నారు",
@@ -116,7 +122,8 @@
     "overseerr": {
         "pending": "పెండింగ్",
         "approved": "ఆమోదించబడింది",
-        "available": "అందుబాటులో"
+        "available": "అందుబాటులో",
+        "processing": "Processing"
     },
     "pihole": {
         "queries": "ప్రశ్నలు",
@@ -209,77 +216,150 @@
         "wait": "దయచేసి వేచి ఉండండి"
     },
     "changedetectionio": {
-        "totalObserved": "Total Observed",
-        "diffsDetected": "Diffs Detected"
+        "totalObserved": "మొత్తం గమనించబడింది",
+        "diffsDetected": "తేడాలు గుర్తించబడ్డాయి"
     },
     "wmo": {
-        "0-day": "Sunny",
-        "0-night": "Clear",
-        "57-night": "Freezing Drizzle",
-        "73-day": "Snow",
-        "73-night": "Snow",
-        "75-day": "Heavy Snow",
-        "75-night": "Heavy Snow",
-        "77-day": "Snow Grains",
-        "77-night": "Snow Grains",
-        "80-day": "Light Showers",
-        "80-night": "Light Showers",
-        "81-day": "Showers",
-        "81-night": "Showers",
-        "82-day": "Heavy Showers",
-        "82-night": "Heavy Showers",
-        "85-day": "Snow Showers",
-        "85-night": "Snow Showers",
-        "1-day": "Mainly Sunny",
-        "1-night": "Mainly Clear",
-        "2-day": "Partly Cloudy",
-        "2-night": "Partly Cloudy",
-        "3-day": "Cloudy",
-        "3-night": "Cloudy",
-        "45-day": "Foggy",
-        "45-night": "Foggy",
-        "48-day": "Foggy",
-        "48-night": "Foggy",
-        "51-day": "Light Drizzle",
-        "51-night": "Light Drizzle",
-        "53-day": "Drizzle",
-        "53-night": "Drizzle",
-        "55-day": "Heavy Drizzle",
-        "55-night": "Heavy Drizzle",
-        "56-day": "Light Freezing Drizzle",
-        "56-night": "Light Freezing Drizzle",
-        "57-day": "Freezing Drizzle",
-        "61-day": "Light Rain",
-        "61-night": "Light Rain",
-        "63-day": "Rain",
-        "63-night": "Rain",
-        "65-day": "Heavy Rain",
-        "65-night": "Heavy Rain",
-        "66-day": "Freezing Rain",
-        "66-night": "Freezing Rain",
-        "67-day": "Freezing Rain",
-        "67-night": "Freezing Rain",
-        "71-day": "Light Snow",
-        "71-night": "Light Snow",
-        "86-day": "Snow Showers",
-        "86-night": "Snow Showers",
-        "95-day": "Thunderstorm",
-        "95-night": "Thunderstorm",
-        "96-day": "Thunderstorm With Hail",
-        "96-night": "Thunderstorm With Hail",
-        "99-day": "Thunderstorm With Hail",
-        "99-night": "Thunderstorm With Hail"
+        "0-day": "సన్నీ",
+        "0-night": "స్పష్టమైన",
+        "57-night": "గడ్డకట్టే చినుకులు",
+        "73-day": "మంచు",
+        "73-night": "మంచు",
+        "75-day": "భారీ మంచు",
+        "75-night": "భారీ మంచు",
+        "77-day": "మంచు గింజలు",
+        "77-night": "మంచు గింజలు",
+        "80-day": "తేలికపాటి జల్లులు",
+        "80-night": "తేలికపాటి జల్లులు",
+        "81-day": "జల్లులు",
+        "81-night": "జల్లులు",
+        "82-day": "భారీ వర్షాలు",
+        "82-night": "భారీ వర్షాలు",
+        "85-day": "మంచు జల్లులు",
+        "85-night": "మంచు జల్లులు",
+        "1-day": "ప్రధానంగా ఎండ",
+        "1-night": "ప్రధానంగా స్పష్టంగా",
+        "2-day": "పాక్షికంగా మేఘావృతమై ఉంటుంది",
+        "2-night": "పాక్షికంగా మేఘావృతమై ఉంటుంది",
+        "3-day": "మేఘావృతం",
+        "3-night": "మేఘావృతం",
+        "45-day": "పొగమంచు",
+        "45-night": "పొగమంచు",
+        "48-day": "పొగమంచు",
+        "48-night": "పొగమంచు",
+        "51-day": "తేలికపాటి చినుకులు",
+        "51-night": "తేలికపాటి చినుకులు",
+        "53-day": "చినుకులు",
+        "53-night": "చినుకులు",
+        "55-day": "భారీ చినుకులు",
+        "55-night": "భారీ చినుకులు",
+        "56-day": "తేలికపాటి గడ్డకట్టే చినుకులు",
+        "56-night": "తేలికపాటి గడ్డకట్టే చినుకులు",
+        "57-day": "గడ్డకట్టే చినుకులు",
+        "61-day": "తేలికపాటి వర్షం",
+        "61-night": "తేలికపాటి వర్షం",
+        "63-day": "వర్షం",
+        "63-night": "వర్షం",
+        "65-day": "భారీవర్షం",
+        "65-night": "భారీవర్షం",
+        "66-day": "గడ్డకట్టే వర్షం",
+        "66-night": "గడ్డకట్టే వర్షం",
+        "67-day": "గడ్డకట్టే వర్షం",
+        "67-night": "గడ్డకట్టే వర్షం",
+        "71-day": "తేలికపాటి మంచు",
+        "71-night": "తేలికపాటి మంచు",
+        "86-day": "మంచు జల్లులు",
+        "86-night": "మంచు జల్లులు",
+        "95-day": "ఉరుము",
+        "95-night": "ఉరుము",
+        "96-day": "వడగళ్లతో కూడిన ఉరుములతో కూడిన వర్షం",
+        "96-night": "వడగళ్లతో కూడిన ఉరుములతో కూడిన వర్షం",
+        "99-day": "వడగళ్లతో కూడిన ఉరుములతో కూడిన వర్షం",
+        "99-night": "వడగళ్లతో కూడిన ఉరుములతో కూడిన వర్షం"
     },
     "quicklaunch": {
-        "bookmark": "Bookmark",
-        "service": "Service"
+        "bookmark": "బుక్మార్క్",
+        "service": "సేవ"
     },
     "homebridge": {
-        "available_update": "System",
-        "updates": "Updates",
-        "update_available": "Update Available",
-        "up_to_date": "Up to Date",
-        "child_bridges": "Child Bridges",
+        "available_update": "వ్యవస్థ",
+        "updates": "నవీకరణలు",
+        "update_available": "అందుబాటులో నవీకరణ",
+        "up_to_date": "తాజాగా",
+        "child_bridges": "పిల్ల వంతెనలు",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "rejectedPushes": "తిరస్కరించారు",
+        "approvedPushes": "ఆమోదించబడింది",
+        "filters": "ఫిల్టర్లు",
+        "indexers": "సూచికలు"
+    },
+    "watchtower": {
+        "containers_scanned": "స్కాన్ చేశారు",
+        "containers_updated": "నవీకరించబడింది",
+        "containers_failed": "విఫలమయ్యారు"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "seed": "Seed",
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech"
+    },
+    "diskstation": {
+        "leech": "Leech",
+        "download": "Download",
+        "upload": "Upload",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 146 - 66
public/locales/tr/common.json

@@ -2,7 +2,11 @@
     "widget": {
         "missing_type": "Kayıp Araç Türü: {{type}}",
         "api_error": "API Hatası",
-        "status": "Durum"
+        "status": "Durum",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "weather": {
         "current": "Mevcut Konum",
@@ -41,7 +45,9 @@
         "tx": "Giden Veri",
         "mem": "Bellek",
         "cpu": "İşlemci",
-        "offline": "Çevrimdışı"
+        "offline": "Çevrimdışı",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "emby": {
         "playing": "Oynatılıyor",
@@ -125,7 +131,8 @@
     "overseerr": {
         "pending": "Bekliyor",
         "approved": "Onaylı",
-        "available": "Kullanılabilir"
+        "available": "Kullanılabilir",
+        "processing": "Processing"
     },
     "pihole": {
         "queries": "Sorgular",
@@ -213,73 +220,146 @@
         "diffsDetected": "Farklar Algılandı"
     },
     "wmo": {
-        "99-day": "Thunderstorm With Hail",
-        "0-day": "Sunny",
-        "0-night": "Clear",
-        "1-day": "Mainly Sunny",
-        "1-night": "Mainly Clear",
-        "2-day": "Partly Cloudy",
-        "2-night": "Partly Cloudy",
-        "3-day": "Cloudy",
-        "3-night": "Cloudy",
-        "45-day": "Foggy",
-        "45-night": "Foggy",
-        "48-day": "Foggy",
-        "48-night": "Foggy",
-        "51-day": "Light Drizzle",
-        "51-night": "Light Drizzle",
-        "53-day": "Drizzle",
-        "53-night": "Drizzle",
-        "55-day": "Heavy Drizzle",
-        "55-night": "Heavy Drizzle",
-        "56-day": "Light Freezing Drizzle",
-        "56-night": "Light Freezing Drizzle",
-        "57-day": "Freezing Drizzle",
-        "57-night": "Freezing Drizzle",
-        "61-day": "Light Rain",
-        "61-night": "Light Rain",
-        "63-day": "Rain",
-        "63-night": "Rain",
-        "65-day": "Heavy Rain",
-        "65-night": "Heavy Rain",
-        "66-day": "Freezing Rain",
-        "66-night": "Freezing Rain",
-        "67-day": "Freezing Rain",
-        "67-night": "Freezing Rain",
-        "71-day": "Light Snow",
-        "71-night": "Light Snow",
-        "73-day": "Snow",
-        "73-night": "Snow",
-        "75-day": "Heavy Snow",
-        "75-night": "Heavy Snow",
-        "77-day": "Snow Grains",
-        "77-night": "Snow Grains",
-        "80-day": "Light Showers",
-        "80-night": "Light Showers",
-        "81-day": "Showers",
-        "81-night": "Showers",
-        "82-day": "Heavy Showers",
-        "95-night": "Thunderstorm",
-        "82-night": "Heavy Showers",
-        "85-day": "Snow Showers",
-        "85-night": "Snow Showers",
-        "86-day": "Snow Showers",
-        "86-night": "Snow Showers",
-        "95-day": "Thunderstorm",
-        "96-day": "Thunderstorm With Hail",
-        "96-night": "Thunderstorm With Hail",
-        "99-night": "Thunderstorm With Hail"
+        "99-day": "Dolu İle Gök Gürültülü Fırtına",
+        "0-day": "Güneşli",
+        "0-night": "Açık",
+        "1-day": "Çoğunlukla Güneşli",
+        "1-night": "Çoğunlukla Açık",
+        "2-day": "Parçalı Bulutlu",
+        "2-night": "Parçalı Bulutlu",
+        "3-day": "Bulutlu",
+        "3-night": "Bulutlu",
+        "45-day": "Sisli",
+        "45-night": "Sisli",
+        "48-day": "Sisli",
+        "48-night": "Sisli",
+        "51-day": "Az Çiseleyen Yağmur",
+        "51-night": "Az Çiseleyen Yağmur",
+        "53-day": "Çiseleyen Yağmur",
+        "53-night": "Çiseleyen Yağmur",
+        "55-day": "Çok Çiseleyen Yağmur",
+        "55-night": "Çok Çiseleyen Yağmur",
+        "56-day": "Soğuk Az Çiseleyen Yağmur",
+        "56-night": "Soğuk Az Çiseleyen Yağmur",
+        "57-day": "Soğuk Çiseleyen Yağmur",
+        "57-night": "Soğuk Çiseleyen Yağmur",
+        "61-day": "Hafif Yağmur",
+        "61-night": "Hafif Yağmur",
+        "63-day": "Yağmur",
+        "63-night": "Yağmur",
+        "65-day": "Çok Yağmur",
+        "65-night": "Çok Yağmur",
+        "66-day": "Dondurucu Yağmur",
+        "66-night": "Dondurucu Yağmur",
+        "67-day": "Dondurucu Yağmur",
+        "67-night": "Dondurucu Yağmur",
+        "71-day": "Hafif Kar",
+        "71-night": "Hafif Kar",
+        "73-day": "Kar",
+        "73-night": "Kar",
+        "75-day": "Çok Kar",
+        "75-night": "Çok Kar",
+        "77-day": "Kar Taneleri",
+        "77-night": "Kar Taneleri",
+        "80-day": "Hafif Sağanak",
+        "80-night": "Hafif Sağanak",
+        "81-day": "Sağanak",
+        "81-night": "Sağanak",
+        "82-day": "Yoğun Sağanak",
+        "95-night": "Gök Gürültülü Fırtına",
+        "82-night": "Yoğun Sağanak",
+        "85-day": "Karlı Sağanak",
+        "85-night": "Karlı Sağanak",
+        "86-day": "Karlı Sağanak",
+        "86-night": "Karlı Sağanak",
+        "95-day": "Gök Gürültülü Fırtına",
+        "96-day": "Dolu İle Gök Gürültülü Fırtına",
+        "96-night": "Dolu İle Gök Gürültülü Fırtına",
+        "99-night": "Dolu İle Gök Gürültülü Fırtına"
     },
     "quicklaunch": {
-        "bookmark": "Bookmark",
-        "service": "Service"
+        "bookmark": "Yer İmi",
+        "service": "Hizmet"
     },
     "homebridge": {
-        "available_update": "System",
-        "updates": "Updates",
-        "update_available": "Update Available",
-        "up_to_date": "Up to Date",
-        "child_bridges": "Child Bridges",
+        "available_update": "Sistem",
+        "updates": "Güncellemeler",
+        "update_available": "Güncelleme Kullanılabilir",
+        "up_to_date": "Güncel",
+        "child_bridges": "Alt Köprüler",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Onaylandı",
+        "rejectedPushes": "Reddedildi",
+        "filters": "Süzgeçler",
+        "indexers": "Dizin Oluşturucular"
+    },
+    "watchtower": {
+        "containers_scanned": "Tarandı",
+        "containers_updated": "Güncellendi",
+        "containers_failed": "Başarısız"
+    },
+    "tubearchivist": {
+        "downloads": "Kuyruk",
+        "videos": "Videolar",
+        "channels": "Kanallar",
+        "playlists": "Oynatma Listeleri"
+    },
+    "truenas": {
+        "load": "Sistem Yükü",
+        "uptime": "Çalışma Süresi",
+        "alerts": "Alarmlar",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "Geçerli Akış Yok",
+        "please_wait": "Lütfen Bekleyin"
+    },
+    "pyload": {
+        "speed": "Hız",
+        "active": "Geçerli",
+        "queue": "Kuyruk",
+        "total": "Toplam"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 83 - 3
public/locales/vi/common.json

@@ -2,7 +2,11 @@
     "widget": {
         "missing_type": "Thiếu loại Widget: {{type}}",
         "api_error": "Lỗi API",
-        "status": "Trạng thái"
+        "status": "Trạng thái",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "search": {
         "placeholder": "Tìm kiếm…"
@@ -19,7 +23,9 @@
         "tx": "TX",
         "mem": "BỘ NHỚ",
         "cpu": "CPU",
-        "offline": "Ngoại tuyến"
+        "offline": "Ngoại tuyến",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "emby": {
         "playing": "Đang chơi",
@@ -98,7 +104,8 @@
     "overseerr": {
         "pending": "Pending",
         "approved": "Đã duyệt",
-        "available": "Available"
+        "available": "Available",
+        "processing": "Processing"
     },
     "sabnzbd": {
         "rate": "Rate",
@@ -281,5 +288,78 @@
         "up_to_date": "Up to Date",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "seed": "Seed",
+        "leech": "Leech"
     }
 }

+ 83 - 3
public/locales/yue/common.json

@@ -20,7 +20,11 @@
     "widget": {
         "missing_type": "缺少小部件類型:{{type}}",
         "api_error": "API 錯誤",
-        "status": "狀況"
+        "status": "狀況",
+        "url": "URL",
+        "information": "Information",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "weather": {
         "current": "依家位置",
@@ -43,7 +47,9 @@
         "tx": "發送",
         "mem": "內存",
         "cpu": "處理器",
-        "offline": "離線"
+        "offline": "離線",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "nzbget": {
         "rate": "速度",
@@ -104,7 +110,8 @@
     "overseerr": {
         "pending": "待定",
         "approved": "批准",
-        "available": "可用"
+        "available": "可用",
+        "processing": "Processing"
     },
     "pihole": {
         "queries": "查詢",
@@ -281,5 +288,78 @@
         "up_to_date": "Up to Date",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 107 - 27
public/locales/zh-CN/common.json

@@ -2,7 +2,11 @@
     "widget": {
         "missing_type": "缺少小部件类型:{{type}}",
         "api_error": "API错误",
-        "status": "状态"
+        "status": "状态",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "search": {
         "placeholder": "搜索…"
@@ -19,7 +23,9 @@
         "tx": "发送",
         "mem": "内存",
         "cpu": "处理器",
-        "offline": "离线"
+        "offline": "离线",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "emby": {
         "playing": "播放中",
@@ -47,7 +53,7 @@
         "wanted": "想看",
         "queued": "队列",
         "movies": "电影",
-        "missing": "Missing"
+        "missing": "丢失"
     },
     "readarr": {
         "wanted": "订阅",
@@ -98,7 +104,8 @@
     "overseerr": {
         "pending": "待办",
         "approved": "已批准",
-        "available": "可用"
+        "available": "可用",
+        "processing": "Processing"
     },
     "sabnzbd": {
         "rate": "速率",
@@ -192,11 +199,11 @@
         "up": "向上",
         "down": "向下",
         "wait": "请稍候",
-        "lan": "LAN",
-        "wlan": "WLAN",
-        "devices": "Devices",
-        "lan_devices": "LAN Devices",
-        "wlan_devices": "WLAN Devices"
+        "lan": "局域网",
+        "wlan": "无线局域网",
+        "devices": "设备",
+        "lan_devices": "局域网设备",
+        "wlan_devices": "无线局域网设备"
     },
     "plex": {
         "streams": "活动流",
@@ -209,26 +216,26 @@
         "wait": "请稍等"
     },
     "changedetectionio": {
-        "totalObserved": "Total Observed",
-        "diffsDetected": "Diffs Detected"
+        "totalObserved": "观察到的总数",
+        "diffsDetected": "检测到差异"
     },
     "wmo": {
-        "0-day": "Sunny",
-        "0-night": "Clear",
-        "1-day": "Mainly Sunny",
-        "3-day": "Cloudy",
-        "3-night": "Cloudy",
-        "45-day": "Foggy",
-        "48-day": "Foggy",
-        "51-day": "Light Drizzle",
+        "0-day": "晴天",
+        "0-night": "晴朗",
+        "1-day": "主要是晴天",
+        "3-day": "阴天",
+        "3-night": "阴天",
+        "45-day": "有雾",
+        "48-day": "有雾",
+        "51-day": "小雨",
         "73-night": "Snow",
         "75-day": "Heavy Snow",
-        "1-night": "Mainly Clear",
-        "2-day": "Partly Cloudy",
-        "2-night": "Partly Cloudy",
-        "45-night": "Foggy",
-        "48-night": "Foggy",
-        "51-night": "Light Drizzle",
+        "1-night": "大部晴朗",
+        "2-day": "多云",
+        "2-night": "多云",
+        "45-night": "有雾",
+        "48-night": "有雾",
+        "51-night": "小雨",
         "53-day": "Drizzle",
         "53-night": "Drizzle",
         "55-day": "Heavy Drizzle",
@@ -271,8 +278,8 @@
         "99-night": "Thunderstorm With Hail"
     },
     "quicklaunch": {
-        "bookmark": "Bookmark",
-        "service": "Service"
+        "bookmark": "书签",
+        "service": "服务"
     },
     "homebridge": {
         "available_update": "System",
@@ -281,5 +288,78 @@
         "up_to_date": "Up to Date",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "以扫描",
+        "containers_updated": "以升级",
+        "containers_failed": "失败"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "警报",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "暂无播放",
+        "please_wait": "请等待"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "leech": "Leech",
+        "download": "Download",
+        "upload": "Upload",
+        "seed": "Seed"
     }
 }

+ 83 - 3
public/locales/zh-Hant/common.json

@@ -2,7 +2,11 @@
     "widget": {
         "missing_type": "Missing Widget Type: {{type}}",
         "api_error": "API Error",
-        "status": "Status"
+        "status": "Status",
+        "information": "Information",
+        "url": "URL",
+        "raw_error": "Raw Error",
+        "response_data": "Response Data"
     },
     "weather": {
         "current": "Current Location",
@@ -15,7 +19,9 @@
         "offline": "Offline",
         "tx": "TX",
         "mem": "MEM",
-        "cpu": "CPU"
+        "cpu": "CPU",
+        "error": "Error",
+        "unknown": "Unknown"
     },
     "emby": {
         "playing": "Playing",
@@ -83,7 +89,8 @@
     "overseerr": {
         "pending": "Pending",
         "approved": "Approved",
-        "available": "Available"
+        "available": "Available",
+        "processing": "Processing"
     },
     "pihole": {
         "queries": "Queries",
@@ -281,5 +288,78 @@
         "up_to_date": "Up to Date",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "navidrome": {
+        "nothing_streaming": "No Active Streams",
+        "please_wait": "Please Wait"
+    },
+    "pyload": {
+        "speed": "Speed",
+        "active": "Active",
+        "queue": "Queue",
+        "total": "Total"
+    },
+    "gluetun": {
+        "public_ip": "Public IP",
+        "region": "Region",
+        "country": "Country"
+    },
+    "hdhomerun": {
+        "channels": "Channels",
+        "hd": "HD"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
+    },
+    "scrutiny": {
+        "passed": "Passed",
+        "failed": "Failed",
+        "unknown": "Unknown"
+    },
+    "paperlessngx": {
+        "inbox": "Inbox",
+        "total": "Total"
+    },
+    "deluge": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "diskstation": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "flood": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
     }
 }

+ 1 - 1
src/components/bookmarks/group.jsx

@@ -3,7 +3,7 @@ import List from "components/bookmarks/list";
 
 export default function BookmarksGroup({ group }) {
   return (
-    <div key={group.name} className="basis-full md:basis-1/2 lg:basis-1/3 xl:basis-1/4 flex-1">
+    <div key={group.name} className="flex-1">
       <h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{group.name}</h2>
       <ErrorBoundary>
         <List bookmarks={group.bookmarks} />

+ 7 - 1
src/components/bookmarks/item.jsx

@@ -1,6 +1,7 @@
 import { useContext } from "react";
 
 import { SettingsContext } from "utils/contexts/settings";
+import ResolvedIcon from "components/resolvedicon";
 
 export default function Item({ bookmark }) {
   const { hostname } = new URL(bookmark.href);
@@ -16,7 +17,12 @@ export default function Item({ bookmark }) {
       >
         <div className="flex">
           <div className="flex-shrink-0 flex items-center justify-center w-11 bg-theme-500/10 dark:bg-theme-900/50 text-theme-700 hover:text-theme-700 dark:text-theme-200 text-sm font-medium rounded-l-md">
-            {bookmark.abbr}
+            {bookmark.icon && 
+              <div className="flex-shrink-0 w-5 h-5">
+                <ResolvedIcon icon={bookmark.icon} />
+              </div>
+            }
+            {!bookmark.icon && bookmark.abbr}
           </div>
           <div className="flex-1 flex items-center justify-between rounded-r-md ">
             <div className="flex-1 grow pl-3 py-2 text-xs">{bookmark.name}</div>

+ 7 - 6
src/components/quicklaunch.jsx

@@ -2,7 +2,7 @@ import { useTranslation } from "react-i18next";
 import { useEffect, useState, useRef, useCallback, useContext } from "react";
 import classNames from "classnames";
 
-import { resolveIcon } from "./services/item";
+import ResolvedIcon from "./resolvedicon";
 
 import { SettingsContext } from "utils/contexts/settings";
 
@@ -107,18 +107,19 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear
 
   function highlightText(text) {
     const parts = text.split(new RegExp(`(${searchString})`, 'gi'));
-    return <span>{parts.map(part => part.toLowerCase() === searchString.toLowerCase() ? <span className="bg-theme-300/10">{part}</span> : part)}</span>;
+    // eslint-disable-next-line react/no-array-index-key
+    return <span>{parts.map((part, i) => part.toLowerCase() === searchString.toLowerCase() ? <span key={`${searchString}_${i}`} className="bg-theme-300/10">{part}</span> : part)}</span>;
   }
 
   return (
     <div className={classNames(
-      "relative z-10 ease-in-out duration-300 transition-opacity",
+      "relative z-20 ease-in-out duration-300 transition-opacity",
       hidden && !isOpen && "hidden",
       !hidden && isOpen && "opacity-100",
       !isOpen && "opacity-0",
     )} role="dialog" aria-modal="true">
       <div className="fixed inset-0 bg-gray-500 bg-opacity-50" />
-      <div className="fixed inset-0 z-10 overflow-y-auto">
+      <div className="fixed inset-0 z-20 overflow-y-auto">
         <div className="flex min-h-full min-w-full items-start justify-center text-center">
           <dialog className="mt-[10%] min-w-[80%] max-w-[90%] md:min-w-[40%] rounded-md p-0 block font-medium text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-50 dark:bg-theme-800">
             <input placeholder="Search" className={classNames(
@@ -135,7 +136,7 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear
                     )} onClick={handleItemClick}>
                     <div className="flex flex-row items-center mr-4 pointer-events-none">
                       <div className="w-5 text-xs mr-4">
-                        {r.icon && resolveIcon(r.icon)}
+                        {r.icon && <ResolvedIcon icon={r.icon} />}
                         {r.abbr && r.abbr}
                       </div>
                       <div className="flex flex-col md:flex-row text-left items-baseline mr-4 pointer-events-none">
@@ -147,7 +148,7 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear
                         }
                       </div>
                     </div>
-                    <div className="text-xs text-theme-600 font-bold pointer-events-none">{r.abbr ? t("quicklaunch.bookmark") : t("quicklaunch.service")}</div>
+                    <div className="text-xs text-theme-600 font-bold pointer-events-none">{r.type === 'service' ? t("quicklaunch.service") : t("quicklaunch.bookmark")}</div>
                   </button>
                 </li>
               ))}

+ 37 - 0
src/components/resolvedicon.jsx

@@ -0,0 +1,37 @@
+import Image from "next/future/image";
+
+export default function ResolvedIcon({ icon }) {
+  // direct or relative URLs
+  if (icon.startsWith("http") || icon.startsWith("/")) {
+    return <Image src={`${icon}`} width={32} height={32} alt="logo" />;
+  }
+
+  // mdi- prefixed, material design icons
+  if (icon.startsWith("mdi-")) {
+    const iconName = icon.replace("mdi-", "").replace(".svg", "");
+    return (
+      <div
+        style={{
+          width: 32,
+          height: 32,
+          maxWidth: '100%',
+          maxHeight: '100%',
+          background: "linear-gradient(180deg, rgb(var(--color-logo-start)), rgb(var(--color-logo-stop)))",
+          mask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${iconName}.svg) no-repeat center / contain`,
+          WebkitMask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${iconName}.svg) no-repeat center / contain`,
+        }}
+      />
+    );
+  }
+
+  // fallback to dashboard-icons
+  const iconName = icon.replace(".png", "");
+  return (
+    <Image
+      src={`https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${iconName}.png`}
+      width={32}
+      height={32}
+      alt="logo"
+    />
+  );
+}

+ 9 - 1
src/components/services/group.jsx

@@ -1,6 +1,7 @@
 import classNames from "classnames";
 
 import List from "components/services/list";
+import ResolvedIcon from "components/resolvedicon";
 
 export default function ServicesGroup({ services, layout }) {
   return (
@@ -11,7 +12,14 @@ export default function ServicesGroup({ services, layout }) {
         "flex-1 p-1"
       )}
     >
-      <h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{services.name}</h2>
+      <div className="flex select-none items-center">
+        {layout?.icon &&
+          <div className="flex-shrink-0 mr-2 w-7 h-7">
+            <ResolvedIcon icon={layout.icon} />
+          </div>
+        }
+        <h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{services.name}</h2>
+      </div>
       <List services={services.services} layout={layout} />
     </div>
   );

+ 27 - 49
src/components/services/item.jsx

@@ -1,46 +1,13 @@
-import Image from "next/future/image";
 import classNames from "classnames";
 import { useContext, useState } from "react";
 
 import Status from "./status";
 import Widget from "./widget";
+import Ping from "./ping";
 
 import Docker from "widgets/docker/component";
 import { SettingsContext } from "utils/contexts/settings";
-
-export function resolveIcon(icon) {
-  // direct or relative URLs
-  if (icon.startsWith("http") || icon.startsWith("/")) {
-    return <Image src={`${icon}`} width={32} height={32} alt="logo" />;
-  }
-
-  // mdi- prefixed, material design icons
-  if (icon.startsWith("mdi-")) {
-    const iconName = icon.replace("mdi-", "").replace(".svg", "");
-    return (
-      <div
-        style={{
-          width: 32,
-          height: 32,
-          background: "linear-gradient(180deg, rgb(var(--color-logo-start)), rgb(var(--color-logo-stop)))",
-          mask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${iconName}.svg) no-repeat center / contain`,
-          WebkitMask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${iconName}.svg) no-repeat center / contain`,
-        }}
-      />
-    );
-  }
-
-  // fallback to dashboard-icons
-  const iconName = icon.replace(".png", "");
-  return (
-    <Image
-      src={`https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${iconName}.png`}
-      width={32}
-      height={32}
-      alt="logo"
-    />
-  );
-}
+import ResolvedIcon from "components/resolvedicon";
 
 export default function Item({ service }) {
   const hasLink = service.href && service.href !== "#";
@@ -64,7 +31,7 @@ export default function Item({ service }) {
       <div
         className={`${
           hasLink ? "cursor-pointer " : " "
-        }transition-all h-15 mb-3 p-1 rounded-md font-medium text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-100/20 hover:bg-theme-300/20 dark:bg-white/5 dark:hover:bg-white/10`}
+        }transition-all h-15 mb-3 p-1 rounded-md font-medium text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-100/20 hover:bg-theme-300/20 dark:bg-white/5 dark:hover:bg-white/10 relative`}
       >
         <div className="flex select-none">
           {service.icon &&
@@ -75,10 +42,12 @@ export default function Item({ service }) {
                 rel="noreferrer"
                 className="flex-shrink-0 flex items-center justify-center w-12 "
               >
-                {resolveIcon(service.icon)}
+                <ResolvedIcon icon={service.icon} />
               </a>
             ) : (
-              <div className="flex-shrink-0 flex items-center justify-center w-12 ">{resolveIcon(service.icon)}</div>
+              <div className="flex-shrink-0 flex items-center justify-center w-12 ">
+                <ResolvedIcon icon={service.icon} />
+              </div>
             ))}
 
           {hasLink ? (
@@ -102,22 +71,31 @@ export default function Item({ service }) {
             </div>
           )}
 
-          {service.container && (
-            <button
-              type="button"
-              onClick={() => (statsOpen ? closeStats() : setStatsOpen(true))}
-              className="flex-shrink-0 flex items-center justify-center w-12 cursor-pointer"
-            >
-              <Status service={service} />
-              <span className="sr-only">View container stats</span>
-            </button>
-          )}
+          <div className="absolute top-0 right-0 w-1/2 flex flex-row justify-end gap-2 mr-2">
+              {service.ping && (
+                <div className="flex-shrink-0 flex items-center justify-center cursor-pointer">
+                  <Ping service={service} />
+                  <span className="sr-only">Ping status</span>
+                </div>
+              )}
+
+              {service.container && (
+                <button
+                  type="button"
+                  onClick={() => (statsOpen ? closeStats() : setStatsOpen(true))}
+                  className="flex-shrink-0 flex items-center justify-center cursor-pointer"
+                >
+                  <Status service={service} />
+                  <span className="sr-only">View container stats</span>
+                </button>
+              )}
+          </div>
         </div>
 
         {service.container && service.server && (
           <div
             className={classNames(
-              statsOpen && !statsClosing ? "max-h-[55px] opacity-100" : " max-h-[0] opacity-0",
+              statsOpen && !statsClosing ? "max-h-[110px] opacity-100" : " max-h-[0] opacity-0",
               "w-full overflow-hidden transition-all duration-300 ease-in-out"
             )}
           >

+ 44 - 0
src/components/services/ping.jsx

@@ -0,0 +1,44 @@
+import { useTranslation } from "react-i18next";
+import useSWR from "swr";
+
+export default function Ping({ service }) {
+  const { t } = useTranslation();
+  const { data, error } = useSWR(`/api/ping?${new URLSearchParams({ping: service.ping}).toString()}`, {
+    refreshInterval: 30000
+  });
+
+  if (error) {
+    return (
+      <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden">
+        <div className="text-[8px] font-bold text-rose-500 uppercase">{t("ping.error")}</div>
+      </div>
+    );
+  }
+  
+  if (!data) {
+    return (
+      <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden">
+        <div className="text-[8px] font-bold text-black/20 dark:text-white/40 uppercase">{t("ping.ping")}</div>
+      </div>
+    );
+  }
+
+  const statusText = `${service.ping}: HTTP status ${data.status}`;
+  
+  if (data && data.status !== 200) {
+    return (
+      <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={statusText}>
+        <div className="text-[8px] font-bold text-rose-500/80">{data.status}</div>
+      </div>
+    );
+  }
+  
+  if (data && data.status === 200) {
+    return (
+      <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={statusText}>
+        <div className="text-[8px] font-bold text-emerald-500/80">{t("common.ms", { value: data.latency, style: "unit", unit: "millisecond", unitDisplay: "narrow", maximumFractionDigits: 0 })}</div>
+      </div>
+    );
+  }
+
+}

+ 38 - 5
src/components/services/status.jsx

@@ -1,19 +1,52 @@
+import { useTranslation } from "react-i18next";
 import useSWR from "swr";
 
 export default function Status({ service }) {
+  const { t } = useTranslation();
+
   const { data, error } = useSWR(`/api/docker/status/${service.container}/${service.server || ""}`);
 
   if (error) {
-    return <div className="w-3 h-3 bg-rose-300 dark:bg-rose-500 rounded-full" />;
+    <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={data.status}>
+      <div className="text-[8px] font-bold text-rose-500/80 uppercase">{t("docker.error")}</div>
+    </div>
   }
 
   if (data && data.status === "running") {
-    return <div className="w-3 h-3 bg-emerald-300 dark:bg-emerald-500 rounded-full" />;
+    if (data.health === "starting") {
+      return (
+        <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={data.health}>
+          <div className="text-[8px] font-bold text-blue-500/80 uppercase">{data.health}</div>
+        </div>
+      );
+    }
+
+    if (data.health === "unhealthy") {
+      return (
+        <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={data.health}>
+          <div className="text-[8px] font-bold text-orange-400/50 dark:text-orange-400/80 uppercase">{data.health}</div>
+        </div>
+      );
+    }
+
+    return (
+      <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={data.health ?? data.status}>
+        <div className="text-[8px] font-bold text-emerald-500/80 uppercase">{data.health ?? data.status}</div>
+      </div>
+    );
   }
 
-  if (data && data.status === "not found") {
-    return <div className="h-2.5 w-2.5 bg-orange-400/50 dark:bg-yellow-200/40 -rotate-45" />;
+  if (data && (data.status === "not found" || data.status === "exited")) {
+    return (
+      <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={data.status}>
+        <div className="text-[8px] font-bold text-orange-400/50 dark:text-orange-400/80 uppercase">{data.status}</div>
+      </div>
+    );
   }
 
-  return <div className="w-3 h-3 bg-black/20 dark:bg-white/40 rounded-full" />;
+  return (
+    <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden">
+      <div className="text-[8px] font-bold text-black/20 dark:text-white/40 uppercase">{t("docker.unknown")}</div>
+    </div>
+  );
 }

+ 1 - 1
src/components/services/widget/block.jsx

@@ -7,7 +7,7 @@ export default function Block({ value, label }) {
   return (
     <div
       className={classNames(
-        "bg-theme-200/50 dark:bg-theme-900/20 rounded m-1 flex-1 flex flex-col items-center justify-center p-1",
+        "bg-theme-200/50 dark:bg-theme-900/20 rounded m-1 flex-1 flex flex-col items-center justify-center text-center p-1",
         value === undefined ? "animate-pulse" : ""
       )}
     >

+ 3 - 5
src/components/services/widget/container.jsx

@@ -1,10 +1,8 @@
+import Error from "./error";
+
 export default function Container({ error = false, children, service }) {
   if (error) {
-    return (
-      <div className="bg-theme-200/50 dark:bg-theme-900/20 rounded m-1 flex-1 flex flex-col items-center justify-center p-1">
-        <div className="font-thin text-sm">{error}</div>
-      </div>
-    );
+    return <Error error={error} />
   }
 
   let visibleChildren = children;

+ 50 - 0
src/components/services/widget/error.jsx

@@ -0,0 +1,50 @@
+import { useTranslation } from "react-i18next";
+import { IoAlertCircle } from "react-icons/io5";
+
+function displayError(error) {
+  return JSON.stringify(error[1] ? error[1] : error, null, 4);
+}
+
+function displayData(data) {
+  return (data.type === 'Buffer') ? Buffer.from(data).toString() : JSON.stringify(data, 4);
+}
+
+export default function Error({ error }) {
+  const { t } = useTranslation();
+
+  if (error?.data?.error) {
+    error = error.data.error; // eslint-disable-line no-param-reassign
+  }
+
+  return (
+    <details className="px-1 pb-1">
+      <summary className="block text-center mt-1 mb-0 mx-auto p-3 rounded bg-rose-900/80 hover:bg-rose-900/95 text-theme-900 cursor-pointer">
+        <div className="flex items-center justify-center text-xs font-bold">
+          <IoAlertCircle className="mr-1 w-5 h-5"/>{t("widget.api_error")} {error.message && t("widget.information")}
+        </div>
+      </summary>
+      <div className="bg-white dark:bg-theme-200/50 mt-2 rounded text-rose-900 text-xs font-mono whitespace-pre-wrap break-all">
+        <ul className="p-4">
+          {error.message && <li>
+            <span className="text-black">{t("widget.api_error")}:</span> {error.message}
+          </li>}
+          {error.url && <li className="mt-2">
+            <span className="text-black">{t("widget.url")}:</span> {error.url}
+          </li>}
+          {error.rawError && <li className="mt-2">
+            <span className="text-black">{t("widget.raw_error")}:</span>
+            <div className="ml-2">
+              {displayError(error.rawError)}
+            </div>
+          </li>}
+          {error.data && <li className="mt-2">
+            <span className="text-black">{t("widget.response_data")}:</span>
+            <div className="ml-2">
+              {displayData(error.data)}
+            </div>
+          </li>}
+        </ul>
+      </div>
+    </details>
+  );
+}

+ 11 - 14
src/components/version.jsx

@@ -8,9 +8,9 @@ import cachedFetch from "utils/proxy/cached-fetch";
 export default function Version() {
   const { t, i18n } = useTranslation();
 
-  const buildTime = process.env.NEXT_PUBLIC_BUILDTIME ?? new Date().toISOString();
-  const revision = process.env.NEXT_PUBLIC_REVISION ?? "dev";
-  const version = process.env.NEXT_PUBLIC_VERSION ?? "dev";
+  const buildTime = process.env.NEXT_PUBLIC_BUILDTIME?.length ? process.env.NEXT_PUBLIC_BUILDTIME : new Date().toISOString();
+  const revision = process.env.NEXT_PUBLIC_REVISION?.length ? process.env.NEXT_PUBLIC_REVISION : "dev";
+  const version = process.env.NEXT_PUBLIC_VERSION?.length ?  process.env.NEXT_PUBLIC_VERSION : "dev";
 
   const cachedFetcher = (resource) => cachedFetch(resource, 5).then((res) => res.json());
 
@@ -36,17 +36,14 @@ export default function Version() {
             {version} ({revision.substring(0, 7)}, {formatDate(buildTime)})
           </>
         ) : (
-          releaseData &&
-          compareVersions(latestRelease.tag_name, version) > 0 && (
-            <a
-              href={`https://github.com/benphelps/homepage/releases/tag/${version}`}
-              target="_blank"
-              rel="noopener noreferrer"
-              className="ml-2 text-xs text-theme-500 dark:text-theme-400 flex flex-row items-center"
-            >
-              {version} ({revision.substring(0, 7)}, {formatDate(buildTime)})
-            </a>
-          )
+          <a
+            href={`https://github.com/benphelps/homepage/releases/tag/${version}`}
+            target="_blank"
+            rel="noopener noreferrer"
+            className="ml-2 text-xs text-theme-500 dark:text-theme-400 flex flex-row items-center"
+          >
+            {version} ({revision.substring(0, 7)}, {formatDate(buildTime)})
+          </a>
         )}
       </span>
       {version === "main" || version === "dev" || version === "nightly"

+ 11 - 10
src/components/widgets/datetime/datetime.jsx

@@ -15,22 +15,23 @@ const textSizes = {
 export default function DateTime({ options }) {
   const { text_size: textSize, format } = options;
   const { i18n } = useTranslation();
-  const [date, setDate] = useState(new Date());
-
+  const [date, setDate] = useState("");
+  
   useEffect(() => {
+    const dateFormat = new Intl.DateTimeFormat(i18n.language, { ...format });
     const interval = setInterval(() => {
-      setDate(new Date());
+      setDate(dateFormat.format(new Date()));
     }, 1000);
     return () => clearInterval(interval);
-  }, [setDate]);
-
-  const dateFormat = new Intl.DateTimeFormat(i18n.language, { ...format });
+  }, [date, setDate, i18n.language, format]);
 
   return (
-    <div className="flex flex-row items-center grow justify-end">
-      <span className={`text-theme-800 dark:text-theme-200 ${textSizes[textSize || "lg"]}`}>
-        {dateFormat.format(date)}
-      </span>
+    <div className="flex flex-col justify-center first:ml-0 ml-4">
+      <div className="flex flex-row items-center grow justify-end">
+        <span className={`text-theme-800 dark:text-theme-200 ${textSizes[textSize || "lg"]}`}>
+          {date}
+        </span>
+      </div>
     </div>
   );
 }

+ 1 - 1
src/components/widgets/openmeteo/icon.jsx

@@ -1,4 +1,4 @@
-import mapIcon from "utils/weather/owm-condition-map";
+import mapIcon from "utils/weather/openmeteo-condition-map";
 
 export default function Icon({ condition, timeOfDay }) {
   const IconComponent = mapIcon(condition, timeOfDay);

+ 1 - 1
src/components/widgets/openweathermap/weather.jsx

@@ -54,7 +54,7 @@ function Widget({ options }) {
         <div className="hidden sm:flex flex-col items-center">
           <Icon
             condition={data.weather[0].id}
-            timeOfDay={data.dt > data.sys.sunrise && data.dt < data.sys.sundown ? "day" : "night"}
+            timeOfDay={data.dt > data.sys.sunrise && data.dt < data.sys.sunset ? "day" : "night"}
           />
         </div>
         <div className="flex flex-col ml-3 text-left">

+ 2 - 2
src/components/widgets/resources/memory.jsx

@@ -52,7 +52,7 @@ export default function Memory({ expanded }) {
       <div className="flex flex-col ml-3 text-left min-w-[85px]">
         <span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
           <div className="pl-0.5">
-            {t("common.bytes", { value: data.memory.freeMemMb * 1024 * 1024, maximumFractionDigits: 0, binary: true })}
+            {t("common.bytes", { value: data.memory.freeMemMb * 1024 * 1024, maximumFractionDigits: 1, binary: true })}
           </div>
           <div className="pr-1">{t("resources.free")}</div>
         </span>
@@ -61,7 +61,7 @@ export default function Memory({ expanded }) {
             <div className="pl-0.5">
               {t("common.bytes", {
                 value: data.memory.totalMemMb * 1024 * 1024,
-                maximumFractionDigits: 0,
+                maximumFractionDigits: 1,
                 binary: true,
               })}
             </div>

+ 1 - 1
src/components/widgets/unifi_console/unifi_console.jsx

@@ -12,7 +12,7 @@ export default function Widget({ options }) {
   options.type = "unifi_console";
   const { data: statsData, error: statsError } = useWidgetAPI(options, "stat/sites", { index: options.index });
 
-  if (statsError || statsData?.error) {
+  if (statsError) {
     return (
       <div className="flex flex-col justify-center first:ml-0 ml-4">
         <div className="flex flex-row items-center justify-end">

+ 0 - 3
src/pages/_document.jsx

@@ -9,9 +9,6 @@ export default function Document() {
           content="A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations."
         />
         <meta name="apple-mobile-web-app-capable" content="yes" />
-        <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?v=4" />
-        <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png?v=4" />
-        <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png?v=4" />
         <link rel="manifest" href="/site.webmanifest?v=4" />
         <link rel="mask-icon" href="/safari-pinned-tab.svg?v=4" color="#1e9cd7" />
       </Head>

+ 1 - 1
src/pages/api/docker/stats/[...service].js

@@ -69,7 +69,7 @@ export default async function handler(req, res) {
     });
   } catch {
     res.status(500).send({
-      error: "unknown error",
+      error: {message: "Unknown error"},
     });
   }
 }

+ 2 - 0
src/pages/api/docker/status/[...service].js

@@ -35,6 +35,7 @@ export default async function handler(req, res) {
 
       return res.status(200).json({
         status: info.State.Status,
+        health: info.State.Health?.Status
       });
     }
 
@@ -56,6 +57,7 @@ export default async function handler(req, res) {
 
       return res.status(200).json({
         status: info.State.Status,
+        health: info.State.Health?.Status
       });
     }
 

+ 35 - 0
src/pages/api/ping.js

@@ -0,0 +1,35 @@
+import { performance } from "perf_hooks";
+
+import createLogger from "utils/logger";
+import { httpProxy } from "utils/proxy/http";
+
+const logger = createLogger("ping");
+
+export default async function handler(req, res) {
+    const { ping: pingURL } = req.query;
+
+    if (!pingURL) {
+        logger.debug("No ping URL specified");
+        return res.status(400).send({
+        error: "No ping URL given",
+        });
+    }
+    
+    let startTime = performance.now();
+    let [status] = await httpProxy(pingURL, {
+      method: "HEAD"
+    });
+    let endTime = performance.now();
+    
+    if (status >= 400) {
+      // try one more time as a GET in case HEAD is rejected for whatever reason
+      startTime = performance.now();
+      [status] = await httpProxy(pingURL);
+      endTime = performance.now();
+    }
+
+    return res.status(200).json({
+      status,
+      latency: endTime - startTime
+    });
+}

+ 3 - 2
src/pages/api/widgets/openmeteo.js

@@ -1,8 +1,9 @@
 import cachedFetch from "utils/proxy/cached-fetch";
 
 export default async function handler(req, res) {
-  const { latitude, longitude, units, cache } = req.query;
+  const { latitude, longitude, units, cache, timezone } = req.query;
   const degrees = units === "imperial" ? "fahrenheit" : "celsius";
-  const apiUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&daily=sunrise,sunset&current_weather=true&temperature_unit=${degrees}&timezone=auto`;
+  const timezeone = timezone ?? 'auto'
+  const apiUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&daily=sunrise,sunset&current_weather=true&temperature_unit=${degrees}&timezone=${timezeone}`;
   return res.send(await cachedFetch(apiUrl, cache));
 }

+ 12 - 4
src/pages/index.jsx

@@ -35,7 +35,7 @@ const Version = dynamic(() => import("components/version"), {
   ssr: false,
 });
 
-const rightAlignedWidgets = ["weatherapi", "openweathermap", "weather", "search", "datetime"];
+const rightAlignedWidgets = ["weatherapi", "openweathermap", "weather", "openmeteo", "search", "datetime"];
 
 export async function getStaticProps() {
   let logger;
@@ -219,9 +219,17 @@ function Home({ initialSettings }) {
         <title>{initialSettings.title || "Homepage"}</title>
         {initialSettings.base && <base href={initialSettings.base} />}
         {initialSettings.favicon ? (
-          <link rel="icon" href={initialSettings.favicon} />
+          <>
+            <link rel="apple-touch-icon" sizes="180x180" href={initialSettings.favicon} />
+            <link rel="icon" href={initialSettings.favicon} />
+          </>
         ) : (
-          <link rel="shortcut icon" href="/homepage.ico" />
+          <>
+            <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?v=4" />
+            <link rel="shortcut icon" href="/homepage.ico" />
+            <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png?v=4" />
+            <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png?v=4" />
+          </>
         )}
         <meta
           name="msapplication-TileColor"
@@ -272,7 +280,7 @@ function Home({ initialSettings }) {
         )}
 
         {bookmarks && (
-          <div className="grow flex flex-wrap pt-0 p-4 sm:p-8">
+          <div className={`grow flex flex-wrap pt-0 p-4 sm:p-8 gap-2 grid-cols-1 lg:grid-cols-2 lg:grid-cols-${Math.min(6, bookmarks.length)}`}>
             {bookmarks.map((group) => (
               <BookmarksGroup key={group.name} group={group} />
             ))}

+ 1 - 1
src/skeleton/bookmarks.yaml

@@ -1,6 +1,6 @@
 ---
 # For configuration options and examples, please see:
-# https://github.com/benphelps/homepage/wiki/Bookmarks
+# https://gethomepage.dev/en/configs/bookmarks
 
 - Developer:
     - Github:

+ 1 - 1
src/skeleton/docker.yaml

@@ -1,6 +1,6 @@
 ---
 # For configuration options and examples, please see:
-# https://github.com/benphelps/homepage/wiki/Docker-Integration
+# https://gethomepage.dev/en/configs/docker/
 
 # my-docker:
 #   host: 127.0.0.1

+ 1 - 1
src/skeleton/services.yaml

@@ -1,6 +1,6 @@
 ---
 # For configuration options and examples, please see:
-# https://github.com/benphelps/homepage/wiki/Services
+# https://gethomepage.dev/en/configs/services
 
 - My First Group:
     - My First Service:

+ 1 - 1
src/skeleton/settings.yaml

@@ -1,6 +1,6 @@
 ---
 # For configuration options and examples, please see:
-# https://github.com/benphelps/homepage/wiki/Settings
+# https://gethomepage.dev/en/configs/settings
 
 providers:
   openweathermap: openweathermapapikey

+ 1 - 1
src/skeleton/widgets.yaml

@@ -1,6 +1,6 @@
 ---
 # For configuration options and examples, please see:
-# https://github.com/benphelps/homepage/wiki/Information-Widgets
+# https://gethomepage.dev/en/configs/widgets
 
 - resources:
     cpu: true

+ 4 - 0
src/styles/globals.css

@@ -54,3 +54,7 @@ body {
   background-color: var(--scrollbar-thumb);
   border-radius: 0.25em;
 }
+
+::-webkit-details-marker {
+  display: none;
+}

+ 21 - 4
src/utils/config/api-response.js

@@ -4,7 +4,7 @@ import path from "path";
 
 import yaml from "js-yaml";
 
-import checkAndCopyConfig from "utils/config/config";
+import checkAndCopyConfig, { getSettings } from "utils/config/config";
 import { servicesFromConfig, servicesFromDocker, cleanServiceGroups } from "utils/config/service-helpers";
 import { cleanWidgetGroups, widgetsFromConfig } from "utils/config/widget-helpers";
 
@@ -46,6 +46,7 @@ export async function widgetsResponse() {
 export async function servicesResponse() {
   let discoveredServices;
   let configuredServices;
+  let initialSettings;
 
   try {
     discoveredServices = cleanServiceGroups(await servicesFromDocker());
@@ -63,11 +64,21 @@ export async function servicesResponse() {
     configuredServices = [];
   }
 
+  try {
+    initialSettings = await getSettings();
+  } catch (e) {
+    console.error("Failed to load settings.yaml, please check for errors");
+    if (e) console.error(e);
+    initialSettings = {};
+  }
+
   const mergedGroupsNames = [
     ...new Set([discoveredServices.map((group) => group.name), configuredServices.map((group) => group.name)].flat()),
   ];
 
-  const mergedGroups = [];
+  const sortedGroups = [];
+  const unsortedGroups = [];
+  const definedLayouts = initialSettings.layout ? Object.keys(initialSettings.layout) : null;
 
   mergedGroupsNames.forEach((groupName) => {
     const discoveredGroup = discoveredServices.find((group) => group.name === groupName) || { services: [] };
@@ -78,8 +89,14 @@ export async function servicesResponse() {
       services: [...discoveredGroup.services, ...configuredGroup.services].filter((service) => service),
     };
 
-    mergedGroups.push(mergedGroup);
+    if (definedLayouts) {
+      const layoutIndex = definedLayouts.findIndex(layout => layout === mergedGroup.name);
+      if (layoutIndex > -1) sortedGroups[layoutIndex] = mergedGroup;
+      else unsortedGroups.push(mergedGroup);
+    } else {
+      unsortedGroups.push(mergedGroup);
+    }
   });
 
-  return mergedGroups;
+  return [...sortedGroups.filter(g => g), ...unsortedGroups];
 }

+ 1 - 1
src/utils/config/config.js

@@ -32,5 +32,5 @@ export function getSettings() {
 
   const settingsYaml = join(process.cwd(), "config", "settings.yaml");
   const fileContents = readFileSync(settingsYaml, "utf8");
-  return yaml.load(fileContents);
+  return yaml.load(fileContents) ?? {};
 }

+ 2 - 0
src/utils/config/service-helpers.js

@@ -118,6 +118,7 @@ export function cleanServiceGroups(groups) {
           container,
           currency, // coinmarketcap widget
           symbols,
+          defaultinterval
         } = cleanedService.widget;
 
         cleanedService.widget = {
@@ -129,6 +130,7 @@ export function cleanServiceGroups(groups) {
 
         if (currency) cleanedService.widget.currency = currency;
         if (symbols) cleanedService.widget.symbols = symbols;
+        if (defaultinterval) cleanedService.widget.defaultinterval = defaultinterval;
 
         if (type === "docker") {
           if (server) cleanedService.widget.server = server;

+ 6 - 1
src/utils/logger.js

@@ -4,10 +4,15 @@ import { format as utilFormat } from "node:util";
 
 import winston from "winston";
 
+import checkAndCopyConfig, { getSettings } from "utils/config/config";
+
 let winstonLogger;
 
 function init() {
   const configPath = join(process.cwd(), "config");
+  checkAndCopyConfig("settings.yaml");
+  const settings = getSettings();
+  const logpath = settings.logpath || configPath;
 
   function combineMessageAndSplat() {
     return {
@@ -57,7 +62,7 @@ function init() {
           winston.format.timestamp(),
           winston.format.printf(messageFormatter)
         ),
-        filename: `${configPath}/logs/homepage.log`,
+        filename: `${logpath}/logs/homepage.log`,
         handleExceptions: true,
         handleRejections: true,
       }),

+ 10 - 1
src/utils/proxy/handlers/credentialed.js

@@ -1,5 +1,6 @@
 import getServiceWidget from "utils/config/service-helpers";
 import { formatApiCall } from "utils/proxy/api-helpers";
+import validateWidgetData from "utils/proxy/validate-widget-data";
 import { httpProxy } from "utils/proxy/http";
 import createLogger from "utils/logger";
 import widgets from "widgets/widgets";
@@ -31,6 +32,10 @@ export default async function credentialedProxyHandler(req, res) {
         headers.Authorization = `Bearer ${widget.key}`;
       } else if (widget.type === "proxmox") {
         headers.Authorization = `PVEAPIToken=${widget.username}=${widget.password}`;
+      } else if (widget.type === "autobrr") {
+        headers["X-API-Token"] = `${widget.key}`;
+      } else if (widget.type === "tubearchivist") {
+        headers.Authorization = `Token ${widget.key}`;
       } else {
         headers["X-API-Key"] = `${widget.key}`;
       }
@@ -47,7 +52,11 @@ export default async function credentialedProxyHandler(req, res) {
       }
 
       if (status >= 400) {
-        logger.debug("HTTP Error %d calling %s//%s%s...", status, url.protocol, url.hostname, url.pathname);
+        logger.error("HTTP Error %d calling %s", status, url.toString());
+      }
+
+      if (!validateWidgetData(widget, endpoint, data)) {
+        return res.status(500).json({error: {message: "Invalid data", url, data}});
       }
 
       if (contentType) res.setHeader("Content-Type", contentType);

+ 7 - 0
src/utils/proxy/handlers/generic.js

@@ -1,5 +1,6 @@
 import getServiceWidget from "utils/config/service-helpers";
 import { formatApiCall } from "utils/proxy/api-helpers";
+import validateWidgetData from "utils/proxy/validate-widget-data";
 import { httpProxy } from "utils/proxy/http";
 import createLogger from "utils/logger";
 import widgets from "widgets/widgets";
@@ -32,6 +33,11 @@ export default async function genericProxyHandler(req, res, map) {
       });
 
       let resultData = data;
+      
+      if (!validateWidgetData(widget, endpoint, resultData)) {
+        return res.status(status).json({error: {message: "Invalid data", url, data: resultData}});
+      }
+
       if (status === 200 && map) {
         resultData = map(data);
       }
@@ -44,6 +50,7 @@ export default async function genericProxyHandler(req, res, map) {
 
       if (status >= 400) {
         logger.debug("HTTP Error %d calling %s//%s%s...", status, url.protocol, url.hostname, url.pathname);
+        return res.status(status).json({error: {message: "HTTP Error", url, data}});
       }
 
       return res.status(status).send(resultData);

+ 82 - 0
src/utils/proxy/handlers/jsonrpc.js

@@ -0,0 +1,82 @@
+import { JSONRPCClient, JSONRPCErrorException } from "json-rpc-2.0";
+
+import { formatApiCall } from "utils/proxy/api-helpers";
+import { httpProxy } from "utils/proxy/http";
+import getServiceWidget from "utils/config/service-helpers";
+import createLogger from "utils/logger";
+import widgets from "widgets/widgets";
+
+const logger = createLogger("jsonrpcProxyHandler");
+
+export async function sendJsonRpcRequest(url, method, params, username, password) {
+  const headers = {
+    "content-type": "application/json",
+    "accept": "application/json"
+  }
+
+  if (username && password) {
+    headers.authorization = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
+  }
+
+  const client = new JSONRPCClient(async (rpcRequest) => {
+    const body = JSON.stringify(rpcRequest);
+    const httpRequestParams = {
+      method: "POST",
+      headers,
+      body
+    };
+
+    // eslint-disable-next-line no-unused-vars
+    const [status, contentType, data] = await httpProxy(url, httpRequestParams);
+    if (status === 200) {
+      const json = JSON.parse(data.toString());
+
+      // in order to get access to the underlying error object in the JSON response
+      // you must set `result` equal to undefined
+      if (json.error && (json.result === null)) {
+        json.result = undefined;
+      }
+      return client.receive(json);
+    }
+
+    return Promise.reject(data?.error ? data : new Error(data.toString()));
+  });
+
+  try {
+    const response = await client.request(method, params);
+    return [200, "application/json", JSON.stringify(response)];
+  }
+  catch (e) {
+    if (e instanceof JSONRPCErrorException) {
+      logger.debug("Error calling JSONPRC endpoint: %s.  %s", url, e.message);
+      return [200, "application/json", JSON.stringify({result: null, error: {code: e.code, message: e.message}})];
+    }
+
+    logger.warn("Error calling JSONPRC endpoint: %s.  %s", url, e);
+    return [500, "application/json", JSON.stringify({result: null, error: {code: 2, message: e.toString()}})];
+  }
+}
+
+export default async function jsonrpcProxyHandler(req, res) {
+  const { group, service, endpoint: method } = req.query;
+
+  if (group && service) {
+    const widget = await getServiceWidget(group, service);
+    const api = widgets?.[widget.type]?.api;
+
+    if (!api) {
+      return res.status(403).json({ error: "Service does not support API calls" });
+    }
+
+    if (widget) {
+      const url = formatApiCall(api, { ...widget });
+
+      // eslint-disable-next-line no-unused-vars
+      const [status, contentType, data] = await sendJsonRpcRequest(url, method, null, widget.username, widget.password);
+      return res.status(status).end(data);
+    }
+  }
+
+  logger.debug("Invalid or missing proxy service type '%s' in group '%s'", service, group);
+  return res.status(400).json({ error: "Invalid proxy service type" });
+}

+ 15 - 30
src/utils/proxy/http.js

@@ -18,10 +18,15 @@ function addCookieHandler(url, params) {
   };
 }
 
-export function httpsRequest(url, params) {
+function handleRequest(requestor, url, params) {
   return new Promise((resolve, reject) => {
     addCookieHandler(url, params);
-    const request = https.request(url, params, (response) => {
+    if (params?.body) {
+      params.headers = params.headers ?? {};
+      params.headers['content-length'] = Buffer.byteLength(params.body);
+    }
+
+    const request = requestor.request(url, params, (response) => {
       const data = [];
 
       response.on("data", (chunk) => {
@@ -38,7 +43,7 @@ export function httpsRequest(url, params) {
       reject([500, error]);
     });
 
-    if (params.body) {
+    if (params?.body) {
       request.write(params.body);
     }
 
@@ -46,32 +51,12 @@ export function httpsRequest(url, params) {
   });
 }
 
-export function httpRequest(url, params) {
-  return new Promise((resolve, reject) => {
-    addCookieHandler(url, params);
-    const request = http.request(url, params, (response) => {
-      const data = [];
-
-      response.on("data", (chunk) => {
-        data.push(chunk);
-      });
-
-      response.on("end", () => {
-        addCookieToJar(url, response.headers);
-        resolve([response.statusCode, response.headers["content-type"], Buffer.concat(data), response.headers]);
-      });
-    });
-
-    request.on("error", (error) => {
-      reject([500, error]);
-    });
-
-    if (params.body) {
-      request.write(params.body);
-    }
+export function httpsRequest(url, params) {
+  return handleRequest(https, url, params);
+}
 
-    request.end();
-  });
+export function httpRequest(url, params) {
+  return handleRequest(http, url, params);
 }
 
 export async function httpProxy(url, params = {}) {
@@ -96,8 +81,8 @@ export async function httpProxy(url, params = {}) {
     return [status, contentType, data, responseHeaders];
   }
   catch (err) {
-    logger.error("Error calling %s//%s%s...", url.protocol, url.hostname, url.pathname);
+    logger.error("Error calling %s//%s%s...", constructedUrl.protocol, constructedUrl.hostname, constructedUrl.pathname);
     logger.error(err);
-    return [500, "application/json", { error: "Unexpected error" }, null];
+    return [500, "application/json", { error: {message: err?.message ?? "Unknown error", url, rawError: err} }, null];
   }
 }

+ 7 - 1
src/utils/proxy/use-widget-api.js

@@ -3,5 +3,11 @@ import useSWR from "swr";
 import { formatProxyUrl } from "./api-helpers";
 
 export default function useWidgetAPI(widget, ...options) {
-  return useSWR(formatProxyUrl(widget, ...options));
+  const config = {};
+  if (options?.refreshInterval) {
+    config.refreshInterval = options.refreshInterval;
+  }
+  const { data, error } = useSWR(formatProxyUrl(widget, ...options), config);
+  // make the data error the top-level error
+  return { data, error: data?.error ?? error }
 }

+ 22 - 0
src/utils/proxy/validate-widget-data.js

@@ -0,0 +1,22 @@
+import widgets from "widgets/widgets";
+
+export default function validateWidgetData(widget, endpoint, data) {
+    let valid = true;
+    let dataParsed;
+    try {
+        dataParsed = JSON.parse(data);
+    } catch (e) {
+        valid = false;
+    }
+
+    if (dataParsed && Object.entries(dataParsed).length) {
+        const validate = widgets[widget.type]?.mappings?.[endpoint]?.validate;
+        validate?.forEach(key => {
+            if (dataParsed[key] === undefined) {
+                valid = false;
+            }
+        });
+    }
+    
+    return valid;
+}

+ 211 - 0
src/utils/weather/openmeteo-condition-map.js

@@ -0,0 +1,211 @@
+import * as Icons from "react-icons/wi";
+
+// see https://open-meteo.com/en/docs
+
+const conditions = [
+  {
+    code: 1,
+    icon: {
+      day: Icons.WiDayCloudy,
+      night: Icons.WiNightAltCloudy,
+    },
+  },
+  {
+    code: 2,
+    icon: {
+      day: Icons.WiDayCloudy,
+      night: Icons.WiNightAltCloudy,
+    },
+  },
+  {
+    code: 3,
+    icon: {
+      day: Icons.WiDayCloudy,
+      night: Icons.WiNightAltCloudy,
+    },
+  },
+  {
+    code: 45,
+    icon: {
+      day: Icons.WiDayFog,
+      night: Icons.WiNightFog,
+    },
+  },
+  {
+    code: 48,
+    icon: {
+      day: Icons.WiDayFog,
+      night: Icons.WiNightFog,
+    },
+  },
+  {
+    code: 51,
+    icon: {
+      day: Icons.WiDaySprinkle,
+      night: Icons.WiNightAltSprinkle,
+    },
+  },
+  {
+    code: 53,
+    icon: {
+      day: Icons.WiDaySprinkle,
+      night: Icons.WiNightAltSprinkle,
+    },
+  },
+  {
+    code: 55,
+    icon: {
+      day: Icons.WiDaySprinkle,
+      night: Icons.WiNightAltSprinkle,
+    },
+  },
+  {
+    code: 56,
+    icon: {
+      day: Icons.WiDaySleet,
+      night: Icons.WiNightAltSleet,
+    },
+  },
+  {
+    code: 57,
+    icon: {
+      day: Icons.WiDaySleet,
+      night: Icons.WiNightAltSleet,
+    },
+  },
+  {
+    code: 61,
+    icon: {
+      day: Icons.WiDayShowers,
+      night: Icons.WiNightAltShowers,
+    },
+  },
+  {
+    code: 63,
+    icon: {
+      day: Icons.WiDayShowers,
+      night: Icons.WiNightAltShowers,
+    },
+  },
+  {
+    code: 65,
+    icon: {
+      day: Icons.WiDayShowers,
+      night: Icons.WiNightAltShowers,
+    },
+  },
+  {
+    code: 66,
+    icon: {
+      day: Icons.WiDaySleet,
+      night: Icons.WiNightAltSleet,
+    },
+  },
+  {
+    code: 67,
+    icon: {
+      day: Icons.WiDaySleet,
+      night: Icons.WiNightAltSleet,
+    },
+  },
+  {
+    code: 71,
+    icon: {
+      day: Icons.WiDaySnow,
+      night: Icons.WiNightAltSnow,
+    },
+  },
+  {
+    code: 73,
+    icon: {
+      day: Icons.WiDaySnow,
+      night: Icons.WiNightAltSnow,
+    },
+  },
+  {
+    code: 75,
+    icon: {
+      day: Icons.WiDaySnow,
+      night: Icons.WiNightAltSnow,
+    },
+  },
+  {
+    code: 77,
+    icon: {
+      day: Icons.WiDaySnow,
+      night: Icons.WiNightAltSnow,
+    },
+  },
+  {
+    code: 80,
+    icon: {
+      day: Icons.WiDaySnow,
+      night: Icons.WiNightAltSnow,
+    },
+  },
+  {
+    code: 81,
+    icon: {
+      day: Icons.WiDaySnow,
+      night: Icons.WiNightAltSnow,
+    },
+  },
+  {
+    code: 82,
+    icon: {
+      day: Icons.WiDaySnow,
+      night: Icons.WiNightAltSnow,
+    },
+  },
+  {
+    code: 85,
+    icon: {
+      day: Icons.WiDaySnow,
+      night: Icons.WiNightAltSnow,
+    },
+  },
+  {
+    code: 86,
+    icon: {
+      day: Icons.WiDaySnow,
+      night: Icons.WiNightAltSnow,
+    },
+  },
+  {
+    code: 95,
+    icon: {
+      day: Icons.WiDayThunderstorm,
+      night: Icons.WiNightAltThunderstorm,
+    },
+  },
+  {
+    code: 96,
+    icon: {
+      day: Icons.WiDayThunderstorm,
+      night: Icons.WiNightAltThunderstorm,
+    },
+  },
+  {
+    code: 99,
+    icon: {
+      day: Icons.WiDayThunderstorm,
+      night: Icons.WiNightAltThunderstorm,
+    },
+  },
+];
+
+export default function mapIcon(weatherStatusCode, timeOfDay) {
+  const mapping = conditions.find((condition) => condition.code === weatherStatusCode);
+
+  if (mapping) {
+    if (timeOfDay === "day") {
+      return mapping.icon.day;
+    }
+
+    if (timeOfDay === "night") {
+      return mapping.icon.night;
+    }
+  }
+
+  return Icons.WiDaySunny;
+}

+ 2 - 2
src/widgets/adguard/component.jsx

@@ -12,9 +12,9 @@ export default function Component({ service }) {
   const { data: adguardData, error: adguardError } = useWidgetAPI(widget, "stats");
 
   if (adguardError) {
-    return <Container error={t("widget.api_error")} />;
+    return <Container error={adguardError} />;
   }
-
+  
   if (!adguardData) {
     return (
       <Container service={service}>

+ 2 - 1
src/widgets/authentik/component.jsx

@@ -14,7 +14,8 @@ export default function Component({ service }) {
   const { data: failedLoginsData, error: failedLoginsError } = useWidgetAPI(widget, "login_failed");
 
   if (usersError || loginsError || failedLoginsError) {
-    return <Container error={t("widget.api_error")} />;
+    const finalError = usersError ?? loginsError ?? failedLoginsError;
+    return <Container error={finalError} />;
   }
 
   if (!usersData || !loginsData || !failedLoginsData) {

+ 40 - 0
src/widgets/autobrr/component.jsx

@@ -0,0 +1,40 @@
+import { useTranslation } from "next-i18next";
+
+import Container from "components/services/widget/container";
+import Block from "components/services/widget/block";
+import useWidgetAPI from "utils/proxy/use-widget-api";
+
+export default function Component({ service }) {
+  const { t } = useTranslation();
+
+  const { widget } = service;
+
+  const { data: statsData, error: statsError } = useWidgetAPI(widget, "stats");
+  const { data: filtersData, error: filtersError } = useWidgetAPI(widget, "filters");
+  const { data: indexersData, error: indexersError } = useWidgetAPI(widget, "indexers");
+
+  if (statsError || filtersError || indexersError) {
+    const finalError = statsError ?? filtersError ?? indexersError;
+    return <Container error={finalError} />;
+  }
+
+  if (!statsData || !filtersData || !indexersData) {
+    return (
+      <Container service={service}>
+        <Block label="autobrr.approvedPushes" />
+        <Block label="autobrr.rejectedPushes" />
+        <Block label="autobrr.filters" />
+        <Block label="autobrr.indexers" />
+      </Container>
+    );
+  }
+
+  return (
+    <Container service={service}>
+      <Block label="autobrr.approvedPushes" value={t("common.number", { value: statsData.push_approved_count })} />
+      <Block label="autobrr.rejectedPushes" value={t("common.number", { value: statsData.push_rejected_count })} />
+      <Block label="autobrr.filters" value={t("common.number", { value: filtersData.length })} />
+      <Block label="autobrr.indexers" value={t("common.number", { value: indexersData.length })} />
+    </Container>
+  );
+}

+ 24 - 0
src/widgets/autobrr/widget.js

@@ -0,0 +1,24 @@
+import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
+
+const widget = {
+  api: "{url}/api/{endpoint}",
+  proxyHandler: credentialedProxyHandler,
+
+  mappings: {
+    stats: {
+      endpoint: "release/stats",
+      validate: [
+        "push_approved_count",
+        "push_rejected_count"
+      ]
+    },
+    filters: {
+      endpoint: "filters",
+    },
+    indexers: {
+      endpoint: "release/indexers",
+    },
+  },
+};
+
+export default widget;

+ 3 - 2
src/widgets/bazarr/component.jsx

@@ -12,8 +12,9 @@ export default function Component({ service }) {
   const { data: episodesData, error: episodesError } = useWidgetAPI(widget, "episodes");
   const { data: moviesData, error: moviesError } = useWidgetAPI(widget, "movies");
 
-  if (episodesError || moviesError) {
-    return <Container error="widget.api_error" />;
+  if (moviesError || episodesError) {
+    const finalError = moviesError ?? episodesError;
+    return <Container error={finalError} />;
   }
 
   if (!episodesData || !moviesData) {

+ 6 - 2
src/widgets/changedetectionio/component.jsx

@@ -9,10 +9,14 @@ export default function Component({ service }) {
 
   const { widget } = service;
 
-  const { data } = useWidgetAPI(widget, "info");
+  const { data, error } = useWidgetAPI(widget, "info");
+
+  if (error) {
+    return <Container error={error} />;
+  }
 
   if (!data) {
-    return <Container error="widget.api_error" />;
+    return <Container service={service} />;
   }
 
   const totalObserved = Object.keys(data).length;

+ 4 - 3
src/widgets/coinmarketcap/component.jsx

@@ -17,11 +17,12 @@ export default function Component({ service }) {
     { label: t("coinmarketcap.30days"), value: "30d" },
   ];
 
-  const [dateRange, setDateRange] = useState(dateRangeOptions[0].value);
-
   const { widget } = service;
   const { symbols } = widget;
   const currencyCode = widget.currency ?? "USD";
+  const interval = widget.defaultinterval ?? dateRangeOptions[0].value;
+
+  const [dateRange, setDateRange] = useState(interval);
 
   const { data: statsData, error: statsError } = useWidgetAPI(widget, "v1/cryptocurrency/quotes/latest", {
     symbol: `${symbols.join(",")}`,
@@ -37,7 +38,7 @@ export default function Component({ service }) {
   }
 
   if (statsError) {
-    return <Container error={t("widget.api_error")} />;
+    return <Container error={statsError} />;
   }
 
   if (!statsData || !dateRange) {

+ 13 - 0
src/widgets/components.js

@@ -3,39 +3,52 @@ import dynamic from "next/dynamic";
 const components = {
   adguard: dynamic(() => import("./adguard/component")),
   authentik: dynamic(() => import("./authentik/component")),
+  autobrr: dynamic(() => import("./autobrr/component")),
   bazarr: dynamic(() => import("./bazarr/component")),
   changedetectionio: dynamic(() => import("./changedetectionio/component")),
   coinmarketcap: dynamic(() => import("./coinmarketcap/component")),
+  deluge: dynamic(() => import("./deluge/component")),
+  diskstation: dynamic(() => import("./diskstation/component")),
   docker: dynamic(() => import("./docker/component")),
   emby: dynamic(() => import("./emby/component")),
+  flood: dynamic(() => import("./flood/component")),
+  gluetun: dynamic(() => import("./gluetun/component")),
   gotify: dynamic(() => import("./gotify/component")),
+  hdhomerun: dynamic(() => import("./hdhomerun/component")),
   homebridge: dynamic(() => import("./homebridge/component")),
   jackett: dynamic(() => import("./jackett/component")),
   jellyfin: dynamic(() => import("./emby/component")),
   jellyseerr: dynamic(() => import("./jellyseerr/component")),
   lidarr: dynamic(() => import("./lidarr/component")),
   mastodon: dynamic(() => import("./mastodon/component")),
+  navidrome: dynamic(() => import("./navidrome/component")),
   npm: dynamic(() => import("./npm/component")),
   nzbget: dynamic(() => import("./nzbget/component")),
   ombi: dynamic(() => import("./ombi/component")),
   overseerr: dynamic(() => import("./overseerr/component")),
+  paperlessngx: dynamic(() => import("./paperlessngx/component")),
   pihole: dynamic(() => import("./pihole/component")),
   plex: dynamic(() => import("./plex/component")),
   portainer: dynamic(() => import("./portainer/component")),
   prowlarr: dynamic(() => import("./prowlarr/component")),
   proxmox: dynamic(() => import("./proxmox/component")),
+  pyload: dynamic(() => import("./pyload/component")),
   qbittorrent: dynamic(() => import("./qbittorrent/component")),
   radarr: dynamic(() => import("./radarr/component")),
   readarr: dynamic(() => import("./readarr/component")),
   rutorrent: dynamic(() => import("./rutorrent/component")),
   sabnzbd: dynamic(() => import("./sabnzbd/component")),
+  scrutiny: dynamic(() => import("./scrutiny/component")),
   sonarr: dynamic(() => import("./sonarr/component")),
   speedtest: dynamic(() => import("./speedtest/component")),
   strelaysrv: dynamic(() => import("./strelaysrv/component")),
   tautulli: dynamic(() => import("./tautulli/component")),
   traefik: dynamic(() => import("./traefik/component")),
   transmission: dynamic(() => import("./transmission/component")),
+  tubearchivist: dynamic(() => import("./tubearchivist/component")),
+  truenas: dynamic(() => import("./truenas/component")),
   unifi: dynamic(() => import("./unifi/component")),
+  watchtower: dynamic(() => import("./watchtower/component")),
 };
 
 export default components;

+ 52 - 0
src/widgets/deluge/component.jsx

@@ -0,0 +1,52 @@
+import { useTranslation } from "next-i18next";
+
+import Container from "components/services/widget/container";
+import Block from "components/services/widget/block";
+import useWidgetAPI from "utils/proxy/use-widget-api";
+
+export default function Component({ service }) {
+  const { t } = useTranslation();
+
+  const { widget } = service;
+
+  const { data: torrentData, error: torrentError } = useWidgetAPI(widget);
+
+  if (torrentError) {
+    return <Container error={torrentError} />;
+  }
+
+  if (!torrentData) {
+    return (
+      <Container service={service}>
+        <Block label="deluge.leech" />
+        <Block label="deluge.download" />
+        <Block label="deluge.seed" />
+        <Block label="deluge.upload" />
+      </Container>
+    );
+  }
+
+  const { torrents } = torrentData;
+  const keys = torrents ? Object.keys(torrents) : [];
+
+  let rateDl = 0;
+  let rateUl = 0;
+  let completed = 0;
+  for (let i = 0; i < keys.length; i += 1) {
+    const torrent = torrents[keys[i]];
+    rateDl += torrent.download_payload_rate;
+    rateUl += torrent.upload_payload_rate;
+    completed += torrent.total_remaining === 0 ? 1 : 0;
+  }
+
+  const leech = keys.length - completed || 0;
+
+  return (
+    <Container service={service}>
+      <Block label="deluge.leech" value={t("common.number", { value: leech })} />
+      <Block label="deluge.download" value={t("common.bitrate", { value: rateDl })} />
+      <Block label="deluge.seed" value={t("common.number", { value: completed })} />
+      <Block label="deluge.upload" value={t("common.bitrate", { value: rateUl })} />
+    </Container>
+  );
+}

+ 63 - 0
src/widgets/deluge/proxy.js

@@ -0,0 +1,63 @@
+import { formatApiCall } from "utils/proxy/api-helpers";
+import { sendJsonRpcRequest } from "utils/proxy/handlers/jsonrpc";
+import getServiceWidget from "utils/config/service-helpers";
+import createLogger from "utils/logger";
+import widgets from "widgets/widgets";
+
+const logger = createLogger("delugeProxyHandler");
+
+const dataMethod = "web.update_ui";
+const dataParams = [
+  ["queue", "name", "total_wanted", "state", "progress", "download_payload_rate", "upload_payload_rate", "total_remaining"],
+  {}
+];
+const loginMethod = "auth.login";
+
+async function sendRpc(url, method, params) {
+  const [status, contentType, data] = await sendJsonRpcRequest(url, method, params);
+  const json = JSON.parse(data.toString());
+  if (json?.error) {
+    if (json.error.code === 1) {
+      return [403, contentType, data];
+    }
+    return [500, contentType, data];
+  }
+
+  return [status, contentType, data];
+}
+
+function login(url, password) {
+  return sendRpc(url, loginMethod, [password]);
+}
+
+export default async function delugeProxyHandler(req, res) {
+  const { group, service } = req.query;
+
+  if (!group || !service) {
+    logger.debug("Invalid or missing service '%s' or group '%s'", service, group);
+    return res.status(400).json({ error: "Invalid proxy service type" });
+  }
+
+  const widget = await getServiceWidget(group, service);
+
+  if (!widget) {
+    logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group);
+    return res.status(400).json({ error: "Invalid proxy service type" });
+  }
+
+  const api = widgets?.[widget.type]?.api
+  const url = new URL(formatApiCall(api, { ...widget }));
+
+  let [status, contentType, data] = await sendRpc(url, dataMethod, dataParams);
+  if (status === 403) {
+    [status, contentType, data] = await login(url, widget.password);
+    if (status !== 200) {
+      return res.status(status).end(data);
+    }
+
+    // eslint-disable-next-line no-unused-vars
+    [status, contentType, data] = await sendRpc(url, dataMethod, dataParams);
+  }
+
+  return res.status(status).end(data);
+}

+ 8 - 0
src/widgets/deluge/widget.js

@@ -0,0 +1,8 @@
+import delugeProxyHandler from "./proxy";
+
+const widget = {
+  api: "{url}/json",
+  proxyHandler: delugeProxyHandler,
+};
+
+export default widget;

+ 41 - 0
src/widgets/diskstation/component.jsx

@@ -0,0 +1,41 @@
+import { useTranslation } from "next-i18next";
+
+import Container from "components/services/widget/container";
+import Block from "components/services/widget/block";
+import useWidgetAPI from "utils/proxy/use-widget-api";
+
+export default function Component({ service }) {
+  const { t } = useTranslation();
+  const { widget } = service;
+  const { data: listData, error: listError } = useWidgetAPI(widget, "list");
+
+  if (listError) {
+    return <Container error={listError} />;
+  }
+
+  const tasks = listData?.data?.tasks;
+  if (!tasks) {
+    return (
+      <Container service={service}>
+        <Block label="diskstation.leech" />
+        <Block label="diskstation.download" />
+        <Block label="diskstation.seed" />
+        <Block label="diskstation.upload" />
+      </Container>
+    );
+  }
+
+  const rateDl = tasks.reduce((acc, task) => acc + (task?.additional?.transfer?.speed_download ?? 0), 0);
+  const rateUl = tasks.reduce((acc, task) => acc + (task?.additional?.transfer?.speed_upload ?? 0), 0);
+  const completed = tasks.filter((task) => task?.additional?.transfer?.size_downloaded === task?.size)?.length || 0;
+  const leech = tasks.length - completed || 0;
+
+  return (
+    <Container service={service}>
+      <Block label="diskstation.leech" value={t("common.number", { value: leech })} />
+      <Block label="diskstation.download" value={t("common.bitrate", { value: rateDl })} />
+      <Block label="diskstation.seed" value={t("common.number", { value: completed })} />
+      <Block label="diskstation.upload" value={t("common.bitrate", { value: rateUl })} />
+    </Container>
+  );
+}

+ 70 - 0
src/widgets/diskstation/proxy.js

@@ -0,0 +1,70 @@
+import { formatApiCall } from "utils/proxy/api-helpers";
+import { httpProxy } from "utils/proxy/http";
+import createLogger from "utils/logger";
+import widgets from "widgets/widgets";
+import getServiceWidget from "utils/config/service-helpers";
+
+const logger = createLogger("diskstationProxyHandler");
+const authApi = "{url}/webapi/auth.cgi?api=SYNO.API.Auth&version=2&method=login&account={username}&passwd={password}&session=DownloadStation&format=cookie"
+
+async function login(widget) {
+  const loginUrl = formatApiCall(authApi, widget);
+  const [status, contentType, data] = await httpProxy(loginUrl);
+  if (status !== 200) {
+    return [status, contentType, data];
+  }
+
+  const json = JSON.parse(data.toString());
+  if (json?.success !== true) {
+    // from https://global.download.synology.com/download/Document/Software/DeveloperGuide/Package/DownloadStation/All/enu/Synology_Download_Station_Web_API.pdf
+    /*
+      Code Description
+      400  No such account or incorrect password
+      401  Account disabled
+      402  Permission denied
+      403  2-step verification code required
+      404  Failed to authenticate 2-step verification code
+    */
+    let message = "Authentication failed.";
+    if (json?.error?.code >= 403) message += " 2FA enabled.";
+    logger.warn("Unable to login.  Code: %d", json?.error?.code);
+    return [401, "application/json", JSON.stringify({ code: json?.error?.code, message })];
+  }
+
+  return [status, contentType, data];
+}
+
+export default async function diskstationProxyHandler(req, res) {
+  const { group, service, endpoint } = req.query;
+
+  if (!group || !service) {
+    return res.status(400).json({ error: "Invalid proxy service type" });
+  }
+
+  const widget = await getServiceWidget(group, service);
+  const api = widgets?.[widget.type]?.api;
+  if (!api) {
+    return res.status(403).json({ error: "Service does not support API calls" });
+  }
+
+  const url = formatApiCall(api, { endpoint, ...widget });
+  let [status, contentType, data] = await httpProxy(url);
+  if (status !== 200) {
+    logger.debug("Error %d calling endpoint %s", status, url);
+    return res.status(status, data);
+  }
+
+  const json = JSON.parse(data.toString());
+  if (json?.success !== true) {
+    logger.debug("Logging in to DiskStation");
+    [status, contentType, data] = await login(widget);
+    if (status !== 200) {
+      return res.status(status).end(data)
+    }
+
+    [status, contentType, data] = await httpProxy(url);
+  }
+
+  if (contentType) res.setHeader("Content-Type", contentType);
+  return res.status(status).send(data);
+}

+ 14 - 0
src/widgets/diskstation/widget.js

@@ -0,0 +1,14 @@
+import diskstationProxyHandler from "./proxy";
+
+const widget = {
+  api: "{url}/webapi/DownloadStation/task.cgi?api=SYNO.DownloadStation.Task&version=1&method={endpoint}",
+  proxyHandler: diskstationProxyHandler,
+
+  mappings: {
+    "list": {
+      endpoint: "list&additional=transfer",
+    },
+  },
+};
+
+export default widget;

+ 6 - 3
src/widgets/docker/component.jsx

@@ -17,8 +17,9 @@ export default function Component({ service }) {
 
   const { data: statsData, error: statsError } = useSWR(`/api/docker/stats/${widget.container}/${widget.server || ""}`);
 
-  if (statsError || statusError) {
-    return <Container error={t("widget.api_error")} />;
+  if (statsError || statsData?.error || statusError || statusData?.error) {
+    const finalError = statsError ?? statsData?.error ?? statusError ?? statusData?.error;
+    return <Container error={finalError} />;
   }
 
   if (statusData && statusData.status !== "running") {
@@ -45,7 +46,9 @@ export default function Component({ service }) {
   return (
     <Container service={service}>
       <Block label="docker.cpu" value={t("common.percent", { value: calculateCPUPercent(statsData.stats) })} />
-      <Block label="docker.mem" value={t("common.bytes", { value: statsData.stats.memory_stats.usage })} />
+      {statsData.stats.memory_stats.usage && 
+        <Block label="docker.mem" value={t("common.bytes", { value: statsData.stats.memory_stats.usage })} />
+      }
       {network && (
         <>
           <Block label="docker.rx" value={t("common.bytes", { value: network.rx_bytes })} />

+ 4 - 4
src/widgets/emby/component.jsx

@@ -1,10 +1,10 @@
-import useSWR from "swr";
 import { useTranslation } from "next-i18next";
 import { BsVolumeMuteFill, BsFillPlayFill, BsPauseFill, BsCpu, BsFillCpuFill } from "react-icons/bs";
 import { MdOutlineSmartDisplay } from "react-icons/md";
 
 import Container from "components/services/widget/container";
-import { formatProxyUrl, formatProxyUrlWithSegments } from "utils/proxy/api-helpers";
+import { formatProxyUrlWithSegments } from "utils/proxy/api-helpers";
+import useWidgetAPI from "utils/proxy/use-widget-api";
 
 function ticksToTime(ticks) {
   const milliseconds = ticks / 10000;
@@ -157,7 +157,7 @@ export default function Component({ service }) {
     data: sessionsData,
     error: sessionsError,
     mutate: sessionMutate,
-  } = useSWR(formatProxyUrl(widget, "Sessions"), {
+  } = useWidgetAPI(widget, "Sessions", {
     refreshInterval: 5000,
   });
 
@@ -172,7 +172,7 @@ export default function Component({ service }) {
   }
 
   if (sessionsError) {
-    return <Container error={t("widget.api_error")} />;
+    return <Container error={sessionsError} />;
   }
 
   if (!sessionsData) {

+ 1 - 1
src/widgets/emby/widget.js

@@ -10,7 +10,7 @@ const widget = {
     },
     PlayControl: {
       method: "POST",
-      enpoint: "Sessions/{sessionId}/Playing/{command}",
+      endpoint: "Sessions/{sessionId}/Playing/{command}",
       segments: ["sessionId", "command"],
     },
   },

+ 53 - 0
src/widgets/flood/component.jsx

@@ -0,0 +1,53 @@
+import { useTranslation } from "next-i18next";
+
+import Container from "components/services/widget/container";
+import Block from "components/services/widget/block";
+import useWidgetAPI from "utils/proxy/use-widget-api";
+
+export default function Component({ service }) {
+  const { t } = useTranslation();
+
+  const { widget } = service;
+
+  const { data: torrentData, error: torrentError } = useWidgetAPI(widget, "torrents");
+
+  if (torrentError || !torrentData?.torrents) {
+    return <Container error={torrentError ?? {message: "No torrent data returned"}} />;
+  }
+
+  if (!torrentData || !torrentData.torrents) {
+    return (
+      <Container service={service}>
+        <Block label="flood.leech" />
+        <Block label="flood.download" />
+        <Block label="flood.seed" />
+        <Block label="flood.upload" />
+      </Container>
+    );
+  }
+
+  let rateDl = 0;
+  let rateUl = 0;
+  let completed = 0;
+  let leech = 0;
+
+  Object.values(torrentData.torrents).forEach(torrent => {
+    rateDl += torrent.downRate;
+    rateUl += torrent.upRate;
+    if(torrent.status.includes('complete')){
+      completed += 1;
+    }
+    if(torrent.status.includes('downloading')){
+      leech += 1;
+    }
+  })
+
+  return (
+    <Container service={service}>
+      <Block label="flood.leech" value={t("common.number", { value: leech })} />
+      <Block label="flood.download" value={t("common.bitrate", { value: rateDl })} />
+      <Block label="flood.seed" value={t("common.number", { value: completed })} />
+      <Block label="flood.upload" value={t("common.bitrate", { value: rateUl })} />
+    </Container>
+  );
+}

+ 66 - 0
src/widgets/flood/proxy.js

@@ -0,0 +1,66 @@
+import { formatApiCall } from "utils/proxy/api-helpers";
+import { httpProxy } from "utils/proxy/http";
+import getServiceWidget from "utils/config/service-helpers";
+import createLogger from "utils/logger";
+
+const logger = createLogger("floodProxyHandler");
+
+async function login(widget) {
+  logger.debug("flood is rejecting the request, logging in.");
+  const loginUrl = new URL(`${widget.url}/api/auth/authenticate`).toString();
+
+  const loginParams = { 
+    method: "POST", 
+    headers: { "Content-Type": "application/json" }, 
+    body: null
+  };
+
+  if (widget.username && widget.password) {
+    loginParams.body = JSON.stringify({
+      "username": widget.username,
+      "password": widget.password
+    });
+  }
+
+  // eslint-disable-next-line no-unused-vars
+  const [status, contentType, data] = await httpProxy(loginUrl, loginParams);
+  return [status, data];
+}
+
+export default async function floodProxyHandler(req, res) {
+  const { group, service, endpoint } = req.query;
+
+  if (!group || !service) {
+    logger.debug("Invalid or missing service '%s' or group '%s'", service, group);
+    return res.status(400).json({ error: "Invalid proxy service type" });
+  }
+
+  const widget = await getServiceWidget(group, service);
+
+  if (!widget) {
+    logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group);
+    return res.status(400).json({ error: "Invalid proxy service type" });
+  }
+
+  const url = new URL(formatApiCall("{url}/api/{endpoint}", { endpoint, ...widget }));
+  const params = { method: "GET", headers: {} };
+
+  let [status, contentType, data] = await httpProxy(url, params);
+  if (status === 401) {
+    [status, data] = await login(widget);
+
+    if (status !== 200) {
+      logger.error("HTTP %d logging in to flood.  Data: %s", status, data);
+      return res.status(status).end(data);
+    }
+
+    [status, contentType, data] = await httpProxy(url, params);
+  }
+
+  if (status !== 200) {
+    logger.error("HTTP %d getting data from flood.  Data: %s", status, data);
+  }
+
+  if (contentType) res.setHeader("Content-Type", contentType);
+  return res.status(status).send(data);
+}

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor