diff --git a/README.md b/README.md index 6646a37..33d7296 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Dieses Verzeichnis enthaelt einen ersten Modul-Prototypen fuer die Analyse von M - `analyzer.js` - Orchestrierung von Dateien, Quellen und Findings - `audit-report-app.js` - - Foundry-UI mit Fortschrittsanzeige, gruppierter Arbeitsansicht und JSON-Export + - Foundry-UI mit Fortschrittsanzeige, gruppierter Arbeitsansicht, JSON-Export und lokalisierter Oberfläche ## Wichtige Heuristik @@ -41,6 +41,7 @@ Dieses Verzeichnis enthaelt einen ersten Modul-Prototypen fuer die Analyse von M - Wildcard-Pfade wie `Ork*_Token.webp` werden nicht als kaputt markiert, solange mindestens eine passende Datei im gescannten Root existiert. - Dieselben Wildcard-Referenzen zaehlen auch als eingehende Referenz fuer passende Dateien und verhindern damit false-positive `orphan-file`-Treffer. - URL-encodierte oder unterschiedlich normalisierte Umlaute sollen auf dieselbe kanonische Pfadform zusammengefuehrt werden. +- Die UI unterstuetzt konsistent Deutsch und Englisch ueber Foundry-Sprachdateien. ## Noch offen diff --git a/lang/de.json b/lang/de.json new file mode 100644 index 0000000..293372a --- /dev/null +++ b/lang/de.json @@ -0,0 +1,100 @@ +{ + "KSA": { + "AppTitle": "Kosmos Storage Audit", + "Menu": { + "Name": "Kosmos Storage Audit", + "Label": "Report öffnen", + "Hint": "Die Storage-Analyse ausführen und priorisierte Findings prüfen." + }, + "Hero": { + "Title": "Kosmos Storage Audit", + "Intro1": "Prüft Medienreferenzen und markiert primär benutzerrelevante Risiken in den Foundry-Roots {dataRoot} und {publicRoot}.", + "Intro2": "Orphans werden bewusst nicht global für den gesamten {dataRoot}-Root behauptet, sondern nur in klar weltlokalen oder explizit riskanten Bereichen.", + "Intro3": "Weltverweise auf Modul- oder Systemassets gelten nicht pauschal als Problem. Relevant sind vor allem Ziele, die im owning Paket selbst nicht sichtbar referenziert werden." + }, + "Action": { + "Run": "Analyse starten", + "Running": "Analysiere...", + "Export": "Report exportieren", + "ShowAll": "Alle Findings", + "ShowWarnings": "Nur Warnungen", + "ShowRaw": "Einzelfälle anzeigen", + "HideRaw": "Einzelfälle ausblenden" + }, + "Progress": { + "Initialize": "Initialisiere Analyse", + "Analyzing": "Analysiere", + "Current": "Aktuell: {source}", + "Files": "Dateien: {count}", + "Sources": "Quellen: {count}", + "References": "Referenzen: {count}", + "Findings": "Findings: {count}", + "BrowseStorage": "Durchsuche {storage}:{path}", + "ReadPack": "Lese Paket-Pack {pack}" + }, + "Notify": { + "Completed": "Kosmos Storage Audit abgeschlossen: {count} Findings.", + "Failed": "Kosmos Storage Audit fehlgeschlagen: {message}" + }, + "Summary": { + "NoAnalysis": "Noch keine Analyse ausgeführt.", + "Files": "Dateien", + "References": "Referenzen", + "Findings": "Findings", + "High": "High", + "Warning": "Warning", + "ByType": "Findings nach Typ", + "NoFindings": "Keine Findings", + "WorkBlocks": "Arbeitsblöcke", + "MissingTargets": "Deduplizierte fehlende Ziele", + "UnanchoredPackageTargets": "Unverankerte Paketziele", + "OrphanCandidates": "Orphan-Kandidaten" + }, + "Section": { + "WorkView": "Arbeitsansicht", + "NoGrouped": "Keine gruppierten {scope}Findings vorhanden.", + "NoPrompt": "Die Analyse kann direkt aus dieser Ansicht gestartet werden.", + "Running": "Analyse läuft...", + "NoRaw": "Keine {scope}Findings gefunden.", + "RawEntries": "Einzelfälle", + "Samples": "Beispiele", + "UnanchoredPackageTargets": "Unverankerte Paketziele", + "UnanchoredPackageTargetsDesc": "Diese Ziele liegen in Modul- oder Systemordnern, werden aus Weltdaten referenziert, sind im owning Paket selbst aber derzeit nicht als Referenz sichtbar.", + "BrokenTargets": "Kaputte Ziele", + "BrokenTargetsDesc": "Diese Dateien fehlen, werden aber weiterhin referenziert.", + "OrphanCandidates": "Orphan-Kandidaten", + "OrphanCandidatesDesc": "Diese Dateien haben im aktuellen Analysekontext keine eingehende Referenz." + }, + "Field": { + "OwnerPackage": "Owner-Paket", + "Assessment": "Bewertung", + "Target": "Ziel", + "Source": "Quelle" + }, + "FindingKind": { + "non-package-to-package-reference": "Unverankerte Paketziele", + "broken-reference": "Kaputte Ziele", + "orphan-file": "Orphan-Kandidaten", + "risky-public-reference": "Riskante Public-Ziele" + }, + "Finding": { + "PackageExplanation": "Weltdaten zeigen auf ein Paketasset, für das in Manifesten und Paket-Packs keine sichtbare Eigenreferenz gefunden wurde.", + "WildcardNoMatch": "Musterreferenz: Wildcard wird von Foundry unterstützt, derzeit aber ohne passenden Treffer im Dateisystem." + }, + "Severity": { + "high": "High", + "warning": "Warning", + "info": "Info" + }, + "Owner": { + "Unknown": "unbekannt" + }, + "Export": { + "Filename": "kosmos-storage-audit-{timestamp}.json" + }, + "Scope": { + "Warning": "warnenden ", + "Empty": "" + } + } +} diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 0000000..9060aa7 --- /dev/null +++ b/lang/en.json @@ -0,0 +1,100 @@ +{ + "KSA": { + "AppTitle": "Kosmos Storage Audit", + "Menu": { + "Name": "Kosmos Storage Audit", + "Label": "Open Report", + "Hint": "Run the storage audit and inspect prioritized findings." + }, + "Hero": { + "Title": "Kosmos Storage Audit", + "Intro1": "Audits media references and highlights primarily user-relevant risks in the Foundry roots {dataRoot} and {publicRoot}.", + "Intro2": "Orphans are intentionally not claimed globally for the entire {dataRoot} root, but only in clearly world-local or explicitly risky areas.", + "Intro3": "World references to module or system assets are not treated as a problem by default. The main focus is on targets that are not visibly referenced by their owning package." + }, + "Action": { + "Run": "Start Analysis", + "Running": "Analyzing...", + "Export": "Export Report", + "ShowAll": "All Findings", + "ShowWarnings": "Warnings Only", + "ShowRaw": "Show Raw Entries", + "HideRaw": "Hide Raw Entries" + }, + "Progress": { + "Initialize": "Initializing analysis", + "Analyzing": "Analyzing", + "Current": "Current: {source}", + "Files": "Files: {count}", + "Sources": "Sources: {count}", + "References": "References: {count}", + "Findings": "Findings: {count}", + "BrowseStorage": "Browsing {storage}:{path}", + "ReadPack": "Reading package pack {pack}" + }, + "Notify": { + "Completed": "Kosmos Storage Audit completed: {count} findings.", + "Failed": "Kosmos Storage Audit failed: {message}" + }, + "Summary": { + "NoAnalysis": "No analysis has been run yet.", + "Files": "Files", + "References": "References", + "Findings": "Findings", + "High": "High", + "Warning": "Warning", + "ByType": "Findings by Type", + "NoFindings": "No findings", + "WorkBlocks": "Work Blocks", + "MissingTargets": "Deduplicated missing targets", + "UnanchoredPackageTargets": "Unanchored package targets", + "OrphanCandidates": "Orphan candidates" + }, + "Section": { + "WorkView": "Work View", + "NoGrouped": "No grouped {scope}findings available.", + "NoPrompt": "The analysis can be started directly from this view.", + "Running": "Analysis in progress...", + "NoRaw": "No {scope}findings found.", + "RawEntries": "Raw Entries", + "Samples": "Examples", + "UnanchoredPackageTargets": "Unanchored Package Targets", + "UnanchoredPackageTargetsDesc": "These targets live in module or system folders, are referenced from world data, but are currently not visibly referenced by their owning package.", + "BrokenTargets": "Broken Targets", + "BrokenTargetsDesc": "These files are missing but are still referenced.", + "OrphanCandidates": "Orphan Candidates", + "OrphanCandidatesDesc": "These files currently have no incoming reference in the active analysis context." + }, + "Field": { + "OwnerPackage": "Owning Package", + "Assessment": "Assessment", + "Target": "Target", + "Source": "Source" + }, + "FindingKind": { + "non-package-to-package-reference": "Unanchored package targets", + "broken-reference": "Broken targets", + "orphan-file": "Orphan candidates", + "risky-public-reference": "Risky public targets" + }, + "Finding": { + "PackageExplanation": "World data points at a package asset for which no visible self-reference was found in manifests or package packs.", + "WildcardNoMatch": "Pattern reference: wildcard is supported by Foundry, but currently has no matching file in the scanned storage." + }, + "Severity": { + "high": "High", + "warning": "Warning", + "info": "Info" + }, + "Owner": { + "Unknown": "unknown" + }, + "Export": { + "Filename": "kosmos-storage-audit-{timestamp}.json" + }, + "Scope": { + "Warning": "warning ", + "Empty": "" + } + } +} diff --git a/module.json b/module.json index f79ad31..abda8f0 100644 --- a/module.json +++ b/module.json @@ -2,7 +2,7 @@ "id": "kosmos-storage-audit", "title": "Kosmos Storage Audit", "description": "Analyzes media references and risky storage locations across Foundry data and public roots.", - "version": "0.0.2", + "version": "0.0.3", "compatibility": { "minimum": "13", "verified": "13" @@ -18,7 +18,18 @@ "styles": [ "styles/audit.css" ], - "languages": [], + "languages": [ + { + "lang": "de", + "name": "Deutsch", + "path": "lang/de.json" + }, + { + "lang": "en", + "name": "English", + "path": "lang/en.json" + } + ], "relationships": {}, "url": "https://gitea.kosmos.ac/kosmos/kosmos-storage-audit", "manifest": "https://gitea.kosmos.ac/kosmos/kosmos-storage-audit/raw/branch/main/module.json", diff --git a/scripts/adapters/foundry-runtime.js b/scripts/adapters/foundry-runtime.js index b9bd497..886994b 100644 --- a/scripts/adapters/foundry-runtime.js +++ b/scripts/adapters/foundry-runtime.js @@ -2,7 +2,7 @@ import { analyzeStorage } from "../core/analyzer.js"; import { isMediaPath, normalizePath } from "../core/path-utils.js"; async function* walkFilePicker(storage, target = "", onProgress = null) { - onProgress?.({ phase: "files", label: `Durchsuche ${storage}:${target || "/"}` }); + onProgress?.({ phase: "files", label: format("KSA.Progress.BrowseStorage", { storage, path: target || "/" }) }); const result = await FilePicker.browse(storage, target); for (const file of result.files ?? []) { const path = normalizePath(file); @@ -78,7 +78,7 @@ async function* packagePackEntries(onProgress = null) { const ownerType = pack.metadata.packageType; const ownerId = pack.metadata.packageName; if (!["module", "system"].includes(ownerType) || !ownerId) continue; - onProgress?.({ phase: "sources", label: `Lese Paket-Pack ${pack.collection}`, currentSource: pack.collection }); + onProgress?.({ phase: "sources", label: format("KSA.Progress.ReadPack", { pack: pack.collection }), currentSource: pack.collection }); const documents = await pack.getDocuments(); for (const document of documents) { yield { @@ -108,3 +108,7 @@ export async function runRuntimeAnalysis({ onProgress }={}) { onProgress }); } + +function format(key, data) { + return game.i18n?.format(key, data) ?? key; +} diff --git a/scripts/apps/audit-report-app.js b/scripts/apps/audit-report-app.js index e047609..7bbf211 100644 --- a/scripts/apps/audit-report-app.js +++ b/scripts/apps/audit-report-app.js @@ -26,6 +26,11 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV #progress = null; #showRaw = false; + constructor(options = {}) { + super(options); + this.options.window.title = localize("KSA.AppTitle"); + } + async _prepareContext() { const findings = this.#analysis?.findings ?? []; const visibleFindings = this.#showAll ? findings : findings.filter(f => f.severity !== "info"); @@ -48,27 +53,27 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV container.innerHTML = `
-

Kosmos Storage Audit

-

Prueft Medienreferenzen und markiert primaer benutzerrelevante Risiken in den Foundry-Roots data und public.

-

Orphans werden bewusst nicht global fuer den gesamten data-Root behauptet, sondern nur in klar weltlokalen oder explizit riskanten Bereichen.

-

Weltverweise auf Modul- oder Systemassets gelten nicht pauschal als Problem. Relevant sind vor allem Ziele, die im owning Paket selbst nicht sichtbar referenziert werden.

+

${localize("KSA.Hero.Title")}

+

${renderLocalizedCodeText("KSA.Hero.Intro1", { dataRoot: "data", publicRoot: "public" }, ["data", "public"])}

+

${renderLocalizedCodeText("KSA.Hero.Intro2", { dataRoot: "data" }, ["data"])}

+

${escapeHtml(localize("KSA.Hero.Intro3"))}

@@ -88,7 +93,7 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV this.#loading = true; this.#progress = { phase: "start", - label: "Initialisiere Analyse", + label: localize("KSA.Progress.Initialize"), files: 0, sources: 0, references: 0, @@ -102,10 +107,10 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV onProgress: update => this.#updateProgress(update) }); const count = this.#analysis.findings.length; - ui.notifications.info(`Kosmos Storage Audit abgeschlossen: ${count} Findings.`); + ui.notifications.info(format("KSA.Notify.Completed", { count })); } catch (error) { console.error("kosmos-storage-audit | analysis failed", error); - ui.notifications.error(`Kosmos Storage Audit fehlgeschlagen: ${error.message}`); + ui.notifications.error(format("KSA.Notify.Failed", { message: error.message })); } finally { this.#loading = false; await this.render({ force: true }); @@ -134,7 +139,7 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; - link.download = `kosmos-storage-audit-${Date.now()}.json`; + link.download = format("KSA.Export.Filename", { timestamp: Date.now() }); link.click(); setTimeout(() => URL.revokeObjectURL(url), 0); } @@ -202,13 +207,13 @@ function renderProgress(progress, loading) {
- ${escapeHtml(progress.label ?? "Analysiere")} - Dateien: ${progress.files ?? 0} - Quellen: ${progress.sources ?? 0} - Referenzen: ${progress.references ?? 0} - ${progress.findings != null ? `Findings: ${progress.findings}` : ""} + ${escapeHtml(progress.label ?? localize("KSA.Progress.Analyzing"))} + ${escapeHtml(format("KSA.Progress.Files", { count: progress.files ?? 0 }))} + ${escapeHtml(format("KSA.Progress.Sources", { count: progress.sources ?? 0 }))} + ${escapeHtml(format("KSA.Progress.References", { count: progress.references ?? 0 }))} + ${progress.findings != null ? `${escapeHtml(format("KSA.Progress.Findings", { count: progress.findings }))}` : ""}
- ${progress.currentSource ? `

Aktuell: ${escapeHtml(progress.currentSource)}

` : ""} + ${progress.currentSource ? `

${escapeHtml(format("KSA.Progress.Current", { source: progress.currentSource }))}

` : ""} `; } @@ -217,7 +222,7 @@ function renderSummary(summary) { if (!summary) { return `
-

Noch keine Analyse ausgefuehrt.

+

${localize("KSA.Summary.NoAnalysis")}

`; } @@ -230,22 +235,22 @@ function renderSummary(summary) { return `
-
Dateien${summary.files}
-
Referenzen${summary.references}
-
Findings${summary.findings}
-
High${summary.bySeverity.high}
-
Warning${summary.bySeverity.warning}
+
${localize("KSA.Summary.Files")}${summary.files}
+
${localize("KSA.Summary.References")}${summary.references}
+
${localize("KSA.Summary.Findings")}${summary.findings}
+
${localize("KSA.Summary.High")}${summary.bySeverity.high}
+
${localize("KSA.Summary.Warning")}${summary.bySeverity.warning}
-

Findings nach Typ

- +

${localize("KSA.Summary.ByType")}

+
-

Arbeitsbloecke

+

${localize("KSA.Summary.WorkBlocks")}

@@ -258,20 +263,20 @@ function renderGroupedFindingList(groupedFindings, hasAnalysis, loading, showAll const sections = []; if (groupedFindings.nonPackageToPackage.length) { sections.push(renderGroupedSection( - "Unverankerte Paketziele", - "Diese Ziele liegen in Modul- oder Systemordnern, werden aus Weltdaten referenziert, sind im owning Paket selbst aber derzeit nicht als Referenz sichtbar.", + localize("KSA.Section.UnanchoredPackageTargets"), + localize("KSA.Section.UnanchoredPackageTargetsDesc"), groupedFindings.nonPackageToPackage, group => `
- ${group.severity} - ${group.count} Referenzen + ${severityLabel(group.severity)} + ${formatCount(group.count, "KSA.Summary.References")}

${escapeHtml(group.target)}

${escapeHtml(group.shortReason)}

-
Owner-Paket
${escapeHtml(group.ownerLabel)}
-
Bewertung
${escapeHtml(group.explanation)}
+
${localize("KSA.Field.OwnerPackage")}
${escapeHtml(group.ownerLabel)}
+
${localize("KSA.Field.Assessment")}
${escapeHtml(group.explanation)}

${escapeHtml(group.recommendation)}

${renderSampleSources(group.sources)} @@ -282,18 +287,18 @@ function renderGroupedFindingList(groupedFindings, hasAnalysis, loading, showAll if (groupedFindings.brokenReferences.length) { sections.push(renderGroupedSection( - "Kaputte Ziele", - "Diese Dateien fehlen, werden aber weiterhin referenziert.", + localize("KSA.Section.BrokenTargets"), + localize("KSA.Section.BrokenTargetsDesc"), groupedFindings.brokenReferences, group => `
- ${group.severity} - ${group.count} Referenzen + ${severityLabel(group.severity)} + ${formatCount(group.count, "KSA.Summary.References")}

${escapeHtml(group.target)}

${escapeHtml(group.shortReason)}

- ${group.targetKind === "wildcard" ? `

Musterreferenz: Wildcard wird von Foundry unterstuetzt, derzeit aber ohne passenden Treffer im Dateisystem.

` : ""} + ${group.targetKind === "wildcard" ? `

${localize("KSA.Finding.WildcardNoMatch")}

` : ""}

${escapeHtml(group.recommendation)}

${renderSampleSources(group.sources)}
@@ -303,14 +308,14 @@ function renderGroupedFindingList(groupedFindings, hasAnalysis, loading, showAll if (showAll && groupedFindings.orphans.length) { sections.push(renderGroupedSection( - "Orphan-Kandidaten", - "Diese Dateien haben im aktuellen Analysekontext keine eingehende Referenz.", + localize("KSA.Section.OrphanCandidates"), + localize("KSA.Section.OrphanCandidatesDesc"), groupedFindings.orphans, group => `
- ${group.severity} - ${group.kind} + ${severityLabel(group.severity)} + ${humanizeKind(group.kind)}

${escapeHtml(group.target)}

${escapeHtml(group.reason)}

@@ -324,8 +329,8 @@ function renderGroupedFindingList(groupedFindings, hasAnalysis, loading, showAll return `
-

Arbeitsansicht

-

Keine gruppierten ${showAll ? "" : "warnenden "}Findings vorhanden.

+

${localize("KSA.Section.WorkView")}

+

${format("KSA.Section.NoGrouped", { scope: localize(showAll ? "KSA.Scope.Empty" : "KSA.Scope.Warning") })}

`; @@ -349,28 +354,28 @@ function renderGroupedSection(title, description, groups, renderGroup) { function renderFindingList(findings, hasAnalysis, loading, showAll, showRaw) { if (loading) { - return `

Analyse laeuft...

`; + return `

${localize("KSA.Section.Running")}

`; } if (!hasAnalysis) { - return `

Die Analyse kann direkt aus dieser Ansicht gestartet werden.

`; + return `

${localize("KSA.Section.NoPrompt")}

`; } if (!showRaw) { return ""; } if (!findings.length) { - return `

Keine ${showAll ? "" : "warnenden "}Findings gefunden.

`; + return `

${format("KSA.Section.NoRaw", { scope: localize(showAll ? "KSA.Scope.Empty" : "KSA.Scope.Warning") })}

`; } const items = findings.map(finding => `
- ${finding.severity} + ${severityLabel(finding.severity)} ${humanizeKind(finding.kind)}

${escapeHtml(finding.reason)}

-
Ziel
${escapeHtml(finding.target.locator ?? `${finding.target.storage}:${finding.target.path}`)}
- ${finding.source ? `
Quelle
${escapeHtml(finding.source.sourceLabel)}
` : ""} +
${localize("KSA.Field.Target")}
${escapeHtml(finding.target.locator ?? `${finding.target.storage}:${finding.target.path}`)}
+ ${finding.source ? `
${localize("KSA.Field.Source")}
${escapeHtml(finding.source.sourceLabel)}
` : ""}

${escapeHtml(finding.recommendation)}

@@ -378,7 +383,7 @@ function renderFindingList(findings, hasAnalysis, loading, showAll, showRaw) { return `
-

Einzelfaelle

+

${localize("KSA.Section.RawEntries")}

${items}
`; @@ -387,7 +392,7 @@ function renderFindingList(findings, hasAnalysis, loading, showAll, showRaw) { function renderSampleSources(sources) { if (!sources.length) return ""; const rows = sources.map(source => `
  • ${escapeHtml(source)}
  • `).join(""); - return `
    Beispiele
      ${rows}
    `; + return `
    ${localize("KSA.Section.Samples")}
      ${rows}
    `; } function groupFindings(findings) { @@ -418,7 +423,7 @@ function groupByTarget(findings) { target, count: 0, ownerLabel, - explanation: "Weltdaten zeigen auf ein Paketasset, fuer das in Manifesten und Paket-Packs keine sichtbare Eigenreferenz gefunden wurde.", + explanation: localize("KSA.Finding.PackageExplanation"), shortReason: shortenReason(finding.reason), reason: finding.reason, recommendation: finding.recommendation, @@ -448,7 +453,7 @@ function deriveOwnerLabel(target) { if ((parts[0] === "modules") || (parts[0] === "systems")) { return `${parts[0]}:${parts[1] ?? "unknown"}`; } - return "unbekannt"; + return localize("KSA.Owner.Unknown"); } function compareFindings(a, b) { @@ -466,12 +471,9 @@ function formatTarget(target) { } function humanizeKind(kind) { - return { - "non-package-to-package-reference": "Unverankerte Paketziele", - "broken-reference": "Kaputte Ziele", - "orphan-file": "Orphan-Kandidaten", - "risky-public-reference": "Riskante Public-Ziele" - }[kind] ?? kind; + const key = `KSA.FindingKind.${kind}`; + const localized = localize(key); + return localized === key ? kind : localized; } function serializeFinding(finding) { @@ -501,3 +503,29 @@ function escapeHtml(value) { .replace(/>/g, ">") .replace(/\"/g, """); } + +function localize(key) { + return game.i18n?.localize(key) ?? key; +} + +function format(key, data) { + return game.i18n?.format(key, data) ?? key; +} + +function severityLabel(severity) { + const key = `KSA.Severity.${severity}`; + const localized = localize(key); + return localized === key ? severity : localized; +} + +function formatCount(count, nounKey) { + return `${count} ${localize(nounKey)}`; +} + +function renderLocalizedCodeText(key, data, codeValues) { + let text = format(key, data); + for (const value of codeValues) { + text = text.replaceAll(value, `@@CODE:${value}@@`); + } + return escapeHtml(text).replace(/@@CODE:([^@]+)@@/g, "$1"); +} diff --git a/scripts/main.js b/scripts/main.js index e5d837c..68663da 100644 --- a/scripts/main.js +++ b/scripts/main.js @@ -4,9 +4,9 @@ import { StorageAuditReportApp } from "./apps/audit-report-app.js"; Hooks.once("init", () => { console.log("kosmos-storage-audit | init"); game.settings.registerMenu("kosmos-storage-audit", "report", { - name: "Kosmos Storage Audit", - label: "Open Report", - hint: "Run the storage audit and inspect prioritized findings.", + name: localize("KSA.Menu.Name"), + label: localize("KSA.Menu.Label"), + hint: localize("KSA.Menu.Hint"), icon: "fa-solid fa-broom-ball", type: StorageAuditReportApp, restricted: true @@ -20,3 +20,7 @@ Hooks.once("init", () => { Hooks.once("ready", () => { console.log("kosmos-storage-audit | ready"); }); + +function localize(key) { + return game.i18n?.localize(key) ?? key; +}