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 = `
Prueft Medienreferenzen und markiert primaer benutzerrelevante Risiken in den Foundry-Roots Orphans werden bewusst nicht global fuer den gesamten Weltverweise auf Modul- oder Systemassets gelten nicht pauschal als Problem. Relevant sind vor allem Ziele, die im owning Paket selbst nicht sichtbar referenziert werden. ${renderLocalizedCodeText("KSA.Hero.Intro1", { dataRoot: "data", publicRoot: "public" }, ["data", "public"])} ${renderLocalizedCodeText("KSA.Hero.Intro2", { dataRoot: "data" }, ["data"])} ${escapeHtml(localize("KSA.Hero.Intro3"))}Kosmos Storage Audit
- data und public.data-Root behauptet, sondern nur in klar weltlokalen oder explizit riskanten Bereichen.${localize("KSA.Hero.Title")}
+
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")}
${escapeHtml(group.target)}
${escapeHtml(group.shortReason)}
${escapeHtml(group.ownerLabel)}${escapeHtml(group.ownerLabel)}${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 => `${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)}${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 `Keine gruppierten ${showAll ? "" : "warnenden "}Findings vorhanden.
+${format("KSA.Section.NoGrouped", { scope: localize(showAll ? "KSA.Scope.Empty" : "KSA.Scope.Warning") })}
Analyse laeuft...
${localize("KSA.Section.Running")}
Die Analyse kann direkt aus dieser Ansicht gestartet werden.
${localize("KSA.Section.NoPrompt")}
Keine ${showAll ? "" : "warnenden "}Findings gefunden.
${format("KSA.Section.NoRaw", { scope: localize(showAll ? "KSA.Scope.Empty" : "KSA.Scope.Warning") })}
${escapeHtml(finding.reason)}
${escapeHtml(finding.target.locator ?? `${finding.target.storage}:${finding.target.path}`)}${escapeHtml(finding.target.locator ?? `${finding.target.storage}:${finding.target.path}`)}${escapeHtml(finding.recommendation)}
$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;
+}