i18n: lokalizované kategórie aplikácií

This commit is contained in:
VAIO73
2026-09-12 11:36:49 +02:00
parent e0c5740a47
commit 8918675393
12 changed files with 145 additions and 22 deletions
+11 -8
View File
@@ -9,6 +9,7 @@ import { ThemeAwareLogo } from "./lxc-app-panel"
import { CustomLinkEditor, type CustomLink, type GuestOption } from "./custom-link-editor"
import { Button } from "./ui/button"
import { categoryChipStyle, useIsLightTheme } from "../lib/category-color"
import { getCategoryLabel } from "../lib/category-label"
// ─── Local subset of /api/vms shape ─────────────────────────────
// Kept narrow on purpose — this component only needs what feeds a
@@ -248,8 +249,8 @@ export function AppsDashboard() {
return map
}, [links, t])
const sortedCategoryEntries = useMemo(
() => Array.from(categoryCounts.entries()).sort((a, b) => a[0].localeCompare(b[0])),
[categoryCounts],
() => Array.from(categoryCounts.entries()).sort((a, b) => getCategoryLabel(t, a[0]).localeCompare(getCategoryLabel(t, b[0]))),
[categoryCounts, t],
)
// ─── Controls state ────────────────────────────────────────────
@@ -327,7 +328,8 @@ export function AppsDashboard() {
l.appName.toLowerCase().includes(q) ||
(l.ctName || "").toLowerCase().includes(q) ||
(l.vmid != null && String(l.vmid).includes(q)) ||
(l.category || "").toLowerCase().includes(q)
(l.category || "").toLowerCase().includes(q) ||
getCategoryLabel(t, l.category || "").toLowerCase().includes(q)
)
}
const sorted = [...filtered]
@@ -344,7 +346,7 @@ export function AppsDashboard() {
// category — grouped alphabetically, then by app name inside
const catA = a.category || uncatKey
const catB = b.category || uncatKey
const c = catA.localeCompare(catB)
const c = getCategoryLabel(t, catA).localeCompare(getCategoryLabel(t, catB))
return c !== 0 ? c : a.appName.localeCompare(b.appName)
})
return sorted
@@ -432,7 +434,7 @@ export function AppsDashboard() {
>
<option value={ALL_CATEGORIES}>{t("apps.filterAll")}</option>
{sortedCategoryEntries.map(([cat, n]) => (
<option key={cat} value={cat}>{`${cat} · ${n}`}</option>
<option key={cat} value={cat}>{`${getCategoryLabel(t, cat)} · ${n}`}</option>
))}
</select>
@@ -532,6 +534,7 @@ function CardsGrid({
editMode: boolean
onEditCustom: (customId: string) => void
}) {
const t = useT()
if (!links.length) {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-3">
@@ -572,7 +575,7 @@ function CardsGrid({
{groups.map(([cat, items]) => (
<div key={`grp-${cat}`} className="contents">
<h3 className="col-span-full uppercase text-xs tracking-wider text-muted-foreground font-semibold pt-3 pb-1.5 border-b border-border/60 flex items-center gap-2">
<span>{cat}</span>
<span>{getCategoryLabel(t, cat)}</span>
<span className="font-mono tabular-nums text-[10px] px-1.5 py-0.5 rounded bg-card border border-border/60 font-normal">{items.length}</span>
</h3>
{items.map((link) => (
@@ -715,9 +718,9 @@ function AppCard({
<span
style={categoryChipStyle(link.category, isLightTheme)}
className="ml-auto px-1.5 py-0.5 border rounded text-[10px] font-medium flex-shrink-0 truncate max-w-[45%]"
title={link.category}
title={getCategoryLabel(t, link.category)}
>
{link.category}
{getCategoryLabel(t, link.category)}
</span>
)}
</div>
+2 -1
View File
@@ -4,6 +4,7 @@ import { useEffect, useState } from "react"
import { Trash2 } from "lucide-react"
import { fetchApi } from "../lib/api-config"
import { useT } from "../lib/i18n/provider"
import { getCategoryLabel } from "../lib/category-label"
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "./ui/dialog"
import { Button } from "./ui/button"
import { Input } from "./ui/input"
@@ -237,7 +238,7 @@ export function CustomLinkEditor({
<SelectContent>
<SelectItem value="__none__">{t("vmLxc.appEditor.portCategoryNone")}</SelectItem>
{categoryPresets.map((c) => (
<SelectItem key={c} value={c}>{c}</SelectItem>
<SelectItem key={c} value={c}>{getCategoryLabel(t, c)}</SelectItem>
))}
<SelectItem value="__add__">{t("vmLxc.appEditor.portCategoryAddNew")}</SelectItem>
</SelectContent>
+4 -3
View File
@@ -35,6 +35,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ".
import { fetchApi } from "../lib/api-config"
import { fetchLxcApps, getLxcAppsCached, setLxcAppsCached, subscribeLxcApps } from "../lib/lxc-apps-cache"
import { categoryChipStyle, useIsLightTheme } from "../lib/category-color"
import { getCategoryLabel } from "../lib/category-label"
import { useT } from "@/lib/i18n/provider"
// installed_via is optional now — an empty value means "register only,
@@ -1604,7 +1605,7 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData }: Prop
<SelectContent>
<SelectItem value="__none__">{t("vmLxc.appEditor.portCategoryNone")}</SelectItem>
{categoryPresets.map((c) => (
<SelectItem key={c} value={c}>{c}</SelectItem>
<SelectItem key={c} value={c}>{getCategoryLabel(t, c)}</SelectItem>
))}
<SelectItem value="__add__">{t("vmLxc.appEditor.portCategoryAddNew")}</SelectItem>
</SelectContent>
@@ -2649,9 +2650,9 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData }: Prop
<span
style={categoryChipStyle(p.category, isLightTheme)}
className="flex-shrink-0 px-1.5 py-0.5 border rounded text-[10px] font-medium truncate max-w-[40%]"
title={p.category}
title={getCategoryLabel(t, p.category)}
>
{p.category}
{getCategoryLabel(t, p.category)}
</span>
)}
</div>
+40
View File
@@ -0,0 +1,40 @@
type Translate = (key: string) => string
// Category values come from the community-scripts catalog and are also
// persisted with application links. Keep those raw values intact for
// filtering and storage; translate only what is displayed in the UI.
const CATEGORY_KEY_BY_VALUE: Record<string, string> = {
"*Arr Suite": "arrSuite",
"AI / Coding & Dev-Tools": "aiCodingDevTools",
"Adblock & DNS": "adblockDns",
"Authentication & Security": "authenticationSecurity",
"Automation & Scheduling": "automationScheduling",
"Backup & Recovery": "backupRecovery",
"Business & ERP": "businessErp",
"Communication & Community": "communicationCommunity",
"Containers & Docker": "containersDocker",
"Dashboards & Frontends": "dashboardsFrontends",
"Databases": "databases",
"Documents & Notes": "documentsNotes",
"Files & Downloads": "filesDownloads",
"Finance & Budgeting": "financeBudgeting",
"Gaming & Leisure": "gamingLeisure",
"Host Management": "hostManagement",
"IoT & Smart Home": "iotSmartHome",
"Media & Streaming": "mediaStreaming",
"Messaging & Queues": "messagingQueues",
"Miscellaneous": "miscellaneous",
"Monitoring & Analytics": "monitoringAnalytics",
"NVR & Cameras": "nvrCameras",
"Network & Firewall": "networkFirewall",
"Operating Systems & Appliances": "operatingSystemsAppliances",
"Productivity & Workflows": "productivityWorkflows",
"Remote Access & VPN": "remoteAccessVpn",
"Webservers & Proxies": "webserversProxies",
"ZigBee, Z-Wave & Matter": "zigbeeZwaveMatter",
}
export function getCategoryLabel(t: Translate, value: string): string {
const key = CATEGORY_KEY_BY_VALUE[value.trim()]
return key ? t(`apps.categoryLabels.${key}`) : value
}
+4 -1
View File
@@ -4929,7 +4929,10 @@
"customLinkCancel": "Cancel",
"customLinkDelete": "Delete",
"customLinkSaveError": "Save failed",
"customLinkDeleteError": "Delete failed"
"customLinkDeleteError": "Delete failed",
"categoryLabels": {
"arrSuite": "*Arr Suite", "aiCodingDevTools": "AI / Coding & Dev-Tools", "adblockDns": "Adblock & DNS", "authenticationSecurity": "Authentication & Security", "automationScheduling": "Automation & Scheduling", "backupRecovery": "Backup & Recovery", "businessErp": "Business & ERP", "communicationCommunity": "Communication & Community", "containersDocker": "Containers & Docker", "dashboardsFrontends": "Dashboards & Frontends", "databases": "Databases", "documentsNotes": "Documents & Notes", "filesDownloads": "Files & Downloads", "financeBudgeting": "Finance & Budgeting", "gamingLeisure": "Gaming & Leisure", "hostManagement": "Host Management", "iotSmartHome": "IoT & Smart Home", "mediaStreaming": "Media & Streaming", "messagingQueues": "Messaging & Queues", "miscellaneous": "Miscellaneous", "monitoringAnalytics": "Monitoring & Analytics", "nvrCameras": "NVR & Cameras", "networkFirewall": "Network & Firewall", "operatingSystemsAppliances": "Operating Systems & Appliances", "productivityWorkflows": "Productivity & Workflows", "remoteAccessVpn": "Remote Access & VPN", "webserversProxies": "Webservers & Proxies", "zigbeeZwaveMatter": "ZigBee, Z-Wave & Matter"
}
},
"audit": {
"presentation": {
+32 -2
View File
@@ -4994,7 +4994,37 @@
"customLinkCancel": "Cancel",
"customLinkDelete": "Delete",
"customLinkSaveError": "Save failed",
"customLinkDeleteError": "Delete failed"
"customLinkDeleteError": "Delete failed",
"categoryLabels": {
"arrSuite": "*Arr Suite",
"aiCodingDevTools": "AI / Coding & Dev-Tools",
"adblockDns": "Adblock & DNS",
"authenticationSecurity": "Authentication & Security",
"automationScheduling": "Automation & Scheduling",
"backupRecovery": "Backup & Recovery",
"businessErp": "Business & ERP",
"communicationCommunity": "Communication & Community",
"containersDocker": "Containers & Docker",
"dashboardsFrontends": "Dashboards & Frontends",
"databases": "Databases",
"documentsNotes": "Documents & Notes",
"filesDownloads": "Files & Downloads",
"financeBudgeting": "Finance & Budgeting",
"gamingLeisure": "Gaming & Leisure",
"hostManagement": "Host Management",
"iotSmartHome": "IoT & Smart Home",
"mediaStreaming": "Media & Streaming",
"messagingQueues": "Messaging & Queues",
"miscellaneous": "Miscellaneous",
"monitoringAnalytics": "Monitoring & Analytics",
"nvrCameras": "NVR & Cameras",
"networkFirewall": "Network & Firewall",
"operatingSystemsAppliances": "Operating Systems & Appliances",
"productivityWorkflows": "Productivity & Workflows",
"remoteAccessVpn": "Remote Access & VPN",
"webserversProxies": "Webservers & Proxies",
"zigbeeZwaveMatter": "ZigBee, Z-Wave & Matter"
}
},
"audit": {
"presentation": {
@@ -6054,4 +6084,4 @@
"cancel": "Cancel"
}
}
}
}
+4 -1
View File
@@ -4929,7 +4929,10 @@
"customLinkCancel": "Cancelar",
"customLinkDelete": "Eliminar",
"customLinkSaveError": "Error al guardar",
"customLinkDeleteError": "Error al eliminar"
"customLinkDeleteError": "Error al eliminar",
"categoryLabels": {
"arrSuite": "*Arr Suite", "aiCodingDevTools": "AI / Coding & Dev-Tools", "adblockDns": "Adblock & DNS", "authenticationSecurity": "Authentication & Security", "automationScheduling": "Automation & Scheduling", "backupRecovery": "Backup & Recovery", "businessErp": "Business & ERP", "communicationCommunity": "Communication & Community", "containersDocker": "Containers & Docker", "dashboardsFrontends": "Dashboards & Frontends", "databases": "Databases", "documentsNotes": "Documents & Notes", "filesDownloads": "Files & Downloads", "financeBudgeting": "Finance & Budgeting", "gamingLeisure": "Gaming & Leisure", "hostManagement": "Host Management", "iotSmartHome": "IoT & Smart Home", "mediaStreaming": "Media & Streaming", "messagingQueues": "Messaging & Queues", "miscellaneous": "Miscellaneous", "monitoringAnalytics": "Monitoring & Analytics", "nvrCameras": "NVR & Cameras", "networkFirewall": "Network & Firewall", "operatingSystemsAppliances": "Operating Systems & Appliances", "productivityWorkflows": "Productivity & Workflows", "remoteAccessVpn": "Remote Access & VPN", "webserversProxies": "Webservers & Proxies", "zigbeeZwaveMatter": "ZigBee, Z-Wave & Matter"
}
},
"audit": {
"presentation": {
+4 -1
View File
@@ -4929,7 +4929,10 @@
"customLinkCancel": "Cancel",
"customLinkDelete": "Delete",
"customLinkSaveError": "Save failed",
"customLinkDeleteError": "Delete failed"
"customLinkDeleteError": "Delete failed",
"categoryLabels": {
"arrSuite": "*Arr Suite", "aiCodingDevTools": "AI / Coding & Dev-Tools", "adblockDns": "Adblock & DNS", "authenticationSecurity": "Authentication & Security", "automationScheduling": "Automation & Scheduling", "backupRecovery": "Backup & Recovery", "businessErp": "Business & ERP", "communicationCommunity": "Communication & Community", "containersDocker": "Containers & Docker", "dashboardsFrontends": "Dashboards & Frontends", "databases": "Databases", "documentsNotes": "Documents & Notes", "filesDownloads": "Files & Downloads", "financeBudgeting": "Finance & Budgeting", "gamingLeisure": "Gaming & Leisure", "hostManagement": "Host Management", "iotSmartHome": "IoT & Smart Home", "mediaStreaming": "Media & Streaming", "messagingQueues": "Messaging & Queues", "miscellaneous": "Miscellaneous", "monitoringAnalytics": "Monitoring & Analytics", "nvrCameras": "NVR & Cameras", "networkFirewall": "Network & Firewall", "operatingSystemsAppliances": "Operating Systems & Appliances", "productivityWorkflows": "Productivity & Workflows", "remoteAccessVpn": "Remote Access & VPN", "webserversProxies": "Webservers & Proxies", "zigbeeZwaveMatter": "ZigBee, Z-Wave & Matter"
}
},
"audit": {
"presentation": {
+4 -1
View File
@@ -4929,7 +4929,10 @@
"customLinkCancel": "Cancel",
"customLinkDelete": "Delete",
"customLinkSaveError": "Save failed",
"customLinkDeleteError": "Delete failed"
"customLinkDeleteError": "Delete failed",
"categoryLabels": {
"arrSuite": "*Arr Suite", "aiCodingDevTools": "AI / Coding & Dev-Tools", "adblockDns": "Adblock & DNS", "authenticationSecurity": "Authentication & Security", "automationScheduling": "Automation & Scheduling", "backupRecovery": "Backup & Recovery", "businessErp": "Business & ERP", "communicationCommunity": "Communication & Community", "containersDocker": "Containers & Docker", "dashboardsFrontends": "Dashboards & Frontends", "databases": "Databases", "documentsNotes": "Documents & Notes", "filesDownloads": "Files & Downloads", "financeBudgeting": "Finance & Budgeting", "gamingLeisure": "Gaming & Leisure", "hostManagement": "Host Management", "iotSmartHome": "IoT & Smart Home", "mediaStreaming": "Media & Streaming", "messagingQueues": "Messaging & Queues", "miscellaneous": "Miscellaneous", "monitoringAnalytics": "Monitoring & Analytics", "nvrCameras": "NVR & Cameras", "networkFirewall": "Network & Firewall", "operatingSystemsAppliances": "Operating Systems & Appliances", "productivityWorkflows": "Productivity & Workflows", "remoteAccessVpn": "Remote Access & VPN", "webserversProxies": "Webservers & Proxies", "zigbeeZwaveMatter": "ZigBee, Z-Wave & Matter"
}
},
"audit": {
"presentation": {
+4 -1
View File
@@ -4930,7 +4930,10 @@
"customLinkCancel": "Cancel",
"customLinkDelete": "Delete",
"customLinkSaveError": "Save failed",
"customLinkDeleteError": "Delete failed"
"customLinkDeleteError": "Delete failed",
"categoryLabels": {
"arrSuite": "*Arr Suite", "aiCodingDevTools": "AI / Coding & Dev-Tools", "adblockDns": "Adblock & DNS", "authenticationSecurity": "Authentication & Security", "automationScheduling": "Automation & Scheduling", "backupRecovery": "Backup & Recovery", "businessErp": "Business & ERP", "communicationCommunity": "Communication & Community", "containersDocker": "Containers & Docker", "dashboardsFrontends": "Dashboards & Frontends", "databases": "Databases", "documentsNotes": "Documents & Notes", "filesDownloads": "Files & Downloads", "financeBudgeting": "Finance & Budgeting", "gamingLeisure": "Gaming & Leisure", "hostManagement": "Host Management", "iotSmartHome": "IoT & Smart Home", "mediaStreaming": "Media & Streaming", "messagingQueues": "Messaging & Queues", "miscellaneous": "Miscellaneous", "monitoringAnalytics": "Monitoring & Analytics", "nvrCameras": "NVR & Cameras", "networkFirewall": "Network & Firewall", "operatingSystemsAppliances": "Operating Systems & Appliances", "productivityWorkflows": "Productivity & Workflows", "remoteAccessVpn": "Remote Access & VPN", "webserversProxies": "Webservers & Proxies", "zigbeeZwaveMatter": "ZigBee, Z-Wave & Matter"
}
},
"audit": {
"presentation": {
+31 -1
View File
@@ -4994,7 +4994,37 @@
"customLinkCancel": "Zrušiť",
"customLinkDelete": "Odstrániť",
"customLinkSaveError": "Odkaz sa nepodarilo uložiť",
"customLinkDeleteError": "Odkaz sa nepodarilo odstrániť"
"customLinkDeleteError": "Odkaz sa nepodarilo odstrániť",
"categoryLabels": {
"arrSuite": "Sada *Arr",
"aiCodingDevTools": "AI, programovanie a vývojárske nástroje",
"adblockDns": "Blokovanie reklám a DNS",
"authenticationSecurity": "Overovanie a bezpečnosť",
"automationScheduling": "Automatizácia a plánovanie",
"backupRecovery": "Zálohovanie a obnova",
"businessErp": "Podnikové aplikácie a ERP",
"communicationCommunity": "Komunikácia a komunita",
"containersDocker": "Kontajnery a Docker",
"dashboardsFrontends": "Prehľady a webové rozhrania",
"databases": "Databázy",
"documentsNotes": "Dokumenty a poznámky",
"filesDownloads": "Súbory a sťahovanie",
"financeBudgeting": "Financie a rozpočet",
"gamingLeisure": "Hry a voľný čas",
"hostManagement": "Správa servera",
"iotSmartHome": "IoT a inteligentná domácnosť",
"mediaStreaming": "Médiá a streamovanie",
"messagingQueues": "Správy a fronty",
"miscellaneous": "Ostatné",
"monitoringAnalytics": "Monitorovanie a analýza",
"nvrCameras": "NVR a kamery",
"networkFirewall": "Sieť a firewall",
"operatingSystemsAppliances": "Operačné systémy a zariadenia",
"productivityWorkflows": "Produktivita a pracovné postupy",
"remoteAccessVpn": "Vzdialený prístup a VPN",
"webserversProxies": "Webové servery a proxy",
"zigbeeZwaveMatter": "Zigbee, Z-Wave a Matter"
}
},
"audit": {
"presentation": {
+5 -2
View File
@@ -4929,7 +4929,10 @@
"customLinkCancel": "Cancel",
"customLinkDelete": "Delete",
"customLinkSaveError": "Save failed",
"customLinkDeleteError": "Delete failed"
"customLinkDeleteError": "Delete failed",
"categoryLabels": {
"arrSuite": "*Arr Suite", "aiCodingDevTools": "AI / Coding & Dev-Tools", "adblockDns": "Adblock & DNS", "authenticationSecurity": "Authentication & Security", "automationScheduling": "Automation & Scheduling", "backupRecovery": "Backup & Recovery", "businessErp": "Business & ERP", "communicationCommunity": "Communication & Community", "containersDocker": "Containers & Docker", "dashboardsFrontends": "Dashboards & Frontends", "databases": "Databases", "documentsNotes": "Documents & Notes", "filesDownloads": "Files & Downloads", "financeBudgeting": "Finance & Budgeting", "gamingLeisure": "Gaming & Leisure", "hostManagement": "Host Management", "iotSmartHome": "IoT & Smart Home", "mediaStreaming": "Media & Streaming", "messagingQueues": "Messaging & Queues", "miscellaneous": "Miscellaneous", "monitoringAnalytics": "Monitoring & Analytics", "nvrCameras": "NVR & Cameras", "networkFirewall": "Network & Firewall", "operatingSystemsAppliances": "Operating Systems & Appliances", "productivityWorkflows": "Productivity & Workflows", "remoteAccessVpn": "Remote Access & VPN", "webserversProxies": "Webservers & Proxies", "zigbeeZwaveMatter": "ZigBee, Z-Wave & Matter"
}
},
"audit": {
"presentation": {
@@ -5989,4 +5992,4 @@
"cancel": "Cancel"
}
}
}
}