saltcorn-samba 0.4.14 → 0.4.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,153 @@ All notable changes to `saltcorn-samba` are documented here.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.4.16] – 2026-07-08
8
+
9
+ ### Added – Internationalisierung (Deutsch + Englisch)
10
+
11
+ Alle sichtbaren Texte im File-Manager, im Tree, im PDF-Fieldview und in den
12
+ Dialogen (Upload, Delete, Rename, Mkdir) sind jetzt übersetzbar.
13
+
14
+ - Neue Kataloge unter `i18n/de.json` und `i18n/en.json` (~90 Schlüssel,
15
+ Punkt-Notation nach Bereich `ui.*`, `fm.*`, `tree.*`, `pdf.*`).
16
+ - Neues Server-Modul `i18n.js` – keine externe Dependency, JSON-Kataloge
17
+ werden gelesen und im Prozess gecacht. Placeholder-Interpolation `{name}`.
18
+ API: `t(key, {locale, ...params})`, `tFor(locale)`, `catalogFor(locale)`,
19
+ `resolveLocaleFromReq(req, explicit)`.
20
+ - Locale-Auflösung: expliziter Wert → `req.getLocale()` → Query `?locale=xx`
21
+ → `Accept-Language`-Header → Fallback `en`. Unbekannte Locales fallen
22
+ automatisch auf die Sprachbasis oder `en` zurück.
23
+ - Neue Diagnose-Route `GET /samba-i18n.json?locale=xx` liefert den Katalog
24
+ als JSON (kein Auth, enthält keine Konfiguration).
25
+ - Client bekommt den Katalog **inline** in die View-Shell injiziert – kein
26
+ zusätzlicher HTTP-Roundtrip beim ersten Rendern, keine englischen Keys, die
27
+ kurz aufblitzen. `SambaCommon.setCatalog(…)` wird aufgerufen, bevor die
28
+ view-spezifische JS-Datei geladen wird.
29
+ - Fehlt ein Key: automatischer Fallback auf Englisch, dann auf den Key
30
+ selbst – nichts bricht.
31
+
32
+ ### Changed – Client-JS: Gemeinsame Utilities in `public/samba-common.js`
33
+
34
+ - Neues Modul `public/samba-common.js` bündelt: `iconFor()`, `extOf()`,
35
+ `mediaTypeFor()`, `isViewable()`, `fmtSize()`, `fmtDate()`, `joinPath()`,
36
+ `parentOf()`, plus die i18n-Funktionen `t()`, `setCatalog()`, `loadCatalog()`.
37
+ - **`iconFor()` war bisher in `samba-filemanager.js` und `samba-tree.js`
38
+ identisch dupliziert** – jetzt einzige Quelle in `samba-common.js`.
39
+ Änderungen am Icon-Mapping (neue Extensions etc.) müssen nur noch an einer
40
+ Stelle gemacht werden.
41
+ - `samba-filemanager.js` und `samba-tree.js` konsumieren die Utilities über
42
+ `window.SambaCommon`. Die lokalen Kopien von `iconFor`, `extOf`,
43
+ `mediaTypeFor`, `isViewable`, `fmtSize`, `fmtDate`, `joinPath`, `parentOf`
44
+ sind entfernt.
45
+ - Alle sichtbaren Strings in beiden Client-JS gehen über `SambaCommon.t(...)`.
46
+
47
+ ### Changed – Bootstrap der View-Shells: zweistufig
48
+
49
+ `filemanager-view.js` und `tree-view.js` laden jetzt zuerst `samba-common.js`
50
+ (setzt `window.SambaCommon`, wendet den inline gelieferten i18n-Katalog an)
51
+ und erst danach die view-spezifische JS-Datei. Sind beide bereits geladen,
52
+ wird nur re-mounted – kein doppeltes `<script>`-Injecten. `pdf-view.js`
53
+ nutzt serverseitig `tFor(resolveLocaleFromReq(req))`.
54
+
55
+ ### Changed – Namens-Cleanup und Kommentare in beiden Client-JS
56
+
57
+ Goal: die Client-Dateien sollen sich wie ordentliche Node-ähnliche Module
58
+ lesen, nicht wie ein Minifier-Output.
59
+
60
+ - `samba-tree.js`: `h`→`element`, `fetchDir`→`fetchDirectory`,
61
+ `renderList`→`renderLevel`, `openDir`→`toggleDirectory`, `li`→`lineItem`
62
+ u.ä. Jede exportierte / interne Funktion hat einen JSDoc-Kommentar.
63
+ - `samba-filemanager.js`: `h`→`element`, `modal`→`openModal`, `m`→`dialog`,
64
+ `r`→`response`, `fd`→`formData`, `overwriteCb`→`overwriteCheckbox`,
65
+ `picked`→`pickedList`, `uploadBtn`→`uploadButton`. Kommentare ergänzt.
66
+
67
+ ### Added – README: Sicherheits-Abschnitt „Client-/Server-Trennung“
68
+
69
+ Neuer Unterabschnitt in `## Sicherheit` erklärt explizit:
70
+
71
+ - Welche Dateien serverseitig laufen (SMB-Credentials, `base_path`,
72
+ `view_base_path`, Sanitizer, Routen) und welche clientseitig (reine UI).
73
+ - Wo genau Sicherheit durchgesetzt wird: CSRF (Saltcorn-csurf), Auth/Rolle
74
+ (Saltcorn + `min_role_read`/`min_role_write`), `sanitizeRelativePath`,
75
+ Base-Path-Enforcement, SMB-Session mit serverseitig konfiguriertem User.
76
+ - Wichtiger Hinweis: `view_base_path` ist **keine** Sicherheitsgrenze
77
+ zwischen Usern; für Mandantentrennung entweder Rollen-gated Views oder
78
+ eigene Plugin-Instanzen mit separaten SMB-Usern.
79
+ - Kurzfassung: JavaScript in `public/` ist reine Kosmetik; jeder Client (auch
80
+ ein manipulierter) bekommt dieselben 403/400-Antworten wie ein regulärer.
81
+
82
+ Zusätzlich neuer Abschnitt `## Internationalisierung (i18n)` mit
83
+ Entwickler-Doku (Kataloge ergänzen, API, Locale-Auflösung, Diagnose-Route).
84
+
85
+ ### Packaging
86
+
87
+ - `package.json`: Version 0.4.15 → 0.4.16. `files` ergänzt um `i18n.js` und
88
+ `i18n/`. `lint`-Script prüft jetzt auch `i18n.js` und `public/samba-common.js`.
89
+ - Keine neuen Runtime-Dependencies. Fortlaufend: nur `smb3-client ^0.2.0`.
90
+
91
+ ### Compatibility
92
+
93
+ Rückwärtskompatibel:
94
+
95
+ - Alle bestehenden Routen (`/sambadir`, `/sambafile`, `/sambalink`,
96
+ `/sambaupload`, `/sambadelete`, `/sambarename`, `/sambamkdir`, `/sambatest`)
97
+ unverändert in Signatur und Verhalten.
98
+ - View-Konfigurationsschema unverändert; existierende Views laufen weiter.
99
+ - Wer die Client-JS direkt einbindet, muss zusätzlich `samba-common.js` vor
100
+ `samba-filemanager.js` / `samba-tree.js` laden (das übernimmt die
101
+ View-Shell automatisch).
102
+
103
+ ## [0.4.15] – 2026-07-07
104
+
105
+ ### Fixed – CSRF-Fehlermeldung bei Upload / Ordner anlegen / Rename / Delete
106
+
107
+ Bei jedem schreibenden Klick in der File-Manager-View („Ordner anlegen“,
108
+ „Datei hochladen“, „umbenennen“, „löschen“) erschien der Fehler
109
+ `Invalid CSRF token`, obwohl der Client den Token korrekt im Header
110
+ `X-CSRF-Token` mitgeschickt hat.
111
+
112
+ **Root Cause:** Saltcorns globaler `csurf`-Middleware validiert bereits
113
+ alle POST-Routen der Plugins gegen das Session-Secret. Unser Plugin hatte
114
+ zusätzlich eine manuelle `checkCsrf(req)`-Funktion, die den vom Client
115
+ geschickten Token mit `req.csrfToken()` verglich. `req.csrfToken()` gibt
116
+ aber bei jedem Aufruf einen **frisch gesalzenen** Token zurück – der
117
+ Stringvergleich `provided !== req.csrfToken()` schlug daher immer fehl,
118
+ auch bei völlig gültigen Requests.
119
+
120
+ **Fix:** `checkCsrf` ist jetzt ein dokumentierter No-Op. Die eigentliche
121
+ CSRF-Prüfung übernimmt weiterhin Saltcorns Middleware (kein Sicherheits-
122
+ abbau). Betroffene Write-Routen (Upload, Mkdir, Rename, Delete) in
123
+ `index.js` sind unverändert und funktionieren jetzt korrekt.
124
+
125
+ ### Added – View-Basispfad (`view_base_path`) für File-Manager und Tree
126
+
127
+ Beide Views (SambaFileManager und SambaTree) haben ein neues optionales
128
+ Konfigurationsfeld **„View-Basispfad (relativ zum Plugin-Basispfad)“**.
129
+ Damit lässt sich der Pfad zweistufig konfigurieren:
130
+
131
+ 1. **Plugin-Basispfad** (weiterhin Pflicht) – z. B. `static` – legt fest,
132
+ in welchem Unterverzeichnis der Freigabe der Plugin-Root liegt.
133
+ 2. **View-Basispfad** (neu, optional) – z. B. `projekte/2026` – ein
134
+ statischer Präfix, der nur für diese eine View gilt.
135
+
136
+ Beispiel: Plugin-Basispfad = `static`, View-Basispfad = `projekte/2026`
137
+ → die View listet den effektiven Pfad `static/projekte/2026`.
138
+
139
+ Der bisherige `extra_subpath` in beiden Views ist jetzt konsistent auf
140
+ den `from_field`-Modus beschränkt (dort weiterhin: Suffix hinter dem
141
+ Feldwert der aktuellen Zeile). Im `static`-Modus zählt nur
142
+ `view_base_path`.
143
+
144
+ Der zusammengesetzte Pfad wird pro Segment slash-getrimmt und
145
+ durchläuft `sanitizeRelativePath`, so dass `..`-Traversal, absolute
146
+ Pfade, UNC-Präfixe usw. weiterhin abgelehnt werden.
147
+
148
+ ### Tests
149
+
150
+ `test/sanitize.test.js` deckt jetzt zusätzlich die Pfad-Komposition
151
+ (`view_base_path` + Feldwert + `extra_subpath`) inklusive Slash-
152
+ Normalisierung, Modus-Regeln und Traversal-Ablehnung ab.
153
+
7
154
  ## [0.4.14] – 2026-07-06
8
155
 
9
156
  ### Changed – Code-Review / Aufräumen (keine Verhaltensänderung)
package/README.md CHANGED
@@ -194,9 +194,10 @@ Optionen:
194
194
 
195
195
  | Option | Zweck |
196
196
  |---|---|
197
- | **Root directory mode** | `static` = immer Base-Path aus Plugin-Config, `from_field` = Sub-Pfad kommt aus einem DB-Feld der aktuellen Zeile |
198
- | **Row field with sub-path** | Feld der Tabelle mit dem relativen Ordnernamen (bei `from_field`) |
199
- | **Extra sub-path** | statischer Suffix (z. B. `invoices`) |
197
+ | **View-Basispfad (relativ zum Plugin-Basispfad)** | Optionaler statischer Präfix nur für diese View. Beispiel: Plugin-Basispfad = `static`, View-Basispfad = `projekte/2026` View listet `static/projekte/2026`. Zweistufige Konfiguration: einmal serverweit im Plugin, einmal pro View. |
198
+ | **Row-Modus** | `static` = nur der View-Basispfad zählt, `from_field` = zusätzlich wird ein DB-Feldwert der aktuellen Zeile angehängt (z. B. in einer Show-View) |
199
+ | **Feld mit Unterpfad** | Feld der Tabelle mit dem relativen Ordnernamen (nur bei `from_field`) |
200
+ | **Zusätzlicher Suffix (nach dem Feldwert)** | statischer Suffix, z. B. `invoices` (nur bei `from_field`) |
200
201
  | **Show hidden files** | `.dotfiles` einblenden |
201
202
  | **Allow navigating up** | Up-Button aktivieren (nur bis zum Root, kein Ausbruch) |
202
203
  | **Open PDFs / images inline** | Klick öffnet die Datei im integrierten Viewer |
@@ -204,6 +205,10 @@ Optionen:
204
205
  | **Page size** | Einträge pro Seite (0 = alle) |
205
206
  | **Panel title** | Text im Karten-Header |
206
207
 
208
+ Der effektive Pfad wird zusammengesetzt aus (jeweils falls gesetzt):
209
+ `Plugin-Basispfad / View-Basispfad / Feldwert / Zusätzlicher Suffix`.
210
+ `..`-Traversal, absolute Pfade und UNC-Präfixe werden abgelehnt.
211
+
207
212
  Beispiel-Datenmodell für kunden-spezifische Ordner:
208
213
 
209
214
  ```
@@ -212,12 +217,13 @@ Felder: name (String), akte_dir (String, z. B. "kunde_42/2026")
212
217
  ```
213
218
 
214
219
  Dann eine Show-View von `kunden` bauen und die `SambaFileManager`-View mit
215
- Mode = `from_field`, Row field = `akte_dir` einbetten.
220
+ Row-Modus = `from_field`, Feld = `akte_dir` einbetten.
216
221
 
217
222
  ### View: `SambaTree`
218
223
 
219
224
  Kompaktere Alternative – lazy-loading Baum, ideal in einer Sidebar oder
220
- neben Formularen. Gleiche Path-Modi wie beim File-Manager.
225
+ neben Formularen. Gleiche Path-Optionen wie beim File-Manager (inkl.
226
+ **View-Basispfad**, **Row-Modus**, **Feld**, **Zusätzlicher Suffix**).
221
227
 
222
228
  ### Fieldview `samba_pdf` (deaktiviert seit 0.3.2)
223
229
 
@@ -298,6 +304,70 @@ führende/abschließende Punkte oder Windows-Reserved-Names enthalten.
298
304
 
299
305
  Unit-Tests zum Path- und Filename-Sanitizer laufen in CI (`npm test`).
300
306
 
307
+ ### Client-/Server-Trennung
308
+
309
+ Damit klar ist, wo Sicherheit tatsächlich durchgesetzt wird: das Plugin
310
+ läuft in zwei streng getrennten Welten.
311
+
312
+ - **Serverseitig (Node.js im Saltcorn-Prozess):**
313
+ `index.js` (HTTP-Routen), `smb-client.js` (SMB3-Verbindung, Auth, Signing,
314
+ Encryption), `readdir-compat.js` (Verzeichnis-Listing),
315
+ `filemanager-view.js` / `tree-view.js` / `pdf-view.js`
316
+ (configuration_workflow, run, computeStartPath, sanitizeRelativePath),
317
+ `i18n.js` (Übersetzungskatalog) und `tools/diag-*.js` (CLI-Diagnose).
318
+ **SMB-Credentials (User, Domain, Passwort), Plugin-`base_path` und pro-View
319
+ `view_base_path` liegen ausschließlich serverseitig in der Saltcorn-DB.**
320
+ Der Browser sieht davon nichts.
321
+
322
+ - **Clientseitig (Browser, unter `public/`):**
323
+ `samba-filemanager.js`, `samba-tree.js`, `samba-common.js` (gemeinsame
324
+ Utilities + i18n) und `samba.css`. Reine UI. Alle Zugriffe laufen über
325
+ `fetch()` mit `X-CSRF-Token`-Header.
326
+
327
+ - **Enforcement-Punkte serverseitig — in jeder Request-Bearbeitung:**
328
+ 1. **CSRF** – Saltcorns globale csurf-Middleware. Ohne Token: 403.
329
+ 2. **Auth/Rolle** – Saltcorn prüft Login und View/Table-Rechte vor `run(…)`.
330
+ Zusätzlich prüft jede Route `min_role_read` bzw. `min_role_write`.
331
+ 3. **Pfad-Sanitizing** – `sanitizeRelativePath` lehnt `..`, absolute Pfade,
332
+ UNC-Präfixe, Laufwerksbuchstaben und NUL-Bytes ab.
333
+ 4. **Base-Path-Enforcement** – der effektive Pfad ist immer relativ zum
334
+ serverseitig konfigurierten `base_path`; `view_base_path` stammt aus der
335
+ View-Konfig, nie aus dem Request.
336
+ 5. **SMB-Session** läuft mit dem serverseitig konfigurierten SMB-User; der
337
+ Client kann keine anderen Credentials injizieren.
338
+
339
+ - **Wichtige Hinweise:**
340
+ `view_base_path` ist **keine** Sicherheitsgrenze zwischen Usern – nur
341
+ Bequemlichkeit. Für Mandantentrennung entweder eigene Views pro Mandant
342
+ (mit Rollen-Steuerung), oder mehrere Plugin-Instanzen mit unterschiedlichen
343
+ SMB-Usern. Produktions-Empfehlung: dedizierter SMB-Service-Account mit
344
+ minimalen Rechten; die Fileserver-ACL limitiert dann zusätzlich.
345
+
346
+ - **Kurzfassung:** JavaScript in `public/` ist reine Kosmetik. Alle
347
+ Zugriffsrechte werden serverseitig durchgesetzt — ein manipulierter Client
348
+ bekommt genau die gleichen 403/400-Antworten wie ein regulärer.
349
+
350
+ ---
351
+
352
+ ## Internationalisierung (i18n)
353
+
354
+ Ab v0.4.16 sind alle sichtbaren Texte übersetzbar. Ausgeliefert werden **de**
355
+ und **en**.
356
+
357
+ - Kataloge liegen als JSON unter `i18n/<locale>.json`.
358
+ - Serverseitige Nutzung: `const { t, tFor } = require("./i18n");`
359
+ → `t("fm.upload.button", { locale: "de" })` bzw.
360
+ `const _t = tFor("de"); _t("fm.upload.button")`.
361
+ - Clientseitig: `window.SambaCommon.t("fm.upload.button")`. Der Katalog wird
362
+ von der View-Shell inline in die Seite injiziert (kein Extra-Request).
363
+ - Locale-Auflösung: explizit übergebener Wert → `req.getLocale()` →
364
+ `?locale=xx` → `Accept-Language` → Fallback `en`.
365
+ - Neue Sprache ergänzen: `i18n/<locale>.json` anlegen, `availableLocales` in
366
+ `i18n.js` erweitern, PR aufmachen. Fehlt ein Key, greift automatisch der
367
+ englische Fallback — nichts bricht.
368
+ - Diagnose-Endpoint: `GET /samba-i18n.json?locale=de` liefert den kompletten
369
+ Katalog als JSON (ohne Auth, enthält keine Konfiguration).
370
+
301
371
  ---
302
372
 
303
373
  ## Entwicklung
@@ -18,6 +18,7 @@ const Field = require("@saltcorn/data/models/field");
18
18
  const Table = require("@saltcorn/data/models/table");
19
19
 
20
20
  const { sanitizeRelativePath } = require("./smb-client");
21
+ const { catalogFor, resolveLocaleFromReq } = require("./i18n");
21
22
 
22
23
  // ---------------------------------------------------------------------------
23
24
  // Configuration workflow
@@ -44,31 +45,42 @@ const configuration_workflow = () =>
44
45
  .map((f) => f.name);
45
46
  return new Form({
46
47
  fields: [
48
+ new Field({
49
+ name: "view_base_path",
50
+ label: "View-Basispfad (relativ zum Plugin-Basispfad)",
51
+ type: "String",
52
+ sublabel:
53
+ "Optional. Statischer Pfad relativ zum Plugin-Basispfad, der f\u00fcr diese View gilt. " +
54
+ "Beispiel: Plugin-Basispfad = 'static', View-Basispfad = 'projekte/2026' \u2192 View listet 'static/projekte/2026'. " +
55
+ "Keine f\u00fchrenden/abschlie\u00dfenden Slashes n\u00f6tig. Traversal (\u201e..\u201c) wird abgelehnt.",
56
+ }),
47
57
  new Field({
48
58
  name: "mode",
49
- label: "Root directory mode",
59
+ label: "Row-Modus",
50
60
  type: "String",
51
61
  required: true,
52
62
  attributes: {
53
63
  options: ["static", "from_field"],
54
64
  },
55
65
  sublabel:
56
- "static = always the plugin base_path. from_field = append a row field value to base_path (only when the view is embedded in a Show view).",
66
+ "static = immer nur der View-Basispfad. from_field = h\u00e4nge einen Feldwert der aktuellen Zeile an " +
67
+ "(nur sinnvoll, wenn die View in einer Show-View eingebettet ist).",
57
68
  default: "static",
58
69
  }),
59
70
  new Field({
60
71
  name: "path_field",
61
- label: "Row field with sub-path",
72
+ label: "Feld mit Unterpfad",
62
73
  type: "String",
63
74
  attributes: { options: stringFieldOptions },
64
75
  showIf: { mode: "from_field" },
65
76
  }),
66
77
  new Field({
67
78
  name: "extra_subpath",
68
- label: "Extra sub-path (appended)",
79
+ label: "Zus\u00e4tzlicher Suffix (nach dem Feldwert)",
69
80
  type: "String",
70
81
  sublabel:
71
- "Optional. Static suffix appended after the field value. Example: 'invoices'",
82
+ "Optional. Statischer Suffix, der nach dem Feldwert angeh\u00e4ngt wird. Beispiel: 'invoices'.",
83
+ showIf: { mode: "from_field" },
72
84
  }),
73
85
  new Field({
74
86
  name: "show_hidden",
@@ -122,22 +134,58 @@ async function get_state_fields() {
122
134
  return [];
123
135
  }
124
136
 
137
+ /**
138
+ * Compose the start path (relative to the plugin base_path) from three
139
+ * optional parts:
140
+ * 1. `view_base_path` — static, per-view prefix
141
+ * 2. row field value — only in "from_field" mode
142
+ * 3. `extra_subpath` — static suffix, only in "from_field" mode
143
+ *
144
+ * Empty parts are dropped. The final joined path is passed through
145
+ * sanitizeRelativePath so traversal / absolute paths / backslashes are
146
+ * rejected consistently.
147
+ */
125
148
  function computeStartPath(configuration, row) {
126
149
  const parts = [];
150
+ if (configuration.view_base_path) {
151
+ parts.push(String(configuration.view_base_path));
152
+ }
127
153
  if (configuration.mode === "from_field" && configuration.path_field && row) {
128
154
  const v = row[configuration.path_field];
129
155
  if (v) parts.push(String(v));
130
156
  }
131
- if (configuration.extra_subpath) parts.push(String(configuration.extra_subpath));
132
- return sanitizeRelativePath(parts.join("/"));
157
+ if (
158
+ configuration.mode === "from_field" &&
159
+ configuration.extra_subpath
160
+ ) {
161
+ parts.push(String(configuration.extra_subpath));
162
+ }
163
+ const joined = parts
164
+ .map((p) => p.replace(/^\/+|\/+$/g, ""))
165
+ .filter(Boolean)
166
+ .join("/");
167
+ return sanitizeRelativePath(joined);
133
168
  }
134
169
 
135
170
  // ---------------------------------------------------------------------------
136
171
  // Shell renderer
137
172
  // ---------------------------------------------------------------------------
138
173
 
139
- function renderShell(startPath, configuration, pluginVersion, csrfToken) {
174
+ /**
175
+ * Serialisiert den i18n-Katalog inline in die Seite. Alternative w\u00e4re ein
176
+ * zus\u00e4tzlicher HTTP-Roundtrip auf /samba-i18n.json — inline spart einen
177
+ * Request und rendert die Oberfl\u00e4che ohne Aufblitzen englischer Keys.
178
+ * `</script>` wird escaped, damit das JSON-Blob den HTML-Parser nicht t\u00f6tet.
179
+ */
180
+ function serialiseCatalog(catalog) {
181
+ return JSON.stringify(catalog || {}).replace(/</g, "\\u003c");
182
+ }
183
+
184
+ function renderShell(startPath, configuration, pluginVersion, csrfToken, locale, catalog) {
140
185
  const id = "samba-fm-" + Math.random().toString(36).slice(2, 10);
186
+ // Panel-Titel: wenn der User keinen eigenen Titel gesetzt hat, greift die
187
+ // \u00fcbersetzte Default-Bezeichnung aus dem i18n-Katalog.
188
+ const defaultTitle = (catalog && catalog["fm.title_default"]) || "Samba files";
141
189
  const opts = {
142
190
  startPath,
143
191
  showHidden: !!configuration.show_hidden,
@@ -145,10 +193,16 @@ function renderShell(startPath, configuration, pluginVersion, csrfToken) {
145
193
  pdfInline: configuration.pdf_inline !== false,
146
194
  exposeSmbLink: configuration.expose_smb_link !== false,
147
195
  pageSize: Number(configuration.page_size) || 0,
148
- title: configuration.title || "Samba files",
196
+ title: configuration.title || defaultTitle,
149
197
  csrfToken: csrfToken || "",
198
+ locale: locale || "en",
150
199
  };
151
200
 
201
+ // Zwei-Stufen-Bootstrap:
202
+ // 1. samba-common.js laden (SambaCommon global) und Katalog setzen
203
+ // 2. samba-filemanager.js laden und mounten
204
+ // Wenn beide Skripte bereits geladen sind, wird nur remounted.
205
+ const catalogJson = serialiseCatalog(catalog);
152
206
  return (
153
207
  div(
154
208
  { class: "samba-fm card" },
@@ -167,14 +221,23 @@ function renderShell(startPath, configuration, pluginVersion, csrfToken) {
167
221
  ) +
168
222
  script(
169
223
  `(function(){
170
- function boot(){window.saltcornSambaMountFM && window.saltcornSambaMountFM(${JSON.stringify(
224
+ var CATALOG=${catalogJson};
225
+ function applyCatalog(){ if(window.SambaCommon && window.SambaCommon.setCatalog) window.SambaCommon.setCatalog(CATALOG); }
226
+ function mount(){window.saltcornSambaMountFM && window.saltcornSambaMountFM(${JSON.stringify(
171
227
  id
172
228
  )}, ${JSON.stringify(opts)});}
173
- if(window.saltcornSambaMountFM){boot();return;}
174
- var s=document.createElement('script');
175
- s.src='/plugins/public/saltcorn-samba@${pluginVersion}/samba-filemanager.js';
176
- s.onload=boot;
177
- document.head.appendChild(s);
229
+ function loadFM(){
230
+ if(window.saltcornSambaMountFM){ mount(); return; }
231
+ var s=document.createElement('script');
232
+ s.src='/plugins/public/saltcorn-samba@${pluginVersion}/samba-filemanager.js';
233
+ s.onload=mount;
234
+ document.head.appendChild(s);
235
+ }
236
+ if(window.SambaCommon){ applyCatalog(); loadFM(); return; }
237
+ var c=document.createElement('script');
238
+ c.src='/plugins/public/saltcorn-samba@${pluginVersion}/samba-common.js';
239
+ c.onload=function(){ applyCatalog(); loadFM(); };
240
+ document.head.appendChild(c);
178
241
  })();`
179
242
  )
180
243
  );
@@ -196,9 +259,21 @@ function extractCsrf(extra) {
196
259
  return "";
197
260
  }
198
261
 
262
+ /**
263
+ * Liest die vom Nutzer bevorzugte Sprache aus dem Request (Saltcorn setzt
264
+ * das aus Login-Cookie / Accept-Language) und liefert Locale + Katalog. Bei
265
+ * Ausfall greift `en` als Notnagel – hardgecodete Strings werden nie leer.
266
+ */
267
+ function resolveLocaleAndCatalog(extra) {
268
+ const req = extra && extra.req;
269
+ const locale = resolveLocaleFromReq(req);
270
+ return { locale, catalog: catalogFor(locale) };
271
+ }
272
+
199
273
  async function run(table_id, viewname, configuration, state, extra) {
200
274
  const pluginVersion = configuration.__pluginVersion || "0.3.0";
201
275
  const csrf = extractCsrf(extra);
276
+ const { locale, catalog } = resolveLocaleAndCatalog(extra);
202
277
  let row = extra && extra.row;
203
278
  if (!row && configuration.mode === "from_field" && table_id) {
204
279
  const table = await Table.findOne({ id: table_id });
@@ -216,12 +291,13 @@ async function run(table_id, viewname, configuration, state, extra) {
216
291
  } catch (e) {
217
292
  return div({ class: "alert alert-danger" }, "Samba file manager: " + e.message);
218
293
  }
219
- return renderShell(startPath, configuration, pluginVersion, csrf);
294
+ return renderShell(startPath, configuration, pluginVersion, csrf, locale, catalog);
220
295
  }
221
296
 
222
297
  async function runMany(table_id, viewname, configuration, state, extra) {
223
298
  const pluginVersion = configuration.__pluginVersion || "0.3.0";
224
299
  const csrf = extractCsrf(extra);
300
+ const { locale, catalog } = resolveLocaleAndCatalog(extra);
225
301
  const rows = (extra && extra.rows) || [];
226
302
  return rows.map((row) => {
227
303
  let startPath = "";
@@ -231,7 +307,7 @@ async function runMany(table_id, viewname, configuration, state, extra) {
231
307
  startPath = "";
232
308
  }
233
309
  return {
234
- html: renderShell(startPath, configuration, pluginVersion, csrf),
310
+ html: renderShell(startPath, configuration, pluginVersion, csrf, locale, catalog),
235
311
  row,
236
312
  };
237
313
  });
package/i18n/de.json ADDED
@@ -0,0 +1,93 @@
1
+ {
2
+ "$meta": {
3
+ "language": "Deutsch",
4
+ "locale": "de",
5
+ "note": "Übersetzungskatalog für saltcorn-samba. Punkt-Notation gruppiert nach Bereich."
6
+ },
7
+
8
+ "ui.loading": "Lädt …",
9
+ "ui.close": "Schließen",
10
+ "ui.cancel": "Abbrechen",
11
+ "ui.confirm": "OK",
12
+ "ui.error": "Fehler",
13
+ "ui.empty": "(leeres Verzeichnis)",
14
+ "ui.empty_short": "(leer)",
15
+ "ui.path": "Pfad:",
16
+ "ui.show_hidden": "Versteckte Dateien anzeigen",
17
+ "ui.up": "⬆ Übergeordnet",
18
+ "ui.up_title": "Ein Verzeichnis nach oben",
19
+ "ui.home_title": "Zur Wurzel springen",
20
+ "ui.refresh_title": "Aktualisieren",
21
+ "ui.page_of": "Seite {page} / {total}",
22
+ "ui.prev_page": "‹",
23
+ "ui.next_page": "›",
24
+
25
+ "fm.title_default": "Samba-Dateien",
26
+ "fm.col.filename": "Dateiname",
27
+ "fm.col.media_type": "MIME-Typ",
28
+ "fm.col.size": "Größe",
29
+ "fm.col.modified": "Geändert",
30
+ "fm.col.actions": "Aktionen",
31
+ "fm.counter": "{dirs} Ordner · {files} Dateien",
32
+ "fm.open": "Öffnen",
33
+ "fm.open_new_tab": "In neuem Tab öffnen",
34
+ "fm.view": "Anzeigen",
35
+ "fm.download": "Herunterladen",
36
+ "fm.rename": "Umbenennen",
37
+ "fm.rename_title": "Umbenennen",
38
+ "fm.delete": "Löschen",
39
+ "fm.delete_title": "Löschen",
40
+ "fm.smb_link_title": "Im Datei-Manager öffnen (Nemo/Nautilus/Explorer)",
41
+ "fm.upload": "⬆ Hochladen",
42
+ "fm.upload_title": "Dateien in dieses Verzeichnis hochladen",
43
+ "fm.new_folder": "+ Ordner",
44
+ "fm.new_folder_title": "Neuen Ordner anlegen",
45
+
46
+ "fm.confirm_delete.title": "Löschen bestätigen",
47
+ "fm.confirm_delete.prompt_prefix": "„",
48
+ "fm.confirm_delete.prompt_suffix": "\" wirklich löschen?",
49
+ "fm.confirm_delete.dir_hint": "Klappt nur, wenn das Verzeichnis leer ist.",
50
+ "fm.confirm_delete.file_hint": "Dieser Vorgang kann nicht rückgängig gemacht werden.",
51
+ "fm.deleted_ok": "„{name}\" gelöscht",
52
+ "fm.deleted_fail": "Löschen fehlgeschlagen: {msg}",
53
+
54
+ "fm.rename.title": "„{name}\" umbenennen",
55
+ "fm.rename.label": "Neuer Name",
56
+ "fm.rename.hint": "Slashes sind nicht erlaubt. Zum Verschieben stattdessen die ↗-Funktion nutzen.",
57
+ "fm.renamed_ok": "Umbenannt in „{name}\"",
58
+ "fm.rename_fail": "Umbenennen fehlgeschlagen: {msg}",
59
+
60
+ "fm.mkdir.title": "Ordner anlegen in {path}",
61
+ "fm.mkdir.label": "Ordnername",
62
+ "fm.mkdir.placeholder": "Neuer Ordnername",
63
+ "fm.mkdir.create": "Anlegen",
64
+ "fm.mkdir_ok": "Ordner „{name}\" angelegt",
65
+ "fm.mkdir_fail": "Ordner anlegen fehlgeschlagen: {msg}",
66
+
67
+ "fm.upload.title": "Dateien hochladen",
68
+ "fm.upload.dropzone": "Dateien hierher ziehen oder klicken zum Auswählen",
69
+ "fm.upload.into_prefix": "Ziel: ",
70
+ "fm.upload.limit": ". Max. {mb} MiB pro Datei.",
71
+ "fm.upload.or_pick": "…oder Dateien auswählen",
72
+ "fm.upload.overwrite": "Vorhandene Dateien überschreiben",
73
+ "fm.upload.button": "Hochladen",
74
+ "fm.upload.uploading": "Wird hochgeladen …",
75
+ "fm.upload.no_files": "Keine Dateien ausgewählt",
76
+ "fm.upload.ok": "{n} Datei(en) hochgeladen",
77
+ "fm.upload.partial_summary": "{ok} von {total} Dateien hochgeladen. Fehler:",
78
+ "fm.upload.some_failed": "Einige Uploads fehlgeschlagen",
79
+ "fm.upload.failed": "Upload fehlgeschlagen: {msg}",
80
+ "fm.upload.failed_short": "Upload fehlgeschlagen",
81
+
82
+ "tree.open_in_fm": "Im Datei-Manager öffnen",
83
+ "tree.open_in_fm_title": "Im Datei-Manager öffnen",
84
+ "tree.error_prefix": "Fehler: ",
85
+ "tree.samba_prefix": "Samba: ",
86
+ "tree.open_new_tab": "In neuem Tab öffnen",
87
+
88
+ "pdf.download": "Herunterladen",
89
+ "pdf.open_in_fm": "Im Datei-Manager öffnen",
90
+ "pdf.open_in_fm_title": "In Nemo/Nautilus/Explorer öffnen",
91
+ "pdf.open_new_tab": "In neuem Tab öffnen",
92
+ "pdf.empty": "—"
93
+ }
package/i18n/en.json ADDED
@@ -0,0 +1,93 @@
1
+ {
2
+ "$meta": {
3
+ "language": "English",
4
+ "locale": "en",
5
+ "note": "Translation catalogue for saltcorn-samba. Dot notation groups keys by area."
6
+ },
7
+
8
+ "ui.loading": "Loading…",
9
+ "ui.close": "Close",
10
+ "ui.cancel": "Cancel",
11
+ "ui.confirm": "OK",
12
+ "ui.error": "Error",
13
+ "ui.empty": "(empty directory)",
14
+ "ui.empty_short": "(empty)",
15
+ "ui.path": "Path:",
16
+ "ui.show_hidden": "Show hidden",
17
+ "ui.up": "⬆ Up",
18
+ "ui.up_title": "Up one directory",
19
+ "ui.home_title": "Go to root",
20
+ "ui.refresh_title": "Refresh",
21
+ "ui.page_of": "Page {page} / {total}",
22
+ "ui.prev_page": "‹",
23
+ "ui.next_page": "›",
24
+
25
+ "fm.title_default": "Samba files",
26
+ "fm.col.filename": "Filename",
27
+ "fm.col.media_type": "Media type",
28
+ "fm.col.size": "Size",
29
+ "fm.col.modified": "Modified",
30
+ "fm.col.actions": "Actions",
31
+ "fm.counter": "{dirs} folders · {files} files",
32
+ "fm.open": "Open",
33
+ "fm.open_new_tab": "Open in new tab",
34
+ "fm.view": "View",
35
+ "fm.download": "Download",
36
+ "fm.rename": "Rename",
37
+ "fm.rename_title": "Rename",
38
+ "fm.delete": "Delete",
39
+ "fm.delete_title": "Delete",
40
+ "fm.smb_link_title": "Open in file manager (Nemo/Nautilus/Explorer)",
41
+ "fm.upload": "⬆ Upload",
42
+ "fm.upload_title": "Upload files to this directory",
43
+ "fm.new_folder": "+ Folder",
44
+ "fm.new_folder_title": "Create new folder",
45
+
46
+ "fm.confirm_delete.title": "Confirm delete",
47
+ "fm.confirm_delete.prompt_prefix": "Delete \"",
48
+ "fm.confirm_delete.prompt_suffix": "\"?",
49
+ "fm.confirm_delete.dir_hint": "This will only succeed if the directory is empty.",
50
+ "fm.confirm_delete.file_hint": "This action cannot be undone.",
51
+ "fm.deleted_ok": "Deleted {name}",
52
+ "fm.deleted_fail": "Delete failed: {msg}",
53
+
54
+ "fm.rename.title": "Rename \"{name}\"",
55
+ "fm.rename.label": "New name",
56
+ "fm.rename.hint": "Slashes are not allowed. To move, use the ↗ file-manager link instead.",
57
+ "fm.renamed_ok": "Renamed to {name}",
58
+ "fm.rename_fail": "Rename failed: {msg}",
59
+
60
+ "fm.mkdir.title": "Create folder in {path}",
61
+ "fm.mkdir.label": "Folder name",
62
+ "fm.mkdir.placeholder": "New folder name",
63
+ "fm.mkdir.create": "Create",
64
+ "fm.mkdir_ok": "Created folder {name}",
65
+ "fm.mkdir_fail": "Create folder failed: {msg}",
66
+
67
+ "fm.upload.title": "Upload files",
68
+ "fm.upload.dropzone": "Drop files here or click to select",
69
+ "fm.upload.into_prefix": "Uploading to ",
70
+ "fm.upload.limit": ". Max {mb} MiB per file.",
71
+ "fm.upload.or_pick": "…or pick files",
72
+ "fm.upload.overwrite": "Overwrite existing files",
73
+ "fm.upload.button": "Upload",
74
+ "fm.upload.uploading": "Uploading…",
75
+ "fm.upload.no_files": "No files selected",
76
+ "fm.upload.ok": "Uploaded {n} file(s)",
77
+ "fm.upload.partial_summary": "Uploaded {ok} / {total} files. Failures:",
78
+ "fm.upload.some_failed": "Some uploads failed",
79
+ "fm.upload.failed": "Upload failed: {msg}",
80
+ "fm.upload.failed_short": "Upload failed",
81
+
82
+ "tree.open_in_fm": "Open in file manager",
83
+ "tree.open_in_fm_title": "Open in file manager",
84
+ "tree.error_prefix": "Error: ",
85
+ "tree.samba_prefix": "Samba: ",
86
+ "tree.open_new_tab": "Open in new tab",
87
+
88
+ "pdf.download": "Download",
89
+ "pdf.open_in_fm": "Open in file manager",
90
+ "pdf.open_in_fm_title": "Open in Nemo/Nautilus/Explorer",
91
+ "pdf.open_new_tab": "Open in new tab",
92
+ "pdf.empty": "—"
93
+ }