diff --git a/BUILD.md b/BUILD.md index 15229bb091..e42545ee7c 100644 --- a/BUILD.md +++ b/BUILD.md @@ -127,6 +127,8 @@ If you're on Linux ARM64, run the following: USE_SYSTEM_FPM=1 task package ``` +On Windows, you can instead run `scripts/build-windows.ps1`, which checks for the required prerequisites (Node, Go, Zig, Task) before invoking `task package`. + ## Debugging ### Frontend logs diff --git a/README.de.md b/README.de.md new file mode 100644 index 0000000000..f64f07e444 --- /dev/null +++ b/README.de.md @@ -0,0 +1,120 @@ +

+ + + + + Wave Terminal Logo + + +
+

+ +# Wave Terminal + +
+ +[English](README.md) | [Deutsch](README.de.md) | [한국어](README.ko.md) | [繁體中文](README.zh-TW.md) + +
+ +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fwavetermdev%2Fwaveterm.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fwavetermdev%2Fwaveterm?ref=badge_shield) + +> Dies ist eine von der Community erstellte deutsche Übersetzung. Die aktuellste Originalversion findest du in [README.md](README.md). + +Wave ist ein Open-Source-Terminal mit integrierter KI für macOS, Linux und Windows. Es funktioniert mit jedem beliebigen KI-Modell. Nutze deine eigenen API-Schlüssel für OpenAI, Claude oder Gemini, oder betreibe lokale Modelle über Ollama und LM Studio. Es ist kein Konto erforderlich. + +Wave unterstützt außerdem dauerhafte SSH-Sitzungen, die Netzwerkunterbrechungen und Neustarts überstehen, inklusive automatischer Wiederverbindung. Bearbeite entfernte Dateien mit einem integrierten grafischen Editor und sieh dir Dateien direkt im Terminal an, ohne es zu verlassen. + +![WaveTerm Screenshot](./assets/wave-screenshot.webp) + +## Hauptfunktionen + +- Wave AI - Kontextbewusster Terminal-Assistent, der deine Terminal-Ausgabe liest, Widgets analysiert und Dateioperationen durchführt +- Dauerhafte SSH-Sitzungen - Entfernte Terminal-Sitzungen überstehen Verbindungsabbrüche, Netzwerkwechsel und Wave-Neustarts dank automatischer Wiederverbindung +- Flexible Drag-&-Drop-Oberfläche zum Organisieren von Terminal-Blöcken, Editoren, Webbrowsern und KI-Assistenten +- Integrierter Editor zum Bearbeiten entfernter Dateien mit Syntax-Highlighting und modernen Editor-Funktionen +- Umfangreiches Dateivorschausystem für entfernte Dateien (Markdown, Bilder, Video, PDFs, CSVs, Verzeichnisse) +- Schnelles Vollbild-Umschalten für jeden Block - Terminals, Editoren und Vorschauen für bessere Sichtbarkeit vergrößern und sofort zur Mehrblock-Ansicht zurückkehren +- KI-Chat-Widget mit Unterstützung für mehrere Modelle (OpenAI, Claude, Azure, Perplexity, Ollama) +- Command Blocks zum Isolieren und Überwachen einzelner Befehle +- Ein-Klick-Verbindungen zu entfernten Rechnern mit vollem Terminal- und Dateisystemzugriff +- Sichere Speicherung von Secrets über native System-Backends - API-Schlüssel und Zugangsdaten lokal speichern und über SSH-Sitzungen hinweg nutzen +- Umfangreiche Anpassungsmöglichkeiten wie Tab-Themes, Terminal-Stile und Hintergrundbilder +- Leistungsstarkes `wsh`-Befehlssystem zur Verwaltung deines Workspace über die CLI und zum Austausch von Daten zwischen Terminal-Sitzungen +- Verbundene Dateiverwaltung mit `wsh file` - Dateien nahtlos zwischen lokalen und entfernten SSH-Hosts kopieren und synchronisieren + +## Wave AI + +Wave AI ist dein kontextbewusster Terminal-Assistent mit Zugriff auf deinen Workspace: + +- **Terminal-Kontext**: Liest Terminal-Ausgabe und Scrollback zur Fehlersuche und Analyse +- **Dateioperationen**: Dateien lesen, schreiben und bearbeiten mit automatischen Backups und Nutzerfreigabe +- **CLI-Integration**: Nutze `wsh ai`, um Ausgaben weiterzuleiten oder Dateien direkt über die Kommandozeile anzuhängen +- **BYOK-Unterstützung**: Bring deine eigenen API-Schlüssel für OpenAI, Claude, Gemini, Azure und andere Anbieter mit +- **Lokale Modelle**: Betreibe lokale Modelle mit Ollama, LM Studio und anderen OpenAI-kompatiblen Anbietern +- **Kostenlose Beta**: Enthaltenes KI-Guthaben, während wir die Erfahrung weiter verfeinern +- **Demnächst**: Befehlsausführung (mit Freigabe) + +Mehr erfährst du in unserer [Wave AI-Dokumentation](https://docs.waveterm.dev/waveai) und der [Dokumentation zu Wave AI-Modi](https://docs.waveterm.dev/waveai-modes). + +## Installation + +Wave Terminal läuft unter macOS, Linux und Windows. + +Plattformspezifische Installationsanleitungen findest du [hier](https://docs.waveterm.dev/gettingstarted). + +Du kannst Wave Terminal auch direkt installieren von: [www.waveterm.dev/download](https://www.waveterm.dev/download). + +### Mindestanforderungen + +Wave Terminal läuft auf folgenden Plattformen: + +- macOS 11 oder neuer (arm64, x64) +- Windows 10 1809 oder neuer (x64) +- Linux basierend auf glibc-2.28 oder neuer (Debian 10, RHEL 8, Ubuntu 20.04 usw.) (arm64, x64) + +Der WSH-Helper läuft auf folgenden Plattformen: + +- macOS 11 oder neuer (arm64, x64) +- Windows 10 oder neuer (x64) +- Linux Kernel 2.6.32 oder neuer (x64), Linux Kernel 3.1 oder neuer (arm64) + +## Roadmap + +Wave wird ständig weiterentwickelt! Unsere Roadmap wird laufend mit unseren Zielen für jedes Release aktualisiert. Du findest sie [hier](./ROADMAP.md). + +Möchtest du zu unseren zukünftigen Releases beitragen? Vernetze dich mit uns auf [Discord](https://discord.gg/XfvZ334gwU) oder erstelle eine [Feature-Anfrage](https://github.com/wavetermdev/waveterm/issues/new/choose)! + +## Links + +- Homepage — https://www.waveterm.dev +- Download-Seite — https://www.waveterm.dev/download +- Dokumentation — https://docs.waveterm.dev +- X — https://x.com/wavetermdev +- Discord-Community — https://discord.gg/XfvZ334gwU + +## Aus dem Quellcode erstellen + +Siehe [Wave Terminal erstellen](BUILD.md). + +## Mitwirken + +Wave nutzt GitHub Issues für die Fehler- und Aufgabenverfolgung. + +Weitere Informationen findest du in unserem [Leitfaden für Mitwirkende](CONTRIBUTING.md), der Folgendes enthält: + +- [Möglichkeiten mitzuwirken](CONTRIBUTING.md#contributing-to-wave-terminal) +- [Richtlinien für Beiträge](CONTRIBUTING.md#before-you-start) + +### Wave sponsern ❤️ + +Wenn dir Wave Terminal nützlich ist - privat oder in deinem Unternehmen - ziehe in Betracht, die Entwicklung zu sponsern. + +Sponsoring hilft, die Zeit zu unterstützen, die in den Aufbau und die Pflege des Projekts investiert wird. + +- https://github.com/sponsors/wavetermdev + +## Lizenz + +Wave Terminal ist unter der Apache-2.0-Lizenz lizenziert. Weitere Informationen zu unseren Abhängigkeiten findest du [hier](./ACKNOWLEDGEMENTS.md). + diff --git a/README.ko.md b/README.ko.md index d18ccfaed9..c678b6d166 100644 --- a/README.ko.md +++ b/README.ko.md @@ -13,7 +13,7 @@
-[English](README.md) | [한국어](README.ko.md) | [繁體中文](README.zh-TW.md) +[English](README.md) | [Deutsch](README.de.md) | [한국어](README.ko.md) | [繁體中文](README.zh-TW.md)
diff --git a/README.md b/README.md index a9f406725c..f03fa625b8 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@
-[English](README.md) | [한국어](README.ko.md) | [繁體中文](README.zh-TW.md) +[English](README.md) | [Deutsch](README.de.md) | [한국어](README.ko.md) | [繁體中文](README.zh-TW.md)
diff --git a/README.zh-TW.md b/README.zh-TW.md index c24dca360c..2e7c10427a 100644 --- a/README.zh-TW.md +++ b/README.zh-TW.md @@ -13,7 +13,7 @@
-[English](README.md) | [한국어](README.ko.md) | [繁體中文](README.zh-TW.md) +[English](README.md) | [Deutsch](README.de.md) | [한국어](README.ko.md) | [繁體中文](README.zh-TW.md)
diff --git a/Taskfile.yml b/Taskfile.yml index bf37a83e45..a90ae76798 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -124,9 +124,13 @@ tasks: package: desc: Package the application for the current platform. cmds: + - task: clean + - task: package:build - npm run build:prod && npm exec electron-builder -- -c electron-builder.config.cjs -p never {{.CLI_ARGS}} + + package:build: + internal: true deps: - - clean - npm:install - build:backend - build:tsunamiscaffold diff --git a/docs/docs/config.mdx b/docs/docs/config.mdx index 05389e99ef..8d05c785d6 100644 --- a/docs/docs/config.mdx +++ b/docs/docs/config.mdx @@ -45,6 +45,7 @@ wsh editconfig | app:disablectrlshiftdisplay | bool | Set to true to disable the Ctrl+Shift visual indicator display (defaults to false) | | app:focusfollowscursor | string | Controls whether block focus follows cursor movement: `"off"` (default), `"on"` (all blocks), or `"term"` (terminal blocks only) | | app:tabbar | string | Controls the position of the tab bar: `"top"` (default) for a horizontal tab bar at the top of the window, or `"left"` for a vertical tab bar on the left side of the window | +| app:language | string | Sets the UI language: `"en"` (default) or `"de"` | | ai:preset | string | the default AI preset to use | | ai:baseurl | string | Set the AI Base Url (must be OpenAI compatible) | | ai:apitoken | string | your AI api token | diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index a5c20d6190..7a834ec2ca 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -31,7 +31,7 @@ const config: Config = { // may want to replace "en" with "zh-Hans". i18n: { defaultLocale: "en", - locales: ["en"], + locales: ["en", "de"], }, plugins: [ [ @@ -113,6 +113,10 @@ const config: Config = { className: "header-link-custom custom-icon-github", "aria-label": "GitHub repository", }, + { + type: "localeDropdown", + position: "right", + }, ] : [], ].flat(), diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current.json b/docs/i18n/de/docusaurus-plugin-content-docs/current.json new file mode 100644 index 0000000000..64666b9e83 --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current.json @@ -0,0 +1,6 @@ +{ + "version.label": { + "message": "Nächste", + "description": "The label for version current" + } +} diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/ai-presets.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/ai-presets.mdx new file mode 100644 index 0000000000..bc23f39c0c --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/ai-presets.mdx @@ -0,0 +1,253 @@ +--- +sidebar_position: 3.6 +id: "ai-presets" +title: "KI-Voreinstellungen (veraltet)" +--- +:::warning Hinweis zur Abkündigung +Das KI-Widget und seine Voreinstellungen werden durch [Wave AI](./waveai.mdx) ersetzt. Die aktuellen KI-Funktionen und Konfigurationsoptionen findest du in der Wave-AI-Dokumentation. +::: + + +![AI Presets Menu](./img/ai-presets.png#right) + +Waves KI-Widget kann über Voreinstellungen (Presets) für verschiedene KI-Anbieter und -Modelle konfiguriert werden. Mit Presets kannst du mehrere KI-Konfigurationen definieren und über das Dropdown-Menü im KI-Widget einfach zwischen ihnen wechseln. + +## Wie KI-Voreinstellungen funktionieren + +KI-Voreinstellungen werden in `~/.config/waveterm/presets/ai.json` definiert. Du kannst diese Datei einfach bearbeiten mit: + +```bash +wsh editconfig presets/ai.json +``` + +Jede Voreinstellung definiert einen vollständigen Satz an Konfigurationswerten für das KI-Widget. Wählst du eine Voreinstellung aus dem Dropdown-Menü, werden diese Konfigurationswerte auf das Widget angewendet. Ist keine Voreinstellung ausgewählt, nutzt das Widget die Standardwerte aus `settings.json`. + +Hier ein einfaches Beispiel mit Claude: + +```json +{ + "ai@claude-sonnet": { + "display:name": "Claude 3 Sonnet", + "display:order": 1, + "ai:*": true, + "ai:apitype": "anthropic", + "ai:model": "claude-3-5-sonnet-latest", + "ai:apitoken": "" + } +} +``` + +Um eine Voreinstellung zu deinem Standard zu machen, füge diese eine Zeile zu deiner `settings.json` hinzu: + +```json +{ + "ai:preset": "ai@claude-sonnet" +} +``` + +:::info +Du kannst deine Standard-Voreinstellung schnell mit dem Befehl `setconfig` festlegen: + +```bash +wsh setconfig ai:preset=ai@claude-sonnet +``` + +Das ist einfacher, als die settings.json direkt zu bearbeiten! +::: + +## Anbieterspezifische Konfigurationen + +### Anthropic (Claude) + +Um Claude-Modelle zu nutzen, erstelle eine Voreinstellung wie diese: + +```json +{ + "ai@claude-sonnet": { + "display:name": "Claude 3 Sonnet", + "display:order": 1, + "ai:*": true, + "ai:apitype": "anthropic", + "ai:model": "claude-3-5-sonnet-latest", + "ai:apitoken": "" + } +} +``` + +### OpenAI + +Um OpenAIs Modelle zu nutzen: + +```json +{ + "ai@openai-gpt41": { + "display:name": "GPT-4.1", + "display:order": 2, + "ai:*": true, + "ai:model": "gpt-4.1", + "ai:apitoken": "" + } +} +``` + +### Lokale LLMs (Ollama) + +Um sich mit einer lokalen Ollama-Instanz zu verbinden: + +```json +{ + "ai@ollama-llama": { + "display:name": "Ollama - Llama2", + "display:order": 3, + "ai:*": true, + "ai:baseurl": "http://localhost:11434/v1", + "ai:name": "llama2", + "ai:model": "llama2", + "ai:apitoken": "ollama" + } +} +``` + +Hinweis: `ai:apitoken` ist erforderlich, kann aber einen beliebigen Wert haben, da Ollama ihn ignoriert. Weitere Details in der [Ollama-OpenAI-Kompatibilitätsdokumentation](https://github.com/ollama/ollama/blob/main/docs/openai.md). + +### Azure OpenAI + +Um sich mit Azure-KI-Diensten zu verbinden: + +```json +{ + "ai@azure-gpt4": { + "display:name": "Azure GPT-4", + "display:order": 4, + "ai:*": true, + "ai:apitype": "azure", + "ai:baseurl": "", + "ai:model": "", + "ai:apitoken": "" + } +} +``` + +Hinweis: Füge keine Query-Parameter oder `api-version` in `ai:baseurl` ein. `ai:model` sollte der Name deines Modell-Deployments in Azure sein. + +### Perplexity + +Um Perplexitys Modelle zu nutzen: + +```json +{ + "ai@perplexity-sonar": { + "display:name": "Perplexity Sonar", + "display:order": 5, + "ai:*": true, + "ai:apitype": "perplexity", + "ai:model": "llama-3.1-sonar-small-128k-online", + "ai:apitoken": "" + } +} +``` + +### Google (Gemini) + +Um Googles Gemini-Modelle über [Google AI Studio](https://aistudio.google.com) zu nutzen: + +```json +{ + "ai@gemini-2.0": { + "display:name": "Gemini 2.0", + "display:order": 6, + "ai:*": true, + "ai:apitype": "google", + "ai:model": "gemini-2.0-flash-exp", + "ai:apitoken": "" + } +} +``` + +### OpenRouter + +Um OpenRouters Modelle zu nutzen: + +```json +{ + "ai@openrouter": { + "display:name": "OpenRouter (Qwen)", + "display:order": 7, + "ai:*": true, + "ai:model": "qwen/qwen3-next-80b-a3b-thinking", + "ai:apitoken": "", + "ai:baseurl": "https://openrouter.ai/api/v1" + } +} +``` + +## Beispiel mit mehreren Voreinstellungen + +Du kannst mehrere Voreinstellungen in deiner `ai.json`-Datei definieren: + +```json +{ + "ai@claude-sonnet": { + "display:name": "Claude 3 Sonnet", + "display:order": 1, + "ai:*": true, + "ai:apitype": "anthropic", + "ai:model": "claude-3-5-sonnet-latest", + "ai:apitoken": "" + }, + "ai@openai-gpt41": { + "display:name": "GPT-4.1", + "display:order": 2, + "ai:*": true, + "ai:model": "gpt-4.1", + "ai:apitoken": "" + }, + "ai@ollama-llama": { + "display:name": "Ollama - Llama2", + "display:order": 3, + "ai:*": true, + "ai:baseurl": "http://localhost:11434/v1", + "ai:name": "llama2", + "ai:model": "llama2", + "ai:apitoken": "ollama" + }, + "ai@perplexity-sonar": { + "display:name": "Perplexity Sonar", + "display:order": 4, + "ai:*": true, + "ai:apitype": "perplexity", + "ai:model": "llama-3.1-sonar-small-128k-online", + "ai:apitoken": "" + } +} +``` + +Der Wert `display:order` bestimmt die Reihenfolge, in der Voreinstellungen im Dropdown-Menü erscheinen. + +Denk daran, deine Standard-Voreinstellung in `settings.json` zu setzen: + +```json +{ + "ai:preset": "ai@claude-sonnet" +} +``` + +## Einen Proxy nutzen + +Wenn du KI-Anfragen über einen HTTP-Proxy leiten musst, kannst du die Einstellung `ai:proxyurl` zu jeder Voreinstellung hinzufügen: + +```json +{ + "ai@claude-with-proxy": { + "display:name": "Claude 3 Sonnet (via Proxy)", + "display:order": 1, + "ai:*": true, + "ai:apitype": "anthropic", + "ai:model": "claude-3-5-sonnet-latest", + "ai:apitoken": "", + "ai:proxyurl": "http://proxy.example.com:8080" + } +} +``` + +Die Proxy-URL sollte das Format `http://host:port` oder `https://host:port` haben. Diese Einstellung funktioniert mit allen KI-Anbietern außer Wave Cloud AI (dem Standard). diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/claude-code.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/claude-code.mdx new file mode 100644 index 0000000000..34bb29992b --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/claude-code.mdx @@ -0,0 +1,131 @@ +--- +sidebar_position: 1.9 +id: "claude-code" +title: "Claude-Code-Integration" +--- + +import { VersionBadge } from "@site/src/components/versionbadge"; + +# Claude-Code-Tab-Badges + +Wenn du mehrere Claude-Code-Sitzungen parallel laufen lässt - eine pro Feature, eine pro Repo, ein paar lang laufende Aufgaben - wird es schwierig, ohne jeden Tab einzeln anzuklicken zu wissen, welcher deine Aufmerksamkeit braucht. Waves Badge-System löst das: Hooks in Claude Code schreiben einen kleinen visuellen Indikator in die Tab-Kopfzeile, sobald etwas Wichtiges passiert, sodass du auf einen Blick siehst, welche Sitzungen warten, fertig sind oder Probleme haben. + +:::info +tl;dr Du kannst diese Seite direkt in Claude Code kopieren und einfügen, und es hilft dir, alles einzurichten! +::: + +## So funktioniert es + +Claude Code unterstützt [Lifecycle-Hooks](https://code.claude.com/docs/en/hooks) - Shell-Befehle, die automatisch an bestimmten Punkten einer Sitzung ausgeführt werden. Waves Befehl `wsh badge` setzt oder entfernt einen visuellen Indikator am aktuellen Block oder Tab. Durch das Verknüpfen dieser beiden Dinge erhältst du ein ambientes Bewusstsein über all deine Sitzungen, ohne eine davon beobachten zu müssen. + +Badges werden automatisch entfernt, sobald du den Block fokussierst - sie sind also rein ein "Hey, schau hier"-Signal. Sobald du hineinklickst und liest, was passiert, verschwindet das Badge von selbst. + +Wave zeigt bereits ein Glockensymbol an, wenn ein Terminal ein BEL-Zeichen ausgibt. Diese Hooks ergänzen das um semantische Badges - *Freigabe benötigt*, *fertig* - die Tab-Wechsel und Splits überstehen. + +### Badge-Rollup + +Hat ein Tab mehrere Terminals (Blöcke), zeigt Wave das Badge mit der höchsten Priorität in der Tab-Kopfzeile. Bei Gleichstand in der Priorität gewinnt das zuerst gesetzte Badge, sodass das dringendste Signal aus jedem Bereich des Tabs nach oben durchdringt. + +## Einrichtung + +Diese Hooks kommen in deine globalen Claude-Code-Einstellungen, damit sie für jede Sitzung auf deinem Rechner gelten, nicht nur für ein Projekt. + +Füge Folgendes zu `~/.claude/settings.json` hinzu. Wenn du bereits einen `hooks`-Schlüssel hast, führe die Einträge zusammen: + +```json +{ + "hooks": { + "Notification": [ + { + "matcher": "permission_prompt", + "hooks": [ + { + "type": "command", + "command": "wsh badge bell-exclamation --color '#e0b956' --priority 20 --beep" + } + ] + }, + { + "matcher": "elicitation_dialog", + "hooks": [ + { + "type": "command", + "command": "wsh badge message-question --color '#e0b956' --priority 20 --beep" + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "wsh badge check --color '#58c142' --priority 10" + } + ] + } + ], + "PreToolUse": [ + { + "matcher": "AskUserQuestion", + "hooks": [ + { + "type": "command", + "command": "wsh badge message-question --color '#e0b956' --priority 20 --beep" + } + ] + } + ] + } +} +``` + +Das war's. Starte alle laufenden Claude-Code-Sitzungen neu, damit die Hooks wirksam werden. + +:::warning Bekanntes Problem +Es gibt ein bekanntes Problem in Claude Code, bei dem `Notification`-Hooks um mehrere Sekunden verzögert ausgelöst werden können. Diese Verzögerung hat nichts mit Wave zu tun - sie tritt in Claude Code selbst auf. Details siehe [#5186](https://github.com/anthropics/claude-code/issues/5186) und [#19627](https://github.com/anthropics/claude-code/issues/19627). +::: + +## Was jeder Hook macht + +### Freigabeabfrage — `bell-exclamation` gold, Priorität 20 + +Claude Code benötigt gelegentlich deine Freigabe, bevor es fortfahren kann - um einen Befehl auszuführen, eine Datei außerhalb des Projekts zu schreiben oder ein Tool zu nutzen, das eine explizite Berechtigung erfordert. Trifft es auf so etwas, hält es an und wartet. Ohne Signal bemerkst du das vielleicht erst nach Minuten. + +Dieser Hook wird beim Benachrichtigungstyp `permission_prompt` ausgelöst und setzt ein hochprioritäres, goldenes Badge mit einem hörbaren Signalton. Priorität 20 bedeutet, dass es jedes andere Badge auf diesem Tab schlägt, sodass eine wartende Sitzung immer über einer fertigen erscheint. + +Klickst du in den Tab und genehmigst oder lehnst die Anfrage ab, verschwindet das Badge automatisch. + +### Sitzung abgeschlossen — `check` grün, Priorität 10 + +Wenn Claude Code mit der Antwort fertig ist, setzt dieser Hook ein grünes Häkchen-Badge. Es ist ein dezentes Signal: ein Blick auf die Tableiste zeigt, welche Sitzungen fertig sind, und du kannst ihre Ausgabe in beliebiger Reihenfolge durchsehen. + +### AskUserQuestion — `message-question` gold, Priorität 20 + +Wenn Claude Code das Tool `AskUserQuestion` nutzt, ist es pausiert und wartet auf deine Antwort, bevor es fortfahren kann. Dieser `PreToolUse`-Hook wird direkt vor diesem Tool-Aufruf ausgelöst und setzt dasselbe hochprioritäre goldene Badge wie die Freigabeabfrage. + +`PreToolUse`-Hooks können jedes Tool anhand seines Namens abgleichen, sodass du auch für andere Tools Badges hinzufügen kannst - zum Beispiel, um ein Signal zu erhalten, wann immer Claude einen Shell-Befehl ausführt (`Bash`) oder eine Datei bearbeitet (`Edit`). Jeder von Claude Code unterstützte Tool-Name kann als Matcher genutzt werden. + +## Eigene Icons und Farben wählen + +Icon-Namen sind [Font-Awesome](https://fontawesome.com/icons)-Icon-Namen ohne das Präfix `fa-`. Farben sind beliebige gültige CSS-Farben - Hex-Werte, benannte Farben oder alles andere, was CSS akzeptiert. + +Ein paar Icon- und Farbideen: + +| Situation | Icon | Farbe | +|-----------|------|-------| +| Benutzerdefinierter Alarm mit hoher Priorität | `triangle-exclamation` | `#FF453A` | +| Blockiert / wartet auf Eingabe | `hourglass-half` | `#FF9500` | +| Neutral / informativ | `circle-info` | `#429DFF` | +| Hintergrundaufgabe läuft | `spinner` | `#00FFDB` | + +Alle verfügbaren Flags findest du in der [`wsh badge`-Referenz](/wsh-reference#badge). + +## Prioritäten anpassen + +Die Priorität bestimmt, welches Badge gewinnt, wenn mehrere Blöcke in einem Tab jeweils eines haben. Höhere Zahlen haben Vorrang. Die obigen Standardwerte nutzen: + +- **20** für Freigabeabfragen — erscheint immer über allem anderen +- **10** für abgeschlossene Sitzungen — sichtbar, wenn nichts Dringenderes aktiv ist + +Wenn du weitere Hooks hinzufügst, halte blockierende Freigabesignale im oberen Bereich (15–25) und informative Signale im unteren Bereich (5–10). diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/config.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/config.mdx new file mode 100644 index 0000000000..87bc706c39 --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/config.mdx @@ -0,0 +1,386 @@ +--- +sidebar_position: 3.45 +id: "config" +title: "Konfiguration" +--- + +import { Kbd } from "@site/src/components/kbd"; +import { PlatformProvider, PlatformSelectorButton } from "@site/src/components/platformcontext"; +import { VersionBadge, DeprecatedBadge } from "@site/src/components/versionbadge"; + + + + +
+ +Waves Konfigurationsdateien befinden sich unter `~/.config/waveterm/`. + +Die Hauptkonfigurationsdatei ist `settings.json` (`~/.config/waveterm/settings.json`). + +Die Datei ist als weitgehend flaches JSON strukturiert. Statt Unterobjekten nutzen wir bevorzugt +":" als Ebenentrenner. + +:::info + +Der einfachste Weg, deine Konfigurationsdateien zu bearbeiten, ist der Befehl wsh editconfig, der deine Wave-Konfigurationsdatei in unserem integrierten Vorschau-Editor öffnet. + +``` +wsh editconfig +``` + +::: + +## Konfigurationsschlüssel + +| Schlüsselname | Typ | Funktion | +| ------------------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| app:globalhotkey | string | Eine systemweite Tastenkombination, um dein zuletzt genutztes Wave-Fenster zu öffnen. Eine Reihe von Tastennamen, getrennt durch `:`. Weitere Infos unter [Anpassbare systemweite globale Tastenkombination](#anpassbare-systemweite-globale-tastenkombination) | +| app:dismissarchitecturewarning | bool | Deaktiviert Warnungen beim App-Start, wenn du eine nicht-native Architektur für Wave nutzt. Weitere Infos unter [Warum warnt mich Wave beim Start vor der ARM64-Übersetzung?](./faq#why-does-wave-warn-me-about-arm64-translation-when-it-launches). | +| app:defaultnewblock | string | Legt den standardmäßigen neuen Block fest (Cmd:n, Cmd:d). "term" für Terminal-Block, "launcher" für Launcher-Block (Standard = "term") | +| app:showoverlayblocknums | bool | Auf false setzen, um das Blocknummern-Overlay zu deaktivieren, das beim Halten von Strg+Umschalt erscheint (Standard true) | +| app:ctrlvpaste | bool | Unter Windows/Linux nutzt der Wert null (Standard) Strg+V nur unter Windows. Auf true setzen, um Strg+V auf allen Nicht-macOS-Plattformen zu erzwingen, false, um das Tastenkürzel zu deaktivieren. macOS nutzt unabhängig von dieser Einstellung immer Cmd+V | +| app:confirmquit | bool | Auf false setzen, um den Bestätigungsdialog beim Beenden von Wave Terminal zu deaktivieren (Standard true, erfordert App-Neustart) | +| app:hideaibutton | bool | Auf true setzen, um den KI-Button in der Tableiste auszublenden (Standard false) | +| app:disablectrlshiftarrows | bool | Auf true setzen, um die Strg+Umschalt-Block-Navigations-Tastenkombinationen (`Arrow` und `h/j/k/l`) zu deaktivieren (Standard false) | +| app:disablectrlshiftdisplay | bool | Auf true setzen, um die visuelle Strg+Umschalt-Anzeige zu deaktivieren (Standard false) | +| app:focusfollowscursor | string | Steuert, ob der Block-Fokus der Mausbewegung folgt: `"off"` (Standard), `"on"` (alle Blöcke) oder `"term"` (nur Terminal-Blöcke) | +| app:tabbar | string | Steuert die Position der Tableiste: `"top"` (Standard) für eine horizontale Tableiste oben im Fenster, oder `"left"` für eine vertikale Tableiste links im Fenster | +| app:language | string | Legt die UI-Sprache fest: `"en"` (Standard) oder `"de"` | +| ai:preset | string | die standardmäßig zu nutzende KI-Voreinstellung | +| ai:baseurl | string | Legt die KI-Basis-URL fest (muss OpenAI-kompatibel sein) | +| ai:apitoken | string | dein KI-API-Token | +| ai:apitype | string | Standard ist "open_ai", kann aber auch auf "azure" (für spezielle Azure-AI-Behandlung), "anthropic" oder "perplexity" gesetzt werden | +| ai:name | string | in der Wave-AI-Block-Kopfzeile angezeigter Text | +| ai:model | string | an die API zu übergebender Modellname | +| ai:apiversion | string | nur für Azure AI (wenn apitype "azure" ist, ist der Standard "2023-05-15") | +| ai:orgid | string | | +| ai:maxtokens | int | maximale Tokenanzahl für die API | +| ai:timeoutms | int | Timeout (in Millisekunden) für KI-Aufrufe | +| ai:proxyurl | string | HTTP-Proxy-URL für KI-API-Anfragen (gilt nicht für Wave Cloud AI) | +| conn:askbeforewshinstall | bool | auf false setzen, um das Popup zu deaktivieren, das fragt, ob du wsh-Erweiterungen auf neuen Rechnern installieren möchtest | +| conn:localhostdisplayname | string | überschreibt den Anzeigenamen für localhost in der UI (z. B. auf "Mein Laptop" oder "Lokal" setzen, oder auf einen leeren String setzen, um den Namen auszublenden) | +| term:fontsize | float | die Schriftgröße für den Terminal-Block | +| term:fontfamily | string | für den Terminal-Block zu nutzende Schriftart | +| term:disablewebgl | bool | auf true setzen, um WebGL-Beschleunigung im Terminal zu deaktivieren (Standard false) | +| term:localshellpath | string | zum Überschreiben des Standard-Shell-Pfads für lokale Terminals | +| term:localshellopts | string[] | zur Übergabe zusätzlicher Parameter an term:localshellpath (Beispiel: `["-NoLogo"]` entfernt für PowerShell den Copyright-Hinweis) | +| term:copyonselect | bool | auf false setzen, um Terminal-Copy-on-Select zu deaktivieren | +| term:scrollback | int | Größe des Terminal-Scrollback-Puffers, max. 10000 | +| term:theme | string | Name der standardmäßig anzuwendenden Terminal-Theme-Voreinstellung (Standard ist "default-dark") | +| term:transparency | float64 | legt die Hintergrundtransparenz des Terminal-Themes fest (Standard 0.5, 0 = nicht transparent, 1.0 = vollständig transparent) | +| term:allowbracketedpaste | bool | Bracketed-Paste-Modus im Terminal erlauben (Standard false) | +| term:shiftenternewline | bool | wenn aktiviert, sendet Umschalt+Enter Escape-Sequenz + Zeilenumbruch (\n) statt Wagenrücklauf, nützlich für Claude Code und ähnliche KI-Coding-Tools (Standard false) | +| term:macoptionismeta | bool | unter macOS die Option-Taste für Terminal-Tastenkombinationen als Meta-Taste behandeln (Standard false) | +| term:cursor | string | Terminal-Cursor-Stil. Gültige Werte sind `block` (Standard), `underline` und `bar` | +| term:cursorblink | bool | wenn aktiviert, blinkt der Terminal-Cursor (Standard false) | +| term:bellsound | bool | wenn aktiviert, wird beim Empfang der Terminal-Glocke (BEL-Zeichen) der System-Signalton abgespielt (Standard false) | +| term:bellindicator | bool | wenn aktiviert, wird beim Empfang der Terminal-Glocke ein visueller Indikator im Tab angezeigt (Standard false) | +| term:osc52 | string | steuert das OSC-52-Zwischenablageverhalten: `always` (Standard, erlaubt OSC 52 jederzeit) oder `focus` (erfordert fokussiertes Fenster und fokussierten Block) | +| term:durable | bool | macht Remote-Terminal-Sitzungen über Netzwerkabbrüche hinweg dauerhaft (Standard false) | +| term:showsplitbuttons | bool | wenn aktiviert, werden horizontale und vertikale Split-Buttons in der Terminal-Block-Kopfzeile angezeigt (Standard false) | +| editor:minimapenabled | bool | auf false setzen, um die Editor-Minimap zu deaktivieren | +| editor:stickyscrollenabled | bool | aktiviert die stickyScroll-Funktion des Monaco-Editors (heftet Kopfzeilen des aktuellen Kontexts an, z. B. Klassennamen, Methodennamen usw.), Standard false | +| editor:wordwrap | bool | auf true setzen, um Zeilenumbruch im Editor zu aktivieren (Standard false) | +| editor:fontsize | float64 | legt die Schriftgröße für den Editor fest (Standard 12px) | +| editor:inlinediff | bool | auf true setzen, um Diffs inline statt nebeneinander anzuzeigen, false für nebeneinander (Standard ist undefined, wodurch Monacos responsives Verhalten genutzt wird) | +| preview:showhiddenfiles | bool | auf false setzen, um versteckte Dateien in der Verzeichnisvorschau auszublenden (Standard true) | +| preview:defaultsort | string | legt die Standard-Sortierspalte für die Verzeichnisvorschau fest. `"name"` (Standard) sortiert alphabetisch aufsteigend nach Namen; `"modtime"` sortiert nach letzter Änderungszeit absteigend (neueste zuerst) | +| markdown:fontsize | float64 | Schriftgröße für normalen Text beim Rendern von Markdown in der Vorschau. Überschriften werden davon ausgehend hochskaliert (Standard 14px) | +| markdown:fixedfontsize | float64 | Schriftgröße für Codeblöcke beim Rendern von Markdown in der Vorschau (Standard 12px) | +| web:openlinksinternally | bool | auf false setzen, um Weblinks im externen Browser zu öffnen | +| web:defaulturl | string | Standard-Webseite, die im Web-Widget geöffnet wird, wenn keine URL angegeben ist (Startseite) | +| web:defaultsearch | string | Vorlage für Websuchen. z. B. `https://www.google.com/search?q={query}`. "\{query}" wird durch den Suchbegriff ersetzt | +| autoupdate:enabled | bool | Aktualisierungssuche aktivieren/deaktivieren (erfordert App-Neustart) | +| autoupdate:intervalms | float64 | Wartezeit in Millisekunden zwischen Aktualisierungsprüfungen (erfordert App-Neustart) | +| autoupdate:installonquit | bool | ob Updates beim Beenden automatisch installiert werden sollen (erfordert App-Neustart) | +| autoupdate:channel | string | der Auto-Update-Kanal "latest" (stabile Builds) oder "beta" (häufiger aktualisiert) (erfordert App-Neustart) | +| tab:preset | string | eine "bg@"-Voreinstellung, die automatisch auf neue Tabs angewendet wird. z. B. `bg@green`. sollte dem Voreinstellungsschlüssel entsprechen. Veraltet zugunsten von `tab:background` | +| tab:background | string | eine "bg@"-Voreinstellung, die automatisch auf neue Tabs angewendet wird. z. B. `bg@green`. sollte dem Voreinstellungsschlüssel entsprechen | +| tab:confirmclose | bool | wenn auf true gesetzt, wird vor dem Schließen eines Tabs ein Bestätigungsdialog angezeigt (Standard false) | +| widget:showhelp | bool | ob Hilfe-/Tipp-Widgets in der rechten Seitenleiste angezeigt werden sollen | +| window:transparent | bool | auf true setzen, um Fenstertransparenz zu aktivieren (kann nicht mit `window:blur` kombiniert werden) (nur macOS und Windows, erfordert App-Neustart, siehe [Hinweis zur Windows-Kompatibilität](https://www.electronjs.org/docs/latest/tutorial/custom-window-styles#limitations)) | +| window:blur | bool | aktiviert die Weichzeichnung des Fensterhintergrunds (kann nicht mit `window:transparent` kombiniert werden) (nur macOS und Windows, erfordert App-Neustart, siehe [Hinweis zur Windows-Kompatibilität](https://www.electronjs.org/docs/latest/tutorial/custom-window-styles#limitations)) | +| window:opacity | float64 | 0-1, Fensterdeckkraft, wenn `window:transparent` oder `window:blur` gesetzt sind | +| window:bgcolor | string | legt die Fensterhintergrundfarbe fest (sollte Hex sein: #xxxxxx) | +| window:reducedmotion | bool | auf true setzen, um die meisten Animationen zu deaktivieren | +| window:tilegapsize | int | ändert den Standard-Abstand (in CSS-Pixeln) zwischen Blöcken | +| window:magnifiedblockopacity | float64 | ändert die Deckkraft eines vergrößerten Blocks (muss zwischen 0 und 1 liegen, Standard 0.6) | +| window:magnifiedblocksize | float64 | ändert die Größe eines vergrößerten Blocks als Prozentsatz der Abmessungen seines übergeordneten Layouts (muss zwischen 0 und 1 liegen, Standard 0.9) | +| window:magnifiedblockblurprimarypx | int | ändert die Weichzeichnung in CSS-Pixeln, die direkt hinter einem vergrößerten Block angewendet wird (siehe [backdrop-filter](https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter) für weitere Infos, wie das angewendet wird) | +| window:magnifiedblockblursecondarypx | int | ändert die Weichzeichnung in CSS-Pixeln, die auf die sichtbaren Bereiche nicht vergrößerter Blöcke angewendet wird, wenn ein Block vergrößert ist (siehe [backdrop-filter](https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter) für weitere Infos, wie das angewendet wird) | +| window:maxtabcachesize | int | Anzahl der zwischengespeicherten Tabs. Bei zwischengespeicherten Tabs ist der Wechsel zwischen ihnen sehr schnell. (Standard 10) | +| window:showmenubar | bool | zur Nutzung der nativen Betriebssystem-Menüleiste (nur Windows und Linux, erfordert App-Neustart) | +| window:nativetitlebar | bool | zur Nutzung der nativen Betriebssystem-Titelleiste statt des Overlays (nur Windows und Linux, erfordert App-Neustart) | +| window:disablehardwareacceleration | bool | deaktiviert die Chromium-Hardwarebeschleunigung, um grafische Fehler zu beheben (erfordert App-Neustart) | +| window:fullscreenonlaunch | bool | auf true setzen, um das Vordergrundfenster im Vollbildmodus zu starten (Standard false) | +| window:savelastwindow | bool | wenn `true`, bleibt das zuletzt geschlossene Fenster erhalten und wird beim nächsten App-Start wieder geöffnet (Standard `true`) | +| window:confirmonclose | bool | wenn `true`, wird der Nutzer aufgefordert zu bestätigen, dass er ein Fenster schließen möchte, wenn es einen ungespeicherten Workspace mit mehr als einem Tab hat (Standard `true`) | +| window:dimensions | string | legt die Standardabmessungen für neue Fenster im Format "BREITExHÖHE" fest (z. B. "1920x1080"). Beim Erstellen eines neuen Fensters werden diese Abmessungen automatisch angewendet. Breite und Höhe sollten in Pixeln angegeben werden. | +| telemetry:enabled | bool | zum Aktivieren/Deaktivieren von Telemetrie | + +Als Referenz, dies ist die aktuelle Standardkonfiguration (v0.14.0): + +```json +{ + "ai:preset": "ai@global", + "ai:model": "gpt-5-mini", + "ai:maxtokens": 4000, + "ai:timeoutms": 60000, + "app:defaultnewblock": "term", + "app:confirmquit": true, + "app:hideaibutton": false, + "app:disablectrlshiftarrows": false, + "app:disablectrlshiftdisplay": false, + "app:focusfollowscursor": "off", + "autoupdate:enabled": true, + "autoupdate:installonquit": true, + "autoupdate:intervalms": 3600000, + "conn:askbeforewshinstall": true, + "conn:wshenabled": true, + "editor:minimapenabled": true, + "web:defaulturl": "https://github.com/wavetermdev/waveterm", + "web:defaultsearch": "https://www.google.com/search?q={query}", + "window:tilegapsize": 3, + "window:maxtabcachesize": 10, + "window:nativetitlebar": true, + "window:magnifiedblockopacity": 0.6, + "window:magnifiedblocksize": 0.9, + "window:magnifiedblockblurprimarypx": 10, + "window:fullscreenonlaunch": false, + "window:magnifiedblockblursecondarypx": 2, + "window:confirmclose": true, + "window:savelastwindow": true, + "telemetry:enabled": true, + "term:bellsound": false, + "term:bellindicator": false, + "term:osc52": "always", + "term:cursor": "block", + "term:cursorblink": false, + "term:copyonselect": true, + "term:durable": false, + "waveai:showcloudmodes": true, + "waveai:defaultmode": "waveai@balanced", + "preview:defaultsort": "name" +} +``` + +:::warning + +Wenn du Wave vor v0.9.0 installiert hast, befindet sich deine Konfigurationsdatei unter +`~/.waveterm/config/settings.json`. Das gilt auch für alle anderen Konfigurationsdateien: +`termthemes.json`, `presets.json` und `widgets.json`. + +::: + +## Auflösung von Umgebungsvariablen + +Um Secrets nicht direkt in Konfigurationsdateien zu speichern, unterstützt Wave die Auflösung von Umgebungsvariablen über die Syntax `$ENV:VARIABLE_NAME` oder `$ENV:VARIABLE_NAME:fallback`. Dies funktioniert für jeden String-Wert in jeder Konfigurationsdatei (settings.json, presets.json, ai.json usw.). + +```json +{ + "ai:apitoken": "$ENV:OPENAI_APIKEY", + "ai:baseurl": "$ENV:AI_BASEURL:https://api.openai.com/v1" +} +``` + +## WebBookmarks-Konfiguration + +Mit WebBookmarks kannst du Weblinks mit anpassbaren Anzeigeeinstellungen speichern und verwalten. Die Lesezeichen werden in einer JSON-Datei (`bookmarks.json`) als Schlüssel-Wert-Map gespeichert, wobei der Schlüssel (`id`) ein beliebiger Bezeichner für das Lesezeichen ist. Üblicherweise sollten deine IDs mit "bookmark@" beginnen. Im Web-Widget kannst du deine Lesezeichen mit aufrufen. + +### Lesezeichen-Struktur + +Jedes Lesezeichen folgt dieser Struktur (nur `url` ist erforderlich): + +```json +{ + "url": "https://example.com", + "title": "Example Site", + "iconurl": "https://example.com/custom-icon.png", + "display:order": 1 +} +``` + +### Felder + +| Feld | Typ | Beschreibung | +| ------------- | ------- | ----------------------------------------------------------------------------------------------------------------- | +| url | string | **Erforderlich.** Die URL des Lesezeichens. | +| title | string | **Optional.** Ein Anzeigetitel für das Lesezeichen. | +| icon | string | **Optional, selten genutzt.** Überschreibt das Standard-Favicon mit einem Icon-Namen. | +| iconcolor | string | **Optional, selten genutzt.** Legt eine benutzerdefinierte Farbe für das angegebene Icon fest. | +| iconurl | string | **Optional.** Gibt eine benutzerdefinierte Icon-URL an, nützlich, wenn das Favicon nicht korrekt ist (z. B. für Kompatibilität mit dem Dark Mode). | +| display:order | float64 | **Optional.** Legt die Reihenfolge fest, in der Lesezeichen erscheinen. | + +### Beispiel `bookmarks.json` + +```json +{ + "bookmark@google": { + "url": "https://www.google.com", + "title": "Google" + }, + "bookmark@claude": { + "url": "https://claude.ai", + "title": "Claude AI" + }, + "bookmark@wave": { + "url": "https://waveterm.dev", + "title": "Wave Terminal", + "display:order": -1 + }, + "bookmark@wave-github": { + "url": "https://github.com/wavetermdev/waveterm", + "title": "Wave Github", + "iconurl": "https://github.githubassets.com/favicons/favicon-dark.png" + }, + "bookmark@chatgpt": { + "url": "https://chatgpt.com", + "iconurl": "https://cdn.oaistatic.com/assets/favicon-miwirzcw.ico" + }, + "bookmark@wave-pulls": { + "url": "https://github.com/wavetermdev/waveterm/pulls", + "title": "Wave Pull Requests", + "iconurl": "https://github.githubassets.com/favicons/favicon-dark.png" + } +} +``` + +### Verhalten + +- Ist `iconurl` gesetzt, wird das Icon von der angegebenen URL statt vom Standard-Favicon der Seite geladen. +- Lesezeichen werden nach `display:order` sortiert (falls angegeben), sonst nach ID. +- `icon` und `iconcolor` werden selten benötigt, da standardmäßig das Favicon der Seite geladen wird. +- Favicons werden alle 24 Stunden aktualisiert + +## Terminal-Theming + +Benutzerdefinierte Terminal-Themes befinden sich unter `~/.config/waveterm/termthemes.json`. + +Diese JSON-Datei ist als Objekt strukturiert, wobei jeder Unterschlüssel ein Theme definiert. +Themes werden angewendet, indem du mit der rechten Maustaste auf die Kopfzeile des Terminals klickst und einen Eintrag aus dem Untermenü "Themes" auswählst. Alternativ können sie auf +den Metadaten-Schlüssel `term:theme` des Blocks angewendet werden. Dabei wird der JSON-Schlüsselwert als Bezeichner genutzt. Beachte, dass für beste Konsistenz alle Farben im Format "#rrggbb" oder "#rrggbbaa" (aa = Alphakanal für Transparenz) angegeben werden sollten. + +``` +wsh setmeta this term:theme="default-dark" +``` + +Hier ist ein Beispiel für die Definition eines vollständigen Terminal-Themes. Alle integrierten Themes sind hier definiert: https://github.com/wavetermdev/waveterm/blob/main/pkg/wconfig/defaultconfig/termthemes.json (wenn du ein beliebtes Terminal-Theme hinzufügen möchtest, reiche bitte einen PR ein!) + +```json +{ + "default-dark": { + "display:name": "Default Dark", + "display:order": 1, + "black": "#757575", + "red": "#cc685c", + "green": "#76c266", + "yellow": "#cbca9b", + "blue": "#85aacb", + "magenta": "#cc72ca", + "cyan": "#74a7cb", + "white": "#c1c1c1", + "brightBlack": "#727272", + "brightRed": "#cc9d97", + "brightGreen": "#a3dd97", + "brightYellow": "#cbcaaa", + "brightBlue": "#9ab6cb", + "brightMagenta": "#cc8ecb", + "brightCyan": "#b7b8cb", + "brightWhite": "#f0f0f0", + "gray": "#8b918a", + "cmdtext": "#f0f0f0", + "foreground": "#c1c1c1", + "selectionBackground": "", + "background": "#00000077", + "cursorAccent": "" + } +} +``` + +:::info + +Du kannst die Konfigurationsdatei termthemes.json einfach öffnen, indem du Folgendes ausführst: + +``` +wsh editconfig termthemes.json +``` + +::: + +| Schlüsselname | Typ | ANSI FG# | ANSI BG# | Funktion | +| ------------------- | --------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| display:name | string | | | der im UI-Kontextmenü angezeigte Name | +| display:order | float | | | Einträge im Kontextmenü werden nach display:order sortiert | +| black | CSS-Farbe | 30 | 40 | Farbe für Schwarz | +| red | CSS-Farbe | 31 | 41 | Farbe für Rot | +| green | CSS-Farbe | 32 | 42 | Farbe für Grün | +| yellow | CSS-Farbe | 33 | 43 | Farbe für Gelb | +| blue | CSS-Farbe | 34 | 44 | Farbe für Blau | +| magenta | CSS-Farbe | 35 | 45 | Farbe für Magenta | +| cyan | CSS-Farbe | 36 | 46 | Farbe für Cyan | +| white | CSS-Farbe | 37 | 47 | Farbe für Weiß | +| brightBlack | CSS-Farbe | 90 | 100 | Farbe für helles Schwarz | +| brightRed | CSS-Farbe | 91 | 101 | Farbe für helles Rot | +| brightGreen | CSS-Farbe | 92 | 102 | Farbe für helles Grün | +| brightYellow | CSS-Farbe | 93 | 103 | Farbe für helles Gelb | +| brightBlue | CSS-Farbe | 94 | 104 | Farbe für helles Blau | +| brightMagenta | CSS-Farbe | 95 | 105 | Farbe für helles Magenta | +| brightCyan | CSS-Farbe | 96 | 106 | Farbe für helles Cyan | +| brightWhite | CSS-Farbe | 97 | 107 | Farbe für helles Weiß | +| gray | CSS-Farbe | | | aktuell ungenutzt | +| cmdtext | CSS-Farbe | | | aktuell ungenutzt | +| foreground | CSS-Farbe | | | Vordergrundfarbe (Standard, wenn kein Farbcode angewendet wird) | +| background | CSS-Farbe | | | Hintergrundfarbe (Standard, wenn kein Farbcode angewendet wird), muss einen Alphakanal (#rrggbbaa) haben, wenn das Terminal transparent sein soll | +| cursorAccent | CSS-Farbe | | | Farbe für den Cursor | +| selectionBackground | CSS-Farbe | | | Hintergrundfarbe für ausgewählten Text | + +## Anpassbare systemweite globale Tastenkombination + +Wave erlaubt es, eine benutzerdefinierte globale Tastenkombination festzulegen, um von überall auf deinem Computer dein zuletzt genutztes Fenster zu öffnen. Diese heißt `"app:globalhotkey"` in der `settings.json`-Datei und besteht aus einer Reihe von Tastennamen, getrennt durch das Zeichen `:`. + +### Beispiele + +Als praktisches Beispiel: Angenommen, du möchtest `F5` als globale Tastenkombination nutzen. Dann kannst du einfach den Wert von `"app:globalhotkey"` auf `"F5"` setzen und Wave neu starten, damit dies deine globale Tastenkombination wird. + +Als weniger praktisches Beispiel: Angenommen, du nutzt die Kombination der Tasten `Ctrl`, `Option` und `e`. Dann wäre der Wert für diese Tastenkombination `"Ctrl:Option:e"`. + +### Erlaubte Tastennamen + +Wir unterstützen folgende Tastennamen: + +- `Ctrl` +- `Cmd` +- `Shift` +- `Alt` +- `Option` +- `Meta` +- `Super` +- Ziffern (nicht Numpad), dargestellt durch `c{Digit0}` bis `c{Digit9}` +- Buchstaben `a` bis `z` +- F-Tasten `F1` bis `F20` +- Soft-Tasten `Soft1` bis `Soft4`. Diese sind im Wesentlichen identisch mit `F21` bis `F24`. +- Leertaste, dargestellt als `Space` oder ein wörtliches Leerzeichen    +- `Enter` (auf dem Mac als "Return" beschriftet) +- `Tab` +- `CapsLock` +- `NumLock` +- `Backspace` (auf dem Mac als "Delete" beschriftet) +- `Delete` +- `Insert` +- Die Pfeiltasten `ArrowUp`, `ArrowDown`, `ArrowLeft` und `ArrowRight` +- `Home` +- `End` +- `PageUp` +- `PageDown` +- `Esc` +- Lautstärkeregler `AudioVolumeUp`, `AudioVolumeDown`, `AudioVolumeMute` +- Medien-Steuerung `MediaTrackNext`, `MediaTrackPrevious`, `MediaPlayPause` und `MediaStop` +- `PrintScreen` +- Numpad-Tasten, dargestellt durch `c{Numpad0}` bis `c{Numpad9}` +- Das Numpad-Komma, dargestellt durch `Decimal` +- Das Numpad-Plus/Addieren, dargestellt durch `Add` +- Das Numpad-Minus/Subtrahieren, dargestellt durch `Subtract` +- Der Numpad-Stern/Multiplizieren, dargestellt durch `Multiply` +- Der Numpad-Schrägstrich/Dividieren, dargestellt durch `Divide` + +
diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/connections.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/connections.mdx new file mode 100644 index 0000000000..cde90bdce8 --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/connections.mdx @@ -0,0 +1,284 @@ +--- +sidebar_position: 3.1 +id: "connections" +title: "Verbindungen" +--- + +import { VersionBadge } from "@site/src/components/versionbadge"; + +# Verbindungen + +Wave erlaubt es Nutzern, sich mit verschiedenen Rechnern zu verbinden und sie so zusammenzuführen, dass das jeweils eigene Verhalten erhalten bleibt. Aktuell umfasst dies SSH-Remote-Verbindungen und lokale WSL-Verbindungen. + +## Auf eine Verbindung in einem Block zugreifen + +Der einfachste Weg, auf Verbindungen zuzugreifen, ist ein Klick auf das -Icon. Von dort aus kannst du je nach gewünschter Verbindung Folgendes eingeben: + +Für SSH-Verbindungen: + +- `[user]@[host]` +- `[host]` +- `[user]@[host]:[port]` + +Für WSL-Verbindungen: + +- `wsl://` + +Alternativ kannst du, wenn die Verbindung bereits in der Dropdown-Liste vorhanden ist, entweder darauf klicken oder mit den Pfeiltasten dorthin navigieren und Enter drücken, um dich zu verbinden. + +![a dropdown showing a list of connections that already exist](./img/connection-dropdown.png) + +## Verschiedene Arten von Verbindungen + +Da es mehrere verschiedene Verbindungsarten gibt, haben nicht alle Zugriff auf dieselben Funktionen. SSH- und WSL-Verbindungen funktionieren immer in Terminal-Widgets, und wenn die `wsh`-Shell-Erweiterungen installiert sind, funktionieren sie auch in Vorschau-Widgets und im sysinfo-Widget. + +## Was sind wsh-Shell-Erweiterungen? + +`wsh` ist ein kleines Programm, das bei der Verwaltung von waveterm hilft, unabhängig davon, mit welchem Rechner du gerade verbunden bist. Es ist immer auf deinem Host-Rechner enthalten, du hast aber auch die Möglichkeit, es bei SSH- und WSL-Verbindungen zu installieren. Wenn es auf der Verbindung installiert ist, befindet es sich unter `~/.waveterm/bin/wsh`. Wenn Wave sich dann mit deiner Verbindung verbindet (und nur dann), passiert Folgendes: + +- `~/.waveterm/bin` wird für diese einzelne Sitzung zu deinem `PATH` hinzugefügt. So kann der Nutzer den Befehl `wsh` nutzen, ohne den vollständigen Pfad angeben zu müssen. +- Mehrere Umgebungsvariablen werden in die Sitzung eingefügt, um bestimmte Aufgaben mit `wsh` zu erleichtern. Diese sind [unten aufgeführt](#zusätzliche-umgebungsvariablen). +- Die benutzerdefinierten Umgebungsvariablen im Eintrag `cmd:env` von `connections.json` werden in die Sitzung eingefügt. +- Die in `connections.json` festgelegten benutzerdefinierten Initialisierungsskripte werden ausgeführt. Weitere Informationen zu diesen Skripten findest du im Abschnitt unten. + +Falls dies aus irgendeinem Grund fehlschlägt, versucht Wave, ohne `wsh` zu arbeiten. Das erkennst du an einem kleinen ****-Icon in der Block-Kopfzeile. Weitere Informationen dazu, was `wsh` kann, findest du unter [wsh-Befehl](/wsh). Und wenn du den Quellcode von `wsh` einsehen möchtest, findest du ihn [hier](https://github.com/wavetermdev/waveterm/tree/main/cmd/wsh). + +Mit installiertem `wsh` kannst du bestimmte Widgets vom entfernten Rechner so ansehen, als wäre er dein Host, zum Beispiel die `files`- und `sysinfo`-Widgets. Zusätzlich kann `wsh` genutzt werden, um Widgets über verschiedene Rechner hinweg zu beeinflussen. Als einfaches Beispiel kannst du ein Widget auf dem Host-Rechner schließen, indem du den `wsh`-Befehl in einem Terminal-Fenster auf einem entfernten Rechner nutzt. Weitere Informationen dazu, was du mit `wsh` erreichen kannst, findest du [hier](/wsh). + +### Zusätzliche Umgebungsvariablen + +Wie oben erwähnt, fügt `wsh` zur Vereinfachung einige Umgebungsvariablen in Remote-Sitzungen ein. Diese sind unten aufgeführt: + +| Variablenname | Beschreibung | +| -------------------- | ----------------------------------------------------------------------------- | +| TERM_PROGRAM | Wird in Wave auf `waveterm` gesetzt. | +| WAVETERM | Wird in Wave auf 1 gesetzt. | +| WAVETERM_BLOCKID | Die ID des Blocks, der dein aktuelles Terminal-Widget enthält. | +| WAVETERM_CLIENTID | Die ID des RPC-Clients, der von deinem aktuellen Terminal-Widget genutzt wird. | +| WAVETERM_CONN | Der Name der von deinem aktuellen Terminal-Widget genutzten Remote-Verbindung. | +| WAVETERM_TABID | Die ID des Tabs, der dein aktuelles Terminal-Widget enthält. | +| WAVETERM_VERSION | Die aktuelle Semver-Version von Wave. | +| WAVETERM_WORKSPACEID | Die ID des Workspace, der dein aktuelles Terminal-Widget enthält. | + +# Initialisierungsskripte + +Wave bietet dir Optionen, um beim Verbinden mit deinen entfernten Rechnern Initialisierungsskripte auszuführen. Diese werden in `connections.json` definiert und können entweder der Pfad zu einem Skript oder ein kurzes, direkt in der Datei geschriebenes Skript sein. Sind mehrere Skripte definiert, wird das spezifischste für die aktuelle Shell relevante Skript angewendet. Die Schlüsselwörter für die Skripte sind: + +| Skript-Schlüsselwort | Shells, in denen es angewendet wird | +| ------------------- | -------------------- | +| cmd:initscript | alle Shells | +| cmd:initscript.sh | bash und zsh | +| cmd:initscript.bash | bash | +| cmd:initscript.zsh | zsh | +| cmd:initscript.pwsh | pwsh | +| cmd:initscript.fish | fish | + +## Eine neue Verbindung zum Dropdown hinzufügen + +Die standardmäßig in das Dropdown geladenen SSH-Werte werden durch Auswertung der internen Datei `config/connections.json` sowie deiner Dateien `~/.ssh/config` und `/etc/ssh/ssh_config` ermittelt. Eine neue Verbindung kann auf verschiedene Arten hinzugefügt werden: + +- Hinzufügen eines neuen `Host`-Eintrags in einer deiner ssh-Konfigurationsdateien, typischerweise `~/.ssh/config` +- Hinzufügen eines neuen Eintrags in der internen Datei `config/connections.json` +- Manuelles Eintippen deiner Verbindung im Verbindungsfeld (bei erfolgreicher Verbindung wird sie zur internen Datei `config/connections.json` hinzugefügt) +- Nutzung von `wsh ssh [user]@[host]` in deinem Terminal (bei erfolgreicher Verbindung wird sie zur internen Datei `config/connections.json` hinzugefügt) + +WSL-Verbindungen werden durch Durchsuchen der installierten WSL-Distributionen hinzugefügt, wie sie in der Windows-Registry erscheinen. Sie befinden sich, ähnlich wie SSH-Verbindungen, ebenfalls in der Datei `config/connections.json`. + +## SSH-Konfigurationsanalyse + +Aktuell können wir jede SSH-Konfigurationsdatei auswerten, die das Schlüsselwort `Match` nicht enthält. Dieses Schlüsselwort ist mit einer von uns genutzten Bibliothek nicht kompatibel, aber wir hoffen, das bald zu beheben. Während alle anderen gültigen Schlüsselwörter ausgewertet werden, unterstützen wir aktuell nur die Funktionalität einer kleinen Teilmenge davon: +| Schlüsselwort | Beschreibung | +|---------|-------------| +| Host | Das Muster, das beim Verbindungsversuch über `[user]@[host]` abgeglichen wird. Wir listen Hosts auf, die keine Platzhalterzeichen (`*`, `?` oder `!`) enthalten. Auch wenn ein Host-Muster Platzhalter enthält, wird es bei der Ermittlung der zugehörigen Werte wie gewohnt ausgewertet.| +| User | Der Benutzer der SSH-Remote-Verbindung. Wird standardmäßig auf den aktuellen Benutzer auf dem lokalen Rechner gesetzt, wenn nicht angegeben.| +|HostName| Der tatsächliche Hostname des Rechners, mit dem man sich verbindet. Auf Wunsch kann auch eine IP-Adresse verwendet werden. Wird standardmäßig auf den Host gesetzt, wenn nicht angegeben. +| Port | Der Port, über den die Verbindung zum entfernten Rechner hergestellt wird. Standard ist `22`, wenn nicht angegeben.| +| IdentityFile | Kann pro Host mehrfach angegeben werden. Gibt den Pfad zu einer privaten Identitätsdatei (id_rsa, id_ed25519, id_ecdsa usw.) an, die zur Authentifizierung der Verbindung genutzt wird. Jede wird der Reihe nach ausprobiert, und sie können bei Bedarf mit einer Passphrase verschlüsselt sein. Ist kein Wert gesetzt, wird standardmäßig der Reihe nach Folgendes versucht: ~/.ssh/id_rsa, ~/.ssh/id_ecdsa, ~/.ssh/id_ecdsa_sk, ~/.ssh/id_ed25519_sk, ~/.ssh/id_dsa.| +|BatchMode| Wenn auf true gesetzt, wird die Benutzerinteraktion über Passwort-, Challenge/Response- und Public-Key-Passphrase-Authentifizierung deaktiviert. Standardmäßig false.| +|PubkeyAuthentication| (teilweise) Gibt an, ob Public-Key-Authentifizierung versucht werden soll. Teilweise implementiert, da die Werte `unbound` und `host-bound` sich einfach wie `yes` verhalten. Standard ist `yes`.| +|PasswordAuthentication| Gibt an, ob Passwort-Authentifizierung versucht werden soll. Standard ist `yes`.| +|KbdInteractiveAuthentication| Gibt an, ob tastaturinteraktive Authentifizierung versucht werden soll. Standard ist `yes`.| +|PreferredAuthentications| (teilweise) Gibt die Reihenfolge an, in der der Client Authentifizierungen versuchen soll. Teilweise implementiert, da `gssapi-with-mic` oder `hostbased`-Authentifizierung nicht unterstützt werden. Standard ist `publickey,keyboard-interactive,password`| +|AddKeysToAgent| (teilweise) Diese Option fügt Schlüssel und ihre zugehörige Passphrase automatisch zu deinem laufenden ssh-Agenten hinzu, sofern aktiviert. Teilweise unterstützt, da nur `yes` und `no` als gültige Eingaben akzeptiert werden. Andere Eingaben wie `confirm` oder ein Zeitintervall verhalten sich wie `no`. Standardwert ist `no`.| +|IdentityAgent| Gibt den Unix-Domain-Socket an, der zur Kommunikation mit dem SSH-Agenten genutzt wird. Damit kann der Identity-Agent `SSH_AUTH_SOCK` überschrieben werden.| +|IdentitiesOnly| Gibt an, dass nur die angegebenen Authentifizierungs-Identitätsdateien genutzt werden sollen. Das sind entweder die Standarddateien oder die mit dem Schlüsselwort IdentityFile angegebenen. Akzeptiert `yes` oder `no`. Standardwert ist `no`.| +|ProxyJump| Gibt einen oder mehrere Jump-Proxys als kommagetrennte Liste an. Jeder wird nacheinander per TCP-Weiterleitung angesteuert, bevor die gewünschte Verbindung (ebenfalls per TCP-Weiterleitung) hergestellt wird. Kann auf `none` gesetzt werden, um die Funktion zu deaktivieren.| +|UserKnownHostsFile| Gibt den Speicherort einer oder mehrerer Host-Key-Datenbankdateien des Benutzers zur Aufzeichnung vertrauenswürdiger Remote-Verbindungen an. Die Dateinamen werden im selben String, durch Leerzeichen getrennt, angegeben. Standardwert ist `"~/.ssh/known_hosts ~/.ssh/known_hosts2"`.| +|GlobalKnownHostsFile| Gibt den Speicherort einer oder mehrerer globaler Host-Key-Datenbankdateien zur Aufzeichnung vertrauenswürdiger Remote-Verbindungen an. Die Dateinamen werden im selben String, durch Leerzeichen getrennt, angegeben. Standardwert ist `"/etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts2"`.| + +### Beispiel für einen SSH-Konfigurationshost + +Als kurzes Beispiel könnte ein Host in deiner Konfigurationsdatei so aussehen: + +``` +Host myhost + User username + HostName 203.0.113.254 + IdentityFile ~/.ssh/id_rsa + AddKeysToAgent yes +``` + +Du könntest dann mit `myhost` oder `username@myhost` auf diese Verbindung zugreifen. Und wenn du manuell einen Port angeben möchtest, z. B. Port 2222, kannst du das entweder tun, indem du `Port 2222` zur Konfigurationsdatei hinzufügst, oder indem du dich mit `username@myhost:2222` verbindest. + +## Interne SSH-Konfiguration + +Zusätzlich zur regulären ssh-Konfigurationsdatei hat Wave auch eine eigene Konfigurationsdatei zur Verwaltung separater Variablen. Dazu gehören +| Schlüsselwort | Beschreibung | +|---------|-------------| +| conn:wshenabled | Dieser Boolean erlaubt die Nutzung von `wsh` für deine Verbindung. Ist er auf `false` gesetzt, wird `wsh` für diese Verbindung nie genutzt. Standard ist `true`.| +| conn:askbeforewshinstall | Dieser Boolean wird genutzt, um den Benutzer vor der Installation von wsh zu fragen. Ist er auf false gesetzt, wird `wsh` stattdessen automatisch ohne Nachfrage installiert. Standard ist `true`.| +| conn:wshpath | Ein String, der den Pfad zur ausführbaren `wsh`-Datei auf der Verbindung angibt. Standard ist `"~/.waveterm/bin/wsh"`.| +| conn:shellpath | Ein String, der den Pfad zur ausführbaren Shell-Datei auf der Verbindung angibt. Wenn nicht gesetzt, wird die Ausgabe von `$SHELL` auf der Verbindung genutzt.| +| conn:ignoresshconfig | Dieser Boolean erlaubt es Wave, die Datei `~/.ssh/config` bei der Auflösung von Schlüsselwörtern für diese Verbindung zu ignorieren. Die regulären Standardwerte werden genutzt, aber alle Änderungen daran müssen stattdessen in der Datei `connections.json` angegeben werden. Standard ist false.| +| display:hidden | Dieser Boolean blendet die Verbindung aus der Dropdown-Liste aus. Standard ist `false` | +| display:order | Dieser Float bestimmt die Reihenfolge der Verbindungen im Verbindungs-Dropdown. Standard ist `0`.| +| term:fontsize | Dieser Int kann genutzt werden, um die Terminal-Schriftgröße für Blöcke zu überschreiben, die diese Verbindung nutzen. Die Block-Metadaten haben Vorrang vor dieser Einstellung. Standard ist null, was bedeutet, dass stattdessen die globale Einstellung genutzt wird. | +| term:fontfamily | Dieser String kann genutzt werden, um eine Terminal-Schriftart für Blöcke anzugeben, die diese Verbindung nutzen. Die Block-Metadaten haben Vorrang vor dieser Einstellung. Standard ist null, was bedeutet, dass stattdessen die globale Einstellung genutzt wird. | +| term:theme | Dieser String kann genutzt werden, um ein Terminal-Theme für Blöcke anzugeben, die diese Verbindung nutzen. Die Block-Metadaten haben Vorrang vor dieser Einstellung. Standard ist null, was bedeutet, dass stattdessen die globale Einstellung genutzt wird. | +| cmd:env | Ein JSON-Objekt mit Schlüssel-Wert-Paaren von Umgebungsvariablen und dem Wert, auf den sie für diesen Remote-Rechner gesetzt werden sollen. Funktioniert nur, wenn `wsh` aktiviert ist. +| cmd:initscript | Ein Skript oder ein Pfad zu einem Skript, das beim Initialisieren dieser Verbindung mit einer beliebigen Shell ausgeführt wird. Funktioniert nur, wenn `wsh` aktiviert ist. | +| cmd:initscript.sh | Ein Skript oder ein Pfad zu einem Skript, das beim Initialisieren dieser Verbindung mit POSIX-Shells wie `bash` oder `zsh` ausgeführt wird. Funktioniert nur, wenn `wsh` aktiviert ist. +| cmd:initscript.bash | Ein Skript oder ein Pfad zu einem Skript, das beim Initialisieren dieser Verbindung mit der `bash`-Shell ausgeführt wird. Funktioniert nur, wenn `wsh` aktiviert ist. | +| cmd:initscript.zsh | Ein Skript oder ein Pfad zu einem Skript, das beim Initialisieren dieser Verbindung mit der `zsh`-Shell ausgeführt wird. Funktioniert nur, wenn `wsh` aktiviert ist. | +| cmd:initscript.pwsh | Ein Skript oder ein Pfad zu einem Skript, das beim Initialisieren dieser Verbindung mit der `pwsh`-Shell ausgeführt wird. Funktioniert nur, wenn `wsh` aktiviert ist. | +| cmd:initscript.fish | Ein Skript oder ein Pfad zu einem Skript, das beim Initialisieren dieser Verbindung mit der `fish`-Shell ausgeführt wird. Funktioniert nur, wenn `wsh` aktiviert ist. | +| ssh:user | Ein String, der den Benutzernamen der Verbindung angibt. Kann genutzt werden, um den Wert in `~/.ssh/config` zu überschreiben oder ihn zu setzen, wenn die ssh-Konfiguration ignoriert wird.| +| ssh:hostname | Ein String, der den internen Hostnamen der Verbindung angibt. Kann genutzt werden, um den Wert in `~/.ssh/config` zu überschreiben oder ihn zu setzen, wenn die ssh-Konfiguration ignoriert wird.| +| ssh:port | Ein String, der den numerischen Port für die Verbindung angibt. Kann genutzt werden, um den Wert in `~/.ssh/config` zu überschreiben oder ihn zu setzen, wenn die ssh-Konfiguration ignoriert wird.| +| ssh:identityfile | Eine Liste von Strings mit den Pfaden zu genutzten Identitätsdateien. Ist ein `wsh ssh`-Befehl mit dem Flag `-i` erfolgreich, wird die Identitätsdatei automatisch hier hinzugefügt. Diese werden vor den Werten aus `~/.ssh/config` genutzt.| +| ssh:identitiesonly | Ein Boolean, der angibt, ob nur die angegebenen Identitätsdateien genutzt werden sollen. Das bedeutet nur die mit dem Flag `ssh:identityfile` gesetzten Dateien oder die Standardwerte. Kann genutzt werden, um den Wert in `~/.ssh/config` zu überschreiben oder ihn zu setzen, wenn die ssh-Konfiguration ignoriert wird.| +| ssh:batchmode | Ein Boolean, der angibt, ob Passwort- und Passphrase-Abfragen übersprungen werden sollen. Kann genutzt werden, um den Wert in `~/.ssh/config` zu überschreiben oder ihn zu setzen, wenn die ssh-Konfiguration ignoriert wird.| +| ssh:pubkeyauthentication | Ein Boolean, der angibt, ob Public-Key-Authentifizierung aktiviert ist. Kann genutzt werden, um den Wert in `~/.ssh/config` zu überschreiben oder ihn zu setzen, wenn die ssh-Konfiguration ignoriert wird.| +| ssh:passwordauthentication | Ein Boolean, der angibt, ob Passwort-Authentifizierung aktiviert ist. Kann genutzt werden, um den Wert in `~/.ssh/config` zu überschreiben oder ihn zu setzen, wenn die ssh-Konfiguration ignoriert wird. | +| ssh:passwordsecretname | Ein String, der den Namen eines im [Secret-Speicher](/secrets) gespeicherten Secrets angibt, das als SSH-Passwort genutzt werden soll. Ist dieser gesetzt, wird dieses Passwort automatisch für die Passwort-Authentifizierung genutzt, anstatt den Benutzer zu fragen. | +| ssh:kbdinteractiveauthentication | Ein Boolean, der angibt, ob tastaturinteraktive Authentifizierung aktiviert ist. Kann genutzt werden, um den Wert in `~/.ssh/config` zu überschreiben oder ihn zu setzen, wenn die ssh-Konfiguration ignoriert wird. | +| ssh:preferredauthentications | Eine Liste von Strings, die eine Reihenfolge verschiedener Authentifizierungsarten angibt. Jede Authentifizierungsart wird der Reihe nach versucht. Unterstützt werden `"publickey"`, `"keyboard-interactive"` und `"password"` als gültige Typen. Andere Authentifizierungsarten werden nicht behandelt und übersprungen. Kann genutzt werden, um den Wert in `~/.ssh/config` zu überschreiben oder ihn zu setzen, wenn die ssh-Konfiguration ignoriert wird.| +| ssh:addkeystoagent | Ein Boolean, der angibt, ob die für eine Verbindung genutzten Schlüssel zum ssh-Agenten hinzugefügt werden sollen. Kann genutzt werden, um den Wert in `~/.ssh/config` zu überschreiben oder ihn zu setzen, wenn die ssh-Konfiguration ignoriert wird.| +| ssh:identityagent | Ein String, der den Pfad zum Unix-Domain-Socket des Identity-Agenten angibt. Kann genutzt werden, um den Wert in `~/.ssh/config` zu überschreiben oder ihn zu setzen, wenn die ssh-Konfiguration ignoriert wird.| +| ssh:proxyjump | Eine Liste von Strings, die die Namen von Hosts angibt, die nacheinander per TCP-Weiterleitung angesteuert werden müssen, um eine Verbindung herzustellen. Kann genutzt werden, um den Wert in `~/.ssh/config` zu überschreiben oder ihn zu setzen, wenn die ssh-Konfiguration ignoriert wird.| +| ssh:userknownhostsfile | Eine Liste mit den Pfaden aller Host-Key-Datenbankdateien des Benutzers, die zur Nachverfolgung autorisierter Verbindungen genutzt werden. Kann genutzt werden, um den Wert in `~/.ssh/config` zu überschreiben oder ihn zu setzen, wenn die ssh-Konfiguration ignoriert wird.| +| ssh:globalknownhostsfile | Eine Liste mit den Pfaden aller globalen Host-Key-Datenbankdateien, die zur Nachverfolgung autorisierter Verbindungen genutzt werden. Kann genutzt werden, um den Wert in `~/.ssh/config` zu überschreiben oder ihn zu setzen, wenn die ssh-Konfiguration ignoriert wird.| + +### SSH-Agent-Erkennung + +Wave löst den Pfad des Identity-Agenten in dieser Reihenfolge auf: + +- Ist `ssh:identityagent` (oder `IdentityAgent` in der SSH-Konfiguration) für die Verbindung gesetzt, wird dieser Socket oder diese Pipe genutzt. +- Ist unter Windows nichts gesetzt, greift Wave auf die integrierte OpenSSH-Agenten-Pipe `\\.\pipe\openssh-ssh-agent` zurück. Stelle sicher, dass der Dienst **OpenSSH Authentication Agent** läuft. +- Ist unter macOS/Linux nichts gesetzt, fragt Wave deine Shell-Umgebung nach `SSH_AUTH_SOCK`, um den Agentenpfad automatisch zu erkennen. + +### Beispiele für interne Konfigurationen + +Hier sind ein paar Beispiele für Dinge, die du mit der internen Konfigurationsdatei `connections.json` tun kannst: + +#### Eine Verbindung ausblenden + +Angenommen, du hast in deiner `~/.ssh/config`-Datei eine Verbindung namens `github.com`, die im Verbindungs-Dropdown als `git@github.com` erscheint. Obwohl sie aus Authentifizierungsgründen in die Konfigurationsdatei gehört, ergibt es keinen Sinn, dass sie im Dropdown erscheint, da keine Verbindung zu einer entfernten Umgebung damit hergestellt wird. In diesem Fall kannst du sie wie im folgenden Beispiel ausblenden: + +```json +{ + <... weitere Verbindungen hier ...>, + "git@github.com" : { + "display:hidden": true + }, + <... weitere Verbindungen hier ...> +} +``` + +#### Eine Verbindung verschieben + +Angenommen, du hast eine Verbindung namens `rarelyused`, die im Verbindungs-Dropdown als `myusername@rarelyused:9999` erscheint. Da sie so selten genutzt wird, möchtest du sie weiter hinten in der Liste haben. In diesem Fall kannst du sie wie im folgenden Beispiel verschieben: + +```json +{ + <... weitere Verbindungen hier ...>, + "myusername@rarelyused:9999" : { + "display:order": 100 + }, + <... weitere Verbindungen hier ...> +} +``` + +#### Eine Verbindung mit einem Theme versehen + +Angenommen, du hast eine Verbindung namens `myhost`, die im Verbindungs-Dropdown als `myusername@myhost` erscheint. Du nutzt diese Verbindung häufig, verwechselst sie aber immer wieder mit deinen lokalen Verbindungen. In diesem Fall kannst du die interne Konfigurationsdatei nutzen, um sie anders zu gestalten. Zum Beispiel: + +```json +{ + <... weitere Verbindungen hier ...>, + "myusername@myhost" : { + "term:theme": "warmyellow", + "term:fontsize": 16, + "term:fontfamily": "menlo" + }, + <... weitere Verbindungen hier ...> +} +``` + +Dieses Theme, diese Schriftgröße und diese Schriftart gelten dann nur für Widgets, die diese Verbindung nutzen. + +### Vollständig intern definiert + +Angenommen, du möchtest eine Verbindung einrichten, hast aber keine Lust, die Syntax von `~/.ssh/config` zu lernen. In diesem Fall kannst du die Verbindung vollständig in deiner `connections.json`-Datei definieren. Zum Beispiel: + +```json +{ + <... weitere Verbindungen hier ...>, + "myusername@myhost" : { + "ssh:hostname": "190.0.2.0", + "ssh:identityfile": ["~/.ssh/myidentityfile"], + "ssh:identitiesonly": true, + "ssh:addkeystoagent": true + }, + <... weitere Verbindungen hier ...> +} +``` + +Dadurch wird eine Verbindung erstellt, ohne dass sie in der Datei `~/.ssh/config` vorhanden sein muss. Zusätzlich sind ein paar weitere Optionen als Beispiel gesetzt, wie das gemacht werden kann. + +### wsh für eine Verbindung deaktivieren + +Wave bietet zwar eine Option, `wsh` beim erstmaligen Verbinden mit einem entfernten Rechner zu deaktivieren, es gibt jedoch Fälle, in denen du es nachträglich deaktivieren möchtest. Der einfachste Weg dafür ist, die Datei `connections.json` zu bearbeiten. Angenommen, die Verbindung erscheint im Dropdown als `root@wshless`. Dann kannst du sie manuell mit folgender Zeile deaktivieren: + +```json +{ + <... weitere Verbindungen hier ...>, + "root@wshless" : { + "conn:enablewsh": false, + }, + <... weitere Verbindungen hier ...> +} +``` + +Beachte, dass dieselbe Zeile automatisch zu deiner `connections.json`-Datei hinzugefügt wird, wenn du `wsh` beim erstmaligen Verbinden in der GUI deaktivierst. + +## Verbindungen mit der CLI verwalten + +Der Befehl `wsh` bietet einige speziell für die Interaktion mit Verbindungen gedachte Befehle. Du findest sie [hier](/wsh-reference#conn). + +## Fehlerbehebung bei Verbindungen + +### Log-Dateien + +Gibt es Probleme mit Verbindungen, ist der einfachste erste Schritt, das Debugging in einem Terminal-Widget zu aktivieren, das versucht, sich zu verbinden. Klicke dazu auf den ****-Button und fahre mit der Maus über den Eintrag **`Debug Connection`**. Von dort aus kannst du zwei Log-Level auswählen, `Info` und `Verbose`. Danach werden während der Verbindung Debug-Informationen im Terminal ausgegeben. + +Reicht das nicht aus, kannst du auch die vollständige Log-Datei einsehen. Führe dazu den Befehl `wsh wavepath log` aus, um den Speicherort einer Log-Datei zu erhalten. + +### Bekannte Einschränkungen + +Tritt beim Einrichten von `wsh` ein Fehler auf, startet deine Verbindung trotzdem ohne `wsh`. Abhängig von den Debug-Informationen kann dies jedoch verschiedene Ursachen haben. + +#### Shell-Typ + +Wave kann `wsh` in folgende Shells einfügen: + +- bash +- zsh +- pwsh (powershell) +- fish + +Ist die Shell eine andere, funktioniert der `wsh`-Befehl unter Umständen standardmäßig nicht. Der einfachste Weg, das aktuell zu beheben, ist der Wechsel des Shell-Typs. Das geht, indem du den Wert `conn:shellpath` mit einem Pfad zu einer der oben genannten Shells in der Datei `connections.json` für die genutzte Verbindung setzt. Alternativ kannst du den Befehl `chsh` nutzen, um die Shell in dieser Verbindung zu ändern, was sich aber auch außerhalb von Wave auswirkt. Starte Wave anschließend neu, damit die Änderungen wirksam werden. + +#### AllowTcpForwarding in sshd + +Manche Systeme haben sshd standardmäßig so konfiguriert, dass TCP-Weiterleitung deaktiviert ist. Das findest du auf der Verbindung in der Datei `/etc/ssh/sshd_config`. Suche dort nach der Zeile mit `AllowTcpForwarding`. Ist diese auf `no` gesetzt, ist dies wahrscheinlich der Grund, warum `wsh` auf deiner Verbindung nicht funktioniert. Um `wsh` zum Laufen zu bringen, setze den Wert für `AllowTcpForwarding` auf `yes` oder `local` (beide bieten unterschiedliche Berechtigungsstufen, funktionieren aber in diesem Fall beide). Starte anschließend den `sshd`-Dienst mit der von deinem entfernten Rechner bereitgestellten Methode neu. Starte danach Wave neu, damit es sich mit dieser Änderung neu verbinden kann. diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/customization.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/customization.mdx new file mode 100644 index 0000000000..a4a9407912 --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/customization.mdx @@ -0,0 +1,83 @@ +--- +sidebar_position: 3.2 +id: "customization" +title: "Anpassung" +--- + +## Tab-Themes + +![Tab Context Menu](./img/tab-context-menu.png#right) + +Klicke mit der rechten Maustaste auf einen beliebigen Tab, um ein Menü zu öffnen, in dem du den Tab umbenennen und verschiedene Hintergründe auswählen kannst. + +Es ist auch möglich, eigene Hintergrund-Themes mit benutzerdefinierten Farben, Verläufen, Bildern und mehr zu erstellen, indem du deine Konfigurationsdatei backgrounds.json bearbeitest. Wie Waves integrierte Tab-Hintergründe definiert sind, kannst du in der [Standard-backgrounds.json-Datei](https://github.com/wavetermdev/waveterm/blob/main/pkg/wconfig/defaultconfig/backgrounds.json) nachsehen. + +Um standardmäßig einen Tab-Hintergrund auf alle neuen Tabs anzuwenden, setze den Schlüssel `tab:background` in deiner [Wave-Konfigurationsdatei](/config) auf einen der voreingestellten Hintergrund-Schlüssel (z. B. `"bg@ocean-depths"`). Die verfügbaren integrierten Hintergrund-Schlüssel findest du in der [Standard-backgrounds.json-Datei](https://github.com/wavetermdev/waveterm/blob/main/pkg/wconfig/defaultconfig/backgrounds.json). + +## Terminal-Anpassung + +#### Terminal-Theme + +![Terminal Context Menu](./img/terminal-context-menu.png#right) + +Klicke mit der rechten Maustaste auf den Kopfbereich eines beliebigen Terminal-Blocks, um ein Menü zu öffnen, in dem du ein Terminal-Theme +für dieses Terminal festlegen kannst. + +Du kannst das Standard-Theme für alle Terminals (deren Theme nicht manuell überschrieben wurde) festlegen, indem du deine settings.json-Datei bearbeitest, den Schlüssel `term:theme` hinzufügst und ihn auf den passenden Schlüssel setzt. Die Schlüssel findest du +in der [Standard-termthemes.json-Datei](https://github.com/wavetermdev/waveterm/blob/main/pkg/wconfig/defaultconfig/termthemes.json). + +Wenn du eine eigene termthemes.json-Datei im Konfigurationsverzeichnis hinzufügst, kannst du auch eigene Terminal-Themes erstellen (folge einfach demselben Format). + +#### Schriftgröße + +Über dasselbe Kontextmenü kannst du auch die Schriftgröße des Terminals ändern. Um die Standard-Schriftgröße für alle deine (nicht überschriebenen) Terminals zu ändern, setze den Konfigurationsschlüssel `term:fontsize` auf die gewünschte Größe, z. B. `{ "term:fontsize": 14}`. + +#### Schriftart + +Es gibt keine Benutzeroberfläche, um die Standard-Terminal-Schriftart zu bearbeiten. Sie _kann_ aber überschrieben werden. In deiner settings.json-Datei kannst du den Schlüssel `term:fontfamily` hinzufügen und auf eine Schriftart setzen, die auf deinem lokalen System _installiert_ ist. Wenn du eine nicht installierte oder eine nicht dicktengleiche (non-monospace) Schriftart angibst, sieht dein Terminal schrecklich aus (mach das nicht 🙂) - lösche den Schlüssel, um zur Standardschrift zurückzukehren. + +## Widget-Seitenleiste + +![Terminal Context Menu](./img/custom-widgets.png#right) + +Siehe [Benutzerdefinierte Widgets](/customwidgets) für eine ausführliche Dokumentation dazu, wie du änderst, was in deiner rechten Widget-Seitenleiste erscheint. + +Mit widgets.json kannst du beliebige Standard-Widgets entfernen und eigene Widgets hinzufügen. Du kannst Icons, Farben, Text und Standardwerte (wie Verzeichnisse, Webseiten, KI-Modell, Remote-Verbindung, Befehle usw.) deiner benutzerdefinierten Widgets vollständig anpassen. + +Du kannst die Hilfe-Widgets unten rechts auch ausblenden, indem du den Konfigurationsschlüssel `widget:showhelp` auf `false` setzt. + +## Tab-Hintergründe + +Wave unterstützt leistungsstarke, benutzerdefinierte Hintergründe für deine Tabs mit Bildern, Mustern, Verläufen und Farben. Der schnellste Weg, einen Bildhintergrund festzulegen, ist der Befehl `wsh setbg`: + +```bash +# Bildhintergrund mit 50% Deckkraft (Standard) festlegen +wsh setbg ~/pictures/background.jpg + +# Farbhintergrund festlegen (Anführungszeichen verhindern, dass # als Shell-Kommentar interpretiert wird) +wsh setbg "#ff0000" # Hex-Farbe +wsh setbg forestgreen # CSS-Farbname + +# Deckkraft anpassen +wsh setbg --opacity 0.3 ~/pictures/light-pattern.png +wsh setbg --opacity 0.7 # nur die Deckkraft des aktuellen Hintergrunds ändern + +# Optionen zur Bildpositionierung +wsh setbg --tile ~/pictures/texture.png # gekacheltes Muster erstellen +wsh setbg --center ~/pictures/logo.png # zentrieren ohne Skalierung +wsh setbg --center --size 200px ~/pictures/logo.png # zentrieren mit bestimmter Größe (px, %, auto) + +# Hintergrund entfernen +wsh setbg --clear +``` + +Du kannst jedes JPEG-, PNG-, GIF-, WebP- oder SVG-Bild als Hintergrund verwenden. Die Option `--center` ist besonders nützlich für Logos oder Icons, bei denen die Originalgröße beibehalten werden soll. + +Um die Metadaten für einen Hintergrund in der Vorschau zu sehen, ohne ihn anzuwenden, nutze das Flag `--print`: + +```bash +wsh setbg --print "#ff0000" +``` + +Weitere fortgeschrittene Anpassungsoptionen, einschließlich Verläufe, Farben und das Speichern eigener benutzerdefinierter Hintergründe, findest du in unserer Dokumentation zu [Tab-Hintergründen](/tab-backgrounds). + diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/customwidgets.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/customwidgets.mdx new file mode 100644 index 0000000000..64ed40efd0 --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/customwidgets.mdx @@ -0,0 +1,428 @@ +--- +sidebar_position: 6 +id: "customwidgets" +title: "Benutzerdefinierte Widgets" +--- + +# Benutzerdefinierte Widgets + +Wave erlaubt es Nutzern, eigene Widgets zu erstellen, um ihre Erfahrung individuell an das anzupassen, was für sie funktioniert. Wir planen, dies in Zukunft stark zu erweitern, aber schon jetzt ist es möglich, Widgets zu erstellen, auf die du per Knopfdruck zugreifen kannst. Alle Widgets können durch Bearbeiten der Datei `/config/widgets.json` erstellt werden. Durch Hinzufügen eines Widgets zu dieser Datei kannst du Widgets zur Widget-Leiste hinzufügen. Standardmäßig sieht die Widget-Leiste so aus: +![The default widget bar](./img/all-widgets-default.webp) + +Durch Hinzufügen weiterer Widgets kannst du eine Widget-Leiste erhalten, die so aussieht: + +![A widget bar with custom widgets added](./img/all-widgets-extra.webp) + +## Der Aufbau eines Widgets + +Alle Widgets haben eine ähnliche Struktur, die ungefähr wie im folgenden Beispiel aussieht: + +```json +"": { + "icon": "", + "label": "", + "color": "", + "blockdef": { + "meta": { + "view": "term", + "controller": "cmd", + "cmd": "" + } + } +} +``` + +Das besteht aus mehreren Teilen. Zunächst hat jedes Widget einen eindeutigen Bezeichner. Der zu diesem Namen gehörige Wert ist der äußere `WidgetConfigType`. Er ist unten rot umrandet: + +![An example of a widget with outer keys labeled as WidgetConfigType and inner keys labeled as MetaTSType. In the example, the outer keys are icon, label, color, and blockdef. The inner keys are view, controller, and cmd.](./img/widget-example.webp) + +Dieser `WidgetConfigType` wird von allen Widget-Typen geteilt. Das heißt, alle Widgets — unabhängig vom Typ — nutzen dafür dieselben Schlüssel. Die zulässigen Schlüssel sind: + +| Schlüssel | Beschreibung | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| "display:order" | (optional) Überschreibt die Reihenfolge der Widgets mit einer Zahl, falls du eine andere Reihenfolge als in der Datei `widgets.json` möchtest. Standard ist 0. | +| "icon" | (optional) Der Name eines [Font-Awesome-Icons](#font-awesome-icons). Standard ist `"browser"`. | +| "color" | (optional) Ein String, der eine Farbe wie in CSS darstellt. Hex-Codes und benutzerdefinierte CSS-Eigenschaften sind eingeschlossen. Standard ist `"var(--secondary-text-color)"`, eine Farbe, die Wave für Text nutzt, der sich von anderem Text abheben soll. Werkseitig ist das `"#c3c8c2"`. | +| "label" | (optional) Ein String, der die Beschriftung unter dem Widget darstellt. Er dient auch als Tooltip beim Hovern, wenn der Schlüssel `"description"` nicht ausgefüllt ist. Standardmäßig null. | +| "description" | (optional) Eine Beschreibung dessen, was das Widget tut. Ist sie angegeben, dient sie als Tooltip beim Hovern. Standardmäßig null. | +| "magnified" | (optional) Ein Boolean, der angibt, ob das Widget vergrößert gestartet werden soll. Standardmäßig false. | +| "blockdef" | Hier wird der nicht-visuelle Teil des Widgets definiert. Beachte, dass alle weiteren Definitionen innerhalb eines meta-Objekts in diesem stattfinden. | + + +:::info + +**Font-Awesome-Icons** + +[Font Awesome](https://fontawesome.com/search) bietet eine Vielzahl nützlicher Icons, die du als Widget-Icon in deiner App verwenden kannst. Im einfachsten Fall gibst du nur den Icon-Namen an, und er wird genutzt. Zum Beispiel liefert der String `"house"` ein Icon mit einem Haus. Wir erlauben dir auch, verschiedene Stile auf dein Icon anzuwenden, indem du den Namen wie folgt änderst: + +| Format | Beschreibung | +| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| <icon name> | Das einfache Icon ohne zusätzliche Stile. | +| solid@<icon name> | Fügt dem Icon die Klasse `fa-solid` hinzu, um den Inhalt mit einer Füllfarbe statt eines Hintergrunds zu füllen. | +| regular@<icon name> | Fügt dem Icon die Klasse `fa-regular` hinzu, damit der Inhalt keine Füllfarbe hat und stattdessen eine Standardumrisslinie nutzt. | +| brands@<icon name> | Erforderlich, um einem markenbezogenen Icon die notwendige Klasse `fa-brands` hinzuzufügen. Ohne dies werden Marken-Icons nicht korrekt gerendert. Funktioniert nicht mit Icons, die keine Marken-Icons sind. | + +::: + +Die übrigen Optionen sind Teil des inneren `MetaTSType` (im Bild blau umrandet). Er enthält alle Details dazu, wie das Widget tatsächlich funktioniert. Die gültigen Schlüssel variieren je nach Widget-Typ. Sie werden im Folgenden im Detail behandelt. + +## Terminal- und CLI-Widgets + +Ein Terminal-Widget oder CLI-Widget ist ein Widget, das einfach ein Terminal öffnet und einen CLI-Befehl ausführt. Sie sehen meist etwa so aus wie im Beispiel unten: + +```json +{ + <... other widgets go here ...>, + "": { + "icon": "", + "label": "", + "color": "", + "blockdef": { + "meta": { + "view": "term", + "controller": "cmd", + "cmd": "" + } + } + }, + <... other widgets go here ...> +} +``` + +Der `WidgetConfigType` nutzt die üblichen, allen Widgets gemeinsamen Optionen. Der `MetaTSType` kann die unten aufgeführten Schlüssel enthalten: + +| Schlüssel | Beschreibung | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| "view" | Ein String, der den allgemeinen Widget-Typ angibt. Bei benutzerdefinierten Terminal-Widgets muss dies auf `"term"` gesetzt sein. | +| "controller" | Ein String, der den Typ des genutzten Befehls angibt. Für dauerhaftere Shell-Sitzungen auf "shell" setzen. Für einmalige Befehle auf `"cmd"` setzen. Ist `"cmd"` gesetzt, hat das Widget in seiner Kopfzeile einen zusätzlichen Aktualisieren-Button, der ein erneutes Ausführen des Befehls erlaubt. | +| "cmd" | (optional) Ist `"controller"` auf `"cmd"` gesetzt, gibt diese Option den tatsächlich auszuführenden Befehl an. Da er als Befehl ausgeführt wird, gibt es keine Shell-Sitzung, es sei denn, du startest einen Befehl, der selbst eine Shell-Sitzung enthält. Standard ist ein leerer String. | +| "cmd:args" | (optional, Array von Strings) an `cmd` zu übergebende Argumente | +| "cmd:shell" | (optional) Ist cmd:shell false (Standard), nutzen wir `cmd` + `cmd:args` (geeignet zur Übergabe an `execve`). Ist cmd:shell true, nutzen wir nur `cmd`, und cmd kann Leerzeichen sowie Shell-Syntax (wie Pipes oder Umleitungen usw.) enthalten | +| "cmd:interactive" | (optional) Ist `"controller"` auf `"term"` gesetzt, fügt dieser Boolean dem gestarteten Terminal das Interactive-Flag hinzu. Standard false. | +| "cmd:login" | (optional) Ist `"controller"` auf `"term"` gesetzt, fügt dieser Boolean dem term-Befehl das Login-Flag hinzu. Standard false. | +| "cmd:runonstart" | (optional) Der Befehl wird erneut ausgeführt, wenn der Block erstellt oder die App gestartet wird. Ohne diese Option musst du den Befehl manuell ausführen. Standard true. | +| "cmd:runonce" | (optional) Läuft beim Start, setzt dann aber "cmd:runonce" und "cmd:runonstart" auf false (sodass künftige Ausführungen einen manuellen Neustart erfordern) | +| "cmd:clearonstart" | (optional) Beim Ausführen des Befehls wird der Inhalt des Blocks gelöscht. Standard false. | +| "cmd:closeonexit" | (optional) Schließt den Block automatisch, wenn der Befehl erfolgreich beendet wird (Exit-Code = 0) | +| "cmd:closeonexitforce" | (optional) Schließt den Block automatisch, wenn der Befehl beendet wird (Erfolg oder Fehlschlag) | +| "cmd:closeonexitdelay | (optional) Ändert die Verzögerung zwischen Beendigung des Befehls und Schließen des Blocks, in Millisekunden, Standard 2000 | +| "cmd:env" | (optional) Ein Schlüssel-Wert-Objekt, das mit dem Befehl auszuführende Umgebungsvariablen darstellt. Standard ist ein leeres Objekt. | +| "cmd:cwd" | (optional) Ein String, der das mit dem Befehl auszuführende aktuelle Arbeitsverzeichnis darstellt. Funktioniert aktuell nur lokal. Standard ist das Home-Verzeichnis. | +| "cmd:nowsh" | (optional) Ein Boolean, der die wsh-Integration für den Befehl deaktiviert. Standard false. | +| "cmd:jwt" | (optional) Ein Boolean, der das Hinzufügen eines JWT-Tokens zur Umgebung erzwingt. Erforderlich, um WaveApps als Widgets auszuführen (lokal und remote). Standard false. | +| "term:localshellpath" | (optional) Legt die für die Ausführung deines Widget-Befehls genutzte Shell fest. Funktioniert nur lokal. Bleibt es leer, ermittelt Wave stattdessen dein Systemstandard. | +| "term:localshellopts" | (optional) Legt die für `"term:localshellpath"` zu nutzenden Shell-Optionen fest. Nützlich, wenn du eine nicht standardmäßige Shell nutzt und eine bestimmte Option angeben musst, die wir nicht abdecken. Funktioniert nur lokal. Standard ist ein leerer String. | +| "cmd:initscript" | (optional) nur für den "shell"-Controller. Ein Init-Skript, das vor dem Starten der Shell ausgeführt wird (kann ein Inline-Skript oder ein absoluter lokaler Dateipfad sein) | +| cmd:initscript.sh" | (optional) wie `cmd:initscript`, gilt aber nur für bash/zsh-Shells | +| cmd:initscript.bash" | (optional) wie `cmd:initscript`, gilt aber nur für bash-Shells | +| cmd:initscript.zsh" | (optional) wie `cmd:initscript`, gilt aber nur für zsh-Shells | +| cmd:initscript.pwsh" | (optional) wie `cmd:initscript`, gilt aber nur für pwsh/powershell-Shells | +| cmd:initscript.fish" | (optional) wie `cmd:initscript`, gilt aber nur für fish-Shells | + +### Beispiel für lokale Shell-Widgets + +Wenn du mehrere Shells auf deinem Rechner installiert hast, möchtest du manchmal eine andere als die Standard-Shell nutzen. Für solche Fälle ist es einfach, für jede ein Widget zu erstellen. + +Angenommen, du möchtest ein Widget, das eine `fish`-Shell startet. Sobald `fish` auf deinem System installiert ist, kannst du ein Widget wie folgt definieren: + +```json +{ + <... other widgets go here ...>, + "fish" : { + "icon": "fish", + "color": "#4abc39", + "label": "fish", + "blockdef": { + "meta": { + "view": "term", + "controller": "shell", + "term:localshellpath": "/usr/local/bin/fish", + "term:localshellopts": "-i -l" + } + } + }, + <... other widgets go here ...> +} +``` + +Dadurch wird ein Icon zur Widget-Leiste hinzugefügt, mit dem du ein Terminal mit der `fish`-Shell starten kannst. +![The example fish widget](./img/widget-example-fish.webp) + +:::info +Es ist möglich, dass `fish` nicht in deinem PATH liegt. In diesem Fall funktioniert `"fish"` als Wert von `"term:localshellpath"` nicht. In diesen Fällen musst du einen direkten Pfad dazu angeben. Das ist oft etwas wie `"/usr/local/bin/fish"`, kann auf deinem System aber abweichen. +::: + +Wenn du dasselbe für z. B. Powershell Core, oder `pwsh`, tun möchtest, kannst du das Widget wie folgt definieren: + +```json +{ + <... other widgets go here ...>, + "pwsh" : { + "icon": "rectangle-terminal", + "color": "#2671be", + "label": "pwsh", + "blockdef": { + "meta": { + "view": "term", + "controller": "shell", + "term:localshellpath": "pwsh" + } + } + }, + <... other widgets go here ...> +} +``` + +Dadurch wird ein Icon zur Widget-Leiste hinzugefügt, mit dem du ein Terminal mit der `pwsh`-Shell starten kannst. +![The example pwsh widget](./img/widget-example-pwsh.webp) + +:::info +Es ist möglich, dass `pwsh` nicht in deinem PATH liegt. In diesem Fall funktioniert `"pwsh"` als Wert von `"term:localshellpath"` nicht. In diesen Fällen musst du einen direkten Pfad dazu angeben. Das könnte auf einem Unix-System etwa `"/usr/local/bin/pwsh"` sein oder unter Windows "C:\\Program Files\\PowerShell\\7\\pwsh.exe", kann auf deinem System aber +abweichen. Beachte außerdem, dass unter Windows sowohl `pwsh.exe` als auch `pwsh` funktionieren, unter Unix-Systemen aber nur `pwsh`. +::: + +### Beispiel für Remote-Shell-Widgets + +Wenn du ein Terminal-Widget für eine bestimmte Verbindung (SSH oder WSL) öffnen möchtest, kannst du den Metadaten-Schlüssel `connection` nutzen. Der Wert des connection-Schlüssels sollte mit connections.json (oder dem, was in deinem Verbindungs-Dropdown erscheint) übereinstimmen. Beachte, dass du nur den kanonischen Namen nutzen solltest (nicht einen benutzerdefinierten "display:name", den du gesetzt hast). Für WSL könnte das etwa `wsl://Ubuntu` sein, für SSH-Verbindungen etwa `user@remotehostname`. + +```json +{ + <... other widgets go here ...>, + "remote-term": { + "icon": "rectangle-terminal", + "label": "remote", + "blockdef": { + "meta": { + "view": "term", + "controller": "shell", + "connection": "" + } + } + }, + <... other widgets go here ...> +} +``` + +### Beispiel für Cmd-Widgets + +Hier sind ein paar einfache Cmd-Widgets als Beispiele. + +Angenommen, du möchtest ein Widget, das beim Öffnen speedtest-go ausführt. Dann kannst du ein Widget wie folgt definieren: + +```json +{ + <... other widgets go here ...>, + "speedtest" : { + "icon": "gauge-high", + "label": "speed", + "blockdef": { + "meta": { + "view": "term", + "controller": "cmd", + "cmd": "speedtest-go --unix", + "cmd:clearonstart": true + } + } + }, + <... other widgets go here ...> +} +``` + +Dadurch wird ein Icon zur Widget-Leiste hinzugefügt, mit dem du ein Terminal starten kannst, das den Befehl `speedtest-go --unix` ausführt. +![The example speedtest widget](./img/widget-example-speed.webp) + +`"cmd"` für `"controller"` zu nutzen, ist der einfachste Weg, das zu erreichen. `"cmd:clearonstart"` ist nicht notwendig, sorgt aber dafür, dass bei jedem Ausführen des Befehls (was per Rechtsklick auf die Kopfzeile und Auswahl von `Force Controller Restart` geschehen kann) der vorherige Inhalt gelöscht wird. + +Angenommen, du möchtest nun eine TUI-App ausführen, zum Beispiel `dua`. Es stellt sich heraus, dass du das mehr oder weniger genauso tun kannst: + +```json +{ + <... other widgets go here ...>, + "dua" : { + "icon": "brands@linux", + "label": "dua", + "blockdef": { + "meta": { + "view": "term", + "controller": "cmd", + "cmd": "dua" + } + } + }, + <... other widgets go here ...> +} +``` + +Dadurch wird ein Icon zur Widget-Leiste hinzugefügt, mit dem du ein Terminal starten kannst, das den Befehl `dua` ausführt. +![The example speedtest widget](./img/widget-example-dua.webp) + +Da dies eine TUI-App ist, die beim Schließen nichts zurückgibt, ändert die Option `"cmd:clearonstart"` das Verhalten nicht, weshalb sie hier weggelassen wurde. + +## Web-Widgets + +Manchmal möchte man direkt eine Seite zu einer Webseite öffnen. Das lässt sich einfach durch Erstellen eines benutzerdefinierten `"web"`-Widgets erreichen. Sie haben im Allgemeinen diese Form: + +```json +{ + <... other widgets go here ...>, + "": { + "icon": "", + "label": "", + "color": "", + "blockdef": { + "meta": { + "view": "web", + "url": "" + } + } + }, + <... other widgets go here ...> +} +``` + +Der `WidgetConfigType` nutzt die üblichen, allen Widgets gemeinsamen Optionen. Der `MetaTSType` kann die unten aufgeführten Schlüssel enthalten: +| Schlüssel | Beschreibung | +|-----|-------------| +| "view" | Ein String, der den allgemeinen Widget-Typ angibt. Bei benutzerdefinierten Web-Widgets muss dies auf `"web"` gesetzt sein.| +| "url" | Dieser String ist die URL der aktuellen Seite. Als Teil eines Widgets dient er als Startseite des Widgets. Ist er nicht angegeben, wird standardmäßig die global konfigurierbare `"web:defaulturl"` genutzt, die bei einer frischen Installation [https://github.com/wavetermdev/waveterm](https://github.com/wavetermdev/waveterm) ist. | +| "pinnedurl" | (optional) Dieser String ist die URL, zu der dich der Startseiten-Button führt. Ist er nicht angegeben, wird standardmäßig die global konfigurierbare `"web:defaulturl"` genutzt, die bei einer frischen Installation [https://github.com/wavetermdev/waveterm](https://github.com/wavetermdev/waveterm) ist. | + +### Beispiel für Web-Widgets + +Angenommen, du möchtest ein Widget, das automatisch bei YouTube startet und YouTube als Startseite nutzt. Das geht so: + +```json +{ + <... other widgets go here ...>, + "youtube" : { + "icon": "brands@youtube", + "label": "youtube", + "blockdef": { + "meta": { + "view": "web", + "url": "https://youtube.com", + "pinnedurl": "https://youtube.com" + } + } + }, + <... other widgets go here ...> +} +``` + +Dadurch wird ein Icon zur Widget-Leiste hinzugefügt, mit dem du ein Web-Widget auf der YouTube-Startseite starten kannst. +![The example speedtest widget](./img/widget-example-youtube.webp) + +Alternativ, angenommen du möchtest ein Web-Widget, das wie ein Lesezeichen zu GitHub öffnet, danach aber Google als Startseite nutzt. Das geht einfach so: + +```json +{ + <... other widgets go here ...>, + "github" : { + "icon": "brands@github", + "label": "github", + "blockdef": { + "meta": { + "view": "web", + "url": "https://github.com", + "pinnedurl": "https://google.com" + } + } + }, + <... other widgets go here ...> +} +``` + +Dadurch wird ein Icon zur Widget-Leiste hinzugefügt, mit dem du ein Web-Widget auf der GitHub-Startseite starten kannst. +![The example speedtest widget](./img/widget-example-github.webp) + +## Sysinfo-Widgets + +Das Sysinfo-Widget beschränkt sich bewusst auf eine kleine Teilmenge möglicher Werte, die wir mit der Zeit erweitern werden. Es ist aber dennoch möglich, deine eigene Version davon zu konfigurieren — zum Beispiel, wenn du standardmäßig ein anderes Diagramm laden möchtest. Die allgemeine Form dieses Widgets ist: + +```json +{ + <... other widgets go here ...>, + "": { + "icon": "", + "label": "", + "color": "", + "blockdef": { + "meta": { + "view": "sysinfo", + "graph:numpoints": , + "sysinfo:type": , + } + } + }, + <... other widgets go here ...> +} +``` + +Der `WidgetConfigType` nutzt die üblichen, allen Widgets gemeinsamen Optionen. Der `MetaTSType` kann die unten aufgeführten Schlüssel enthalten: +| Schlüssel | Beschreibung | +|-----|-------------| +| "view" | Ein String, der den allgemeinen Widget-Typ angibt. Bei benutzerdefinierten Sysinfo-Widgets muss dies auf `"sysinfo"` gesetzt sein.| +| "graph:numpoints" | Die maximale Anzahl an Punkten, die im Diagramm angezeigt werden können. Entspricht der Anzahl der Sekunden, die das Diagrammfenster abdeckt. Standard ist 100.| +| "sysinfo:type" | Ein String, der die im Diagramm anzuzeigende Typ-Sammlung darstellt. Gültige Werte hierfür sind `"CPU"`, `"Mem"`, `"CPU + Mem"` und `All CPU`. Beachte, dass Groß-/Kleinschreibung beachtet wird. Ist kein Wert angegeben, zeigt das Diagramm standardmäßig `"CPU"`.| + +### Beispiel für Sysinfo-Widgets + +Angenommen, du hast einen Build-Prozess, der 3 Minuten dauert, und möchtest den gesamten Build im Sysinfo-Diagramm sehen können. Außerdem möchtest du sowohl CPU als auch Speicher sehen, da beide von diesem Prozess betroffen sind. In diesem Fall kannst du ein Widget wie folgt einrichten: + +```json +{ + <... other widgets go here ...>, + "3min-info" : { + "icon": "circle-3", + "label": "3mininfo", + "blockdef": { + "meta": { + "view": "sysinfo", + "graph:numpoints": 180, + "sysinfo:type": "CPU + Mem" + } + } + }, + <... other widgets go here ...> +} +``` + +Dadurch wird ein Icon zur Widget-Leiste hinzugefügt, mit dem du standardmäßig die CPU- und Speicher-Diagramme mit 180 Sekunden an Daten starten kannst. +![The example speedtest widget](./img/widget-example-3mininfo.webp) + +Angenommen, du bist mit den standardmäßigen 100 Punkten (und 100 Sekunden) zufrieden, möchtest aber beim Start alle CPU-Daten sehen. In diesem Fall kannst du Folgendes schreiben: + +```json +{ + <... other widgets go here ...>, + "all-cpu" : { + "icon": "chart-scatter", + "label": "all-cpu", + "blockdef": { + "meta": { + "view": "sysinfo", + "sysinfo:type": "All CPU" + } + } + }, + <... other widgets go here ...> +} +``` + +Dadurch wird ein Icon zur Widget-Leiste hinzugefügt, mit dem du standardmäßig die Alle-CPU-Diagramme starten kannst. + +![The example speedtest widget](./img/widget-example-all-cpu.webp) + +## Standard-Widgets überschreiben + +Wave wird mit 5 Standard-Widgets in der Widget-Leiste ausgeliefert (terminal, files, web, ai und sysinfo). Du kannst diese ändern oder entfernen, indem du ihre Konfiguration in widgets.json überschreibst. Die Namen der 5 Widgets, in Reihenfolge, sind: + +- `defwidget@terminal` +- `defwidget@files` +- `defwidget@web` +- `defwidget@ai` +- `defwidget@sysinfo` + +Um eines davon zu entfernen, setze diesen Schlüssel einfach auf `null` in deiner widgets.json-Datei. + +Um ihre Definitionen einzusehen, zu kopieren und einzufügen oder zu verstehen, wie sie funktionieren, kannst du alle ihre Definitionen unter [GitHub - default widgets.json](https://github.com/wavetermdev/waveterm/blob/main/pkg/wconfig/defaultconfig/widgets.json) ansehen. diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/durable-sessions.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/durable-sessions.mdx new file mode 100644 index 0000000000..9410e04f46 --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/durable-sessions.mdx @@ -0,0 +1,216 @@ +--- +sidebar_position: 3.5 +id: "durable-sessions" +title: "Dauerhafte Sitzungen" +--- + +import { VersionBadge } from "@site/src/components/versionbadge"; + +# Dauerhafte Sitzungen + +Halte deine entfernten SSH-Shell-Sitzungen über Netzwerkwechsel, Ruhezustand des Computers und Wave-Neustarts hinweg am Leben. + +## Überblick + +Dauerhafte Sitzungen schützen deinen Terminal-Status bei der Arbeit mit entfernten SSH-Verbindungen, ähnlich wie tmux oder screen, aber direkt in Wave integriert. Anders als bei Standard-SSH-Sitzungen, die bei Verbindungsabbruch enden, bleiben bei dauerhaften Sitzungen erhalten: + +- **Shell-Status** - Aktuelles Verzeichnis, Umgebungsvariablen und Shell-Verlauf +- **Laufende Programme** - Hintergrundjobs und lang laufende Befehle werden weiter ausgeführt +- **Terminal-Verlauf** - Vollständiger Scrollback-Puffer bleibt über Wiederverbindungen hinweg erhalten + +Dauerhafte Sitzungen verbinden sich automatisch neu, sobald deine Verbindung wiederhergestellt ist, und setzen genau dort fort, wo du aufgehört hast. + +:::info Nur Remote-Verbindungen +Dauerhafte Sitzungen sind ausschließlich für **entfernte SSH-Verbindungen** konzipiert. Lokale Terminals und WSL-Verbindungen nutzen Standard-Sitzungen, da sie von Netzwerkunterbrechungen nicht betroffen sind und aktiv bleiben, solange Wave läuft. +::: + +## So funktioniert es + +Wenn du eine dauerhafte Sitzung startest, startet Wave einen schlanken Job-Manager auf dem entfernten Server. Ähnlich wie tmux und screen tut dieser Manager Folgendes: + +1. Hält deinen Shell-Prozess unabhängig von der Wave-Verbindung am Laufen +2. Puffert Terminal-Ausgaben während der Trennung +3. Ermöglicht Wave, sich beim Wiederverbinden nahtlos erneut anzuhängen +4. Übersteht Wave-Neustarts und Netzwerkunterbrechungen + +Die Sitzung läuft auf dem entfernten Server weiter, selbst wenn du Wave schließt, deinen Computer in den Ruhezustand versetzt oder das Netzwerk wechselst. + +## Sitzungsstatus-Anzeige + +Das Schild-Symbol in deiner Terminal-Kopfzeile zeigt den aktuellen Sitzungsstatus: + +| Symbol | Status | Beschreibung | +|------|--------|-------------| +| | Standard-Sitzung | Verbindungsabbrüche beenden die Sitzung | +| | Dauerhaft (verbunden) | Sitzung ist geschützt und verbunden | +| | Dauerhaft (getrennt) | Sitzung läuft, aktuell getrennt | +| | Dauerhaft (wartend) | Konfiguriert, aber noch nicht gestartet | + +Fahre mit der Maus über das Schild-Symbol, um detaillierte Statusinformationen und verfügbare Aktionen zu sehen. + +## Konfiguration + +Dauerhafte Sitzungen können auf drei Ebenen konfiguriert werden, wobei spezifischere Einstellungen allgemeinere überschreiben: + +### Globale Einstellungen (niedrigste Priorität) + +Lege den Standard für alle SSH-Verbindungen in deiner `settings.json` fest: + +```json +{ + "term:durable": true +} +``` + +### Verbindungseinstellungen (mittlere Priorität) + +Konfiguriere die Dauerhaftigkeit pro Verbindung in deiner `connections.json`: + +```json +{ + "connections": { + "user@host": { + "term:durable": true + } + } +} +``` + +### Blockeinstellungen (höchste Priorität) + +Überschreibe die Einstellung für einzelne Terminal-Blöcke über: + +- **Kontextmenü**: Rechtsklick auf Terminal → Erweitert → Session Durability +- **Flyover-Aktionen**: Klick auf das Schild-Symbol → "Restart as Durable" oder "Restart as Standard" +- **Kommandozeile**: Nutze `wsh setmeta term:durable=true` oder `wsh setmeta term:durable=false` + +Konfigurationshierarchie (höchste zu niedrigste Priorität): +1. Einstellung auf Blockebene +2. Einstellung auf Verbindungsebene +3. Globale Einstellung + +### Standardverhalten + +- **SSH-Verbindungen**: Dauerhafte Sitzungen standardmäßig deaktiviert (Opt-in über Konfiguration) +- **Lokale Terminals**: Nutzen immer Standard-Sitzungen (Dauerhaftigkeit nicht anwendbar) +- **WSL-Verbindungen**: Nutzen immer Standard-Sitzungen (Dauerhaftigkeit nicht anwendbar) + +## Zwischen Modi wechseln + +### Standard zu dauerhaft + +1. Fahre mit der Maus über das reguläre Schild-Symbol +2. Klicke im Flyover auf **"Restart as Durable"** +3. Deine Sitzung startet mit aktivierter Dauerhaftigkeit neu + +Oder nutze das Kontextmenü: +- Rechtsklick auf Terminal → Erweitert → Session Durability → Restart Session in Durable Mode + +### Dauerhaft zu Standard + +1. Öffne das Terminal-Kontextmenü (Rechtsklick) +2. Navigiere zu Erweitert → Session Durability +3. Wähle **"Restart Session in Standard Mode"** + +:::warning Moduswechsel startet die Sitzung neu +Der Wechsel zwischen Standard- und dauerhaftem Modus erfordert einen Neustart der Shell. Alle laufenden Prozesse in der aktuellen Sitzung werden beendet. +::: + +## Sitzungsstatus + +### Verbunden +Dein Terminal ist mit der entfernten Sitzung verbunden. Du kannst mit der Shell interagieren und Ausgaben in Echtzeit sehen. + +### Getrennt +Verbindung verloren, aber die Sitzung läuft auf dem entfernten Server weiter. Wave verbindet sich automatisch neu, sobald möglich. Alle von dir gestarteten Befehle laufen weiter. + +### Wartet auf Start +Sitzung ist für Dauerhaftigkeit konfiguriert, aber noch nicht gestartet. Klicke auf "Start Session" oder führe einen Befehl aus, um zu beginnen. + +### Wird gestartet +Der Job-Manager wird auf dem entfernten Server initialisiert. Die Sitzung wird in Kürze verbunden. + +### Beendet +Die Sitzung wurde beendet. Häufige Gründe: +- **Beendet (Exited)**: Shell wurde normal geschlossen (z. B. `exit` eingegeben) +- **Verloren (Lost)**: Sitzung auf dem Server nicht gefunden (könnte beendet worden sein oder das System wurde neu gestartet) +- **Start fehlgeschlagen (Failed to Start)**: Der Job-Manager hatte bei der Initialisierung einen Fehler + +Klicke auf "Restart Session", um eine neue dauerhafte Sitzung zu starten, oder auf "Restart as Standard", um den Modus zu wechseln. + +## Anwendungsfälle + +### Lang laufende Befehle +Starte einen Build-, Deployment- oder Datenverarbeitungsjob und klapp deinen Laptop zu. Der Befehl läuft weiter, und du kannst später nachsehen. + +```bash +# Einen langen Build starten +./build.sh + +# Laptop zuklappen, Kaffee holen +# Später: erneut verbinden und die fertige Ausgabe sehen +``` + +### Instabile Netzwerke +Arbeite aus einem Café, im Zug oder über eine Mobilfunkverbindung. Kurze Unterbrechungen beenden deine Sitzung nicht und du verlierst keine Arbeit. + +### Mehrere Standorte +Beginne die Arbeit an deinem Desktop, mach auf deinem Laptop weiter. Deine Sitzung und ihr Status bleiben auf dem entfernten Server erhalten. + +### Systemwartung +Wave-Updates, -Neustarts oder -Abstürze unterbrechen deine Remote-Arbeit nicht. Verbinde dich erneut und mach sofort weiter. + +## Sitzungslebenszyklus + +Dauerhafte Sitzungen sind an den Terminal-Block in Wave gebunden. Die Sitzung wird beendet, wenn du: + +- **Den Block schließt**: Schließt das Terminal und beendet die entfernte Sitzung +- **Die Verbindung wechselst**: Das Ändern der Verbindung eines Blocks beendet die alte Sitzung +- **Den Workspace/Tab löschst**: Entfernt den Block und beendet zugehörige Sitzungen + +### Aufräumverhalten + +Wenn du einen Block schließt, während er **getrennt** ist, läuft die entfernte Sitzung bis zur nächsten Wiederverbindung weiter. Wenn Wave sich wieder mit diesem Server verbindet, räumt es automatisch verwaiste Sitzungen von geschlossenen Blöcken auf. + +Das stellt sicher, dass sich entfernte Sitzungen nicht auf deinen Servern ansammeln, wenn du Terminals offline schließt. + +## Einschränkungen + +- **Lokale Terminals**: Nicht anwendbar (bereits dauerhaft mit deinem lokalen Rechner) +- **WSL-Verbindungen**: Nicht anwendbar (WSL-Sitzungen werden von Windows verwaltet) +- **Netzwerklatenz**: Getrennte Sitzungen puffern Ausgaben; das Wiederverbinden kann einen Moment zur Synchronisierung dauern +- **Serverressourcen**: Jede dauerhafte Sitzung unterhält für die Sitzungsverwaltung einen schlanken Go-Prozess auf dem entfernten Server + +## Fehlerbehebung + +### Sitzung zeigt "Lost" an +Die Sitzung wurde auf dem entfernten Server beendet, möglicherweise wegen: +- Serverneustart +- Manuelle Beendigung des Job-Manager-Prozesses +- Dem entfernten System sind die Ressourcen ausgegangen + +**Lösung**: Klicke auf "Restart Session", um eine neue dauerhafte Sitzung zu starten. + +### Sitzung verbindet sich nicht neu +Prüfe, ob: +- Deine SSH-Verbindung zum Server funktioniert (Verbindungsstatus prüfen) +- Der Job-Manager-Prozess auf dem entfernten Server noch läuft + +**Versuche**: Rechtsklick auf Terminal → Erweitert → Force Restart Controller + +### Fehler "Failed to Start" +Der Job-Manager konnte auf dem entfernten Server nicht initialisiert werden. Prüfe die Fehlermeldung für Details. + +**Versuche**: Starte die Sitzung neu. Falls das Problem weiter besteht, melde einen Fehlerbericht mit den Fehlerdetails. + +:::info Technische Details +Dauerhafte Sitzungen nutzen Unix-Domain-Sockets auf dem entfernten Server, um dauerhafte Verbindungen zwischen der Shell und Waves Job-Manager aufrechtzuerhalten. Der Job-Manager-Prozess läuft unabhängig und übersteht SSH-Verbindungsabbrüche. +::: + +## Datenschutz & Sicherheit + +- Dauerhafte Sitzungen laufen vollständig auf deinen entfernten Servern +- Alle Daten werden über SSH zwischen deiner lokalen Wave-Instanz und dem entfernten Rechner übertragen +- Keine offenen Ports auf dem entfernten Rechner - die Kommunikation läuft über deine bestehende SSH-Verbindung +- Bei Trennung werden Ausgaben lokal auf dem entfernten Rechner gepuffert, bis du dich wieder verbindest +- Sitzungen sind pro Benutzer isoliert und nutzen die Berechtigungen deines entfernten Benutzers diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/faq.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/faq.mdx new file mode 100644 index 0000000000..548498db49 --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/faq.mdx @@ -0,0 +1,70 @@ +--- +sidebar_position: 101 +id: "faq" +title: "FAQ" +--- + +import { VersionBadge } from "@site/src/components/versionbadge"; + +# FAQ + +### Wie kann ich die Blocknummern sehen? + +Die Blocknummern erscheinen, wenn du Strg-Umschalt (Ctrl-Shift) gedrückt hältst (und verschwinden wieder, sobald du die Tastenkombination loslässt). + +### Wie stelle ich eine Remote-Verbindung her? + +In der Kopfzeile gibt es einen Button. Klicke auf das Symbol oder +und gib `[user]@[host]` ein, mit dem du dich verbinden möchtest. + +### Wie nutze ich unter Windows Git Bash als Standard-Shell? + +Wave erkennt Git-Bash-Installationen automatisch und fügt sie zum Verbindungs-Dropdown hinzu. Klicke einfach auf das Symbol oder in der Block-Kopfzeile und wähle "Git Bash" aus der Liste. + +Alternativ kannst du Git Bash manuell als Standard-Shell festlegen, indem du die Konfigurationsvariable `term:localshellpath` auf +den Speicherort der Git-Bash-Datei "bash.exe" setzt. Standardmäßig befindet sie sich unter "C:\Program Files\Git\bin\bash.exe". +Denk daran, dass in JSON Backslashes maskiert werden müssen. Füge dies also zu deiner [settings.json](./config)-Datei hinzu: + +```json +"term:localshellpath": "C:\\Program Files\\Git\\bin\\bash.exe" +``` + +### Kann ich WSH außerhalb von Wave nutzen? + +`wsh` ist eine interne CLI, um die Kontrolle über Wave auf die Kommandozeile zu erweitern; mehr dazu erfährst du [hier](./wsh). Um Missbrauch durch andere Anwendungen zu verhindern, benötigt `wsh` ein von Wave bereitgestelltes Zugriffstoken und funktioniert außerhalb der App nicht. + + +## Warum warnt mich Wave beim Start vor der ARM64-Übersetzung? + +macOS und Windows verfügen beide über Kompatibilitätsschichten, die es x64-Anwendungen erlauben, auf ARM-Rechnern zu laufen. Das hilft, dass mehr Apps auf diesen Systemen laufen, während Entwickler native ARM-Unterstützung für ihre Anwendungen ergänzen. Das geht jedoch mit spürbaren Leistungseinbußen einher. + +Für die beste Erfahrung mit Wave wird empfohlen, Wave zu deinstallieren und die Version neu zu installieren, die nativ für deinen Rechner kompiliert wurde. Die richtige Version findest du in unserer [Installationsanleitung](./gettingstarted#installation). + +Du kannst diese Warnung deaktivieren, indem du `app:dismissarchitecturewarning=true` in [deiner Konfiguration](./config) setzt. + +## Wie nehme ich an den Beta-Builds von Wave teil? + +Wave veröffentlicht in zwei Kanälen, `latest` und `beta`. Wenn du die App für macOS, Windows oder Linux über DEB oder RPM installiert hast, kannst du folgende Einstellungen in deiner `settings.json` setzen (siehe [Konfiguration](./config) für weitere Infos): + +```json +"autoupdate:enabled": true, +"autoupdate:channel": "beta" +``` + +Wenn du über Snap installiert hast, kannst du folgenden Befehl verwenden: + +```sh +sudo snap install waveterm --classic --beta +``` + +## Kann ich Wave AI nutzen, ohne Telemetrie zu aktivieren? + + + +Ja! Wave AI ist normalerweise deaktiviert, wenn Telemetrie nicht aktiviert ist. Du kannst Wave-AI-Funktionen jedoch auch ohne Telemetrie aktivieren, indem du dein eigenes benutzerdefiniertes KI-Modell konfigurierst (entweder ein lokales Modell oder mit deinem eigenen API-Schlüssel). + +So aktivierst du Wave AI ohne Telemetrie: +1. Konfiguriere einen benutzerdefinierten KI-Modus (siehe [Wave-AI-Dokumentation](./waveai-modes)) +2. Setze `waveai:defaultmode` auf den Schlüssel deines benutzerdefinierten Modus in deinen Wave-Einstellungen + +Sobald du beide Schritte abgeschlossen hast, ist Wave AI aktiviert und du kannst es vollständig privat ohne Telemetrie nutzen. So kannst du lokale Modelle wie Ollama oder deine eigenen API-Schlüssel mit Anbietern wie OpenAI, OpenRouter oder anderen verwenden. diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/gettingstarted.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/gettingstarted.mdx new file mode 100644 index 0000000000..2d15455826 --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/gettingstarted.mdx @@ -0,0 +1,164 @@ +--- +sidebar_position: 1 +id: "gettingstarted" +title: "Erste Schritte" +--- + +import { PlatformProvider, PlatformSelectorButton, PlatformItem } from "@site/src/components/platformcontext"; +import { Kbd } from "@site/src/components/kbd"; + +Wave Terminal ist ein modernes Terminal, das neben klassischen Terminal-Funktionen grafische Fähigkeiten wie Web-Browsing, Dateivorschau und KI-Unterstützung bietet. Diese Anleitung hilft dir beim Einstieg. + +## Installation + + + + +### Plattformanforderungen + + + +- Unterstützte Architekturen: Apple Silicon, x64 +- Unterstützte Betriebssystemversion: macOS 11 Big Sur oder neuer + + + + + +- Unterstützte Architekturen: x64 +- Unterstützte Betriebssystemversion: Windows 10 1809 oder neuer, Windows 11 + +:::note + +ARM64 ist geplant, wird aber derzeit durch vorgelagerte Abhängigkeiten blockiert (siehe [Windows-ARM-Unterstützung](https://github.com/wavetermdev/waveterm/issues/928)). + +::: + + + + + +- Unterstützte Architekturen: x64, ARM64 +- Unterstützte Betriebssystemversion: benötigt glibc-2.28 oder neuer (Debian >=10, RHEL >=8, Ubuntu >=20.04 usw.) + + + +### Paketmanager + + + +#### Homebrew + +```bash +brew install --cask wave +``` + + + + + +#### Windows Package Manager + +```powershell +winget install CommandLine.Wave +``` + +#### Chocolatey + +```powershell +choco install wave +``` + + + + + +#### Snap + +```bash +sudo snap install --classic waveterm +``` + +Weitere Optionen verfügbar: [AUR-Paket](https://aur.archlinux.org/packages/waveterm) (community-gepflegt), [Nix-Paket](https://search.nixos.org/packages?channel=unstable&show=waveterm) (community-gepflegt) + + + +Du kannst Installationsprogramme auch direkt von unserer [Download-Seite](https://www.waveterm.dev/download) herunterladen. + +## Grundkonzepte + +### Tabs und Blöcke + +- **Tabs**: Wie Browser-Tabs helfen sie, deine Arbeit zu organisieren. Erstelle neue Tabs mit . +- **Blöcke**: Die Bausteine von Wave. Jeder Block kann ein Terminal, ein Webbrowser, eine Dateivorschau oder ein anderes Widget sein. +- **Layout**: Blöcke können gezogen, abgelegt und in der Größe angepasst werden, um dein ideales Layout zu erstellen. + +### Hauptfunktionen + +1. **Terminal-Funktionen** + + - Funktioniert mit gängigen Shells (bash, zsh, fish) + - Unterstützt Standard-Terminal-Funktionen (readline, Steuersequenzen usw.) + - Enthält den Befehl `wsh` zur Interaktion mit Waves GUI-Funktionen + - GPU-beschleunigt (auf den meisten Plattformen) + +2. **Grafische Widgets** + + - Dateivorschau (Bilder, Video, Markdown, Code mit Syntax-Highlighting) + - Webseiten durchsuchen + - Fragen stellen und KI-Hilfe direkt aus dem Terminal erhalten (mehrere KI-Modelle einrichten) + - Einfache Systemüberwachungs-Diagramme + +3. **Remote-Verbindungen** + - Einfache SSH-Verbindungen über den Verbindungs-Button + - WSL-Integration unter Windows + - Einheitliche Erfahrung bei lokalen und entfernten Sitzungen + +## Schnellstart-Anleitung + +1. **Öffne deinen ersten neuen Tab** + + - Neue Wave-Tabs starten mit einem einzelnen Terminal-Block + - Nutze ihn wie dein gewohntes Terminal + - Erstelle weitere Terminal-Blöcke mit + +2. **Probiere ein paar grundlegende Befehle aus** + + ```bash + # Eine Datei oder ein Verzeichnis anzeigen + wsh view ~/Documents + + # Eine Webseite öffnen + wsh web open github.com + + # KI-Unterstützung erhalten + wsh ai -m "how do I find large files in my current directory?" -s + ``` + +3. **Passe dein Layout an** + + - Ziehe Block-Kopfzeilen, um sie neu anzuordnen + - Fahre zwischen Blöcken mit der Maus, um sie in der Größe anzupassen + - Rechtsklick auf Tab-Kopfzeilen für Hintergrundoptionen + - Rechtsklick auf Block-Kopfzeilen für blockspezifische Optionen + +4. **Mit entfernten Rechnern verbinden** + - Klicke auf den -Button + - Gib `username@hostname` für SSH-Verbindungen ein + - Oder wähle unter Windows eine WSL-Distribution aus + +## Nächste Schritte + +- Entdecke [Tastenkombinationen](./keybindings), um effizienter zu arbeiten +- Erfahre mehr über [Tab-Layouts](./layout), um deinen Workspace zu organisieren +- Richte [benutzerdefinierte Widgets](./customwidgets) für schnellen Zugriff auf deine Tools ein +- Konfiguriere [Wave AI](./waveai), um deine bevorzugten KI-Modelle zu nutzen +- Schau dir [Konfiguration](./config) für ausführliche Anpassungsoptionen an + +## Hilfe erhalten + +- Tritt unserer [Discord-Community](https://discord.gg/XfvZ334gwU) für Hilfe und Diskussionen bei +- Melde Probleme auf [GitHub](https://github.com/wavetermdev/waveterm/issues) +- Schau in unsere [FAQ](./faq) für häufige Fragen + + diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/index.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/index.mdx new file mode 100644 index 0000000000..02053e2f69 --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/index.mdx @@ -0,0 +1,100 @@ +--- +sidebar_position: -1 +id: "index" +title: "Startseite" +hide_title: true +hide_table_of_contents: true +--- + +import { Card, CardGroup } from "@site/src/components/card"; + +# Willkommen bei Wave Terminal + +Wave ist ein [Open-Source](https://github.com/wavetermdev/waveterm)-Terminal, das klassische Terminal-Funktionen mit grafischen Fähigkeiten wie Dateivorschau, Web-Browsing und KI-Unterstützung kombiniert. Es läuft unter macOS, Linux und Windows. + +Moderne Entwicklung bedeutet ständigen Wechsel zwischen Terminals und Browsern - Dokumentation nachschlagen, Dateien in der Vorschau ansehen, Systeme überwachen und KI-Tools nutzen. Wave bringt diese grafischen Werkzeuge direkt ins Terminal und lässt dich sie von der Kommandozeile aus steuern. So bleibst du in deinem Terminal-Workflow und hast trotzdem Zugriff auf die visuellen Oberflächen, die du brauchst. + +Schau dir [Erste Schritte](./gettingstarted) für Installationsanweisungen an. + +![Wave Screenshot](./img/wave-screenshot.webp) + + + + + + + + + + + +
+ +:::info + +Wenn du eine Frage hast, kannst du uns gerne im [Discord](https://discord.gg/XfvZ334gwU) fragen. Wenn du einen Fehler melden oder eine Verbesserung vorschlagen möchtest, nutze bitte [Github Issues](https://github.com/wavetermdev/waveterm/issues). Diese Dokumentation ist ebenfalls Open Source, und wir nehmen gerne PRs für die Docs [hier](https://github.com/wavetermdev/waveterm/blob/main/docs) entgegen. Klicke auf den Link "Edit this page" am Ende der Seite, um direkt zur Editor-Seite dieses Dokuments auf GitHub zu gelangen. + +::: + +
+ +Weitere Referenzen: + +- [Konfiguration](./config) +- [Benutzerdefinierte Widgets](./customwidgets) +- [Vollständige wsh-Referenz](./wsh-reference) +- [Telemetrie](./telemetry) +- [FAQ](./faq) +- [Release Notes](./releasenotes) + +
+ +## Roadmap + +Möchtest du zu unseren zukünftigen Releases beitragen? Vernetze dich mit uns auf [Discord](https://discord.gg/XfvZ334gwU) oder erstelle eine [Feature-Anfrage](https://github.com/wavetermdev/waveterm/issues/new/choose)! + +## Links + +- **Homepage** https://waveterm.dev +- **Download** https://waveterm.dev/download +- **Discord** https://discord.gg/XfvZ334gwU +- **GitHub** https://github.com/wavetermdev/waveterm/ + +## Suchst du die WaveLegacy-Dokumentation? + +Die WaveLegacy-Dokumentation findest du unter [legacydocs.waveterm.dev](https://legacydocs.waveterm.dev). diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/keybindings.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/keybindings.mdx new file mode 100644 index 0000000000..686bc4467e --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/keybindings.mdx @@ -0,0 +1,122 @@ +--- +sidebar_position: 2 +id: "keybindings" +title: "Tastenkombinationen" +--- + +import { Kbd, KbdChord } from "@site/src/components/kbd"; +import { PlatformProvider, PlatformSelectorButton } from "@site/src/components/platformcontext"; +import { VersionBadge } from "@site/src/components/versionbadge"; + + + +Hier ist die Liste der Standard-Tastenkombinationen in Wave, unterteilt in Abschnitte. +Manche Tastenkombinationen sind immer aktiv. Andere gelten nur für bestimmte Blocktypen. + +Beachte, dass dies die macOS-Tastenkombinationen sind (mit "Cmd"). Für Windows und Linux +ersetze "Cmd" durch "Alt" (beachte, dass "Ctrl" sowohl unter Mac, Windows als auch Linux "Ctrl" ist). + +Chords werden mit einem + zwischen den Tasten dargestellt. Du hast 2 Sekunden Zeit, um nach der ersten Taste die zweite Chord-Taste zu drücken. Escape nach der ersten Chord-Taste ist immer wirkungslos. + +## Globale Tastenkombinationen + + +
+ +| Taste | Funktion | +| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| | Neuen Tab öffnen | +| | Neuen Block öffnen (standardmäßig ein Terminal-Block mit derselben Verbindung und demselben Arbeitsverzeichnis). Mit der Einstellung `app:defaultnewblock` zum Launcher wechseln | +| | Sichtbarkeit des WaveAI-Panels umschalten | +| | Horizontal teilen, neuen Block rechts öffnen | +| | Vertikal teilen, neuen Block unten öffnen | +| | Vertikal teilen, neuen Block oben öffnen | +| | Vertikal teilen, neuen Block unten öffnen | +| | Horizontal teilen, neuen Block links öffnen | +| | Horizontal teilen, neuen Block rechts öffnen | +| | Neues Fenster öffnen | +| | Aktuellen Block schließen | +| | Aktuellen Tab schließen | +| | Aktuellen Block vergrößern / verkleinern | +| | "Verbindungs"-Umschalter öffnen | +| | Fokus auf aktuellen Block zurücksetzen (nützlich, wenn der Block den Eingabefokus verloren hat) | +| | Blocknummern anzeigen | +| | WaveAI-Eingabe fokussieren | +| | Zu Blocknummer wechseln | +| / | Zwischen Blöcken nach links, rechts, oben, unten bewegen | +| | Aktuellen Block durch einen Launcher-Block ersetzen | +| | Aktuellen Tab umbenennen | +| | Zu Tab-Nummer wechseln | +| / | Zum Tab links wechseln | +| / | Zum Tab rechts wechseln | +| | Zu Workspace-Nummer wechseln | +| | UI aktualisieren | +| | Terminal-Mehrfacheingabemodus umschalten | + +## Tastenkombinationen für die Dateivorschau + +| Taste | Funktion | +| ----------------------------------------- | -------------------------------------------------------------------------------------------------- | +| | Jedes normale Zeichen (z. B. "a", "b") filtert die Dateiliste | +| | Setzt den Filter zurück | +| / | Dateiauswahl nach oben/unten ändern | +| | Aktuell ausgewählte Datei/Verzeichnis öffnen | +| | Ein Verzeichnis "nach oben" bewegen (übergeordnetes Verzeichnis) | +| | Zurück, zur zuvor ausgewählten Datei/Verzeichnis wechseln | +| | Vorwärts (Gegenteil von Zurück) | +| | Eine neue Datei öffnen (akzeptiert relative Pfade zum aktuellen Verzeichnis) | +| | Wenn der Datei-Editor geöffnet ist, Datei speichern | +| | Für Dateien, die in Vorschau oder bearbeitet werden können (Markdown, CSVs), zwischen Vorschau- und Bearbeitungsmodus wechseln | +| | Wenn der Datei-Editor geöffnet ist, Änderungen verwerfen | + +## Web-Tastenkombinationen + +| Taste | Funktion | +| ------------------------- | ------------------------------------------------------------- | +| | URL-Eingabeleiste fokussieren | +| | Wenn die URL-Eingabeleiste fokussiert ist, wird der Webinhalt fokussiert | +| | Webseite neu laden | +| | Zurück | +| | Vorwärts | +| | In Webseite suchen | +| | Ein Lesezeichen öffnen | + +## WaveAI-Tastenkombinationen + +| Taste | Funktion | +| ----------------------- | ----------------------- | +| | WaveAI-Panel umschalten | +| | WaveAI-Eingabe fokussieren | +| | KI-Chat leeren | + +## Terminal-Tastenkombinationen + +| Taste | Funktion | +| ----------------------- | ---------------------------- | +| | Kopieren | +| | Einfügen | +| | Einfügen (nur Windows) | +| | Terminal leeren | +| | Im Terminal suchen | +| | Nach oben scrollen | +| | Nach unten scrollen | +| | Nach oben scrollen (nur macOS) | +| | Nach unten scrollen (nur macOS)| +| | Zum Zeilenanfang springen (nur macOS) | +| | Zum Zeilenende springen (nur macOS) | +| | Eine Seite nach oben scrollen | +| | Eine Seite nach unten scrollen | + +## Tastenkombinationen für den Prozess-Viewer + +| Taste | Funktion | +| ----------------------- | ------------------------------------- | +| | Live-Updates pausieren / fortsetzen | +| | Prozessfilter / -suche öffnen | +| | Suchleiste schließen | + +## Anpassbare systemweite globale Tastenkombination + +Wave erlaubt es, eine benutzerdefinierte globale Tastenkombination festzulegen, um von überall auf deinem Computer aus dein zuletzt genutztes Fenster zu fokussieren. Weitere Informationen dazu findest du in der [Konfigurationsdokumentation](./config#customizable-systemwide-global-hotkey). + +
diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/layout.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/layout.mdx new file mode 100644 index 0000000000..dcad9e32ee --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/layout.mdx @@ -0,0 +1,10 @@ +--- +sidebar_class_name: hidden +id: "layout" +--- + +import { Redirect } from "@docusaurus/router"; + + + + diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/releasenotes.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/releasenotes.mdx new file mode 100644 index 0000000000..4998096103 --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/releasenotes.mdx @@ -0,0 +1,696 @@ +--- +id: "releasenotes" +title: "Release Notes" +sidebar_position: 200 +--- + +# Release Notes + +### v0.14.5 — 16. Apr 2026 + +Wave v0.14.5 führt ein neues Prozess-Viewer-Widget, den Quake-Modus für die globale Tastenkombination und mehrere Verbesserungen der Alltagstauglichkeit ein. + +- **Prozess-Viewer** - Neues Widget, das laufende Prozesse auf lokalen und entfernten Rechnern anzeigt, mit CPU- und Speichernutzung, sortierbaren Spalten und der Möglichkeit, Signale an Prozesse zu senden +- **Quake-Modus** - Die globale Tastenkombination (`app:globalhotkey`) schaltet nun ein Wave-Fenster sichtbar/unsichtbar +- **[Fehlerbehebung] Settings-Widget** - Ein Fehler wurde behoben, bei dem noch nicht existierende Konfigurationsdateien nicht über die Settings-Widget-Oberfläche erstellt oder bearbeitet werden konnten +- **Dateien per Drag & Drop ins Terminal** - Ziehe Dateien aus dem Finder (macOS) oder deinem Dateimanager in einen Terminal-Block, um deren in Anführungszeichen gesetzten Pfad einzufügen ([#746](https://github.com/wavetermdev/waveterm/issues/746)) +- Neue Opt-in-Einstellung `app:showsplitbuttons` fügt Block-Kopfzeilen horizontale/vertikale Split-Buttons hinzu +- Die Widget-Seitenleiste kann über das View-Menü umgeschaltet werden; die Sichtbarkeit bleibt pro Workspace erhalten +- F2 zum Umbenennen des aktiven Tabs +- Maustaste 3/4 (zurück/vorwärts) navigiert jetzt in Web-Widgets +- Terminal-Sitzungen setzen jetzt `COLORTERM=truecolor` für bessere Farbunterstützung in CLI-Tools +- [Fehlerbehebung] Nachgestellte Leerzeichen werden beim Kopieren aus dem Terminal in die Zwischenablage entfernt +- Paket-Updates und Abhängigkeits-Upgrades + +### v0.14.4 — 26. Mär 2026 + +Wave v0.14.4 führt vertikale Tabs ein, aktualisiert auf xterm.js v6 und enthält eine Reihe von Fehlerbehebungen und internen Verbesserungen. + +**Vertikale Tableiste:** + +- **Neue Option für vertikale Tableiste** - Tabs können jetzt vertikal an der Fensterseite angezeigt werden, was mehr horizontalen Platz und leichteren Zugriff auf Tabs bietet, wenn viele geöffnet sind. In den Einstellungen zwischen horizontalem und vertikalem Tab-Layout umschaltbar. + +**Terminal-Verbesserungen:** + +- **xterm.js-v6.0.0-Upgrade** - Upgrade auf das neueste xterm.js v6, was verbesserte Terminal-Kompatibilität und -Darstellung mit sich bringt. Das sollte diverse Darstellungsprobleme beheben, die z. B. bei Claude Code auftraten. + +**Weitere Änderungen:** + +- **`backgrounds.json`** - `presets/bg.json` wurde in `backgrounds.json` umbenannt, Hintergrundkonfiguration in den neuen Schlüssel `tab:background` verschoben (automatische Migration beim Start) +- **Konfigurationsfehler verschoben** - Konfigurationsfehler wurden aus der Tableiste entfernt und in die Settings-/WaveConfig-Ansicht verschoben, um weniger Unordnung zu erzeugen +- **Warnung bei ungespeicherten Änderungen** - Die WaveConfig-Ansicht warnt jetzt, bevor ungespeicherte Änderungen verworfen werden +- **Stream-Performance** - Datei-Streaming auf eine neue, moderne Schnittstelle mit Flusskontrolle migriert, wodurch ein großer Time-to-First-Byte-Streaming-Fehler behoben wurde +- **macOS Erster Klick** - Verbesserte Behandlung des ersten Klicks unter macOS (Klick wird abgebrochen, aber Block-/WaveAI-Fokus korrekt gesetzt) +- Das veraltete Legacy-KI-Widget wurde entfernt +- [Fehlerbehebung] Fokus-Fehler bei neu erstellten Blöcken behoben +- [Fehlerbehebung] Problem beim Start einer neuen dauerhaften Sitzung durch Aufteilen einer alten behoben +- Electron auf v41 aktualisiert +- Paket-Updates und Abhängigkeits-Upgrades + +### v0.14.2 — 12. Mär 2026 + +Wave v0.14.2 fügt Block-/Tab-Badges, Verbesserungen der Verzeichnisvorschau und diverse Fehlerbehebungen hinzu. + +**Block-/Tab-Badges:** + +- **Badges auf Blockebene, in Tabs zusammengefasst** - Blöcke können jetzt Icon-Badges (mit Farbe und Priorität) anzeigen, die zusammengefasst und in der Tableiste für einen Statusüberblick auf einen Blick sichtbar sind +- **Glocken-Indikator standardmäßig aktiviert** - Das Terminal-Glocken-Badge ist jetzt standardmäßig aktiviert und leuchtet den Block und Tab auf, wenn dein Terminal die Glocke auslöst (gesteuert über `term:bellindicator`) +- **`wsh badge`** - Neuer Befehl `wsh badge` zum Setzen oder Entfernen von Badges an Blöcken über die Kommandozeile. Unterstützt Icons, Farben, Prioritäten, Signalton und PID-gebundene Badges, die automatisch entfernt werden, wenn ein Prozess beendet wird. Ideal in Kombination mit Claude-Code-Hooks, um Benachrichtigungen in der Tableiste sichtbar zu machen ([Dokumentation](https://docs.waveterm.dev/wsh-reference#badge)) + +**Weitere Änderungen:** + +- **Verbesserungen der Verzeichnisvorschau** - Verbesserte Formatierung der Änderungszeit, zebragestreifte Zeilen, bessere Standardsortierung, YAML-Dateiunterstützung und Verbesserungen am Kontextmenü +- **Suchleiste** - Verbesserungen bei Zwischenablage und Fokus in der Suchleiste +- [Fehlerbehebung] "New Window" hing sich unter GNOME-Desktops auf/funktionierte nicht - behoben +- [Fehlerbehebung] "Save Session As..." behoben (Fehler bei der Verfolgung des fokussierten Fensters) +- [Fehlerbehebung] Zoom-Änderungsbenachrichtigungen wurden nicht korrekt an alle Tabs gesendet (Layout-Inkonsistenzen) +- Ein Release-Notes-Link im Einstellungsmenü hinzugefügt +- Arbeit am anthropic-messages-Wave-AI-Backend (für native Claude-Integration) +- Viel interne Arbeit an Test-/Mock-Infrastruktur, um schnellere asynchrone KI-Bearbeitungen zu ermöglichen +- Dokumentations-Updates +- Paket-Updates und Abhängigkeits-Upgrades + +### v0.14.1 — 3. Mär 2026 + +Wave v0.14.1 behebt mehrere folgenreiche Terminal-Fehler (Claude-Code-Scrolling, IME-Eingabe) und fügt neue Konfigurationsoptionen hinzu: Fokus folgt dem Mauszeiger, Anpassung des Cursor-Stils, Workspace-gebundene Widgets und vim-artige Block-Navigation. + +**Terminal-Verbesserungen:** + +- **Claude-Code-Scroll-Fix** - Ein langjähriger Fehler behoben, durch den Terminal-Fenster unerwartet nach oben sprangen, was viele Claude-Code-Nutzer betraf +- **IME-Fix** - Koreanische/CJK-Eingabe behoben, bei der Zeichen verloren gingen oder in der Komposition hängen blieben und erst bei Leertaste übernommen wurden +- **Scroll-Position bei Größenänderung erhalten** - Das Terminal bleibt bei Größenänderungen jetzt am unteren Rand gescrollt, wenn es dort bereits war +- **Bessere Link-Behandlung** - Terminal-URLs haben jetzt verbesserte Kontextmenüs und Tooltips für einfachere Navigation +- **Terminal-Scrollback speichern** - Neuer Kontextmenü-Eintrag und `wsh`-Befehl, um den Terminal-Scrollback in eine Datei zu speichern + +**Neue Funktionen:** + +- **Fokus folgt Mauszeiger** - Neue Einstellung `app:focusfollowscursor` (off/on/term) für Hover-basierten Block-Fokus +- **Terminal-Cursor-Stil & Blinken** - Neue Einstellungen für Cursor-Stil (block/bar/underline) und Blinken, pro Block konfigurierbar +- **Tab-Schließen-Bestätigung** - Neue Einstellung `tab:confirmclose`, um vor dem Schließen eines Tabs nachzufragen +- **Workspace-gebundene Widgets** - Neues optionales Feld `workspaces` in `widgets.json`, um Widgets pro Workspace ein-/auszublenden +- **Vim-artige Block-Navigation** - Strg+Umschalt+H/J/K/L zum Navigieren zwischen Blöcken hinzugefügt +- **Neue KI-Anbieter** - Groq und NanoGPT als integrierte KI-Anbieter-Voreinstellungen hinzugefügt + +**Weitere Änderungen:** + +- Zeitweise auftretende Fehler beim Verbindungswechsel in Terminal-Blöcken behoben +- Verbesserungen am widgets.json-Schema für bessere Editor-Validierung +- Paket-Updates und Abhängigkeits-Upgrades +- Interne Code-Bereinigung und Refactoring + +### v0.14.0 — 10. Feb 2026 + +**Dauerhafte SSH-Sitzungen und verbesserte Verbindungsüberwachung** + +Wave v0.14 führt dauerhafte Sitzungen für SSH-Verbindungen ein, sodass deine entfernten Terminal-Sitzungen Verbindungsunterbrechungen, Netzwerkwechsel und Wave-Neustarts überstehen. Dieses Release enthält außerdem größere Verbesserungen der Verbindungsüberwachung, der RPC-Infrastruktur mit Flusskontrolle und erweiterte Terminal-Funktionen. + +**Dauerhafte Sitzungen (nur entferntes SSH):** +- **Unterbrechungen überstehen** - SSH-Terminal-Sitzungen bleiben über Netzwerkwechsel, Ruhezustand des Computers und Wave-Neustarts hinweg bestehen und verbinden sich automatisch neu, sobald die Verbindung wiederhergestellt ist +- **Sitzungsschutz** - Shell-Status, laufende Programme und Terminal-Verlauf bleiben erhalten, selbst wenn Wave geschlossen oder getrennt ist +- **Visuelle Statusanzeigen** - Schild-Symbole in Terminal-Kopfzeilen zeigen den Sitzungsstatus (Standard, dauerhaft verbunden, dauerhaft getrennt, dauerhaft wartend) mit detaillierten Flyover-Informationen +- **Flexible Konfiguration** - Konfiguration auf globaler, Verbindungs- oder Blockebene mit einfachem Wechsel zwischen Standard- und dauerhaftem Modus +- Siehe die neue [Dokumentation zu dauerhaften Sitzungen](https://docs.waveterm.dev/durable-sessions) für Einrichtung und Nutzung + +**Verbesserte Verbindungsüberwachung:** +- **Verbindungs-Keepalives** - Aktive Überwachung von SSH-Verbindungen mit automatischen Keepalive-Prüfungen +- **Erkennung hängender Verbindungen** - Ein neuer Verbindungsmonitor erkennt und zeigt "hängende" Verbindungszustände bei Netzwerkproblemen an und liefert klares visuelles Feedback +- **Bessere Fehlerbehandlung** - Verbesserte Nachverfolgung des Verbindungsstatus und nutzerseitige Statusanzeigen + +**Terminal-Verbesserungen:** +- **OSC-52-Zwischenablage-Unterstützung** - Terminal-Anwendungen können jetzt über OSC-52-Escape-Sequenzen direkt in deine Systemzwischenablage kopieren +- **Erweitertes Kontextmenü** - Rechtsklick auf Terminals für schnellen Zugriff auf Splits, URL-Öffnen, Themes, Dateibrowser und mehr +- **Vereinfachtes Kopfzeilenlayout** - Terminal-Kopfzeilen konzentrieren sich jetzt auf Verbindungsinformationen ohne redundante Ansichtstyp-Beschriftungen + +**Wave-AI-Updates:** +- **Bild-/Vision-Unterstützung** - Bildunterstützung für die OpenAI-Chat-Completions-API hinzugefügt, was Vision-Fähigkeiten mit kompatiblen Modellen ermöglicht +- **Generierung stoppen** - Neue Möglichkeit, KI-Antworten mitten in der Generierung zu stoppen, für OpenAI- und Gemini-Backends +- **KI-Panel-Scroll-Verriegelung** - Verbessertes automatisches Scroll-Verhalten im Wave-AI-Panel +- **Konfigurierbare Ausführlichkeit** - Steuerung der Ausführlichkeitsstufen für die OpenAI-Responses-API +- Veralteter alter KI-Widget-Proxy-Endpunkt abgekündigt + +**RPC und Performance:** +- **RPC-Streaming mit Flusskontrolle** - Neue Streaming-Primitive mit integrierter Flusskontrolle für bessere Performance und Zuverlässigkeit +- **WSH-Router-Refactor** - Größere Verbesserungen der Routing-Architektur, um Hänger bei Verbindungsunterbrechungen zu verhindern +- **RPC-Client-/Server-Bereinigung** - Verbesserte RPC-Implementierung und Fehlerbehandlung + +**Konfigurations-Updates:** +- **KI-Button ausblenden** - Neue Einstellung `app:hideaibutton`, um den KI-Button aus der UI auszublenden +- **Strg+Umschalt-Pfeile deaktivieren** - Neue Einstellung `app:disablectrlshiftarrows` für Konflikte mit Tastenkombinationen +- **Strg+Umschalt-Anzeige deaktivieren** - Neue Einstellung `app:disablectrlshiftdisplay`, um die Blocknummern-Overlay-Anzeige zu deaktivieren + +**Breaking Changes:** +- **Angeheftete Tabs entfernt** - Die Funktion für angeheftete Tabs wurde aus der UI entfernt +- **S3 und WaveFile entfernt** - S3-Dateisystem- und Wavefile-Implementierungen entfernt, um Probleme mit großen/rekursiven Dateiübertragungen zu vermeiden und die Codebasis zu vereinfachen + +**Weitere Änderungen:** +- **Bestätigung beim Beenden** - Bestätigungsdialog beim Schließen von Wave mit aktiven Sitzungen hinzugefügt +- Monaco-Editor-Upgrade, das die Abhängigkeiten `monaco-editor/loader` und `monaco-editor/react` für bessere Performance und Stabilität entfernt +- Neues Tab-Modell mit React-Provider für verbessertes State-Management +- Veraltete OSC-23198- und OSC-9283-Legacy-Handler entfernt +- Contribution-Guidelines aktualisiert +- Go-Toolchain auf 1.25.6 aktualisiert +- Erweiterte Dokumentation für OpenAI-kompatible API-Anbieter +- [Fehlerbehebung] Behandlung leerer Daten in der Sysinfo-Ansicht behoben +- [Fehlerbehebung] Einstellung `app:ctrlvpaste` unter Windows behoben (kann jetzt deaktiviert werden) +- [Fehlerbehebung] Doppelter Wave-AI-Systemprompt bei manchen Anbietern behoben +- [Fehlerbehebung] Hängenbleiben bei Trennung behoben - Trennungen erfolgen jetzt sofort +- [Fehlerbehebung] Lebenszyklus der Tool-Genehmigung an das Timing der SSE-Verbindung angepasst +- [Fehlerbehebung] WSL-Verbindungs-Timeout erhöht, um langsamen anfänglichen WSL-Start zu berücksichtigen +- [Fehlerbehebung] Terminal-Beendigung mit SIGHUP verbessert für einen sauberen Shell-Exit +- Paket-Updates und Abhängigkeits-Upgrades + +### v0.13.1 — 16. Dez 2025 + +**Windows-Verbesserungen und Wave-AI-Erweiterungen** + +Dieses Release konzentriert sich auf deutliche Verbesserungen der Windows-Plattform, visuelle Wave-AI-Updates und mehr Flexibilität bei der lokalen KI-Nutzung. + +**Windows-Plattform-Erweiterungen:** +- **Integriertes Fensterlayout** - Separate Titel- und Menüleiste unter Windows entfernt, Steuerelemente direkt in die Tableisten-Kopfzeile integriert für eine übersichtlichere, einheitlichere Oberfläche +- **Automatische Git-Bash-Erkennung** - Wave erkennt jetzt automatisch Git-Bash-Installationen und fügt sie zum Verbindungs-Dropdown für einfachen Zugriff hinzu +- **SSH-Agent-Fallback** - Verbesserte SSH-Agent-Unterstützung mit automatischem Fallback auf `\\.\pipe\openssh-ssh-agent` unter Windows +- **Aktualisierte Fokus-Tastenkombination** - Die WaveAI-Fokus-Taste wurde unter Windows für bessere Konsistenz auf Alt:0 geändert +- **Konfigurationsschemas** - Verbesserte Konfigurationsvalidierung und Schema-Unterstützung +- Strg-V funktioniert unter Windows im Terminal jetzt als Standard-Einfügen + +**Wave-AI-Updates:** +- **Aufgefrischtes visuelles Design** - Vollständige UI-Auffrischung, blaue Akzente entfernt, Transparenzunterstützung für bessere Integration mit benutzerdefinierten Hintergründen hinzugefügt +- **BYOK ohne Telemetrie** - Wave AI funktioniert jetzt mit Bring-your-own-Key und lokalen Modellen, ohne dass Telemetrie aktiviert sein muss +- [Fehlerbehebung] Kompatibilität des Tool-Typs "function" mit Anbietern wie Mistral behoben + +**Terminal-Verbesserungen:** +- **Neue Scroll-Tastenkombinationen** - Umschalt+Pos1, Umschalt+Ende, Umschalt+Bild-hoch und Umschalt+Bild-runter für bessere Terminal-Navigation hinzugefügt + +**Weitere Änderungen:** +- Paket-Updates und Abhängigkeits-Upgrades + +### v0.13.0 — 8. Dez 2025 + +**Wave v0.13 bringt lokale KI-Unterstützung, BYOK und einheitliche Konfiguration** + +Wave v0.13 ist ein größeres Release, das Wave AI für lokale Modelle, Drittanbieter und Bring-your-own-Key-(BYOK)-Konfigurationen öffnet. Dieses Release enthält außerdem ein komplett neu gestaltetes Konfigurationssystem und mehrere Terminal-Verbesserungen. + +**Lokale KI & BYOK-Unterstützung:** +- **OpenAI-kompatible API** - Wave unterstützt jetzt jeden Anbieter oder lokalen Server, der den Endpunkt `/v1/chat/completions` nutzt, was die Nutzung von Ollama, LM Studio, vLLM, OpenRouter und unzähligen anderen lokalen und gehosteten Modellen ermöglicht +- **Google-Gemini-Integration** - Native Unterstützung für Googles Gemini-Modelle mit dediziertem API-Adapter +- **Anbieter-Voreinstellungen** - Vereinfachte Konfiguration mit integrierten Voreinstellungen für OpenAI, OpenRouter, Google, Azure und benutzerdefinierte Endpunkte +- **Mehrere KI-Modi** - Einfacher Wechsel zwischen verschiedenen Modellen und Anbietern über eine einheitliche Oberfläche +- Siehe die neue [Dokumentation zu Wave-AI-Modi](https://docs.waveterm.dev/waveai-modes) für Konfigurationsbeispiele und Einrichtungsanleitungen + +**Einheitliches Konfigurations-Widget:** +- **Neue Konfigurationsoberfläche** - Den einfachen JSON-Editor durch ein dediziertes, über die Seitenleiste zugängliches Konfigurations-Widget ersetzt +- **Bessere Organisation** - Verschiedene Konfigurationstypen (allgemeine Einstellungen, KI-Modi, Secrets) mit verbesserter Validierung und Fehlerbehandlung durchsuchen und bearbeiten +- **Integrierte Secrets-Verwaltung** - Direkter Zugriff auf Waves Secret-Speicher aus dem Konfigurations-Widget für sichere Zugangsdatenverwaltung + +**Terminal-Verbesserungen:** +- **Bracketed-Paste-Modus** - Jetzt standardmäßig aktiviert, um das Einfügen mehrzeiliger Inhalte und die Kompatibilität mit Tools wie Claude Code zu verbessern +- **Windows-Einfüge-Fix** - Strg+V funktioniert unter Windows jetzt als Standard-Einfüge-Tastenkombination +- **SSH-Passwortverwaltung** - SSH-Verbindungspasswörter in Waves Secret-Speicher speichern, um wiederholtes Eintippen von Zugangsdaten zu vermeiden + +**Weitere Änderungen:** +- Paket-Updates und Abhängigkeits-Upgrades +- Verschiedene Fehlerbehebungen und Stabilitätsverbesserungen + +### v0.12.5 — 24. Nov 2025 + +Schnelles Patch-Release, um das Einfügeverhalten unter Linux zu beheben (verhindert, dass rohes HTML ins Terminal eingefügt wird). + +### v0.12.4 — 21. Nov 2025 + +Schnelles Patch-Release mit Fehlerbehebungen und kleineren Verbesserungen. + +- Neue Einstellung `term:macoptionismeta` für macOS, um die Option-Taste im Terminal als Meta-Taste zu behandeln +- Verzeichnisverfolgung für zsh-Shells behoben +- Editor-Kopieroperationen behoben +- Kleinere Wave-AI-Verbesserungen (Bildbehandlung, Scrollen, Fokus) +- Paket-Updates und Abhängigkeits-Upgrades +- WIP: WaveApps-Builder-Framework (noch nicht veröffentlicht) + +### v0.12.3 — 17. Nov 2025 + +Patch-Release mit Wave-AI-Modell-Upgrade, neuen Secret-Verwaltungsfunktionen und verbesserter Terminal-Eingabebehandlung. + +**Wave-AI-Updates:** +- **GPT-5.1-Modell** - Upgrade auf OpenAIs GPT-5.1-Modell für verbesserte Antworten +- **Umschalter für Denkmodus** - Neues Dropdown zur Auswahl zwischen den Denkmodi Quick, Balanced und Deep für optimale Balance zwischen Antwortqualität und Geschwindigkeit +- [Fehlerbehebung] Pfad-Abgleichsproblem bei der Wiederherstellung von KI-Datei-Schreibsicherungen behoben + +**Neue Funktionen:** +- **Secret-Speicher** - Neues Secret-Verwaltungs-Widget zum Speichern und Verwalten sensibler Zugangsdaten. Zugriff auf Secrets über die CLI mit den Befehlen `wsh secret list/get/set` + +**Terminal-Verbesserungen:** +- **Erweiterte Eingabebehandlung** - Bessere Unterstützung für interaktive CLI-Tools wie Claude Code. Umschalt+Enter fügt jetzt standardmäßig Zeilenumbrüche für mehrzeilige Befehle ein +- **Bild-Einfüge-Unterstützung** - Bilder direkt ins Terminal einfügen (werden in temporären Dateien gespeichert, Pfad wird eingefügt). Funktioniert hervorragend mit Claude Code! +- **IME-Fix** - Problem mit doppeltem Text beim Wechseln der Eingabemethode während chinesischer/japanischer/koreanischer Komposition behoben + +**Weitere Änderungen:** +- Verbesserte Backend-Panic-Nachverfolgung für besseres Debugging +- Speicherleck rund um sysinfo-Ereignisse behoben +- WIP: Neues WaveApps-Builder-Framework (noch nicht veröffentlicht) +- Paket-Updates und Abhängigkeitsanhebungen + +### v0.12.2 — 4. Nov 2025 + +Wave v0.12.2 fügt Wave AI die Fähigkeit hinzu, Dateien zu bearbeiten. Vor dem Genehmigen einer Dateibearbeitung kannst du leicht einen Diff einsehen (dargestellt im Monaco-Editor-Diff-Viewer), und nach dem Genehmigen einer Bearbeitung kannst du die Änderung mit einem "Revert File"-Button leicht rückgängig machen. + +**Wave-AI-Updates:** +- **Datei-Schreib-Tool** - Wave AI kann jetzt mit deiner Genehmigung Dateien erstellen und ändern +- **Visuelle Diff-Vorschau** - Sieh genau, was sich ändert, bevor du Bearbeitungen genehmigst, dargestellt im Monaco Editor +- **Einfaches Rückgängigmachen** - Dateiänderungen mit einem einfachen "Revert File"-Button rückgängig machen +- **Dateien per Drag & Drop** - Dateien direkt aus dem Vorschau-Viewer zu Wave AI ziehen +- **Verzeichnislisten** - `wsh ai` kann jetzt Verzeichnislisten an Chats anhängen +- **Anpassbare Einstellungen** - Denk-Level und maximale Ausgabe-Tokens pro Chat steuern + +**Fehlerbehebungen & Verbesserungen:** +- Ein erhebliches Speicherleck im RPC-System behoben +- Schema-Validierung für Konfigurationsdateien funktioniert wieder +- Verbesserte Tool-Beschreibungen und Eingabevalidierungen (laufen vor Tool-Genehmigungen) +- Problem mit vorzeitigen Tool-Timeouts behoben +- Regression mit PowerShell 5.x behoben +- Problem mit Prompt-Caching beim Anhängen von Dateien behoben + +### v0.12.1 — 20. Okt 2025 + +Patch-Release mit Fokus auf Verbesserungen der Shell-Integration und Wave-AI-Erweiterungen. Dieses Release behebt Syntax-Highlighting im Code-Editor und fügt umfangreiche Shell-Kontext-Verfolgung hinzu. + +**Shell-Integration & Kontext:** +- **OSC-7-Unterstützung** - OSC-7-Unterstützung (aktuelles Arbeitsverzeichnis) für bash, zsh, fish und pwsh hinzugefügt. Wave verfolgt und stellt jetzt automatisch dein aktuelles Verzeichnis über Neustarts hinweg wieder her, sowohl für lokale als auch entfernte Terminals. +- **Shell-Kontext-Verfolgung** - Shell-Integration für bash-, zsh- und fish-Shells implementiert. Wave verfolgt jetzt, wann deine Shell bereit ist, Befehle zu empfangen, den zuletzt ausgeführten Befehl und Exit-Codes. Dieser erweiterte Kontext ermöglicht besseres Terminal-Management und legt den Grundstein dafür, dass Wave AI Befehle intelligent schreiben und ausführen kann. + +**Wave-AI-Verbesserungen:** +- Zusammenfassungen des Denkprozesses in der UI anzeigen, während auf KI-Antworten gewartet wird +- Erweiterten Terminal-Kontext hinzugefügt - Wave AI hat jetzt Zugriff auf Shell-Status einschließlich aktuellem Verzeichnis, Befehlsverlauf und Exit-Codes +- Feedback-Buttons (Daumen hoch/runter) für KI-Antworten hinzugefügt, um die Erfahrung zu verbessern +- Kopieren-Button hinzugefügt, um KI-Antworten einfach in die Zwischenablage zu kopieren + +**Weitere Änderungen:** +- Unterstützung für mobile User-Agent-Emulation für Web-Widgets [#2442](https://github.com/wavetermdev/waveterm/issues/2442) +- [Fehlerbehebung] Abstand für Kopfzeilen-Buttons im Code-Editor behoben (Tailwind-Regression) +- [Fehlerbehebung] Syntax-Highlighting in Code-Editor-Vorschau-Blöcken wiederhergestellt [#2427](https://github.com/wavetermdev/waveterm/issues/2427) +- Paket-Updates und Abhängigkeitsanhebungen + +### v0.12.0 — 16. Okt 2025 + +**Wave v0.12 ist da, mit Wave AI (Beta)!** + +Wave Terminal v0.12.0 führt eine komplett neu gestaltete KI-Erfahrung ein, angetrieben von OpenAI GPT-5. Das ist ein bedeutendes Upgrade und eine Modernisierung gegenüber Waves bisheriger KI-Integration, mit Multimodalitäts-Unterstützung, fortschrittlicher Tool-Integration und einer intuitiven neuen Oberfläche. Allein der Haupt-KI-PR umfasste 128 Commits und fügte über 13.000 Zeilen Code hinzu. + +**Wave-AI-Funktionen:** +- **Neues ausklappbares Chat-Panel** - Zugriff auf Wave AI über Tastenkombinationen (Cmd-Shift-A oder Strg-Umschalt-0) von der linken Bildschirmseite +- **Multimodale Eingabe** - Unterstützung für Bild-, PDF- und Textdatei-Anhänge +- **Dateien per Drag & Drop** - Dateien einfach in den Chat ziehen, um sie anzuhängen +- **Kommandozeilen-Integration** - Dateien und Befehlsausgaben direkt mit `wsh ai` an Wave AI senden +- **Intelligenter Kontextwechsel** - Wave AI ermöglichen, in deine Widgets und dein Dateisystem zu sehen +- **Integrierte Tools:** + - Websuche-Fähigkeiten + - Lokale Datei- und Verzeichnisoperationen + - Widget-Screenshots + - Zugriff auf Terminal-Scrollback + - Web-Navigation + +Wave AI befindet sich in einer aktiven Beta-Phase mit enthaltenem KI-Guthaben, während wir die Erfahrung weiter verfeinern. BYOK (Bring Your Own Key) wird verfügbar sein, sobald wir die Kernfunktionen stabilisiert und Feedback dazu gesammelt haben, was am besten funktioniert. Teile dein Feedback in unserem [Discord](https://discord.gg/XfvZ334gwU). + +Weitere Informationen und kommende Funktionen findest du in unserer [Wave-AI-Dokumentation](https://docs.waveterm.dev/waveai). + +**Weitere Verbesserungen:** +- Neuer Onboarding-Ablauf, der Block-Vergrößerung, Wave AI und wsh view/edit-Fähigkeiten vorstellt +- Neuer Befehl `wsh blocks list` zum Auflisten und Filtern von Blöcken nach Workspace, Tab oder Ansichtstyp +- Fortgesetzte Migration von SCSS zu Tailwind v4 +- Paket-Upgrades und Abhängigkeits-Updates +- Interne Code-Bereinigung und Refactoring + +### v0.11.6 — 22. Sep 2025 + +Patch-Release zur Behebung eines Editor-Fehlers, wenn zwei Dateien in separaten Bearbeitungs-Widgets geöffnet werden. Fügt außerdem Mermaid-Unterstützung für Markdown-Blöcke hinzu. + +* WIP: Große KI-Überarbeitung kommt (Multimodalitäts-Unterstützung, Premium-Modelle und Tool-Unterstützung) +* WIP: Integration des neuen Tsunami-Widget-Frameworks, um das Schreiben und Ausführen von Wave-Widgets zu vereinfachen +* Viele Paket-Updates +* Viel interne Bereinigung (Vorschau-Widget) +* Weitere Migration zu Tailwind-v4-CSS +* Build-Updates, Wechsel von yarn zu npm + +### v0.11.5 — 28. Aug 2025 + +Ein weiteres Aufräum-Release, um Wave zu modernisieren und aktueller zu machen. + +* Der Wave-AI-Cloud-Proxy nutzt jetzt gpt-5-mini (Upgrade von gpt-4o-mini) +* JWT-Problem beim Ausführen von "Wave Apps" aus Widgets behoben +* Eine "$ENV:envvar:fallback"-Syntax zu den Konfigurationsdateien hinzugefügt, damit Waves Konfiguration Werte aus der Umgebung übernehmen kann (hauptsächlich, um Secrets aus den Konfigurationsdateien herauszuhalten) +* Neue Einstellung, um die Anzeige von Overlay-Blocknummern beim Halten von Strg:Umschalt zu deaktivieren (`app:showoverlayblocknums`) +* Neue Einstellung, damit Umschalt-Enter mit Tools wie Claude Code funktioniert (`term:shiftenternewline`) +* Frontend auf React 19 aktualisiert +* Weitere Teile des Frontends zu Tailwind v4 migriert (in Arbeit) +* Universellen MacOS-Build entfernt. 90% der Mac-Nutzer sind mittlerweile auf Apple Silicon, sodass der universelle Build weniger wichtig ist (größere Dateigröße, komplizierterer Build-Prozess) +* [Fehlerbehebung] Build-IDs im RPM-Build entfernt, um Konflikte mit Slack zu beheben +* Einige Wave-v7-bewusste Upgrades und alte Codepfade entfernt +* Interne Bereinigung, TypeScript-Fehler, Linting-Fixes usw. +* Weitere diverse Go-/npm-Paket-Anhebungen + +### v0.11.4 — 19. Aug 2025 + +Schnelles Patch-Release zur Aktualisierung von Paketen, Behebung einiger Sicherheitsprobleme (bei abhängigen Paketen) und einiger kleiner Fehlerbehebungen. + +* KI-Bibliotheken aktualisiert, GPT-5 wird jetzt in WaveAI unterstützt +* Einstellung `ai:proxyurl` hinzugefügt, um Proxy-Zugriff (z. B. SOCKS) für KI-Zugriff zu ermöglichen + +### v0.11.3 — 2. Mai 2025 + +Schnelles Patch-Release zur Aktualisierung von Paketen, Behebung einiger Sicherheitsprobleme (bei abhängigen Paketen) und einiger kleiner Fehlerbehebungen. + +### v0.11.2 — 8. März 2025 + +Schnelles Patch-Release zur Behebung eines Backend-Panics und Rückgängigmachen einer Änderung, die WSL-Verbindungen zum Hängen brachte. + +### v0.11.1 — 28. Feb 2025 + +Wave Terminal v0.11.1 fügt gegenüber v0.11.0 eine Menge neuer Funktionen hinzu (es hätte fast v0.12 werden können)! + +Die Hauptfunktion ist, dass unser Datei-/Vorschau-Widget jetzt das Durchsuchen von S3-Buckets unterstützt. Wir lesen Zugangsdaten direkt aus deiner ~/.aws/config, und du kannst jetzt einfach eines deiner AWS-Profile in unserem Verbindungs-Dropdown auswählen, um S3-Dateien anzusehen. Wir unterstützen sogar das Bearbeiten von S3-Textdateien mit unserem integrierten Editor. + +Außerdem viele weitere Funktionen und Fehlerbehebungen: + +- **S3-Bucket**-Verzeichnisansicht und Dateivorschau +- **Dateien und Verzeichnisse per Drag & Drop** zwischen Wave-Verzeichnisansichten verschieben. Das funktioniert über Rechner hinweg und zwischen entfernten Rechnern und S3-Verbindungen. +- JSON-Schema-Unterstützung für einige unserer Konfigurationsdateien hinzugefügt. Du erhältst jetzt Autovervollständigungs-Popups für Felder in unseren Dateien settings.json, widgets.json, ai.json und connections.json. +- Neue Unterstützung für das Teilen von Blöcken -- Nutze Cmd-D und Cmd-Umschalt-D, um horizontal und vertikal zu teilen. Für mehr Kontrolle kannst du Strg-Umschalt-S und dann Hoch/Runter/Links/Rechts nutzen, um in die gewünschte Richtung zu teilen. +- Block löschen (ohne ihn aus dem Layout zu entfernen). Mit Strg-Umschalt-D kannst du einen Block entfernen, während er im Layout bleibt. Du kannst dann an seiner Stelle ein neues Widget starten. +- `wsh file` unterstützt jetzt das Kopieren von Dateien zwischen deinem lokalen Rechner, entfernten Rechnern und zu/von S3 +- Neues Analytics-Framework (ereignisbasiert statt zählerbasiert). Siehe die Telemetrie-Dokumentation für weitere Informationen. +- Web-Lesezeichen! In deiner bookmarks.json-Datei bearbeiten, im Web-Widget mit Cmd+O öffnen +- Änderungen an deiner ai.json-Voreinstellungsdatei wirken sich jetzt _sofort_ in KI-Widgets aus +- Deutlich bessere Fehlerbehandlung und -meldungen bei Fehlern im Vorschau- oder Editor-Widget +- `wsh ssh --new` hinzugefügt, um die neue SSH-Verbindung in einem neuen Widget zu öffnen +- Neuer Befehl `wsh launch`, um ein beliebiges, in widget.json definiertes benutzerdefiniertes Widget zu öffnen +- Bei Nutzung der Terminal-Mehrfacheingabe (Strg-Umschalt-I) wird eingefügter Text jetzt an alle Terminals gesendet +- [Fehlerbehebung] Einige hängende Goroutinen behoben, wenn Befehle fehlschlugen oder ein Timeout hatten +- [Fehlerbehebung] Einige Dateiendungs-MIME-Types behoben, um den Editor für mehr Dateitypen zu aktivieren +- [Fehlerbehebung] Drücken von "Tab" ließ manchmal ein Widget aus dem Bildschirm scrollen und dadurch unbenutzbar werden - behoben +- [Fehlerbehebung] XDG-Variablen gelangen nicht mehr in Terminal-Widgets +- Tailwind-CSS- und shadcn-Unterstützung hinzugefügt, um neue Widgets schneller bauen zu können +- Bessere interne Widget-Abstraktionen + +### v0.11.0 — 24. Jan 2025 + +Wave Terminal v0.11.0 enthält eine grundlegende Überarbeitung unserer Verbindungsinfrastruktur, mit Änderungen sowohl an unserem Backend als auch an den Remote-Dateiprotokollsystemen, zusätzlich zu zahlreichen Funktionen, Fehlerbehebungen und Stabilitätsverbesserungen. + +Eine zentrale Neuerung in diesem Release ist das neue Shell-Initialisierungssystem, das die Anpassung deiner Shell-Umgebung über lokale und entfernte Verbindungen hinweg ermöglicht. Du kannst jetzt Umgebungsvariablen und shell-spezifische Init-Skripte sowohl pro Block als auch pro Verbindung konfigurieren. + +Für den täglichen Gebrauch haben wir Suchfunktionen sowohl für Terminal- als auch für Web-Blöcke hinzugefügt, zusammen mit einer Terminal-Mehrfacheingabefunktion für gleichzeitige Eingabe an alle Terminals innerhalb eines Tabs. Wir haben außerdem Unterstützung für Google Gemini in Wave AI hinzugefügt, was unsere Suite an KI-Integrationen erweitert. + +Hinter den Kulissen haben wir unser Remote-Dateiprotokoll neu gestaltet und damit den Grundstein für die kommende S3-Unterstützung (und S3-kompatible Systeme) in unserem Vorschau-Widget gelegt. Diese architektonische Änderung ebnet den Weg, um in Zukunft weitere Datei-Backends hinzuzufügen. + +- **Anpassung der Shell-Umgebung** -- Konfiguriere deine Shell-Umgebung mit Umgebungsvariablen und Init-Skripten, mit Unterstützung für lokale und entfernte Verbindungen +- **Verbesserungen am Verbindungs-Backend** -- Große Überarbeitung mit verbesserter Shell-Erkennung, besserem Fehler-Logging und weniger 2FA-Abfragen bei Nutzung von ForceCommand +- **Multi-Shell-Unterstützung** -- Erweiterte Unterstützung für bash-, zsh-, pwsh- und fish-Shells mit shell-spezifischen Initialisierungsfähigkeiten +- **Terminal-Suche** -- nutze Cmd-F, um Text in Terminal-Widgets zu suchen +- **Web-Suche** -- nutze Cmd-F, um Text in Web-Ansichten zu suchen +- **Terminal-Mehrfacheingabe** -- Nutze Strg-Umschalt-I, um Mehrfacheingabe an alle Terminals im selben Tab zu ermöglichen +- **Wave AI unterstützt jetzt Google Gemini** +- Verbesserte WSL-Unterstützung mit wsh-freien Verbindungsoptionen +- Inline-Debugging-Informationen für Verbindungen hinzugefügt +- Probleme mit der Dateiberechtigungsbehandlung unter Windows-Systemen behoben +- Verbindungsbezogene Popups werden jetzt nur an das auslösende Fenster ausgeliefert +- Verbesserte Timeout-Behandlung für SSH-Verbindungen, die 2FA-Abfragen erfordern +- Behandlung der Escape-Taste in globalen Ereignis-Handlern behoben (Schließen von Modals) +- Die Verzeichnisvorschau füllt jetzt die gesamte Blockbreite +- Benutzerdefinierte Widgets können jetzt im vergrößerten Modus gestartet werden +- Verschiedene Verbesserungen der Workspace-UX rund um Schließen/Löschen +- file:///-URLs funktionieren jetzt im Web-Widget +- Maximale Dateigröße für `wsh ai` auf 50k erhöht +- Maximal zulässiges term:scrollback auf 50k Zeilen erhöht +- Verbindungen können jetzt vollständig in connections.json definiert werden, ohne auf ~/.ssh/config angewiesen zu sein +- Option hinzugefügt, um Dateien für die lokale Verbindung im externen Dateibetrachter anzuzeigen +- Neue Option "New Window" beim Rechtsklick auf das MacOS-Dock-Icon hinzugefügt +- [Build] Zu kostenlosen Ubuntu-ARM-Runnern für bessere ARM64-Build-Unterstützung gewechselt +- [Build] Windows-Builds nutzen jetzt zig, was das Windows-Entwicklungssetup vereinfacht +- [Fehlerbehebung] Das Verbindungs-Dropdown wird jetzt auch befüllt, wenn die ssh-Konfiguration fehlt oder ungültig ist +- [Fehlerbehebung] Bracketed-Paste-Modus standardmäßig deaktiviert (Konfigurationsoption, um ihn wieder zu aktivieren) +- [Fehlerbehebung] Timeout für `wsh ssh` auf 60s erhöht +- [Fehlerbehebung] Fix für das Sysinfo-Widget bei der Anzeige einer sehr großen Anzahl von CPU-Diagrammen +- [Fehlerbehebung] XDG-Variablen für Snap-Installationen behoben +- [Fehlerbehebung] SSH-Flag IdentitiesOnly wird jetzt beachtet (nützlich, wenn viele Schlüssel im ssh-agent geladen sind) +- [Fehlerbehebung] Bessere Einrichtung der Shell-Umgebungsvariablen beim Ausführen lokaler Shells +- [Fehlerbehebung] Vorschau für große Textdateien behoben +- [Fehlerbehebung] URLs im Terminal behoben (wieder anklickbar) +- [Fehlerbehebung] Windows-URLs funktionieren jetzt korrekt für Wave-Hintergrundbilder +- [Fehlerbehebung] Verbindungen starten ohne wsh, wenn der Unix-Domain-Socket nicht geöffnet werden kann +- [Fehlerbehebung] Die Verbindungsstatusliste leuchtet jetzt korrekt für aktuell verbundene Verbindungen +- [Fehlerbehebung] en_US.UTF-8 wird genutzt, wenn die angeforderte LANG in deinem Terminal nicht verfügbar ist +- Weitere Fehlerbehebungen, Performance-Verbesserungen und Abhängigkeits-Updates + +### v0.10.4 — 20. Dez 2024 + +Schnelles Update mit Fehlerbehebungen und neuen Konfigurationsoptionen + +- Konfigurationsoptionen "window:confirmclose" und "window:savelastwindow" hinzugefügt +- [Fehlerbehebung] Defekte Scrollleiste im KI-Widget behoben +- [Fehlerbehebung] Standardpfad für die wsh-Shell-Erkennung behoben (genutzt bei Remote-Verbindungen) +- Abhängigkeits-Updates + +### v0.10.3 — 19. Dez 2024 + +Schnelles Update zu v0.10 mit neuen Funktionen und Fehlerbehebungen. + +- Unterstützung für globale Tastenkombination [Dokumentation](https://docs.waveterm.dev/config#customizable-systemwide-global-hotkey) +- Konfiguration hinzugefügt, um die Schriftgröße für Markdown, KI-Chat und den Vorschau-Editor zu überschreiben [Dokumentation](https://docs.waveterm.dev/config) +- Möglichkeit hinzugefügt, eine unabhängige Zoomstufe für die Web-Ansicht festzulegen (Rechtsklick auf die Block-Kopfzeile) +- Neuer Befehl `wsh wavepath`, um das Konfigurationsverzeichnis, das Datenverzeichnis und die Log-Datei zu öffnen +- [Fehlerbehebung] Absturz behoben, wenn /etc/sshd_config eine nicht unterstützte Match-Direktive enthielt (am häufigsten bei Fedora) +- [Fehlerbehebung] Workspaces sind jetzt konsistenter über Fenster hinweg, schließt das zugehörige Fenster, wenn Workspaces gelöscht werden +- [Fehlerbehebung] zsh unter WSL behoben +- [Fehlerbehebung] Langjährigen Fehler behoben, bei dem manchmal Steuersequenzen in der Terminal-Ausgabe erschienen, wenn zwischen Tabs gewechselt wurde +- Viele neue Beispiele in der Dokumentation für Shell-Überschreibungen, Voreinstellungen, Widgets und Verbindungen +- Weitere Fehlerbehebungen und UI-Updates + +(Hinweis: Die Release Notes von v0.10.2 und v0.10.3 wurden zusammengeführt) + +### v0.10.1 — 12. Dez 2024 + +Schnelles Update zur Behebung der Workspace-App-Menü-Aktionen. Behebt außerdem den Workspace-Wechsel, sodass immer ein neues Fenster geöffnet wird, wenn er von einem Nicht-Workspace-Fenster aus aufgerufen wird. Das verringert die Gefahr, versehentlich die Tabs eines Nicht-Workspace-Fensters zu verlieren. + +### v0.10.0 — 11. Dez 2024 + +Wave Terminal v0.10.0 führt Workspaces ein, wodurch die Verwaltung mehrerer Arbeitsumgebungen einfacher wird. Wir haben leistungsstarke neue Befehlsausführungsfähigkeiten mit `wsh run` hinzugefügt, mit denen du Befehle in dedizierten Blöcken starten und steuern kannst. Dieses Release bringt außerdem deutliche Verbesserungen bei SSH mit einem neuen Konfigurationssystem für Verbindungen zur Verwaltung deiner entfernten Umgebungen. + +- **Workspaces**: Organisiere deine Arbeit in separate Umgebungen, jede mit eigenen Tabs, Layouts und Einstellungen +- **Command Blocks**: Neuer Befehl `wsh run` zum Starten von Terminal-Befehlen in dedizierten Blöcken, mit Unterstützung für Vergrößerung, automatisches Schließen und Ausführungssteuerung ([Dokumentation](https://docs.waveterm.dev/wsh-reference#run)) +- **Verbindungen**: Neues Konfigurationssystem zur Verwaltung von SSH-Verbindungen, mit Unterstützung für wsh-freien Betrieb, Themes pro Verbindung und mehr ([Dokumentation](https://docs.waveterm.dev/connections)) +- Verbessertes Tab-Management mit besserem Wechselverhalten und Kontextmenüs (viele Fehlerbehebungen) +- Neue Tab-Funktionen einschließlich angehefteter Tabs und Verbesserungen beim Drag & Drop +- Dateien/Verzeichnisse direkt in der Verzeichnisvorschau erstellen, umbenennen und löschen +- Versucht eine wsh-freie Verbindung als Fallback, wenn die wsh-Installation oder -Ausführung fehlschlägt +- Neues Flag `-i`, um Identitätsdateien mit dem Befehl `wsh ssh` hinzuzufügen +- Perplexity-API-Integration hinzugefügt ([Dokumentation](https://docs.waveterm.dev/faq#perplexity)) +- Befehl `wsh setbg` für die Hintergrundverwaltung ([Dokumentation](https://docs.waveterm.dev/wsh-reference#setbg)) +- Von Less zu SCSS für das Styling gewechselt +- [Fehlerbehebung] Tab-Flackern beim Tab-Wechsel behoben +- [Fehlerbehebung] Größenänderungsverhalten des WaveAI-Textbereichs korrigiert +- [Fehlerbehebung] Probleme mit gleichzeitigem Start von Block-Controllern behoben +- [Fehlerbehebung] Vorschau-Blöcke für nicht initialisierte Verbindungen behoben +- [Fehlerbehebung] Nicht reagierende Kontextmenüs behoben +- [Fehlerbehebung] Verbindungsfehler im Hilfe-Block behoben +- Go-Toolchain auf 1.23.4 aktualisiert +- Viel neue Dokumentation, darunter neue Seiten für [Erste Schritte](https://docs.waveterm.dev/gettingstarted), [KI-Voreinstellungen](https://docs.waveterm.dev/ai-presets) und den [wsh-Überblick](https://docs.waveterm.dev/wsh) +- Weitere Fehlerbehebungen, Performance-Verbesserungen und Abhängigkeits-Updates + +### v0.9.3 — 20. Nov 2024 + +Neues Minor-Release, das Waves verbundene Computing-Erweiterungen einführt. Wir haben neue `wsh`-Befehle eingeführt, mit denen du Variablen und Dateien speichern und über Terminal-Sitzungen hinweg (sowohl auf lokalen als auch entfernten Rechnern) darauf zugreifen kannst. + +- `wsh setvar/getvar` zum Abrufen und Setzen von Variablen -- [Dokumentation](https://docs.waveterm.dev/wsh-reference#getvarsetvar) +- `wsh file`-Operationen (cat, write, append, rm, info, cp und ls) -- [Dokumentation](https://docs.waveterm.dev/wsh-reference#file) +- Verbesserte Golang-Panic-Behandlung, um Backend-Abstürze zu verhindern +- Verbessertes SSH-Konfigurations-Logging und Behebung eines Fehlers bei wiederverwendeten Verbindungen +- Telemetrie aktualisiert, um zusätzliche Zähler zu verfolgen +- Neue Konfigurationseinstellungen (unter "window:magnifiedblock") zur Steuerung von Rändern und Anzeige vergrößerter Blöcke +- Neue Block-/Zonen-Aliase (client, global, block, workspace, temp) +- `wsh ai`-Dateianhänge werden jetzt mit spezieller Behandlung im KI-Block dargestellt +- Neuer flüchtiger Blocktyp zum Erstellen von modalen Widgets, die das darunterliegende Layout nicht stören +- Das Bearbeiten der KI-Voreinstellungsdatei aus dem Wave-AI-Block öffnet jetzt einen flüchtigen Editor +- Ein Klick außerhalb eines vergrößerten Blocks verkleinert ihn jetzt wieder +- Neuer Button zum Leeren des KI-Chats (auch an Cmd-L gebunden) +- Neuer Button zum Zurücksetzen von Terminal-Befehlen in benutzerdefinierten cmd-Widgets +- [Fehlerbehebung] Das Voreinstellungsverzeichnis wurde unter Windows nicht korrekt geladen +- [Fehlerbehebung] Vergrößerte Blöcke wurden beim Start nicht korrekt angezeigt +- [Fehlerbehebung] Fensterdeckkraft und Hintergrundfarbe wurden nicht in allen Fällen korrekt angewendet +- [Fehlerbehebung] Terminal-Theming beim Anwenden globaler Standardwerte behoben [#1287](https://github.com/wavetermdev/waveterm/issues/1287) +- MacOS 10.15 (Catalina) wird nicht mehr unterstützt +- Weitere Fehlerbehebungen, Dokumentationsverbesserungen und Abhängigkeitsanhebungen + +### v0.9.2 — 11. Nov 2024 + +Neues Minor-Release mit Fehlerbehebungen und neuen Funktionen! Der Fehler beim Vollbildmodus von Wave wurde behoben (betraf auch bestimmte Fenstermanager wie Hyprland). Wir haben außerdem viel Arbeit in die Dokumentations-Website (https://docs.waveterm.dev) gesteckt, einschließlich der Dokumentation, wie [Widgets](./widgets) und Voreinstellungen funktionieren! + +- Dokumentation aktualisiert +- Wave AI unterstützt jetzt die Anthropic-API! Schau in die [FAQ](./faq), wie du die Claude-Modelle mit Wave AI nutzt. +- defaultwidgets.json entfernt und in widgets.json vereinheitlicht. Macht es einfacher, die Standard-Widgets zu überschreiben. +- Neue Resolver für den `-b`-Parameter in `wsh`. "tab:N" für den Zugriff auf den n-ten Tab, "[view]" und "[view]:N" für den Zugriff auf Blöcke eines bestimmten Ansichtstyps. +- Neuer Befehl `wsh ai`, um KI-Chats (und Dateien) direkt an einen neuen oder bestehenden KI-Block zu senden +- Verbesserungen an wsh setmeta/getmeta. setmeta kann jetzt eine JSON-Datei entgegennehmen (auch von stdin lesen), außerdem bessere Ausgabeformate für getmeta (kompatibel mit setmeta) +- [Fehlerbehebung] Maximale Completion-Tokens in der OpenAI-API gesetzt, sodass wir jetzt mit o1-Modellen arbeiten können (auch Fallback auf Nicht-Streaming-Modus) +- [Fehlerbehebung] Inhaltsgrößenänderung beim Wechsel in den "Vollbild"-Modus behoben. Dieser Fehler betraf auch bestimmte Fenstermanager (wie Hyprland) +- Viele weitere kleine Fehlerbehebungen, Dokumentations-Updates und Abhängigkeitsanhebungen + +### v0.9.1 — 1. Nov 2024 + +Kleines Bugfix-Release als Nachfolger zum v0.9.0-Build. Viele Probleme behoben (besonders für Windows). + +- CLI-Anwendungen, die Mikrofon-, Kamera- oder Standortzugriff benötigen, funktionieren jetzt unter MacOS. Du siehst ein Sicherheits-Popup in Wave zum Erlauben/Ablehnen [#1086](https://github.com/wavetermdev/waveterm/issues/1086) +- `wsh version -v` kann jetzt genutzt werden, um die neuen Daten-/Konfigurationsverzeichnisse auszugeben +- Stellt die alte T1-, T2-, T3-, ...-Tab-Benennungslogik wieder her +- Vorübergehend zurück zur "Titelleiste" unter Windows, um einen Fehler zu umgehen, bei dem die Fenstersteuerelemente unsere Tabs überlagerten (an einer echten Lösung für das nächste Release wird gearbeitet) +- Es gibt eine neue Einstellung im Editor, um Zeilenumbruch zu aktivieren/deaktivieren [#1038](https://github.com/wavetermdev/waveterm/issues/1038) +- Strg-S speichert jetzt Dateien in codeedit [#1081](https://github.com/wavetermdev/waveterm/issues/1081) +- [#1020](https://github.com/wavetermdev/waveterm/issues/1020) es gibt jetzt eine Voreinstellungs-Konfigurationsoption, um die Farbe des aktiven Rahmens in Tab-Themes zu ändern +- [Fehlerbehebung] Mehrere Fixes für [#1167](https://github.com/wavetermdev/waveterm/issues/1167), um Tab-Verlust bei Updates zu beheben +- [Fehlerbehebung] Die Windows-App stürzte beim Öffnen des View-Menüs wegen eines fehlerhaften Accelerator-Keys ab +- [Fehlerbehebung] Die Auto-Updater-Meldungen in der Tableiste sind jetzt beim Tab-Wechsel konsistenter, und Fehler werden nicht mehr angezeigt, wenn das Netzwerk getrennt ist +- [Fehlerbehebung] Der Vollbildmodus zeigt jetzt tatsächlich Tabs im Vollbild an +- [Fehlerbehebung] [#1175](https://github.com/wavetermdev/waveterm/issues/1175) .awk-Dateien können jetzt bearbeitet werden +- [Fehlerbehebung] [#1066](https://github.com/wavetermdev/waveterm/issues/1066) Das Anwenden eines Standard-Themes aktualisiert den Hintergrund jetzt korrekt ohne Aktualisierung + +### v0.9.0 — 28. Okt 2024 + +Neues größeres Wave-Terminal-Release! Wave-Tabs werden jetzt zwischengespeichert. Der Tab-Wechsel ist +jetzt deutlich schneller, und Webview-Status, Editor-Status und Scroll-Positionen bleiben jetzt +über Tab-Wechsel hinweg erhalten. Wir haben außerdem native WSL2-Unterstützung. Du kannst native Wave-Verbindungen +zu deinen Windows-WSL2-Distributionen über den Verbindungs-Button erstellen. + +Wir haben außerdem den Grundstein für einige große Funktionen gelegt, die in den +kommenden Wochen veröffentlicht werden, darunter Workspaces, KI-Verbesserungen und benutzerdefinierte Widgets. + +Viele weitere kleinere Änderungen und Fehlerbehebungen. Die vollständige Liste der PRs findest du unter https://github.com/wavetermdev/waveterm/releases/tag/v0.9.0 + +### v0.8.13 — 24. Okt 2024 + +- Wave ist jetzt als Snap für Linux-Nutzer verfügbar! Du findest es [im Snap Store](https://snapcraft.io/waveterm). +- Wave ist jetzt über den Windows Package Manager verfügbar! Installation über `winget install CommandLine.Wave` +- "term:fontsize" kann jetzt genutzt werden, um die Schriftgröße eines einzelnen Terminal-Blocks zu überschreiben (auch im Kontextmenü) +- Wir erlauben jetzt gemischte Groß-/Kleinschreibung bei Hostnamen für Verbindungen, um mit der ssh-Konfiguration kompatibel zu sein +- Das Linux-App-Icon wurde aktualisiert, um dem Windows-Icon zu entsprechen +- [Fehlerbehebung] Fehler behoben, der manchmal dazu führte, dass beim Wechseln zwischen Tabs Escape-Sequenzen angezeigt wurden +- [Fehlerbehebung] Problem behoben, bei dem der Vorschau-Block temporäre Dateien nicht bereinigte (nur Windows) +- [Fehlerbehebung] Chrome-Sandbox-Berechtigungsfehler unter Linux behoben +- [Fehlerbehebung] Beendigungslogik unter MacOS/Linux behoben, die manchmal verwaiste Prozesse überleben ließ + +### v0.8.12 — 18. Okt 2024 + +- Unterstützung für mehrere KI-Konfigurationen hinzugefügt! Du kannst jetzt Open AI parallel zu Ollama-Modellen betreiben. KI-Voreinstellungen können in presets.json erstellt werden, und du kannst mit einem neuen Dropdown im KI-Widget einfach zwischen ihnen wechseln +- WebSocket-Wiederverbindungsfehler behoben. Dies führte manchmal dazu, dass sich das Terminal beim Aufwachen aus dem Ruhezustand aufhängte +- Speicher-Diagramme und Pro-CPU-Diagramme zum sysinfo-Widget hinzugefügt (und von cpuplot umbenannt) +- Einen neuen großen roten "Config Error"-Button hinzugefügt, wenn Parse-Fehler in der Konfigurations-JSON-Datei auftreten +- Das Vorschau-/CodeEdit-Widget zeigt jetzt Fehler (geschlängelte Linien) an, wenn JSON- oder YAML-Dateien nicht geparst werden können +- Neues App-Icon für Windows, das besser den Fluent-UI-Standards entspricht +- Kopieren-bei-Auswahl für das Terminal hinzugefügt (standardmäßig aktiviert, kann mit "term:copyonselect" deaktiviert werden) +- Button zum Stummschalten von Audio in Webviews hinzugefügt +- Rechtsklick-Option "Open Clipboard URL" hinzugefügt, um einfach eine in der Systemzwischenablage gespeicherte URL in einer Webview zu öffnen +- [Fehlerbehebung] Leere "Help"-Seiten beim Aufwachen aus dem Ruhezustand oder Neustart der App behoben + +### v0.8.11 — 10. Okt 2024 + +Hotfix-Release zur Behebung einiger in v0.8.10 eingeführter Fehler + +- Behebt eine Regression in v0.8.10, die dazu führte, dass neue Tabs manchmal leer und defekt erschienen +- Layout-Fixes für die Abstände im KI-Widget +- Die Terminal-Scrollleiste ist jetzt halbtransparent und überlagert die letzte Spalte +- Behebt die anfängliche Fenstergröße (beim ersten Start) sowohl für kleinere als auch größere Bildschirme +- Eine "Nicht erneut fragen"-Checkbox für die Installation von `wsh` auf entfernten Rechnern hinzugefügt (setzt ein neues Konfigurations-Flag) +- Verhindert, dass die App herabgestuft wird, wenn du einen Beta-Build installierst. Die Installation eines Beta-Builds wechselt dich jetzt in den Beta-Update-Kanal. + +### v0.8.10 — 9. Okt 2024 + +Kleines Bugfix-Release (aber mit einigen neuen Funktionen). + +- Unterstützung für Azure AI hinzugefügt [siehe FAQ](https://docs.waveterm.dev/faq#how-can-i-connect-to-azure-ai) +- KI-Fehler erscheinen jetzt im Chat +- Unter MacOS öffnet das Drücken von "Leertaste" in der Verzeichnisvorschau die ausgewählte Datei in Quick Look +- [Fehlerbehebung] Transparenz-Einstellungen behoben +- [Fehlerbehebung] Problem mit nicht standardmäßigen Portnummern im Verbindungs-Dropdown behoben +- [Fehlerbehebung] Problem mit eingebetteter Docsite behoben (lieferte 404 nach Aktualisierung) + +### v0.8.9 — 8. Okt 2024 + +Viele Fehlerbehebungen und neue Funktionen! + +- Neue "Help"-Ansicht -- nutzt eine eingebettete Version unserer Dokumentations-Website -- https://docs.waveterm.dev +- [Breaking] wsh getmeta, wsh setmeta und wsh deleteblock nehmen jetzt eine blockid über einen `-b`-Parameter statt als Positionsparameter entgegen +- Erlaubt Metadaten, Block-Icon, -Kopfzeile und -Text zu überschreiben (frame:title, frame:icon und frame:text) +- Home-Button im Web-Widget, um zur Startseite zurückzukehren, Option, eine Standard-Startseite für die gesamte App oder nur für den angegebenen Block festzulegen +- Checkpointing des Terminals seltener durchgeführt, um die Häufigkeit des Ausgabefehlers zu reduzieren (an einer vollständigen Lösung wird noch gearbeitet) +- Neue Terminal-Themes -- Warm Yellow und One Dark Pro +- Wir unterstützen jetzt GitHub-Flavored-Markdown-Hinweise +- Befehl `wsh notify`, um eine Desktop-Benachrichtigung zu senden +- `wsh createblock`, um über die CLI einen beliebigen Block zu erstellen +- Rechtsklick zum "Bild speichern" in der Webview +- `wsh edit` erlaubt jetzt das Öffnen neuer Dateien (solange das übergeordnete Verzeichnis existiert) +- 8 neue lustige Tab-Hintergrund-Voreinstellungen hinzugefügt (Rechtsklick auf einen beliebigen Tab und "Backgrounds" auswählen, um sie auszuprobieren) +- [Konfiguration] neuer Konfigurationsschlüssel "term:scrollback", um die Anzahl der Scrollback-Zeilen für Terminals festzulegen. Nutze "-1", um 0 zu setzen, Maximum ist 10000. +- [Konfiguration] neuer Konfigurationsschlüssel "term:theme", um das Standard-Terminal-Theme für alle neuen Terminals festzulegen +- [Konfiguration] neuer Konfigurationsschlüssel "preview:showhiddenfiles", um die Standardeinstellung "versteckte Dateien anzeigen" für die Vorschau festzulegen +- [Fehlerbehebung] Formatierungsproblem mit `wsh getmeta` behoben +- [Fehlerbehebung] Startproblem unter Linux behoben, wenn das Home-Verzeichnis ein NFS-Mount ist +- [Fehlerbehebung] Cursorfarbe in Terminal-Themes funktioniert jetzt korrekt +- [Fehlerbehebung] Einige doppelte Scrollleisten bei der Anzeige von Markdown-Inhalten behoben +- [Fehlerbehebung] Beendigungsablauf verbessert, um wavesrv-Logs besser zu erfassen +- [Fehlerbehebung] Alt+G-Tastenkombination für Linux/Windows behoben +- Weitere diverse Fehlerbehebungen, Bereinigungen und Sicherheitsfixes + +### v0.8.8 — 1. Okt 2024 + +Schnelles Patch-Release zur Behebung der "Alt"-Tastenkombinationen unter Windows/Linux. Bringt außerdem eine enorme Performance-Verbesserung bei der KI-Streaming-Geschwindigkeit. + +### v0.8.7 — 30. Sep 2024 + +Schnelles Patch-Release zur Fehlerbehebung: + +- Behebt Windows-SSH-Verbindungen (ungültiger Pfad beim Versuch, wsh-Tools zu installieren) +- Behebt ein Problem bei der Auflösung von `~` in Windows-Pfaden, `~\` funktioniert jetzt statt nur `~/` +- Versucht, die Hintergrundfarbe für Webseiten zu beheben. Ruft das Meta-Tag für color-scheme ab und setzt bei erkanntem Dark-Modus einen schwarzen Hintergrund (behebt ein Darstellungsproblem bei rohen githubusercontent-Dateien) +- Unseren useDimensions-Hook so behoben, dass er korrekt auslöst. Behebt einige Größenprobleme, einschließlich der konsistenten Anzeige von Fehlermeldungen bei fehlgeschlagenen SSH-Verbindungen +- "data:"-URLs in benutzerdefinierten Tab-Hintergründen erlaubt +- Der Alias "tab" für die UUID des aktuellen Tabs bei Nutzung von wsh +- [BUILD] Generierte Dateien werden nur bedingt geschrieben, wenn sie aktualisiert wurden + +### v0.8.6 — 26. Sep 2024 + +Ein weiteres schnelles Hotfix-Update. Behebt ein Problem, bei dem das Fenster beim nächsten Start komplett leer wiederhergestellt wurde, wenn alle Tabs in einem Fenster gelöscht worden waren. + +Außerdem, als Bonus, haben wir Unterstützung für die fish-Shell hinzugefügt! + +### v0.8.5 — 25. Sep 2024 + +Hotfix, Downgrade der `jotai`-Bibliothek. Das Upgrade verursachte eine erhebliche Regression in codeedit, durch die +Nutzer keine Dateien mehr bearbeiten konnten. + +### v0.8.4 — 25. Sep 2024 + +- Eine Einstellung `window:disablehardwareacceleration` hinzugefügt, um native Hardwarebeschleunigung zu deaktivieren +- Neues Startmodell für Legacy-Nutzer, das ihnen die Option gibt, WaveLegacy herunterzuladen +- WAVETERM_HOME konsistent für das Home-Verzeichnis genutzt + +### v0.8.3 — 25. Sep 2024 + +Weitere Hotfixes für Linux-Nutzer. Wir binden jetzt gegen eine ältere Version von glibc und nutzen +den zig-Compiler unter Linux (die neuere Version verhinderte die Ausführung auf älteren Distributionen). +Behebt außerdem ein Berechtigungsproblem bei der Installation über .deb. Es gibt außerdem einen neuen Konfigurationswert +`window:nativetitlebar`, der die native Titelleiste unter Windows/Linux wiederherstellt. + +### v0.8.2 — 24. Sep 2024 + +Hotfix, behebt einen üblen Absturz beim Start für Linux-Nutzer (dynamisches Linking, aber mit der netcgo-DNS-Bibliothek) + +### v0.8.1 — 23. Sep 2024 + +Kleines Aufräum-Release. + +- Zahlenverarbeitung für bestimmte Konfigurationsdateiwerte behoben +- Link zur Dokumentations-Website hinzugefügt +- Neuer Zurück-Button für die Verzeichnisansicht hinzugefügt +- Telemetrie-Fixes + +### v0.8.0 — 20. Sep 2024 + +**Großes neues Release von Wave Terminal** + +Der neue Build ist ein Neuanfang und ein klarer Bruch mit der aktuellen Version. Daher werden dein Verlauf, deine Einstellungen und deine Konfiguration nicht übernommen. Wenn du weiterhin die Legacy-Version nutzen möchtest, musst du sie separat herunterladen. diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/secrets.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/secrets.mdx new file mode 100644 index 0000000000..10a2a4afcd --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/secrets.mdx @@ -0,0 +1,147 @@ +--- +sidebar_position: 3.2 +id: "secrets" +title: "Secrets" +--- + +import { VersionBadge } from "@site/src/components/versionbadge"; + +# Secrets + + + +Wave Terminal bietet einen sicheren Weg, sensible Informationen wie Passwörter, API-Schlüssel und Token zu speichern. Secrets werden verschlüsselt im nativen Schlüsselbund deines Systems gespeichert (macOS Keychain, Windows Credential Manager oder Linux Secret Service), sodass deine sensiblen Daten geschützt bleiben. + +## Warum Secrets nutzen? + +Mit Secrets in Wave Terminal kannst du: + +- **SSH-Passwörter speichern** - Automatische Authentifizierung bei SSH-Verbindungen, ohne Passwörter einzutippen +- **API-Schlüssel verwalten** - API-Token, Schlüssel und Zugangsdaten sicher aufbewahren +- **Über Sitzungen hinweg teilen** - Auf deine Secrets aus jedem Terminal-Block oder jeder Remote-Verbindung zugreifen +- **Klartext-Speicherung vermeiden** - Niemals sensible Daten in Konfigurationsdateien oder Skripten speichern + +## Die Secrets-Oberfläche öffnen + +Es gibt mehrere Wege, auf die Secrets-Verwaltungsoberfläche zuzugreifen: + +1. **Über die Widget-Leiste** (empfohlen): + - Klicke auf das Einstellungssymbol **** in der Widget-Leiste + - Wähle **Secrets** aus dem Menü + +2. **Über die Kommandozeile:** + ```bash + wsh secret ui + ``` + + +Die Secrets-Oberfläche bietet eine grafische Oberfläche zum Anzeigen, Hinzufügen, Bearbeiten und Löschen von Secrets. + +## Secrets über die CLI verwalten + +Wave Terminal bietet eine vollständige CLI zur Verwaltung von Secrets aus jedem Terminal-Block: + +```bash +# Alle Secret-Namen auflisten (nicht die Werte) +wsh secret list + +# Einen bestimmten Secret-Wert abrufen +wsh secret get MY_SECRET_NAME + +# Ein Secret setzen (Format: name=value, keine Leerzeichen um das =) +wsh secret set GITHUB_TOKEN=ghp_xxxxxxxxxx +wsh secret set DB_PASSWORD=super_secure_password + +# Ein Secret löschen +wsh secret delete MY_SECRET_NAME +``` + +## Regeln für Secret-Namen + +Secret-Namen müssen dem Muster entsprechen: `^[A-Za-z][A-Za-z0-9_]*$` + +Das bedeutet: +- Muss mit einem Buchstaben beginnen (A-Z oder a-z) +- Darf nur Buchstaben, Zahlen und Unterstriche enthalten +- Darf keine Leerzeichen oder Sonderzeichen enthalten + +**Gültige Namen:** `MY_SECRET`, `ApiKey`, `ssh_password_1` +**Ungültige Namen:** `123_SECRET`, `my-secret`, `secret name` + +## Secrets mit SSH-Verbindungen nutzen + + + +Secrets können genutzt werden, um Passwörter für SSH-Verbindungen automatisch bereitzustellen, sodass du Passwörter nicht wiederholt eintippen musst. + +### In connections.json konfigurieren + +Füge das Feld `ssh:passwordsecretname` zu deiner Verbindungskonfiguration hinzu: + +```json +{ + "myserver": { + "ssh:hostname": "example.com", + "ssh:user": "myuser", + "ssh:passwordsecretname": "SERVER_PASSWORD" + } +} +``` + +Speichere dann dein Passwort als Secret: + +```bash +wsh secret set SERVER_PASSWORD=my_actual_password +``` + +Wenn Wave sich nun mit `myserver` verbindet, nutzt es automatisch das Passwort aus deinem Secret-Speicher, anstatt dich danach zu fragen. + +### Vorteile + +- **Sicherheit**: Passwort verschlüsselt im System-Schlüsselbund gespeichert +- **Komfort**: Kein Eintippen von Passwörtern für jede Verbindung nötig +- **Flexibilität**: Passwörter aktualisieren, indem du das Secret änderst, nicht die Konfiguration + +## Sicherheitsaspekte + +- **Verschlüsselte Speicherung**: Secrets werden verschlüsselt in deinem Wave-Konfigurationsverzeichnis gespeichert. Der Verschlüsselungsschlüssel selbst wird durch den sicheren Zugangsdatenspeicher deines Betriebssystems geschützt (macOS Keychain, Windows Credential Manager oder Linux Secret Service). + +- **Kein Klartext**: Secrets werden niemals unverschlüsselt in Logs oder zugänglichen Dateien gespeichert. + +- **Zugriffskontrolle**: Secrets sind ausschließlich für Wave Terminal zugänglich. + + +## Speicher-Backend + +Wave Terminal erkennt automatisch das passende Secret-Speicher-Backend für dein Betriebssystem und nutzt es: + +- **macOS**: Nutzt die macOS Keychain +- **Windows**: Nutzt den Windows Credential Manager +- **Linux**: Nutzt die Secret Service API (freedesktop.org-Spezifikation) + +:::warning Linux-Secret-Speicher +Unter Linux benötigt Wave ein kompatibles Secret-Service-Backend (typischerweise GNOME Keyring oder KWallet). Diese sind normalerweise mit deiner Desktop-Umgebung vorinstalliert. Wird kein kompatibles Backend erkannt, kannst du keine Secrets setzen, und die Oberfläche zeigt eine Warnung an. +::: + +## Fehlerbehebung + +### "No appropriate secret manager found" + +Dieser Fehler tritt unter Linux auf, wenn kein kompatibles Secret-Service-Backend verfügbar ist. Installiere GNOME Keyring oder KWallet und stelle sicher, dass der Secret Service läuft. + +### Secret nicht gefunden + +Stelle sicher, dass der Secret-Name korrekt geschrieben ist (Namen unterscheiden Groß-/Kleinschreibung) und dass das Secret existiert: + +```bash +wsh secret list +``` + +### Zugriff verweigert unter Linux + +Der Secret Service verlangt möglicherweise, dass du deinen Schlüsselbund entsperrst. Das passiert normalerweise nach dem Login. Konsultiere die Dokumentation deiner Desktop-Umgebung zur Schlüsselbundverwaltung. + +## Verwandte Dokumentation + +- [Verbindungen](/connections) - Erfahre mehr über SSH-Verbindungen und Konfiguration +- [wsh-Befehlsreferenz](/wsh-reference#secret) - Vollständige CLI-Befehlsdokumentation für Secrets diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/tab-backgrounds.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/tab-backgrounds.mdx new file mode 100644 index 0000000000..4be4f60894 --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/tab-backgrounds.mdx @@ -0,0 +1,120 @@ +--- +sidebar_position: 3.5 +id: "tab-backgrounds" +title: "Tab-Hintergründe" +--- + +# Tab-Hintergründe + +Waves Hintergrundsystem nutzt die volle Kraft von CSS-Hintergründen und lässt dich über das Attribut "background" umfangreiche visuelle Effekte erzeugen. Du kannst Vollfarben, Verläufe (linear und radial), Bilder und sogar mehrere Elemente überblendet anwenden. + +## Hintergründe verwalten + +Benutzerdefinierte Hintergründe werden in `~/.config/waveterm/backgrounds.json` gespeichert. + +**Zum Bearbeiten über die Oberfläche:** +1. Klicke auf das Einstellungssymbol (Zahnrad) in der Widget-Leiste +2. Wähle "Settings" aus dem Menü +3. Wähle "Tab Backgrounds" aus der Einstellungs-Seitenleiste + +**Oder starte über die Kommandozeile:** +```bash +wsh editconfig backgrounds.json +``` + +## Dateiformat + +Hintergründe folgen diesem Format: + +```json +{ + "bg@": { + "display:name": "", + "display:order": , + "bg": "", + "bg:opacity": + } +} +``` + +Wie Waves integrierte Hintergründe definiert sind, kannst du in der [Standard-backgrounds.json-Datei](https://github.com/wavetermdev/waveterm/blob/main/pkg/wconfig/defaultconfig/backgrounds.json) nachsehen. + +## Konfigurationsschlüssel + +| Schlüsselname | Typ | Funktion | +| -------------------- | ------ | ------------------------------------------------------------------------------------------------------- | +| display:name | string | In der UI angezeigter Name (erforderlich) | +| display:order | float | Steuert die Reihenfolge im Menü (optional) | +| bg | string | CSS-Attribut `background` für den Tab (unterstützt Farben, Verläufe, Bilder usw.) | +| bg:opacity | float | Die Deckkraft des Hintergrunds (Standard: 0.5) | +| bg:blendmode | string | Der [Blend-Modus](https://developer.mozilla.org/en-US/docs/Web/CSS/blend-mode) des Hintergrunds | +| bg:bordercolor | string | Die Rahmenfarbe, wenn ein Block nicht aktiv ist (selten verwendet) | +| bg:activebordercolor | string | Die Rahmenfarbe, wenn ein Block aktiv ist | + +## Beispiele + +#### Einfache Vollfarbe: + +```json +{ + "bg@blue": { + "display:name": "Blue", + "bg": "blue", + "bg:opacity": 0.3, + "bg:activebordercolor": "rgba(0, 0, 255, 1.0)" + } +} +``` + +#### Komplexer Verlauf: + +```json +{ + "bg@duskhorizon": { + "display:name": "Dusk Horizon", + "bg": "linear-gradient(0deg, rgba(128,0,0,1) 0%, rgba(204,85,0,0.7) 20%, rgba(255,140,0,0.6) 45%, rgba(160,90,160,0.5) 65%, rgba(60,60,120,1) 100%), radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.05), transparent 70%)", + "bg:opacity": 0.9, + "bg:blendmode": "overlay" + } +} +``` + +#### Hintergrundbild: + +```json +{ + "bg@ocean": { + "display:name": "Ocean Scene", + "bg": "url('/path/to/ocean.jpg') center/cover no-repeat", + "bg:opacity": 0.2 + } +} +``` + +:::info +Hintergrundbilder unterstützen sowohl URLs als auch lokale Dateipfade. Für bessere Zuverlässigkeit empfehlen wir lokale Dateien. Lokale Pfade müssen absolut sein oder mit `~` beginnen (z. B. `~/Downloads/background.png`). Wir unterstützen gängige Webformate: PNG, JPEG/JPG, WebP, GIF und SVG. +::: + +:::tip +Der Befehl `setbg` kann dir helfen, das Hintergrund-JSON zu erzeugen: + +```bash +# Vorschau eines Vollfarben-Hintergrunds +wsh setbg --print "#ff0000" +{ + "bg:*": true, + "bg": "#ff0000", + "bg:opacity": 0.5 +} + +# Vorschau eines zentrierten Bildhintergrunds +wsh setbg --print --center --opacity 0.3 ~/logo.png +{ + "bg:*": true, + "bg": "url('/absolute/path/to/logo.png') no-repeat center/auto", + "bg:opacity": 0.3 +} +``` + +Füge einfach das erforderliche Feld `display:name` und eine `bg@`-Umschließung hinzu, um deinen Hintergrundeintrag zu vervollständigen! +::: diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/tabs.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/tabs.mdx new file mode 100644 index 0000000000..ad64004303 --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/tabs.mdx @@ -0,0 +1,134 @@ +--- +sidebar_position: 3.25 +id: "tabs" +title: "Tabs" +--- + +import { PlatformProvider, PlatformSelectorButton } from "@site/src/components/platformcontext"; +import { Kbd } from "@site/src/components/kbd"; + + + +Tabs sind Sammlungen von [Widgets](./widgets), die zu gekachelten Dashboards angeordnet werden können. Du kannst innerhalb eines Workspace beliebig viele Tabs erstellen, um deine Workflows zu organisieren. + +## Tableiste + +Die Tableiste befindet sich oben im Fenster und zeigt alle Tabs innerhalb eines Workspace an. Du kannst auf einen Tab klicken, um zu ihm zu wechseln. Beim Wechseln laufen Befehle im vorherigen Tab weiter, und nicht gespeicherte Arbeit bleibt erhalten, bis du zu ihm zurückkehrst. Wenn du das Fenster schließt oder innerhalb desselben Fensters den Workspace wechselst, geht diese Arbeit verloren. + + + +### Einen neuen Tab erstellen + +Du kannst einen neuen Tab erstellen, indem du auf den -Button rechts neben den Tabs in der Tableiste klickst, oder indem du drückst. Dadurch wird der Fokus auch auf den neuen Tab gesetzt. + +### Einen Tab schließen + +Du kannst einen Tab schließen, indem du mit der Maus darüber fährst und auf den erscheinenden -Button klickst, oder indem du drückst. Du kannst einen Tab auch schließen, indem du [alle Blöcke darin schließt](#einen-block-löschen). + +Das Schließen eines Blocks ist eine destruktive Aktion, die alle laufenden Prozesse stoppt und nicht gespeicherte Arbeit verwirft. Dies kann nicht rückgängig gemacht werden. + +### Tabs neu anordnen + +Du kannst Tabs neu anordnen, indem du sie innerhalb der Tableiste ziehst. + +### Zwischen Tabs wechseln + +Du kannst zu einem vorhandenen Tab wechseln, indem du in der Tableiste darauf klickst. Du kannst auch folgende Tastenkombinationen nutzen: + +| Taste | Funktion | +| ------------------ | -------------------- | +| | Zu Tab-Nummer wechseln | +| | Zum Tab links wechseln | +| | Zum Tab rechts wechseln | + +### Einen Tab anheften + +Das Anheften eines Tabs erschwert das versehentliche Schließen. Du kannst einen Tab anheften, indem du mit der rechten Maustaste darauf klickst und im erscheinenden Kontextmenü "Pin Tab" auswählst. Du kannst einen Tab auch anheften, indem du ihn auf einen niedrigeren Index als einen bereits angehefteten Tab ziehst. Wenn ein Tab angeheftet ist, wird der -Button des Tabs durch einen -Button ersetzt. Ein Klick auf diesen Button löst die Anheftung. Du kannst einen Tab auch lösen, indem du ihn auf einen höheren Index als einen bereits nicht angehefteten Tab ziehst. + +## Tab-Layout-System + +Die Tabs bestehen aus gekachelten Blöcken. Der Inhalt jedes Blocks ist ein einzelnes Widget. Du kannst Blöcke verschieben und zu Layouts anordnen, die am besten zu deinem Workflow passen. Du kannst Blöcke auch vergrößern, um dich auf ein bestimmtes Widget zu konzentrieren. + +![screenshot showing a block being dragged over another block, with the placeholder depicting a out-of-line before outer drop](./img/drag-edge.png) + +### Das Layout-System unter der Haube + +:::info + +**Definitionen** + +- Layout-Baum: die In-Memory-Darstellung eines Tab-Layouts, bestehend aus Knoten +- Knoten: ein Eintrag im Layout-Baum, entweder ein einzelner Block (ein Blatt) oder eine geordnete Liste von Knoten. Definiert eine Kachelrichtung (Zeile oder Spalte) und eine einheitenlose Größe +- Block: der Inhalt eines Blatts im Layout-Baum, definiert, welcher Inhalt an der jeweiligen Layout-Position angezeigt wird + +::: + +Unser Layout-System ahmt das [CSS-Flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout/Basic_concepts_of_flexbox)-System nach und besteht aus einem Baum von Spalten und Zeilen. Intern wird das Layout als N-Baum dargestellt, wobei jeder Knoten im Baum entweder ein einzelner Block oder eine Liste von Knoten ist. Jede Ebene im Baum wechselt die Kachelrichtung (Ebene 1 kachelt als Zeile, Ebene 2 als Spalte, Ebene 3 als Zeile usw.). + +### Layout-Aktionen + + + +#### Einen neuen Block hinzufügen + +Du kannst neue Blöcke hinzufügen, indem du ein Widget aus der rechten Seitenleiste auswählst. + +Ausgehend von der obersten Ebene des Baums, da die erste Ebene als Zeile kachelt, werden neue Blöcke rechts neben bestehenden Blöcken hinzugefügt. Sobald 5 Blöcke nebeneinander stehen, werden neue Blöcke beginnend von rechts nach links unterhalb bestehender Blöcke hinzugefügt. Wenn ein neuer Block unterhalb eines bestehenden hinzugefügt wird, wird der Knoten, der den bestehenden Block enthält, von einem Einzelblock-Knoten in einen Listen-Knoten umgewandelt, und die bestehende Blockdefinition wird eine Ebene tiefer im Baum als erstes Element der Knotenliste verschoben. Neue Blöcke werden immer dem letzten verfügbaren Knoten auf der tiefsten Ebene hinzugefügt, wobei "verfügbar" bedeutet, dass er weniger als fünf Kinder hat. Wir setzen kein Limit für die Anzahl der Blöcke in einem Tab, aber ab etwa 25 Blöcken kann die Leistung nachlassen. + +Auch wenn wir für jeden Knoten im Baum bei der automatischen Platzierung neuer Blöcke ein Limit von 5 Kindern definieren, gibt es kein tatsächliches Limit für die Anzahl der Kinder, die ein Knoten haben kann. Nach der Platzierung kannst du den Block frei im Layout verschieben. + +#### Einen Block löschen + +Du kannst Blöcke löschen, indem du auf den -Button oben rechts im Block klickst, mit der rechten Maustaste auf die Block-Kopfzeile klickst und im Kontextmenü "Close Block" auswählst, oder indem du den Befehl [`wsh deleteblock`](./wsh-reference#deleteblock) ausführst. Alternativ kann der aktuell fokussierte Block/das aktuell fokussierte Widget durch Drücken von geschlossen werden. + +Wenn du einen Block löschst, wird der Layout-Baum automatisch angepasst, um die Baumtiefe zu minimieren. + +#### Einen Block verschieben + +Du kannst Blöcke verschieben, indem du auf die Block-Kopfzeile klickst und den Block im Tab ziehst. Du siehst Platzhalter, die anzeigen, wo der Block landet, wenn du ihn ablegst. + +Es gibt 7 verschiedene Ablagebereiche für jeden Block. Ein Block wird entlang seiner Diagonalen in Quadranten unterteilt. Wenn der Block als Zeile kachelt (von links nach rechts), platziert das Ablegen eines Blocks im linken oder rechten Quadranten den abgelegten Block auf derselben Ebene wie den Zielblock. Dies kann als Inline-Ablage betrachtet werden. Wenn du den Block außerhalb der Reihe ablegst (in Quadranten, die der entgegengesetzten Kachelrichtung entsprechen), wird der Block entweder eine Ebene über oder eine Ebene unter dem Zielblock platziert. Ablegen zur Außenseite platziert ihn auf derselben Ebene wie das übergeordnete Element des Zielblocks, während Ablegen zur Mitte des Blocks eine neue Ebene erzeugt, auf die sowohl der Zielblock als auch der abgelegte Block verschoben werden. Das mittlere Fünftel des Blocks ist für die Tausch-Aktion reserviert. Ablegen eines Blocks hier führt dazu, dass Zielblock und abgelegter Block ihre Position im Layout tauschen. + + + +##### Mögliche Block-Bewegungen + +:::note +Alle Block-Bewegungen außer dem Tausch führen dazu, dass sich der Rest des Layouts verschiebt, um die neue Position des Blocks aufzunehmen. +::: + +![screenshot showing a block being dragged over another block, with the placeholder depicting a swap movement](./img/drag-swap.png) +![annotated example showing the drop targets within a block](./img/block-drag-example.jpg) + +1. Inline davor: Legt den Block unter demselben Knoten wie den Zielblock ab und platziert ihn in derselben Kachelrichtung vor dem Ziel +2. Inline danach: Legt den Block unter demselben Knoten wie den Zielblock ab und platziert ihn in derselben Kachelrichtung nach dem Ziel +3. Außerhalb der Reihe, davor außen: Legt den Block vor dem übergeordneten Knoten des Zielblocks in der entgegengesetzten Kachelrichtung ab +4. Außerhalb der Reihe, davor innen: Segmentiert den Zielblock und erzeugt einen neuen Knoten im Baum. Platziert den abgelegten Block vor dem Zielblock in der entgegengesetzten Kachelrichtung. +5. Außerhalb der Reihe, danach innen: Segmentiert den Zielblock und erzeugt einen neuen Knoten im Baum. Platziert den abgelegten Block nach dem Zielblock in der entgegengesetzten Kachelrichtung. +6. Außerhalb der Reihe, danach außen: Legt den Block nach dem übergeordneten Knoten des Zielblocks in der entgegengesetzten Kachelrichtung ab +7. Tausch: Tauscht die Position des abgelegten Blocks und des Zielblocks im Layout, der Rest des Layouts bleibt erhalten + +#### Einen Block in der Größe ändern + + + +![screenshot showing the line that appears when the cursor hovers over the margin of a block, indicating which blocks +will be resized by dragging the margin](./img/node-resize.png) + +Du änderst nicht direkt die Größe eines Blocks, sondern die Größe der Knoten, die die Blöcke enthalten. Wenn du mit der Maus über den Rand eines Blocks fährst, ändert sich der Cursor zu oder , um die Richtung anzuzeigen, in der der Knoten in der Größe geändert werden kann. Nach 500ms erscheint außerdem eine Linie, die zeigt, wie viele Blöcke durch das Verschieben dieses Rands in der Größe geändert werden. Klicken und Ziehen auf diesem Rand ändert die Größe der Blöcke. + +Knotengrößen sind einheitenlose Werte. Das Verhältnis aller Knotengrößen auf einer bestimmten Baumebene bestimmt die Verschiebung jedes Knotens. Wenn du einen Block verschiebst und sein Knoten gelöscht wird, passen die anderen Knoten auf dieser Baumebene ihre Größen an das neue Größenverhältnis an. + +### Einen Block vergrößern + +Du kannst einen Block vergrößern, indem du auf den -Button klickst oder drückst. Du kannst einen Block wieder verkleinern, indem du auf den -Button klickst oder erneut drückst. + +### Den Abstand zwischen Blöcken ändern + +Der Abstand zwischen Blöcken beträgt standardmäßig 3px, dieser Wert kann jedoch durch Ändern des Konfigurationswerts `window:tilegapsize` angepasst werden. Siehe [Konfiguration](./config) für weitere Informationen zum Ändern von Konfigurationswerten. + + diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/telemetry-old.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/telemetry-old.mdx new file mode 100644 index 0000000000..3315315cab --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/telemetry-old.mdx @@ -0,0 +1,130 @@ +--- +id: "telemetry-old" +title: "Legacy-Telemetrie" +sidebar_class_name: hidden +--- + +Wave Terminal erfasst Telemetriedaten, um die Nutzung von Funktionen nachzuvollziehen, künftige Produktentscheidungen zu steuern und aggregierte Kennzahlen zur Popularität und Nutzung von Wave zu erstellen. Wir erfassen oder speichern keine personenbezogenen Daten (PII), und alle Kennzahlen werden ausschließlich mit deiner zufällig generierten _ClientId_ verknüpft und aggregiert. Du kannst die Erfassung jederzeit deaktivieren. + +Wenn du Telemetrie an- oder ausschalten möchtest, ist die erste Gelegenheit dazu ein Button auf der ersten Willkommensseite. Danach kann sie deaktiviert werden, indem du `"telemetry:enabled": false` zur Datei `config/settings.json` hinzufügst. Alternativ kann sie aktiviert werden, indem du `"telemetry:enabled": true` zur Datei `config/settings.json` hinzufügst. + +:::info + +Du kannst deine Telemetrie-Einstellung auch durch Ausführen des wsh-Befehls ändern: + +``` +wsh setconfig telemetry:enabled=true +``` + +::: + +--- + +## Telemetrie senden + +Sofern Telemetrie aktiviert ist, wird sie 10 Sekunden nach dem ersten Start von Waveterm gesendet und danach alle 4 Stunden erneut. Sie kann auch in Reaktion auf einige unten aufgeführte Sonderfälle gesendet werden. Beim Senden von Telemetrie wird sie nach einzelnen Tagen gruppiert, bestimmt durch deine Zeitzone. Daten eines vorherigen Tages werden als `Uploaded` markiert, sodass sie nicht erneut gesendet werden müssen. + +### Senden nach Aktivierung der Telemetrie + +Sobald Telemetrie aktiviert wird, wird ein Telemetrie-Update gesendet, unabhängig davon, wie lange der letzte Sendevorgang zurückliegt. Dies setzt den üblichen Timer für Telemetrie-Sendevorgänge nicht zurück. + +### Benachrichtigung über deaktivierte Telemetrie + +Sobald Telemetrie deaktiviert wird, sendet Waveterm ein spezielles Update, das uns über diese Änderung informiert. Siehe [Wenn Telemetrie deaktiviert wird](#wenn-telemetrie-deaktiviert-wird) für weitere Infos. Der Timer läuft im Hintergrund weiter, aber es werden keine Daten gesendet. + +### Wenn Waveterm geschlossen wird + +Sofern Telemetrie aktiviert ist, wird sie gesendet, wenn Waveterm geschlossen wird. + +--- + +## Telemetriedaten + +Bei aktiver Telemetrie erfassen wir folgende Daten. Sie werden im Typ `telemetry.TelemetryData` im Quellcode gespeichert. + +| Name | Beschreibung | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ActiveMinutes | Die Anzahl der Minuten, in denen der Nutzer Waveterm an einem bestimmten Tag aktiv genutzt hat. Dafür muss das Terminal-Fenster fokussiert sein, während der Nutzer aktiv damit interagiert. | +| FgMinutes | Die Anzahl der Minuten, in denen Waveterm an einem bestimmten Tag im Vordergrund war. Dafür muss das Terminal-Fenster fokussiert sein, unabhängig von der Nutzerinteraktion. | +| OpenMinutes | Die Anzahl der Minuten, in denen Waveterm an einem bestimmten Tag geöffnet war. Dafür muss das Terminal nur geöffnet sein, auch wenn das Fenster nicht fokussiert ist. | +| NumBlocks | Die Anzahl der an einem bestimmten Tag geöffneten Blöcke | +| NumTabs | Die Anzahl der an einem bestimmten Tag geöffneten Tabs. | +| NewTab | Die Anzahl der an einem bestimmten Tag neu erstellten Tabs | +| NumWindows | Die Anzahl der an einem bestimmten Tag geöffneten Fenster. | +| NumWS | Die Anzahl der an einem bestimmten Tag bestehenden Workspaces. | +| NumWSNamed | Die Anzahl der an einem bestimmten Tag benannten Workspaces. | +| NewTab | Die Anzahl der an einem bestimmten Tag geöffneten neuen Tabs. | +| NumStartup | Die Anzahl der Starts von Waveterm an einem bestimmten Tag. | +| NumShutdown | Die Anzahl der Beendigungen von Waveterm an einem bestimmten Tag. | +| SetTabTheme | Die Anzahl, wie oft das Tab-Theme über das Kontextmenü geändert wird | +| NumMagnify | Die Anzahl, wie oft ein Block vergrößert wird | +| NumPanics | Die Anzahl der an diesem Tag abgefangenen Backend-(Golang-)Panics | +| NumAIReqs | Die Anzahl der an diesem Tag gestellten KI-Anfragen | +| NumSSHConn | Die Anzahl unterschiedlicher SSH-Verbindungen, die zu unterschiedlichen Hosts hergestellt wurden | +| NumWSLConns | Die Anzahl unterschiedlicher WSL-Verbindungen, die zu unterschiedlichen Distributionen hergestellt wurden | +| Renderers | Die Anzahl neuer Block-Ansichten jedes Typs, die an einem bestimmten Tag geöffnet sind. | +| WshCmds | Die Anzahl der an einem bestimmten Tag ausgeführten wsh-Befehle jedes Typs | +| Blocks | Die Anzahl der Blöcke unterschiedlicher Ansichtstypen, die an einem bestimmten Tag geöffnet sind | +| Conn | Die Anzahl der erfolgreichen Remote-Verbindungen (und Fehler), die an einem bestimmten Tag hergestellt wurden | + +## Zugehörige Daten + +Zusätzlich zu den erfassten Telemetriedaten wird auch Folgendes gemeldet. Es wird im Typ `telemetry.ActivityType` im Quellcode gespeichert. + +| Name | Beschreibung | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Day | Das Datum, dem die Telemetrie zugeordnet ist. Es enthält keine Uhrzeit. | +| Uploaded | Ein Boolean, der angibt, ob die Telemetrie für diesen Tag abgeschlossen ist. Er ist während des zugehörigen Tages false, wird aber beim ersten Telemetrie-Upload danach auf true gesetzt. Ist er einmal true, werden die Daten dieses bestimmten Tages nicht mehr mit der Telemetrie mitgesendet. | +| TzName | Der Code für die vom Betriebssystem des Nutzers gemeldete Zeitzone (z. B. PST, GMT, JST) | +| TzOffset | Der von Betriebssystem des Nutzers gemeldete Zeitzonen-Versatz (z. B. -08:00, +00:00, +09:00) | +| ClientVersion | Welche Version von Waveterm installiert ist. | +| ClientArch | Umfasst das Betriebssystem des Nutzers (z. B. linux oder darwin) und die Architektur (z. B. x86_64 oder arm64). Enthält aktuell keine Daten zu Verbindungen. | +| BuildTime | Dient als genauere Versionsnummer, die festhält, wann diese Version gebaut wurde. Hat keinen Bezug dazu, wann diese Version von dir installiert wurde. | +| OSRelease | Listet die Version des vom Nutzer installierten Betriebssystems. | +| Displays | Bildschirmauflösungen (hinzugefügt in v0.9.3, um zu verstehen, für welche Bildschirmauflösungen optimiert werden soll) | + +## Telemetrie-Metadaten + +Zuletzt werden mit der Telemetrie einige Daten gesendet, die beschreiben, wie sie einzuordnen ist. Sie werden im Typ `wcloud.TelemetryInputType` im Quellcode gespeichert. + +| Name | Beschreibung | +| ----------------- | --------------------------------------------------------------------------------------------------------------------------- | +| UserId | Aktuell ungenutzt. Dies ist eine anonyme UUID, die für zukünftige Funktionen vorgesehen ist. | +| ClientId | Dies ist eine anonyme UUID, die beim ersten Start von Waveterm erstellt wird. Sie wird für Telemetrie und das Senden von Prompts an Open AI genutzt. | +| AppType | Wird genutzt, um die aktuelle Version von Waveterm von der Legacy-App zu unterscheiden. | +| AutoUpdateEnabled | Ob automatische Updates aktiviert sind. | +| AutoUpdateChannel | Die Art des genutzten automatischen Updates. Bezieht sich speziell darauf, ob ein latest- oder beta-Kanal ausgewählt ist. | +| CurDay | Der aktuelle Tag (in deiner Zeitzone), an dem die Telemetrie gesendet wird. Enthält keine Uhrzeit. | + +## Geo-Daten + +Wir speichern keine IP-Adressen in unserer Telemetrietabelle. CloudFlare übermittelt uns jedoch Geo-Location-Header. Wir speichern diese zwei Header-Werte: + +| Name | Beschreibung | +| ------------ | ----------------------------------------------------------------- | +| CFCountry | 2-stelliger Ländercode (z. B. "US", "FR" oder "JP") | +| CFRegionCode | Regionscode (oft eine Provinz, Region oder ein Bundesstaat innerhalb eines Landes) | + +--- + +## Wenn Telemetrie deaktiviert wird + +Wenn ein Nutzer die Telemetrie deaktiviert, sendet Waveterm eine Benachrichtigung, dass die Telemetrie für seine anonyme _ClientId_ deaktiviert wurde. Dies geschieht über den Typ `wcloud.NoTelemetryInputType` im Quellcode. Darüber hinaus werden keine weiteren Informationen gesendet, bis Telemetrie wieder aktiviert wird. Wird sie wieder aktiviert, werden die Telemetriedaten der letzten 30 Tage gesendet. + +--- + +## Ein Hinweis zu IP-Adressen + +Telemetrie wird über HTTPS hochgeladen, wodurch deine IP-Adresse dem Telemetrieserver bekannt ist. Wir speichern deine IP-Adresse **nicht** in unserer Telemetrietabelle und verknüpfen sie **nicht** mit deiner _ClientId_. + +--- + +## Zuvor erfasste Telemetriedaten + +Wir sind der Meinung, dass die von uns per Telemetrie erfassten Daten recht minimal sind, können diese Entscheidung aber nicht für jeden Nutzer treffen. Solltest du deine Meinung zu den zuvor erfassten Daten ändern, kannst du die Löschung deiner Daten beantragen, indem du uns eine E-Mail an [support@waveterm.dev](mailto:support@waveterm.dev) schreibst. In diesem Fall benötigen wir deine _ClientId_, um sie zu entfernen. + +--- + +## Datenschutzerklärung + +Eine Zusammenfassung des Obigen findest du in unserer [Datenschutzerklärung](https://www.waveterm.dev/privacy). diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/telemetry.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/telemetry.mdx new file mode 100644 index 0000000000..8a1a936433 --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/telemetry.mdx @@ -0,0 +1,71 @@ +--- +sidebar_position: 100 +title: Telemetrie +id: "telemetry" +--- + +## tl;dr + +Wave Terminal erfasst Telemetriedaten, um Feature-Nutzung nachzuvollziehen, künftige Produktentscheidungen zu steuern und aggregierte Kennzahlen zur Popularität und Nutzung von Wave zu erstellen. Wir erfassen KEINE personenbezogenen Daten (PII), Tastatureingaben, Dateiinhalte, KI-Prompts, IP-Adressen, Hostnamen oder Befehle. Wir verknüpfen alle Informationen mit einer anonymen, zufällig generierten _ClientId_ (UUID). Du kannst die Erfassung jederzeit deaktivieren. + +Hier eine kurze Zusammenfassung dessen, was erfasst wird: + +- Grundlegende App-/Systeminformationen - Betriebssystem, Architektur, App-Version, Update-Einstellungen +- Nutzungskennzahlen - App-Start/-Beendigung, aktive Minuten, Vordergrundzeit, Tab-/Block-Anzahl/-Nutzung +- Feature-Interaktionen - Wenn du Tabs erstellst, Befehle ausführst, Einstellungen änderst usw. +- Anzeigeinformationen - Monitorauflösung, Anzahl der Displays +- Verbindungsereignisse - SSH-/WSL-Verbindungsversuche (aber KEINE Hostnamen/IPs) +- Wave-AI-Nutzung - Modell-/Anbieterauswahl, Token-Anzahl, Anfragemetriken, Latenz (aber KEINE Prompts oder Antworten) +- Fehlerberichte - Absturz-/Panic-Ereignisse mit minimalen Debug-Informationen, aber keine Stack-Traces oder detaillierte Fehler + +Telemetrie kann jederzeit in den Einstellungen deaktiviert werden. Wenn sie nicht deaktiviert ist, wird sie beim Start, beim Beenden und alle 4 Stunden gesendet. + +## Telemetrie deaktivieren + +Telemetrie kann auf dem anfänglichen Willkommensbildschirm aktiviert oder deaktiviert werden, wenn Wave zum ersten Mal startet. Nach der Einrichtung kann Telemetrie deaktiviert werden, indem du den Schlüssel `telemetry:enabled` in Waves allgemeiner Konfigurationsdatei auf `false` setzt. Sie kann auch über den CLI-Befehl `wsh setconfig telemetry:enabled=false` deaktiviert werden. + +:::info + +Dieses Dokument beschreibt das aktuelle Telemetriesystem ab v0.11.1. Ab v0.12.5 sendet Wave Terminal keine Legacy-Telemetrie mehr. Die vorherige Telemetrie-Dokumentation findest du zu historischen Zwecken in unserer [Legacy-Telemetrie-Dokumentation](./telemetry-old.mdx). + +::: + +## Diagnose-Ping + +Wave sendet einen kleinen, anonymen Diagnose-Ping, nachdem die App kurze Zeit gelaufen ist, und danach höchstens einmal pro Tag. Damit schätzen wir aktive Installationen ab und verstehen, welche Versionen noch genutzt werden, um fundierte Entscheidungen über laufenden Support und Abkündigungen zu treffen. + +Der Ping enthält nur: deine Wave-Version, Betriebssystem/CPU-Architektur, lokales Datum (JJJJ-MM-TT, ohne Zeitzone oder Uhrzeit), deine zufällig generierte anonyme Client-ID und ob Nutzungstelemetrie aktiviert oder deaktiviert ist. + +Er enthält keine Nutzungsdaten, Befehle, Dateien oder sonstige Telemetrieereignisse. + +Dieser Ping ist bewusst von der Telemetrie getrennt, damit Wave aktive Installationen zählen kann. Wenn du ihn deaktivieren möchtest, setze die Umgebungsvariable WAVETERM_NOPING. + +## Telemetrie senden + +Sofern Telemetrie aktiviert ist, wird sie kurz nach dem ersten Start von Wave und danach alle 4 Stunden erneut gesendet. Sie kann auch in Reaktion auf einige unten aufgeführte Sonderfälle gesendet werden. Beim Senden von Telemetrie werden Ereignisse als gesendet markiert, um doppelte Übertragungen zu vermeiden. + +### Senden nach Aktivierung der Telemetrie + +Sobald Telemetrie aktiviert wird, wird ein Telemetrie-Update gesendet, unabhängig davon, wie lange der letzte Sendevorgang zurückliegt. Dies setzt den üblichen Timer für Telemetrie-Sendevorgänge nicht zurück. + +### Wenn Wave geschlossen wird + +Sofern Telemetrie aktiviert ist, wird sie gesendet, wenn Waveterm geschlossen wird. + +## Ereignistypen und Eigenschaften + +Wave erfasst die oben zusammengefassten Ereignistypen und Eigenschaften. Mit neuen Features können neue Ereignisse und Eigenschaften hinzukommen, um deren Nutzung zu verfolgen. + +Die vollständige, aktuelle Liste aller Telemetrieereignisse und -eigenschaften findest du im Quellcode: [telemetrydata.go](https://github.com/wavetermdev/waveterm/blob/main/pkg/telemetry/telemetrydata/telemetrydata.go) + +## DSGVO-Opt-out-Konformität + +Wenn Telemetrie deaktiviert wird, sendet Wave einen einzelnen, minimalen Opt-out-Datensatz, der mit der anonymen Client-ID verknüpft ist und festhält, dass und wann Telemetrie deaktiviert wurde. Dieser Datensatz wird zu Compliance-Zwecken aufbewahrt. Danach werden keine Telemetrie- oder Nutzungsdaten mehr gesendet. + +## Deine Daten löschen + +Wenn du möchtest, dass deine zuvor erfassten Telemetriedaten gelöscht werden, schreibe uns eine E-Mail an support (at) waveterm.dev mit deiner _ClientId_, und wir entfernen sie. + +## Datenschutzerklärung + +Eine Zusammenfassung des Obigen findest du in unserer [Datenschutzerklärung](https://www.waveterm.dev/privacy). diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/waveai-modes.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/waveai-modes.mdx new file mode 100644 index 0000000000..78da5ac17e --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/waveai-modes.mdx @@ -0,0 +1,565 @@ +--- +sidebar_position: 1.6 +id: "waveai-modes" +title: "Wave AI (lokale Modelle + BYOK)" +--- + +import { VersionBadge } from "@site/src/components/versionbadge"; + + + +Wave AI unterstützt benutzerdefinierte KI-Modi, mit denen du lokale Modelle, benutzerdefinierte API-Endpunkte und alternative KI-Anbieter nutzen kannst. So hast du vollständige Kontrolle darüber, welche Modelle und Anbieter du mit Waves KI-Funktionen nutzt. + +## Konfigurationsüberblick + +KI-Modi werden in `~/.config/waveterm/waveai.json` konfiguriert. + +**Zum Bearbeiten über die Oberfläche:** +1. Klicke auf das Einstellungssymbol (Zahnrad) in der Widget-Leiste +2. Wähle "Settings" aus dem Menü +3. Wähle "Wave AI Modes" aus der Einstellungs-Seitenleiste + +**Oder starte über die Kommandozeile:** +```bash +wsh editconfig waveai.json +``` + +Jeder Modus definiert eine vollständige KI-Konfiguration einschließlich Modell, API-Endpunkt, Authentifizierung und Anzeigeeigenschaften. + +## Anbieterbasierte Konfiguration + +Wave AI unterstützt jetzt eine anbieterbasierte Konfiguration, die automatisch sinnvolle Standardwerte für gängige Anbieter setzt. Durch Angabe des Feldes `ai:provider` kannst du deine Konfiguration deutlich vereinfachen, da das System automatisch Endpunkte, API-Typen und Secret-Namen einrichtet. + +### Unterstützte Anbieter + +- **`openai`** - OpenAI-API (konfiguriert automatisch Endpunkt und Secret-Namen) [[Beispiel ansehen](#openai)] +- **`openrouter`** - OpenRouter-API (konfiguriert automatisch Endpunkt und Secret-Namen) [[Beispiel ansehen](#openrouter)] +- **`nanogpt`** - NanoGPT-API (konfiguriert automatisch Endpunkt und Secret-Namen) [[Beispiel ansehen](#nanogpt)] +- **`groq`** - Groq-API (konfiguriert automatisch Endpunkt und Secret-Namen) [[Beispiel ansehen](#groq)] +- **`google`** - Google AI (Gemini) [[Beispiel ansehen](#google-ai-gemini)] +- **`azure`** - Azure OpenAI Service (moderne API) [[Beispiel ansehen](#azure-openai-moderne-api)] +- **`azure-legacy`** - Azure OpenAI Service (Legacy-Deployment-API) [[Beispiel ansehen](#azure-openai-legacy-deployment-api)] +- **`custom`** - Benutzerdefinierter API-Endpunkt (vollständig manuelle Konfiguration) [[Beispiele ansehen](#beispiele-für-lokale-modelle)] + +### Unterstützte API-Typen + +Wave AI unterstützt folgende API-Typen: + +- **`openai-chat`**: Nutzt den Endpunkt `/v1/chat/completions` (am häufigsten) +- **`openai-responses`**: Nutzt den Endpunkt `/v1/responses` (moderne API für GPT-5+-Modelle) +- **`google-gemini`**: Googles Gemini-API-Format (wird automatisch gesetzt, wenn `ai:provider: "google"` genutzt wird, normalerweise nicht direkt verwendet) + +## Globale Wave-AI-Einstellungen + +Du kannst das globale Verhalten von Wave AI in deinen Wave-Terminal-Einstellungen konfigurieren (getrennt von den Modus-Konfigurationen in `waveai.json`). + +### Einen Standard-KI-Modus festlegen + +Nachdem du ein lokales Modell oder einen benutzerdefinierten Modus konfiguriert hast, kannst du ihn zum Standard machen, indem du `waveai:defaultmode` in deinen Wave-Terminal-Einstellungen setzt. + +:::important +Nutze den **Modus-Schlüssel** (den Schlüssel in deiner `waveai.json`-Konfiguration), nicht den Anzeigenamen. Nutze zum Beispiel `"ollama-llama"` (den Schlüssel), nicht `"Ollama - Llama 3.3"` (den Anzeigenamen). +::: + +**Mit dem Einstellungsbefehl:** +```bash +wsh setconfig waveai:defaultmode="ollama-llama" +``` + +**Oder settings.json direkt bearbeiten:** +1. Klicke auf das Einstellungssymbol (Zahnrad) in der Widget-Leiste +2. Wähle "Settings" aus dem Menü +3. Füge den Schlüssel `waveai:defaultmode` zu deiner settings.json hinzu: +```json + "waveai:defaultmode": "ollama-llama" +``` + +Dadurch wird der angegebene Modus zur Standardauswahl beim Öffnen der Wave-AI-Funktionen. + +:::note +Wave AI erfordert normalerweise aktivierte Telemetrie. Wenn du jedoch dein eigenes benutzerdefiniertes Modell konfigurierst (lokal oder BYOK) und `waveai:defaultmode` auf den Schlüssel dieses benutzerdefinierten Modus setzt, erhältst du keine Telemetrie-Pflichtmeldungen. So kannst du Wave-AI-Funktionen vollständig privat mit deinen eigenen Modellen nutzen. +::: + +### Wave-Cloud-Modi ausblenden + +Wenn du nur deine lokalen oder benutzerdefinierten Modelle nutzen und Waves Cloud-KI-Modi aus dem Modus-Dropdown ausblenden möchtest, setze `waveai:showcloudmodes` auf `false`: + +**Mit dem Einstellungsbefehl:** +```bash +wsh setconfig waveai:showcloudmodes=false +``` + +**Oder settings.json direkt bearbeiten:** +1. Klicke auf das Einstellungssymbol (Zahnrad) in der Widget-Leiste +2. Wähle "Settings" aus dem Menü +3. Füge den Schlüssel `waveai:showcloudmodes` zu deiner settings.json hinzu: +```json + "waveai:showcloudmodes": false +``` + +Dadurch werden Waves integrierte Cloud-KI-Modi ausgeblendet und nur deine benutzerdefiniert konfigurierten Modi angezeigt. + +## Beispiele für lokale Modelle + +### Ollama + +[Ollama](https://ollama.ai) bietet eine OpenAI-kompatible API zum lokalen Ausführen von Modellen: + +```json +{ + "ollama-llama": { + "display:name": "Ollama - Llama 3.3", + "display:order": 1, + "display:icon": "microchip", + "display:description": "Local Llama 3.3 70B model via Ollama", + "ai:apitype": "openai-chat", + "ai:model": "llama3.3:70b", + "ai:thinkinglevel": "medium", + "ai:endpoint": "http://localhost:11434/v1/chat/completions", + "ai:apitoken": "ollama" + } +} +``` + +:::tip +Das Feld `ai:apitoken` ist erforderlich, wird von Ollama aber ignoriert - du kannst es auf einen beliebigen Wert wie `"ollama"` setzen. +::: + +### LM Studio + +[LM Studio](https://lmstudio.ai) bietet einen lokalen Server, der verschiedene Modelle ausführen kann: + +```json +{ + "lmstudio-qwen": { + "display:name": "LM Studio - Qwen", + "display:order": 2, + "display:icon": "server", + "display:description": "Local Qwen model via LM Studio", + "ai:apitype": "openai-chat", + "ai:model": "qwen/qwen-2.5-coder-32b-instruct", + "ai:thinkinglevel": "medium", + "ai:endpoint": "http://localhost:1234/v1/chat/completions", + "ai:apitoken": "not-needed" + } +} +``` + +### vLLM + +[vLLM](https://docs.vllm.ai) ist ein leistungsstarker Inferenzserver mit OpenAI-API-Kompatibilität: + +```json +{ + "vllm-local": { + "display:name": "vLLM", + "display:order": 3, + "display:icon": "server", + "display:description": "Local model via vLLM", + "ai:apitype": "openai-chat", + "ai:model": "your-model-name", + "ai:thinkinglevel": "medium", + "ai:endpoint": "http://localhost:8000/v1/chat/completions", + "ai:apitoken": "not-needed" + } +} +``` + +## Beispiele für Cloud-Anbieter + +### OpenAI + +Die Nutzung des `openai`-Anbieters konfiguriert Endpunkt und Secret-Namen automatisch: + +```json +{ + "openai-gpt4o": { + "display:name": "GPT-4o", + "ai:provider": "openai", + "ai:model": "gpt-4o" + } +} +``` + +Der Anbieter setzt automatisch: +- `ai:endpoint` auf `https://api.openai.com/v1/chat/completions` +- `ai:apitype` auf `openai-chat` (oder `openai-responses` für GPT-5+-Modelle) +- `ai:apitokensecretname` auf `OPENAI_KEY` (speichere deinen OpenAI-API-Schlüssel unter diesem Namen) +- `ai:capabilities` auf `["tools", "images", "pdfs"]` (automatisch basierend auf dem Modell bestimmt) + +Für neuere Modelle wie GPT-4.1 oder GPT-5 wird der API-Typ automatisch bestimmt: + +```json +{ + "openai-gpt41": { + "display:name": "GPT-4.1", + "ai:provider": "openai", + "ai:model": "gpt-4.1" + } +} +``` + +### OpenAI-kompatibel + +Um einen OpenAI-kompatiblen API-Anbieter zu nutzen, musst du die Parameter ai:endpoint, ai:apitokensecretname, ai:model angeben +und "openai-chat" als ai:apitype nutzen. + +:::note +Der ai:endpoint ist *NICHT* eine baseurl. Der Endpunkt sollte den vollständigen Endpunkt enthalten, nicht nur die baseurl. +Zum Beispiel: https://api.x.ai/v1/chat/completions + +Wenn du nur die baseurl angibst, erhältst du wahrscheinlich eine 404-Meldung. +::: + +```json +{ + "xai-grokfast": { + "display:name": "xAI Grok Fast", + "display:order": 2, + "display:icon": "server", + "ai:apitype": "openai-chat", + "ai:model": "grok-4-1-fast-reasoning", + "ai:endpoint": "https://api.x.ai/v1/chat/completions", + "ai:apitokensecretname": "XAI_KEY", + "ai:capabilities": ["tools", "images", "pdfs"] + } +} +``` + +`ai:apitokensecretname` sollte der Name einer Umgebungsvariable sein, die deinen API-Schlüssel enthält. Setze diese Umgebungsvariable, bevor du Wave Terminal ausführst. + + +### OpenRouter + +[OpenRouter](https://openrouter.ai) bietet Zugriff auf mehrere KI-Modelle. Die Nutzung des `openrouter`-Anbieters vereinfacht die Konfiguration: + +```json +{ + "openrouter-qwen": { + "display:name": "OpenRouter - Qwen", + "ai:provider": "openrouter", + "ai:model": "qwen/qwen-2.5-coder-32b-instruct" + } +} +``` + +Der Anbieter setzt automatisch: +- `ai:endpoint` auf `https://openrouter.ai/api/v1/chat/completions` +- `ai:apitype` auf `openai-chat` +- `ai:apitokensecretname` auf `OPENROUTER_KEY` (speichere deinen OpenRouter-API-Schlüssel unter diesem Namen) + +:::note +Für OpenRouter musst du `ai:capabilities` manuell basierend auf den Funktionen deines Modells angeben. Beispiel: +```json +{ + "openrouter-qwen": { + "display:name": "OpenRouter - Qwen", + "ai:provider": "openrouter", + "ai:model": "qwen/qwen-2.5-coder-32b-instruct", + "ai:capabilities": ["tools"] + } +} +``` +::: + +### NanoGPT + +[NanoGPT](https://nano-gpt.com) bietet Zugriff auf mehrere KI-Modelle zu wettbewerbsfähigen Preisen. Die Nutzung des `nanogpt`-Anbieters vereinfacht die Konfiguration: + +```json +{ + "nanogpt-glm47": { + "display:name": "NanoGPT - GLM 4.7", + "ai:provider": "nanogpt", + "ai:model": "zai-org/glm-4.7" + } +} +``` + +Der Anbieter setzt automatisch: +- `ai:endpoint` auf `https://nano-gpt.com/api/v1/chat/completions` +- `ai:apitype` auf `openai-chat` +- `ai:apitokensecretname` auf `NANOGPT_KEY` (speichere deinen NanoGPT-API-Schlüssel unter diesem Namen) + +:::note +NanoGPT ist ein Proxy-Dienst, der Zugriff auf mehrere KI-Modelle bietet. Du musst `ai:capabilities` manuell basierend auf den Funktionen des Modells angeben. NanoGPT unterstützt OpenAI-kompatiblen Tool-Aufruf für Modelle mit dieser Fähigkeit. Prüfe das Feld `capabilities.vision` des Modells über die [NanoGPT-Modelle-API](https://nano-gpt.com/api/v1/models?detailed=true), um die Bildunterstützung zu bestimmen. Beispiel für ein reines Textmodell mit Tool-Unterstützung: +```json +{ + "nanogpt-glm47": { + "display:name": "NanoGPT - GLM 4.7", + "ai:provider": "nanogpt", + "ai:model": "zai-org/glm-4.7", + "ai:capabilities": ["tools"] + } +} +``` +Für Vision-fähige Modelle wie `openai/gpt-5` füge `"images"` zu den capabilities hinzu. +::: + +### Groq + +[Groq](https://groq.com) bietet schnelle Inferenz für offene Modelle über eine OpenAI-kompatible API. Die Nutzung des `groq`-Anbieters vereinfacht die Konfiguration: + +```json +{ + "groq-kimi-k2": { + "display:name": "Groq - Kimi K2", + "ai:provider": "groq", + "ai:model": "moonshotai/kimi-k2-instruct" + } +} +``` + +Der Anbieter setzt automatisch: +- `ai:endpoint` auf `https://api.groq.com/openai/v1/chat/completions` +- `ai:apitype` auf `openai-chat` +- `ai:apitokensecretname` auf `GROQ_KEY` (speichere deinen Groq-API-Schlüssel unter diesem Namen) + +:::note +Für Groq musst du `ai:capabilities` manuell basierend auf den Funktionen deines Modells angeben. +::: + +### Google AI (Gemini) + +[Google AI](https://ai.google.dev) bietet die Gemini-Modellfamilie. Die Nutzung des `google`-Anbieters vereinfacht die Konfiguration: + +```json +{ + "google-gemini": { + "display:name": "Gemini 3.5 Flash", + "ai:provider": "google", + "ai:model": "gemini-3.5-flash" + } +} +``` + +Der Anbieter setzt automatisch: +- `ai:endpoint` auf `https://generativelanguage.googleapis.com/v1beta/models/{model}:streamGenerateContent` +- `ai:apitype` auf `google-gemini` +- `ai:apitokensecretname` auf `GOOGLE_AI_KEY` (speichere deinen Google-AI-API-Schlüssel unter diesem Namen) +- `ai:capabilities` auf `["tools", "images", "pdfs"]` (automatisch konfiguriert) + +### Azure OpenAI (moderne API) + +Für die moderne Azure-OpenAI-API nutze den `azure`-Anbieter: + +```json +{ + "azure-gpt4": { + "display:name": "Azure GPT-4", + "ai:provider": "azure", + "ai:model": "gpt-4", + "ai:azureresourcename": "your-resource-name" + } +} +``` + +Der Anbieter setzt automatisch: +- `ai:endpoint` auf `https://your-resource-name.openai.azure.com/openai/v1/chat/completions` (oder `/responses` für neuere Modelle) +- `ai:apitype` basierend auf dem Modell +- `ai:apitokensecretname` auf `AZURE_OPENAI_KEY` (speichere deinen Azure-OpenAI-Schlüssel unter diesem Namen) + +:::note +Für Azure-Anbieter musst du `ai:capabilities` manuell basierend auf den Funktionen deines Modells angeben. Beispiel: +```json +{ + "azure-gpt4": { + "display:name": "Azure GPT-4", + "ai:provider": "azure", + "ai:model": "gpt-4", + "ai:azureresourcename": "your-resource-name", + "ai:capabilities": ["tools", "images"] + } +} +``` +::: + +### Azure OpenAI (Legacy-Deployment-API) + +Für Legacy-Azure-Deployments nutze den `azure-legacy`-Anbieter: + +```json +{ + "azure-legacy-gpt4": { + "display:name": "Azure GPT-4 (Legacy)", + "ai:provider": "azure-legacy", + "ai:azureresourcename": "your-resource-name", + "ai:azuredeployment": "your-deployment-name" + } +} +``` + +Der Anbieter erstellt automatisch die vollständige Endpunkt-URL und setzt die API-Version (Standard ist `2025-04-01-preview`). Du kannst die API-Version bei Bedarf mit `ai:azureapiversion` überschreiben. + +:::note +Für den Azure-Legacy-Anbieter musst du `ai:capabilities` manuell basierend auf den Funktionen deines Modells angeben. +::: + +## Secrets für API-Schlüssel nutzen + +Statt API-Schlüssel direkt in der Konfiguration zu speichern, solltest du Waves Secret-Speicher nutzen, um deine Zugangsdaten sicher aufzubewahren. Secrets werden verschlüsselt im nativen Schlüsselbund deines Systems gespeichert. + +### Einen API-Schlüssel speichern + +**Über die Secrets-Oberfläche (empfohlen):** +1. Klicke auf das Einstellungssymbol (Zahnrad) in der Widget-Leiste +2. Wähle "Secrets" aus dem Menü +3. Klicke auf "Add New Secret" +4. Gib den Secret-Namen (z. B. `OPENAI_API_KEY`) und deinen API-Schlüssel ein +5. Klicke auf "Save" + +**Oder über die Kommandozeile:** +```bash +wsh secret set OPENAI_KEY=sk-xxxxxxxxxxxxxxxx +wsh secret set OPENROUTER_KEY=sk-xxxxxxxxxxxxxxxx +``` + +### Auf das Secret verweisen + +Bei Nutzung von Anbietern wie `openai` oder `openrouter` wird der Secret-Name automatisch gesetzt. Stelle nur sicher, dass das Secret mit dem richtigen Namen existiert: + +```json +{ + "my-openai-mode": { + "display:name": "OpenAI GPT-4o", + "ai:provider": "openai", + "ai:model": "gpt-4o" + } +} +``` + +Der `openai`-Anbieter sucht automatisch nach dem Secret `OPENAI_KEY`. Weitere Informationen zur sicheren Verwaltung von Secrets in Wave findest du in der [Secrets-Dokumentation](./secrets.mdx). + +## Beispiel mit mehreren Modi + +Du kannst mehrere KI-Modi definieren und einfach zwischen ihnen wechseln: + +```json +{ + "ollama-llama": { + "display:name": "Ollama - Llama 3.3", + "display:order": 1, + "ai:model": "llama3.3:70b", + "ai:endpoint": "http://localhost:11434/v1/chat/completions", + "ai:apitoken": "ollama" + }, + "ollama-codellama": { + "display:name": "Ollama - CodeLlama", + "display:order": 2, + "ai:model": "codellama:34b", + "ai:endpoint": "http://localhost:11434/v1/chat/completions", + "ai:apitoken": "ollama" + }, + "openai-gpt4o": { + "display:name": "GPT-4o", + "display:order": 10, + "ai:provider": "openai", + "ai:model": "gpt-4o" + } +} +``` + +## Fehlerbehebung + +### Verbindungsprobleme + +Wenn Wave sich nicht mit deinem Modellserver verbinden kann: + +1. **Für Cloud-Anbieter mit gesetztem `ai:provider`**: Stelle sicher, dass das korrekte Secret gespeichert ist (z. B. `OPENAI_KEY`, `OPENROUTER_KEY`) +2. **Für lokale/benutzerdefinierte Endpunkte**: Prüfe, ob der Server läuft (`curl http://localhost:11434/v1/models` für Ollama) +3. Prüfe, ob `ai:endpoint` die vollständige Endpunkt-URL einschließlich Pfad ist (z. B. `http://localhost:11434/v1/chat/completions`) +4. Prüfe, ob `ai:apitype` zur API deines Servers passt (die Standardwerte sind bei Nutzung von Anbietern meist korrekt) +5. Prüfe die Firewall-Einstellungen, wenn du eine Nicht-localhost-Adresse nutzt + +### Modell nicht gefunden + +Wenn du Fehler wie "model not found" erhältst: + +1. Prüfe, ob der Modellname genau dem entspricht, was dein Server erwartet +2. Nutze bei Ollama `ollama list`, um verfügbare Modelle zu sehen +3. Manche Server benötigen Präfixe oder bestimmte Namensformate + +### Auswahl des API-Typs + +- Der API-Typ ist standardmäßig `openai-chat`, wenn nicht angegeben, was für die meisten Anbieter funktioniert +- Nutze `openai-chat` für Ollama, LM Studio, benutzerdefinierte Endpunkte und die meisten Cloud-Anbieter +- Nutze `openai-responses` für neuere OpenAI-Modelle (GPT-5+) oder wenn dein Anbieter dies ausdrücklich erfordert +- Anbieter-Voreinstellungen setzen bei Bedarf automatisch den richtigen API-Typ + +## Konfigurationsreferenz + +### Minimale Konfiguration (mit Anbieter) + +```json +{ + "mode-key": { + "display:name": "Qwen (OpenRouter)", + "ai:provider": "openrouter", + "ai:model": "qwen/qwen-2.5-coder-32b-instruct" + } +} +``` + +### Vollständige Konfiguration (alle Felder) + +```json +{ + "mode-key": { + "display:name": "Display Name", + "display:order": 1, + "display:icon": "icon-name", + "display:description": "Full description", + "ai:provider": "custom", + "ai:apitype": "openai-chat", + "ai:model": "model-name", + "ai:thinkinglevel": "medium", + "ai:endpoint": "http://localhost:11434/v1/chat/completions", + "ai:azureapiversion": "v1", + "ai:apitoken": "your-token", + "ai:apitokensecretname": "PROVIDER_KEY", + "ai:azureresourcename": "your-resource", + "ai:azuredeployment": "your-deployment", + "ai:capabilities": ["tools", "images", "pdfs"] + } +} +``` + +### Feldreferenz + +| Feld | Erforderlich | Beschreibung | +|-------|----------|-------------| +| `display:name` | Ja | Im KI-Modus-Auswahlmenü angezeigter Name | +| `display:order` | Nein | Sortierreihenfolge in der Auswahl (niedrigere Zahlen zuerst) | +| `display:icon` | Nein | Icon-Bezeichner für den Modus (kann jedes [FontAwesome-Icon](https://fontawesome.com/search) nutzen, ohne das Präfix "fa-"). Standard ist "sparkles" | +| `display:description` | Nein | Vollständige Beschreibung des Modus | +| `ai:provider` | Nein | Anbieter-Voreinstellung: `openai`, `openrouter`, `nanogpt`, `groq`, `google`, `azure`, `azure-legacy`, `custom` | +| `ai:apitype` | Nein | API-Typ: `openai-chat`, `openai-responses` oder `google-gemini` (Standard `openai-chat`, wenn nicht angegeben) | +| `ai:model` | Nein | Modellbezeichner (für die meisten Anbieter erforderlich) | +| `ai:thinkinglevel` | Nein | Denk-Level: `low`, `medium` oder `high` | +| `ai:endpoint` | Nein | *Vollständige* API-Endpunkt-URL (automatisch vom Anbieter gesetzt, falls verfügbar) | +| `ai:azureapiversion` | Nein | Azure-API-Version (für den `azure-legacy`-Anbieter, Standard `2025-04-01-preview`) | +| `ai:apitoken` | Nein | API-Schlüssel/-Token (nicht empfohlen - nutze stattdessen Secrets) | +| `ai:apitokensecretname` | Nein | Name des Secrets, das das API-Token enthält (automatisch vom Anbieter gesetzt) | +| `ai:azureresourcename` | Nein | Azure-Ressourcenname (für Azure-Anbieter) | +| `ai:azuredeployment` | Nein | Azure-Deployment-Name (für den `azure-legacy`-Anbieter) | +| `ai:capabilities` | Nein | Array unterstützter Fähigkeiten: `"tools"`, `"images"`, `"pdfs"` | +| `waveai:cloud` | Nein | Intern - nur für die Konfiguration von Wave Cloud AI | +| `waveai:premium` | Nein | Intern - nur für die Konfiguration von Wave Cloud AI | + +### KI-Fähigkeiten + +Das Feld `ai:capabilities` gibt an, welche Funktionen der KI-Modus unterstützt: + +- **`tools`** - Aktiviert KI-Tool-Nutzung zum Lesen/Schreiben von Dateien, Shell-Integration und Widget-Interaktion +- **`images`** - Erlaubt Bildanhänge im Chat (das Modell kann hochgeladene Bilder ansehen) +- **`pdfs`** - Erlaubt PDF-Dateianhänge im Chat (das Modell kann PDF-Inhalte lesen) + +**Anbieterspezifisches Verhalten:** +- **OpenAI- und Google-Anbieter**: Fähigkeiten werden automatisch basierend auf dem Modell konfiguriert. Du musst sie nicht angeben. +- **OpenRouter-, NanoGPT-, Groq-, Azure-, Azure-Legacy- und Custom-Anbieter**: Du musst die Fähigkeiten manuell basierend auf den Funktionen deines Modells angeben. + +:::warning +Wenn du `"tools"` nicht in das Array `ai:capabilities` aufnimmst, kann das KI-Modell nicht mit deinen Wave-Terminal-Widgets interagieren, Dateien lesen/schreiben oder Befehle ausführen. Die meisten KI-Modi sollten `"tools"` enthalten, um die beste Wave-Erfahrung zu bieten. +::: + +Die meisten Modelle unterstützen `tools` und profitieren davon. Vision-fähige Modelle sollten `images` enthalten. Nicht alle Modelle unterstützen PDFs, füge `pdfs` daher nur hinzu, wenn dein Modell sie verarbeiten kann. diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/waveai.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/waveai.mdx new file mode 100644 index 0000000000..e925a385f6 --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/waveai.mdx @@ -0,0 +1,110 @@ +--- +sidebar_position: 1.5 +id: "waveai" +title: "Wave AI" +--- + +import { Kbd } from "@site/src/components/kbd"; +import { PlatformProvider, PlatformSelectorButton } from "@site/src/components/platformcontext"; + + + + + +

+Kontextbewusster Terminal-Assistent mit Zugriff auf Terminal-Ausgabe, Widgets und Dateisystem. + +## Tastenkombinationen + +| Kombination | Aktion | +|----------|--------| +| | KI-Panel umschalten | +| | KI-Eingabe fokussieren | +| | Chat leeren / neu starten | +| | Nachricht senden | +| | Neue Zeile | + +## Widget-Kontext-Umschalter + +Steuert den Zugriff der KI auf deinen Workspace: + +**AN**: Die KI kann Terminal-Ausgaben lesen, Screenshots von Widgets erstellen, auf Dateien/Verzeichnisse zugreifen (mit Freigabe), in Web-Widgets navigieren und Tools für benutzerdefinierte Widgets nutzen. Nützlich für Debugging, Code-Analyse und Workspace-Aufgaben. + +**AUS**: Die KI sieht nur deine Nachrichten und angehängte Dateien. Standard-Chat-Modus für allgemeine Fragen. + +## Dateianhänge + +Ziehe Dateien auf das KI-Panel, um sie anzuhängen (nicht bei allen Modellen unterstützt): + +| Typ | Formate | Größenlimit | Hinweise | +|------|---------|------------|-------| +| Bilder | JPEG, PNG, GIF, WebP, SVG | 10 MB | Automatisch auf max. 4096px verkleinert, zu WebP konvertiert | +| PDFs | `.pdf` | 5 MB | Textextraktion zur Analyse | +| Text/Code | `.js`, `.ts`, `.py`, `.go`, `.md`, `.json`, `.yaml` usw. | 200 KB | Alle gängigen Sprachen und Konfigurationen | + +## CLI-Integration + +Nutze `wsh ai`, um Dateien und Prompts von der Kommandozeile aus zu senden: + +```bash +git diff | wsh ai - # An die KI weiterleiten +wsh ai main.go -m "find bugs" # Dateien mit Nachricht anhängen +wsh ai $(tail -n 500 my.log) -m "review" -s # Automatisch mit Ausgabe absenden +``` + +Unterstützt Textdateien, Bilder, PDFs und Verzeichnisse. Nutze `-n` für einen neuen Chat, `-s` zum automatischen Absenden. + +## KI-Tools (bei aktiviertem Widget-Kontext) + +### Terminal +- **Terminal-Ausgabe lesen**: Ruft den Scrollback von Terminal-Widgets ab, unterstützt Zeilenbereiche + +### Dateisystem +- **Dateien lesen**: Liest Textdateien mit Unterstützung für Zeilenbereiche (Freigabe erforderlich) +- **Verzeichnisse auflisten**: Liefert Dateiinformationen, Größen, Berechtigungen, Zeitstempel (Freigabe erforderlich) +- **Textdateien schreiben**: Dateien mit Diff-Vorschau und Freigabe erstellen oder ändern (Freigabe erforderlich) + +### Web +- **Im Web navigieren**: Ändert URLs in Web-Browser-Widgets + +### Alle Widgets +- **Screenshots erstellen**: Erstellt Screenshots eines beliebigen Widgets zur visuellen Analyse (nicht bei allen Modellen unterstützt) + +:::warning Sicherheit +Dateisystemoperationen erfordern eine ausdrückliche Freigabe. Du behältst die Kontrolle über jeden Dateizugriff. +::: + +## Lokale Modelle & BYOK + +Wave AI unterstützt die Nutzung eigener KI-Modelle und API-Schlüssel: + +- **Lokale Modelle**: Betreibe KI-Modelle lokal mit [Ollama](https://ollama.ai), [LM Studio](https://lmstudio.ai), [vLLM](https://docs.vllm.ai) und anderen OpenAI-kompatiblen Servern +- **BYOK (Bring Your Own Key)**: Nutze deine eigenen API-Schlüssel mit OpenAI, OpenRouter, Google AI (Gemini), Azure OpenAI und anderen Cloud-Anbietern +- **Mehrere Modi**: Konfiguriere mehrere KI-Anbieter und -Modelle und wechsle zwischen ihnen +- **Datenschutz**: Behalte deine Daten lokal oder nutze deinen bevorzugten Cloud-Anbieter + +Die vollständige Anleitung zu Konfiguration, Beispielen und Fehlerbehebung findest du im [**Leitfaden zu lokalen Modellen & BYOK**](./waveai-modes.mdx). + +## Datenschutz + +**Standard-Wave-AI-Dienst:** +- Nachrichten werden über den Wave-Cloud-AI-Dienst weitergeleitet (betrieben mit den APIs von OpenAI). Details zum Umgang mit deinen Daten findest du in der Datenschutzerklärung von OpenAI. +- Wave speichert deine Chats und Anhänge nicht und nutzt sie nicht zum Training +- Nutzungszähler sind in der anonymen Telemetrie enthalten +- Dateizugriff erfordert eine ausdrückliche Freigabe + +**Lokale Modelle & BYOK:** +- Bei Nutzung lokaler Modelle verlassen deine Chatdaten niemals deinen Rechner +- Bei Nutzung von BYOK mit Cloud-Anbietern werden Anfragen direkt an den von dir gewählten Anbieter gesendet +- Details zum Umgang mit deinen Daten findest du in der Datenschutzerklärung deines Anbieters + +:::info In aktiver Entwicklung +Wave AI befindet sich in einer aktiven Beta-Phase mit enthaltenem KI-Guthaben, während wir die Erfahrung weiter verfeinern. Teile dein Feedback in unserem [Discord](https://discord.gg/XfvZ334gwU). + +**Demnächst:** +- **Remote-Dateizugriff**: Dateien auf per SSH verbundenen Systemen lesen +- **Befehlsausführung**: Terminal-Befehle mit Freigabe ausführen +- **Webinhalte**: Text aus Webseiten extrahieren (aktuell nur Screenshots) +::: + +
diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/widgets.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/widgets.mdx new file mode 100644 index 0000000000..aa494f9313 --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/widgets.mdx @@ -0,0 +1,148 @@ +--- +sidebar_position: 3.3 +id: "widgets" +title: "Widgets" +--- + +import { Kbd } from "@site/src/components/kbd"; +import { PlatformProvider, PlatformSelectorButton } from "@site/src/components/platformcontext"; +import { VersionBadge } from "@site/src/components/versionbadge"; + + + +# Widgets + +Jede einzelne Komponente befindet sich in ihrem eigenen Widget. Diese können hinzugefügt, entfernt, verschoben und in der Größe angepasst werden. Jedes Widget hat seine eigene Kopfzeile, die per Rechtsklick weitere Optionen für dieses Widget anzeigt. + + + +### Ein Widget hinzufügen + +Ein Widget kannst du über die Widget-Leiste auf der rechten Seite des Fensters hinzufügen. Dadurch wird ein Widget des ausgewählten Typs zum aktuellen Tab hinzugefügt. + +### Ein Widget schließen + +Widgets können geschlossen werden, indem du auf den ****-Button auf der rechten Seite der Kopfzeile klickst. Alternativ kann das aktuell fokussierte Widget durch Drücken von geschlossen werden. + +### Zwischen Widgets navigieren + +Es kann höchstens ein Widget fokussiert sein. Je nach Widget-Typ kannst du dadurch direkt mit dem Inhalt dieses Widgets interagieren. Ein fokussiertes Widget ist immer mit einem deutlichen Rahmen umrandet. Ein Widget kann durch Anklicken fokussiert werden. Alternativ kannst du das fokussierte Widget wechseln, indem du (Strg + Umschalt + Pfeiltasten) drückst, um relativ zum aktuell ausgewählten Widget zu navigieren. + +### Widgets vergrößern + +Das Vergrößern eines Widgets lässt es vor allem anderen erscheinen. Du kannst über das Kopfzeilen-Icon vergrößern, oder mit . + +### Widgets neu anordnen + +Durch Ziehen und Ablegen ihrer Kopfzeilen können Widgets an andere Stellen im Layout verschoben werden. So kannst du deinen Bildschirm ganz nach deinen Wünschen neu organisieren. Beim Ziehen siehst du eine Vorschau des gezogenen Widgets. Befindet sich das Widget über einer gültigen Ablagestelle, wird der Bereich, in den es verschoben würde, grün markiert. Loslassen platziert das Widget dort und ordnet die anderen Widgets entsprechend neu an. Wenn eine grüne Box die Hälfte zweier verschiedener Widgets bedeckt, wird das Widget zwischen den beiden platziert. Wenn die grüne Box die Hälfte eines Widgets am Bildschirmrand bedeckt, wird das Widget zwischen diesem Widget und dem Bildschirmrand platziert. Bedeckt die grüne Box ein Widget vollständig, tauschen die beiden Widgets ihre Position. + +Weitere Informationen findest du im [Tab-Layout-System](./layout#move-a-block). + +### Widgets in der Größe anpassen + +Wenn du mit der Maus zwischen zwei Widgets fährst, ändert sich dein Cursor zu oder , und eine grüne Linie zwischen den Widgets wird sichtbar. Durch Ziehen und Ablegen dieser grünen Linie kannst du die angrenzenden Widgets in der Größe anpassen. + +Weitere Informationen findest du im [Tab-Layout-System](./layout#resize-a-block). + +## Widget-Typen + +### Term + +Das übliche Terminal, das du kennst und schätzt. Wir fügen über den Befehl `wsh` einige Plugins hinzu, über die du weiter unten mehr erfährst. + +### Preview + +Preview ist der generische Widget-Typ zum Ansehen von Dateien. Je nach Dateityp kann dies viele verschiedene Formen annehmen. +Du kannst \`wsh view [path]\` aus jedem Wave-Terminal-Fenster nutzen, um ein Vorschau-Widget mit dem Inhalt des angegebenen Pfads zu öffnen (z. B. `wsh view .` oder `wsh view ~/myimage.jpg`). + +#### Verzeichnis + +Beim Betrachten eines Verzeichnisses zeigt Preview einen Dateibetrachter ähnlich der macOS-App _Finder_ oder der Windows-App _Explorer_. Diese Variante ist etwas stärker auf Softwareentwicklung ausgerichtet, mit Fokus darauf, was der Befehl `ls -alh` anzeigt. + +##### Eine neue Datei ansehen + +Der einfachste Weg, eine neue Datei anzusehen, ist ein Doppelklick auf ihre Zeile im Dateibetrachter. Alternativ kannst du bei fokussiertem Widget mit den Pfeiltasten und eine Zeile auswählen und mit Enter die zugehörige Datei in der Vorschau öffnen. + +##### Eine Datei kopieren + +Wenn du zwei Verzeichnis-Widgets geöffnet hast, kannst du eine Datei oder ein Verzeichnis zwischen ihnen kopieren. Ziehe dazu die Datei oder das Verzeichnis von einem Verzeichnis-Vorschau-Widget in ein anderes, das an der gewünschten Zielstelle geöffnet ist. Das funktioniert sogar beim Kopieren von Dateien und Verzeichnissen über Verbindungen hinweg. + +##### Das übergeordnete Verzeichnis ansehen + +In der Verzeichnisansicht ist das so einfach wie das Öffnen der Datei `..`, als wäre es eine normale Datei. Das geht mit der oben beschriebenen Methode. Du kannst auch das Tastenkürzel nutzen. + +##### Vor- und zurücknavigieren + +Beim Betrachten einer Datei kannst du zurücknavigieren, indem du auf den Zurück-Button in der Widget-Kopfzeile klickst oder das Tastenkürzel nutzt. Du kannst jederzeit mit und vor- und zurücknavigieren. + +##### Die Dateiliste filtern + +Bei fokussiertem Widget kannst du nach Dateinamen filtern, indem du einen Teil des gesuchten Dateinamens eingibst. Um den Filter zu löschen, klicke auf das **** im Filter-Dropdown oder drücke . + +##### Nach einer Dateispalte sortieren + +Um Dateien nach einer bestimmten Spalte zu sortieren, klicke auf die Kopfzeile dieser Spalte. Ein erneuter Klick auf die Kopfzeile kehrt die Sortierreihenfolge um. + +##### Versteckte Dateien ein-/ausblenden + +Rechts in der Widget-Kopfzeile gibt es einen ****-Button. Ein Klick darauf blendet versteckte Dateien ein und aus. + +##### Das Verzeichnis aktualisieren + +Rechts in der Widget-Kopfzeile gibt es einen Aktualisieren-Button ****. Ein Klick darauf aktualisiert den Verzeichnisinhalt. + +##### Zu häufig genutzten Verzeichnissen navigieren + +Links in der Widget-Kopfzeile gibt es ein Datei-Icon ****. Klicken und Halten dieses Icons öffnet ein Menü, in dem du zu einem häufig genutzten Ordner navigieren kannst. Die verfügbaren Optionen sind _Home_, _Desktop_, _Downloads_ und _Root_. + +##### Ein neues Terminal im aktuellen Verzeichnis öffnen + +Wenn du mit der rechten Maustaste auf die Kopfzeile des Widgets klickst (alternativ auf das Zahnrad-Icon ****), findest du unter anderem den Menüpunkt **Open Terminal in New Widget**. Dadurch wird ein neues Terminal-Widget in deinem aktuellen Verzeichnis erstellt. + +##### Ein neues Terminal in einem Unterverzeichnis öffnen + +Wenn du stattdessen ein Terminal für ein Unterverzeichnis öffnen möchtest, kannst du mit der rechten Maustaste auf die Zeile dieser Datei klicken, um die Option **Open Terminal in New Widget** zu erhalten. Ein Klick darauf öffnet ein Terminal in diesem Verzeichnis. Beachte, dass diese Option nur für Unterelemente verfügbar ist, die Verzeichnisse sind. + +##### Eine neue Vorschau für ein Unterelement öffnen + +Um eine neue Vorschau für ein Unterelement zu öffnen, kannst du mit der rechten Maustaste auf die Zeile dieser Datei klicken und die Option **Open Preview in New Widget** auswählen. + +##### Quick Look (nur macOS) + +Auf einem macOS-Host kannst du die Quick-Look-Funktion aus der Verzeichnisvorschau nutzen. Wähle dazu die gewünschte Datei aus und drücke . Dadurch öffnet sich eine Vorschau deiner Datei in einem separaten Fenster. Diese Vorschau kann durch erneutes Drücken von wieder geschlossen werden. Dies unterstützt aktuell die Dateitypen, auf die der Befehl `qlmanage` zugreifen kann. + +#### Markdown + +Das Öffnen einer Markdown-Datei zeigt eine gerenderte Ansicht des Markdown-Inhalts. Diese Dateien können derzeit nicht in der Vorschau bearbeitet werden. + +#### Bilder/Medien + +Das Öffnen eines Bildes zeigt dieses Bild an. Das Öffnen eines Videos öffnet einen Player, mit dem du das Video ansehen kannst. + +### Codeedit + +Das Öffnen der meisten Textdateien öffnet Codeedit, um die Datei anzusehen oder zu bearbeiten. Technisch gesehen ist es Teil des Preview-Widgets, aber wichtig genug, um separat aufgeführt zu werden. +Nach dem Öffnen eines Codeedit-Widgets ist es oft nützlich, es zu vergrößern (), um eine größere Ansicht zu erhalten. Anschließend kannst du mit den folgenden Tastenkürzeln in den Bearbeitungsmodus wechseln, Änderungen vornehmen, speichern und dann mit das Widget schließen (alles ohne die Maus!). + +#### In den Bearbeitungsmodus wechseln + +Um in den Bearbeitungsmodus zu wechseln, klicke auf den Bearbeiten-Button rechts in der Kopfzeile. Damit kannst du den Dateiinhalt mit einem gewöhnlichen Monaco-Editor bearbeiten. +Du kannst auch mit in den Bearbeitungsmodus wechseln. + +#### Eine Bearbeitung speichern + +Sobald du im **Bearbeitungsmodus** eine Änderung vorgenommen hast, klicke auf den Speichern-Button rechts in der Kopfzeile, um den Inhalt zu speichern. +Du kannst auch mit speichern. + +#### Bearbeitungsmodus ohne Speichern verlassen + +Um den **Bearbeitungsmodus** ohne Speichern zu verlassen, klicke auf den Abbrechen-Button rechts in der Kopfzeile. +Du kannst auch mit ohne Speichern verlassen. + +### Prozess-Viewer + +Der Prozess-Viewer zeigt eine Live-Liste laufender Prozesse auf jedem verbundenen Host. Er ähnelt `top` oder `htop` und zeigt PID, Befehl, CPU% und Speichernutzung an. Unter Linux zeigt er zusätzlich Prozessstatus und Thread-Anzahl. + +Spalten lassen sich durch Klick auf ihre Kopfzeile sortieren. Ein Rechtsklick auf eine Zeile erlaubt das Senden von Unix-Signalen (SIGTERM, SIGKILL usw.) oder das Kopieren der PID. Du kannst die Liste filtern, indem du drückst und einen Suchbegriff eingibst. Drücke , um Live-Updates zu pausieren (nützlich, um einen bestimmten Prozess zu untersuchen); erneutes Drücken setzt sie fort. + + diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/workspaces.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/workspaces.mdx new file mode 100644 index 0000000000..ca88342eda --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/workspaces.mdx @@ -0,0 +1,72 @@ +--- +sidebar_position: 3 +id: "workspaces" +title: "Workspaces" +--- + +# Workspaces + +Workspaces sind eine leistungsstarke Möglichkeit, deine Workflows in separate Umgebungen zu gliedern, die du anpassen und optimieren kannst. + +## Workspace-Umschalter + +![Workspace switcher screenshot](./img/workspace-switcher.png#right) + +Der primäre Weg, mit Workspaces zu interagieren, ist der Workspace-Umschalter links neben der Tableiste. + +Hier kannst du einen neuen Workspace erstellen, ändern, wie ein Workspace-Eintrag angezeigt wird, und einen Workspace löschen. + +Der Workspace-Umschalter-Button zeigt Icon und Farbe des aktiven Workspace an. Wenn der aktuelle Workspace nicht gespeichert ist, wird das Icon angezeigt. Ein Klick auf den Button öffnet den Workspace-Umschalter. + +Der Umschalter enthält eine Liste aller gespeicherten Workspaces deiner Installation, jeweils mit anpassbarem Icon, Icon-Farbe und Namen. + +Der aktive Workspace des aktuellen Fensters hat ein daneben. Jeder Workspace, der aktuell in einem anderen Fenster geöffnet ist, hat das Icon daneben. + +Wenn du mit der Maus über einen Workspace im Umschalter fährst, erscheint ein -Icon. Ein Klick darauf öffnet einen Editor-Bereich, in dem du Name, Icon und Icon-Farbe des Workspace ändern kannst. Von dort aus kannst du einen Workspace auch löschen. + +## Einen neuen Workspace erstellen + +Jedes neue Fenster wird mit einem leeren Workspace initialisiert, der einen einzelnen Tab mit einem einzelnen Terminal-Block enthält. Es gibt drei Möglichkeiten, einen neuen Workspace zu erstellen: + +1. Erstelle ein neues Fenster, entweder über das `Datei`-App-Menü oder mit dem [Tastenkürzel](./keybindings.mdx#global-keybindings). Dadurch werden ein neues Fenster und darin ein neuer Workspace erstellt. +2. Erstelle einen neuen Workspace über das `Workspace`-App-Menü. Dadurch wird ein neuer Workspace erstellt und das aktuelle Fenster wechselt zu diesem Workspace. +3. Wenn du dich in einem gespeicherten Workspace befindest, kannst du unten im Workspace-Umschalter auf "Neuen Workspace erstellen" klicken. Dadurch wird ein neuer Workspace erstellt und das aktuelle Fenster wechselt zu diesem Workspace. + +## Einen Workspace speichern + +:::info + +Ein neuer Workspace ist flüchtig. Wenn ein Fenster geschlossen wird, werden sein Workspace und alle seine Tabs gelöscht, sofern der Workspace nicht gespeichert wurde. + +Die Ausnahme von dieser Regel ist, dass das zuletzt geöffnete Fenster beim Schließen erhalten bleibt und beim nächsten Öffnen der App wieder geöffnet wird, unabhängig davon, ob der Workspace gespeichert ist. + +::: + +Um einen neuen Workspace dauerhaft zu erhalten, musst du ihn speichern. Klicke dazu unten im Workspace-Umschalter auf "Workspace speichern". + +Wenn du stattdessen unten im Workspace-Umschalter "Neuen Workspace erstellen" siehst, befindest du dich bereits in einem gespeicherten Workspace. Das kannst du auch am Text oben im Workspace-Umschalter erkennen: Bei einem ungespeicherten Workspace siehst du "Workspace öffnen"; bei einem gespeicherten Workspace siehst du "Workspaces wechseln". Außerdem erkennst du es daran, dass das Icon des Workspace-Umschalter-Buttons ist. + +Sobald ein Workspace gespeichert ist, siehst du einen neuen Eintrag in der Workspace-Umschalter-Liste für deinen gespeicherten Workspace. Er wird `New Workspace ()` genannt. Um deinen Workspace optimal zu nutzen, wird empfohlen, diesen Namen sowie Icon und Icon-Farbe in etwas Einprägsameres oder Aussagekräftigeres zu ändern. + +## Workspaces wechseln + +Es gibt zwei Möglichkeiten, Workspaces zu wechseln: + +1. Öffne in einem offenen Fenster den Workspace-Umschalter und klicke auf einen Workspace aus der Liste. +2. Klicke im Workspace-App-Menü auf einen Workspace aus der Liste. + +Wenn der Workspace bereits in einem anderen Fenster geöffnet ist (er hat das -Icon daneben, wenn du dich im Workspace-Umschalter befindest), erhält dieses Fenster den Fokus. + +Wenn der Workspace nicht geöffnet ist, wechselt dein aktuelles Fenster dorthin. Wenn dein aktueller Workspace ungespeichert ist, wirst du gefragt, ob du den neuen Workspace in einem neuen Fenster oder im aktuellen Fenster öffnen möchtest. **Wenn du Letzteres wählst, werden der aktuelle Workspace und sein Inhalt gelöscht.** + +Der Workspace-Umschalter-Button aktualisiert sich mit dem farbigen Icon deines neuen aktiven Workspace. + +## Einen Workspace bearbeiten + +:::info + +Tabs, Layouts sowie Terminal- und KI-Verlauf eines [gespeicherten Workspace](#einen-workspace-speichern) werden automatisch dauerhaft gespeichert. Ungespeicherte Dateiänderungen in einem Editor oder auf einer Webseite gehen jedoch beim Schließen des Fensters verloren. + +::: + +Um Name, Icon und Icon-Farbe eines Workspace zu ändern, fahre im Workspace-Umschalter mit der Maus über den Workspace und klicke auf den erscheinenden -Button. Dadurch öffnet sich ein Editor-Bereich, in dem du deine Änderungen vornehmen kannst. Sie werden automatisch gespeichert und aktualisiert. diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/wsh-reference.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/wsh-reference.mdx new file mode 100644 index 0000000000..1f60009d32 --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/wsh-reference.mdx @@ -0,0 +1,1123 @@ +--- +sidebar_position: 4.1 +id: "wsh-reference" +title: "wsh-Referenz" +--- + +import { Kbd } from "@site/src/components/kbd"; +import { PlatformProvider, PlatformSelectorButton } from "@site/src/components/platformcontext"; +import { VersionBadge } from "@site/src/components/versionbadge"; + + + +# wsh-Befehl + +Der Befehl `wsh` ist in Wave-Blöcken immer verfügbar. Er ist ein leistungsstarkes Werkzeug zur Interaktion mit Wave-Blöcken und kann Daten zwischen deiner CLI und den Widget-Oberflächen austauschen. + +Dies ist die ausführliche wsh-Referenzdokumentation. Einen Überblick über die `wsh`-Funktionalität findest du in unserer [wsh-Befehlsdokumentation](/wsh). + +--- + +## view + +Du kannst einen Vorschau-Block mit dem Inhalt einer beliebigen Datei oder eines Verzeichnisses öffnen, indem du Folgendes ausführst: + +```sh +wsh view [path] +wsh view -m [path] # öffnet in vergrößertem Block +``` + +Mit diesem Befehl kannst du einfach Bilder, Markdown-Dateien und Verzeichnisse in der Vorschau ansehen. Bei Code-/Textdateien öffnet sich +ein Codeedit-Block, mit dem du die Datei schnell in Waves integriertem grafischen Editor bearbeiten kannst. + +--- + +## edit + +```sh +wsh edit [path] +wsh edit -m [path] # öffnet in vergrößertem Block +``` + +Dies öffnet einen Codeedit-Block für die angegebene Datei. Nützlich, um Dateien auf einem lokalen oder entfernten Rechner schnell in Waves grafischem Editor zu bearbeiten. Dieser Befehl kehrt sofort nach dem Öffnen des Blocks zurück. + +Für die `$EDITOR`-Integration (z. B. mit `git commit`) siehe [`wsh editor`](#editor), der blockiert, bis der Editor geschlossen wird. + +--- + +## editor + +```sh +wsh editor [path] +wsh editor -m [path] # öffnet in vergrößertem Block +``` + +Dies öffnet einen Codeedit-Block für die angegebene Datei und **blockiert, bis der Editor geschlossen wird**. Nützlich, um deine Umgebungsvariable `$EDITOR` so zu setzen, dass CLI-Tools (z. B. `git commit`, `crontab -e`) Dateien in Waves grafischem Editor öffnen: + +```sh +export EDITOR="wsh editor" +``` + +Die Datei muss bereits existieren. Nutze `-m`, um den Editor im vergrößerten Modus zu öffnen. + +--- + +## getmeta + +Du kannst dir die Metadaten eines beliebigen Blocks oder Tabs ansehen, indem du Folgendes ausführst: + +```sh +# Metadaten für den aktuellen Terminal-Block abrufen +wsh getmeta + +# Metadaten für Block Nummer 2 abrufen (Blocknummern siehst du bei gedrückter Strg+Umschalt-Taste) +wsh getmeta -b 2 + +# Metadaten für eine Block-ID abrufen (Block-IDs per Rechtsklick auf eine Block-Kopfzeile "Copy Block Id") +wsh getmeta -b [blockid] + +# Metadaten für einen Tab abrufen +wsh getmeta -b tab + +# Einen einzelnen Metadaten-Schlüssel ausgeben +wsh getmeta [-b [blockid]] [key] + +# Eine Reihe von Schlüsseln mit einem bestimmten Präfix ausgeben +wsh getmeta -b tab "bg:*" + +# Eine Reihe von Schlüsseln mit Präfix ausgeben (und den 'clear'-Schlüssel einschließen) +wsh getmeta -b tab --clear-prefix "bg:*" +``` + +Das ist besonders nützlich für Vorschau- und Web-Blöcke, da du die Datei oder URL sehen kannst, auf die sie verweisen, und sie in deinen CLI-Skripten nutzen kannst. + +Format der blockid: + +- `this` -- der aktuelle Block (das ist auch der Standard) +- `tab` -- die ID des aktuellen Tabs +- `d6ff4966-231a-4074-b78a-20acc7226b41` -- eine vollständige blockid ist eine UUID +- `a67f55a3` -- blockids können auf die ersten 8 Zeichen gekürzt werden +- `5` -- wird eine Zahl kleiner als 100 angegeben, ist es eine Blocknummer. Blöcke werden im aktuellen Tab fortlaufend von oben links nach unten rechts nummeriert. Halte gedrückt, um ein Blocknummern-Overlay anzuzeigen. + +--- + +## setmeta + +Du kannst jedes Metadaten-Schlüssel-Wert-Paar für Blöcke (und Tabs) mit dem Befehl setmeta aktualisieren. Der setmeta-Befehl nimmt dieselben `-b`-Argumente wie getmeta. + +```sh +wsh setmeta -b [blockid] [key]=[value] +wsh setmeta -b [blockid] file=~/myfile.txt +wsh setmeta -b [blockid] url=https://waveterm.dev/ + +# Metadaten für den aktuellen Tab mit der angegebenen JSON-Datei setzen +wsh setmeta -b tab --json [jsonfile] + +# Metadaten für den aktuellen Tab mit einer von stdin gelesenen JSON-Datei setzen +wsh setmeta -b tab --json +``` + +Block- und Tab-IDs erhältst du per Rechtsklick auf den entsprechenden Block und Auswahl von "Copy BlockId" (oder über die Blocknummer mit Strg+Umschalt). Wenn du +die Metadaten eines Vorschau- oder Web-Blocks aktualisierst, siehst du die Änderungen sofort im Block widergespiegelt. + +Weitere nützliche Metadatenwerte zum Überschreiben von Block-Titeln, -Icons, -Farben, -Themes usw. + +Hier ist ein komplexerer Befehl, der den Hintergrund (bg:\*-Schlüssel) von einem Tab in den aktuellen Tab kopiert: + +```sh +wsh getmeta -b [other-tab-id] "bg:*" --clear-prefix | wsh setmeta -b tab --json - +``` + +--- + +## ai + +Fügt der Wave-AI-Seitenleiste Inhalte hinzu. Dateien werden als richtige Dateianhänge angehängt (mit Unterstützung für Bilder, PDFs und Text), nicht als Text kodiert. Standardmäßig wird der Inhalt der Seitenleiste hinzugefügt, ohne automatisch abzusenden, sodass du vor dem Senden an die KI weiteren Kontext prüfen und hinzufügen kannst. + +Du kannst mehrere Dateien gleichzeitig anhängen (bis zu 15 Dateien). Nutze `-m`, um eine Nachricht zusammen mit Dateien hinzuzufügen, `-s`, um sofort automatisch abzusenden, und `-n`, um ein neues Gespräch zu beginnen. Nutze "-", um von stdin zu lesen. + +```sh +# Befehlsausgabe an KI weiterleiten (Frage in der UI stellen) +git diff | wsh ai - +docker logs mycontainer | wsh ai - + +# Dateien ohne automatisches Absenden anhängen (zuerst in der UI prüfen) +wsh ai main.go utils.go +wsh ai screenshot.png logs.txt + +# Dateien mit Nachricht anhängen +wsh ai app.py -m "find potential bugs" +wsh ai *.log -m "analyze these error logs" + +# Sofort automatisch absenden +wsh ai config.json -s -m "explain this configuration" +tail -n 50 app.log | wsh ai -s - -m "what's causing these errors?" + +# Neuen Chat starten und Dateien anhängen +wsh ai -n report.pdf data.csv -m "summarize these reports" + +# Verschiedene Dateitypen anhängen (Bilder, PDFs, Code) +wsh ai architecture.png api-spec.pdf server.go -m "review the system design" +``` + +**Dateigrößenlimits:** +- Textdateien: max. 200KB +- PDF-Dateien: max. 5MB +- Bilddateien: max. 7MB (berücksichtigt den Overhead der Base64-Kodierung) +- Maximal 15 Dateien pro Befehl + +**Flags:** +- `-m, --message ` - Nachrichtentext zusammen mit Dateien hinzufügen +- `-s, --submit` - Sofort automatisch absenden (Standard wartet auf den Nutzer) +- `-n, --new` - Aktuellen Chat leeren und neues Gespräch beginnen + +--- + +## editconfig + +Mit diesem Befehl kannst du einfach jede beliebige Konfigurationsdatei von Wave öffnen. + +```sh +wsh editconfig [config-file-name] + +# öffnet die Standard-Datei settings.json +wsh editconfig + +# öffnet presets.json +wsh editconfig presets.json + +# öffnet widgets.json +wsh editconfig widgets.json + +# öffnet die KI-Voreinstellungen +wsh editconfig presets/ai.json +``` + +--- + +## setbg + +Mit dem Befehl `setbg` kannst du für den aktuellen Tab ein Hintergrundbild oder eine Hintergrundfarbe mit verschiedenen Anpassungsoptionen festlegen. + +```sh +wsh setbg [--opacity value] [--tile|--center] [--size value] [--border-color color] [--active-border-color color] (image-path|"#color"|color-name) +``` + +Du kannst einen Hintergrund festlegen mit: + +- Einer Bilddatei (angezeigt als Cover, gekachelt oder zentriert) +- Einer Hex-Farbe (muss in Anführungszeichen stehen wie "#ff0000") +- Einem CSS-Farbnamen (wie "blue" oder "forestgreen") + +Flags: + +- `--opacity value` - legt die Hintergrunddeckkraft fest (0.0-1.0, Standard 0.5) +- `--tile` - kachelt das Hintergrundbild statt des Cover-Modus +- `--center` - zentriert das Bild ohne Skalierung (gut für Logos) +- `--size` - Größe für zentrierte Bilder (px, % oder auto) +- `--clear` - entfernt den Hintergrund +- `--border-color color` - legt die Rahmenfarbe des Blocks fest (Hex oder CSS-Farbname) +- `--active-border-color color` - legt die Rahmenfarbe des fokussierten Blocks fest (Hex oder CSS-Farbname) +- `--print` - zeigt die Metadaten an, ohne sie anzuwenden + +Unterstützte Bildformate: JPEG, PNG, GIF, WebP und SVG. + +Beispiele: + +```sh +# Bildhintergrund mit Standardeinstellungen festlegen +wsh setbg ~/pictures/background.jpg + +# Hintergrund mit benutzerdefinierter Deckkraft festlegen +wsh setbg --opacity 0.3 ~/pictures/light-pattern.png + +# Gekachelten Hintergrund festlegen +wsh setbg --tile --opacity 0.2 ~/pictures/texture.png + +# Ein Bild zentrieren (gut für Logos) +wsh setbg --center ~/pictures/logo.png +wsh setbg --center --size 200px ~/pictures/logo.png + +# Farbhintergründe festlegen +wsh setbg "#ff0000" # Hex-Farbe (benötigt Anführungszeichen) +wsh setbg forestgreen # CSS-Farbname + +# Nur die Deckkraft des aktuellen Hintergrunds ändern +wsh setbg --opacity 0.7 + +# Rahmenfarben zusammen mit einem Hintergrund festlegen +wsh setbg --border-color "#ff0000" --active-border-color "#00ff00" ~/pictures/background.jpg +wsh setbg --border-color steelblue forestgreen + +# Hintergrund entfernen +wsh setbg --clear + +# Die Metadaten in der Vorschau ansehen +wsh setbg --print "#ff0000" +``` + +Der Befehl prüft, dass: + +- Farbwerte gültige Hex-Codes oder CSS-Farbnamen sind +- Bildpfade auf zugängliche, unterstützte Bilddateien verweisen +- Der Deckkraftwert zwischen 0.0 und 1.0 liegt +- Die Optionen center und tile nicht zusammen genutzt werden + +:::tip +Nutze `--print`, um die Metadaten für eine Hintergrundkonfiguration in der Vorschau anzusehen, ohne sie anzuwenden. Du kannst diese JSON-Darstellung dann als [Hintergrundeintrag](/tab-backgrounds) kopieren +::: + +--- + +## badge + + + +Der Befehl `badge` setzt oder entfernt einen visuellen Badge-Indikator an einer Block- oder Tab-Kopfzeile. + +```sh +wsh badge [icon] +wsh badge --clear +``` + +Badges werden genutzt, um Aufmerksamkeit auf einen Block oder Tab zu lenken, zum Beispiel um anzuzeigen, dass ein Prozess abgeschlossen ist oder Aufmerksamkeit benötigt. Wird kein Icon angegeben, ist der Standard `circle-small`. Icon-Namen sind [Font-Awesome](https://fontawesome.com/icons)-Icon-Namen (ohne das Präfix `fa-`). + +Flags: + +- `--color string` - legt die Badge-Farbe fest (CSS-Farbname oder Hex) +- `--priority float` - legt die Badge-Priorität fest (Standard 10; Badges mit höherer Priorität haben Vorrang) +- `--clear` - entfernt das Badge vom Block oder Tab +- `--beep` - spielt beim Setzen des Badges den System-Signalton ab +- `--pid int` - überwacht eine PID und entfernt das Badge automatisch, wenn sie beendet wird (setzt die Standardpriorität auf 5) +- `-b, --block` - zielt auf einen bestimmten Block oder Tab (gleiches Format wie `getmeta`) + +Beispiele: + +```sh +# Ein Standard-Badge auf dem aktuellen Block setzen +wsh badge + +# Ein Badge mit benutzerdefiniertem Icon und Farbe setzen +wsh badge circle-check --color green + +# Ein hochprioritäres Badge auf einem bestimmten Block setzen +wsh badge triangle-exclamation --color red --priority 20 -b 2 + +# Ein Badge setzen, das entfernt wird, wenn ein Prozess beendet wird +wsh badge --pid 12345 + +# Beim Fertigstellen den Signalton abspielen und ein Badge setzen +wsh badge circle-check --beep + +# Das Badge am aktuellen Block entfernen +wsh badge --clear + +# Das Badge an einem bestimmten Tab entfernen +wsh badge --clear -b tab +``` + +:::note +Das Flag `--pid` wird unter Windows nicht unterstützt. +::: + +--- + +## run + +Der Befehl `run` erstellt einen neuen Terminal-Befehlsblock und führt einen angegebenen Befehl darin aus. Der Befehl kann entweder als Argumente nach `--` oder mit dem Flag `-c` angegeben werden. Sofern nicht die Flags `-x` oder `-X` übergeben werden, können Befehle durch Drücken von `Enter` erneut ausgeführt werden, sobald der Befehl beendet ist. + +```sh +# Einen nach -- angegebenen Befehl ausführen +wsh run -- ls -la + +# Einen Befehl mit dem Flag -c ausführen +wsh run -c "ls -la" + +# Mit angegebenem Arbeitsverzeichnis ausführen +wsh run --cwd /path/to/dir -- ./script.sh + +# Im vergrößerten Modus ausführen +wsh run -m -- make build + +# Ausführen und bei erfolgreichem Abschluss automatisch schließen +wsh run -x -- npm test + +# Ausführen und unabhängig vom Status automatisch schließen +wsh run -X -- ./long-running-task.sh +``` + +Der Befehl übernimmt standardmäßig die aktuellen Umgebungsvariablen und das aktuelle Arbeitsverzeichnis. + +Flags: + +- `-m, --magnified` - öffnet den Block im vergrößerten Modus +- `-c, --command string` - führt einen Befehlsstring in der _Shell_ aus +- `-x, --exit` - schließt den Block, wenn der Befehl erfolgreich beendet wird (bleibt bei einem Fehler offen) +- `-X, --forceexit` - schließt den Block, wenn der Befehl beendet wird, unabhängig vom Status +- `--delay int` - bei Nutzung von -x/-X, Verzögerung in Millisekunden vor dem Schließen des Blocks (Standard 2000) +- `-p, --paused` - erstellt den Block im pausierten Zustand +- `-a, --append` - hängt die Ausgabe beim Neustart des Befehls an, statt sie zu löschen +- `--cwd string` - legt das Arbeitsverzeichnis für den Befehl fest + +Beispiele: + +```sh +# Einen Build-Befehl im vergrößerten Modus ausführen +wsh run -m -- npm run build + +# Ein Skript ausführen und nach Erfolg automatisch schließen +wsh run -x -- ./backup-script.sh + +# Einen Befehl in einem bestimmten Verzeichnis ausführen +wsh run --cwd ./project -- make test + +# Einen Shell-Befehl ausführen und nach Abschluss zwangsweise schließen +wsh run -X -c "find . -name '*.log' -delete" + +# Einen Befehl im pausierten Zustand starten +wsh run -p -- ./server --dev + +# Mit benutzerdefinierter Schließverzögerung ausführen +wsh run -x --delay 5000 -- ./deployment.sh +``` + +Bei Nutzung der Flags `-x` oder `-X` schließt sich der Block automatisch, sobald der Befehl abgeschlossen ist. Das Flag `-x` schließt nur bei erfolgreichem Abschluss (Exit-Code 0), während `-X` unabhängig vom Status schließt. Das Flag `--delay` steuert, wie lange vor dem Schließen gewartet wird (Standard 2000ms). + +Das Flag `-p` erstellt den Block im pausierten Zustand, sodass du den Befehl vor der Ausführung prüfen kannst. + +:::tip +Du kannst entweder `--` gefolgt von deinem Befehl und Argumenten nutzen, oder das Flag `-c` mit einem in Anführungszeichen gesetzten Befehlsstring. Die `--`-Methode ist vorzuziehen, wenn du die Argumentbehandlung erhalten möchtest, während `-c` für Shell-Befehle mit Pipes oder Umleitungen nützlich ist. +::: + +--- + +## deleteblock + +```sh +wsh deleteblock -b [blockid] +``` + +Dies löscht den Block mit der angegebenen ID. + +--- + +## ssh + +```sh +wsh ssh [user@host] +``` + +Dies nutzt Waves interne SSH-Implementierung, um sich mit dem angegebenen entfernten Rechner zu verbinden. Mit dem Flag `-i` kann ein Pfad zu einer Identitätsdatei angegeben werden. + +--- + +## wsl + +```sh +wsh wsl [-d ] +``` + +Dies verbindet sich mit einer WSL-Distribution auf dem lokalen Rechner. Ist keine Distribution angegeben, wird die Standarddistribution genutzt. + +--- + +## web + +Der Befehl `web` öffnet URLs in einem Web-Block innerhalb von Wave Terminal. + +```sh +wsh web open [url] [-m] [-r blockid] +``` + +Du kannst eine bestimmte URL öffnen oder mit der konfigurierten Suchmaschine eine Suche durchführen. + +Flags: + +- `-m, --magnified` - öffnet den Web-Block im vergrößerten Modus +- `-r, --replace ` - ersetzt einen bestehenden Block, statt einen neuen zu erstellen + +Beispiele: + +```sh +# Eine URL öffnen +wsh web open https://waveterm.dev + +# Mit der konfigurierten Suchmaschine suchen +wsh web open "wave terminal documentation" + +# Im vergrößerten Modus öffnen +wsh web open -m https://github.com + +# Einen bestehenden Block ersetzen +wsh web open -r 2 https://example.com +``` + +Der Befehl öffnet einen neuen Web-Block mit der gewünschten Seite oder ersetzt einen bestehenden Block, wenn das Flag `-r` genutzt wird. Beachte, dass `--replace` und `--magnified` nicht zusammen genutzt werden können. + +--- + +## notify + +Der Befehl `notify` erstellt eine Desktop-Benachrichtigung von Wave Terminal. + +```sh +wsh notify [message] [-t title] [-s] +``` + +Damit kannst du Desktop-Benachrichtigungen aus Skripten oder Befehlen auslösen. Die Benachrichtigung erscheint über das native Benachrichtigungssystem deines Systems. Sie funktioniert sowohl auf entfernten Rechnern als auch auf deinem lokalen Rechner. + +Flags: + +- `-t, --title string` - legt den Titel der Benachrichtigung fest (Standard "Wsh Notify") +- `-s, --silent` - deaktiviert den Benachrichtigungston + +Beispiele: + +```sh +# Einfache Benachrichtigung +wsh notify "Build completed successfully" + +# Benachrichtigung mit benutzerdefiniertem Titel +wsh notify -t "Deployment Status" "Production deployment finished" + +# Stille Benachrichtigung +wsh notify -s "Background task completed" +``` + +Das ist besonders nützlich für lang laufende Befehle, bei denen du über Abschluss oder Statusänderungen benachrichtigt werden möchtest. + +--- + +## conn + +Dieser Befehl hat mehrere Unterbefehle, die verschiedene Funktionen im Zusammenhang mit Verbindungen ausführen. + +### status + +```sh +wsh conn status +``` + +Dieser Befehl liefert den Status aller Verbindungen, die seit dem Start von Waveterm hergestellt wurden. + +### reinstall + +Für SSH-Verbindungen, + +```sh +wsh conn reinstall [user@host] +``` + +Für WSL-Verbindungen, + +```sh +wsh conn reinstall [wsl://] +``` + +Dieser Befehl installiert die Wave-Shell-Erweiterungen auf der angegebenen Verbindung neu. + +### disconnect + +Für SSH-Verbindungen, + +```sh +wsh conn disconnect [user@host] +``` + +Für WSL-Verbindungen, + +```sh +wsh conn disconnect [wsl://] +``` + +Dieser Befehl trennt die angegebene Verbindung vollständig. Das gilt für alle Blöcke, in denen die Verbindung genutzt wird. + +### connect + +Für SSH-Verbindungen, + +```sh +wsh conn connect [user@host] +``` + +Für WSL-Verbindungen, + +```sh +wsh conn connect [wsl://] +``` + +Dieser Befehl stellt eine Verbindung zur angegebenen Verbindung her, erstellt aber keinen Block dafür. + +### ensure + +Für SSH-Verbindungen, + +```sh +wsh conn ensure [user@host] +``` + +Für WSL-Verbindungen, + +```sh +wsh conn ensure [wsl://] +``` + +Dieser Befehl stellt eine Verbindung zur angegebenen Verbindung her, falls sie noch nicht verbunden ist. + +--- + +## setconfig + +```sh +wsh setconfig [=] +``` + +Damit lassen sich verschiedene Optionen in der Datei `config/settings.json` festlegen. Es wird geprüft, ob eine gültige Konfigurationsoption angegeben wurde. + +--- + +## file + +Der Befehl `file` bietet eine Reihe von Unterbefehlen zur Verwaltung von Dateien über verschiedene Speichersysteme hinweg, wie z. B. `wsh`-Remote-Server. + +:::note + +Wave Terminal kann Dateien von entfernten SSH-Hosts verwalten. Dateien werden über URIs adressiert, die +je nach Speichersystem variieren. Ist kein Schema angegeben, wird die Datei als lokale Verbindung behandelt. + +URI-Format: `[profile]:[uri-scheme]://[connection]/[path]` + +Unterstützte URI-Schemata: + +- `wsh` - Wird genutzt, um über SSH per WSH-Helfer auf Dateien auf entfernten Hosts zuzugreifen. Ermöglicht Datei-Streaming zu Wave und anderen entfernten Rechnern. + + Profile sind für WSH-URIs optional, sofern du den entfernten Host in deiner "connections.json" oder "~/.ssh/config"-Datei konfiguriert hast. + + Ist ein Profil angegeben, muss es in "profiles.json" im Wave-Konfigurationsverzeichnis definiert sein. + + Format: `wsh://[remote]/[path]` + + Für den aktuellen Remote-Rechner und deinen lokalen Computer können Kurzformen genutzt werden: + `[path]` ein relativer oder absoluter Pfad auf dem aktuellen Remote-Rechner + `//[remote]/[path]` ein Pfad auf einem entfernten Rechner + `/~/[path]` ein Pfad relativ zum Home-Verzeichnis auf deinem lokalen Computer + +::: + +### cat + +```sh +wsh file cat [file-uri] +``` + +Zeigt den Inhalt einer Datei an (maximale Dateigröße 10MB). Zum Beispiel: + +```sh +wsh file cat wsh://user@ec2/home/user/config.txt +wsh file cat ./local-config.txt +``` + +### write + +```sh +wsh file write [file-uri] +``` + +Schreibt Daten von stdin in eine Datei. Die maximale Dateigröße beträgt 10MB. Zum Beispiel: + +```sh +echo "hello" | wsh file write ./greeting.txt +cat config.json | wsh file write //ec2-user@remote01/~/config.json +``` + +### append + +```sh +wsh file append [file-uri] +``` + +Hängt Daten von stdin an eine Datei an. Die Eingabe wird lokal gepuffert (bis zu einem Gesamtdateigrößenlimit von 10MB), bevor sie geschrieben wird. Zum Beispiel: + +```sh +cat additional-content.txt | wsh file append ./notes.txt +echo "new line" | wsh file append //user@remote/~/notes.txt +``` + +### rm + +```sh +wsh file rm [flag] [file-uri] +``` + +Entfernt eine Datei. Zum Beispiel: + +```sh +wsh file rm wsh://user@ec2/home/user/config.txt +wsh file rm ./local-config.txt +``` + +Flags: + +- `-r, --recursive` - löscht Verzeichniseinträge rekursiv + +### info + +```sh +wsh file info [file-uri] +``` + +Zeigt Informationen über eine Datei an, einschließlich Größe, Erstellungszeit, Änderungszeit und Metadaten. Zum Beispiel: + +```sh +wsh file info wsh://user@ec2/home/user/config.txt +wsh file info ./local-config.txt +``` + +### cp + +```sh +wsh file cp [flags] [source-uri] [destination-uri] +``` + +Kopiert Dateien zwischen verschiedenen Speichersystemen (maximale Dateigröße 10MB). Zum Beispiel: + +```sh +# Eine entfernte Datei in dein lokales Dateisystem kopieren +wsh file cp wsh://user@ec2/home/user/config.txt ./local-config.txt + +# Eine lokale Datei auf ein entferntes System kopieren +wsh file cp ./local-config.txt wsh://user@ec2/home/user/config.txt + +# Zwischen entfernten Systemen kopieren +wsh file cp wsh://user@ec2/home/user/config.txt wsh://user@server2/home/user/backup.txt +``` + +Flags: + +- `-f, --force` - überschreibt beim Kopieren jegliche Konflikte +- `-m, --merge` - löscht beim Kopieren eines Verzeichnisses keine bestehenden Verzeichniseinträge, sondern führt dessen Inhalt mit dem des Ziels zusammen + +### mv + +```sh +wsh file mv [flags] [source-uri] [destination-uri] +``` + +Verschiebt Dateien zwischen verschiedenen Speichersystemen (maximale Dateigröße 10MB). Die Quelldatei wird gelöscht, sobald der Vorgang erfolgreich abgeschlossen ist. Zum Beispiel: + +```sh +# Eine entfernte Datei in dein lokales Dateisystem verschieben +wsh file mv wsh://user@ec2/home/user/config.txt ./local-config.txt + +# Eine lokale Datei auf ein entferntes System verschieben +wsh file mv ./local-config.txt wsh://user@ec2/home/user/config.txt + +# Zwischen entfernten Systemen verschieben +wsh file mv wsh://user@ec2/home/user/config.txt wsh://user@server2/home/user/backup.txt +``` + +Flags: + +- `-f, --force` - überschreibt beim Verschieben jegliche Konflikte + +### ls + +```sh +wsh file ls [flags] [file-uri] +``` + +Listet Dateien in einem Verzeichnis auf. Standardmäßig werden die Dateien im aktuellen Verzeichnis der aktuellen Terminal-Sitzung aufgelistet. + +Beispiele: + +```sh +wsh file ls wsh://user@ec2/home/user/ +wsh file ls ./local-dir/ +``` + +Flags: + +- `-l, --long` - nutzt das lange Listenformat mit Größe, Zeitstempeln und Metadaten +- `-1, --one` - listet eine Datei pro Zeile auf +- `-f, --files` - listet nur Dateien auf (keine Verzeichnisse) + +Wird die Ausgabe an einen anderen Befehl weitergeleitet, wird automatisch auf das Format "eine Datei pro Zeile" umgeschaltet: + +```sh +# Einfach mit grep, awk usw. zu verarbeiten +wsh file ls ./ | grep ".json$" +``` + +--- + +## launch + +Mit dem Befehl `wsh launch` kannst du vorkonfigurierte Widgets direkt aus deinem Terminal öffnen. + +```sh +wsh launch [flags] widget-id +``` + +Der Befehl sucht die angegebene Widget-ID sowohl in benutzerdefinierten als auch in Standard-Widgets und erstellt dann einen neuen Block mit der Konfiguration des Widgets. + +Flags: + +- `-m, --magnify` - öffnet das Widget im vergrößerten Modus und überschreibt die Standard-Vergrößerungseinstellung des Widgets + +Beispiele: + +```sh +# Ein Widget mit seinen Standardeinstellungen starten +wsh launch my-custom-widget + +# Ein Widget im vergrößerten Modus starten +wsh launch -m system-monitor +``` + +Die Konfiguration des Widgets bestimmt die anfänglichen Blockeinstellungen, einschließlich Ansichtstyp, Metadaten und Standard-Vergrößerungszustand. Das Flag `-m` kann genutzt werden, um die Standard-Vergrößerungseinstellung des Widgets zu überschreiben. + +:::tip +Widget-Konfigurationen können in deiner Konfigurationsdatei `widgets.json` angepasst werden, die du mit `wsh editconfig widgets.json` bearbeiten kannst +::: + +--- + +## getvar/setvar + +Wave Terminal bietet Befehle zur Verwaltung dauerhafter Variablen auf verschiedenen Ebenen (Block, Tab, Workspace oder clientweit). + +### setvar + +```sh +wsh setvar [flags] KEY=VALUE... +``` + +Setzt eine oder mehrere Variablen. Standardmäßig werden Variablen auf Client-(globaler) Ebene gesetzt. Nutze `-l` für blocklokale Variablen. + +Beispiele: + +```sh +# Eine einzelne Variable setzen +wsh setvar API_KEY=abc123 + +# Mehrere Variablen gleichzeitig setzen +wsh setvar HOST=localhost PORT=8080 DEBUG=true + +# Eine blocklokale Variable setzen +wsh setvar -l BLOCK_SPECIFIC=value + +# Variablen entfernen +wsh setvar -r API_KEY PORT +``` + +Flags: + +- `-l, --local` - setzt Variablen lokal für den aktuellen Block +- `-r, --remove` - entfernt die angegebenen Variablen, statt sie zu setzen +- `--varfile string` - nutzt eine andere Variablendatei (Standard "var") +- `-b [blockid]` - zum Festlegen einer bestimmten Zone (Block, Tab, Workspace, Client oder UUID) + +### getvar + +```sh +wsh getvar [flags] [key] +``` + +Ruft den Wert einer Variable ab. Gibt den Exit-Code 0 zurück, wenn die Variable existiert, sonst 1. Das ermöglicht Shell-Skripte wie: + +```sh +# Prüfen, ob eine Variable existiert +if wsh getvar API_KEY >/dev/null; then + echo "API key is set" +fi + +# Eine Variable in einem Befehl nutzen +curl -H "Authorization: $(wsh getvar API_KEY)" https://api.example.com + +# Eine blocklokale Variable abrufen +wsh getvar -l BLOCK_SPECIFIC + +# Alle Variablen auflisten +wsh getvar --all + +# Alle Variablen mit Null-Terminatoren auflisten (zum Skripten) +wsh getvar --all -0 +``` + +Flags: + +- `-l, --local` - ruft für den aktuellen Block lokale Variablen ab +- `--all` - listet alle Variablen auf +- `-0, --null` - nutzt in der Ausgabe Null-Terminatoren statt Zeilenumbrüche +- `--varfile string` - nutzt eine andere Variablendatei (Standard "var") + +Auf Variablen kann mit dem Flag `-b` in verschiedenen Geltungsbereichen zugegriffen werden: + +```sh +# Auf Block-Ebene abrufen/setzen +wsh getvar -b block MYVAR +wsh setvar -b block MYVAR=value + +# Auf Tab-Ebene abrufen/setzen +wsh getvar -b tab MYVAR +wsh setvar -b tab MYVAR=value + +# Auf Workspace-Ebene abrufen/setzen +wsh getvar -b workspace MYVAR +wsh setvar -b workspace MYVAR=value + +# Auf Client-(globaler) Ebene abrufen/setzen +wsh getvar -b client MYVAR +wsh setvar -b client MYVAR=value +``` + +Mit diesen Befehlen gesetzte Variablen bleiben über Sitzungen hinweg erhalten und können genutzt werden, um Konfigurationswerte, Secrets oder andere String-Daten zu speichern, auf die über Blöcke oder Tabs hinweg zugegriffen werden muss. + +--- + +## termscrollback + +Ruft den Terminal-Scrollback eines Terminal-Blocks ab. Nützlich, um Terminal-Ausgaben zur Weiterverarbeitung oder Archivierung zu erfassen. + +```sh +wsh termscrollback [-b blockid] [flags] +``` + +Standardmäßig werden alle Zeilen des aktuellen Terminal-Blocks abgerufen. Du kannst Zeilenbereiche angeben oder nur die Ausgabe des letzten Befehls abrufen. + +Flags: + +- `-b, --block ` - gibt den Ziel-Terminal-Block an (Standard: aktueller Block) +- `--start ` - Startzeilennummer (0 = Anfang, Standard: 0) +- `--end ` - Endzeilennummer (0 = alle Zeilen, Standard: 0) +- `--lastcommand` - ruft die Ausgabe des letzten Befehls ab (erfordert Shell-Integration) +- `-o, --output ` - schreibt die Ausgabe in eine Datei statt nach stdout + +Beispiele: + +```sh +# Den gesamten Scrollback des aktuellen Terminals abrufen +wsh termscrollback + +# Scrollback von einem bestimmten Terminal-Block abrufen +wsh termscrollback -b 2 + +# Nur die Ausgabe des letzten Befehls abrufen +wsh termscrollback --lastcommand + +# Einen bestimmten Zeilenbereich abrufen (Zeilen 100-200) +wsh termscrollback --start 100 --end 200 + +# Scrollback in eine Datei speichern +wsh termscrollback -o terminal-log.txt + +# Ausgabe des letzten Befehls in eine Datei speichern +wsh termscrollback --lastcommand -o last-output.txt + +# Ausgabe des letzten Befehls mit grep verarbeiten +wsh termscrollback --lastcommand | grep "ERROR" +``` + +:::note +Das Flag `--lastcommand` erfordert aktivierte Shell-Integration. Mit dieser Funktion kannst du nur die Ausgabe des zuletzt ausgeführten Befehls erfassen, was besonders für Skripte und Automatisierung nützlich ist. +::: + +--- + +## wavepath + +Mit dem Befehl `wavepath` kannst du die Pfade zu verschiedenen Wave-Terminal-Verzeichnissen und -Dateien abrufen, darunter Konfiguration, Datenspeicherung und Logs. + +```sh +wsh wavepath {config|data|log} +``` + +Dieser Befehl liefert den vollständigen Pfad zum angeforderten Wave-Terminal-Systemverzeichnis oder zur Datei. Nützlich für den Zugriff auf Waves Konfigurationsdateien, Datenspeicherung oder zur Prüfung von Logs. + +Flags: + +- `-o, --open` - öffnet den Pfad in einem neuen Block +- `-O, --open-external` - öffnet den Pfad in der Standard-externen Anwendung +- `-t, --tail` - zeigt die letzten ~100 Zeilen der Log-Datei an (nur für den Log-Pfad gültig) + +Beispiele: + +```sh +# Pfad zum Konfigurationsverzeichnis abrufen +wsh wavepath config + +# Pfad zum Datenverzeichnis abrufen +wsh wavepath data + +# Pfad zur Log-Datei abrufen +wsh wavepath log + +# Log-Datei in einem neuen Block öffnen +wsh wavepath -o log + +# Konfigurationsverzeichnis im System-Datei-Explorer öffnen +wsh wavepath -O config + +# Aktuelle Log-Einträge ansehen +wsh wavepath -t log +``` + +Der Befehl zeigt dir den vollständigen Pfad zu: + +- `config` - Wo Wave Terminal seine Konfigurationsdateien speichert +- `data` - Wo Wave Terminal seine dauerhaften Daten speichert +- `log` - Die Haupt-Log-Datei von Wave Terminal + +:::tip +Nutze das Flag `-t` mit dem Log-Pfad, um schnell aktuelle Log-Einträge anzusehen, ohne die gesamte Datei öffnen zu müssen. Das ist besonders nützlich für die Fehlerbehebung. +::: + +--- + +## blocks + +Der Befehl `blocks` bietet Operationen zum Auflisten und Abfragen von Blöcken über Workspaces, Fenster und Tabs hinweg. In erster Linie nützlich zum Debuggen und Skripten. + +### list + +```sh +wsh blocks list [flags] +``` + +Listet alle Blöcke mit optionaler Filterung nach Workspace, Fenster, Tab oder Ansichtstyp auf. Die Ausgabe kann als Tabelle (Standard) oder JSON zum Skripten formatiert werden. + +Flags: +- `--workspace ` - beschränkt auf eine bestimmte Workspace-ID +- `--window ` - beschränkt auf eine bestimmte Fenster-ID +- `--tab ` - beschränkt auf eine bestimmte Tab-ID +- `--view ` - filtert nach Ansichtstyp (term, web, preview, edit, sysinfo, waveai) +- `--json` - gibt die Ergebnisse als JSON aus +- `--timeout ` - RPC-Timeout in Millisekunden (Standard: 5000) + +Beispiele: + +```sh +# Alle Blöcke auflisten +wsh blocks list + +# Nur Terminal-Blöcke auflisten +wsh blocks list --view=term + +# Nach Workspace filtern +wsh blocks list --workspace=12d0c067-378e-454c-872e-77a314248114 + +# Als JSON zum Skripten ausgeben +wsh blocks list --json +``` + + +--- + +## secret + +Der Befehl `secret` bietet sichere Speicherung und Verwaltung sensibler Informationen wie API-Schlüssel, Passwörter und Token. Secrets werden über das native sichere Speicher-Backend deines Systems gespeichert (Keychain unter macOS, Secret Service unter Linux, Credential Manager unter Windows). + +Secret-Namen müssen mit einem Buchstaben beginnen und dürfen nur Buchstaben, Zahlen und Unterstriche enthalten. + +### get + +```sh +wsh secret get [name] +``` + +Ruft den Wert eines gespeicherten Secrets ab und zeigt ihn an. + +Beispiele: + +```sh +# Einen API-Schlüssel abrufen +wsh secret get github_token + +# In Skripten nutzen +export API_KEY=$(wsh secret get my_api_key) +``` + +### set + +```sh +wsh secret set [name]=[value] +``` + +Speichert einen Secret-Wert sicher. Dieser Befehl erfordert einen geeigneten System-Secret-Manager und schlägt fehl, wenn nur einfache Textspeicherung verfügbar ist. + +Beispiele: + +```sh +# Ein API-Token setzen +wsh secret set github_token=ghp_abc123xyz + +# Ein Datenbankpasswort setzen +wsh secret set db_password=mySecurePassword123 +``` + +:::warning +Der Befehl `set` erfordert einen richtigen System-Secret-Manager (Keychain, Secret Service oder Credential Manager). Aus Sicherheitsgründen funktioniert er nicht mit einfacher Textspeicherung. +::: + +### list + +```sh +wsh secret list +``` + +Zeigt alle gespeicherten Secret-Namen an (Werte werden nicht angezeigt). + +Beispiel: + +```sh +# Alle Secrets auflisten +wsh secret list +``` + +### delete + +```sh +wsh secret delete [name] +``` + +Entfernt ein Secret aus dem sicheren Speicher. + +Beispiele: + +```sh +# Einen API-Schlüssel löschen +wsh secret delete github_token + +# Mehrere Secrets löschen +wsh secret delete old_api_key +wsh secret delete temp_token +``` + +### ui + +```sh +wsh secret ui [-m] +``` + +Öffnet die Secrets-Verwaltungsoberfläche in einem neuen Block. Bietet eine grafische Oberfläche zum Anzeigen und Verwalten all deiner Secrets. + +Flags: + +- `-m, --magnified` - öffnet die Secrets-Oberfläche im vergrößerten Modus + +Beispiele: + +```sh +# Die Secrets-Oberfläche öffnen +wsh secret ui + +# Die Secrets-Oberfläche im vergrößerten Modus öffnen +wsh secret ui -m +``` + +Die Secrets-Oberfläche bietet eine bequeme grafische Möglichkeit, Secrets zu durchsuchen, hinzuzufügen, zu bearbeiten und zu löschen, ohne die Kommandozeile nutzen zu müssen. + +:::tip +Nutze Secrets in deinen Skripten, um das Hartkodieren sensibler Werte zu vermeiden. Secrets funktionieren über entfernte Rechner hinweg - speichere einen API-Schlüssel lokal mit `wsh secret set` und greife dann von jeder SSH- oder WSL-Verbindung aus mit `wsh secret get` darauf zu. Das Secret wird sicher von deinem lokalen Rechner abgerufen, ohne es auf entfernten Systemen duplizieren zu müssen. +::: + diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current/wsh.mdx b/docs/i18n/de/docusaurus-plugin-content-docs/current/wsh.mdx new file mode 100644 index 0000000000..5d80d27f38 --- /dev/null +++ b/docs/i18n/de/docusaurus-plugin-content-docs/current/wsh.mdx @@ -0,0 +1,210 @@ +--- +sidebar_position: 4 +id: "wsh" +title: "wsh-Überblick" +--- + +Der Befehl `wsh` stellt die zentrale Kommandozeilen-Schnittstelle von Wave Terminal bereit und ermöglicht es Nutzern, sowohl mit Terminal- als auch mit grafischen Elementen von der Kommandozeile aus zu interagieren. Diese Anleitung behandelt die Grundlagen der Nutzung von `wsh` und seine wichtigsten Funktionen. + +Eine Liste aller wsh-Befehle und ihrer Argumente findest du in der [wsh-Referenz](/wsh-reference). + +## Überblick + +Im Kern ermöglicht `wsh` eine nahtlose Interaktion zwischen deinen Terminal-Befehlen und Waves grafischen Blöcken. Damit kannst du: + +- Grafische Widgets direkt von der Kommandozeile aus steuern +- Daten zwischen Terminal-Sitzungen und GUI-Komponenten teilen +- Deinen Workspace programmatisch verwalten +- Entfernte und lokale Umgebungen verbinden +- CLI-Ausgaben und Dateien direkt an KI-Gespräche senden +- Terminal-Befehle in separaten, isolierten Blöcken ausführen + +## Grundkonzepte + +### Mit Blöcken interagieren + +`wsh` ermöglicht die direkte Interaktion mit Waves grafischen Blöcken über die Kommandozeile. Zum Beispiel: + +```bash +# Eine Datei im Editor öffnen +wsh edit config.json + +# Den aktuellen Dateipfad aus einem Vorschau-Block abrufen +wsh getmeta -b 2 file + +# Ausgabe an einen KI-Assistenten senden (das "-" liest von stdin) +ls -la | wsh ai - "what are the largest files here?" +``` + +### Dauerhafter Status + +`wsh` kann über sein Variablensystem einen Status über Terminal-Sitzungen hinweg beibehalten: + +```bash +# Eine Variable speichern, die über Sitzungen hinweg erhalten bleibt +wsh setvar API_KEY=abc123 + +# Global speichern +wsh setvar DEPLOY_ENV=prod +# Oder im aktuellen Workspace speichern +wsh setvar -b workspace DEPLOY_ENV=staging + +# Gespeicherte Variablen in Befehlen nutzen +curl -H "Authorization: $(wsh getvar API_KEY)" https://api.example.com +``` + +### Auf lokale Dateien von einem entfernten Rechner aus zugreifen + +Wenn du auf entfernten Rechnern arbeitest, kannst du mit dem Pfadpräfix `wsh://local/~/` zusammen mit `wsh file`-Befehlen auf Dateien auf deinem lokalen Computer zugreifen. Die Kurzform `/~/` kann ebenfalls als Alias für `wsh://local/~/` genutzt werden: + +```bash +# Eine lokale Datei von einem entfernten Rechner lesen +wsh file cat wsh://local/~/config/app.json + +# Ein lokales Skript per Shell-Prozesssubstitution auf dem entfernten Rechner ausführen +bash <(wsh file cat wsh://local/~/scripts/deploy.sh) +python <(wsh file cat wsh://local/~/scripts/deploy.py) + +# Entfernte Ausgabe an eine lokale Log-Datei anhängen +echo "Remote machine log entry" | wsh file append wsh://local/~/app.log + +# Eine lokale Datei auf den entfernten Rechner kopieren +wsh file cp wsh://local/~/data.csv ./remote-data.csv + +# Entfernte Datei zurück auf den lokalen Rechner kopieren +wsh file cp ./results.txt wsh://local/~/results.txt + +# Du kannst auch die Kurzform /~/ anstelle von wsh://local/~/ nutzen +wsh file cat /~/config/app.json +``` + +### Blockverwaltung + +Jedes visuelle Element in Wave ist ein Block, und `wsh` gibt dir vollständige Kontrolle darüber (Strg+Umschalt gedrückt halten, um Blocknummern anzuzeigen): + +```bash +# Einen neuen Block erstellen, der eine Webseite zeigt +wsh web open github.com + +# Eine Websuche in einem neuen Block durchführen +wsh web open "wave terminal" + +# Einen Befehl in einem neuen Block ausführen und nach Abschluss automatisch schließen +wsh run -x -- npm test + +# Informationen über den aktuellen Block abrufen +wsh getmeta +``` + +## Häufige Workflows + +Hier sind einige gängige Wege, `wsh` zu nutzen: + +### Entwicklungs-Workflow + +```bash +# Verzeichnis oder Markdown-Dateien öffnen +wsh view . +wsh view README.md + +# -m hinzufügen, um den Block im "vergrößerten" Modus zu öffnen +wsh view -m README.md + +# Entwicklungsserver in einem neuen Block starten (-m vergrößert den Block beim Start) +wsh run -m -- npm run dev + +# Dokumentation in einem Web-Block öffnen +wsh web open http://localhost:3000 +``` + +### Remote-Entwicklung + +```bash +# Mit optionalem Schlüssel zu einem entfernten Server verbinden +wsh ssh -i ~/.ssh/mykey.pem dev@server + +# Entfernte Dateien bearbeiten +wsh edit /etc/nginx/nginx.conf + +# Entfernte Logs überwachen +wsh run -- tail -f /var/log/app.log + +# Variablen zwischen Sitzungen teilen +wsh setvar -b tab SHARED_ENV=staging +``` + +### KI-unterstützte Entwicklung + +Der Befehl `wsh ai` fügt der Wave-AI-Seitenleiste Inhalte hinzu. Standardmäßig werden Dateien angehängt, ohne automatisch abzusenden, sodass du vor dem Senden weiteren Kontext prüfen und hinzufügen kannst. + +```bash +# Ausgabe an die KI-Seitenleiste weiterleiten (Frage in der UI stellen) +git diff | wsh ai - + +# Dateien mit einer Nachricht anhängen +wsh ai main.go utils.go -m "find bugs in these files" + +# Mit Nachricht automatisch absenden +wsh ai config.json -s -m "explain this config" + +# Neuen Chat mit angehängten Dateien starten +wsh ai -n *.log -m "analyze these logs" + +# Mehrere Dateitypen anhängen (Bilder, PDFs, Code) +wsh ai screenshot.png report.pdf app.py -m "review these" + +# Debuggen mit stdin und automatischem Absenden +dmesg | wsh ai -s - -m "help me understand these errors" +``` + +**Flags:** +- `-` - Von stdin statt aus einer Datei lesen +- `-m, --message` - Nachrichtentext zusammen mit Dateien hinzufügen +- `-s, --submit` - Sofort automatisch absenden (Standard ist, auf den Nutzer zu warten) +- `-n, --new` - Chat leeren und neues Gespräch beginnen + +**Dateigrenzen:** +- Textdateien: max. 200KB +- PDFs: max. 5MB +- Bilder: max. 7MB +- Maximal 15 Dateien pro Befehl + +## Tipps & Funktionen + +1. **Mit Blöcken arbeiten** + + - Nutze Blocknummern (1-9), um bestimmte Blöcke innerhalb eines Tabs anzusprechen (Strg+Umschalt gedrückt halten, um Blocknummern anzuzeigen) + - Vollständige Block-IDs erhältst du per Rechtsklick auf die Kopfzeile eines Blocks und Auswahl von "Copy Block Id" (nützlich zum Skripten) + - Nutze Referenzen wie "this", "tab", "workspace" oder "global" für verschiedene Geltungsbereiche + +2. **Datenspeicherung** + + - Nutze `wsh setvar/getvar` für Konfiguration und Secrets + - Speichere Dateidaten mit `wsh file`, die sich leicht in allen Terminals (lokal und entfernt) referenzieren lassen + - Nutze passende Speicher-Geltungsbereiche (Block, Tab, Workspace, global) + +3. **Befehlsausführung** + - Nutze `wsh run`, um Befehle in neuen Blöcken auszuführen + - Sende Befehlsausgaben und Dateien schnell mit `wsh ai` an KI-Blöcke + +## Skripten mit wsh + +wsh-Befehle können in Skripten kombiniert werden, um wiederkehrende Aufgaben zu automatisieren. Hier ist ein Beispiel, das eine Entwicklungsumgebung einrichtet und mit `wsh notify` einen lang laufenden Build überwacht: + +```bash +#!/bin/bash +# Entwicklungsumgebung einrichten +wsh run -- docker-compose up -d +wsh web open localhost:8080 +wsh view ./src +wsh run -- npm run test:watch + +# Mit wsh notify benachrichtigt werden, wenn lang laufende Aufgaben abgeschlossen sind +npm run build && wsh notify "Build complete" || wsh notify "Build failed" +``` + +## Hilfe erhalten + +Du kannst dir Hilfe zu verfügbaren Befehlen anzeigen lassen, indem du `wsh` ohne Argumente ausführst, oder detaillierte Hilfe zu einem bestimmten Befehl mit `wsh [command] -h` erhalten. + +Eine vollständige Referenz aller `wsh`-Funktionen findest du in der [WSH-Befehlsreferenz](./wsh-reference). diff --git a/docs/i18n/de/docusaurus-theme-classic/footer.json b/docs/i18n/de/docusaurus-theme-classic/footer.json new file mode 100644 index 0000000000..4b3aa2b6c6 --- /dev/null +++ b/docs/i18n/de/docusaurus-theme-classic/footer.json @@ -0,0 +1,6 @@ +{ + "copyright": { + "message": "Copyright © 2026 Command Line Inc. Erstellt mit Docusaurus.", + "description": "The footer copyright" + } +} diff --git a/docs/i18n/de/docusaurus-theme-classic/navbar.json b/docs/i18n/de/docusaurus-theme-classic/navbar.json new file mode 100644 index 0000000000..5a82c4d1f7 --- /dev/null +++ b/docs/i18n/de/docusaurus-theme-classic/navbar.json @@ -0,0 +1,14 @@ +{ + "item.label.Docs": { + "message": "Dokumentation", + "description": "Navbar item with label Docs" + }, + "item.label.Storybook": { + "message": "Storybook", + "description": "Navbar item with label Storybook" + }, + "logo.alt": { + "message": "Wave Terminal Logo", + "description": "The alt text of navbar logo" + } +} diff --git a/emain/emain-i18n.ts b/emain/emain-i18n.ts new file mode 100644 index 0000000000..9afc067f67 --- /dev/null +++ b/emain/emain-i18n.ts @@ -0,0 +1,30 @@ +// Copyright 2026, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +// The Electron main process builds the native app menu, which runs outside +// the renderer's react-i18next context, so it needs its own small i18next +// instance sharing the same locale resources. + +import i18next, { TFunction } from "i18next"; +import de from "@/util/i18n/locales/de.json"; +import en from "@/util/i18n/locales/en.json"; + +const menuI18n = i18next.createInstance(); +menuI18n.init({ + resources: { + en: { translation: en }, + de: { translation: de }, + }, + lng: "en", + fallbackLng: "en", + interpolation: { escapeValue: false }, + initImmediate: false, +}); + +export function getMenuT(language: string): TFunction { + const lang = language === "de" ? "de" : "en"; + if (menuI18n.language !== lang) { + menuI18n.changeLanguage(lang); + } + return menuI18n.t.bind(menuI18n); +} diff --git a/emain/emain-menu.ts b/emain/emain-menu.ts index 1bdf6a7139..64c3c36526 100644 --- a/emain/emain-menu.ts +++ b/emain/emain-menu.ts @@ -4,8 +4,10 @@ import { waveEventSubscribeSingle } from "@/app/store/wps"; import { RpcApi } from "@/app/store/wshclientapi"; import * as electron from "electron"; +import type { TFunction } from "i18next"; import { fireAndForget } from "../frontend/util/util"; import { focusedBuilderWindow, getBuilderWindowById } from "./emain-builder"; +import { getMenuT } from "./emain-i18n"; import { openBuilderWindow } from "./emain-ipc"; import { isDev, unamePlatform } from "./emain-platform"; import { clearTabCache } from "./emain-tabview"; @@ -45,11 +47,14 @@ function getWindowWebContents(window: electron.BaseWindow): electron.WebContents return null; } -async function getWorkspaceMenu(ww?: WaveBrowserWindow): Promise { +async function getWorkspaceMenu( + t: TFunction, + ww?: WaveBrowserWindow +): Promise { const workspaceList = await RpcApi.WorkspaceListCommand(ElectronWshClient); const workspaceMenu: Electron.MenuItemConstructorOptions[] = [ { - label: "Create Workspace", + label: t("appMenu.createWorkspace"), click: (_, window) => fireAndForget(() => createWorkspace((window as WaveBrowserWindow) ?? ww)), }, ]; @@ -75,7 +80,7 @@ async function getWorkspaceMenu(ww?: WaveBrowserWindow): Promise fireAndForget(callbacks.createNewWaveWindow), }, { role: "close", + label: t("appMenu.closeWindow"), accelerator: "", click: () => { focusedWaveWindow?.close(); @@ -147,7 +162,7 @@ function makeFileMenu( const featureWaveAppBuilder = fullConfig?.settings?.["feature:waveappbuilder"]; if (isDev || featureWaveAppBuilder) { fileMenu.splice(1, 0, { - label: "New WaveApp Builder Window", + label: t("appMenu.newWaveAppBuilderWindow"), accelerator: unamePlatform === "darwin" ? "Command+Shift+B" : "Alt+Shift+B", click: () => openBuilderWindow(""), }); @@ -171,16 +186,16 @@ function makeFileMenu( return fileMenu; } -function makeAppMenuItems(webContents: electron.WebContents): Electron.MenuItemConstructorOptions[] { +function makeAppMenuItems(t: TFunction, webContents: electron.WebContents): Electron.MenuItemConstructorOptions[] { const appMenuItems: Electron.MenuItemConstructorOptions[] = [ { - label: "About Wave Terminal", + label: t("appMenu.aboutWaveTerminal"), click: (_, window) => { (getWindowWebContents(window) ?? webContents)?.send("menu-item-about"); }, }, { - label: "Check for Updates", + label: t("appMenu.checkForUpdates"), click: () => { fireAndForget(() => updater?.checkForUpdates(true)); }, @@ -189,18 +204,19 @@ function makeAppMenuItems(webContents: electron.WebContents): Electron.MenuItemC ]; if (unamePlatform === "darwin") { appMenuItems.push( - { role: "services" }, + { role: "services", label: t("appMenu.services") }, { type: "separator" }, - { role: "hide" }, - { role: "hideOthers" }, + { role: "hide", label: t("appMenu.hide") }, + { role: "hideOthers", label: t("appMenu.hideOthers") }, { type: "separator" } ); } - appMenuItems.push({ role: "quit" }); + appMenuItems.push({ role: "quit", label: t("appMenu.quit") }); return appMenuItems; } function makeViewMenu( + t: TFunction, webContents: electron.WebContents, callbacks: AppMenuCallbacks, isBuilderWindowFocused: boolean, @@ -209,22 +225,22 @@ function makeViewMenu( const devToolsAccel = unamePlatform === "darwin" ? "Option+Command+I" : "Alt+Shift+I"; return [ { - label: isBuilderWindowFocused ? "Reload Window" : "Reload Tab", + label: isBuilderWindowFocused ? t("appMenu.reloadWindow") : t("appMenu.reloadTab"), accelerator: "Shift+CommandOrControl+R", click: (_, window) => { (getWindowWebContents(window) ?? webContents)?.reloadIgnoringCache(); }, }, { - label: "Relaunch All Windows", + label: t("appMenu.relaunchAllWindows"), click: () => callbacks.relaunchBrowserWindows(), }, { - label: "Clear Tab Cache", + label: t("appMenu.clearTabCache"), click: () => clearTabCache(), }, { - label: "Toggle DevTools", + label: t("appMenu.toggleDevTools"), accelerator: devToolsAccel, click: (_, window) => { const wc = getWindowWebContents(window) ?? webContents; @@ -233,7 +249,7 @@ function makeViewMenu( }, { type: "separator" }, { - label: "Reset Zoom", + label: t("appMenu.resetZoom"), accelerator: "CommandOrControl+0", click: (_, window) => { const wc = getWindowWebContents(window) ?? webContents; @@ -243,7 +259,7 @@ function makeViewMenu( }, }, { - label: "Zoom In", + label: t("appMenu.zoomIn"), accelerator: "CommandOrControl+=", click: (_, window) => { const wc = getWindowWebContents(window) ?? webContents; @@ -265,7 +281,7 @@ function makeViewMenu( acceleratorWorksWhenHidden: true, }, { - label: "Zoom Out", + label: t("appMenu.zoomOut"), accelerator: "CommandOrControl+-", click: (_, window) => { const wc = getWindowWebContents(window) ?? webContents; @@ -287,10 +303,10 @@ function makeViewMenu( acceleratorWorksWhenHidden: true, }, { - label: "Launch On Full Screen", + label: t("appMenu.launchOnFullScreen"), submenu: [ { - label: "On", + label: t("term.on"), type: "radio", checked: fullscreenOnLaunch, click: () => { @@ -298,7 +314,7 @@ function makeViewMenu( }, }, { - label: "Off", + label: t("term.off"), type: "radio", checked: !fullscreenOnLaunch, click: () => { @@ -310,10 +326,11 @@ function makeViewMenu( { type: "separator" }, { role: "togglefullscreen", + label: t("appMenu.toggleFullScreen"), }, { type: "separator" }, { - label: "Toggle Widgets Bar", + label: t("appMenu.toggleWidgetsBar"), click: () => { fireAndForget(async () => { const workspaceId = focusedWaveWindow?.workspaceId; @@ -331,7 +348,6 @@ function makeViewMenu( async function makeFullAppMenu(callbacks: AppMenuCallbacks, workspaceOrBuilderId?: string): Promise { const numWaveWindows = getAllWaveWindows().length; const webContents = workspaceOrBuilderId && getWebContentsByWorkspaceOrBuilderId(workspaceOrBuilderId); - const appMenuItems = makeAppMenuItems(webContents); const isBuilderWindowFocused = focusedBuilderWindow != null; let fullscreenOnLaunch = false; @@ -342,36 +358,39 @@ async function makeFullAppMenu(callbacks: AppMenuCallbacks, workspaceOrBuilderId } catch (e) { console.error("Error fetching config:", e); } - const editMenu = makeEditMenu(fullConfig); - const fileMenu = makeFileMenu(numWaveWindows, callbacks, fullConfig); - const viewMenu = makeViewMenu(webContents, callbacks, isBuilderWindowFocused, fullscreenOnLaunch); + const t = getMenuT(fullConfig?.settings?.["app:language"]); + const appMenuItems = makeAppMenuItems(t, webContents); + const editMenu = makeEditMenu(t, fullConfig); + const fileMenu = makeFileMenu(t, numWaveWindows, callbacks, fullConfig); + const viewMenu = makeViewMenu(t, webContents, callbacks, isBuilderWindowFocused, fullscreenOnLaunch); let workspaceMenu: Electron.MenuItemConstructorOptions[] = null; try { - workspaceMenu = await getWorkspaceMenu(); + workspaceMenu = await getWorkspaceMenu(t); } catch (e) { console.error("getWorkspaceMenu error:", e); } const windowMenu: Electron.MenuItemConstructorOptions[] = [ - { role: "minimize", accelerator: "" }, - { role: "zoom" }, + { role: "minimize", label: t("appMenu.minimize"), accelerator: "" }, + { role: "zoom", label: t("appMenu.zoomWindow") }, { type: "separator" }, - { role: "front" }, + { role: "front", label: t("appMenu.bringAllToFront") }, ]; const menuTemplate: Electron.MenuItemConstructorOptions[] = [ { role: "appMenu", submenu: appMenuItems }, - { role: "fileMenu", submenu: fileMenu }, - { role: "editMenu", submenu: editMenu }, - { role: "viewMenu", submenu: viewMenu }, + { role: "fileMenu", label: t("appMenu.fileMenu"), submenu: fileMenu }, + { role: "editMenu", label: t("appMenu.editMenu"), submenu: editMenu }, + { role: "viewMenu", label: t("appMenu.viewMenu"), submenu: viewMenu }, ]; if (workspaceMenu != null && !isBuilderWindowFocused) { menuTemplate.push({ - label: "Workspace", + label: t("appMenu.workspace"), id: "workspace-menu", submenu: workspaceMenu, }); } menuTemplate.push({ role: "windowMenu", + label: t("appMenu.windowMenu"), submenu: windowMenu, }); return electron.Menu.buildFromTemplate(menuTemplate); diff --git a/frontend/app/aipanel/aifeedbackbuttons.tsx b/frontend/app/aipanel/aifeedbackbuttons.tsx index 30d9accc07..59fa660bdb 100644 --- a/frontend/app/aipanel/aifeedbackbuttons.tsx +++ b/frontend/app/aipanel/aifeedbackbuttons.tsx @@ -3,6 +3,7 @@ import { cn, makeIconClass } from "@/util/util"; import { memo, useState } from "react"; +import { useTranslation } from "react-i18next"; import { WaveAIModel } from "./waveai-model"; interface AIFeedbackButtonsProps { @@ -10,6 +11,7 @@ interface AIFeedbackButtonsProps { } export const AIFeedbackButtons = memo(({ messageText }: AIFeedbackButtonsProps) => { + const { t } = useTranslation(); const [thumbsUpClicked, setThumbsUpClicked] = useState(false); const [thumbsDownClicked, setThumbsDownClicked] = useState(false); const [copied, setCopied] = useState(false); @@ -50,7 +52,7 @@ export const AIFeedbackButtons = memo(({ messageText }: AIFeedbackButtonsProps) ? "text-accent" : "text-secondary hover:bg-zinc-700 hover:text-primary" )} - title="Good Response" + title={t("aiPanel.goodResponse")} > @@ -62,7 +64,7 @@ export const AIFeedbackButtons = memo(({ messageText }: AIFeedbackButtonsProps) ? "text-accent" : "text-secondary hover:bg-zinc-700 hover:text-primary" )} - title="Bad Response" + title={t("aiPanel.badResponse")} > @@ -75,7 +77,7 @@ export const AIFeedbackButtons = memo(({ messageText }: AIFeedbackButtonsProps) ? "text-success" : "text-secondary hover:bg-zinc-700 hover:text-primary" )} - title="Copy Message" + title={t("aiPanel.copyMessage")} > diff --git a/frontend/app/aipanel/aimessage.tsx b/frontend/app/aipanel/aimessage.tsx index 1bfadd121d..885d37a155 100644 --- a/frontend/app/aipanel/aimessage.tsx +++ b/frontend/app/aipanel/aimessage.tsx @@ -4,6 +4,7 @@ import { WaveStreamdown } from "@/app/element/streamdown"; import { cn } from "@/util/util"; import { memo, useEffect, useRef } from "react"; +import { useTranslation } from "react-i18next"; import { getFileIcon } from "./ai-utils"; import { AIFeedbackButtons } from "./aifeedbackbuttons"; import { AIToolUseGroup } from "./aitooluse"; @@ -12,7 +13,7 @@ import { WaveAIModel } from "./waveai-model"; const AIThinking = memo( ({ - message = "AI is thinking...", + message, reasoningText, isWaitingApproval = false, }: { @@ -20,6 +21,8 @@ const AIThinking = memo( reasoningText?: string; isWaitingApproval?: boolean; }) => { + const { t } = useTranslation(); + const displayMessage = message ?? t("aiPanel.aiThinking"); const scrollRef = useRef(null); useEffect(() => { @@ -47,7 +50,7 @@ const AIThinking = memo(
)} - {message && {message}} + {displayMessage && {displayMessage}}
{displayText} @@ -64,6 +67,7 @@ interface UserMessageFilesProps { } const UserMessageFiles = memo(({ fileParts }: UserMessageFilesProps) => { + const { t } = useTranslation(); if (fileParts.length === 0) return null; return ( @@ -76,7 +80,7 @@ const UserMessageFiles = memo(({ fileParts }: UserMessageFilesProps) => { {file.data?.previewurl ? ( {file.data?.filename ) : ( @@ -90,9 +94,9 @@ const UserMessageFiles = memo(({ fileParts }: UserMessageFilesProps) => {
- {file.data?.filename || "File"} + {file.data?.filename || t("aiPanel.file")}
@@ -179,7 +183,8 @@ const groupMessageParts = (parts: WaveUIMessagePart[]): MessagePart[] => { const getThinkingMessage = ( parts: WaveUIMessagePart[], isStreaming: boolean, - role: string + role: string, + t: (key: string) => string ): { message: string; reasoningText?: string; isWaitingApproval?: boolean } | null => { if (!isStreaming || role !== "assistant") { return null; @@ -190,14 +195,14 @@ const getThinkingMessage = ( ); if (hasPendingApprovals) { - return { message: "Waiting for Tool Approvals...", isWaitingApproval: true }; + return { message: t("aiPanel.waitingForToolApprovals"), isWaitingApproval: true }; } const lastPart = parts[parts.length - 1]; if (lastPart?.type === "reasoning") { const reasoningContent = lastPart.text || ""; - return { message: "AI is thinking...", reasoningText: reasoningContent }; + return { message: t("aiPanel.aiThinking"), reasoningText: reasoningContent }; } if (lastPart?.type === "text" && lastPart.text) { @@ -208,13 +213,14 @@ const getThinkingMessage = ( }; export const AIMessage = memo(({ message, isStreaming }: AIMessageProps) => { + const { t } = useTranslation(); const parts = message.parts || []; const displayParts = parts.filter(isDisplayPart); const fileParts = parts.filter( (part): part is WaveUIMessagePart & { type: "data-userfile" } => part.type === "data-userfile" ); - const thinkingData = getThinkingMessage(parts, isStreaming, message.role); + const thinkingData = getThinkingMessage(parts, isStreaming, message.role, t); const groupedParts = groupMessageParts(displayParts); return ( @@ -228,7 +234,7 @@ export const AIMessage = memo(({ message, isStreaming }: AIMessageProps) => { )} > {displayParts.length === 0 && !isStreaming && !thinkingData ? ( -
(no text content)
+
{t("aiPanel.noTextContent")}
) : ( <> {groupedParts.map((group, index: number) => diff --git a/frontend/app/aipanel/aimode.tsx b/frontend/app/aipanel/aimode.tsx index 3602cdd360..5d20597141 100644 --- a/frontend/app/aipanel/aimode.tsx +++ b/frontend/app/aipanel/aimode.tsx @@ -8,6 +8,8 @@ import { TabRpcClient } from "@/app/store/wshrpcutil"; import { cn, fireAndForget, makeIconClass } from "@/util/util"; import { useAtomValue } from "jotai"; import { memo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import type { TFunction } from "i18next"; import { getFilteredAIModeConfigs, getModeDisplayName } from "./ai-utils"; import { WaveAIModel } from "./waveai-model"; @@ -22,6 +24,7 @@ interface AIModeMenuItemProps { } const AIModeMenuItem = memo(({ config, isSelected, isDisabled, isPremiumDisabled, onClick, isFirst, isLast }: AIModeMenuItemProps) => { + const { t } = useTranslation(); return (
)} @@ -310,14 +310,14 @@ export const AIModeDropdown = memo(({ compatibilityMode = false }: AIModeDropdow className="w-full flex items-center gap-2 px-3 pt-1 pb-1 text-gray-300 hover:bg-zinc-700 cursor-pointer transition-colors text-left" > - New Chat + {t("aiMode.newChat")} diff --git a/frontend/app/aipanel/aipanel-contextmenu.ts b/frontend/app/aipanel/aipanel-contextmenu.ts index 4e78389198..b768da91d7 100644 --- a/frontend/app/aipanel/aipanel-contextmenu.ts +++ b/frontend/app/aipanel/aipanel-contextmenu.ts @@ -7,6 +7,7 @@ import { isDev } from "@/app/store/global"; import { globalStore } from "@/app/store/jotaiStore"; import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; +import i18n from "@/util/i18n/i18n"; import { WaveAIModel } from "./waveai-model"; export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boolean): Promise { @@ -27,7 +28,7 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo } menu.push({ - label: "New Chat", + label: i18n.t("aiMode.newChat"), click: () => { model.clearChat(); }, @@ -47,7 +48,7 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo if (model.inBuilder) { maxTokensSubmenu.push( { - label: "24k", + label: i18n.t("aiMode.tokens24k"), type: "checkbox", checked: currentMaxTokens === 24576, click: () => { @@ -58,7 +59,7 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo }, }, { - label: "64k (Pro)", + label: i18n.t("aiMode.tokens64kPro"), type: "checkbox", checked: currentMaxTokens === 65536, click: () => { @@ -72,7 +73,7 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo } else { if (isDev()) { maxTokensSubmenu.push({ - label: "1k (Dev Testing)", + label: i18n.t("aiMode.tokens1kDevTesting"), type: "checkbox", checked: currentMaxTokens === 1024, click: () => { @@ -85,7 +86,7 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo } maxTokensSubmenu.push( { - label: "4k", + label: i18n.t("aiMode.tokens4k"), type: "checkbox", checked: currentMaxTokens === 4096, click: () => { @@ -96,7 +97,7 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo }, }, { - label: "16k (Pro)", + label: i18n.t("aiMode.tokens16kPro"), type: "checkbox", checked: currentMaxTokens === 16384, click: () => { @@ -107,7 +108,7 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo }, }, { - label: "64k (Pro)", + label: i18n.t("aiMode.tokens64kPro"), type: "checkbox", checked: currentMaxTokens === 65536, click: () => { @@ -121,14 +122,14 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo } menu.push({ - label: "Max Output Tokens", + label: i18n.t("aiMode.maxOutputTokens"), submenu: maxTokensSubmenu, }); menu.push({ type: "separator" }); menu.push({ - label: "Configure Modes", + label: i18n.t("aiMode.configureModes"), click: () => { RpcApi.RecordTEventCommand( TabRpcClient, @@ -148,7 +149,7 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo menu.push({ type: "separator" }); menu.push({ - label: "Hide Wave AI", + label: i18n.t("aiMode.hideWaveAi"), click: () => { model.closeWaveAIPanel(); }, diff --git a/frontend/app/aipanel/aipanel.tsx b/frontend/app/aipanel/aipanel.tsx index 32b8582141..7717e65f51 100644 --- a/frontend/app/aipanel/aipanel.tsx +++ b/frontend/app/aipanel/aipanel.tsx @@ -18,6 +18,7 @@ import { DefaultChatTransport } from "ai"; import * as jotai from "jotai"; import { memo, useCallback, useEffect, useRef, useState } from "react"; import { useDrop } from "react-dnd"; +import { useTranslation } from "react-i18next"; import { formatFileSizeError, isAcceptableFile, validateFileSize } from "./ai-utils"; import { AIDroppedFiles } from "./aidroppedfiles"; import { AIModeDropdown } from "./aimode"; @@ -55,6 +56,7 @@ const AIBlockMask = memo(() => { AIBlockMask.displayName = "AIBlockMask"; const AIDragOverlay = memo(() => { + const { t } = useTranslation(); return (
{ >
-
Drop files here
-
Images, PDFs, and text/code files supported
+
{t("aiPanel.dropFilesHere")}
+
{t("aiPanel.dropFilesSupported")}
); @@ -87,6 +89,7 @@ const KeyCap = memo(({ children, className }: { children: React.ReactNode; class KeyCap.displayName = "KeyCap"; const AIWelcomeMessage = memo(() => { + const { t } = useTranslation(); const modKey = isMacOS() ? "⌘" : "Alt"; const aiModeConfigs = jotai.useAtomValue(atoms.waveaiModeConfigAtom); const hasCustomModes = Object.keys(aiModeConfigs).some((key) => !key.startsWith("waveai@")); @@ -94,31 +97,28 @@ const AIWelcomeMessage = memo(() => {
-

Welcome to Wave AI

+

{t("aiPanel.welcomeTitle")}

-

- Wave AI is your terminal assistant with context. I can read your terminal output, analyze widgets, - access files, and help you solve problems faster. -

+

{t("aiPanel.welcomeIntro")}

-
Getting Started:
+
{t("aiPanel.gettingStarted")}
{!hasCustomModes && } -
- BETA: Free to use. Daily limits keep our costs in check. -
+
{t("aiPanel.betaNotice")}
); @@ -184,16 +182,15 @@ const AIWelcomeMessage = memo(() => { AIWelcomeMessage.displayName = "AIWelcomeMessage"; const AIBuilderWelcomeMessage = memo(() => { + const { t } = useTranslation(); return (
-

WaveApp Builder

+

{t("aiPanel.builderWelcomeTitle")}

-

- The WaveApp builder helps create wave widgets that integrate seamlessly into Wave Terminal. -

+

{t("aiPanel.builderWelcomeIntro")}

); @@ -202,6 +199,7 @@ const AIBuilderWelcomeMessage = memo(() => { AIBuilderWelcomeMessage.displayName = "AIBuilderWelcomeMessage"; const AIErrorMessage = memo(() => { + const { t } = useTranslation(); const model = WaveAIModel.getInstance(); const errorMessage = jotai.useAtomValue(model.errorMessage); @@ -214,7 +212,7 @@ const AIErrorMessage = memo(() => { @@ -224,7 +222,7 @@ const AIErrorMessage = memo(() => { onClick={() => model.clearChat()} className="ml-2 text-xs text-red-300 hover:text-red-200 cursor-pointer underline" > - New Chat + {t("aiPanel.newChat")} @@ -252,6 +250,7 @@ type AIPanelComponentInnerProps = { }; const AIPanelComponentInner = memo(({ roundTopLeft }: AIPanelComponentInnerProps) => { + const { t } = useTranslation(); const [isDragOver, setIsDragOver] = useState(false); const [isReactDndDragOver, setIsReactDndDragOver] = useState(false); const [initialLoadDone, setInitialLoadDone] = useState(false); @@ -295,7 +294,7 @@ const AIPanelComponentInner = memo(({ roundTopLeft }: AIPanelComponentInnerProps }), onError: (error) => { console.error("AI Chat error:", error); - model.setError(error.message || "An error occurred"); + model.setError(error.message || t("aiPanel.errorOccurred")); }, }); @@ -466,9 +465,7 @@ const AIPanelComponentInner = memo(({ roundTopLeft }: AIPanelComponentInnerProps const rejectedCount = files.length - acceptableFiles.length; const rejectedFiles = files.filter((f) => !isAcceptableFile(f)); const fileNames = rejectedFiles.map((f) => f.name).join(", "); - model.setError( - `${rejectedCount} file${rejectedCount > 1 ? "s" : ""} rejected (unsupported type): ${fileNames}. Supported: images, PDFs, and text/code files.` - ); + model.setError(t("aiPanel.filesRejected", { count: rejectedCount, fileNames })); } }; diff --git a/frontend/app/aipanel/aipanelheader.tsx b/frontend/app/aipanel/aipanelheader.tsx index da54f6c9e9..f089c1fe66 100644 --- a/frontend/app/aipanel/aipanelheader.tsx +++ b/frontend/app/aipanel/aipanelheader.tsx @@ -4,9 +4,11 @@ import { handleWaveAIContextMenu } from "@/app/aipanel/aipanel-contextmenu"; import { useAtomValue } from "jotai"; import { memo } from "react"; +import { useTranslation } from "react-i18next"; import { WaveAIModel } from "./waveai-model"; export const AIPanelHeader = memo(() => { + const { t } = useTranslation(); const model = WaveAIModel.getInstance(); const widgetAccess = useAtomValue(model.widgetAccessAtom); const inBuilder = model.inBuilder; @@ -32,8 +34,8 @@ export const AIPanelHeader = memo(() => {
{!inBuilder && (
- Context - Widget Context + {t("aiPanel.context")} + {t("aiPanel.widgetContext")}
@@ -65,7 +67,7 @@ export const AIPanelHeader = memo(() => { diff --git a/frontend/app/aipanel/aipanelinput.tsx b/frontend/app/aipanel/aipanelinput.tsx index ec52ca0d13..e0ae9d8258 100644 --- a/frontend/app/aipanel/aipanelinput.tsx +++ b/frontend/app/aipanel/aipanelinput.tsx @@ -8,6 +8,7 @@ import { Tooltip } from "@/element/tooltip"; import { cn } from "@/util/util"; import { useAtom, useAtomValue } from "jotai"; import { memo, useCallback, useEffect, useRef } from "react"; +import { useTranslation } from "react-i18next"; interface AIPanelInputProps { onSubmit: (e: React.FormEvent) => void; @@ -22,6 +23,7 @@ export interface AIPanelInputRef { } export const AIPanelInput = memo(({ onSubmit, status, model }: AIPanelInputProps) => { + const { t } = useTranslation(); const [input, setInput] = useAtom(model.inputAtom); const isFocused = useAtomValue(model.isWaveAIFocusedAtom); const isChatEmpty = useAtomValue(model.isChatEmptyAtom); @@ -31,11 +33,11 @@ export const AIPanelInput = memo(({ onSubmit, status, model }: AIPanelInputProps let placeholder: string; if (!isChatEmpty) { - placeholder = "Continue..."; + placeholder = t("aiPanel.continuePlaceholder"); } else if (model.inBuilder) { - placeholder = "What would you like to build..."; + placeholder = t("aiPanel.builderPlaceholder"); } else { - placeholder = "Ask Wave AI anything..."; + placeholder = t("aiPanel.askPlaceholder"); } const resizeTextarea = useCallback(() => { @@ -158,7 +160,7 @@ export const AIPanelInput = memo(({ onSubmit, status, model }: AIPanelInputProps style={{ fontSize: "13px" }} rows={2} /> - + ) : ( - +
@@ -41,12 +44,12 @@ const GetMoreButton = memo(({ variant, showClose = true }: { variant: "yellow" | GetMoreButton.displayName = "GetMoreButton"; -function formatTimeRemaining(expirationEpoch: number): string { +function formatTimeRemaining(expirationEpoch: number, t: TFunction): string { const now = Math.floor(Date.now() / 1000); const secondsRemaining = expirationEpoch - now; if (secondsRemaining <= 0) { - return "soon"; + return t("aiPanel.soon"); } const hours = Math.floor(secondsRemaining / 3600); @@ -59,6 +62,7 @@ function formatTimeRemaining(expirationEpoch: number): string { } const AIRateLimitStripComponent = memo(() => { + const { t } = useTranslation(); let rateLimitInfo = jotai.useAtomValue(atoms.waveAIRateLimitInfoAtom); // rateLimitInfo = { req: 0, reqlimit: 200, preq: 0, preqlimit: 50, resetepoch: 1759374575 + 45 * 60 }; // testing const [, forceUpdate] = useState({}); @@ -82,7 +86,7 @@ const AIRateLimitStripComponent = memo(() => { } const { req, reqlimit, preq, preqlimit, resetepoch } = rateLimitInfo; - const timeRemaining = formatTimeRemaining(resetepoch); + const timeRemaining = formatTimeRemaining(resetepoch, t); const totalLimit = preqlimit + reqlimit; if (preq > 0 && preq <= 5) { @@ -90,11 +94,9 @@ const AIRateLimitStripComponent = memo(() => {
- - {preqlimit - preq}/{preqlimit} Premium Used - + {t("aiPanel.premiumUsed", { used: preqlimit - preq, limit: preqlimit })}
- Resets in {timeRemaining} + {t("aiPanel.resetsIn", { time: timeRemaining })}
@@ -106,13 +108,11 @@ const AIRateLimitStripComponent = memo(() => {
- - {preqlimit}/{preqlimit} Premium - + {t("aiPanel.premiumOf", { limit: preqlimit })} - Now on Basic + {t("aiPanel.nowOnBasic")}
- Resets in {timeRemaining} + {t("aiPanel.resetsIn", { time: timeRemaining })}
@@ -124,13 +124,11 @@ const AIRateLimitStripComponent = memo(() => {
- - {totalLimit}/{totalLimit} Reqs - + {t("aiPanel.reqsOf", { limit: totalLimit })} - Limit Reached + {t("aiPanel.limitReached")}
- Resets in {timeRemaining} + {t("aiPanel.resetsIn", { time: timeRemaining })}
diff --git a/frontend/app/aipanel/aitooluse.tsx b/frontend/app/aipanel/aitooluse.tsx index 7868c188e9..efda2bdeb6 100644 --- a/frontend/app/aipanel/aitooluse.tsx +++ b/frontend/app/aipanel/aitooluse.tsx @@ -7,6 +7,7 @@ import { recordTEvent } from "@/app/store/global"; import { cn, fireAndForget } from "@/util/util"; import { useAtomValue } from "jotai"; import { memo, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import { WaveUIMessagePart } from "./aitypes"; import { RestoreBackupModal } from "./restorebackupmodal"; import { WaveAIModel } from "./waveai-model"; @@ -88,8 +89,9 @@ interface AIToolApprovalButtonsProps { } const AIToolApprovalButtons = memo(({ count, onApprove, onDeny }: AIToolApprovalButtonsProps) => { - const approveText = count > 1 ? `Approve All (${count})` : "Approve"; - const denyText = count > 1 ? "Deny All" : "Deny"; + const { t } = useTranslation(); + const approveText = count > 1 ? t("aiToolUse.approveAll", { count }) : t("aiToolUse.approve"); + const denyText = count > 1 ? t("aiToolUse.denyAll") : t("aiToolUse.deny"); return (
@@ -117,6 +119,7 @@ interface AIToolUseBatchItemProps { } const AIToolUseBatchItem = memo(({ part, effectiveApproval }: AIToolUseBatchItemProps) => { + const { t } = useTranslation(); const statusIcon = part.data.status === "completed" ? "✓" : part.data.status === "error" ? "✗" : "•"; const statusColor = part.data.status === "completed" @@ -124,7 +127,8 @@ const AIToolUseBatchItem = memo(({ part, effectiveApproval }: AIToolUseBatchItem : part.data.status === "error" ? "text-error" : "text-gray-400"; - const effectiveErrorMessage = part.data.errormessage || (effectiveApproval === "timeout" ? "Not approved" : null); + const effectiveErrorMessage = + part.data.errormessage || (effectiveApproval === "timeout" ? t("aiToolUse.notApproved") : null); return (
@@ -145,6 +149,7 @@ interface AIToolUseBatchProps { } const AIToolUseBatch = memo(({ parts, isStreaming }: AIToolUseBatchProps) => { + const { t } = useTranslation(); const [userApprovalOverride, setUserApprovalOverride] = useState(null); const firstTool = parts[0].data; @@ -168,7 +173,7 @@ const AIToolUseBatch = memo(({ parts, isStreaming }: AIToolUseBatchProps) => { return (
-
Reading Files
+
{t("aiToolUse.readingFiles")}
{parts.map((part, idx) => ( @@ -190,6 +195,7 @@ interface AIToolUseProps { } const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => { + const { t } = useTranslation(); const toolData = part.data; const [userApprovalOverride, setUserApprovalOverride] = useState(null); const model = WaveAIModel.getInstance(); @@ -286,9 +292,9 @@ const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => { model.openRestoreBackupModal(toolData.toolcallid); }} className="flex-shrink-0 px-1.5 py-0.5 border border-zinc-600 hover:border-zinc-500 hover:bg-zinc-700 rounded cursor-pointer transition-colors flex items-center gap-1 text-zinc-400" - title="Restore backup file" + title={t("aiToolUse.restoreBackupFile")} > - Revert File + {t("aiToolUse.revertFile")} )} @@ -296,16 +302,16 @@ const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => { )}
{toolData.tooldesc && } {(toolData.errormessage || effectiveApproval === "timeout") && ( -
{toolData.errormessage || "Not approved"}
+
{toolData.errormessage || t("aiToolUse.notApproved")}
)} {effectiveApproval === "needs-approval" && (
diff --git a/frontend/app/aipanel/byokannouncement.tsx b/frontend/app/aipanel/byokannouncement.tsx index 935cc4a3b0..7938a621ec 100644 --- a/frontend/app/aipanel/byokannouncement.tsx +++ b/frontend/app/aipanel/byokannouncement.tsx @@ -3,9 +3,11 @@ import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; +import { useTranslation } from "react-i18next"; import { WaveAIModel } from "./waveai-model"; const BYOKAnnouncement = () => { + const { t } = useTranslation(); const model = WaveAIModel.getInstance(); const handleOpenConfig = async () => { @@ -40,17 +42,14 @@ const BYOKAnnouncement = () => {
-
New: BYOK & Local AI Support
-
- Wave AI now supports bring-your-own-key (BYOK) with OpenAI, Google Gemini, Azure, and - OpenRouter, plus local models via Ollama, LM Studio, and other OpenAI-compatible providers. -
+
{t("aiPanel.byokTitle")}
+
{t("aiPanel.byokDescription")}
diff --git a/frontend/app/aipanel/restorebackupmodal.tsx b/frontend/app/aipanel/restorebackupmodal.tsx index 36b4be5b5d..ade5f1e18d 100644 --- a/frontend/app/aipanel/restorebackupmodal.tsx +++ b/frontend/app/aipanel/restorebackupmodal.tsx @@ -5,6 +5,7 @@ import { Modal } from "@/app/modals/modal"; import { recordTEvent } from "@/app/store/global"; import { useAtomValue } from "jotai"; import { memo } from "react"; +import { useTranslation } from "react-i18next"; import { WaveUIMessagePart } from "./aitypes"; import { WaveAIModel } from "./waveai-model"; @@ -13,6 +14,7 @@ interface RestoreBackupModalProps { } export const RestoreBackupModal = memo(({ part }: RestoreBackupModalProps) => { + const { t } = useTranslation(); const model = WaveAIModel.getInstance(); const toolData = part.data; const status = useAtomValue(model.restoreBackupStatus); @@ -40,12 +42,18 @@ export const RestoreBackupModal = memo(({ part }: RestoreBackupModalProps) => { if (status === "success") { return ( - +
-
Backup Successfully Restored
+
{t("restoreBackup.restoredTitle")}
- The file {toolData.inputfilename} has - been restored to its previous state. + {t("restoreBackup.restoredBodyPrefix")}{" "} + {toolData.inputfilename}{" "} + {t("restoreBackup.restoredBodySuffix")}
@@ -54,12 +62,15 @@ export const RestoreBackupModal = memo(({ part }: RestoreBackupModalProps) => { if (status === "error") { return ( - +
-
Failed to Restore Backup
-
- An error occurred while restoring the backup: -
+
{t("restoreBackup.failedTitle")}
+
{t("restoreBackup.failedBody")}
{error}
@@ -74,21 +85,20 @@ export const RestoreBackupModal = memo(({ part }: RestoreBackupModalProps) => { onClose={handleCancel} onCancel={handleCancel} onOk={handleConfirm} - okLabel={isProcessing ? "Restoring..." : "Confirm Restore"} - cancelLabel="Cancel" + okLabel={isProcessing ? t("restoreBackup.restoring") : t("restoreBackup.confirmRestore")} + cancelLabel={t("modal.cancel")} okDisabled={isProcessing} cancelDisabled={isProcessing} >
-
Restore File Backup
+
{t("restoreBackup.title")}
- This will restore {toolData.inputfilename}{" "} - to its state before this edit was made + {t("restoreBackup.confirmBodyPrefix")}{" "} + {toolData.inputfilename}{" "} + {t("restoreBackup.confirmBodySuffix")} {toolData.runts && ({formatTimestamp(toolData.runts)})}.
-
- Any changes made by this edit and subsequent edits will be lost. -
+
{t("restoreBackup.warning")}
); diff --git a/frontend/app/aipanel/telemetryrequired.tsx b/frontend/app/aipanel/telemetryrequired.tsx index 692dec73d5..1beadb244f 100644 --- a/frontend/app/aipanel/telemetryrequired.tsx +++ b/frontend/app/aipanel/telemetryrequired.tsx @@ -5,6 +5,7 @@ import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { cn } from "@/util/util"; import { useState } from "react"; +import { useTranslation } from "react-i18next"; import { WaveAIModel } from "./waveai-model"; interface TelemetryRequiredMessageProps { @@ -12,6 +13,7 @@ interface TelemetryRequiredMessageProps { } const TelemetryRequiredMessage = ({ className }: TelemetryRequiredMessageProps) => { + const { t } = useTranslation(); const [isEnabling, setIsEnabling] = useState(false); const handleEnableTelemetry = async () => { @@ -35,33 +37,20 @@ const TelemetryRequiredMessage = ({ className }: TelemetryRequiredMessageProps)

Wave AI

-

- Wave AI is free to use and provides integrated AI chat that can interact with your widgets, - help you with code, analyze files, and assist with your terminal workflows. -

+

{t("aiPanel.telemetryIntro")}

-
Telemetry keeps Wave AI free
+
{t("aiPanel.telemetryKeepsFree")}
-

- To keep Wave AI free for everyone, we require a small amount of anonymous{" "} - usage data (app version, feature usage, system info). -

-

- This helps us block abuse by automated systems and ensure it's used by real - people like you. -

-

- We never collect your files, prompts, keystrokes, hostnames, or personally - identifying information. Wave AI is powered by OpenAI's APIs, please refer to - OpenAI's privacy policy for details on how they handle your data. -

+

{t("aiPanel.telemetryParagraph1")}

+

{t("aiPanel.telemetryParagraph2")}

+

{t("aiPanel.telemetryParagraph3")}

- For information about BYOK and local model support, see{" "} + {t("aiPanel.telemetryByokInfo")}{" "} - {isEnabling ? "Enabling..." : "Enable Telemetry and Continue"} + {isEnabling ? t("aiPanel.enabling") : t("aiPanel.enableTelemetryAndContinue")}

@@ -91,7 +80,7 @@ const TelemetryRequiredMessage = ({ className }: TelemetryRequiredMessageProps) rel="noopener noreferrer" className="!text-secondary hover:!text-accent/80 cursor-pointer" > - Privacy Policy + {t("aiPanel.privacyPolicy")}
diff --git a/frontend/app/aipanel/waveai-model.tsx b/frontend/app/aipanel/waveai-model.tsx index 194005adc6..85f0890aa1 100644 --- a/frontend/app/aipanel/waveai-model.tsx +++ b/frontend/app/aipanel/waveai-model.tsx @@ -17,6 +17,7 @@ import { TabRpcClient } from "@/app/store/wshrpcutil"; import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model"; import { BuilderFocusManager } from "@/builder/store/builder-focusmanager"; import { getWebServerEndpoint } from "@/util/endpoints"; +import i18n from "@/util/i18n/i18n"; import { base64ToArrayBuffer } from "@/util/util"; import { ChatStatus } from "ai"; import * as jotai from "jotai"; @@ -41,26 +42,28 @@ export interface DroppedFile { previewUrl?: string; } -const BuilderAIModeConfigs: Record = { - "waveaibuilder@default": { - "display:name": "Builder Default", - "display:order": -2, - "display:icon": "sparkles", - "display:description": "Good mix of speed and accuracy\n(gpt-5.4 with minimal thinking)", - "ai:provider": "wave", - "ai:switchcompat": ["wavecloud"], - "waveai:premium": true, - }, - "waveaibuilder@deep": { - "display:name": "Builder Deep", - "display:order": -1, - "display:icon": "lightbulb", - "display:description": "Slower but most capable\n(gpt-5.4 with full reasoning)", - "ai:provider": "wave", - "ai:switchcompat": ["wavecloud"], - "waveai:premium": true, - }, -}; +function getBuilderAIModeConfigs(): Record { + return { + "waveaibuilder@default": { + "display:name": i18n.t("aiPanel.builderDefaultModeName"), + "display:order": -2, + "display:icon": "sparkles", + "display:description": i18n.t("aiPanel.builderDefaultModeDescription"), + "ai:provider": "wave", + "ai:switchcompat": ["wavecloud"], + "waveai:premium": true, + }, + "waveaibuilder@deep": { + "display:name": i18n.t("aiPanel.builderDeepModeName"), + "display:order": -1, + "display:icon": "lightbulb", + "display:description": i18n.t("aiPanel.builderDeepModeDescription"), + "ai:provider": "wave", + "ai:switchcompat": ["wavecloud"], + "waveai:premium": true, + }, + }; +} export class WaveAIModel { private static instance: WaveAIModel | null = null; @@ -102,7 +105,7 @@ export class WaveAIModel { this.inBuilder = inBuilder; this.chatId = jotai.atom(null) as jotai.PrimitiveAtom; if (inBuilder) { - this.aiModeConfigs = jotai.atom(BuilderAIModeConfigs) as jotai.Atom>; + this.aiModeConfigs = jotai.atom(getBuilderAIModeConfigs()) as jotai.Atom>; } else { this.aiModeConfigs = atoms.waveaiModeConfigAtom; } @@ -226,18 +229,18 @@ export class WaveAIModel { async addFileFromRemoteUri(draggedFile: DraggedFile): Promise { if (draggedFile.isDir) { - this.setError("Cannot add directories to Wave AI. Please select a file."); + this.setError(i18n.t("aiPanel.cannotAddDirectories")); return; } try { const fileInfo = await RpcApi.FileInfoCommand(TabRpcClient, { info: { path: draggedFile.uri } }, null); if (fileInfo.notfound) { - this.setError(`File not found: ${draggedFile.relName}`); + this.setError(i18n.t("aiPanel.fileNotFound", { name: draggedFile.relName })); return; } if (fileInfo.isdir) { - this.setError("Cannot add directories to Wave AI. Please select a file."); + this.setError(i18n.t("aiPanel.cannotAddDirectories")); return; } @@ -251,16 +254,14 @@ export class WaveAIModel { const fileData = await RpcApi.FileReadCommand(TabRpcClient, { info: { path: draggedFile.uri } }, null); if (!fileData.data64) { - this.setError(`Failed to read file: ${draggedFile.relName}`); + this.setError(i18n.t("aiPanel.failedToReadFile", { name: draggedFile.relName })); return; } const buffer = base64ToArrayBuffer(fileData.data64); const file = new File([buffer], draggedFile.relName, { type: mimeType }); if (!isAcceptableFile(file)) { - this.setError( - `File type not supported: ${draggedFile.relName}. Supported: images, PDFs, and text/code files.` - ); + this.setError(i18n.t("aiPanel.fileTypeNotSupported", { name: draggedFile.relName })); return; } @@ -268,7 +269,7 @@ export class WaveAIModel { } catch (error) { console.error("Error handling FILE_ITEM drop:", error); const errorMsg = error instanceof Error ? error.message : String(error); - this.setError(`Failed to add file: ${errorMsg}`); + this.setError(i18n.t("aiPanel.failedToAddFile", { error: errorMsg })); } } @@ -498,7 +499,7 @@ export class WaveAIModel { return await this.reloadChatFromBackend(chatIdValue); } catch (error) { console.error("Failed to load chat:", error); - this.setError("Failed to load chat. Starting new chat..."); + this.setError(i18n.t("aiPanel.failedToLoadChat")); this.clearChat(); return []; @@ -641,7 +642,7 @@ export class WaveAIModel { const chatId = this.getChatId(); if (!chatId || !fileName) { - console.error("Missing chatId or fileName for opening diff", chatId, fileName); + console.error(i18n.t("aiPanel.missingChatIdOrFileName"), chatId, fileName); return; } diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx index e9c70a35df..c920fc2d17 100644 --- a/frontend/app/app.tsx +++ b/frontend/app/app.tsx @@ -17,9 +17,10 @@ import { makeWaveEnvImpl } from "@/app/waveenv/waveenvimpl"; import { Workspace } from "@/app/workspace/workspace"; import { getLayoutModelForStaticTab } from "@/layout/index"; import { ContextMenuModel } from "@/store/contextmenu"; -import { atoms, createBlock, getSettingsPrefixAtom, refocusNode } from "@/store/global"; +import { atoms, createBlock, getSettingsKeyAtom, getSettingsPrefixAtom, refocusNode } from "@/store/global"; import { appHandleKeyDown, keyboardMouseDownHandler } from "@/store/keymodel"; import { getElemAsStr } from "@/util/focusutil"; +import { setLanguage, SupportedLanguage } from "@/util/i18n/i18n"; import * as keyutil from "@/util/keyutil"; import { PLATFORM } from "@/util/platformutil"; import * as util from "@/util/util"; @@ -167,6 +168,14 @@ function AppSettingsUpdater() { return null; } +function AppLanguageSync() { + const language = useAtomValue(getSettingsKeyAtom("app:language")); + useEffect(() => { + setLanguage((language as SupportedLanguage) ?? "en"); + }, [language]); + return null; +} + function appFocusIn(e: FocusEvent) { focusLog("focusin", getElemAsStr(e.target), "<=", getElemAsStr(e.relatedTarget)); } @@ -382,6 +391,7 @@ const AppInner = () => { + diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index f199dc5e9c..a7723ebddc 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -21,6 +21,7 @@ import { isBlank, useAtomValueSafe } from "@/util/util"; import clsx from "clsx"; import { useAtomValue } from "jotai"; import { memo, Suspense, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import "./block.scss"; import { BlockEnv } from "./blockenv"; import { BlockFrame } from "./blockframe"; @@ -31,13 +32,14 @@ function getViewElem( blockRef: React.RefObject, contentRef: React.RefObject, blockView: string, - viewModel: ViewModel + viewModel: ViewModel, + t: (key: string) => string ): React.ReactElement { if (isBlank(blockView)) { - return No View; + return {t("block.noView")}; } if (viewModel.viewComponent == null) { - return No View Component; + return {t("block.noViewComponent")}; } const VC = viewModel.viewComponent; return ; @@ -61,14 +63,15 @@ const BlockPreview = memo(({ nodeModel, viewModel }: FullBlockProps) => { }); const BlockSubBlock = memo(({ nodeModel, viewModel }: FullSubBlockProps) => { + const { t } = useTranslation(); const waveEnv = useWaveEnv(); const blockIsNull = useAtomValue(waveEnv.wos.isWaveObjectNullAtom(makeORef("block", nodeModel.blockId))); const blockView = useAtomValue(waveEnv.getBlockMetaKeyAtom(nodeModel.blockId, "view")) ?? ""; const blockRef = useRef(null); const contentRef = useRef(null); const viewElem = useMemo( - () => getViewElem(nodeModel.blockId, blockRef, contentRef, blockView, viewModel), - [nodeModel.blockId, blockView, viewModel] + () => getViewElem(nodeModel.blockId, blockRef, contentRef, blockView, viewModel, t), + [nodeModel.blockId, blockView, viewModel, t] ); const noPadding = useAtomValueSafe(viewModel.noPadding); if (blockIsNull) { @@ -77,7 +80,7 @@ const BlockSubBlock = memo(({ nodeModel, viewModel }: FullSubBlockProps) => { return (
- Loading...}>{viewElem} + {t("block.loading")}}>{viewElem}
); @@ -85,6 +88,7 @@ const BlockSubBlock = memo(({ nodeModel, viewModel }: FullSubBlockProps) => { const BlockFull = memo(({ nodeModel, viewModel }: FullBlockProps) => { counterInc("render-BlockFull"); + const { t } = useTranslation(); const waveEnv = useWaveEnv(); const focusElemRef = useRef(null); const blockRef = useRef(null); @@ -159,8 +163,8 @@ const BlockFull = memo(({ nodeModel, viewModel }: FullBlockProps) => { }, [innerRect, disablePointerEvents, blockContentOffset]); const viewElem = useMemo( - () => getViewElem(nodeModel.blockId, blockRef, contentRef, blockView, viewModel), - [nodeModel.blockId, blockView, viewModel] + () => getViewElem(nodeModel.blockId, blockRef, contentRef, blockView, viewModel, t), + [nodeModel.blockId, blockView, viewModel, t] ); const handleChildFocus = useCallback( @@ -259,7 +263,7 @@ const BlockFull = memo(({ nodeModel, viewModel }: FullBlockProps) => { style={blockContentStyle} > - Loading...}>{viewElem} + {t("block.loading")}}>{viewElem}
diff --git a/frontend/app/block/blockframe-header.tsx b/frontend/app/block/blockframe-header.tsx index a70f323e71..710598fea6 100644 --- a/frontend/app/block/blockframe-header.tsx +++ b/frontend/app/block/blockframe-header.tsx @@ -26,8 +26,10 @@ import { IconButton } from "@/element/iconbutton"; import { NodeModel } from "@/layout/index"; import * as util from "@/util/util"; import { cn, makeIconClass } from "@/util/util"; +import type { TFunction } from "i18next"; import * as jotai from "jotai"; import * as React from "react"; +import { useTranslation } from "react-i18next"; import { BlockEnv } from "./blockenv"; import { BlockFrameProps } from "./blocktypes"; @@ -36,21 +38,22 @@ function handleHeaderContextMenu( blockId: string, viewModel: ViewModel, nodeModel: NodeModel, - blockEnv: BlockEnv + blockEnv: BlockEnv, + t: TFunction ) { e.preventDefault(); e.stopPropagation(); const magnified = globalStore.get(nodeModel.isMagnified); const menu: ContextMenuItem[] = [ { - label: magnified ? "Un-Magnify Block" : "Magnify Block", + label: magnified ? t("blockFrame.unMagnifyBlock") : t("blockFrame.magnifyBlock"), click: () => { nodeModel.toggleMagnify(); }, }, { type: "separator" }, { - label: "Copy BlockId", + label: t("blockFrame.copyBlockId"), click: () => { navigator.clipboard.writeText(blockId); }, @@ -61,7 +64,7 @@ function handleHeaderContextMenu( menu.push( { type: "separator" }, { - label: "Close Block", + label: t("blockFrame.closeBlock"), click: () => uxCloseBlock(blockId), } ); @@ -76,6 +79,7 @@ type HeaderTextElemsProps = { }; const HeaderTextElems = React.memo(({ viewModel, blockId, preview, error }: HeaderTextElemsProps) => { + const { t } = useTranslation(); const waveEnv = useWaveEnv(); const frameTextAtom = waveEnv.getBlockMetaKeyAtom(blockId, "frame:text"); const frameText = jotai.useAtomValue(frameTextAtom); @@ -102,7 +106,7 @@ const HeaderTextElems = React.memo(({ viewModel, blockId, preview, error }: Head
); @@ -119,6 +123,7 @@ type HeaderEndIconsProps = { }; const HeaderEndIcons = React.memo(({ viewModel, nodeModel, blockId }: HeaderEndIconsProps) => { + const { t } = useTranslation(); const blockEnv = useWaveEnv(); const endIconButtons = util.useAtomValueSafe(viewModel?.endIconButtons); const magnified = jotai.useAtomValue(nodeModel.isMagnified); @@ -136,7 +141,7 @@ const HeaderEndIcons = React.memo(({ viewModel, nodeModel, blockId }: HeaderEndI const splitHorizontalDecl: IconButtonDecl = { elemtype: "iconbutton", icon: "columns", - title: "Split Horizontally", + title: t("blockFrame.splitHorizontally"), click: (e) => { e.stopPropagation(); const blockAtom = WOS.getWaveObjectAtom(WOS.makeORef("block", blockId)); @@ -150,7 +155,7 @@ const HeaderEndIcons = React.memo(({ viewModel, nodeModel, blockId }: HeaderEndI const splitVerticalDecl: IconButtonDecl = { elemtype: "iconbutton", icon: "grip-lines", - title: "Split Vertically", + title: t("blockFrame.splitVertically"), click: (e) => { e.stopPropagation(); const blockAtom = WOS.getWaveObjectAtom(WOS.makeORef("block", blockId)); @@ -167,15 +172,15 @@ const HeaderEndIcons = React.memo(({ viewModel, nodeModel, blockId }: HeaderEndI const settingsDecl: IconButtonDecl = { elemtype: "iconbutton", icon: "cog", - title: "Settings", - click: (e) => handleHeaderContextMenu(e, blockId, viewModel, nodeModel, blockEnv), + title: t("blockFrame.settings"), + click: (e) => handleHeaderContextMenu(e, blockId, viewModel, nodeModel, blockEnv, t), }; endIconsElem.push(); if (ephemeral) { const addToLayoutDecl: IconButtonDecl = { elemtype: "iconbutton", icon: "circle-plus", - title: "Add to Layout", + title: t("blockFrame.addToLayout"), click: () => { nodeModel.addEphemeralNodeToLayout(); }, @@ -198,7 +203,7 @@ const HeaderEndIcons = React.memo(({ viewModel, nodeModel, blockId }: HeaderEndI const closeDecl: IconButtonDecl = { elemtype: "iconbutton", icon: "xmark-large", - title: "Close", + title: t("blockFrame.close"), click: () => uxCloseBlock(nodeModel.blockId), }; endIconsElem.push(); @@ -215,12 +220,13 @@ const BlockFrame_Header = ({ changeConnModalAtom, error, }: BlockFrameProps & { changeConnModalAtom: jotai.PrimitiveAtom; error?: Error }) => { + const { t } = useTranslation(); const waveEnv = useWaveEnv(); const metaView = jotai.useAtomValue(waveEnv.getBlockMetaKeyAtom(nodeModel.blockId, "view")); const metaFrameTitle = jotai.useAtomValue(waveEnv.getBlockMetaKeyAtom(nodeModel.blockId, "frame:title")); const metaFrameIcon = jotai.useAtomValue(waveEnv.getBlockMetaKeyAtom(nodeModel.blockId, "frame:icon")); const metaConnection = jotai.useAtomValue(waveEnv.getBlockMetaKeyAtom(nodeModel.blockId, "connection")); - let viewName = util.useAtomValueSafe(viewModel?.viewName) ?? blockViewToName(metaView); + let viewName = util.useAtomValueSafe(viewModel?.viewName) ?? blockViewToName(metaView, t); let viewIconUnion = util.useAtomValueSafe(viewModel?.viewIcon) ?? blockViewToIcon(metaView); const preIconButton = util.useAtomValueSafe(viewModel?.preIconButton); const useTermHeader = util.useAtomValueSafe(viewModel?.useTermHeader); @@ -251,7 +257,7 @@ const BlockFrame_Header = ({ className={cn("block-frame-default-header", useTermHeader && "!pl-[2px]")} data-role="block-header" ref={dragHandleRef} - onContextMenu={(e) => handleHeaderContextMenu(e, nodeModel.blockId, viewModel, nodeModel, waveEnv)} + onContextMenu={(e) => handleHeaderContextMenu(e, nodeModel.blockId, viewModel, nodeModel, waveEnv, t)} > {!useTermHeader && ( <> diff --git a/frontend/app/block/blockutil.tsx b/frontend/app/block/blockutil.tsx index 3ef4d39821..53bda19352 100644 --- a/frontend/app/block/blockutil.tsx +++ b/frontend/app/block/blockutil.tsx @@ -14,6 +14,7 @@ import * as util from "@/util/util"; import clsx from "clsx"; import * as jotai from "jotai"; import * as React from "react"; +import { useTranslation } from "react-i18next"; export type TabBackgroundEnv = WaveEnvSubset<{ getTabMetaKeyAtom: MetaKeyAtomFnType<"bg:activebordercolor" | "bg:bordercolor" | "tab:background">; @@ -48,30 +49,30 @@ export function blockViewToIcon(view: string): string { return "square"; } -export function blockViewToName(view: string): string { +export function blockViewToName(view: string, t: (key: string) => string): string { if (util.isBlank(view)) { - return "(No View)"; + return t("blockUtil.noView"); } if (view == "term") { - return "Terminal"; + return t("blockUtil.terminal"); } if (view == "preview") { - return "Preview"; + return t("blockUtil.preview"); } if (view == "web") { - return "Web"; + return t("blockUtil.web"); } if (view == "waveai") { return "WaveAI"; } if (view == "help") { - return "Help"; + return t("blockUtil.help"); } if (view == "tips") { - return "Tips"; + return t("blockUtil.tips"); } if (view == "processviewer") { - return "Processes"; + return t("blockUtil.processes"); } return view; } @@ -212,10 +213,11 @@ export const Input = React.memo( export const OptMagnifyButton = React.memo( ({ magnified, toggleMagnify, disabled }: { magnified: boolean; toggleMagnify: () => void; disabled: boolean }) => { + const { t } = useTranslation(); const magnifyDecl: IconButtonDecl = { elemtype: "iconbutton", icon: , - title: magnified ? "Minimize" : "Magnify", + title: magnified ? t("blockUtil.minimize") : t("blockUtil.magnify"), click: toggleMagnify, disabled, }; diff --git a/frontend/app/block/connectionbutton.tsx b/frontend/app/block/connectionbutton.tsx index c5a9b635c3..73be8e0110 100644 --- a/frontend/app/block/connectionbutton.tsx +++ b/frontend/app/block/connectionbutton.tsx @@ -8,6 +8,7 @@ import { IconButton } from "@/element/iconbutton"; import * as util from "@/util/util"; import * as jotai from "jotai"; import * as React from "react"; +import { useTranslation } from "react-i18next"; import DotsSvg from "../asset/dots-anim-4.svg"; import { BlockEnv } from "./blockenv"; @@ -20,6 +21,7 @@ interface ConnectionButtonProps { export const ConnectionButton = React.memo( React.forwardRef( ({ connection, changeConnModalAtom, isTerminalBlock }: ConnectionButtonProps, ref) => { + const { t } = useTranslation(); const waveEnv = useWaveEnv(); const [_connModalOpen, setConnModalOpen] = jotai.useAtom(changeConnModalAtom); const isLocal = util.isLocalConnName(connection); @@ -40,10 +42,10 @@ export const ConnectionButton = React.memo( if (isLocal) { color = "var(--color-secondary)"; if (connection === "local:gitbash") { - titleText = "Connected to Git Bash"; + titleText = t("connectionButton.connectedToGitBash"); connDisplayName = "Git Bash"; } else { - titleText = "Connected to Local Machine"; + titleText = t("connectionButton.connectedToLocalMachine"); if (localName) { titleText += ` (${localName})`; } @@ -59,12 +61,12 @@ export const ConnectionButton = React.memo( /> ); } else { - titleText = "Connected to " + connection; + titleText = t("connectionButton.connectedTo", { connection }); let iconName = "arrow-right-arrow-left"; let iconSvg = null; if (connStatus?.status == "connecting") { color = "var(--warning-color)"; - titleText = "Connecting to " + connection; + titleText = t("connectionButton.connectingTo", { connection }); shouldSpin = false; iconSvg = (
@@ -73,22 +75,22 @@ export const ConnectionButton = React.memo( ); } else if (connStatus?.status == "error") { color = "var(--error-color)"; - titleText = "Error connecting to " + connection; + titleText = t("connectionButton.errorConnectingTo", { connection }); if (connStatus?.error != null) { titleText += " (" + connStatus.error + ")"; } showDisconnectedSlash = true; } else if (!connStatus?.connected) { color = "var(--grey-text-color)"; - titleText = "Disconnected from " + connection; + titleText = t("connectionButton.disconnectedFrom", { connection }); showDisconnectedSlash = true; } else if (connStatus?.connhealthstatus === "degraded" || connStatus?.connhealthstatus === "stalled") { color = "var(--warning-color)"; iconName = "signal-bars-slash"; if (connStatus.connhealthstatus === "degraded") { - titleText = "Connection degraded: " + connection; + titleText = t("connectionButton.connectionDegraded", { connection }); } else { - titleText = "Connection stalled: " + connection; + titleText = t("connectionButton.connectionStalled", { connection }); } } if (iconSvg != null) { @@ -147,7 +149,7 @@ export const ConnectionButton = React.memo( decl={{ elemtype: "iconbutton", icon: "link-slash", - title: "wsh is not installed for this connection", + title: t("connectionButton.wshNotInstalled"), }} /> )} diff --git a/frontend/app/block/connstatusoverlay.tsx b/frontend/app/block/connstatusoverlay.tsx index d4d6ad14b8..e695eb71d2 100644 --- a/frontend/app/block/connstatusoverlay.tsx +++ b/frontend/app/block/connstatusoverlay.tsx @@ -12,9 +12,10 @@ import clsx from "clsx"; import * as jotai from "jotai"; import { OverlayScrollbarsComponent } from "overlayscrollbars-react"; import * as React from "react"; +import { useTranslation } from "react-i18next"; import { BlockEnv } from "./blockenv"; -function formatElapsedTime(elapsedMs: number): string { +function formatElapsedTime(elapsedMs: number, t: (key: string) => string): string { if (elapsedMs <= 0) { return ""; } @@ -40,7 +41,7 @@ function formatElapsedTime(elapsedMs: number): string { return `${elapsedHours}h${remainingMinutes}m`; } - return "more than a day"; + return t("connStatus.moreThanADay"); } const StalledOverlay = React.memo( @@ -53,6 +54,7 @@ const StalledOverlay = React.memo( connStatus: ConnStatus; overlayRefCallback: (el: HTMLDivElement | null) => void; }) => { + const { t } = useTranslation(); const [elapsedTime, setElapsedTime] = React.useState(""); const waveEnv = useWaveEnv(); @@ -70,7 +72,7 @@ const StalledOverlay = React.memo( const now = Date.now(); const lastActivity = connStatus.lastactivitybeforestalledtime!; const elapsed = now - lastActivity; - setElapsedTime(formatElapsedTime(elapsed)); + setElapsedTime(formatElapsedTime(elapsed, t)); }; updateElapsed(); @@ -87,19 +89,19 @@ const StalledOverlay = React.memo(
- Connection to "{connName}" is stalled - {elapsedTime && ` (no activity for ${elapsedTime})`} + {t("connStatus.connectionStalledText", { connName })} + {elapsedTime && ` ${t("connStatus.noActivityFor", { elapsedTime })}`}
@@ -119,6 +121,7 @@ export const ConnStatusOverlay = React.memo( viewModel: ViewModel; changeConnModalAtom: jotai.PrimitiveAtom; }) => { + const { t } = useTranslation(); const waveEnv = useWaveEnv(); const connName = jotai.useAtomValue(waveEnv.getBlockMetaKeyAtom(nodeModel.blockId, "connection")); const [connModalOpen] = jotai.useAtom(changeConnModalAtom); @@ -171,10 +174,10 @@ export const ConnStatusOverlay = React.memo( } }, [connName, waveEnv]); - let statusText = `Disconnected from "${connName}"`; + let statusText = t("connStatus.disconnectedFrom", { connName }); let showReconnect = true; if (connStatus.status == "connecting") { - statusText = `Connecting to "${connName}"...`; + statusText = t("connStatus.connectingTo", { connName }); showReconnect = false; } if (connStatus.status == "connected") { @@ -186,7 +189,7 @@ export const ConnStatusOverlay = React.memo( reconDisplay = ; reconClassName = clsx(reconClassName, "text-[12px] py-[5px] px-[6px]"); } else { - reconDisplay = "Reconnect"; + reconDisplay = t("connStatus.reconnect"); reconClassName = clsx(reconClassName, "text-[11px] py-[3px] px-[7px]"); } const showIcon = connStatus.status != "connecting"; @@ -205,10 +208,10 @@ export const ConnStatusOverlay = React.memo( async (e: React.MouseEvent) => { const errTexts = []; if (showError) { - errTexts.push(`error: ${connStatus.error}`); + errTexts.push(`${t("connStatus.errorPrefix")} ${connStatus.error}`); } if (showWshError) { - errTexts.push(`unable to use wsh: ${connStatus.wsherror}`); + errTexts.push(`${t("connStatus.unableToUseWsh")} ${connStatus.wsherror}`); } const textToCopy = errTexts.join("\n"); await navigator.clipboard.writeText(textToCopy); @@ -239,14 +242,22 @@ export const ConnStatusOverlay = React.memo( className="connstatus-error" options={{ scrollbars: { autoHide: "leave" } }} > - - {showError ?
error: {connStatus.error}
: null} - {showWshError ?
unable to use wsh: {connStatus.wsherror}
: null} + + {showError ? ( +
+ {t("connStatus.errorPrefix")} {connStatus.error} +
+ ) : null} + {showWshError ? ( +
+ {t("connStatus.unableToUseWsh")} {connStatus.wsherror} +
+ ) : null} )} {showWshError && ( )}
diff --git a/frontend/app/block/durable-session-flyover.tsx b/frontend/app/block/durable-session-flyover.tsx index 7ab7fa0b10..da3f322899 100644 --- a/frontend/app/block/durable-session-flyover.tsx +++ b/frontend/app/block/durable-session-flyover.tsx @@ -19,6 +19,7 @@ import { } from "@floating-ui/react"; import * as jotai from "jotai"; import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import { BlockEnv } from "./blockenv"; function isTermViewModel(viewModel: ViewModel): viewModel is TermViewModel { @@ -26,13 +27,14 @@ function isTermViewModel(viewModel: ViewModel): viewModel is TermViewModel { } function LearnMoreButton() { + const { t } = useTranslation(); const waveEnv = useWaveEnv(); return ( ); } @@ -43,6 +45,7 @@ interface StandardSessionContentProps { } function StandardSessionContent({ viewModel, onClose }: StandardSessionContentProps) { + const { t } = useTranslation(); const handleRestartAsDurable = () => { recordTEvent("action:termdurable", { "action:type": "restartdurable" }); onClose(); @@ -53,18 +56,15 @@ function StandardSessionContent({ viewModel, onClose }: StandardSessionContentPr
- Standard SSH Session -
-
- Standard SSH sessions end when the connection drops. Durable sessions keep your shell state, running - programs, and history alive through network changes, computer sleep, and Wave restarts. + {t("durableSession.standardSshSession")}
+
{t("durableSession.standardSessionBody")}
@@ -76,16 +76,14 @@ interface DurableAttachedContentProps { } function DurableAttachedContent({ onClose }: DurableAttachedContentProps) { + const { t } = useTranslation(); return (
- Durable Session (Attached) -
-
- Your shell state, running programs, and history are protected. This session will survive network - disconnects. + {t("durableSession.attachedTitle")}
+
{t("durableSession.attachedBody")}
); @@ -96,16 +94,14 @@ interface DurableDetachedContentProps { } function DurableDetachedContent({ onClose }: DurableDetachedContentProps) { + const { t } = useTranslation(); return (
- Durable Session (Detached) -
-
- Connection lost, but your session is still running on the remote server. Wave will automatically - reconnect when the connection is restored. + {t("durableSession.detachedTitle")}
+
{t("durableSession.detachedBody")}
); @@ -118,6 +114,7 @@ interface DurableAwaitingStartProps { } function DurableAwaitingStart({ connected, viewModel, onClose }: DurableAwaitingStartProps) { + const { t } = useTranslation(); const handleStartSession = () => { onClose(); util.fireAndForget(() => viewModel.forceRestartController()); @@ -128,10 +125,10 @@ function DurableAwaitingStart({ connected, viewModel, onClose }: DurableAwaiting
- Durable Session (Awaiting Connection) + {t("durableSession.awaitingConnectionTitle")}
- Configured for a durable session. The session will start when the connection is established. + {t("durableSession.awaitingConnectionBody")}
@@ -142,17 +139,15 @@ function DurableAwaitingStart({ connected, viewModel, onClose }: DurableAwaiting
- Durable Session (Awaiting Start) -
-
- Configured for a durable session, but session hasn't started yet. Click below to start it manually. + {t("durableSession.awaitingStartTitle")}
+
{t("durableSession.awaitingStartBody")}
@@ -164,13 +159,14 @@ interface DurableStartingContentProps { } function DurableStartingContent({ onClose }: DurableStartingContentProps) { + const { t } = useTranslation(); return (
- Durable Session (Starting) + {t("durableSession.startingTitle")}
-
The durable session is starting.
+
{t("durableSession.startingBody")}
); @@ -184,6 +180,7 @@ interface DurableEndedContentProps { } function DurableEndedContent({ doneReason, startupError, viewModel, onClose }: DurableEndedContentProps) { + const { t } = useTranslation(); const handleRestartSession = () => { onClose(); util.fireAndForget(() => viewModel.forceRestartController()); @@ -194,21 +191,19 @@ function DurableEndedContent({ doneReason, startupError, viewModel, onClose }: D util.fireAndForget(() => viewModel.restartSessionWithDurability(false)); }; - let titleText = "Durable Session (Ended)"; - let descriptionText = "The durable session has ended. This block is still configured for durable sessions."; + let titleText = t("durableSession.endedTitle"); + let descriptionText = t("durableSession.endedBody"); const showRestartButton = true; if (doneReason === "terminated") { - titleText = "Durable Session (Ended, Exited)"; - descriptionText = - "The shell was terminated and is no longer running. This block is still configured for durable sessions."; + titleText = t("durableSession.endedExitedTitle"); + descriptionText = t("durableSession.endedExitedBody"); } else if (doneReason === "gone") { - titleText = "Durable Session (Ended, Lost)"; - descriptionText = - "The session was lost or not found on the remote server. This may have occurred due to a system reboot or the session being manually terminated."; + titleText = t("durableSession.endedLostTitle"); + descriptionText = t("durableSession.endedLostBody"); } else if (doneReason === "startuperror") { - titleText = "Durable Session (Failed to Start)"; - descriptionText = "The durable session failed to start."; + titleText = t("durableSession.failedToStartTitle"); + descriptionText = t("durableSession.failedToStartBody"); return (
@@ -226,14 +221,14 @@ function DurableEndedContent({ doneReason, startupError, viewModel, onClose }: D onClick={handleRestartSession} > - Restart Session + {t("durableSession.restartSession")}
@@ -253,7 +248,7 @@ function DurableEndedContent({ doneReason, startupError, viewModel, onClose }: D onClick={handleRestartSession} > - Restart Session + {t("durableSession.restartSession")} )} diff --git a/frontend/app/element/copybutton.tsx b/frontend/app/element/copybutton.tsx index 027311a096..af4c51ae86 100644 --- a/frontend/app/element/copybutton.tsx +++ b/frontend/app/element/copybutton.tsx @@ -3,16 +3,18 @@ import clsx from "clsx"; import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import "./copybutton.scss"; import { IconButton } from "./iconbutton"; type CopyButtonProps = { - title: string; + title?: string; className?: string; onClick: (e: React.MouseEvent) => void; }; const CopyButton = ({ title, className, onClick }: CopyButtonProps) => { + const { t } = useTranslation(); const [isCopied, setIsCopied] = useState(false); const timeoutRef = useRef(null); @@ -47,7 +49,7 @@ const CopyButton = ({ title, className, onClick }: CopyButtonProps) => { decl={{ elemtype: "iconbutton", icon: isCopied ? "check" : "copy", - title, + title: title ?? t("element.copy"), className: clsx("copy-button", { copied: isCopied }), click: handleOnClick, }} diff --git a/frontend/app/element/emojipalette.tsx b/frontend/app/element/emojipalette.tsx index db480951d6..baaa0e8de9 100644 --- a/frontend/app/element/emojipalette.tsx +++ b/frontend/app/element/emojipalette.tsx @@ -4,6 +4,7 @@ import { type Placement } from "@floating-ui/react"; import clsx from "clsx"; import { memo, useState } from "react"; +import { useTranslation } from "react-i18next"; import { Button } from "./button"; import { Input, InputGroup, InputLeftElement } from "./input"; import { Popover, PopoverButton, PopoverContent } from "./popover"; @@ -220,6 +221,7 @@ interface EmojiPaletteProps { } const EmojiPalette = memo(({ className, placement, onSelect }: EmojiPaletteProps) => { + const { t } = useTranslation(); const [searchTerm, setSearchTerm] = useState(""); const handleSearchChange = (val: string) => { @@ -243,7 +245,11 @@ const EmojiPalette = memo(({ className, placement, onSelect }: EmojiPaletteProps - +
{filteredEmojis.length > 0 ? ( @@ -253,7 +259,7 @@ const EmojiPalette = memo(({ className, placement, onSelect }: EmojiPaletteProps )) ) : ( -
No emojis found
+
{t("element.noEmojisFound")}
)}
diff --git a/frontend/app/element/markdown.tsx b/frontend/app/element/markdown.tsx index 5ecc252876..f5aa76ba6f 100644 --- a/frontend/app/element/markdown.tsx +++ b/frontend/app/element/markdown.tsx @@ -15,6 +15,7 @@ import clsx from "clsx"; import { Atom } from "jotai"; import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react"; import { useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import ReactMarkdown, { Components } from "react-markdown"; import rehypeHighlight from "rehype-highlight"; import rehypeRaw from "rehype-raw"; @@ -69,6 +70,7 @@ const Heading = ({ props, hnum }: { props: React.HTMLAttributes { + const { t } = useTranslation(); const ref = useRef(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); @@ -97,7 +99,7 @@ const Mermaid = ({ chart }: { chart: string }) => { setIsLoading(false); } catch (err) { console.error("Error rendering mermaid diagram:", err); - setError(`Failed to render diagram: ${err.message || err}`); + setError(`${t("element.failedToRenderDiagram")} ${err.message || err}`); setIsLoading(false); } }; @@ -109,10 +111,10 @@ const Mermaid = ({ chart }: { chart: string }) => { if (!ref.current) return; if (error) { - ref.current.textContent = `Error: ${error}`; + ref.current.textContent = `${t("element.errorPrefix")} ${error}`; ref.current.className = "mermaid error"; } else if (isLoading) { - ref.current.textContent = "Loading diagram..."; + ref.current.textContent = t("element.loadingDiagram"); ref.current.className = "mermaid"; } else { ref.current.className = "mermaid"; @@ -166,7 +168,7 @@ const CodeBlock = ({ children, onClickExecute }: CodeBlockProps) => {
             {children}
             
- + {onClickExecute && ( { + const { t } = useTranslation(); const textAtomValue = useAtomValueSafe(textAtom); const tocRef = useRef([]); const showToc = useAtomValueSafe(showTocAtom) ?? false; @@ -498,7 +501,7 @@ const Markdown = ({ {toc && (
-

Table of Contents

+

{t("element.tableOfContents")}

{toc}
diff --git a/frontend/app/element/modal.tsx b/frontend/app/element/modal.tsx index 4655dc2afc..b93e1154f9 100644 --- a/frontend/app/element/modal.tsx +++ b/frontend/app/element/modal.tsx @@ -3,6 +3,7 @@ import { Button } from "@/element/button"; import React from "react"; +import { useTranslation } from "react-i18next"; import "./modal.scss"; @@ -68,13 +69,14 @@ interface WaveModalProps { children: React.ReactNode; } -function WaveModal({ title, description, onSubmit, onCancel, buttonLabel = "Ok", children }: WaveModalProps) { +function WaveModal({ title, description, onSubmit, onCancel, buttonLabel, children }: WaveModalProps) { + const { t } = useTranslation(); return ( {children} - + ); diff --git a/frontend/app/element/quicktips.tsx b/frontend/app/element/quicktips.tsx index 868c1b125e..73605a2c35 100644 --- a/frontend/app/element/quicktips.tsx +++ b/frontend/app/element/quicktips.tsx @@ -4,6 +4,7 @@ import { MagnifyIcon } from "@/app/element/magnify"; import { PLATFORM, PlatformMacOS } from "@/util/platformutil"; import { cn } from "@/util/util"; +import { useTranslation } from "react-i18next"; const KeyCap = ({ children }: { children: React.ReactNode }) => { return ( @@ -32,6 +33,7 @@ const IconBox = ({ children, variant = "accent" }: { children: React.ReactNode; }; const KeyBinding = ({ keyDecl }: { keyDecl: string }) => { + const { t } = useTranslation(); const chordParts = keyDecl.split("+"); const chordElems: React.ReactNode[] = []; @@ -42,18 +44,18 @@ const KeyBinding = ({ keyDecl }: { keyDecl: string }) => { for (let part of parts) { if (part === "Cmd") { if (PLATFORM === PlatformMacOS) { - elems.push(⌘ Cmd); + elems.push(⌘ {t("element.keyCmd")}); } else { - elems.push(Alt); + elems.push({t("element.keyAlt")}); } continue; } if (part == "Ctrl") { - elems.push(^ Ctrl); + elems.push(^ {t("element.keyCtrl")}); continue; } if (part == "Shift") { - elems.push(⇧ Shift); + elems.push(⇧ {t("element.keyShift")}); continue; } if (part == "Arrows") { @@ -64,7 +66,7 @@ const KeyBinding = ({ keyDecl }: { keyDecl: string }) => { continue; } if (part == "Digit") { - elems.push(Number (1-9)); + elems.push({t("element.keyNumberRange")}); continue; } if (part == "[" || part == "]") { @@ -93,12 +95,13 @@ const KeyBinding = ({ keyDecl }: { keyDecl: string }) => { }; const QuickTips = () => { + const { t } = useTranslation(); return (
- Header Icons + {t("quickTips.headerIcons")}
@@ -106,7 +109,7 @@ const QuickTips = () => {
- Magnify a Block + {t("quickTips.magnifyBlock")}
@@ -115,7 +118,7 @@ const QuickTips = () => {
- Connect to a remote server + {t("quickTips.connectRemoteServer")}
@@ -123,14 +126,14 @@ const QuickTips = () => { - Block Settings + {t("quickTips.blockSettings")}
- Close Block + {t("quickTips.closeBlock")}
@@ -140,78 +143,80 @@ const QuickTips = () => {
- Important Keybindings + {t("quickTips.importantKeybindings")}
- Main Keybindings + {t("quickTips.mainKeybindings")}
- New Tab + {t("quickTips.newTab")}
- New Terminal Block + {t("quickTips.newTerminalBlock")}
- Open Wave AI Panel + {t("quickTips.openWaveAiPanel")}
- Tab Switching ({PLATFORM === PlatformMacOS ? "Cmd" : "Alt"}) + {t("quickTips.tabSwitching", { + modifier: PLATFORM === PlatformMacOS ? t("element.keyCmd") : t("element.keyAlt"), + })}
- Switch To Nth Tab + {t("quickTips.switchToNthTab")}
- Previous Tab + {t("quickTips.previousTab")}
- Next Tab + {t("quickTips.nextTab")}
- Block Navigation (Ctrl-Shift) + {t("quickTips.blockNavigation")}
- Navigate Between Blocks + {t("quickTips.navigateBetweenBlocks")}
- Focus Nth Block + {t("quickTips.focusNthBlock")}
- Focus Wave AI + {t("quickTips.focusWaveAi")}
- Split Blocks + {t("quickTips.splitBlocks")}
- Split Right + {t("quickTips.splitRight")}
- Split Below + {t("quickTips.splitBelow")}
- Split in Direction + {t("quickTips.splitInDirection")}
@@ -221,7 +226,7 @@ const QuickTips = () => {
- wsh commands + {t("quickTips.wshCommands")}
@@ -230,7 +235,7 @@ const QuickTips = () => { wsh view [filename|url] -
Preview files, directories, or web URLs
+
{t("quickTips.wshViewDesc")}
@@ -238,7 +243,7 @@ const QuickTips = () => { wsh edit [filename] -
Edit config and code files
+
{t("quickTips.wshEditDesc")}
@@ -246,7 +251,7 @@ const QuickTips = () => {
- More Tips + {t("quickTips.moreTips")}
@@ -254,7 +259,7 @@ const QuickTips = () => { - Tabs - Right click any tab to change backgrounds or rename. + {t("quickTips.tabsLabel")} - {t("quickTips.tabsBody")}
@@ -262,7 +267,7 @@ const QuickTips = () => { - Web View - Click the gear in the web view to set your homepage + {t("quickTips.webViewLabel")} - {t("quickTips.webViewBody")}
@@ -270,7 +275,7 @@ const QuickTips = () => { - Terminal - Click the gear in the terminal to set your terminal theme and font size + {t("quickTips.terminalLabel")} - {t("quickTips.terminalBody")}
@@ -279,7 +284,7 @@ const QuickTips = () => {
- Need More Help? + {t("quickTips.needMoreHelp")}
@@ -292,7 +297,7 @@ const QuickTips = () => { rel="noopener" className="hover:text-accent-400 hover:underline transition-colors font-medium" > - Join Our Discord + {t("quickTips.joinDiscord")}
@@ -305,7 +310,7 @@ const QuickTips = () => { rel="noopener" className="hover:text-accent-400 hover:underline transition-colors font-medium" > - Configuration Options + {t("quickTips.configOptions")}
@@ -318,7 +323,7 @@ const QuickTips = () => { rel="noopener" className="hover:text-accent-400 hover:underline transition-colors font-medium" > - All Keybindings + {t("quickTips.allKeybindings")}
@@ -331,7 +336,7 @@ const QuickTips = () => { rel="noopener" className="hover:text-accent-400 hover:underline transition-colors font-medium" > - Full Documentation + {t("quickTips.fullDocumentation")}
diff --git a/frontend/app/element/search.tsx b/frontend/app/element/search.tsx index e09e8a1078..75f41a2b7c 100644 --- a/frontend/app/element/search.tsx +++ b/frontend/app/element/search.tsx @@ -5,6 +5,7 @@ import { autoUpdate, FloatingPortal, Middleware, offset, useFloating } from "@fl import clsx from "clsx"; import { atom, useAtom, WritableAtom } from "jotai"; import { memo, useCallback, useEffect, useMemo, useRef } from "react"; +import { useTranslation } from "react-i18next"; import { IconButton, ToggleIconButton } from "./iconbutton"; import { Input } from "./input"; import "./search.scss"; @@ -34,6 +35,7 @@ const SearchComponent = ({ onNext, onPrev, }: SearchProps) => { + const { t } = useTranslation(); const [isOpen, setIsOpen] = useAtom(isOpenAtom); const [search, setSearch] = useAtom(searchAtom); const [index, setIndex] = useAtom(indexAtom); @@ -129,7 +131,7 @@ const SearchComponent = ({ const prevDecl: IconButtonDecl = { elemtype: "iconbutton", icon: "chevron-up", - title: "Previous Result (Shift+Enter)", + title: t("element.searchPreviousResult"), disabled: numResults === 0, click: onPrevWrapper, }; @@ -137,7 +139,7 @@ const SearchComponent = ({ const nextDecl: IconButtonDecl = { elemtype: "iconbutton", icon: "chevron-down", - title: "Next Result (Enter)", + title: t("element.searchNextResult"), disabled: numResults === 0, click: onNextWrapper, }; @@ -145,13 +147,17 @@ const SearchComponent = ({ const closeDecl: IconButtonDecl = { elemtype: "iconbutton", icon: "xmark-large", - title: "Close (Esc)", + title: t("element.searchCloseEsc"), click: () => setIsOpen(false), }; - const regexDecl = createToggleButtonDecl(regexAtom, "custom@regex", "Regular Expression"); - const wholeWordDecl = createToggleButtonDecl(wholeWordAtom, "custom@whole-word", "Whole Word"); - const caseSensitiveDecl = createToggleButtonDecl(caseSensitiveAtom, "custom@case-sensitive", "Case Sensitive"); + const regexDecl = createToggleButtonDecl(regexAtom, "custom@regex", t("element.searchRegularExpression")); + const wholeWordDecl = createToggleButtonDecl(wholeWordAtom, "custom@whole-word", t("element.searchWholeWord")); + const caseSensitiveDecl = createToggleButtonDecl( + caseSensitiveAtom, + "custom@case-sensitive", + t("element.searchCaseSensitive") + ); return ( <> @@ -160,7 +166,7 @@ const SearchComponent = ({
{index + 1}/{numResults}
diff --git a/frontend/app/element/streamdown.tsx b/frontend/app/element/streamdown.tsx index 2426f385e2..b6a8346357 100644 --- a/frontend/app/element/streamdown.tsx +++ b/frontend/app/element/streamdown.tsx @@ -161,7 +161,7 @@ const CodeBlock = ({ children, onClickExecute, codeBlockMaxWidthAtom }: CodeBloc
{language}
- + {onClickExecute && ( { + afterEach(() => { + setLanguage("en"); + }); + + it("renders English strings by default", () => { + const markup = renderToStaticMarkup( + null} /> + ); + expect(markup).toContain("Open-Source AI-Integrated Terminal"); + expect(markup).toContain("Client Version 0.14.5 (test)"); + }); + + it("renders German strings once the language is switched", () => { + setLanguage("de"); + const markup = renderToStaticMarkup( + null} /> + ); + expect(markup).toContain("Open-Source-KI-Terminal"); + expect(markup).toContain("Client-Version 0.14.5 (test)"); + }); +}); diff --git a/frontend/app/modals/about.tsx b/frontend/app/modals/about.tsx index d3a43d386d..1726e914b9 100644 --- a/frontend/app/modals/about.tsx +++ b/frontend/app/modals/about.tsx @@ -7,10 +7,12 @@ import { atoms } from "@/app/store/global"; import { modalsModel } from "@/app/store/modalmodel"; import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; +import { languageAtom, SUPPORTED_LANGUAGES, SupportedLanguage } from "@/util/i18n/i18n"; import { isDev } from "@/util/isdev"; import { fireAndForget } from "@/util/util"; import { useAtomValue } from "jotai"; import { useEffect } from "react"; +import { useTranslation } from "react-i18next"; import { Modal } from "./modal"; interface AboutModalVProps { @@ -21,6 +23,8 @@ interface AboutModalVProps { const AboutModalV = ({ versionString, updaterChannel, onClose }: AboutModalVProps) => { const currentDate = new Date(); + const { t } = useTranslation(); + const language = useAtomValue(languageAtom); return ( @@ -28,17 +32,17 @@ const AboutModalV = ({ versionString, updaterChannel, onClose }: AboutModalVProp
-
Wave Terminal
+
{t("about.title")}
- Open-Source AI-Integrated Terminal + {t("about.tagline1")}
- Built for Seamless Workflows + {t("about.tagline2")}
- Client Version {versionString} + {t("about.clientVersion", { version: versionString })}
- Update Channel: {updaterChannel} + {t("about.updateChannel", { channel: updaterChannel })}
- © {currentDate.getFullYear()} Command Line Inc. + {t("about.copyright", { year: currentDate.getFullYear() })} +
+
+ {SUPPORTED_LANGUAGES.map((lang: SupportedLanguage) => ( + + ))}
diff --git a/frontend/app/modals/conntypeahead.tsx b/frontend/app/modals/conntypeahead.tsx index 95cf831e24..e90646af98 100644 --- a/frontend/app/modals/conntypeahead.tsx +++ b/frontend/app/modals/conntypeahead.tsx @@ -20,6 +20,8 @@ import * as keyutil from "@/util/keyutil"; import * as util from "@/util/util"; import * as jotai from "jotai"; import * as React from "react"; +import { useTranslation } from "react-i18next"; +import type { TFunction } from "i18next"; // newConnList -> connList => filteredList -> remoteItems -> sortedRemoteItems => remoteSuggestion // filteredList -> createNew @@ -114,6 +116,7 @@ function createFilteredLocalSuggestionItem( } function getReconnectItem( + t: TFunction, connStatus: ConnStatus, connSelected: string, blockId: string, @@ -126,7 +129,7 @@ function getReconnectItem( status: "connected", icon: "arrow-right-arrow-left", iconColor: "var(--grey-text-color)", - label: `Reconnect to ${connStatus.connection}`, + label: t("connTypeahead.reconnectTo", { connection: connStatus.connection }), value: "", onSelect: async (_: string) => { globalStore.set(changeConnModalAtom, false); @@ -142,6 +145,7 @@ function getReconnectItem( } function getLocalSuggestions( + t: TFunction, localName: string, connList: Array, connection: string, @@ -173,13 +177,14 @@ function getLocalSuggestions( return null; } const localSuggestions: SuggestionConnectionScope = { - headerText: "Local", + headerText: t("connTypeahead.local"), items: sortedSuggestionItems, }; return localSuggestions; } function getRemoteSuggestions( + t: TFunction, connList: Array, connection: string, connSelected: string, @@ -194,13 +199,14 @@ function getRemoteSuggestions( return null; } const remoteSuggestions: SuggestionConnectionScope = { - headerText: "Remote", + headerText: t("connTypeahead.remote"), items: sortedSuggestionItems, }; return remoteSuggestions; } function getDisconnectItem( + t: TFunction, connection: string, connStatusMap: Map, changeConnModalAtom: jotai.PrimitiveAtom @@ -216,7 +222,7 @@ function getDisconnectItem( status: "connected", icon: "xmark", iconColor: "var(--grey-text-color)", - label: `Disconnect ${connStatus.connection}`, + label: t("connTypeahead.disconnect", { connection: connStatus.connection }), value: "", onSelect: async (_: string) => { globalStore.set(changeConnModalAtom, false); @@ -228,6 +234,7 @@ function getDisconnectItem( } function getConnectionsEditItem( + t: TFunction, changeConnModalAtom: jotai.PrimitiveAtom, connSelected: string ): SuggestionConnectionItem | null { @@ -239,7 +246,7 @@ function getConnectionsEditItem( icon: "gear", iconColor: "var(--grey-text-color)", value: "Edit Connections", - label: "Edit Connections", + label: t("connTypeahead.editConnections"), onSelect: () => { util.fireAndForget(async () => { globalStore.set(changeConnModalAtom, false); @@ -257,6 +264,7 @@ function getConnectionsEditItem( } function getNewConnectionSuggestionItem( + t: TFunction, connSelected: string, localName: string, remoteConns: Array, @@ -274,7 +282,7 @@ function getNewConnectionSuggestionItem( status: "connected", icon: "plus", iconColor: "var(--grey-text-color)", - label: `${connSelected} (New Connection)`, + label: t("connTypeahead.newConnection", { connection: connSelected }), value: "", onSelect: (_: string) => { changeConnection(connSelected); @@ -300,6 +308,7 @@ const ChangeConnectionBlockModal = React.memo( changeConnModalAtom: jotai.PrimitiveAtom; nodeModel: NodeModel; }) => { + const { t } = useTranslation(); const [connSelected, setConnSelected] = React.useState(""); const changeConnModalOpen = jotai.useAtomValue(changeConnModalAtom); const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); @@ -375,8 +384,9 @@ const ChangeConnectionBlockModal = React.memo( [blockId, blockData] ); - const reconnectSuggestionItem = getReconnectItem(connStatus, connSelected, blockId, changeConnModalAtom); + const reconnectSuggestionItem = getReconnectItem(t, connStatus, connSelected, blockId, changeConnModalAtom); const localSuggestions = getLocalSuggestions( + t, localName, wslList, connection, @@ -387,6 +397,7 @@ const ChangeConnectionBlockModal = React.memo( hasGitBash ); const remoteSuggestions = getRemoteSuggestions( + t, connList, connection, connSelected, @@ -394,9 +405,10 @@ const ChangeConnectionBlockModal = React.memo( fullConfig, filterOutNowsh ); - const connectionsEditItem = getConnectionsEditItem(changeConnModalAtom, connSelected); - const disconnectItem = getDisconnectItem(connection, connStatusMap, changeConnModalAtom); + const connectionsEditItem = getConnectionsEditItem(t, changeConnModalAtom, connSelected); + const disconnectItem = getDisconnectItem(t, connection, connStatusMap, changeConnModalAtom); const newConnectionSuggestionItem = getNewConnectionSuggestionItem( + t, connSelected, localName, connList, @@ -486,7 +498,7 @@ const ChangeConnectionBlockModal = React.memo( onKeyDown={(e) => keyutil.keydownWrapper(handleTypeAheadKeyDown)(e)} onChange={(current: string) => setConnSelected(current)} value={connSelected} - label="Connect to (username@host)..." + label={t("connTypeahead.connectPlaceholder")} onClickBackdrop={() => globalStore.set(changeConnModalAtom, false)} /> ); diff --git a/frontend/app/modals/modal.tsx b/frontend/app/modals/modal.tsx index 5733ec2608..06b8a1a7e7 100644 --- a/frontend/app/modals/modal.tsx +++ b/frontend/app/modals/modal.tsx @@ -6,6 +6,7 @@ import { cn } from "@/util/util"; import clsx from "clsx"; import { forwardRef } from "react"; import ReactDOM from "react-dom"; +import { useTranslation } from "react-i18next"; import "./modal.scss"; @@ -38,6 +39,7 @@ const Modal = forwardRef( }: ModalProps, ref ) => { + const { t } = useTranslation(); const renderBackdrop = (onClick) =>
; const renderFooter = () => { @@ -48,7 +50,7 @@ const Modal = forwardRef(
{renderBackdrop(onClickBackdrop)}
-
@@ -89,24 +91,18 @@ interface ModalFooterProps { cancelDisabled?: boolean; } -const ModalFooter = ({ - onCancel, - onOk, - cancelLabel = "Cancel", - okLabel = "Ok", - okDisabled, - cancelDisabled, -}: ModalFooterProps) => { +const ModalFooter = ({ onCancel, onOk, cancelLabel, okLabel, okDisabled, cancelDisabled }: ModalFooterProps) => { + const { t } = useTranslation(); return (
{onCancel && ( )} {onOk && ( )}
diff --git a/frontend/app/onboarding/fakechat.tsx b/frontend/app/onboarding/fakechat.tsx index 00eac0598c..e839f76c2e 100644 --- a/frontend/app/onboarding/fakechat.tsx +++ b/frontend/app/onboarding/fakechat.tsx @@ -3,6 +3,7 @@ import { WaveStreamdown } from "@/app/element/streamdown"; import { memo, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; interface ChatConfig { userPrompt: string; @@ -78,16 +79,19 @@ Need me to look at a longer slice (e.g., last 1000 lines) or a different time ra }, ]; -const AIThinking = memo(() => ( -
-
- - - +const AIThinking = memo(() => { + const { t } = useTranslation(); + return ( +
+
+ + + +
+ {t("aiPanel.aiThinking")}
- AI is thinking... -
-)); + ); +}); AIThinking.displayName = "AIThinking"; @@ -200,6 +204,7 @@ const FakeAssistantMessage = memo(({ config, onComplete }: { config: ChatConfig; FakeAssistantMessage.displayName = "FakeAssistantMessage"; const FakeAIPanelHeader = memo(() => { + const { t } = useTranslation(); return (

@@ -209,21 +214,21 @@ const FakeAIPanelHeader = memo(() => {
- Context + {t("aiPanel.context")}
diff --git a/frontend/app/onboarding/onboarding-durable.tsx b/frontend/app/onboarding/onboarding-durable.tsx index b716b3d7da..b6f1aa82e5 100644 --- a/frontend/app/onboarding/onboarding-durable.tsx +++ b/frontend/app/onboarding/onboarding-durable.tsx @@ -6,6 +6,7 @@ import { EmojiButton } from "@/app/element/emojibutton"; import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { useState } from "react"; +import { useTranslation } from "react-i18next"; import { CurrentOnboardingVersion } from "./onboarding-common"; import { OnboardingFooter } from "./onboarding-features-footer"; import { TailDeployLogCommand } from "./onboarding-layout-term"; @@ -19,6 +20,7 @@ export const DurableSessionPage = ({ onSkip: () => void; onPrev?: () => void; }) => { + const { t } = useTranslation(); const [fireClicked, setFireClicked] = useState(false); const handleFireClick = () => { @@ -40,38 +42,35 @@ export const DurableSessionPage = ({
-
Durable SSH Sessions
+
{t("onboardingFeatures.durableTitle")}
- SSH Sessions, Protected + {t("onboardingFeatures.durableBadge")}
-

Close your laptop, switch networks, restart Wave — your remote sessions keep running.

+

{t("onboardingFeatures.durableIntro")}

-

Shell state, running programs, and terminal history are all preserved

+

{t("onboardingFeatures.durableShellState")}

-

Sessions automatically reconnect when your connection is restored

+

{t("onboardingFeatures.durableReconnect")}

-

Buffered output streams back in, never miss a line

+

{t("onboardingFeatures.durableBuffered")}

-

- All the persistence of tmux, built into your terminal. Look for the shield icon to - enable durability on any SSH session. -

+

{t("onboardingFeatures.durableOutro")}

diff --git a/frontend/app/onboarding/onboarding-features-footer.tsx b/frontend/app/onboarding/onboarding-features-footer.tsx index 91909f6b53..73602ba542 100644 --- a/frontend/app/onboarding/onboarding-features-footer.tsx +++ b/frontend/app/onboarding/onboarding-features-footer.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { Button } from "@/app/element/button"; +import { useTranslation } from "react-i18next"; export const OnboardingFooter = ({ currentStep, @@ -16,19 +17,20 @@ export const OnboardingFooter = ({ onPrev?: () => void; onSkip?: () => void; }) => { + const { t } = useTranslation(); const isLastStep = currentStep === totalSteps; - const buttonText = isLastStep ? "Get Started" : "Next"; + const buttonText = isLastStep ? t("onboardingFeatures.getStarted") : t("onboardingFeatures.next"); return (
{currentStep > 1 && onPrev && ( )} - {currentStep} of {totalSteps} + {t("onboardingFeatures.stepOf", { current: currentStep, total: totalSteps })}
@@ -41,7 +43,7 @@ export const OnboardingFooter = ({ className="absolute right-0 top-1/2 -translate-y-1/2 text-muted cursor-pointer hover:text-muted-hover text-[13px]" onClick={onSkip} > - Skip Feature Tour > + {t("onboardingFeatures.skipFeatureTour")} > )}
diff --git a/frontend/app/onboarding/onboarding-features.tsx b/frontend/app/onboarding/onboarding-features.tsx index b47bbc4e1a..90b9ef1d1d 100644 --- a/frontend/app/onboarding/onboarding-features.tsx +++ b/frontend/app/onboarding/onboarding-features.tsx @@ -10,6 +10,7 @@ import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { isMacOS } from "@/util/platformutil"; import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import { FakeChat } from "./fakechat"; import { EditBashrcCommand, ViewLogoCommand, ViewShortcutsCommand } from "./onboarding-command"; import { CurrentOnboardingVersion } from "./onboarding-common"; @@ -20,6 +21,7 @@ import { FakeLayout } from "./onboarding-layout"; type FeaturePageName = "waveai" | "durable" | "magnify" | "files"; export const WaveAIPage = ({ onNext, onSkip }: { onNext: () => void; onSkip: () => void }) => { + const { t } = useTranslation(); const isMac = isMacOS(); const shortcutKey = isMac ? "⌘-Shift-A" : "Alt-Shift-A"; const [fireClicked, setFireClicked] = useState(false); @@ -54,40 +56,34 @@ export const WaveAIPage = ({ onNext, onSkip }: { onNext: () => void; onSkip: ()
-

- Wave AI is your terminal assistant with context. I can read your terminal output, - analyze widgets, read/write files, and help you solve problems faster. -

+

{t("onboardingFeatures.waveAIIntro")}

- Toggle the Wave AI panel with the{" "} + {t("onboardingFeatures.toggleWithButtonPrefix")}{" "} AI {" "} - button in the header (top left) + {t("onboardingFeatures.toggleWithButtonSuffix")}

- Or use the keyboard shortcut{" "} + {t("onboardingFeatures.toggleWithShortcutPrefix")}{" "} {shortcutKey} {" "} - to quickly toggle + {t("onboardingFeatures.toggleWithShortcutSuffix")}

-

- Bring your own API keys or run local models with Ollama, LM Studio, and other - OpenAI-compatible providers -

+

{t("onboardingFeatures.byokInfo")}

@@ -115,6 +111,7 @@ export const MagnifyBlocksPage = ({ onSkip: () => void; onPrev?: () => void; }) => { + const { t } = useTranslation(); const isMac = isMacOS(); const shortcutKey = isMac ? "⌘" : "Alt"; const [fireClicked, setFireClicked] = useState(false); @@ -138,27 +135,22 @@ export const MagnifyBlocksPage = ({
-
Magnify Blocks
+
{t("onboardingFeatures.magnifyTitle")}
{shortcutKey}-M
-

- Magnify any block to focus on what matters. Expand terminals, editors, and previews for a - better view. -

-

Use the magnify feature to work with complex outputs and large files more efficiently.

+

{t("onboardingFeatures.magnifyIntro")}

+

{t("onboardingFeatures.magnifyUsage")}

- You can also magnify a block by clicking on the{" "} + {t("onboardingFeatures.magnifyIconPrefix")}{" "} {" "} - icon in the block header. + {t("onboardingFeatures.magnifyIconSuffix")}
-

- A quick {shortcutKey}-M to magnify and another {shortcutKey}-M to unmagnify -

+

{t("onboardingFeatures.magnifyShortcutHint", { key: shortcutKey })}

@@ -173,6 +165,7 @@ export const MagnifyBlocksPage = ({ }; export const FilesPage = ({ onFinish, onPrev }: { onFinish: () => void; onPrev?: () => void }) => { + const { t } = useTranslation(); const [fireClicked, setFireClicked] = useState(false); const isMac = isMacOS(); const [commandIndex, setCommandIndex] = useState(0); @@ -208,26 +201,23 @@ export const FilesPage = ({ onFinish, onPrev }: { onFinish: () => void; onPrev?:
-
Viewing/Editing Files
+
{t("onboardingFeatures.filesTitle")}
-

- Wave can preview markdown, images, and video files on both local and remote{" "} - machines. -

+

{t("onboardingFeatures.filesIntro")}

- Use{" "} + {t("onboardingFeatures.filesViewPrefix")}{" "} wsh view [filename] {" "} - to preview files in Wave's graphical viewer + {t("onboardingFeatures.filesViewSuffix")}

@@ -236,19 +226,16 @@ export const FilesPage = ({ onFinish, onPrev }: { onFinish: () => void; onPrev?:

- Use{" "} + {t("onboardingFeatures.filesEditPrefix")}{" "} wsh edit [filename] {" "} - to open config files or code files in Wave's graphical editor + {t("onboardingFeatures.filesEditSuffix")}

-

- These commands work seamlessly on both local and remote machines, making it easy to view - and edit files wherever they are. -

+

{t("onboardingFeatures.filesOutro")}

diff --git a/frontend/app/onboarding/onboarding-layout-term.tsx b/frontend/app/onboarding/onboarding-layout-term.tsx index 95a4bde9e9..5e37019cb8 100644 --- a/frontend/app/onboarding/onboarding-layout-term.tsx +++ b/frontend/app/onboarding/onboarding-layout-term.tsx @@ -4,6 +4,7 @@ import { MagnifyIcon } from "@/app/element/magnify"; import { cn, makeIconClass } from "@/util/util"; import { useCallback, useLayoutEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import { CommandReveal } from "./onboarding-command"; export type FakeTermBlockProps = { @@ -88,6 +89,7 @@ const deployMessages = [ type OverlayState = null | "disconnected" | "connected"; const ConnectionOverlay = ({ state }: { state: OverlayState }) => { + const { t } = useTranslation(); if (!state) return null; const isConnected = state === "connected"; @@ -103,7 +105,7 @@ const ConnectionOverlay = ({ state }: { state: OverlayState }) => { )} />
- {isConnected ? "Connected" : "Disconnected"} + {isConnected ? t("onboardingFeatures.connected") : t("onboardingFeatures.disconnected")}
diff --git a/frontend/app/onboarding/onboarding-layout.tsx b/frontend/app/onboarding/onboarding-layout.tsx index bbe2aece5f..2e9fc7a8d9 100644 --- a/frontend/app/onboarding/onboarding-layout.tsx +++ b/frontend/app/onboarding/onboarding-layout.tsx @@ -6,6 +6,7 @@ import { WaveStreamdown } from "@/app/element/streamdown"; import { CodeEditor } from "@/app/view/codeeditor/codeeditor"; import { cn, makeIconClass } from "@/util/util"; import { useLayoutEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; export type FakeBlockProps = { icon: string; @@ -72,6 +73,7 @@ export const FakeBlock = ({ }; export const FakeLayout = () => { + const { t } = useTranslation(); const layoutRef = useRef(null); const highlightedContainerRef = useRef(null); const [blockRect, setBlockRect] = useState<{ left: number; top: number; width: number; height: number } | null>( @@ -155,14 +157,19 @@ export const FakeLayout = () => { return (
- +
- +
- +
{blockRect && ( @@ -174,7 +181,7 @@ export const FakeLayout = () => { )} />
- +
)} diff --git a/frontend/app/onboarding/onboarding-starask.tsx b/frontend/app/onboarding/onboarding-starask.tsx index bb7678ab2a..7cad552566 100644 --- a/frontend/app/onboarding/onboarding-starask.tsx +++ b/frontend/app/onboarding/onboarding-starask.tsx @@ -7,6 +7,7 @@ import { ClientModel } from "@/app/store/client-model"; import * as WOS from "@/app/store/wos"; import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; +import { useTranslation } from "react-i18next"; type StarAskPageProps = { onClose: () => void; @@ -14,6 +15,7 @@ type StarAskPageProps = { }; export function StarAskPage({ onClose, page = "upgrade" }: StarAskPageProps) { + const { t } = useTranslation(); const handleStarClick = async () => { RpcApi.RecordTEventCommand( TabRpcClient, @@ -84,14 +86,11 @@ export function StarAskPage({ onClose, page = "upgrade" }: StarAskPageProps) {
-
Support open-source. Star Wave. ⭐
+
{t("onboardingStarAsk.header")}
-
- Wave is free, open-source, and open-model. Stars help us stay visible against closed - alternatives. One click makes a difference. -
+
{t("onboardingStarAsk.description")}
diff --git a/frontend/app/onboarding/onboarding-upgrade-minor.tsx b/frontend/app/onboarding/onboarding-upgrade-minor.tsx index b165c0ce7f..db25fac7d3 100644 --- a/frontend/app/onboarding/onboarding-upgrade-minor.tsx +++ b/frontend/app/onboarding/onboarding-upgrade-minor.tsx @@ -15,6 +15,7 @@ import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { OverlayScrollbarsComponent } from "overlayscrollbars-react"; import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import { debounce } from "throttle-debounce"; type UpgradeMinorWelcomePageProps = { @@ -24,13 +25,16 @@ type UpgradeMinorWelcomePageProps = { }; const UpgradeMinorWelcomePage = ({ onStarClick, onAlreadyStarred, onMaybeLater }: UpgradeMinorWelcomePageProps) => { + const { t } = useTranslation(); return (
-
Welcome to Wave v0.14!
+
+ {t("onboardingUpgrade.minor.welcomeTitle")} +
- Durable SSH Sessions + + {t("onboardingUpgrade.minor.durableSshBadge")} +
+

{t("onboardingUpgrade.minor.waveAiIntro")}

- Wave AI is your terminal assistant with full context. It can read your terminal output, - analyze widgets, read and write files, and help you solve problems faster. -

-

- New in v0.13: Wave AI now - supports local models and bring-your-own-key! Use Ollama, LM Studio, vLLM, OpenRouter, - or any OpenAI-compatible provider. + + {t("onboardingUpgrade.minor.newInV013Label")} + {" "} + {t("onboardingUpgrade.minor.newInV013Body")}

- New in v0.14: Durable SSH - sessions survive network drops, laptop sleep, and restarts — all without tmux or screen. + + {t("onboardingUpgrade.minor.newInV014Label")} + {" "} + {t("onboardingUpgrade.minor.newInV014Body")}

@@ -68,10 +74,9 @@ const UpgradeMinorWelcomePage = ({ onStarClick, onAlreadyStarred, onMaybeLater }
-
Thanks for being an early Wave adopter! ⭐
+
{t("onboardingUpgrade.minor.thanksAdopter")}
- A GitHub star shows your support for Wave (and open-source) and helps us reach more - developers. + {t("onboardingUpgrade.minor.githubStarHint")}
@@ -79,13 +84,13 @@ const UpgradeMinorWelcomePage = ({ onStarClick, onAlreadyStarred, onMaybeLater }
diff --git a/frontend/app/onboarding/onboarding-upgrade-patch.tsx b/frontend/app/onboarding/onboarding-upgrade-patch.tsx index 87ffadb1e4..b6e8f3349c 100644 --- a/frontend/app/onboarding/onboarding-upgrade-patch.tsx +++ b/frontend/app/onboarding/onboarding-upgrade-patch.tsx @@ -16,6 +16,7 @@ import { TabRpcClient } from "@/app/store/wshrpcutil"; import { useAtomValue } from "jotai"; import { OverlayScrollbarsComponent } from "overlayscrollbars-react"; import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import { debounce } from "throttle-debounce"; import { UpgradeOnboardingModal_v0_12_1_Content } from "./onboarding-upgrade-v0121"; import { UpgradeOnboardingModal_v0_12_2_Content } from "./onboarding-upgrade-v0122"; @@ -31,8 +32,6 @@ import { UpgradeOnboardingModal_v0_14_5_Content } from "./onboarding-upgrade-v01 interface VersionConfig { version: string; content: () => React.ReactNode; - prevText?: string; - nextText?: string; } interface UpgradeOnboardingPatchProps { @@ -58,6 +57,7 @@ export function UpgradeOnboardingFooter({ onNext, onClose, }: UpgradeOnboardingFooterProps) { + const { t } = useTranslation(); return (
@@ -75,7 +75,7 @@ export function UpgradeOnboardingFooter({
@@ -96,67 +96,20 @@ export function UpgradeOnboardingFooter({ } export const UpgradeOnboardingVersions: VersionConfig[] = [ - { - version: "v0.12.1", - content: () => , - nextText: "Next (v0.12.2)", - }, - { - version: "v0.12.2", - content: () => , - prevText: "Prev (v0.12.1)", - nextText: "Next (v0.12.3)", - }, - { - version: "v0.12.5", - content: () => , - prevText: "Prev (v0.12.2)", - nextText: "Next (v0.13.0)", - }, - { - version: "v0.13.0", - content: () => , - prevText: "Prev (v0.12.5)", - nextText: "Next (v0.13.1)", - }, - { - version: "v0.13.1", - content: () => , - prevText: "Prev (v0.13.0)", - nextText: "Next (v0.14.0)", - }, - { - version: "v0.14.0", - content: () => , - prevText: "Prev (v0.13.1)", - nextText: "Next (v0.14.1)", - }, - { - version: "v0.14.1", - content: () => , - prevText: "Prev (v0.14.0)", - nextText: "Next (v0.14.3)", - }, - { - version: "v0.14.3", - content: () => , - prevText: "Prev (v0.14.1)", - nextText: "Next (v0.14.4)", - }, - { - version: "v0.14.4", - content: () => , - prevText: "Prev (v0.14.3)", - nextText: "Next (v0.14.5)", - }, - { - version: "v0.14.5", - content: () => , - prevText: "Prev (v0.14.4)", - }, + { version: "v0.12.1", content: () => }, + { version: "v0.12.2", content: () => }, + { version: "v0.12.5", content: () => }, + { version: "v0.13.0", content: () => }, + { version: "v0.13.1", content: () => }, + { version: "v0.14.0", content: () => }, + { version: "v0.14.1", content: () => }, + { version: "v0.14.3", content: () => }, + { version: "v0.14.4", content: () => }, + { version: "v0.14.5", content: () => }, ]; const UpgradeOnboardingPatch = ({ isReleaseNotes = false }: UpgradeOnboardingPatchProps) => { + const { t } = useTranslation(); const modalRef = useRef(null); const [isCompact, setIsCompact] = useState(window.innerHeight < 800); const [currentIndex, setCurrentIndex] = useState(UpgradeOnboardingVersions.length - 1); @@ -264,7 +217,7 @@ const UpgradeOnboardingPatch = ({ isReleaseNotes = false }: UpgradeOnboardingPat
- Wave {currentVersion.version} Update + {t("onboardingUpgrade.patch.updateTitle", { version: currentVersion.version })}
{ + const { t } = useTranslation(); return (
-

- Patch release focused on shell integration improvements, Wave AI enhancements, and restoring syntax - highlighting in code editor blocks. -

+

{t("onboardingUpgrade.v0121.intro")}

@@ -17,17 +17,17 @@ const UpgradeOnboardingModal_v0_12_1_Content = () => {
- Shell Integration & Context + {t("onboardingUpgrade.v0121.shellSectionTitle")}
  • - OSC 7 Support - Wave now automatically tracks and restores your current - directory across restarts for bash, zsh, fish, and pwsh shells + {t("onboardingUpgrade.v0121.osc7Label")} -{" "} + {t("onboardingUpgrade.v0121.osc7Body")}
  • - Shell Context Tracking - Tracks when your shell is ready, last command - executed, and exit codes for better terminal management + {t("onboardingUpgrade.v0121.shellContextLabel")} -{" "} + {t("onboardingUpgrade.v0121.shellContextBody")}
@@ -40,17 +40,14 @@ const UpgradeOnboardingModal_v0_12_1_Content = () => {
- Wave AI Improvements + {t("onboardingUpgrade.v0121.aiSectionTitle")}
    -
  • Display reasoning summaries while waiting for AI responses
  • -
  • - Enhanced terminal context - AI now has access to shell state, current directory, command - history, and exit codes -
  • -
  • Added feedback buttons (thumbs up/down) for AI responses
  • -
  • Added copy button to easily copy AI responses to clipboard
  • +
  • {t("onboardingUpgrade.v0121.aiItem1")}
  • +
  • {t("onboardingUpgrade.v0121.aiItem2")}
  • +
  • {t("onboardingUpgrade.v0121.aiItem3")}
  • +
  • {t("onboardingUpgrade.v0121.aiItem4")}
@@ -61,12 +58,14 @@ const UpgradeOnboardingModal_v0_12_1_Content = () => {
-
Other Changes
+
+ {t("onboardingUpgrade.v0121.otherSectionTitle")} +
    -
  • Mobile user agent emulation support for web widgets
  • -
  • Fixed padding for header buttons in code editor
  • -
  • Restored syntax highlighting in code editor preview blocks
  • +
  • {t("onboardingUpgrade.v0121.otherItem1")}
  • +
  • {t("onboardingUpgrade.v0121.otherItem2")}
  • +
  • {t("onboardingUpgrade.v0121.otherItem3")}
diff --git a/frontend/app/onboarding/onboarding-upgrade-v0122.tsx b/frontend/app/onboarding/onboarding-upgrade-v0122.tsx index 723672a357..0a4ba2932e 100644 --- a/frontend/app/onboarding/onboarding-upgrade-v0122.tsx +++ b/frontend/app/onboarding/onboarding-upgrade-v0122.tsx @@ -1,14 +1,14 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { useTranslation } from "react-i18next"; + const UpgradeOnboardingModal_v0_12_2_Content = () => { + const { t } = useTranslation(); return (
-

- Wave AI can now create and modify files with visual diff previews and easy rollback capabilities. - Plus performance improvements and bug fixes. -

+

{t("onboardingUpgrade.v0122.intro")}

@@ -16,19 +16,22 @@ const UpgradeOnboardingModal_v0_12_2_Content = () => {
-
Wave AI File Editing
+
+ {t("onboardingUpgrade.v0122.fileEditingSectionTitle")} +
  • - File Write Tool - Wave AI can now create and modify files with your - approval + {t("onboardingUpgrade.v0122.fileWriteToolLabel")} -{" "} + {t("onboardingUpgrade.v0122.fileWriteToolBody")}
  • - Visual Diff Preview - See exactly what will change before approving - edits + {t("onboardingUpgrade.v0122.diffPreviewLabel")} -{" "} + {t("onboardingUpgrade.v0122.diffPreviewBody")}
  • - Easy Rollback - Revert file changes with a simple "Revert File" button + {t("onboardingUpgrade.v0122.rollbackLabel")} -{" "} + {t("onboardingUpgrade.v0122.rollbackBody")}
@@ -41,16 +44,17 @@ const UpgradeOnboardingModal_v0_12_2_Content = () => {
- Additional AI Improvements + {t("onboardingUpgrade.v0122.aiSectionTitle")}
    -
  • Drag & drop files from preview viewer directly to Wave AI
  • +
  • {t("onboardingUpgrade.v0122.aiItem1")}
  • - Directory listings support in `wsh ai` commands + {t("onboardingUpgrade.v0122.aiItem2Prefix")}{" "} + `wsh ai` {t("onboardingUpgrade.v0122.aiItem2Suffix")}
  • -
  • Adjustable thinking level and max output tokens per chat
  • -
  • Improved tool descriptions and input validations
  • +
  • {t("onboardingUpgrade.v0122.aiItem3")}
  • +
  • {t("onboardingUpgrade.v0122.aiItem4")}
@@ -62,13 +66,13 @@ const UpgradeOnboardingModal_v0_12_2_Content = () => {
- Bug Fixes & Improvements + {t("onboardingUpgrade.v0122.bugFixesSectionTitle")}
    -
  • Fixed significant memory leak in the RPC system
  • -
  • Config file schema validation restored
  • -
  • Fixed PowerShell 5.x regression
  • +
  • {t("onboardingUpgrade.v0122.bugFixItem1")}
  • +
  • {t("onboardingUpgrade.v0122.bugFixItem2")}
  • +
  • {t("onboardingUpgrade.v0122.bugFixItem3")}
diff --git a/frontend/app/onboarding/onboarding-upgrade-v0123.tsx b/frontend/app/onboarding/onboarding-upgrade-v0123.tsx index 6999fccef4..85695c7fc6 100644 --- a/frontend/app/onboarding/onboarding-upgrade-v0123.tsx +++ b/frontend/app/onboarding/onboarding-upgrade-v0123.tsx @@ -1,14 +1,14 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { useTranslation } from "react-i18next"; + const UpgradeOnboardingModal_v0_12_3_Content = () => { + const { t } = useTranslation(); return (
-

- Wave AI model upgrade to GPT-5.1, new secret management features, and improved terminal input - handling for interactive CLI tools. -

+

{t("onboardingUpgrade.v0123.intro")}

@@ -16,18 +16,20 @@ const UpgradeOnboardingModal_v0_12_3_Content = () => {
-
Wave AI Updates
+
+ {t("onboardingUpgrade.v0123.aiSectionTitle")} +
  • - GPT-5.1 Model - Upgraded to OpenAI's GPT-5.1 model for improved - responses + {t("onboardingUpgrade.v0123.gpt51Label")} -{" "} + {t("onboardingUpgrade.v0123.gpt51Body")}
  • - Thinking Mode Toggle - New dropdown to select between Quick, Balanced, - and Deep thinking modes + {t("onboardingUpgrade.v0123.thinkingModeLabel")} -{" "} + {t("onboardingUpgrade.v0123.thinkingModeBody")}
  • -
  • Fixed path mismatch issue when restoring AI write file backups
  • +
  • {t("onboardingUpgrade.v0123.aiItem3")}
@@ -38,18 +40,21 @@ const UpgradeOnboardingModal_v0_12_3_Content = () => {
-
Terminal Improvements
+
+ {t("onboardingUpgrade.v0123.terminalSectionTitle")} +
  • - Enhanced Input Handling - Better support for CLI tools like Claude Code + {t("onboardingUpgrade.v0123.inputHandlingLabel")} -{" "} + {t("onboardingUpgrade.v0123.inputHandlingBody")}
  • - Image Paste Support - Paste images directly into terminal (saved to - temp files) + {t("onboardingUpgrade.v0123.imagePasteLabel")} -{" "} + {t("onboardingUpgrade.v0123.imagePasteBody")}
  • -
  • Shift+Enter now inserts newlines by default for multi-line commands
  • -
  • Fixed duplicate text issue when switching input methods (IME)
  • +
  • {t("onboardingUpgrade.v0123.terminalItem3")}
  • +
  • {t("onboardingUpgrade.v0123.terminalItem4")}
@@ -60,16 +65,19 @@ const UpgradeOnboardingModal_v0_12_3_Content = () => {
-
Secret Store
+
+ {t("onboardingUpgrade.v0123.secretStoreSectionTitle")} +
  • - Secret Management Widget - Store and manage sensitive credentials - securely + {t("onboardingUpgrade.v0123.secretWidgetLabel")} -{" "} + {t("onboardingUpgrade.v0123.secretWidgetBody")}
  • - Access secrets via CLI with wsh secret list/get/set{" "} - commands + {t("onboardingUpgrade.v0123.secretCliPrefix")}{" "} + wsh secret list/get/set{" "} + {t("onboardingUpgrade.v0123.secretCliSuffix")}
diff --git a/frontend/app/onboarding/onboarding-upgrade-v0130.tsx b/frontend/app/onboarding/onboarding-upgrade-v0130.tsx index 57ce06403e..ff7995dd4d 100644 --- a/frontend/app/onboarding/onboarding-upgrade-v0130.tsx +++ b/frontend/app/onboarding/onboarding-upgrade-v0130.tsx @@ -1,14 +1,14 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { useTranslation } from "react-i18next"; + const UpgradeOnboardingModal_v0_13_0_Content = () => { + const { t } = useTranslation(); return (
-

- Wave v0.13 brings local AI support, bring-your-own-key (BYOK), a redesigned configuration system, - and improved terminal functionality. -

+

{t("onboardingUpgrade.v0130.intro")}

@@ -16,22 +16,26 @@ const UpgradeOnboardingModal_v0_13_0_Content = () => {
-
Local AI & BYOK
+
+ {t("onboardingUpgrade.v0130.localAiSectionTitle")} +
  • - OpenAI-Compatible API - Connect to Ollama, LM Studio, vLLM, OpenRouter, - and other local or hosted models + {t("onboardingUpgrade.v0130.openaiCompatLabel")} -{" "} + {t("onboardingUpgrade.v0130.openaiCompatBody")}
  • - Google Gemini - Native support for Gemini models + {t("onboardingUpgrade.v0130.geminiLabel")} -{" "} + {t("onboardingUpgrade.v0130.geminiBody")}
  • - Provider Presets - Built-in configs for OpenAI, OpenRouter, Google, - Azure, and custom endpoints + {t("onboardingUpgrade.v0130.presetsLabel")} -{" "} + {t("onboardingUpgrade.v0130.presetsBody")}
  • - Multiple AI Modes - Easily switch between models and providers + {t("onboardingUpgrade.v0130.multiModeLabel")} -{" "} + {t("onboardingUpgrade.v0130.multiModeBody")}
@@ -43,19 +47,22 @@ const UpgradeOnboardingModal_v0_13_0_Content = () => {
-
Configuration Widget
+
+ {t("onboardingUpgrade.v0130.configSectionTitle")} +
  • - New Config Interface - Dedicated widget accessible from the sidebar + {t("onboardingUpgrade.v0130.newConfigLabel")} -{" "} + {t("onboardingUpgrade.v0130.newConfigBody")}
  • - Better Organization - Browse and edit settings with improved validation - and error handling + {t("onboardingUpgrade.v0130.betterOrgLabel")} -{" "} + {t("onboardingUpgrade.v0130.betterOrgBody")}
  • - Integrated Secrets - Manage API keys and credentials from the config - widget + {t("onboardingUpgrade.v0130.integratedSecretsLabel")} -{" "} + {t("onboardingUpgrade.v0130.integratedSecretsBody")}
@@ -67,18 +74,22 @@ const UpgradeOnboardingModal_v0_13_0_Content = () => {
-
Terminal Updates
+
+ {t("onboardingUpgrade.v0130.terminalSectionTitle")} +
  • - Bracketed Paste Mode - Enabled by default for better multi-line paste - behavior + {t("onboardingUpgrade.v0130.bracketedPasteLabel")} -{" "} + {t("onboardingUpgrade.v0130.bracketedPasteBody")}
  • - Windows Paste Fix - Ctrl+V now works as standard paste on Windows + {t("onboardingUpgrade.v0130.windowsPasteLabel")} -{" "} + {t("onboardingUpgrade.v0130.windowsPasteBody")}
  • - SSH Password Storage - Store SSH passwords in Wave's secret store + {t("onboardingUpgrade.v0130.sshPasswordLabel")} -{" "} + {t("onboardingUpgrade.v0130.sshPasswordBody")}
diff --git a/frontend/app/onboarding/onboarding-upgrade-v0131.tsx b/frontend/app/onboarding/onboarding-upgrade-v0131.tsx index 7584607384..a14ae66950 100644 --- a/frontend/app/onboarding/onboarding-upgrade-v0131.tsx +++ b/frontend/app/onboarding/onboarding-upgrade-v0131.tsx @@ -1,14 +1,14 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { useTranslation } from "react-i18next"; + const UpgradeOnboardingModal_v0_13_1_Content = () => { + const { t } = useTranslation(); return (
-

- Wave v0.13.1 focuses on Windows platform improvements, Wave AI visual updates, and enhanced - terminal navigation. -

+

{t("onboardingUpgrade.v0131.intro")}

@@ -17,23 +17,25 @@ const UpgradeOnboardingModal_v0_13_1_Content = () => {
- Windows Platform Enhancements + {t("onboardingUpgrade.v0131.windowsSectionTitle")}
  • - Integrated Window Layout - Cleaner interface with controls integrated - into the tab-bar header + {t("onboardingUpgrade.v0131.integratedLayoutLabel")} -{" "} + {t("onboardingUpgrade.v0131.integratedLayoutBody")}
  • - Git Bash Auto-Detection - Automatically detects Git Bash installations + {t("onboardingUpgrade.v0131.gitBashLabel")} -{" "} + {t("onboardingUpgrade.v0131.gitBashBody")}
  • - SSH Agent Fallback - Improved SSH agent support on Windows + {t("onboardingUpgrade.v0131.sshAgentLabel")} -{" "} + {t("onboardingUpgrade.v0131.sshAgentBody")}
  • - Updated Focus Keybinding - Wave AI focus key changed to Alt:0 on - Windows + {t("onboardingUpgrade.v0131.focusKeybindingLabel")} -{" "} + {t("onboardingUpgrade.v0131.focusKeybindingBody")}
@@ -45,16 +47,18 @@ const UpgradeOnboardingModal_v0_13_1_Content = () => {
-
Wave AI Updates
+
+ {t("onboardingUpgrade.v0131.aiSectionTitle")} +
  • - Refreshed Visual Design - Complete UI refresh with transparency - support for custom backgrounds + {t("onboardingUpgrade.v0131.visualDesignLabel")} -{" "} + {t("onboardingUpgrade.v0131.visualDesignBody")}
  • - BYOK Without Telemetry - Wave AI now works with bring-your-own-key and - local models without requiring telemetry + {t("onboardingUpgrade.v0131.byokLabel")} -{" "} + {t("onboardingUpgrade.v0131.byokBody")}
@@ -66,12 +70,14 @@ const UpgradeOnboardingModal_v0_13_1_Content = () => {
-
Terminal Improvements
+
+ {t("onboardingUpgrade.v0131.terminalSectionTitle")} +
  • - New Scrolling Keybindings - Added Shift+Home, Shift+End, - Shift+PageUp, and Shift+PageDown for better navigation + {t("onboardingUpgrade.v0131.scrollingKeybindingsLabel")} -{" "} + {t("onboardingUpgrade.v0131.scrollingKeybindingsBody")}
@@ -83,4 +89,4 @@ const UpgradeOnboardingModal_v0_13_1_Content = () => { UpgradeOnboardingModal_v0_13_1_Content.displayName = "UpgradeOnboardingModal_v0_13_1_Content"; -export { UpgradeOnboardingModal_v0_13_1_Content }; \ No newline at end of file +export { UpgradeOnboardingModal_v0_13_1_Content }; diff --git a/frontend/app/onboarding/onboarding-upgrade-v0140.tsx b/frontend/app/onboarding/onboarding-upgrade-v0140.tsx index 0102b691e2..5994fb5484 100644 --- a/frontend/app/onboarding/onboarding-upgrade-v0140.tsx +++ b/frontend/app/onboarding/onboarding-upgrade-v0140.tsx @@ -2,17 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 import { useWaveEnv } from "@/app/waveenv/waveenv"; +import { useTranslation } from "react-i18next"; const UpgradeOnboardingModal_v0_14_0_Content = () => { const waveEnv = useWaveEnv(); + const { t } = useTranslation(); return (
-

- Wave v0.14 introduces Durable Sessions. Enable them to keep your remote sessions alive through - network interruptions, computer sleep, and restarts — they'll automatically reconnect when your - connection is restored. -

+

{t("onboardingUpgrade.v0140.intro")}

@@ -21,25 +19,27 @@ const UpgradeOnboardingModal_v0_14_0_Content = () => {
- Durable SSH Sessions{" "} + {t("onboardingUpgrade.v0140.durableSectionTitle")}{" "}
  • - Session Protection - Programs and shell state survive disconnects + {t("onboardingUpgrade.v0140.sessionProtectionLabel")} -{" "} + {t("onboardingUpgrade.v0140.sessionProtectionBody")}
  • - Visual Status Indicators - Shield icons show status + {t("onboardingUpgrade.v0140.visualStatusLabel")} -{" "} + {t("onboardingUpgrade.v0140.visualStatusBody")}
  • - Flexible Configuration - Enable globally, per-connection, or - per-terminal + {t("onboardingUpgrade.v0140.flexibleConfigLabel")} -{" "} + {t("onboardingUpgrade.v0140.flexibleConfigBody")}
@@ -52,15 +52,17 @@ const UpgradeOnboardingModal_v0_14_0_Content = () => {
- Enhanced Connection Monitoring + {t("onboardingUpgrade.v0140.connectionMonitoringSectionTitle")}
  • - Connection Keepalives - Active monitoring with keepalive probes + {t("onboardingUpgrade.v0140.keepalivesLabel")} -{" "} + {t("onboardingUpgrade.v0140.keepalivesBody")}
  • - Stalled Connection Detection - Visual feedback for network issues + {t("onboardingUpgrade.v0140.stalledDetectionLabel")} -{" "} + {t("onboardingUpgrade.v0140.stalledDetectionBody")}
@@ -72,17 +74,21 @@ const UpgradeOnboardingModal_v0_14_0_Content = () => {
-
Wave AI Updates
+
+ {t("onboardingUpgrade.v0140.aiSectionTitle")} +
  • - Image Support - Vision capabilities for BYOK providers + {t("onboardingUpgrade.v0140.imageSupportLabel")} -{" "} + {t("onboardingUpgrade.v0140.imageSupportBody")}
  • - Stop Generation - Ability to stop AI responses mid-generation + {t("onboardingUpgrade.v0140.stopGenerationLabel")} -{" "} + {t("onboardingUpgrade.v0140.stopGenerationBody")}
  • - Improved Auto-scrolling + {t("onboardingUpgrade.v0140.autoScrollLabel")}
@@ -94,14 +100,18 @@ const UpgradeOnboardingModal_v0_14_0_Content = () => {
-
Terminal Improvements
+
+ {t("onboardingUpgrade.v0140.terminalSectionTitle")} +
  • - Enhanced Context Menu - Quick access to splits, themes, and more + {t("onboardingUpgrade.v0140.contextMenuLabel")} -{" "} + {t("onboardingUpgrade.v0140.contextMenuBody")}
  • - OSC 52 Clipboard Support - CLI apps can copy to system clipboard + {t("onboardingUpgrade.v0140.osc52Label")} -{" "} + {t("onboardingUpgrade.v0140.osc52Body")}
diff --git a/frontend/app/onboarding/onboarding-upgrade-v0141.tsx b/frontend/app/onboarding/onboarding-upgrade-v0141.tsx index 82b0b34aef..196e669972 100644 --- a/frontend/app/onboarding/onboarding-upgrade-v0141.tsx +++ b/frontend/app/onboarding/onboarding-upgrade-v0141.tsx @@ -1,14 +1,14 @@ // Copyright 2026, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { useTranslation } from "react-i18next"; + const UpgradeOnboardingModal_v0_14_1_Content = () => { + const { t } = useTranslation(); return (
-

- Wave v0.14.1 fixes several high-impact terminal bugs and adds new config options for focus, cursor - style, and block navigation. -

+

{t("onboardingUpgrade.v0141.intro")}

@@ -16,21 +16,27 @@ const UpgradeOnboardingModal_v0_14_1_Content = () => {
-
Terminal Fixes
+
+ {t("onboardingUpgrade.v0141.terminalFixesSectionTitle")} +
  • - Claude Code Scroll Fix - Fixed unexpected terminal scroll jumps + {t("onboardingUpgrade.v0141.scrollFixLabel")} -{" "} + {t("onboardingUpgrade.v0141.scrollFixBody")}
  • - IME Fix - Fixed Korean/CJK input losing or sticking characters + {t("onboardingUpgrade.v0141.imeFixLabel")} -{" "} + {t("onboardingUpgrade.v0141.imeFixBody")}
  • - Scroll Position on Resize - Terminal stays at bottom across resizes + {t("onboardingUpgrade.v0141.scrollResizeLabel")} -{" "} + {t("onboardingUpgrade.v0141.scrollResizeBody")}
  • - Terminal Scrollback Save - New context menu item and{" "} - wsh command to save scrollback to a file + {t("onboardingUpgrade.v0141.scrollbackSaveLabel")} -{" "} + {t("onboardingUpgrade.v0141.scrollbackSaveBodyPrefix")} wsh{" "} + {t("onboardingUpgrade.v0141.scrollbackSaveBodySuffix")}
@@ -42,22 +48,28 @@ const UpgradeOnboardingModal_v0_14_1_Content = () => {
-
New Config Options
+
+ {t("onboardingUpgrade.v0141.newConfigSectionTitle")} +
  • - Focus Follows Cursor - New app:focusfollowscursor setting - (off/on/term) + {t("onboardingUpgrade.v0141.focusFollowsCursorLabel")} -{" "} + {t("onboardingUpgrade.v0141.focusFollowsCursorBodyPrefix")}{" "} + app:focusfollowscursor{" "} + {t("onboardingUpgrade.v0141.focusFollowsCursorBodySuffix")}
  • - Terminal Cursor Style & Blink - Configure cursor shape and blink - per-block + {t("onboardingUpgrade.v0141.cursorStyleLabel")} -{" "} + {t("onboardingUpgrade.v0141.cursorStyleBody")}
  • - Vim-Style Block Navigation - Ctrl+Shift+H/J/K/L to navigate blocks + {t("onboardingUpgrade.v0141.vimNavLabel")} -{" "} + {t("onboardingUpgrade.v0141.vimNavBody")}
  • - New AI Providers - Added Groq and NanoGPT as built-in presets + {t("onboardingUpgrade.v0141.newProvidersLabel")} -{" "} + {t("onboardingUpgrade.v0141.newProvidersBody")}
diff --git a/frontend/app/onboarding/onboarding-upgrade-v0142.tsx b/frontend/app/onboarding/onboarding-upgrade-v0142.tsx index 2fb8c1bd82..2886dba88c 100644 --- a/frontend/app/onboarding/onboarding-upgrade-v0142.tsx +++ b/frontend/app/onboarding/onboarding-upgrade-v0142.tsx @@ -2,17 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 import { useWaveEnv } from "@/app/waveenv/waveenv"; +import { useTranslation } from "react-i18next"; const UpgradeOnboardingModal_v0_14_2_Content = () => { const waveEnv = useWaveEnv(); + const { t } = useTranslation(); return (
-

- Wave v0.14.2 introduces a new block badge system for at-a-glance status, along with directory - preview improvements and bug fixes. v0.14.3 is a patch release fixing a showstopper bug in - onboarding. -

+

{t("onboardingUpgrade.v0142.intro")}

@@ -20,34 +18,38 @@ const UpgradeOnboardingModal_v0_14_2_Content = () => {
-
Block & Tab Badges
+
+ {t("onboardingUpgrade.v0142.badgesSectionTitle")} +
  • - Block Badges Roll Up to Tabs - Blocks can display icon badges (with - color and priority) that are visible in the tab bar for at-a-glance status + {t("onboardingUpgrade.v0142.badgesRollUpLabel")} -{" "} + {t("onboardingUpgrade.v0142.badgesRollUpBody")}
  • - Bell Indicator On by Default - Terminal bell badge now lights up the - block and tab when your terminal rings (controlled by term:bellindicator) + {t("onboardingUpgrade.v0142.bellIndicatorLabel")} -{" "} + {t("onboardingUpgrade.v0142.bellIndicatorBodyPrefix")}{" "} + term:bellindicator + {t("onboardingUpgrade.v0142.bellIndicatorBodySuffix")}
  • wsh badge {" "} - - New command to set or clear badges from the CLI. Supports icons, colors, priorities, - and PID-linked badges + - {t("onboardingUpgrade.v0142.wshBadgeBody")}
  • - Claude Code Integration - Use wsh badge with Claude Code - hooks to surface AI task status as tab bar notifications{" "} + {t("onboardingUpgrade.v0142.claudeIntegrationLabel")} -{" "} + {t("onboardingUpgrade.v0142.claudeIntegrationBodyPrefix")} wsh badge{" "} + {t("onboardingUpgrade.v0142.claudeIntegrationBodySuffix")}{" "}
@@ -60,21 +62,25 @@ const UpgradeOnboardingModal_v0_14_2_Content = () => {
-
Other Changes
+
+ {t("onboardingUpgrade.v0142.otherSectionTitle")} +
  • - [v0.14.3] [bugfix] Fixed a showstopper onboarding bug + {t("onboardingUpgrade.v0142.v0143Tag")} + {t("onboardingUpgrade.v0142.v0143Body")}
  • - Directory Preview - Improved mod time formatting, zebra-striped rows, - better default sort, and YAML file support + {t("onboardingUpgrade.v0142.dirPreviewLabel")} -{" "} + {t("onboardingUpgrade.v0142.dirPreviewBody")}
  • - Search Bar - Clipboard and focus improvements + {t("onboardingUpgrade.v0142.searchBarLabel")} -{" "} + {t("onboardingUpgrade.v0142.searchBarBody")}
  • -
  • [bugfix] Fixed "New Window" hanging on GNOME desktops
  • -
  • [bugfix] Fixed "Save Session As..." focused window tracking bug
  • +
  • {t("onboardingUpgrade.v0142.bugfixNewWindow")}
  • +
  • {t("onboardingUpgrade.v0142.bugfixSaveSession")}
diff --git a/frontend/app/onboarding/onboarding-upgrade-v0144.tsx b/frontend/app/onboarding/onboarding-upgrade-v0144.tsx index 6fc50f8919..2e19832e44 100644 --- a/frontend/app/onboarding/onboarding-upgrade-v0144.tsx +++ b/frontend/app/onboarding/onboarding-upgrade-v0144.tsx @@ -1,14 +1,14 @@ // Copyright 2026, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { useTranslation } from "react-i18next"; + const UpgradeOnboardingModal_v0_14_4_Content = () => { + const { t } = useTranslation(); return (
-

- Wave v0.14.4 introduces vertical tabs, upgrades to xterm.js v6, and includes bug fixes and UI - improvements. -

+

{t("onboardingUpgrade.v0144.intro")}

@@ -16,13 +16,14 @@ const UpgradeOnboardingModal_v0_14_4_Content = () => {
-
Vertical Tab Bar
+
+ {t("onboardingUpgrade.v0144.verticalTabSectionTitle")} +
  • - New Vertical Tab Bar Option - Tabs can now be displayed vertically - along the side of the window for more horizontal space. Toggle between horizontal and - vertical layouts in settings. + {t("onboardingUpgrade.v0144.verticalTabLabel")} -{" "} + {t("onboardingUpgrade.v0144.verticalTabBody")}
@@ -34,12 +35,14 @@ const UpgradeOnboardingModal_v0_14_4_Content = () => {
-
Terminal Improvements
+
+ {t("onboardingUpgrade.v0144.terminalSectionTitle")} +
  • - xterm.js v6.0.0 Upgrade - Improved terminal compatibility and - rendering, resolving quirks with tools like Claude Code + {t("onboardingUpgrade.v0144.xtermUpgradeLabel")} -{" "} + {t("onboardingUpgrade.v0144.xtermUpgradeBody")}
@@ -51,26 +54,30 @@ const UpgradeOnboardingModal_v0_14_4_Content = () => {
-
Other Changes
+
+ {t("onboardingUpgrade.v0144.otherSectionTitle")} +
  • - macOS First Click - First click now focuses the clicked widget + {t("onboardingUpgrade.v0144.macFirstClickLabel")} -{" "} + {t("onboardingUpgrade.v0144.macFirstClickBody")}
  • backgrounds.json {" "} - - Renamed presets/bg.json to backgrounds.json + - {t("onboardingUpgrade.v0144.renamedPrefix")} presets/bg.json{" "} + {t("onboardingUpgrade.v0144.renamedTo")} backgrounds.json
  • - Config Errors Moved - Config errors to the WaveConfig view for less - clutter + {t("onboardingUpgrade.v0144.configErrorsLabel")} -{" "} + {t("onboardingUpgrade.v0144.configErrorsBody")}
  • -
  • WaveConfig now warns on Unsaved Changes
  • -
  • Preview streaming fixes for images/videos
  • -
  • Deprecated legacy AI widget has been removed
  • -
  • [bugfix] Fixed focus bug for newly created blocks
  • +
  • {t("onboardingUpgrade.v0144.unsavedChangesWarning")}
  • +
  • {t("onboardingUpgrade.v0144.previewStreamingFix")}
  • +
  • {t("onboardingUpgrade.v0144.legacyAiRemoved")}
  • +
  • {t("onboardingUpgrade.v0144.bugfixFocus")}
diff --git a/frontend/app/onboarding/onboarding-upgrade-v0145.tsx b/frontend/app/onboarding/onboarding-upgrade-v0145.tsx index be0b43cf4a..a9aa80b618 100644 --- a/frontend/app/onboarding/onboarding-upgrade-v0145.tsx +++ b/frontend/app/onboarding/onboarding-upgrade-v0145.tsx @@ -1,14 +1,14 @@ // Copyright 2026, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { useTranslation } from "react-i18next"; + const UpgradeOnboardingModal_v0_14_5_Content = () => { + const { t } = useTranslation(); return (
-

- Wave v0.14.5 introduces a new Process Viewer widget, several quality-of-life improvements, and a - fix for creating new config files from the Settings widget. -

+

{t("onboardingUpgrade.v0145.intro")}

@@ -16,11 +16,10 @@ const UpgradeOnboardingModal_v0_14_5_Content = () => {
-
Process Viewer
-
- New widget that displays running processes on local and remote machines, with CPU and memory - usage and sortable columns. +
+ {t("onboardingUpgrade.v0145.processViewerTitle")}
+
{t("onboardingUpgrade.v0145.processViewerBody")}
@@ -29,26 +28,30 @@ const UpgradeOnboardingModal_v0_14_5_Content = () => {
-
Other Changes
+
+ {t("onboardingUpgrade.v0145.otherSectionTitle")} +
  • - Quake Mode — global hotkey ( - app:globalhotkey) now toggles a Wave window visible and invisible + {t("onboardingUpgrade.v0145.quakeModeLabel")} —{" "} + {t("onboardingUpgrade.v0145.quakeModeBodyPrefix")} (app:globalhotkey){" "} + {t("onboardingUpgrade.v0145.quakeModeBodySuffix")}
  • - Drag & Drop Files into Terminal - to paste their quoted path + {t("onboardingUpgrade.v0145.dragDropLabel")}{" "} + {t("onboardingUpgrade.v0145.dragDropBody")}
  • - New app:showsplitbuttons setting adds split buttons to block headers + {t("onboardingUpgrade.v0145.splitButtonsPrefix")}{" "} + app:showsplitbuttons {t("onboardingUpgrade.v0145.splitButtonsSuffix")}
  • -
  • Toggle the widgets sidebar on and off from the View menu
  • -
  • F2 to rename the active tab
  • -
  • Mouse back/forward buttons now navigate in web widgets
  • +
  • {t("onboardingUpgrade.v0145.toggleSidebar")}
  • +
  • {t("onboardingUpgrade.v0145.f2Rename")}
  • +
  • {t("onboardingUpgrade.v0145.mouseNavigation")}
  • - [bugfix] Config files that didn't exist yet couldn't be - created or edited from the Settings widget + {t("onboardingUpgrade.v0145.bugfixTag")}{" "} + {t("onboardingUpgrade.v0145.bugfixConfigFiles")}
diff --git a/frontend/app/onboarding/onboarding.tsx b/frontend/app/onboarding/onboarding.tsx index ba139e81df..71fc4bd750 100644 --- a/frontend/app/onboarding/onboarding.tsx +++ b/frontend/app/onboarding/onboarding.tsx @@ -19,6 +19,7 @@ import { fireAndForget } from "@/util/util"; import { atom, PrimitiveAtom, useAtom, useAtomValue, useSetAtom } from "jotai"; import { OverlayScrollbarsComponent } from "overlayscrollbars-react"; import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import { debounce } from "throttle-debounce"; // Page flow: @@ -36,6 +37,7 @@ const InitPage = ({ isCompact: boolean; telemetryUpdateFn: (value: boolean) => Promise; }) => { + const { t } = useTranslation(); const telemetrySetting = useSettingsKeyAtom("telemetry:enabled"); const clientData = useAtomValue(ClientModel.getInstance().clientAtom); const [telemetryEnabled, setTelemetryEnabled] = useState(!!telemetrySetting); @@ -75,7 +77,7 @@ const InitPage = ({ ); }; - const label = telemetryEnabled ? "Enabled" : "Disabled"; + const label = telemetryEnabled ? t("onboarding.enabled") : t("onboarding.disabled"); return (
@@ -85,7 +87,9 @@ const InitPage = ({
-
Welcome to Wave Terminal
+
+ {t("onboarding.welcomeTitle")} +
-
Support us on GitHub
+
+ {t("onboarding.supportUsOnGithub")} +
- We're open source, open-model, and committed to providing a free terminal - for individual users. Please show your support by giving us a star on{" "} + {t("onboarding.githubSupportBody")}{" "} - Github (wavetermdev/waveterm) + {t("onboarding.githubLinkText")}
@@ -133,10 +138,11 @@ const InitPage = ({
-
Join our Community
+
+ {t("onboarding.joinCommunity")} +
- Get help, submit feature requests, report bugs, or just chat with fellow terminal - enthusiasts. + {t("onboarding.communityBody")}
- Join the Wave Discord Channel + {t("onboarding.discordLinkText")}
@@ -155,7 +161,7 @@ const InitPage = ({
- Anonymous usage data helps us improve features you use. + {t("onboarding.telemetryHint")}
- Privacy Policy + {t("onboarding.privacyPolicy")}
diff --git a/frontend/app/tab/vtabbar.tsx b/frontend/app/tab/vtabbar.tsx index e40bcfb374..90cb664e11 100644 --- a/frontend/app/tab/vtabbar.tsx +++ b/frontend/app/tab/vtabbar.tsx @@ -12,6 +12,7 @@ import { validateCssColor } from "@/util/color-validator"; import { cn, fireAndForget } from "@/util/util"; import { useAtomValue } from "jotai"; import { memo, useCallback, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import { buildTabBarContextMenu, buildTabContextMenu } from "./tabcontextmenu"; import { UpdateStatusBanner } from "./updatebanner"; import { VTab, VTabItem } from "./vtab"; @@ -20,6 +21,7 @@ import { WorkspaceSwitcher } from "./workspaceswitcher"; export type { VTabItem } from "./vtab"; const VTabBarAIButton = memo(() => { + const { t } = useTranslation(); const env = useWaveEnv(); const aiPanelOpen = useAtomValue(WorkspaceLayoutModel.getInstance().panelVisibleAtom); const hideAiButton = useAtomValue(env.getSettingsKeyAtom("app:hideaibutton")); @@ -35,7 +37,7 @@ const VTabBarAIButton = memo(() => { return ( { VTabBarAIButton.displayName = "VTabBarAIButton"; const MacOSHeader = memo(() => { + const { t } = useTranslation(); const env = useWaveEnv(); const isFullScreen = useAtomValue(env.atoms.isFullScreen); return ( @@ -69,7 +72,12 @@ const MacOSHeader = memo(() => { style={{ WebkitAppRegion: "no-drag" } as React.CSSProperties} > - + @@ -118,6 +126,7 @@ function VTabWrapper({ onDragEnd, onHoverChanged, }: VTabWrapperProps) { + const { t } = useTranslation(); const env = useWaveEnv(); const [tabData] = env.wos.useWaveObjectValue(makeORef("tab", tabId)); const badges = useAtomValue(getTabBadgeAtom(tabId, env)); @@ -156,10 +165,10 @@ function VTabWrapper({ (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - const menu = buildTabContextMenu(tabId, renameRef, () => onClose(), env); + const menu = buildTabContextMenu(tabId, renameRef, () => onClose(), env, t); env.showContextMenu(menu, e); }, - [tabId, onClose, env] + [tabId, onClose, env, t] ); return ( @@ -185,6 +194,7 @@ function VTabWrapper({ } export function VTabBar({ workspace, className }: VTabBarProps) { + const { t } = useTranslation(); const env = useWaveEnv(); const activeTabId = useAtomValue(env.atoms.staticTabId); const reinitVersion = useAtomValue(env.atoms.reinitVersion); @@ -318,10 +328,10 @@ export function VTabBar({ workspace, className }: VTabBarProps) { const handleTabBarContextMenu = useCallback( (e: React.MouseEvent) => { e.preventDefault(); - const menu = buildTabBarContextMenu(env); + const menu = buildTabBarContextMenu(env, t); env.showContextMenu(menu, e); }, - [env] + [env, t] ); return ( @@ -425,11 +435,11 @@ export function VTabBar({ workspace, className }: VTabBarProps) { onClick={() => env.electron.createTab()} onMouseEnter={() => setIsNewTabHovered(true)} onMouseLeave={() => setIsNewTabHovered(false)} - aria-label="New Tab" + aria-label={t("tabBar.newTab")} >
- New Tab + {t("tabBar.newTab")}
); diff --git a/frontend/app/tab/workspaceeditor.tsx b/frontend/app/tab/workspaceeditor.tsx index dee2ccb6e1..0ca524c680 100644 --- a/frontend/app/tab/workspaceeditor.tsx +++ b/frontend/app/tab/workspaceeditor.tsx @@ -1,6 +1,7 @@ import { fireAndForget, makeIconClass } from "@/util/util"; import clsx from "clsx"; import { memo, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import { Button } from "../element/button"; import { Input } from "../element/input"; import { WorkspaceService } from "../store/services"; @@ -80,6 +81,7 @@ const WorkspaceEditorComponent = ({ onIconChange, onDeleteWorkspace, }: WorkspaceEditorProps) => { + const { t } = useTranslation(); const inputRef = useRef(null); const [colors, setColors] = useState([]); @@ -115,7 +117,7 @@ const WorkspaceEditorComponent = ({
diff --git a/frontend/app/tab/workspaceswitcher.tsx b/frontend/app/tab/workspaceswitcher.tsx index 5cc17516ec..9b2e880bd0 100644 --- a/frontend/app/tab/workspaceswitcher.tsx +++ b/frontend/app/tab/workspaceswitcher.tsx @@ -17,6 +17,7 @@ import { atom, PrimitiveAtom, useAtom, useAtomValue, useSetAtom } from "jotai"; import { splitAtom } from "jotai/utils"; import { OverlayScrollbarsComponent } from "overlayscrollbars-react"; import { CSSProperties, forwardRef, useCallback, useEffect } from "react"; +import { useTranslation } from "react-i18next"; import WorkspaceSVG from "../asset/workspace.svg"; import { IconButton } from "../element/iconbutton"; import { globalStore } from "@/app/store/jotaiStore"; @@ -50,6 +51,7 @@ const workspaceMapAtom = atom([]); const workspaceSplitAtom = splitAtom(workspaceMapAtom); const editingWorkspaceAtom = atom(); const WorkspaceSwitcher = forwardRef((_, ref) => { + const { t } = useTranslation(); const env = useWaveEnv(); const setWorkspaceList = useSetAtom(workspaceMapAtom); const activeWorkspace = useAtomValueSafe(env.atoms.workspace); @@ -123,7 +125,9 @@ const WorkspaceSwitcher = forwardRef((_, ref) => { {workspaceIcon} -
{isActiveWorkspaceSaved ? "Switch workspace" : "Open workspace"}
+
+ {isActiveWorkspaceSaved ? t("tabBar.switchWorkspace") : t("tabBar.openWorkspace")} +
{workspaceList.map((entry, i) => ( @@ -138,14 +142,14 @@ const WorkspaceSwitcher = forwardRef((_, ref) => { -
Create new workspace
+
{t("tabBar.createNewWorkspace")}
) : ( saveWorkspace()}> -
Save workspace
+
{t("tabBar.saveWorkspace")}
)}
@@ -161,6 +165,7 @@ const WorkspaceSwitcherItem = ({ entryAtom: PrimitiveAtom; onDeleteWorkspace: (workspaceId: string) => void; }) => { + const { t } = useTranslation(); const env = useWaveEnv(); const activeWorkspace = useAtomValueSafe(env.atoms.workspace); const [workspaceEntry, setWorkspaceEntry] = useAtom(entryAtom); @@ -189,7 +194,7 @@ const WorkspaceSwitcherItem = ({ elemtype: "iconbutton", className: "edit", icon: "pencil", - title: "Edit workspace", + title: t("tabBar.editWorkspace"), click: (e) => { e.stopPropagation(); if (editingWorkspace === workspace.oid) { @@ -204,7 +209,7 @@ const WorkspaceSwitcherItem = ({ className: "window", noAction: true, icon: isCurrentWorkspace ? "check" : "window", - title: isCurrentWorkspace ? "This is your current workspace" : "This workspace is open", + title: isCurrentWorkspace ? t("tabBar.currentWorkspace") : t("tabBar.workspaceIsOpen"), }; const isEditing = editingWorkspace === workspace.oid; diff --git a/frontend/app/treeview/treeview.tsx b/frontend/app/treeview/treeview.tsx index 4481d2c68f..025f214679 100644 --- a/frontend/app/treeview/treeview.tsx +++ b/frontend/app/treeview/treeview.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { makeIconClass } from "@/util/util"; +import i18n from "@/util/i18n/i18n"; import { useVirtualizer } from "@tanstack/react-virtual"; import clsx from "clsx"; import React, { @@ -143,7 +144,7 @@ export function buildVisibleRows( parentId: id, depth: depth + 1, kind: "loading", - label: "Loading…", + label: i18n.t("treeView.loading"), }); return; } @@ -153,7 +154,9 @@ export function buildVisibleRows( parentId: id, depth: depth + 1, kind: "error", - label: node.staterror ? `Error: ${node.staterror}` : "Unable to load directory", + label: node.staterror + ? i18n.t("treeView.errorPrefix", { error: node.staterror }) + : i18n.t("treeView.unableToLoadDirectory"), }); return; } @@ -167,7 +170,7 @@ export function buildVisibleRows( parentId: id, depth: depth + 1, kind: "capped", - label: `Showing first ${capMax} entries`, + label: i18n.t("treeView.showingFirstEntries", { count: capMax }), }); } }; diff --git a/frontend/app/view/aifilediff/aifilediff.tsx b/frontend/app/view/aifilediff/aifilediff.tsx index 9d96d290c2..6934516e84 100644 --- a/frontend/app/view/aifilediff/aifilediff.tsx +++ b/frontend/app/view/aifilediff/aifilediff.tsx @@ -7,9 +7,11 @@ import { TabRpcClient } from "@/app/store/wshrpcutil"; import { DiffViewer } from "@/app/view/codeeditor/diffviewer"; import type { WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv"; import { globalStore } from "@/store/jotaiStore"; +import i18n from "@/util/i18n/i18n"; import { base64ToString } from "@/util/util"; import * as jotai from "jotai"; import { useEffect } from "react"; +import { useTranslation } from "react-i18next"; type DiffData = { original: string; @@ -48,7 +50,7 @@ export class AiFileDiffViewModel implements ViewModel { this.errorAtom = jotai.atom(null) as jotai.PrimitiveAtom; this.loadingAtom = jotai.atom(true); this.viewIcon = jotai.atom("file-lines"); - this.viewName = jotai.atom("AI Diff Viewer"); + this.viewName = jotai.atom(i18n.t("aiFileDiff.viewName")); this.viewText = jotai.atom((get) => { const diffData = get(this.diffDataAtom); return diffData?.fileName ?? ""; @@ -61,6 +63,7 @@ export class AiFileDiffViewModel implements ViewModel { } function AiFileDiffView({ blockId, model }: ViewComponentProps) { + const { t } = useTranslation(); const blockData = jotai.useAtomValue(model.blockAtom); const diffData = jotai.useAtomValue(model.diffDataAtom); const error = jotai.useAtomValue(model.errorAtom); @@ -73,13 +76,13 @@ function AiFileDiffView({ blockId, model }: ViewComponentProps -
Loading diff...
+
{t("aiFileDiff.loadingDiff")}
); } @@ -134,7 +137,7 @@ function AiFileDiffView({ blockId, model }: ViewComponentProps -
No diff data available
+
{t("aiFileDiff.noDiffDataAvailable")}
); } diff --git a/frontend/app/view/helpview/helpview.tsx b/frontend/app/view/helpview/helpview.tsx index 02f4db48b0..30f44889bf 100644 --- a/frontend/app/view/helpview/helpview.tsx +++ b/frontend/app/view/helpview/helpview.tsx @@ -5,6 +5,7 @@ import { globalStore, WOS } from "@/app/store/global"; import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { WebView, WebViewModel } from "@/app/view/webview/webview"; +import i18n from "@/util/i18n/i18n"; import { atom } from "jotai"; const docsiteUrl = "https://docs.waveterm.dev/?ref=app"; @@ -43,7 +44,7 @@ class HelpViewModel extends WebViewModel { this.homepageUrl = atom(docsiteUrl); this.viewType = "help"; this.viewIcon = atom("circle-question"); - this.viewName = atom("Help"); + this.viewName = atom(i18n.t("helpView.viewName")); } setZoomFactor(factor: number | null) { @@ -84,7 +85,7 @@ class HelpViewModel extends WebViewModel { }; } zoomSubMenu.push({ - label: "Reset", + label: i18n.t("webview.reset"), click: () => { model.setZoomFactor(null); }, @@ -104,7 +105,9 @@ class HelpViewModel extends WebViewModel { return [ { - label: this.webviewRef.current?.isDevToolsOpened() ? "Close DevTools" : "Open DevTools", + label: this.webviewRef.current?.isDevToolsOpened() + ? i18n.t("webview.closeDevTools") + : i18n.t("webview.openDevTools"), click: async () => { if (this.webviewRef.current) { if (this.webviewRef.current.isDevToolsOpened()) { @@ -116,7 +119,7 @@ class HelpViewModel extends WebViewModel { }, }, { - label: "Set Zoom Factor", + label: i18n.t("webview.setZoomFactor"), submenu: zoomSubMenu, }, ]; diff --git a/frontend/app/view/launcher/launcher.tsx b/frontend/app/view/launcher/launcher.tsx index 7b5a935f19..7d105a6e33 100644 --- a/frontend/app/view/launcher/launcher.tsx +++ b/frontend/app/view/launcher/launcher.tsx @@ -6,10 +6,12 @@ import type { BlockNodeModel } from "@/app/block/blocktypes"; import { atoms, globalStore, replaceBlock } from "@/app/store/global"; import type { TabModel } from "@/app/store/tab-model"; import { checkKeyPressed, keydownWrapper } from "@/util/keyutil"; +import i18n from "@/util/i18n/i18n"; import { isBlank, makeIconClass } from "@/util/util"; import clsx from "clsx"; import { atom, useAtom, useAtomValue } from "jotai"; import React, { useEffect, useLayoutEffect, useRef } from "react"; +import { useTranslation } from "react-i18next"; function sortByDisplayOrder(wmap: { [key: string]: WidgetConfigType } | null | undefined): WidgetConfigType[] { if (!wmap) return []; @@ -26,7 +28,7 @@ export class LauncherViewModel implements ViewModel { tabModel: TabModel; viewType = "launcher"; viewIcon = atom("shapes"); - viewName = atom("Widget Launcher"); + viewName = atom(i18n.t("launcher.viewName")); viewComponent = LauncherView; noHeader = atom(true); inputRef = { current: null } as React.RefObject; @@ -138,6 +140,7 @@ export class LauncherViewModel implements ViewModel { } function LauncherView({ blockId, model }: ViewComponentProps) { + const { t } = useTranslation(); // Search and selection state const [searchTerm, setSearchTerm] = useAtom(model.searchTerm); const [selectedIndex, setSelectedIndex] = useAtom(model.selectedIndex); @@ -219,13 +222,17 @@ function LauncherView({ blockId, model }: ViewComponentProps) onKeyDown={keydownWrapper(model.keyDownHandler.bind(model))} onChange={(e) => setSearchTerm(e.target.value)} className="sr-only dummy" - aria-label="Search widgets" + aria-label={t("launcher.searchWidgets")} /> {/* Logo */} {showLogo && (
- Logo + {t("launcher.logoAlt")}
)} @@ -272,11 +279,12 @@ function LauncherView({ blockId, model }: ViewComponentProps) {/* Search instructions */}
{filteredWidgets.length === 0 ? ( - No widgets found. Press Escape to clear search. + {t("launcher.noWidgetsFound")} ) : ( - {searchTerm == "" ? "Type to Filter" : "Searching " + '"' + searchTerm + '"'}, Enter to Launch, - {searchTerm == "" ? "Arrow Keys to Navigate" : null} + {searchTerm == "" ? t("launcher.typeToFilter") : t("launcher.searching", { term: searchTerm })},{" "} + {t("launcher.enterToLaunch")} + {searchTerm == "" ? `, ${t("launcher.arrowKeysToNavigate")}` : null} )}
diff --git a/frontend/app/view/preview/entry-manager.tsx b/frontend/app/view/preview/entry-manager.tsx index 08ee00b13f..0c1d2d66aa 100644 --- a/frontend/app/view/preview/entry-manager.tsx +++ b/frontend/app/view/preview/entry-manager.tsx @@ -4,6 +4,7 @@ import { Button } from "@/app/element/button"; import { Input } from "@/app/element/input"; import React, { memo, useState } from "react"; +import { useTranslation } from "react-i18next"; export enum EntryManagerType { NewFile = "New File", @@ -31,10 +32,16 @@ export const EntryManagerOverlay = memo( style, getReferenceProps, }: EntryManagerOverlayProps) => { + const { t } = useTranslation(); const [value, setValue] = useState(startingValue); + const entryManagerTypeLabels: Record = { + [EntryManagerType.NewFile]: t("preview.newFile"), + [EntryManagerType.NewDirectory]: t("preview.newFolder"), + [EntryManagerType.EditName]: t("preview.rename"), + }; return (
-
{entryManagerType}
+
{entryManagerTypeLabels[entryManagerType]}
diff --git a/frontend/app/view/preview/preview-directory-utils.tsx b/frontend/app/view/preview/preview-directory-utils.tsx index e278475cac..406bd1e816 100644 --- a/frontend/app/view/preview/preview-directory-utils.tsx +++ b/frontend/app/view/preview/preview-directory-utils.tsx @@ -3,6 +3,7 @@ import { globalStore } from "@/app/store/jotaiStore"; import { TabRpcClient } from "@/app/store/wshrpcutil"; +import i18n from "@/util/i18n/i18n"; import { fireAndForget, isBlank } from "@/util/util"; import dayjs from "dayjs"; import React from "react"; @@ -102,7 +103,7 @@ export function handleRename( const errorText = `${e}`; console.warn(`Rename failed: ${errorText}`); const errorMsg: ErrorMsg = { - status: "Rename Failed", + status: i18n.t("preview.renameFailed"), text: `${e}`, }; setErrorMsg(errorMsg); @@ -130,19 +131,19 @@ export function handleFileDelete( let errorMsg: ErrorMsg; if (errorText.includes(recursiveError) && !recursive) { errorMsg = { - status: "Confirm Delete Directory", - text: "Deleting a directory requires the recursive flag. Proceed?", + status: i18n.t("preview.confirmDeleteDirectory"), + text: i18n.t("preview.deleteRequiresRecursive"), level: "warning", buttons: [ { - text: "Delete Recursively", + text: i18n.t("preview.deleteRecursively"), onClick: () => handleFileDelete(model, path, true, setErrorMsg), }, ], }; } else { errorMsg = { - status: "Delete Failed", + status: i18n.t("preview.deleteFailed"), text: `${e}`, }; } @@ -157,10 +158,10 @@ export function makeDirectoryDefaultMenuItems(model: PreviewModel): ContextMenuI const showHiddenFiles = globalStore.get(model.showHiddenFiles) ?? true; return [ { - label: "Directory Sort Order", + label: i18n.t("preview.directorySortOrder"), submenu: [ { - label: "Name", + label: i18n.t("preview.name"), type: "checkbox", checked: defaultSort === "name", click: () => @@ -169,7 +170,7 @@ export function makeDirectoryDefaultMenuItems(model: PreviewModel): ContextMenuI ), }, { - label: "Last Modified", + label: i18n.t("preview.lastModified"), type: "checkbox", checked: defaultSort === "modtime", click: () => @@ -180,10 +181,10 @@ export function makeDirectoryDefaultMenuItems(model: PreviewModel): ContextMenuI ], }, { - label: "Show Hidden Files", + label: i18n.t("preview.showHiddenFiles"), submenu: [ { - label: "On", + label: i18n.t("preview.on"), type: "checkbox", checked: showHiddenFiles, click: () => { @@ -194,7 +195,7 @@ export function makeDirectoryDefaultMenuItems(model: PreviewModel): ContextMenuI }, }, { - label: "Off", + label: i18n.t("preview.off"), type: "checkbox", checked: !showHiddenFiles, click: () => { diff --git a/frontend/app/view/preview/preview-directory.tsx b/frontend/app/view/preview/preview-directory.tsx index 0940ba43b3..66129d7143 100644 --- a/frontend/app/view/preview/preview-directory.tsx +++ b/frontend/app/view/preview/preview-directory.tsx @@ -5,6 +5,7 @@ import { ContextMenuModel } from "@/app/store/contextmenu"; import { globalStore } from "@/app/store/jotaiStore"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { useWaveEnv } from "@/app/waveenv/waveenv"; +import i18n from "@/util/i18n/i18n"; import { checkKeyPressed, isCharacterKeyEvent } from "@/util/keyutil"; import { PLATFORM, PlatformMacOS } from "@/util/platformutil"; import { addOpenMenuItems } from "@/util/previewutil"; @@ -148,35 +149,35 @@ function DirectoryTable({ }), columnHelper.accessor("name", { cell: (info) => {info.getValue()}, - header: () => Name, + header: () => {i18n.t("preview.name")}, sortingFn: "alphanumeric", size: 200, minSize: 90, }), columnHelper.accessor("modestr", { cell: (info) => {info.getValue()}, - header: () => Perm, + header: () => {i18n.t("preview.perm")}, size: 91, minSize: 90, sortingFn: "alphanumeric", }), columnHelper.accessor("modtime", { cell: (info) => {getLastModifiedTime(info.getValue())}, - header: () => Last Modified, + header: () => {i18n.t("preview.lastModified")}, size: 91, minSize: 65, sortingFn: "datetime", }), columnHelper.accessor("size", { cell: (info) => {getBestUnit(info.getValue())}, - header: () => Size, + header: () => {i18n.t("preview.size")}, size: 55, minSize: 50, sortingFn: "auto", }), columnHelper.accessor("mimetype", { cell: (info) => {cleanMimetype(info.getValue() ?? "")}, - header: () => Type, + header: () => {i18n.t("preview.type")}, size: 97, minSize: 97, sortingFn: "alphanumeric", @@ -370,19 +371,19 @@ function TableBody({ const fileName = finfo.path.split("/").pop(); const menu: ContextMenuItem[] = [ { - label: "New File", + label: i18n.t("preview.newFile"), click: () => { table.options.meta.newFile(); }, }, { - label: "New Folder", + label: i18n.t("preview.newFolder"), click: () => { table.options.meta.newDirectory(); }, }, { - label: "Rename", + label: i18n.t("preview.rename"), click: () => { table.options.meta.updateName(finfo.path, finfo.isdir); }, @@ -391,19 +392,19 @@ function TableBody({ type: "separator", }, { - label: "Copy File Name", + label: i18n.t("preview.copyFileName"), click: () => fireAndForget(() => navigator.clipboard.writeText(fileName)), }, { - label: "Copy Full File Name", + label: i18n.t("preview.copyFullFileName"), click: () => fireAndForget(() => navigator.clipboard.writeText(finfo.path)), }, { - label: "Copy File Name (Shell Quoted)", + label: i18n.t("preview.copyFileNameShellQuoted"), click: () => fireAndForget(() => navigator.clipboard.writeText(shellQuote([fileName]))), }, { - label: "Copy Full File Name (Shell Quoted)", + label: i18n.t("preview.copyFullFileNameShellQuoted"), click: () => fireAndForget(() => navigator.clipboard.writeText(shellQuote([finfo.path]))), }, ]; @@ -413,14 +414,14 @@ function TableBody({ type: "separator", }, { - label: "Default Settings", + label: i18n.t("preview.defaultSettings"), submenu: makeDirectoryDefaultMenuItems(model), }, { type: "separator", }, { - label: "Delete", + label: i18n.t("preview.delete"), click: () => handleFileDelete(model, finfo.path, false, setErrorMsg), } ); @@ -437,7 +438,9 @@ function TableBody({
{(searchActive || search !== "") && (
- {search === "" ? "Type to search (Esc to cancel)" : `Searching for "${search}"`} + + {search === "" ? i18n.t("preview.typeToSearch") : i18n.t("preview.searchingFor", { search })} +
{ @@ -607,7 +610,7 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) { } catch (e) { console.error("Directory Read Error", e); setErrorMsg({ - status: "Cannot Read Directory", + status: i18n.t("preview.cannotReadDirectory"), text: `${e}`, }); } @@ -722,19 +725,19 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) { let errorMsg: ErrorMsg; if (allowRetry) { errorMsg = { - status: "Confirm Overwrite File(s)", - text: "This copy operation will overwrite an existing file. Would you like to continue?", + status: i18n.t("preview.confirmOverwriteFiles"), + text: i18n.t("preview.overwriteConfirmText"), level: "warning", buttons: [ { - text: "Delete Then Copy", + text: i18n.t("preview.deleteThenCopy"), onClick: async () => { data.opts.overwrite = true; await handleDropCopy(data, isDir); }, }, { - text: "Sync", + text: i18n.t("preview.sync"), onClick: async () => { data.opts.merge = true; await handleDropCopy(data, isDir); @@ -744,7 +747,7 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) { }; } else { errorMsg = { - status: "Copy Failed", + status: i18n.t("preview.copyFailed"), text: copyError, level: "error", }; @@ -840,13 +843,13 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) { e.stopPropagation(); const menu: ContextMenuItem[] = [ { - label: "New File", + label: i18n.t("preview.newFile"), click: () => { newFile(); }, }, { - label: "New Folder", + label: i18n.t("preview.newFolder"), click: () => { newDirectory(); }, diff --git a/frontend/app/view/preview/preview-error-overlay.tsx b/frontend/app/view/preview/preview-error-overlay.tsx index 714e58dbf8..682f8b66fd 100644 --- a/frontend/app/view/preview/preview-error-overlay.tsx +++ b/frontend/app/view/preview/preview-error-overlay.tsx @@ -42,7 +42,6 @@ export const ErrorOverlay = memo(({ errorMsg, resetOverlay }: { errorMsg: ErrorM
{errorMsg.text}
diff --git a/frontend/app/view/preview/preview-model.tsx b/frontend/app/view/preview/preview-model.tsx index 8315e48b2a..e38164273d 100644 --- a/frontend/app/view/preview/preview-model.tsx +++ b/frontend/app/view/preview/preview-model.tsx @@ -21,15 +21,18 @@ import { createRef } from "react"; import { PreviewView } from "./preview"; import { makeDirectoryDefaultMenuItems } from "./preview-directory-utils"; import type { PreviewEnv } from "./previewenv"; +import i18n from "@/util/i18n/i18n"; // TODO drive this using config -const BOOKMARKS: { label: string; path: string }[] = [ - { label: "Home", path: "~" }, - { label: "Desktop", path: "~/Desktop" }, - { label: "Downloads", path: "~/Downloads" }, - { label: "Documents", path: "~/Documents" }, - { label: "Root", path: "/" }, -]; +function getBookmarks(): { label: string; path: string }[] { + return [ + { label: i18n.t("preview.home"), path: "~" }, + { label: i18n.t("preview.desktop"), path: "~/Desktop" }, + { label: i18n.t("preview.downloads"), path: "~/Downloads" }, + { label: i18n.t("preview.documents"), path: "~/Documents" }, + { label: i18n.t("preview.rootBookmark"), path: "/" }, + ]; +} const MaxFileSize = 1024 * 1024 * 10; // 10MB const MaxCSVSize = 1024 * 1024 * 1; // 1MB @@ -208,8 +211,8 @@ export class PreviewModel implements ViewModel { elemtype: "iconbutton", icon: "folder-open", longClick: (e: React.MouseEvent) => { - const menuItems: ContextMenuItem[] = BOOKMARKS.map((bookmark) => ({ - label: `Go to ${bookmark.label} (${bookmark.path})`, + const menuItems: ContextMenuItem[] = getBookmarks().map((bookmark) => ({ + label: i18n.t("preview.goTo", { label: bookmark.label, path: bookmark.path }), click: () => this.goHistory(bookmark.path), })); ContextMenuModel.getInstance().showContextMenu(menuItems, e); @@ -222,7 +225,7 @@ export class PreviewModel implements ViewModel { const blockData = get(this.blockAtom); return blockData?.meta?.edit ?? false; }); - this.viewName = atom("Preview"); + this.viewName = atom(i18n.t("blockUtil.preview")); this.hideViewName = atom(true); this.viewText = atom((get) => { let headerPath = get(this.metaFilePath); @@ -266,21 +269,21 @@ export class PreviewModel implements ViewModel { if (fileInfo.state != "hasData") { viewTextChildren.push({ elemtype: "textbutton", - text: "Loading ...", + text: i18n.t("preview.loadingEllipsis"), className: clsx(`grey rounded-[4px] !py-[2px] !px-[10px] text-[11px] font-[500]`), onClick: () => {}, }); } else if (fileInfo.data.readonly) { viewTextChildren.push({ elemtype: "textbutton", - text: "Read Only", + text: i18n.t("preview.readOnly"), className: clsx(`yellow rounded-[4px] !py-[2px] !px-[10px] text-[11px] font-[500]`), onClick: () => {}, }); } else { viewTextChildren.push({ elemtype: "textbutton", - text: "Save", + text: i18n.t("preview.save"), className: clsx(`${saveClassName} rounded-[4px] !py-[2px] !px-[10px] text-[11px] font-[500]`), onClick: () => fireAndForget(this.handleFileSave.bind(this)), }); @@ -288,7 +291,7 @@ export class PreviewModel implements ViewModel { if (get(this.canPreview)) { viewTextChildren.push({ elemtype: "textbutton", - text: "Preview", + text: i18n.t("preview.preview"), className: "grey rounded-[4px] !py-[2px] !px-[10px] text-[11px] font-[500]", onClick: () => fireAndForget(() => this.setEditMode(false)), }); @@ -296,7 +299,7 @@ export class PreviewModel implements ViewModel { } else if (get(this.canPreview)) { viewTextChildren.push({ elemtype: "textbutton", - text: "Edit", + text: i18n.t("preview.edit"), className: "grey rounded-[4px] !py-[2px] !px-[10px] text-[11px] font-[500]", onClick: () => fireAndForget(() => this.setEditMode(true)), }); @@ -338,7 +341,7 @@ export class PreviewModel implements ViewModel { { elemtype: "iconbutton", icon: showHiddenFiles ? "eye" : "eye-slash", - title: showHiddenFiles ? "Hide Hidden Files" : "Show Hidden Files", + title: showHiddenFiles ? i18n.t("preview.hideHiddenFiles") : i18n.t("preview.showHiddenFiles"), click: () => { globalStore.set(this.showHiddenFiles, (prev) => !prev); }, @@ -354,13 +357,13 @@ export class PreviewModel implements ViewModel { { elemtype: "iconbutton", icon: "book", - title: "Table of Contents", + title: i18n.t("element.tableOfContents"), click: () => this.markdownShowTocToggle(), }, { elemtype: "iconbutton", icon: "arrows-rotate", - title: "Refresh", + title: i18n.t("preview.refresh"), click: () => this.refreshCallback?.(), }, ] as IconButtonDecl[]; @@ -370,7 +373,7 @@ export class PreviewModel implements ViewModel { { elemtype: "iconbutton", icon: "arrows-rotate", - title: "Refresh", + title: i18n.t("preview.refresh"), click: () => this.refreshCallback?.(), }, ] as IconButtonDecl[]; @@ -416,7 +419,7 @@ export class PreviewModel implements ViewModel { return statFile; } catch (e) { const errorStatus: ErrorMsg = { - status: "File Read Failed", + status: i18n.t("preview.fileReadFailed"), text: `${e}`, }; globalStore.set(this.errorMsgAtom, errorStatus); @@ -446,7 +449,7 @@ export class PreviewModel implements ViewModel { return file; } catch (e) { const errorStatus: ErrorMsg = { - status: "File Read Failed", + status: i18n.t("preview.fileReadFailed"), text: `${e}`, }; globalStore.set(this.errorMsgAtom, errorStatus); @@ -508,29 +511,29 @@ export class PreviewModel implements ViewModel { const genErr = getFn(this.errorMsgAtom); if (!fileInfo) { - return { errorStr: `Load Error: ${genErr?.text}` }; + return { errorStr: `${i18n.t("preview.loadError")} ${genErr?.text}` }; } if (connErr != "") { - return { errorStr: `Connection Error: ${connErr}` }; + return { errorStr: `${i18n.t("preview.connectionError")} ${connErr}` }; } if (fileInfo?.notfound) { return { specializedView: "codeedit" }; } if (mimeType == null) { - return { errorStr: `Unable to determine mimetype for: ${fileInfo.path}` }; + return { errorStr: `${i18n.t("preview.unableToDetermineMimetype")} ${fileInfo.path}` }; } if (isStreamingType(mimeType)) { return { specializedView: "streaming" }; } if (!fileInfo) { const fileNameStr = fileName ? " " + JSON.stringify(fileName) : ""; - return { errorStr: "File Not Found" + fileNameStr }; + return { errorStr: i18n.t("preview.fileNotFound") + fileNameStr }; } if (fileInfo.size > MaxFileSize) { - return { errorStr: "File Too Large to Preview (10 MB Max)" }; + return { errorStr: i18n.t("preview.fileTooLarge") }; } if (mimeType == "text/csv" && fileInfo.size > MaxCSVSize) { - return { errorStr: "CSV File Too Large to Preview (1 MB Max)" }; + return { errorStr: i18n.t("preview.csvFileTooLarge") }; } if (mimeType == "directory") { return { specializedView: "directory" }; @@ -550,7 +553,7 @@ export class PreviewModel implements ViewModel { if (isTextFile(mimeType) || fileInfo.size == 0) { return { specializedView: "codeedit" }; } - return { errorStr: `Preview (${mimeType})` }; + return { errorStr: i18n.t("preview.previewMimeType", { mimeType }) }; } updateOpenFileModalAndError(isOpen, errorMsg = null) { @@ -667,7 +670,7 @@ export class PreviewModel implements ViewModel { console.log("saved file", filePath); } catch (e) { const errorStatus: ErrorMsg = { - status: "Save Failed", + status: i18n.t("preview.saveFailed"), text: `${e}`, }; globalStore.set(this.errorMsgAtom, errorStatus); @@ -706,7 +709,7 @@ export class PreviewModel implements ViewModel { const overrideFontSize = blockData?.meta?.["editor:fontsize"]; const menuItems: ContextMenuItem[] = []; menuItems.push({ - label: "Copy Full Path", + label: i18n.t("preview.copyFullPath"), click: () => fireAndForget(async () => { const filePath = await globalStore.get(this.statFilePath); @@ -724,7 +727,7 @@ export class PreviewModel implements ViewModel { }), }); menuItems.push({ - label: "Copy File Name", + label: i18n.t("preview.copyFileName"), click: () => fireAndForget(async () => { const fileInfo = await globalStore.get(this.statFile); @@ -758,7 +761,7 @@ export class PreviewModel implements ViewModel { } ); fontSizeSubMenu.unshift({ - label: "Default (" + defaultFontSize + "px)", + label: i18n.t("preview.defaultFontSize", { fontSize: defaultFontSize }), type: "checkbox", checked: overrideFontSize == null, click: () => { @@ -769,23 +772,23 @@ export class PreviewModel implements ViewModel { }, }); menuItems.push({ - label: "Editor Font Size", + label: i18n.t("preview.editorFontSize"), submenu: fontSizeSubMenu, }); if (globalStore.get(this.newFileContent) != null) { menuItems.push({ type: "separator" }); menuItems.push({ - label: "Save File", + label: i18n.t("preview.saveFile"), click: () => fireAndForget(this.handleFileSave.bind(this)), }); menuItems.push({ - label: "Revert File", + label: i18n.t("preview.revertFile"), click: () => fireAndForget(this.handleFileRevert.bind(this)), }); } menuItems.push({ type: "separator" }); menuItems.push({ - label: "Word Wrap", + label: i18n.t("preview.wordWrap"), type: "checkbox", checked: wordWrap, click: () => @@ -799,7 +802,7 @@ export class PreviewModel implements ViewModel { } if (loadableSV.state == "hasData" && loadableSV.data.specializedView == "directory") { menuItems.push({ type: "separator" }); - menuItems.push({ label: "Default Settings", enabled: false }); + menuItems.push({ label: i18n.t("preview.defaultSettings"), enabled: false }); menuItems.push(...makeDirectoryDefaultMenuItems(this)); } return menuItems; diff --git a/frontend/app/view/preview/preview-streaming.tsx b/frontend/app/view/preview/preview-streaming.tsx index 57f7305064..c55273173d 100644 --- a/frontend/app/view/preview/preview-streaming.tsx +++ b/frontend/app/view/preview/preview-streaming.tsx @@ -8,21 +8,23 @@ import { getWebServerEndpoint } from "@/util/endpoints"; import { formatRemoteUri } from "@/util/waveutil"; import { useAtomValue } from "jotai"; import { useEffect } from "react"; +import { useTranslation } from "react-i18next"; import { TransformComponent, TransformWrapper, useControls } from "react-zoom-pan-pinch"; import type { SpecializedViewProps } from "./preview"; function ImageZoomControls() { + const { t } = useTranslation(); const { zoomIn, zoomOut, resetTransform } = useControls(); return (
- - -
@@ -47,6 +49,7 @@ function StreamingImagePreview({ url }: { url: string }) { } function StreamingPreview({ model }: SpecializedViewProps) { + const { t } = useTranslation(); useEffect(() => { model.refreshCallback = () => { globalStore.set(model.refreshVersion, (v) => v + 1); @@ -86,7 +89,7 @@ function StreamingPreview({ model }: SpecializedViewProps) { if (fileInfo.mimetype.startsWith("image/")) { return ; } - return Preview Not Supported; + return {t("preview.previewNotSupported")}; } export { StreamingPreview }; diff --git a/frontend/app/view/preview/preview.tsx b/frontend/app/view/preview/preview.tsx index 87cf44678a..e415fa1006 100644 --- a/frontend/app/view/preview/preview.tsx +++ b/frontend/app/view/preview/preview.tsx @@ -9,6 +9,7 @@ import { useWaveEnv } from "@/app/waveenv/waveenv"; import { isBlank, makeConnRoute } from "@/util/util"; import { useAtom, useAtomValue, useSetAtom } from "jotai"; import { memo, useEffect } from "react"; +import { useTranslation } from "react-i18next"; import { CSVView } from "./csvview"; import { DirectoryPreview } from "./preview-directory"; import { CodeEditPreview } from "./preview-edit"; @@ -106,6 +107,7 @@ function PreviewView({ contentRef: React.RefObject; model: PreviewModel; }) { + const { t } = useTranslation(); const env = useWaveEnv(); const connStatus = useAtomValue(model.connStatus); const [errorMsg, setErrorMsg] = useAtom(model.errorMsgAtom); @@ -161,7 +163,7 @@ function PreviewView({ onSelect={handleSelect} onTab={handleTab} fetchSuggestions={fetchSuggestionsFn} - placeholderText="Open File..." + placeholderText={t("preview.openFile")} /> ); diff --git a/frontend/app/view/processviewer/processviewer.tsx b/frontend/app/view/processviewer/processviewer.tsx index d7f284bcf3..976ddd6bcc 100644 --- a/frontend/app/view/processviewer/processviewer.tsx +++ b/frontend/app/view/processviewer/processviewer.tsx @@ -7,10 +7,12 @@ import { globalStore } from "@/app/store/jotaiStore"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { MetaKeyAtomFnType, WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv"; import * as keyutil from "@/util/keyutil"; +import i18n from "@/util/i18n/i18n"; import { isMacOS } from "@/util/platformutil"; import { isBlank, makeConnRoute } from "@/util/util"; import * as jotai from "jotai"; import * as React from "react"; +import { useTranslation } from "react-i18next"; // ---- types ---- @@ -75,7 +77,7 @@ export class ProcessViewerViewModel implements ViewModel { env: ProcessViewerEnv; viewIcon = jotai.atom("microchip"); - viewName = jotai.atom("Processes"); + viewName = jotai.atom(i18n.t("processViewer.viewName")); manageConnection = jotai.atom(true); filterOutNowsh = jotai.atom(true); noPadding = jotai.atom(true); @@ -390,10 +392,10 @@ export class ProcessViewerViewModel implements ViewModel { async sendSignal(pid: number, signal: string, killLabel?: boolean) { const conn = globalStore.get(this.connection); const route = makeConnRoute(conn); - const label = killLabel ? "Killed" : `sent ${signal}`; + const label = killLabel ? i18n.t("processViewer.killed") : i18n.t("processViewer.sentSignal", { signal }); try { await this.env.rpc.RemoteProcessSignalCommand(TabRpcClient, { pid, signal }, { route }); - this.setActionStatus({ pid, message: `Process #${pid} ${label}`, isError: false }); + this.setActionStatus({ pid, message: i18n.t("processViewer.processLabel", { pid, label }), isError: false }); } catch (e) { this.setActionStatus({ pid, message: String(e), isError: true }); } @@ -424,23 +426,23 @@ export class ProcessViewerViewModel implements ViewModel { const currentInterval = globalStore.get(this.fetchIntervalAtom); return [ { - label: "Refresh Interval", + label: i18n.t("processViewer.refreshInterval"), type: "submenu", submenu: [ { - label: "1 second", + label: i18n.t("processViewer.oneSecond"), type: "checkbox", checked: currentInterval === 1000, click: () => this.setFetchInterval(1000), }, { - label: "2 seconds", + label: i18n.t("processViewer.nSeconds", { count: 2 }), type: "checkbox", checked: currentInterval === 2000, click: () => this.setFetchInterval(2000), }, { - label: "5 seconds", + label: i18n.t("processViewer.nSeconds", { count: 5 }), type: "checkbox", checked: currentInterval === 5000, click: () => this.setFetchInterval(5000), @@ -470,18 +472,32 @@ type ColDef = { hideOnPlatform?: string[]; }; -const Columns: ColDef[] = [ - { key: "pid", label: "PID", width: "70px", align: "right" }, - { key: "command", label: "Command", width: "minmax(120px, 4fr)" }, - { key: "status", label: "Status", width: "75px", hideOnPlatform: ["windows", "darwin"] }, - { key: "user", label: "User", width: "80px", hideOnPlatform: ["windows"] }, - { key: "threads", label: "NT", tooltip: "Num Threads", width: "40px", align: "right", hideOnPlatform: ["windows"] }, - { key: "cpu", label: "CPU%", width: "70px", align: "right" }, - { key: "mem", label: "Memory", width: "90px", align: "right" }, -]; +function getAllColumns(): ColDef[] { + return [ + { key: "pid", label: i18n.t("processViewer.colPid"), width: "70px", align: "right" }, + { key: "command", label: i18n.t("processViewer.colCommand"), width: "minmax(120px, 4fr)" }, + { + key: "status", + label: i18n.t("processViewer.colStatus"), + width: "75px", + hideOnPlatform: ["windows", "darwin"], + }, + { key: "user", label: i18n.t("processViewer.colUser"), width: "80px", hideOnPlatform: ["windows"] }, + { + key: "threads", + label: i18n.t("processViewer.colThreads"), + tooltip: i18n.t("processViewer.colThreadsTooltip"), + width: "40px", + align: "right", + hideOnPlatform: ["windows"], + }, + { key: "cpu", label: i18n.t("processViewer.colCpu"), width: "70px", align: "right" }, + { key: "mem", label: i18n.t("processViewer.colMem"), width: "90px", align: "right" }, + ]; +} function getColumns(platform: string): ColDef[] { - return Columns.filter((c) => !c.hideOnPlatform?.includes(platform)); + return getAllColumns().filter((c) => !c.hideOnPlatform?.includes(platform)); } function getGridTemplate(platform: string): string { @@ -499,6 +515,7 @@ const SortIndicator = React.memo(function SortIndicator({ active, desc }: { acti SortIndicator.displayName = "SortIndicator"; const StatusIndicator = React.memo(function StatusIndicator({ model }: { model: ProcessViewerViewModel }) { + const { t } = useTranslation(); const paused = jotai.useAtomValue(model.pausedAtom); const error = jotai.useAtomValue(model.errorAtom); const lastSuccess = jotai.useAtomValue(model.lastSuccessAtom); @@ -513,8 +530,8 @@ const StatusIndicator = React.memo(function StatusIndicator({ model }: { model: if (paused) { const tooltipContent = (
- Paused - Click to resume + {t("processViewer.paused")} + {t("processViewer.clickToResume")}
); return ( @@ -534,11 +551,11 @@ const StatusIndicator = React.memo(function StatusIndicator({ model }: { model: const stalled = lastSuccess > 0 && now - lastSuccess > 5000; const circleColor = error != null ? "text-error" : stalled ? "text-warning" : "text-success"; - const statusLabel = error != null ? "Error" : stalled ? "Stalled" : "Updating"; + const statusLabel = error != null ? t("processViewer.error") : stalled ? t("processViewer.stalled") : t("processViewer.updating"); const tooltipContent = (
{statusLabel} - Click to pause + {t("processViewer.clickToPause")}
); @@ -607,6 +624,7 @@ const ProcessRow = React.memo(function ProcessRow({ onSelect: (pid: number) => void; onContextMenu: (pid: number, e: React.MouseEvent) => void; }) { + const { t } = useTranslation(); const cols = getColumns(platform); const visibleKeys = new Set(cols.map((c) => c.key)); const gridTemplate = getGridTemplate(platform); @@ -621,7 +639,7 @@ const ProcessRow = React.memo(function ProcessRow({
{proc.pid}
-
(gone)
+
{t("processViewer.gone")}
{visibleKeys.has("status") &&
} {visibleKeys.has("user") &&
} {visibleKeys.has("threads") &&
} @@ -662,6 +680,7 @@ const ProcessRow = React.memo(function ProcessRow({ ProcessRow.displayName = "ProcessRow"; const ActionStatusBar = React.memo(function ActionStatusBar({ model }: { model: ProcessViewerViewModel }) { + const { t } = useTranslation(); const actionStatus = jotai.useAtomValue(model.actionStatusAtom); if (actionStatus == null) return null; @@ -670,7 +689,9 @@ const ActionStatusBar = React.memo(function ActionStatusBar({ model }: { model: className={`shrink-0 flex items-center px-3 py-1 text-xs border-t border-white/10 ${actionStatus.isError ? "text-error" : "text-secondary"}`} > - {actionStatus.isError ? `Error: ${actionStatus.message}` : actionStatus.message} + {actionStatus.isError + ? t("processViewer.errorPrefix", { message: actionStatus.message }) + : actionStatus.message} {actionStatus.isError && ( )} - {isRestarting &&
Starting...
} + {isRestarting &&
{t("tsunami.starting")}
}
); }); diff --git a/frontend/app/view/vdom/vdom-model.tsx b/frontend/app/view/vdom/vdom-model.tsx index 77b01495e2..d887fe36d4 100644 --- a/frontend/app/view/vdom/vdom-model.tsx +++ b/frontend/app/view/vdom/vdom-model.tsx @@ -13,6 +13,7 @@ import { DefaultRouter, TabRpcClient } from "@/app/store/wshrpcutil"; import { VDomView } from "@/app/view/vdom/vdom"; import { applyCanvasOp, mergeBackendUpdates, restoreVDomElems } from "@/app/view/vdom/vdom-utils"; import { getWebServerEndpoint } from "@/util/endpoints"; +import i18n from "@/util/i18n/i18n"; import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil"; import { PLATFORM, PlatformMacOS } from "@/util/platformutil"; import debug from "debug"; @@ -148,7 +149,7 @@ export class VDomModel { this.contextActive = jotai.atom(false); this.reset(); this.viewIcon = jotai.atom("bolt"); - this.viewName = jotai.atom("Wave App"); + this.viewName = jotai.atom(i18n.t("vdom.viewName")); this.backendRoute = jotai.atom((get) => { const blockData = get(WOS.getWaveObjectAtom(makeORef("block", this.blockId))); return blockData?.meta?.["vdom:route"]; diff --git a/frontend/app/view/vdom/vdom.tsx b/frontend/app/view/vdom/vdom.tsx index f4f8fcf073..cc25fea436 100644 --- a/frontend/app/view/vdom/vdom.tsx +++ b/frontend/app/view/vdom/vdom.tsx @@ -4,6 +4,7 @@ import { Markdown } from "@/app/element/markdown"; import { VDomModel } from "@/app/view/vdom/vdom-model"; import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil"; +import i18n from "@/util/i18n/i18n"; import clsx from "clsx"; import debug from "debug"; import * as jotai from "jotai"; @@ -426,7 +427,7 @@ function VDomTag({ elem, model }: { elem: VDomElem; model: VDomModel }) { return ; } if (!AllowedSimpleTags[elem.tag] && !AllowedSvgTags[elem.tag]) { - return
{"Invalid Tag <" + elem.tag + ">"}
; + return
{i18n.t("vdom.invalidTag", { tag: elem.tag })}
; } let childrenComps = convertChildren(elem, model); if (elem.tag == FragmentTag) { diff --git a/frontend/app/view/waveai/waveai.tsx b/frontend/app/view/waveai/waveai.tsx index baf6acf711..d14ee22819 100644 --- a/frontend/app/view/waveai/waveai.tsx +++ b/frontend/app/view/waveai/waveai.tsx @@ -3,13 +3,15 @@ import { Button } from "@/app/element/button"; import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model"; +import i18n from "@/util/i18n/i18n"; import { atom } from "jotai"; import { useCallback } from "react"; +import { useTranslation } from "react-i18next"; export class WaveAiModel implements ViewModel { viewType = "waveai"; viewIcon = atom("sparkles"); - viewName = atom("Wave AI"); + viewName = atom(i18n.t("waveAiLegacy.viewName")); noPadding = atom(true); viewComponent = WaveAiDeprecatedView; @@ -17,6 +19,7 @@ export class WaveAiModel implements ViewModel { } function WaveAiDeprecatedView() { + const { t } = useTranslation(); const handleOpenAIPanel = useCallback(() => { WorkspaceLayoutModel.getInstance().setAIPanelVisible(true); }, []); @@ -25,13 +28,10 @@ function WaveAiDeprecatedView() {
-

This legacy Wave AI block is no longer supported

-

- This older AI widget has been retired. Please use the modern Wave AI panel for AI chats, terminal - context, tools, and uploads going forward. -

+

{t("waveAiLegacy.noLongerSupported")}

+

{t("waveAiLegacy.retiredMessage")}

diff --git a/frontend/app/view/waveconfig/secretscontent.tsx b/frontend/app/view/waveconfig/secretscontent.tsx index bde67a31cb..48eab6bc05 100644 --- a/frontend/app/view/waveconfig/secretscontent.tsx +++ b/frontend/app/view/waveconfig/secretscontent.tsx @@ -5,6 +5,7 @@ import { SecretNameRegex, type WaveConfigViewModel } from "@/app/view/waveconfig import { cn } from "@/util/util"; import { useAtomValue, useSetAtom } from "jotai"; import { memo, useMemo } from "react"; +import { useTranslation } from "react-i18next"; interface ErrorDisplayProps { message: string; @@ -39,17 +40,18 @@ const LoadingSpinner = memo(({ message }: { message: string }) => { LoadingSpinner.displayName = "LoadingSpinner"; const EmptyState = memo(({ onAddSecret }: { onAddSecret: () => void }) => { + const { t } = useTranslation(); return (
-

No Secrets

-

Add a secret to get started

+

{t("waveConfig.noSecrets")}

+

{t("waveConfig.addSecretToGetStarted")}

); @@ -57,11 +59,12 @@ const EmptyState = memo(({ onAddSecret }: { onAddSecret: () => void }) => { EmptyState.displayName = "EmptyState"; const CLIInfoBubble = memo(() => { + const { t } = useTranslation(); return (
-
CLI Access
+
{t("waveConfig.cliAccess")}
wsh secret list @@ -82,6 +85,7 @@ interface SecretListViewProps { } const SecretListView = memo(({ secretNames, onSelectSecret, onAddSecret }: SecretListViewProps) => { + const { t } = useTranslation(); return (
@@ -105,7 +109,7 @@ const SecretListView = memo(({ secretNames, onSelectSecret, onAddSecret }: Secre onClick={onAddSecret} > - Add New Secret + {t("waveConfig.addNewSecret")}
@@ -134,13 +138,14 @@ const AddSecretForm = memo( onCancel, onSubmit, }: AddSecretFormProps) => { + const { t } = useTranslation(); const isNameInvalid = newSecretName !== "" && !SecretNameRegex.test(newSecretName); return (
-

Add New Secret

+

{t("waveConfig.addNewSecret")}

- + -
- Must start with a letter and contain only letters, numbers, and underscores -
+
{t("waveConfig.secretNameHint")}
- +