From 3c8c22ee07e3a91ebafd76d6508e397541993161 Mon Sep 17 00:00:00 2001 From: tolvitty Date: Thu, 12 Feb 2026 12:17:24 +0100 Subject: [PATCH] Localize UI to English across all 22 source files Translates all German user-facing strings (command names, notices, settings, modal labels, template names/descriptions, error messages, status bar, and code comments) to English. Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 107 ++++++++++++++++++++ src/collectors/content-collector.ts | 2 +- src/core/event-bus.ts | 4 +- src/main.ts | 66 ++++++------ src/management/favorites.ts | 28 +++--- src/management/history.ts | 4 +- src/management/templates.ts | 80 +++++++-------- src/projection/formatters.ts | 2 +- src/projection/presets/daily-log.ts | 24 ++--- src/projection/presets/session-log.ts | 22 ++-- src/projection/presets/weekly-digest.ts | 28 +++--- src/projection/projection-engine.ts | 24 ++--- src/query/autocomplete.ts | 4 +- src/query/processor.ts | 16 +-- src/query/query-modal.ts | 44 ++++---- src/ui/event-stream-view.ts | 10 +- src/ui/initial-scan-modal.ts | 20 ++-- src/ui/schema-view.ts | 12 +-- src/ui/settings-tab.ts | 128 ++++++++++++------------ src/ui/status-bar.ts | 2 +- src/viz/chart-renderer.ts | 2 +- src/viz/dashboard.ts | 14 +-- src/viz/keyboard-nav.ts | 4 +- 23 files changed, 377 insertions(+), 270 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5d760cd --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,107 @@ +# Logfire — Obsidian Plugin + +Kombiniertes Plugin aus **Basefire** (SQLite-Query-Engine) und **Logfire** (Event-Logging). +Trackt alle Vault-Aktivitaeten, speichert in SQLite, macht per SQL abfragbar, visualisiert mit Charts/Dashboards. + +## Technischer Stack + +- **SQLite**: `better-sqlite3` (nativ, synchron, schnell) +- **Build**: esbuild mit `nativeModulePlugin` fuer Electron-Kompatibilitaet +- **Desktop-only** (FileSystemAdapter erforderlich) +- **Autor**: tolvitty + +## Architektur + +``` +src/ +├── main.ts # Plugin-Einstieg, Lifecycle +├── types.ts # Event-Typen, Settings, Query-Interfaces +├── core/ +│ ├── database.ts # better-sqlite3, Schema, Retention, Maintenance +│ ├── event-bus.ts # Circular Buffer, Pub/Sub, Auto-Flush +│ ├── session-manager.ts # Session-Start/End, Dauer-Tracking +│ ├── content-analyzer.ts # Snapshot-Cache, Wort-/Link-/Tag-Diffs +│ └── query-builder.ts # QueryConfig → parametrisiertes SQL +├── collectors/ +│ ├── file-collector.ts # File CRUD Events +│ ├── content-collector.ts # Semantische Content-Analyse +│ ├── nav-collector.ts # Navigation-Tracking +│ ├── editor-collector.ts # CM6 ViewPlugin, Debouncing +│ └── system-collector.ts # Command-Tracking +├── query/ +│ ├── processor.ts # Code-Block-Prozessoren (logfire, logfire-sql, logfire-dashboard) +│ ├── query-modal.ts # Interaktiver SQL-Editor (Shorthand + SQL) +│ └── virtual-tables.ts # _files, _links, _tags, _headings +├── viz/ +│ ├── table-renderer.ts # Table, Timeline, Summary, Metric, List, Heatmap +│ ├── chart-renderer.ts # 10 SVG-Chart-Typen (Bar, Line, Pie, Gauge, ...) +│ └── dashboard.ts # Multi-Widget-Dashboards, Grid-Layout +├── management/ +│ ├── history.ts # Automatische Query-History mit Metriken +│ ├── favorites.ts # Gespeicherte Queries, Kategorien, Tags +│ └── templates.ts # Built-in + Custom Templates, Parameter-Substitution +├── projection/ +│ ├── formatters.ts # Query-Results → Markdown (Timeline, Table, Summary, Metric, Heatmap) +│ ├── template-registry.ts # Built-in + Custom ProjectionTemplate Verwaltung +│ ├── projection-engine.ts # Kern-Engine: Scheduling, Session-End-Listener, PickerModal +│ └── presets/ +│ ├── daily-log.ts # Tagesprotokoll-Preset +│ ├── session-log.ts # Session-Protokoll-Preset +│ └── weekly-digest.ts # Wochenuebersicht-Preset +├── ui/ +│ ├── settings-tab.ts # Obsidian-native Settings +│ ├── status-bar.ts # Live-Status-Widget (Recording/Paused) +│ ├── event-stream-view.ts # Echtzeit-Event-Sidebar +│ ├── schema-view.ts # Schema-Browser (Tabellen, Spalten, Indizes) +│ └── initial-scan-modal.ts # Initialer Vault-Scan mit Fortschritt +``` + +## DB-Schema + +**Kern-Tabellen** (database.ts): +- `events` — id, timestamp, type, category, source, target, payload, session +- `sessions` — id, start_time, end_time, vault_name +- `baseline` — file_path, word_count, char_count, links, tags, headings, ... +- `daily_stats` — date, file_path, events_count, words_added/removed, time_active_ms +- `monthly_stats` — wie daily_stats, aggregiert pro Monat + +**Virtual Tables** (virtual-tables.ts): +- `_files` — path, name, basename, extension, size, created, modified, folder +- `_links` — from_path, to_path, display_text, link_type +- `_tags` — path, tag +- `_headings` — path, level, heading + +## Konventionen + +- **Sprache**: Code und Variablennamen auf Englisch, UI-Texte und Commits auf Deutsch +- **Commits**: Kleinschrittig, atomar, deutsch. Niemals pushen (Nutzer pusht manuell) +- **Branching**: Feature-Branches (`feature/`), Merge mit `--no-ff` in `main` +- **CSS**: Ausschliesslich Obsidian-Variablen, Monospace, "Utilitarian System Monitor" Aesthetic +- **Charts**: Reines SVG, keine externen Bibliotheken +- **Queries**: `Record[]` Format (better-sqlite3 Rueckgabe) +- **Storage**: History/Favorites/Templates in localStorage, Plugin-Daten via loadData/saveData + +## Feature-Roadmap + +### Abgeschlossen + +- [x] **Feature 1**: Projekt-Grundgeruest & Datenbank (`feature/grundgeruest`) +- [x] **Feature 2**: Event-System & Collectors (`feature/event-system`) +- [x] **Feature 3**: Echtzeit-UI (`feature/echtzeit-ui`) +- [x] **Feature 4**: SQL-Query-Engine (`feature/sql-engine`) +- [x] **Feature 5**: Virtual Tables (`feature/virtual-tables`) +- [x] **Feature 6**: Datenvisualisierung (`feature/visualisierung`) +- [x] **Feature 7**: Query-Management (`feature/query-management`) +- [x] **Feature 8**: Projections & Reports (`feature/projections`) +- [x] **Feature 9**: Polish & Extras (`feature/polish`) + +## Build & Test + +```bash +npm run build # Production-Build (esbuild) +npm run dev # Watch-Mode +``` + +Build-Output: `main.js` (aktuell ~86KB), `styles.css`, `manifest.json` + +Zum Testen: Plugin-Ordner in `.obsidian/plugins/logfire/` eines Vaults verlinken/kopieren. diff --git a/src/collectors/content-collector.ts b/src/collectors/content-collector.ts index 25cb4fc..01dedf2 100644 --- a/src/collectors/content-collector.ts +++ b/src/collectors/content-collector.ts @@ -92,7 +92,7 @@ export class ContentCollector { }); } } catch (err) { - console.error('[Logfire] ContentCollector-Fehler:', err); + console.error('[Logfire] ContentCollector error:', err); } } diff --git a/src/core/event-bus.ts b/src/core/event-bus.ts index 595655f..7bcec8f 100644 --- a/src/core/event-bus.ts +++ b/src/core/event-bus.ts @@ -49,7 +49,7 @@ export class EventBus { try { this.db.insertEvents(batch); } catch (err) { - console.error('[Logfire] Flush fehlgeschlagen:', err); + console.error('[Logfire] Flush failed:', err); this.buffer.unshift(...batch); } } @@ -93,7 +93,7 @@ export class EventBus { try { cb(event); } catch (err) { - console.error('[Logfire] Subscriber-Fehler:', err); + console.error('[Logfire] Subscriber error:', err); } } } diff --git a/src/main.ts b/src/main.ts index aa9e0f3..125af51 100644 --- a/src/main.ts +++ b/src/main.ts @@ -49,7 +49,7 @@ export default class LogfirePlugin extends Plugin { private paused = false; async onload(): Promise { - console.log('[Logfire] Lade Plugin...'); + console.log('[Logfire] Loading plugin...'); await this.loadSettings(); @@ -125,7 +125,7 @@ export default class LogfirePlugin extends Plugin { this.statusBar = new StatusBar(this); this.statusBar.start(); - // Query: Code-Block-Prozessoren + // Query: Code-block processors registerLogfireBlock(this.db, (lang, handler) => { this.registerMarkdownCodeBlockProcessor(lang, handler); }); @@ -137,7 +137,7 @@ export default class LogfirePlugin extends Plugin { }); // Ribbon icons - this.addRibbonIcon('activity', 'Logfire: Event-Stream', () => { + this.addRibbonIcon('activity', 'Logfire: Event Stream', () => { this.activateEventStream(); }); this.addRibbonIcon('layout-dashboard', 'Logfire: Dashboard', () => { @@ -163,7 +163,7 @@ export default class LogfirePlugin extends Plugin { try { this.db.runMaintenance(this.settings.advanced.retention); } catch (err) { - console.error('[Logfire] Wartung beim Start fehlgeschlagen:', err); + console.error('[Logfire] Startup maintenance failed:', err); } } @@ -175,18 +175,18 @@ export default class LogfirePlugin extends Plugin { this.projectionEngine = new ProjectionEngine(this.app, this.db, this.eventBus, this.settings); this.projectionEngine.start(); - // Autocomplete (nach Virtual Tables) + // Autocomplete (after virtual tables) this.autocomplete = new SqlAutocomplete(this.db); // Keyboard Navigator this.keyboardNav = new KeyboardNavigator(this.app); }); - console.log('[Logfire] Plugin geladen. Session:', this.sessionManager.currentSessionId); + console.log('[Logfire] Plugin loaded. Session:', this.sessionManager.currentSessionId); } async onunload(): Promise { - console.log('[Logfire] Entlade Plugin...'); + console.log('[Logfire] Unloading plugin...'); cleanupAllRefreshTimers(); this.projectionEngine?.destroy(); @@ -206,7 +206,7 @@ export default class LogfirePlugin extends Plugin { this.db.close(); } - console.log('[Logfire] Plugin entladen.'); + console.log('[Logfire] Plugin unloaded.'); } // --------------------------------------------------------------------------- @@ -304,57 +304,57 @@ export default class LogfirePlugin extends Plugin { private registerCommands(): void { this.addCommand({ id: 'show-event-stream', - name: 'Event-Stream anzeigen', + name: 'Show event stream', callback: () => this.activateEventStream(), }); this.addCommand({ id: 'toggle-tracking', - name: 'Tracking pausieren/fortsetzen', + name: 'Toggle tracking', callback: () => { if (this.paused) { this.resume(); - new Notice('Logfire: Tracking fortgesetzt.'); + new Notice('Logfire: Tracking resumed.'); } else { this.pause(); - new Notice('Logfire: Tracking pausiert.'); + new Notice('Logfire: Tracking paused.'); } }, }); this.addCommand({ id: 'rescan-vault', - name: 'Vault erneut scannen', + name: 'Rescan vault', callback: () => this.runInitialScan(), }); this.addCommand({ id: 'run-maintenance', - name: 'Wartung ausführen', + name: 'Run maintenance', callback: () => { this.db.runMaintenance(this.settings.advanced.retention); - new Notice('Logfire: Wartung abgeschlossen.'); + new Notice('Logfire: Maintenance complete.'); }, }); this.addCommand({ id: 'refresh-virtual-tables', - name: 'Virtual Tables neu aufbauen', + name: 'Rebuild virtual tables', callback: () => { this.virtualTables?.rebuild(); - new Notice('Logfire: Virtual Tables aktualisiert.'); + new Notice('Logfire: Virtual tables updated.'); }, }); this.addCommand({ id: 'show-dashboard', - name: 'Dashboard anzeigen', + name: 'Show dashboard', callback: () => this.activateDashboard(), }); this.addCommand({ id: 'open-query', - name: 'Query-Editor \u00f6ffnen', + name: 'Open query editor', callback: () => { new QueryModal(this.app, this.db, this.historyManager, undefined, this.autocomplete).open(); }, @@ -362,13 +362,13 @@ export default class LogfirePlugin extends Plugin { this.addCommand({ id: 'show-schema', - name: 'Schema-Browser anzeigen', + name: 'Show schema browser', callback: () => this.activateSchema(), }); this.addCommand({ id: 'show-templates', - name: 'Query-Templates anzeigen', + name: 'Show query templates', callback: () => { new TemplatePickerModal(this, this.templateManager, (sql) => { new QueryModal(this.app, this.db, this.historyManager, sql, this.autocomplete).open(); @@ -378,7 +378,7 @@ export default class LogfirePlugin extends Plugin { this.addCommand({ id: 'save-favorite', - name: 'Aktuelle Query als Favorit speichern', + name: 'Save current query as favorite', callback: () => { new SaveFavoriteModal(this, this.favoritesManager, '').open(); }, @@ -402,7 +402,7 @@ export default class LogfirePlugin extends Plugin { this.addCommand({ id: 'run-projection', - name: 'Projektion manuell ausführen', + name: 'Run projection', callback: () => { new ProjectionPickerModal(this.app, this.projectionEngine).open(); }, @@ -410,7 +410,7 @@ export default class LogfirePlugin extends Plugin { this.addCommand({ id: 'run-all-projections', - name: 'Alle Projektionen ausführen', + name: 'Run all projections', callback: () => { this.projectionEngine.runAllProjections(); }, @@ -418,33 +418,33 @@ export default class LogfirePlugin extends Plugin { this.addCommand({ id: 'export-csv', - name: 'Letzte Query als CSV exportieren', + name: 'Export last query as CSV', callback: () => { - new Notice('CSV-Export: Bitte Query-Editor öffnen und dort exportieren.'); + new Notice('CSV export: Please open the query editor and export from there.'); }, }); this.addCommand({ id: 'export-json', - name: 'Letzte Query als JSON exportieren', + name: 'Export last query as JSON', callback: () => { - new Notice('JSON-Export: Bitte Query-Editor öffnen und dort exportieren.'); + new Notice('JSON export: Please open the query editor and export from there.'); }, }); this.addCommand({ id: 'toggle-vim-nav', - name: 'Vim-Navigation umschalten', + name: 'Toggle Vim navigation', callback: () => { if (this.keyboardNav?.isActive()) { this.keyboardNav.detach(); - new Notice('Logfire: Vim-Navigation deaktiviert.'); + new Notice('Logfire: Vim navigation disabled.'); } else { const active = document.querySelector('.logfire-qm-results, .logfire-dash-widget-content'); if (active instanceof HTMLElement && this.keyboardNav?.attach(active)) { - new Notice('Logfire: Vim-Navigation aktiviert (j/k/h/l, gg/G, /, y, Enter, Esc).'); + new Notice('Logfire: Vim navigation enabled (j/k/h/l, gg/G, /, y, Enter, Esc).'); } else { - new Notice('Logfire: Keine Tabelle gefunden.'); + new Notice('Logfire: No table found.'); } } }, @@ -522,7 +522,7 @@ export default class LogfirePlugin extends Plugin { private getDatabasePath(): string { const adapter = this.app.vault.adapter; if (!(adapter instanceof FileSystemAdapter)) { - throw new Error('[Logfire] Benötigt einen Desktop-Vault mit Dateisystem-Zugriff.'); + throw new Error('[Logfire] Requires a desktop vault with filesystem access.'); } const basePath = adapter.getBasePath(); return `${basePath}/${this.app.vault.configDir}/plugins/logfire/logfire.db`; diff --git a/src/management/favorites.ts b/src/management/favorites.ts index 09296a4..0c1ad37 100644 --- a/src/management/favorites.ts +++ b/src/management/favorites.ts @@ -25,9 +25,9 @@ export interface FavoriteCategory { const STORAGE_KEY = 'logfire-favorites'; const DEFAULT_CATEGORIES: FavoriteCategory[] = [ - { id: 'allgemein', name: 'Allgemein', color: '#4C78A8' }, - { id: 'analyse', name: 'Analyse', color: '#F58518' }, - { id: 'wartung', name: 'Wartung', color: '#E45756' }, + { id: 'general', name: 'General', color: '#4C78A8' }, + { id: 'analysis', name: 'Analysis', color: '#F58518' }, + { id: 'maintenance', name: 'Maintenance', color: '#E45756' }, ]; // --------------------------------------------------------------------------- @@ -75,7 +75,7 @@ export class FavoritesManager { // Favorites CRUD // --------------------------------------------------------------------------- - add(name: string, sql: string, category = 'allgemein', tags: string[] = []): Favorite { + add(name: string, sql: string, category = 'general', tags: string[] = []): Favorite { const fav: Favorite = { id: `fav-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, name, @@ -154,7 +154,7 @@ export class SaveFavoriteModal extends Modal { private manager: FavoritesManager; private sql: string; private name = ''; - private category = 'allgemein'; + private category = 'general'; private tags: string[] = []; constructor(plugin: LogfirePlugin, manager: FavoritesManager, sql: string) { @@ -167,41 +167,41 @@ export class SaveFavoriteModal extends Modal { onOpen(): void { const { contentEl } = this; contentEl.addClass('logfire-save-fav-modal'); - contentEl.createEl('h3', { text: 'Als Favorit speichern' }); + contentEl.createEl('h3', { text: 'Save as Favorite' }); new Setting(contentEl).setName('Name').addText(text => - text.setPlaceholder('Meine Query').onChange(v => { this.name = v; }), + text.setPlaceholder('My Query').onChange(v => { this.name = v; }), ); - new Setting(contentEl).setName('Kategorie').addDropdown(dd => { + new Setting(contentEl).setName('Category').addDropdown(dd => { for (const cat of this.manager.getCategories()) dd.addOption(cat.id, cat.name); dd.setValue(this.category); dd.onChange(v => { this.category = v; }); }); - new Setting(contentEl).setName('Tags').setDesc('Komma-getrennt').addText(text => + new Setting(contentEl).setName('Tags').setDesc('Comma-separated').addText(text => text.setPlaceholder('select, events').onChange(v => { this.tags = v.split(',').map(s => s.trim()).filter(Boolean); }), ); - contentEl.createEl('h4', { text: 'SQL-Vorschau' }); + contentEl.createEl('h4', { text: 'SQL Preview' }); contentEl.createEl('pre', { cls: 'logfire-sql-preview', text: this.sql.length > 200 ? this.sql.slice(0, 200) + '...' : this.sql, }); const btns = contentEl.createDiv({ cls: 'logfire-modal-buttons' }); - const saveBtn = btns.createEl('button', { text: 'Speichern', cls: 'mod-cta' }); + const saveBtn = btns.createEl('button', { text: 'Save', cls: 'mod-cta' }); saveBtn.addEventListener('click', () => this.doSave()); - const cancelBtn = btns.createEl('button', { text: 'Abbrechen' }); + const cancelBtn = btns.createEl('button', { text: 'Cancel' }); cancelBtn.addEventListener('click', () => this.close()); } private doSave(): void { - if (!this.name.trim()) { new Notice('Bitte einen Namen eingeben.'); return; } + if (!this.name.trim()) { new Notice('Please enter a name.'); return; } this.manager.add(this.name.trim(), this.sql, this.category, this.tags); - new Notice(`Favorit "${this.name}" gespeichert.`); + new Notice(`Favorite "${this.name}" saved.`); this.close(); } diff --git a/src/management/history.ts b/src/management/history.ts index 5e0554a..1a85e86 100644 --- a/src/management/history.ts +++ b/src/management/history.ts @@ -54,7 +54,7 @@ export class HistoryManager { // --------------------------------------------------------------------------- addEntry(sql: string, executionTimeMs?: number, rowCount?: number): HistoryEntry { - // Deduplizieren: gleiche SQL aktualisiert vorhandenen Eintrag + // Deduplicate: same SQL updates existing entry for (const entry of this.entries.values()) { if (entry.sql === sql) { entry.executedAt = new Date().toISOString(); @@ -75,7 +75,7 @@ export class HistoryManager { }; this.entries.set(entry.id, entry); - // Limit: aelteste nicht-favorisierte loeschen + // Limit: delete oldest non-favorited entries if (this.entries.size > MAX_ENTRIES) { const sorted = this.getAll(); for (const e of sorted.slice(MAX_ENTRIES)) { diff --git a/src/management/templates.ts b/src/management/templates.ts index af9aa29..286a5dc 100644 --- a/src/management/templates.ts +++ b/src/management/templates.ts @@ -42,7 +42,7 @@ export class TemplateManager { for (const t of arr) this.templates.set(t.id, t); } catch { /* ignore */ } } - // Immer Built-in Templates sicherstellen + // Ensure built-in templates are always present for (const t of BUILTIN_TEMPLATES) { if (!this.templates.has(t.id)) this.templates.set(t.id, t); } @@ -123,15 +123,15 @@ function parseParameters(sql: string): TemplateParameter[] { } // --------------------------------------------------------------------------- -// Built-in Templates (angepasst an Logfire-Schema) +// Built-in Templates (adapted for Logfire schema) // --------------------------------------------------------------------------- const BUILTIN_TEMPLATES: QueryTemplate[] = [ { id: 'builtin-events-today', - name: 'Events heute', - description: 'Alle Events seit Mitternacht', - sql: `SELECT type, source, datetime(timestamp/1000, 'unixepoch', 'localtime') as zeit + name: 'Events Today', + description: 'All events since midnight', + sql: `SELECT type, source, datetime(timestamp/1000, 'unixepoch', 'localtime') as time FROM events WHERE timestamp > (strftime('%s', 'now', 'start of day') * 1000) ORDER BY timestamp DESC @@ -141,9 +141,9 @@ LIMIT {{limit:100}}`, }, { id: 'builtin-active-files', - name: 'Aktivste Dateien', - description: 'Dateien mit den meisten Events', - sql: `SELECT source as datei, COUNT(*) as events + name: 'Most Active Files', + description: 'Files with the most events', + sql: `SELECT source as file, COUNT(*) as events FROM events WHERE source IS NOT NULL GROUP BY source @@ -155,14 +155,14 @@ LIMIT {{limit:20}}`, { id: 'builtin-session-overview', name: 'Sessions', - description: 'Letzte Sessions mit Dauer', + description: 'Recent sessions with duration', sql: `SELECT id, datetime(start_time/1000, 'unixepoch', 'localtime') as start, CASE WHEN end_time IS NOT NULL THEN ROUND((end_time - start_time) / 60000.0, 1) || ' min' - ELSE 'aktiv' - END as dauer + ELSE 'active' + END as duration FROM sessions ORDER BY start_time DESC LIMIT {{limit:10}}`, @@ -171,23 +171,23 @@ LIMIT {{limit:10}}`, }, { id: 'builtin-daily-stats', - name: 'Tagesstatistik', - description: 'Aggregierte Statistiken pro Tag', - sql: `SELECT date as tag, SUM(events_count) as events, - SUM(words_added) as woerter_hinzu, SUM(words_removed) as woerter_entfernt + name: 'Daily Statistics', + description: 'Aggregated statistics per day', + sql: `SELECT date as day, SUM(events_count) as events, + SUM(words_added) as words_added, SUM(words_removed) as words_removed FROM daily_stats GROUP BY date ORDER BY date DESC LIMIT {{limit:14}}`, - parameters: [{ name: 'limit', label: 'Tage', defaultValue: '14', type: 'number' }], + parameters: [{ name: 'limit', label: 'Days', defaultValue: '14', type: 'number' }], builtIn: true, }, { id: 'builtin-recent-files', - name: 'Zuletzt geaenderte Dateien', - description: 'Dateien nach Aenderungsdatum', + name: 'Recently Modified Files', + description: 'Files by modification date', sql: `SELECT name, extension, - datetime(modified/1000, 'unixepoch', 'localtime') as geaendert, + datetime(modified/1000, 'unixepoch', 'localtime') as modified_at, folder FROM _files WHERE extension = 'md' @@ -198,22 +198,22 @@ LIMIT {{limit:20}}`, }, { id: 'builtin-tag-stats', - name: 'Tag-Statistiken', - description: 'Notizen pro Tag zaehlen', - sql: `SELECT tag, COUNT(DISTINCT path) as notizen + name: 'Tag Statistics', + description: 'Count notes per tag', + sql: `SELECT tag, COUNT(DISTINCT path) as notes FROM _tags GROUP BY tag -ORDER BY notizen DESC +ORDER BY notes DESC LIMIT {{limit:30}}`, parameters: [{ name: 'limit', label: 'Limit', defaultValue: '30', type: 'number' }], builtIn: true, }, { id: 'builtin-orphan-notes', - name: 'Verwaiste Notizen', - description: 'Notizen ohne eingehende Links', + name: 'Orphan Notes', + description: 'Notes without incoming links', sql: `SELECT f.path, f.name, - datetime(f.modified/1000, 'unixepoch', 'localtime') as geaendert + datetime(f.modified/1000, 'unixepoch', 'localtime') as modified_at FROM _files f WHERE f.extension = 'md' AND f.path NOT IN (SELECT DISTINCT to_path FROM _links) @@ -223,9 +223,9 @@ ORDER BY f.modified DESC`, }, { id: 'builtin-broken-links', - name: 'Defekte Links', - description: 'Links zu nicht-existierenden Notizen', - sql: `SELECT l.from_path as von, l.to_path as nach, l.display_text + name: 'Broken Links', + description: 'Links to non-existent notes', + sql: `SELECT l.from_path as from_file, l.to_path as to_file, l.display_text FROM _links l WHERE l.to_path NOT IN (SELECT path FROM _files) AND l.link_type = 'link'`, @@ -234,12 +234,12 @@ WHERE l.to_path NOT IN (SELECT path FROM _files) }, { id: 'builtin-event-types', - name: 'Event-Typen', - description: 'Verteilung der Event-Typen', - sql: `SELECT type as typ, category as kategorie, COUNT(*) as anzahl + name: 'Event Types', + description: 'Distribution of event types', + sql: `SELECT type, category, COUNT(*) as count FROM events GROUP BY type, category -ORDER BY anzahl DESC`, +ORDER BY count DESC`, parameters: [], builtIn: true, }, @@ -262,7 +262,7 @@ export class TemplatePickerModal extends Modal { onOpen(): void { const { contentEl } = this; contentEl.addClass('logfire-template-picker'); - contentEl.createEl('h2', { text: 'Query-Templates' }); + contentEl.createEl('h2', { text: 'Query Templates' }); const list = contentEl.createDiv({ cls: 'logfire-template-list' }); @@ -285,7 +285,7 @@ export class TemplatePickerModal extends Modal { }); const actions = item.createDiv({ cls: 'logfire-template-actions' }); - const useBtn = actions.createEl('button', { text: 'Verwenden', cls: 'mod-cta' }); + const useBtn = actions.createEl('button', { text: 'Use', cls: 'mod-cta' }); useBtn.addEventListener('click', () => { if (tpl.parameters.length > 0) { this.close(); @@ -299,11 +299,11 @@ export class TemplatePickerModal extends Modal { }); if (!tpl.builtIn) { - const delBtn = actions.createEl('button', { text: 'Entfernen' }); + const delBtn = actions.createEl('button', { text: 'Remove' }); delBtn.addEventListener('click', () => { this.manager.delete(tpl.id); item.remove(); - new Notice('Template entfernt.'); + new Notice('Template removed.'); }); } } @@ -334,7 +334,7 @@ class ParameterModal extends Modal { onOpen(): void { const { contentEl } = this; - contentEl.createEl('h3', { text: `Parameter: ${this.template.name}` }); + contentEl.createEl('h3', { text: `Parameters: ${this.template.name}` }); for (const param of this.template.parameters) { new Setting(contentEl).setName(param.label).addText(text => { @@ -346,9 +346,9 @@ class ParameterModal extends Modal { } const btns = contentEl.createDiv({ cls: 'logfire-modal-buttons' }); - const runBtn = btns.createEl('button', { text: 'Ausfuehren', cls: 'mod-cta' }); + const runBtn = btns.createEl('button', { text: 'Execute', cls: 'mod-cta' }); runBtn.addEventListener('click', () => { this.onSubmit(this.values); this.close(); }); - const cancelBtn = btns.createEl('button', { text: 'Abbrechen' }); + const cancelBtn = btns.createEl('button', { text: 'Cancel' }); cancelBtn.addEventListener('click', () => this.close()); } diff --git a/src/projection/formatters.ts b/src/projection/formatters.ts index 85f9eed..844388e 100644 --- a/src/projection/formatters.ts +++ b/src/projection/formatters.ts @@ -6,7 +6,7 @@ import { toMarkdownTable } from '../viz/table-renderer'; // --------------------------------------------------------------------------- export function formatSection(rows: Record[], section: SectionConfig): string { - if (rows.length === 0) return `### ${section.heading}\n\n*Keine Daten.*\n`; + if (rows.length === 0) return `### ${section.heading}\n\n*No data.*\n`; const heading = `### ${section.heading}\n\n`; diff --git a/src/projection/presets/daily-log.ts b/src/projection/presets/daily-log.ts index 7b933b8..4aa210f 100644 --- a/src/projection/presets/daily-log.ts +++ b/src/projection/presets/daily-log.ts @@ -2,8 +2,8 @@ import { ProjectionTemplate } from '../../types'; export const dailyLogTemplate: ProjectionTemplate = { id: 'builtin:daily-log', - name: 'Tagesprotokoll', - description: 'Sessions, aktive Dateien, Events und Zeitleiste des Tages.', + name: 'Daily Log', + description: 'Sessions, active files, events, and timeline of the day.', enabled: false, trigger: { type: 'manual' }, output: { @@ -30,8 +30,8 @@ export const dailyLogTemplate: ProjectionTemplate = { name: 'table', columns: [ { header: 'Session', value: 'session' }, - { header: 'Typ', value: 'type' }, - { header: 'Zeit', value: 'timestamp' }, + { header: 'Type', value: 'type' }, + { header: 'Time', value: 'timestamp' }, ], }, }, @@ -39,7 +39,7 @@ export const dailyLogTemplate: ProjectionTemplate = { }, { id: 'active-files', - heading: 'Aktive Dateien', + heading: 'Active Files', type: 'table', query: { timeRange: { type: 'relative', value: 'today' }, @@ -53,10 +53,10 @@ export const dailyLogTemplate: ProjectionTemplate = { builtin: { name: 'table', columns: [ - { header: 'Datei', value: 'group' }, + { header: 'File', value: 'group' }, { header: 'Events', value: 'count' }, - { header: 'Woerter+', value: 'words_added' }, - { header: 'Woerter-', value: 'words_removed' }, + { header: 'Words+', value: 'words_added' }, + { header: 'Words-', value: 'words_removed' }, ], }, }, @@ -64,7 +64,7 @@ export const dailyLogTemplate: ProjectionTemplate = { }, { id: 'event-summary', - heading: 'Event-Uebersicht', + heading: 'Event Summary', type: 'table', query: { timeRange: { type: 'relative', value: 'today' }, @@ -77,8 +77,8 @@ export const dailyLogTemplate: ProjectionTemplate = { builtin: { name: 'table', columns: [ - { header: 'Typ', value: 'group' }, - { header: 'Anzahl', value: 'count' }, + { header: 'Type', value: 'group' }, + { header: 'Count', value: 'count' }, ], }, }, @@ -86,7 +86,7 @@ export const dailyLogTemplate: ProjectionTemplate = { }, { id: 'timeline', - heading: 'Zeitleiste', + heading: 'Timeline', type: 'timeline', query: { timeRange: { type: 'relative', value: 'today' }, diff --git a/src/projection/presets/session-log.ts b/src/projection/presets/session-log.ts index db76218..b4733be 100644 --- a/src/projection/presets/session-log.ts +++ b/src/projection/presets/session-log.ts @@ -2,8 +2,8 @@ import { ProjectionTemplate } from '../../types'; export const sessionLogTemplate: ProjectionTemplate = { id: 'builtin:session-log', - name: 'Session-Protokoll', - description: 'Einzelne Session: Dauer, bearbeitete Dateien, ausgefuehrte Befehle.', + name: 'Session Log', + description: 'Single session: duration, edited files, executed commands.', enabled: false, trigger: { type: 'on-session-end' }, output: { @@ -19,7 +19,7 @@ export const sessionLogTemplate: ProjectionTemplate = { sections: [ { id: 'overview', - heading: 'Session-Uebersicht', + heading: 'Session Overview', type: 'summary', query: { timeRange: { type: 'session' }, @@ -29,7 +29,7 @@ export const sessionLogTemplate: ProjectionTemplate = { builtin: { name: 'summary', metrics: [ - { label: 'Events gesamt', aggregate: 'count' }, + { label: 'Total events', aggregate: 'count' }, ], }, }, @@ -37,7 +37,7 @@ export const sessionLogTemplate: ProjectionTemplate = { }, { id: 'files', - heading: 'Bearbeitete Dateien', + heading: 'Edited Files', type: 'table', query: { timeRange: { type: 'session' }, @@ -51,10 +51,10 @@ export const sessionLogTemplate: ProjectionTemplate = { builtin: { name: 'table', columns: [ - { header: 'Datei', value: 'group' }, + { header: 'File', value: 'group' }, { header: 'Events', value: 'count' }, - { header: 'Woerter+', value: 'words_added' }, - { header: 'Woerter-', value: 'words_removed' }, + { header: 'Words+', value: 'words_added' }, + { header: 'Words-', value: 'words_removed' }, ], }, }, @@ -62,7 +62,7 @@ export const sessionLogTemplate: ProjectionTemplate = { }, { id: 'commands', - heading: 'Ausgefuehrte Befehle', + heading: 'Executed Commands', type: 'table', query: { timeRange: { type: 'session' }, @@ -74,8 +74,8 @@ export const sessionLogTemplate: ProjectionTemplate = { builtin: { name: 'table', columns: [ - { header: 'Zeit', value: 'timestamp' }, - { header: 'Befehl', value: 'source' }, + { header: 'Time', value: 'timestamp' }, + { header: 'Command', value: 'source' }, ], }, }, diff --git a/src/projection/presets/weekly-digest.ts b/src/projection/presets/weekly-digest.ts index 9fdac2a..4e9f509 100644 --- a/src/projection/presets/weekly-digest.ts +++ b/src/projection/presets/weekly-digest.ts @@ -2,8 +2,8 @@ import { ProjectionTemplate } from '../../types'; export const weeklyDigestTemplate: ProjectionTemplate = { id: 'builtin:weekly-digest', - name: 'Wochen-Digest', - description: 'Wochenuebersicht: Tages-Summary, Top-Dateien, Schreib-Statistiken, Heatmap.', + name: 'Weekly Digest', + description: 'Weekly overview: daily summary, top files, writing statistics, heatmap.', enabled: false, trigger: { type: 'manual' }, output: { @@ -18,7 +18,7 @@ export const weeklyDigestTemplate: ProjectionTemplate = { sections: [ { id: 'daily-summary', - heading: 'Tages-Uebersicht', + heading: 'Daily Overview', type: 'table', query: { timeRange: { type: 'relative', value: 'last-7-days' }, @@ -31,10 +31,10 @@ export const weeklyDigestTemplate: ProjectionTemplate = { builtin: { name: 'table', columns: [ - { header: 'Tag', value: 'group' }, + { header: 'Day', value: 'group' }, { header: 'Events', value: 'count' }, - { header: 'Woerter+', value: 'words_added' }, - { header: 'Woerter-', value: 'words_removed' }, + { header: 'Words+', value: 'words_added' }, + { header: 'Words-', value: 'words_removed' }, ], }, }, @@ -42,7 +42,7 @@ export const weeklyDigestTemplate: ProjectionTemplate = { }, { id: 'top-files', - heading: 'Top-Dateien', + heading: 'Top Files', type: 'table', query: { timeRange: { type: 'relative', value: 'last-7-days' }, @@ -56,9 +56,9 @@ export const weeklyDigestTemplate: ProjectionTemplate = { builtin: { name: 'table', columns: [ - { header: 'Datei', value: 'group' }, + { header: 'File', value: 'group' }, { header: 'Events', value: 'count' }, - { header: 'Woerter+', value: 'words_added' }, + { header: 'Words+', value: 'words_added' }, ], }, }, @@ -66,7 +66,7 @@ export const weeklyDigestTemplate: ProjectionTemplate = { }, { id: 'write-stats', - heading: 'Schreib-Statistiken', + heading: 'Writing Statistics', type: 'summary', query: { timeRange: { type: 'relative', value: 'last-7-days' }, @@ -77,9 +77,9 @@ export const weeklyDigestTemplate: ProjectionTemplate = { builtin: { name: 'summary', metrics: [ - { label: 'Tage aktiv', aggregate: 'count' }, - { label: 'Woerter geschrieben', aggregate: 'sum', field: 'words_added' }, - { label: 'Woerter geloescht', aggregate: 'sum', field: 'words_removed' }, + { label: 'Days active', aggregate: 'count' }, + { label: 'Words written', aggregate: 'sum', field: 'words_added' }, + { label: 'Words deleted', aggregate: 'sum', field: 'words_removed' }, ], }, }, @@ -87,7 +87,7 @@ export const weeklyDigestTemplate: ProjectionTemplate = { }, { id: 'activity-heatmap', - heading: 'Aktivitaets-Heatmap', + heading: 'Activity Heatmap', type: 'chart-data', query: { timeRange: { type: 'relative', value: 'last-7-days' }, diff --git a/src/projection/projection-engine.ts b/src/projection/projection-engine.ts index 01a866a..6ee9d47 100644 --- a/src/projection/projection-engine.ts +++ b/src/projection/projection-engine.ts @@ -32,7 +32,7 @@ export class ProjectionEngine { start(): void { if (!this.settings.projections.enabled) return; - // Session-End-Listener fuer session-log + // Session-end listener for session-log if (this.settings.projections.sessionLog.enabled) { this.sessionEndUnsub = this.eventBus.onEvent('system:session-end', (event) => { const templates = this.registry.getByTrigger('on-session-end'); @@ -42,7 +42,7 @@ export class ProjectionEngine { }); } - // Scheduler fuer daily + weekly (prueft alle 60s) + // Scheduler for daily + weekly (checks every 60s) this.schedulerTimer = setInterval(() => this.checkSchedule(), 60_000); } @@ -75,7 +75,7 @@ export class ProjectionEngine { if (template) this.runProjection(template); } - // Weekly Digest (dayOfWeek: 0=So, 1=Mo, ...) + // Weekly Digest (dayOfWeek: 0=Sun, 1=Mon, ...) if ( this.settings.projections.weeklyDigest.enabled && now.getDay() === this.settings.projections.weeklyDigest.dayOfWeek && @@ -115,7 +115,7 @@ export class ProjectionEngine { const queryConfig = { ...section.query }; - // Session-Kontext einsetzen + // Inject session context if (queryConfig.timeRange.type === 'session' && context?.sessionId) { queryConfig.timeRange = { ...queryConfig.timeRange, sessionId: context.sessionId }; } @@ -133,10 +133,10 @@ export class ProjectionEngine { }); const filePath = `${outputFolder}/${fileName}`; - // Ordner erstellen falls noetig + // Create folder if needed await this.ensureFolder(outputFolder); - // Datei schreiben + // Write file const existing = this.app.vault.getAbstractFileByPath(filePath); if (existing) { if (template.output.mode === 'append') { @@ -151,8 +151,8 @@ export class ProjectionEngine { return filePath; } catch (err) { - console.error('[Logfire] Projektion fehlgeschlagen:', err); - new Notice(`Logfire: Projektion "${template.name}" fehlgeschlagen.`); + console.error('[Logfire] Projection failed:', err); + new Notice(`Logfire: Projection "${template.name}" failed.`); return null; } } @@ -166,7 +166,7 @@ export class ProjectionEngine { if (result) count++; } } - new Notice(`Logfire: ${count} Projektion(en) ausgefuehrt.`); + new Notice(`Logfire: ${count} projection(s) executed.`); } private async ensureFolder(path: string): Promise { @@ -198,7 +198,7 @@ export class ProjectionPickerModal extends Modal { contentEl.empty(); contentEl.addClass('logfire-projection-picker'); - contentEl.createEl('h2', { text: 'Projektion ausfuehren' }); + contentEl.createEl('h2', { text: 'Run Projection' }); const list = contentEl.createDiv({ cls: 'logfire-template-list' }); const templates = this.engine.getRegistry().getAll(); @@ -221,11 +221,11 @@ export class ProjectionPickerModal extends Modal { }); const actions = item.createDiv({ cls: 'logfire-template-actions' }); - const runBtn = actions.createEl('button', { text: 'Ausfuehren', cls: 'mod-cta' }); + const runBtn = actions.createEl('button', { text: 'Execute', cls: 'mod-cta' }); runBtn.addEventListener('click', async () => { const path = await this.engine.runProjection(template); if (path) { - new Notice(`Projektion geschrieben: ${path}`); + new Notice(`Projection written: ${path}`); } this.close(); }); diff --git a/src/query/autocomplete.ts b/src/query/autocomplete.ts index b40011b..6c203e6 100644 --- a/src/query/autocomplete.ts +++ b/src/query/autocomplete.ts @@ -63,7 +63,7 @@ export class SqlAutocomplete { ) as { name: string }[]; this.columnCache.set(table, rows.map(r => r.name)); } catch { - // Virtual tables oder nicht existente Tabellen ueberspringen + // Skip virtual tables or non-existent tables } } } @@ -91,7 +91,7 @@ export class SqlAutocomplete { case 'where': case 'group-by': case 'order-by': { - // Spalten aus aktiven Tabellen + // Columns from active tables const tables = extractTables(textBefore); for (const table of tables) { const cols = this.columnCache.get(table) ?? []; diff --git a/src/query/processor.ts b/src/query/processor.ts index df73888..ad4b71c 100644 --- a/src/query/processor.ts +++ b/src/query/processor.ts @@ -76,14 +76,14 @@ export function registerLogfireSqlBlock( const { sql, refresh } = parseSqlBlock(source); if (!sql) { - renderError(el, new Error('Leere Query.')); + renderError(el, new Error('Empty query.')); return; } // Safety: only SELECT/WITH const firstWord = sql.split(/\s+/)[0].toUpperCase(); if (firstWord !== 'SELECT' && firstWord !== 'WITH') { - renderError(el, new Error('Nur SELECT- und WITH-Queries sind erlaubt.')); + renderError(el, new Error('Only SELECT and WITH queries are allowed.')); return; } @@ -92,7 +92,7 @@ export function registerLogfireSqlBlock( try { const rows = db.queryReadOnly(sql) as Record[]; if (!Array.isArray(rows) || rows.length === 0) { - el.createEl('p', { text: 'Keine Ergebnisse.', cls: 'logfire-empty' }); + el.createEl('p', { text: 'No results.', cls: 'logfire-empty' }); return; } @@ -108,7 +108,7 @@ export function registerLogfireSqlBlock( try { const freshRows = db.queryReadOnly(sql) as Record[]; if (!Array.isArray(freshRows) || freshRows.length === 0) { - el.createEl('p', { text: 'Keine Ergebnisse.', cls: 'logfire-empty' }); + el.createEl('p', { text: 'No results.', cls: 'logfire-empty' }); return; } if (chartConfig) { @@ -139,7 +139,7 @@ export function registerLogfireDashboardBlock( registerFn('logfire-dashboard', (source, el, ctx) => { const dashboard = parseDashboardBlock(source); if (!dashboard) { - renderError(el, new Error('Ungültige Dashboard-Definition.')); + renderError(el, new Error('Invalid dashboard definition.')); return; } @@ -170,7 +170,7 @@ export function registerLogfireDashboardBlock( } else if (widget.sql) { const rows = db.queryReadOnly(widget.sql) as Record[]; if (rows.length === 0) { - content.createDiv({ cls: 'logfire-empty', text: 'Keine Ergebnisse.' }); + content.createDiv({ cls: 'logfire-empty', text: 'No results.' }); } else if (widget.type === 'chart' && widget.chartConfig) { renderChart(content, rows, widget.chartConfig); } else if (widget.type === 'stat') { @@ -182,7 +182,7 @@ export function registerLogfireDashboardBlock( } catch (err) { content.createDiv({ cls: 'logfire-error', - text: `Fehler: ${err instanceof Error ? err.message : String(err)}`, + text: `Error: ${err instanceof Error ? err.message : String(err)}`, }); } } @@ -292,7 +292,7 @@ function parseSqlBlock(source: string): { sql: string; refresh?: number } { function renderResult(el: HTMLElement, rows: Record[], format: string, columns?: string[]): void { if (rows.length === 0) { - el.createEl('p', { text: 'Keine Events gefunden.', cls: 'logfire-empty' }); + el.createEl('p', { text: 'No events found.', cls: 'logfire-empty' }); return; } diff --git a/src/query/query-modal.ts b/src/query/query-modal.ts index ad9b1c1..56aa7e3 100644 --- a/src/query/query-modal.ts +++ b/src/query/query-modal.ts @@ -35,17 +35,17 @@ export class QueryModal extends Modal { // Mode toggle const toolbar = contentEl.createDiv({ cls: 'logfire-qm-toolbar' }); - this.modeToggle = toolbar.createEl('button', { text: 'Modus: Shorthand' }); + this.modeToggle = toolbar.createEl('button', { text: 'Mode: Shorthand' }); this.modeToggle.addEventListener('click', () => { this.mode = this.mode === 'shorthand' ? 'sql' : 'shorthand'; - this.modeToggle.textContent = this.mode === 'shorthand' ? 'Modus: Shorthand' : 'Modus: SQL'; + this.modeToggle.textContent = this.mode === 'shorthand' ? 'Mode: Shorthand' : 'Mode: SQL'; this.editorEl.placeholder = this.mode === 'shorthand' ? 'events today\nstats this-week group by file\nfiles modified yesterday' : 'SELECT * FROM events\nWHERE type = \'file:create\'\nORDER BY timestamp DESC\nLIMIT 50'; }); const helpSpan = toolbar.createEl('span', { - text: 'Ctrl+Enter: Ausf\u00fchren', + text: 'Ctrl+Enter: Execute', cls: 'logfire-qm-hint', }); @@ -87,13 +87,13 @@ export class QueryModal extends Modal { // Buttons const buttonRow = contentEl.createDiv({ cls: 'logfire-qm-buttons' }); - const runBtn = buttonRow.createEl('button', { text: 'Ausf\u00fchren', cls: 'mod-cta' }); + const runBtn = buttonRow.createEl('button', { text: 'Execute', cls: 'mod-cta' }); runBtn.addEventListener('click', () => this.executeQuery()); - const copyBtn = buttonRow.createEl('button', { text: 'Als Markdown kopieren' }); + const copyBtn = buttonRow.createEl('button', { text: 'Copy as Markdown' }); copyBtn.addEventListener('click', () => this.copyAsMarkdown()); - const insertBtn = buttonRow.createEl('button', { text: 'In Notiz einf\u00fcgen' }); + const insertBtn = buttonRow.createEl('button', { text: 'Insert into note' }); insertBtn.addEventListener('click', () => this.insertInNote()); const csvBtn = buttonRow.createEl('button', { text: 'CSV Export' }); @@ -102,7 +102,7 @@ export class QueryModal extends Modal { const jsonBtn = buttonRow.createEl('button', { text: 'JSON Export' }); jsonBtn.addEventListener('click', () => this.exportJson()); - const clearBtn = buttonRow.createEl('button', { text: 'Leeren' }); + const clearBtn = buttonRow.createEl('button', { text: 'Clear' }); clearBtn.addEventListener('click', () => { this.editorEl.value = ''; this.resultEl.empty(); @@ -113,10 +113,10 @@ export class QueryModal extends Modal { // Results this.resultEl = contentEl.createDiv({ cls: 'logfire-qm-results' }); - // Initial SQL setzen + // Set initial SQL if (this.initialSql) { this.mode = 'sql'; - this.modeToggle.textContent = 'Modus: SQL'; + this.modeToggle.textContent = 'Mode: SQL'; this.editorEl.value = this.initialSql; this.editorEl.placeholder = 'SELECT * FROM events\nWHERE type = \'file:create\'\nORDER BY timestamp DESC\nLIMIT 50'; } @@ -147,7 +147,7 @@ export class QueryModal extends Modal { const firstWord = input.split(/\s+/)[0].toUpperCase(); if (firstWord !== 'SELECT' && firstWord !== 'WITH') { this.resultEl.createEl('pre', { - text: 'Nur SELECT- und WITH-Queries sind erlaubt.', + text: 'Only SELECT and WITH queries are allowed.', cls: 'logfire-error', }); return; @@ -170,7 +170,7 @@ export class QueryModal extends Modal { this.renderResults(rows); } catch (err) { this.resultEl.createEl('pre', { - text: `Fehler: ${err instanceof Error ? err.message : String(err)}`, + text: `Error: ${err instanceof Error ? err.message : String(err)}`, cls: 'logfire-error', }); } @@ -180,7 +180,7 @@ export class QueryModal extends Modal { this.resultEl.empty(); if (rows.length === 0) { - this.resultEl.createEl('p', { text: 'Keine Ergebnisse.' }); + this.resultEl.createEl('p', { text: 'No results.' }); return; } @@ -189,7 +189,7 @@ export class QueryModal extends Modal { if (rows.length > 200) { this.resultEl.createEl('p', { - text: `${rows.length} Ergebnisse, 200 angezeigt.`, + text: `${rows.length} results, 200 shown.`, cls: 'logfire-qm-truncated', }); } @@ -197,22 +197,22 @@ export class QueryModal extends Modal { private copyAsMarkdown(): void { if (this.lastRows.length === 0) { - new Notice('Keine Ergebnisse zum Kopieren.'); + new Notice('No results to copy.'); return; } const md = toMarkdownTable(this.lastKeys, this.lastRows); navigator.clipboard.writeText(md); - new Notice('In Zwischenablage kopiert.'); + new Notice('Copied to clipboard.'); } private insertInNote(): void { if (this.lastRows.length === 0) { - new Notice('Keine Ergebnisse zum Einf\u00fcgen.'); + new Notice('No results to insert.'); return; } const view = this.app.workspace.getActiveViewOfType(MarkdownView); if (!view) { - new Notice('Kein aktiver Editor zum Einf\u00fcgen.'); + new Notice('No active editor to insert into.'); return; } const md = toMarkdownTable(this.lastKeys, this.lastRows); @@ -226,7 +226,7 @@ export class QueryModal extends Modal { private exportCsv(): void { if (this.lastRows.length === 0) { - new Notice('Keine Ergebnisse zum Exportieren.'); + new Notice('No results to export.'); return; } const bom = '\uFEFF'; @@ -235,17 +235,17 @@ export class QueryModal extends Modal { this.lastKeys.map(k => csvEscape(row[k])).join(',') ).join('\n'); downloadBlob(bom + header + '\n' + body, 'logfire-export.csv', 'text/csv;charset=utf-8'); - new Notice('CSV exportiert.'); + new Notice('CSV exported.'); } private exportJson(): void { if (this.lastRows.length === 0) { - new Notice('Keine Ergebnisse zum Exportieren.'); + new Notice('No results to export.'); return; } const json = JSON.stringify(this.lastRows, null, 2); downloadBlob(json, 'logfire-export.json', 'application/json'); - new Notice('JSON exportiert.'); + new Notice('JSON exported.'); } // --------------------------------------------------------------------------- @@ -303,7 +303,7 @@ export class QueryModal extends Modal { const before = text.substring(0, cursorPos); const after = text.substring(cursorPos); - // Letztes Wort ersetzen + // Replace last word const lastWordMatch = before.match(/[\w._:-]+$/); const replaceStart = lastWordMatch ? cursorPos - lastWordMatch[0].length : cursorPos; const newBefore = text.substring(0, replaceStart) + suggestion.text; diff --git a/src/ui/event-stream-view.ts b/src/ui/event-stream-view.ts index 72ef44c..32c0579 100644 --- a/src/ui/event-stream-view.ts +++ b/src/ui/event-stream-view.ts @@ -41,7 +41,7 @@ export class EventStreamView extends ItemView { const searchInput = toolbar.createEl('input', { type: 'text', - placeholder: 'Nach Quelle filtern...', + placeholder: 'Filter by source...', cls: 'logfire-stream-search', }); searchInput.addEventListener('input', () => { @@ -54,11 +54,11 @@ export class EventStreamView extends ItemView { const pauseBtn = btnGroup.createEl('button', { text: 'Pause' }); pauseBtn.addEventListener('click', () => { this.viewPaused = !this.viewPaused; - pauseBtn.textContent = this.viewPaused ? 'Weiter' : 'Pause'; + pauseBtn.textContent = this.viewPaused ? 'Resume' : 'Pause'; pauseBtn.toggleClass('is-active', this.viewPaused); }); - const clearBtn = btnGroup.createEl('button', { text: 'Leeren' }); + const clearBtn = btnGroup.createEl('button', { text: 'Clear' }); clearBtn.addEventListener('click', () => { this.entries = []; this.renderList(); @@ -132,9 +132,9 @@ export class EventStreamView extends ItemView { const p = event.payload; switch (event.type) { case 'content:words-changed': - return `+${p.wordsAdded ?? 0}/-${p.wordsRemoved ?? 0} Wörter`; + return `+${p.wordsAdded ?? 0}/-${p.wordsRemoved ?? 0} words`; case 'editor:change': - return `+${p.insertedChars ?? 0}/-${p.deletedChars ?? 0} Zeichen`; + return `+${p.insertedChars ?? 0}/-${p.deletedChars ?? 0} chars`; case 'nav:file-close': return typeof p.duration === 'number' ? `${Math.round(p.duration / 1000)}s` : ''; case 'file:rename': diff --git a/src/ui/initial-scan-modal.ts b/src/ui/initial-scan-modal.ts index 65df147..9619448 100644 --- a/src/ui/initial-scan-modal.ts +++ b/src/ui/initial-scan-modal.ts @@ -39,9 +39,9 @@ export class InitialScanModal extends Modal { const files = this.app.vault.getMarkdownFiles() .filter(f => this.shouldTrack(f.path)); - contentEl.createEl('h2', { text: 'Logfire – Vault-Scan' }); + contentEl.createEl('h2', { text: 'Logfire – Vault Scan' }); contentEl.createEl('p', { - text: `${files.length} Markdown-Dateien gefunden. Der Scan erstellt die Baseline für Content-Tracking.`, + text: `${files.length} Markdown files found. The scan creates the baseline for content tracking.`, }); const progressContainer = contentEl.createDiv({ cls: 'logfire-scan-progress' }); @@ -62,15 +62,15 @@ export class InitialScanModal extends Modal { buttonContainer.style.gap = '8px'; buttonContainer.style.marginTop = '16px'; - const cancelBtn = buttonContainer.createEl('button', { text: 'Abbrechen' }); + const cancelBtn = buttonContainer.createEl('button', { text: 'Cancel' }); cancelBtn.addEventListener('click', () => { this.cancelled = true; }); - const startBtn = buttonContainer.createEl('button', { text: 'Scan starten', cls: 'mod-cta' }); + const startBtn = buttonContainer.createEl('button', { text: 'Start scan', cls: 'mod-cta' }); startBtn.addEventListener('click', async () => { startBtn.disabled = true; - cancelBtn.textContent = 'Stoppen'; + cancelBtn.textContent = 'Stop'; let scanned = 0; let totalWords = 0; @@ -103,9 +103,9 @@ export class InitialScanModal extends Modal { progressBar.value = scanned; currentFileEl.textContent = file.path; - totalsEl.textContent = `${scanned}/${files.length} Dateien | ${totalWords.toLocaleString()} Wörter | ${totalLinks} Links | ${totalTags} Tags`; + totalsEl.textContent = `${scanned}/${files.length} files | ${totalWords.toLocaleString()} words | ${totalLinks} links | ${totalTags} tags`; } catch (err) { - console.error(`[Logfire] Scan-Fehler bei ${file.path}:`, err); + console.error(`[Logfire] Scan error at ${file.path}:`, err); } } @@ -129,11 +129,11 @@ export class InitialScanModal extends Modal { }); currentFileEl.textContent = this.cancelled - ? `Scan gestoppt. ${scanned} von ${files.length} Dateien gescannt.` - : `Scan abgeschlossen! ${scanned} Dateien gescannt.`; + ? `Scan stopped. ${scanned} of ${files.length} files scanned.` + : `Scan complete! ${scanned} files scanned.`; cancelBtn.style.display = 'none'; - const closeBtn = buttonContainer.createEl('button', { text: 'Schließen', cls: 'mod-cta' }); + const closeBtn = buttonContainer.createEl('button', { text: 'Close', cls: 'mod-cta' }); closeBtn.addEventListener('click', () => { this.close(); this.resolve(true); diff --git a/src/ui/schema-view.ts b/src/ui/schema-view.ts index a2b844a..bd9e10b 100644 --- a/src/ui/schema-view.ts +++ b/src/ui/schema-view.ts @@ -46,7 +46,7 @@ export class SchemaView extends ItemView { } getDisplayText(): string { - return 'Schema-Browser'; + return 'Schema Browser'; } getIcon(): string { @@ -63,7 +63,7 @@ export class SchemaView extends ItemView { const refreshBtn = header.createEl('button', { cls: 'logfire-dash-btn clickable-icon', - attr: { 'aria-label': 'Aktualisieren' }, + attr: { 'aria-label': 'Refresh' }, text: '\u21bb', }); refreshBtn.addEventListener('click', () => this.refresh()); @@ -77,7 +77,7 @@ export class SchemaView extends ItemView { const tables = this.introspect(); if (tables.length === 0) { - this.contentContainer.createDiv({ cls: 'logfire-empty', text: 'Keine Tabellen gefunden.' }); + this.contentContainer.createDiv({ cls: 'logfire-empty', text: 'No tables found.' }); return; } @@ -144,11 +144,11 @@ export class SchemaView extends ItemView { this.refresh(); }); - // Context menu: SQL kopieren + // Context menu: copy SQL header.addEventListener('contextmenu', (e) => { e.preventDefault(); navigator.clipboard.writeText(`SELECT * FROM "${table.name}" LIMIT 100;`); - new Notice(`SELECT auf "${table.name}" kopiert.`); + new Notice(`SELECT for "${table.name}" copied.`); }); if (!isExpanded) return; @@ -166,7 +166,7 @@ export class SchemaView extends ItemView { // Indexes if (table.indexes.length > 0) { - details.createDiv({ cls: 'logfire-schema-section-header', text: 'Indizes' }); + details.createDiv({ cls: 'logfire-schema-section-header', text: 'Indexes' }); for (const idx of table.indexes) { const row = details.createDiv({ cls: 'logfire-schema-idx' }); row.createSpan({ text: `${idx.unique ? 'UNIQUE ' : ''}${idx.name}` }); diff --git a/src/ui/settings-tab.ts b/src/ui/settings-tab.ts index b87d19e..be94f27 100644 --- a/src/ui/settings-tab.ts +++ b/src/ui/settings-tab.ts @@ -11,11 +11,11 @@ export class LogfireSettingTab extends PluginSettingTab { containerEl.empty(); // ----- General ----- - containerEl.createEl('h2', { text: 'Allgemein' }); + containerEl.createEl('h2', { text: 'General' }); new Setting(containerEl) - .setName('Tracking aktiviert') - .setDesc('Hauptschalter für alle Event-Collector.') + .setName('Tracking enabled') + .setDesc('Master switch for all event collectors.') .addToggle(t => t .setValue(this.plugin.settings.general.enabled) .onChange(async (v) => { @@ -24,8 +24,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(containerEl) - .setName('Log-Ordner') - .setDesc('Ordner für Markdown-Projektionen. Wird automatisch vom Tracking ausgeschlossen.') + .setName('Log folder') + .setDesc('Folder for Markdown projections. Automatically excluded from tracking.') .addText(t => t .setPlaceholder('Logfire') .setValue(this.plugin.settings.general.logFolder) @@ -35,8 +35,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(containerEl) - .setName('Beim Start pausieren') - .setDesc('Startet im pausierten Zustand — keine Events bis manuell fortgesetzt.') + .setName('Pause on startup') + .setDesc('Starts in paused state — no events until manually resumed.') .addToggle(t => t .setValue(this.plugin.settings.general.pauseOnStartup) .onChange(async (v) => { @@ -48,8 +48,8 @@ export class LogfireSettingTab extends PluginSettingTab { containerEl.createEl('h2', { text: 'Tracking' }); new Setting(containerEl) - .setName('Datei-Events') - .setDesc('Erstellen, Löschen, Umbenennen, Verschieben und Ändern von Dateien.') + .setName('File events') + .setDesc('Create, delete, rename, move, and modify files.') .addToggle(t => t .setValue(this.plugin.settings.tracking.fileEvents) .onChange(async (v) => { @@ -58,8 +58,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(containerEl) - .setName('Content-Analyse') - .setDesc('Semantische Diffs: Wortzählung, Links, Tags, Überschriften, Frontmatter.') + .setName('Content analysis') + .setDesc('Semantic diffs: word count, links, tags, headings, frontmatter.') .addToggle(t => t .setValue(this.plugin.settings.tracking.contentAnalysis) .onChange(async (v) => { @@ -69,7 +69,7 @@ export class LogfireSettingTab extends PluginSettingTab { new Setting(containerEl) .setName('Navigation') - .setDesc('Aktive Datei-Wechsel, Datei öffnen/schließen mit Dauer.') + .setDesc('Active file switches, file open/close with duration.') .addToggle(t => t .setValue(this.plugin.settings.tracking.navigation) .onChange(async (v) => { @@ -78,8 +78,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(containerEl) - .setName('Editor-Änderungen') - .setDesc('Tastenanschläge (debounced und aggregiert).') + .setName('Editor changes') + .setDesc('Keystrokes (debounced and aggregated).') .addToggle(t => t .setValue(this.plugin.settings.tracking.editorChanges) .onChange(async (v) => { @@ -88,8 +88,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(containerEl) - .setName('Kommando-Tracking') - .setDesc('Ausführung von Befehlen über Palette und Hotkeys.') + .setName('Command tracking') + .setDesc('Execution of commands via palette and hotkeys.') .addToggle(t => t .setValue(this.plugin.settings.tracking.commandTracking) .onChange(async (v) => { @@ -98,8 +98,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(containerEl) - .setName('Ausgeschlossene Ordner') - .setDesc('Komma-getrennte Liste von Ordnern, die nicht getrackt werden.') + .setName('Excluded folders') + .setDesc('Comma-separated list of folders to exclude from tracking.') .addText(t => t .setPlaceholder('.obsidian, templates') .setValue(this.plugin.settings.tracking.excludedFolders.join(', ')) @@ -112,8 +112,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(containerEl) - .setName('Ausgeschlossene Patterns') - .setDesc('Komma-getrennte Glob-Patterns (z.B. **/*.excalidraw.md).') + .setName('Excluded patterns') + .setDesc('Comma-separated glob patterns (e.g. **/*.excalidraw.md).') .addText(t => t .setPlaceholder('**/*.excalidraw.md') .setValue(this.plugin.settings.tracking.excludedPatterns.join(', ')) @@ -126,11 +126,11 @@ export class LogfireSettingTab extends PluginSettingTab { })); // ----- Projections ----- - containerEl.createEl('h2', { text: 'Projektionen' }); + containerEl.createEl('h2', { text: 'Projections' }); new Setting(containerEl) - .setName('Projektionen aktiviert') - .setDesc('Automatische Markdown-Reports aus Event-Daten.') + .setName('Projections enabled') + .setDesc('Automatic Markdown reports from event data.') .addToggle(t => t .setValue(this.plugin.settings.projections.enabled) .onChange(async (v) => { @@ -139,8 +139,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(containerEl) - .setName('Output-Ordner') - .setDesc('Ordner für generierte Projektions-Dateien.') + .setName('Output folder') + .setDesc('Folder for generated projection files.') .addText(t => t .setPlaceholder('Logfire') .setValue(this.plugin.settings.projections.outputFolder) @@ -150,8 +150,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(containerEl) - .setName('Tagesprotokoll') - .setDesc('Automatisches Tagesprotokoll generieren.') + .setName('Daily log') + .setDesc('Generate automatic daily log.') .addToggle(t => t .setValue(this.plugin.settings.projections.dailyLog.enabled) .onChange(async (v) => { @@ -169,8 +169,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(containerEl) - .setName('Session-Protokoll') - .setDesc('Protokoll bei Session-Ende generieren.') + .setName('Session log') + .setDesc('Generate log on session end.') .addToggle(t => t .setValue(this.plugin.settings.projections.sessionLog.enabled) .onChange(async (v) => { @@ -179,8 +179,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(containerEl) - .setName('Wochen-Digest') - .setDesc('Wöchentliche Zusammenfassung generieren.') + .setName('Weekly digest') + .setDesc('Generate weekly summary.') .addToggle(t => t .setValue(this.plugin.settings.projections.weeklyDigest.enabled) .onChange(async (v) => { @@ -189,8 +189,8 @@ export class LogfireSettingTab extends PluginSettingTab { })) .addDropdown(d => d .addOptions({ - '0': 'Sonntag', '1': 'Montag', '2': 'Dienstag', '3': 'Mittwoch', - '4': 'Donnerstag', '5': 'Freitag', '6': 'Samstag', + '0': 'Sunday', '1': 'Monday', '2': 'Tuesday', '3': 'Wednesday', + '4': 'Thursday', '5': 'Friday', '6': 'Saturday', }) .setValue(String(this.plugin.settings.projections.weeklyDigest.dayOfWeek)) .onChange(async (v) => { @@ -200,12 +200,12 @@ export class LogfireSettingTab extends PluginSettingTab { // ----- Advanced (collapsible) ----- const advancedHeader = containerEl.createEl('details'); - advancedHeader.createEl('summary', { text: 'Erweiterte Einstellungen' }) + advancedHeader.createEl('summary', { text: 'Advanced Settings' }) .style.cursor = 'pointer'; const advEl = advancedHeader.createDiv(); advEl.createEl('p', { - text: 'Diese Einstellungen beeinflussen Performance und Speicher. Die Standardwerte sind für die meisten Vaults optimal.', + text: 'These settings affect performance and storage. The defaults are optimal for most vaults.', cls: 'setting-item-description', }); @@ -213,8 +213,8 @@ export class LogfireSettingTab extends PluginSettingTab { advEl.createEl('h3', { text: 'Performance' }); new Setting(advEl) - .setName('Editor-Debounce (ms)') - .setDesc('Wartezeit nach letztem Tastenanschlag vor editor:change Event.') + .setName('Editor debounce (ms)') + .setDesc('Wait time after last keystroke before editor:change event.') .addText(t => t .setValue(String(this.plugin.settings.advanced.debounceMs)) .onChange(async (v) => { @@ -226,8 +226,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(advEl) - .setName('Flush-Intervall (ms)') - .setDesc('Wie oft gepufferte Events in die Datenbank geschrieben werden.') + .setName('Flush interval (ms)') + .setDesc('How often buffered events are written to the database.') .addText(t => t .setValue(String(this.plugin.settings.advanced.flushIntervalMs)) .onChange(async (v) => { @@ -239,8 +239,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(advEl) - .setName('Flush-Schwellwert') - .setDesc('Maximale Events im Puffer vor erzwungenem Schreiben.') + .setName('Flush threshold') + .setDesc('Maximum events in buffer before forced write.') .addText(t => t .setValue(String(this.plugin.settings.advanced.flushThreshold)) .onChange(async (v) => { @@ -252,11 +252,11 @@ export class LogfireSettingTab extends PluginSettingTab { })); // Retention - advEl.createEl('h3', { text: 'Aufbewahrung' }); + advEl.createEl('h3', { text: 'Retention' }); new Setting(advEl) - .setName('Raw-Events aufbewahren (Tage)') - .setDesc('Danach werden Events in Tages-Statistiken aggregiert und gelöscht.') + .setName('Keep raw events (days)') + .setDesc('After this, events are aggregated into daily stats and deleted.') .addText(t => t .setValue(String(this.plugin.settings.advanced.retention.rawEventsDays)) .onChange(async (v) => { @@ -268,8 +268,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(advEl) - .setName('Tages-Statistiken aufbewahren (Tage)') - .setDesc('Danach werden Tages-Statistiken in Monats-Statistiken aggregiert.') + .setName('Keep daily stats (days)') + .setDesc('After this, daily stats are aggregated into monthly stats.') .addText(t => t .setValue(String(this.plugin.settings.advanced.retention.dailyStatsDays)) .onChange(async (v) => { @@ -281,8 +281,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(advEl) - .setName('Wartung beim Start') - .setDesc('Automatisch Retention-Cleanup beim Plugin-Start ausführen.') + .setName('Maintenance on startup') + .setDesc('Automatically run retention cleanup on plugin start.') .addToggle(t => t .setValue(this.plugin.settings.advanced.retention.maintenanceOnStartup) .onChange(async (v) => { @@ -291,11 +291,11 @@ export class LogfireSettingTab extends PluginSettingTab { })); // Database settings - advEl.createEl('h3', { text: 'Datenbank' }); + advEl.createEl('h3', { text: 'Database' }); new Setting(advEl) - .setName('WAL-Modus') - .setDesc('Write-Ahead-Logging für bessere Parallelität (Neustart erforderlich).') + .setName('WAL mode') + .setDesc('Write-Ahead Logging for better concurrency (restart required).') .addToggle(t => t .setValue(this.plugin.settings.advanced.database.walMode) .onChange(async (v) => { @@ -304,8 +304,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(advEl) - .setName('Cache-Größe (MB)') - .setDesc('SQLite Page-Cache (8–256 MB). Mehr Cache = schnellere Queries.') + .setName('Cache size (MB)') + .setDesc('SQLite page cache (8–256 MB). More cache = faster queries.') .addSlider(s => s .setLimits(8, 256, 8) .setValue(this.plugin.settings.advanced.database.cacheSizeMb) @@ -316,8 +316,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(advEl) - .setName('MMap-Größe (MB)') - .setDesc('Memory-Mapped I/O (0–1024 MB). 0 deaktiviert MMap.') + .setName('MMap size (MB)') + .setDesc('Memory-Mapped I/O (0–1024 MB). 0 disables MMap.') .addSlider(s => s .setLimits(0, 1024, 32) .setValue(this.plugin.settings.advanced.database.mmapSizeMb) @@ -328,8 +328,8 @@ export class LogfireSettingTab extends PluginSettingTab { })); new Setting(advEl) - .setName('Geschützte Event-Typen') - .setDesc('Event-Typen die nie gelöscht werden (komma-getrennt).') + .setName('Protected event types') + .setDesc('Event types that are never deleted (comma-separated).') .addText(t => t .setPlaceholder('file:create, file:delete, file:rename') .setValue(this.plugin.settings.advanced.retention.neverDeleteTypes.join(', ')) @@ -349,10 +349,10 @@ export class LogfireSettingTab extends PluginSettingTab { const oldest = this.plugin.db.getOldestEventTimestamp(); dbInfoEl.createEl('p', { - text: `Events: ${eventCount.toLocaleString()} | Größe: ${formatBytes(dbSize)} | Ältestes: ${oldest ? new Date(oldest).toLocaleDateString() : 'k.A.'}`, + text: `Events: ${eventCount.toLocaleString()} | Size: ${formatBytes(dbSize)} | Oldest: ${oldest ? new Date(oldest).toLocaleDateString() : 'N/A'}`, }); } catch { - dbInfoEl.createEl('p', { text: 'Datenbank-Info nicht verfügbar.' }); + dbInfoEl.createEl('p', { text: 'Database info unavailable.' }); } // Info section @@ -362,17 +362,17 @@ export class LogfireSettingTab extends PluginSettingTab { }); new Setting(advEl) - .setName('Datenbank-Aktionen') + .setName('Database actions') .addButton(b => b - .setButtonText('Wartung ausführen') + .setButtonText('Run maintenance') .onClick(() => { try { this.plugin.db.runMaintenance(this.plugin.settings.advanced.retention); - new Notice('Logfire: Wartung abgeschlossen.'); + new Notice('Logfire: Maintenance complete.'); this.display(); } catch (err) { - new Notice('Logfire: Wartung fehlgeschlagen.'); - console.error('[Logfire] Wartung fehlgeschlagen:', err); + new Notice('Logfire: Maintenance failed.'); + console.error('[Logfire] Maintenance failed:', err); } })); } diff --git a/src/ui/status-bar.ts b/src/ui/status-bar.ts index 4a2285d..f7d5c8e 100644 --- a/src/ui/status-bar.ts +++ b/src/ui/status-bar.ts @@ -36,7 +36,7 @@ export class StatusBar { private update(): void { const paused = this.plugin.isPaused(); const indicator = paused ? '\u23F8' : '\u{1F534}'; - const state = paused ? 'Pausiert' : 'Aufnahme'; + const state = paused ? 'Paused' : 'Recording'; const duration = this.formatDuration(this.plugin.sessionManager.sessionDurationMs); const words = this.wordsAdded > 0 ? ` | +${this.wordsAdded}w` : ''; diff --git a/src/viz/chart-renderer.ts b/src/viz/chart-renderer.ts index 7fa6797..643bd9d 100644 --- a/src/viz/chart-renderer.ts +++ b/src/viz/chart-renderer.ts @@ -36,7 +36,7 @@ export function renderChart( config: ChartConfig, ): void { if (rows.length === 0) { - el.createEl('p', { text: 'Keine Daten.', cls: 'logfire-empty' }); + el.createEl('p', { text: 'No data.', cls: 'logfire-empty' }); return; } diff --git a/src/viz/dashboard.ts b/src/viz/dashboard.ts index 1140d5d..b28232f 100644 --- a/src/viz/dashboard.ts +++ b/src/viz/dashboard.ts @@ -71,7 +71,7 @@ export class DashboardView extends ItemView { const refreshBtn = actions.createEl('button', { cls: 'logfire-dash-btn clickable-icon', - attr: { 'aria-label': 'Aktualisieren' }, + attr: { 'aria-label': 'Refresh' }, text: '\u21bb', }); refreshBtn.addEventListener('click', () => this.refreshAll()); @@ -121,7 +121,7 @@ export class DashboardView extends ItemView { this.contentContainer.empty(); this.contentContainer.createDiv({ cls: 'logfire-empty', - text: 'Kein Dashboard geladen.', + text: 'No dashboard loaded.', }); } @@ -170,19 +170,19 @@ export class DashboardView extends ItemView { } catch (err) { container.createDiv({ cls: 'logfire-error', - text: `Fehler: ${err instanceof Error ? err.message : String(err)}`, + text: `Error: ${err instanceof Error ? err.message : String(err)}`, }); } } private renderQueryWidget(container: HTMLElement, widget: DashboardWidget): void { if (!widget.sql) { - container.createDiv({ cls: 'logfire-empty', text: 'Keine Query konfiguriert.' }); + container.createDiv({ cls: 'logfire-empty', text: 'No query configured.' }); return; } const rows = this.db.queryReadOnly(widget.sql) as Record[]; if (rows.length === 0) { - container.createDiv({ cls: 'logfire-empty', text: 'Keine Ergebnisse.' }); + container.createDiv({ cls: 'logfire-empty', text: 'No results.' }); return; } renderTable(container, rows); @@ -190,7 +190,7 @@ export class DashboardView extends ItemView { private renderChartWidget(container: HTMLElement, widget: DashboardWidget): void { if (!widget.sql || !widget.chartConfig) { - container.createDiv({ cls: 'logfire-empty', text: 'Kein Chart konfiguriert.' }); + container.createDiv({ cls: 'logfire-empty', text: 'No chart configured.' }); return; } const rows = this.db.queryReadOnly(widget.sql) as Record[]; @@ -199,7 +199,7 @@ export class DashboardView extends ItemView { private renderStatWidget(container: HTMLElement, widget: DashboardWidget): void { if (!widget.sql) { - container.createDiv({ cls: 'logfire-empty', text: 'Keine Query konfiguriert.' }); + container.createDiv({ cls: 'logfire-empty', text: 'No query configured.' }); return; } const rows = this.db.queryReadOnly(widget.sql) as Record[]; diff --git a/src/viz/keyboard-nav.ts b/src/viz/keyboard-nav.ts index 29981cf..04d220d 100644 --- a/src/viz/keyboard-nav.ts +++ b/src/viz/keyboard-nav.ts @@ -154,7 +154,7 @@ export class KeyboardNavigator { navigator.clipboard.writeText(td.textContent ?? ''); td.classList.add('logfire-vim-yanked'); setTimeout(() => td.classList.remove('logfire-vim-yanked'), 600); - new Notice('In Zwischenablage kopiert.'); + new Notice('Copied to clipboard.'); } // --------------------------------------------------------------------------- @@ -225,7 +225,7 @@ export class KeyboardNavigator { return; } } - new Notice('Nicht gefunden.'); + new Notice('Not found.'); } }