From d4b0213a55834b2f976ac76c1adbf99b3bb0783a Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Fri, 24 Apr 2026 18:34:01 +0200 Subject: [PATCH] feat: more UI cleanup --- lib/bds/ui/shell_page.ex | 7 +- lib/bds/ui/workbench.ex | 4 +- priv/i18n/locales/de.json | 119 +++++++++++++++++++- priv/i18n/locales/en.json | 119 +++++++++++++++++++- priv/ui/app.js | 223 ++++++++++++++++++++----------------- test/bds/ui/shell_test.exs | 40 +++++++ 6 files changed, 407 insertions(+), 105 deletions(-) diff --git a/lib/bds/ui/shell_page.ex b/lib/bds/ui/shell_page.ex index af942a7..148c148 100644 --- a/lib/bds/ui/shell_page.ex +++ b/lib/bds/ui/shell_page.ex @@ -10,10 +10,11 @@ defmodule BDS.UI.ShellPage do def render do bootstrap = bootstrap() + ui_language = get_in(bootstrap, [:i18n, :ui_language]) || "en" [ "", - "", + "", "", " ", " ", @@ -58,6 +59,10 @@ defmodule BDS.UI.ShellPage do title: Application.get_env(:bds, :desktop)[:title] || "Blogging Desktop Server", i18n: %{ ui_language: ui_language, + catalogs: + Enum.into(I18n.supported_languages(), %{}, fn language -> + {language.code, I18n.get_ui_translations(language.code)} + end), supported_ui_languages: Enum.map(I18n.supported_languages(), fn language -> %{ diff --git a/lib/bds/ui/workbench.ex b/lib/bds/ui/workbench.ex index 2a44a52..c97026d 100644 --- a/lib/bds/ui/workbench.ex +++ b/lib/bds/ui/workbench.ex @@ -183,7 +183,9 @@ defmodule BDS.UI.Workbench do right: %{ post_status: post_status(state, Keyword.get(opts, :active_post_status)), post_count: "#{Keyword.get(opts, :post_count, 0)} posts", + post_count_value: Keyword.get(opts, :post_count, 0), media_count: "#{Keyword.get(opts, :media_count, 0)} media", + media_count_value: Keyword.get(opts, :media_count, 0), token_usage: token_usage(state, Keyword.get(opts, :token_usage)), theme_badge: Keyword.get(opts, :theme_badge, "default"), offline_mode: Keyword.get(opts, :offline_mode, false), @@ -265,4 +267,4 @@ defmodule BDS.UI.Workbench do defp clamp_sidebar_width(width), do: max(200, min(width, 500)) defp clamp_assistant_sidebar_width(width), do: max(280, min(width, 640)) -end \ No newline at end of file +end diff --git a/priv/i18n/locales/de.json b/priv/i18n/locales/de.json index e6d968b..212dcec 100644 --- a/priv/i18n/locales/de.json +++ b/priv/i18n/locales/de.json @@ -44,5 +44,122 @@ "render.taxonomy.ariaLabel": "Taxonomie", "render.video.vimeoTitle": "Vimeo-Video", "render.video.youtubeTitle": "YouTube-Video", - "sidebar.chat.yesterday": "Gestern" + "sidebar.chat.yesterday": "Gestern", + "%{count} media": "%{count} Medien", + "%{count} posts": "%{count} Beiträge", + "2 langs": "2 Sprachen", + "AI Assistant": "KI-Assistent", + "Across draft, published, and archive": "Über Entwürfe, veröffentlichte Beiträge und Archiv verteilt", + "Activated %{name}": "%{name} aktiviert", + "Archived": "Archiviert", + "Archived Jan 12, 2026": "Archiviert am 12. Jan. 2026", + "Assistant": "Assistent", + "Automatic AI actions stay gated by airplane mode.": "Automatische KI-Aktionen bleiben durch den Flugmodus gesperrt.", + "Automation can boot the shell in a separate process and capture screenshots": "Die Automatisierung kann die Shell in einem separaten Prozess starten und Screenshots aufnehmen", + "Blog": "Blog", + "Calendar regeneration is not wired yet, but the base shell now surfaces the command and keeps the Output tab selectable.": "Die Kalender-Neuerstellung ist noch nicht verdrahtet, aber die Basisshell zeigt den Befehl jetzt an und hält den Ausgabe-Tab auswählbar.", + "Chat": "Chat", + "Close %{title}": "%{title} schließen", + "Close tab": "Tab schließen", + "Command completed": "Befehl abgeschlossen", + "Command failed": "Befehl fehlgeschlagen", + "Command failed with HTTP %{status}": "Befehl mit HTTP %{status} fehlgeschlagen", + "Create Project": "Projekt erstellen", + "Dashboard": "Instrumententafel", + "Desktop Runtime": "Desktop-Laufzeit", + "Desktop workbench content routed through the Elixir shell.": "Desktop-Arbeitsbereichsinhalte werden durch die Elixir-Shell geleitet.", + "Desktop workbench shell wired through Elixir": "Desktop-Workbench-Shell über Elixir verdrahtet", + "Diff Reports": "Diff-Berichte", + "Diffs": "Differenzen", + "Documentation": "Dokumentation", + "Drafts": "Entwürfe", + "Drafts, published entries, and archive history": "Entwürfe, veröffentlichte Einträge und Archivverlauf", + "Edit": "Bearbeiten", + "Extra": "Zusätzlich", + "Extra Pages": "Zusätzliche Seiten", + "File": "Datei", + "Filesystem Sync": "Dateisystem-Abgleich", + "Fill Missing Translations": "Fehlende Übersetzungen ergänzen", + "Find Duplicates": "Duplikate finden", + "Git": "Git", + "Git Log": "Git-Protokoll", + "Help": "Hilfe", + "Idle": "Leerlauf", + "Images and documents indexed": "Bilder und Dokumente indexiert", + "Import": "Importieren", + "Launch plan": "Startplan", + "Main Language": "Hauptsprache", + "Media": "Medien", + "Menu": "Menü", + "Metadata": "Metadaten", + "Metadata Diff": "Metadaten-Diff", + "Metadata flush, diffing, and rebuild hooks still need editor wiring.": "Metadaten-Schreiben, Diffing und Rebuild-Hooks brauchen noch die Editor-Anbindung.", + "Missing": "Fehlend", + "Missing Pages": "Fehlende Seiten", + "Missing Translations": "Fehlende Übersetzungen", + "Mode": "Modus", + "Native menu groups mirror the old application shell": "Native Menügruppen spiegeln die alte Anwendungsshell wider", + "New Project": "Neues Projekt", + "New project name": "Neuer Projektname", + "No active background tasks": "Keine aktiven Hintergrundaufgaben", + "No background tasks running": "Keine Hintergrundaufgaben aktiv", + "No items": "Keine Einträge", + "No missing pages": "Keine fehlenden Seiten", + "No orphan translation files": "Keine verwaisten Übersetzungsdateien", + "No shell output yet": "Noch keine Shell-Ausgabe", + "Offline": "Offline", + "Offline Gate": "Offline-Sperre", + "Open": "Öffnen", + "Open Data Folder": "Datenordner öffnen", + "Open in Browser": "Im Browser öffnen", + "Opened URL": "URL geöffnet", + "Orphan Files": "Verwaiste Dateien", + "Orphan Reports": "Berichte zu verwaisten Dateien", + "Orphans": "Verwaiste", + "Output": "Ausgabe", + "Pages": "Seiten", + "Pairs": "Paare", + "Post": "Beitrag", + "Posts": "Beiträge", + "Preview": "Vorschau", + "Projects": "Projekte", + "Published": "Veröffentlicht", + "Published Feb 10, 2026": "Veröffentlicht am 10. Feb. 2026", + "Queued": "In Warteschlange", + "Regenerate Calendar": "Kalender neu erzeugen", + "Retrospective": "Rückblick", + "Roadmap": "Fahrplan", + "Running": "Läuft", + "Script": "Skript", + "Scripts": "Skripte", + "Select Project": "Projekt auswählen", + "Settings": "Einstellungen", + "Sidebar, tabs, panel, and assistant panes are inspectable DOM regions": "Seitenleiste, Tabs, Panel und Assistentenbereiche sind als DOM-Regionen inspizierbar", + "Site Validation": "Website-Validierung", + "Source Control": "Quellcodeverwaltung", + "Stale": "Veraltet", + "Stale Pages": "Veraltete Seiten", + "Status": "Status", + "Style": "Stil", + "Switch project": "Projekt wechseln", + "Tags": "Tags", + "Tasks": "Aufgaben", + "Template": "Vorlage", + "Templates": "Vorlagen", + "The app window is now served from the Elixir shell renderer.": "Das App-Fenster wird jetzt vom Elixir-Shell-Renderer ausgeliefert.", + "The shared lower panel is available for tasks, output, git details, and editor-specific diagnostics.": "Das gemeinsame untere Panel steht für Aufgaben, Ausgabe, Git-Details und editorbezogene Diagnosen bereit.", + "Toggle assistant": "Assistent umschalten", + "Toggle offline mode": "Offline-Modus umschalten", + "Toggle panel": "Panel umschalten", + "Toggle sidebar": "Seitenleiste umschalten", + "Translation fill is not wired yet, but the command is now routed into Output instead of being ignored.": "Das Ergänzen fehlender Übersetzungen ist noch nicht verdrahtet, aber der Befehl wird jetzt in die Ausgabe geleitet statt ignoriert zu werden.", + "Translations": "Übersetzungen", + "UI": "UI", + "Updated today": "Heute aktualisiert", + "Updated yesterday": "Gestern aktualisiert", + "Upload Site": "Website hochladen", + "View": "Ansicht", + "Welcome to bDS2": "Willkommen bei bDS2", + "Workbench Notes": "Workbench-Hinweise", + "Working tree integration is not wired yet in the shell, but the tab is selectable and ready for command output.": "Die Integration des Arbeitsverzeichnisses ist in der Shell noch nicht verdrahtet, aber der Tab ist auswählbar und bereit für Befehlsausgaben." } \ No newline at end of file diff --git a/priv/i18n/locales/en.json b/priv/i18n/locales/en.json index e42134f..7e13be0 100644 --- a/priv/i18n/locales/en.json +++ b/priv/i18n/locales/en.json @@ -44,5 +44,122 @@ "render.taxonomy.ariaLabel": "Taxonomy", "render.video.vimeoTitle": "Vimeo video", "render.video.youtubeTitle": "YouTube video", - "sidebar.chat.yesterday": "Yesterday" + "sidebar.chat.yesterday": "Yesterday", + "%{count} media": "%{count} media", + "%{count} posts": "%{count} posts", + "2 langs": "2 langs", + "AI Assistant": "AI Assistant", + "Across draft, published, and archive": "Across draft, published, and archive", + "Activated %{name}": "Activated %{name}", + "Archived": "Archived", + "Archived Jan 12, 2026": "Archived Jan 12, 2026", + "Assistant": "Assistant", + "Automatic AI actions stay gated by airplane mode.": "Automatic AI actions stay gated by airplane mode.", + "Automation can boot the shell in a separate process and capture screenshots": "Automation can boot the shell in a separate process and capture screenshots", + "Blog": "Blog", + "Calendar regeneration is not wired yet, but the base shell now surfaces the command and keeps the Output tab selectable.": "Calendar regeneration is not wired yet, but the base shell now surfaces the command and keeps the Output tab selectable.", + "Chat": "Chat", + "Close %{title}": "Close %{title}", + "Close tab": "Close tab", + "Command completed": "Command completed", + "Command failed": "Command failed", + "Command failed with HTTP %{status}": "Command failed with HTTP %{status}", + "Create Project": "Create Project", + "Dashboard": "Dashboard", + "Desktop Runtime": "Desktop Runtime", + "Desktop workbench content routed through the Elixir shell.": "Desktop workbench content routed through the Elixir shell.", + "Desktop workbench shell wired through Elixir": "Desktop workbench shell wired through Elixir", + "Diff Reports": "Diff Reports", + "Diffs": "Diffs", + "Documentation": "Documentation", + "Drafts": "Drafts", + "Drafts, published entries, and archive history": "Drafts, published entries, and archive history", + "Edit": "Edit", + "Extra": "Extra", + "Extra Pages": "Extra Pages", + "File": "File", + "Filesystem Sync": "Filesystem Sync", + "Fill Missing Translations": "Fill Missing Translations", + "Find Duplicates": "Find Duplicates", + "Git": "Git", + "Git Log": "Git Log", + "Help": "Help", + "Idle": "Idle", + "Images and documents indexed": "Images and documents indexed", + "Import": "Import", + "Launch plan": "Launch plan", + "Main Language": "Main Language", + "Media": "Media", + "Menu": "Menu", + "Metadata": "Metadata", + "Metadata Diff": "Metadata Diff", + "Metadata flush, diffing, and rebuild hooks still need editor wiring.": "Metadata flush, diffing, and rebuild hooks still need editor wiring.", + "Missing": "Missing", + "Missing Pages": "Missing Pages", + "Missing Translations": "Missing Translations", + "Mode": "Mode", + "Native menu groups mirror the old application shell": "Native menu groups mirror the old application shell", + "New Project": "New Project", + "New project name": "New project name", + "No active background tasks": "No active background tasks", + "No background tasks running": "No background tasks running", + "No items": "No items", + "No missing pages": "No missing pages", + "No orphan translation files": "No orphan translation files", + "No shell output yet": "No shell output yet", + "Offline": "Offline", + "Offline Gate": "Offline Gate", + "Open": "Open", + "Open Data Folder": "Open Data Folder", + "Open in Browser": "Open in Browser", + "Opened URL": "Opened URL", + "Orphan Files": "Orphan Files", + "Orphan Reports": "Orphan Reports", + "Orphans": "Orphans", + "Output": "Output", + "Pages": "Pages", + "Pairs": "Pairs", + "Post": "Post", + "Posts": "Posts", + "Preview": "Preview", + "Projects": "Projects", + "Published": "Published", + "Published Feb 10, 2026": "Published Feb 10, 2026", + "Queued": "Queued", + "Regenerate Calendar": "Regenerate Calendar", + "Retrospective": "Retrospective", + "Roadmap": "Roadmap", + "Running": "Running", + "Script": "Script", + "Scripts": "Scripts", + "Select Project": "Select Project", + "Settings": "Settings", + "Sidebar, tabs, panel, and assistant panes are inspectable DOM regions": "Sidebar, tabs, panel, and assistant panes are inspectable DOM regions", + "Site Validation": "Site Validation", + "Source Control": "Source Control", + "Stale": "Stale", + "Stale Pages": "Stale Pages", + "Status": "Status", + "Style": "Style", + "Switch project": "Switch project", + "Tags": "Tags", + "Tasks": "Tasks", + "Template": "Template", + "Templates": "Templates", + "The app window is now served from the Elixir shell renderer.": "The app window is now served from the Elixir shell renderer.", + "The shared lower panel is available for tasks, output, git details, and editor-specific diagnostics.": "The shared lower panel is available for tasks, output, git details, and editor-specific diagnostics.", + "Toggle assistant": "Toggle assistant", + "Toggle offline mode": "Toggle offline mode", + "Toggle panel": "Toggle panel", + "Toggle sidebar": "Toggle sidebar", + "Translation fill is not wired yet, but the command is now routed into Output instead of being ignored.": "Translation fill is not wired yet, but the command is now routed into Output instead of being ignored.", + "Translations": "Translations", + "UI": "UI", + "Updated today": "Updated today", + "Updated yesterday": "Updated yesterday", + "Upload Site": "Upload Site", + "View": "View", + "Welcome to bDS2": "Welcome to bDS2", + "Workbench Notes": "Workbench Notes", + "Working tree integration is not wired yet in the shell, but the tab is selectable and ready for command output.": "Working tree integration is not wired yet in the shell, but the tab is selectable and ready for command output." } \ No newline at end of file diff --git a/priv/ui/app.js b/priv/ui/app.js index 83c4c12..6d6ce35 100644 --- a/priv/ui/app.js +++ b/priv/ui/app.js @@ -23,6 +23,25 @@ const state = { tabMeta: {}, }; +function translationsForLanguage(language) { + return bootstrap.i18n?.catalogs?.[language] || bootstrap.i18n?.catalogs?.en || {}; +} + +function t(key, bindings = {}) { + const catalog = translationsForLanguage(state.uiLanguage); + let text = catalog[key] || key; + + Object.entries(bindings).forEach(([binding, value]) => { + text = text.replaceAll(`%{${binding}}`, String(value)); + }); + + return text; +} + +function tText(value, bindings = {}) { + return t(String(value), bindings); +} + bindNativeMenuBridge(); bindGlobalHotkeys(); scheduleTaskPolling(); @@ -51,23 +70,23 @@ function renderTitlebar() { root.querySelector(".window-titlebar").innerHTML = `
${bootstrap.menu_groups - .map((group) => ``) + .map((group) => ``) .join("")}
${escapeHtml(bootstrap.title)}
- ${renderTitlebarAction("toggle-sidebar", "toggle-sidebar", "Toggle sidebar", ` + ${renderTitlebarAction("toggle-sidebar", "toggle-sidebar", t("Toggle sidebar"), ` `)} - ${renderTitlebarAction("toggle-panel", "toggle-panel", "Toggle panel", ` + ${renderTitlebarAction("toggle-panel", "toggle-panel", t("Toggle panel"), ` `)} - ${renderTitlebarAction("toggle-assistant-sidebar", "toggle-assistant", "Toggle assistant", ` + ${renderTitlebarAction("toggle-assistant-sidebar", "toggle-assistant", t("Toggle assistant"), ` @@ -104,8 +123,8 @@ function renderActivityButton(view) { data-active="${String(active)}" data-testid="activity-button" type="button" - aria-label="${escapeHtml(view.label)}" - title="${escapeHtml(view.label)}" + aria-label="${escapeHtml(tText(view.label))}" + title="${escapeHtml(tText(view.label))}" > ${activityIcon(view.id)} @@ -119,8 +138,8 @@ function renderSidebar() { root.querySelector(".sidebar").innerHTML = `