Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ i18n is **global, URL-driven infrastructure**, not a per-section feature. `src/l
3. Add the locale's chrome strings. `en` and `zh-Hans` live inline in the `strings` dict in `i18n.ts`; every other locale keeps its `UIStrings` in its own file `src/lib/ui/<slug>.ts` (imported into the dict) so locales can be authored independently without colliding on one file. Keep each locale's `UIStrings` complete — page body copy lives in the page, only shared chrome lives here.
4. Keep localized JSON-LD in sync: set the localized `url` and add `inLanguage` (see `zh/voice/index.astro`).

**Blog posts** are localized via the content collection: English posts live at `src/content/blog/*.md`; translations live under `src/content/blog/<lang>/*.md` with a `lang:` frontmatter field (schema in `content.config.ts`). The default-locale blog routes filter to `data.lang === 'en'`; each `/​<slug>/blog/` route filters to its own language and strips the `<slug>/` id prefix for the URL. **Data-driven pages** (e.g. `voice/alternatives/`) keep one template per locale and read locale-keyed datasets via `getAlternatives(locale)` in `voice-alternatives.ts`; `en` and `zh-Hans` are inline there, every other locale's dataset lives in `src/lib/alternatives/<slug>.ts` and is imported (same per-locale-file pattern as the UI strings).
**Blog posts** are localized via the content collection: English posts live at `src/content/blog/*.md`; translations live under `src/content/blog/<lang>/*.md` with a `lang:` frontmatter field (schema in `content.config.ts`). The default-locale blog routes filter to `data.lang === 'en'`; each `/​<slug>/blog/` route filters to its own language and strips the `<slug>/` id prefix for the URL. Every blog route (listings, per-post pages, RSS) filters through **`blogFilter(lang)` in `src/lib/blog.ts`**, the single place that drops `draft: true` posts — so drafts vanish everywhere at once. Drafts are hidden in normal builds; to preview one locally set `DRAFTS=1` (`make dev-draft` for the dev server, `make build-draft` for a production build). The var is read in Node at build/dev time, never ships to the client, and is unset in CI/Cloudflare so drafts can never leak to production. **Data-driven pages** (e.g. `voice/alternatives/`) keep one template per locale and read locale-keyed datasets via `getAlternatives(locale)` in `voice-alternatives.ts`; `en` and `zh-Hans` are inline there, every other locale's dataset lives in `src/lib/alternatives/<slug>.ts` and is imported (same per-locale-file pattern as the UI strings).

To add a whole new language: add its `localeDefs` entry (with `slug` and any `hreflangAliases`), add its base paths to `translatedRoutes`, add its slug↔code to both the `i18n` and `sitemap` maps in `astro.config.mjs`, create `src/lib/ui/<slug>.ts` + `src/lib/alternatives/<slug>.ts`, and create the `src/pages/<slug>/**` page tree + `src/content/blog/<slug>/*.md` posts.

Expand Down
12 changes: 11 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
SHELL := /bin/bash
NVM := . ~/.nvm/nvm.sh && nvm use 22 &&
.PHONY: dev build preview sync screenshots install clean cf-build help
.PHONY: dev dev-draft build build-draft preview sync screenshots install clean cf-build help
.DEFAULT_GOAL := help

# Copy needed assets from wavekat-brand submodule → public/
Expand All @@ -17,10 +17,18 @@ screenshots:
dev:
$(NVM) npm run dev

# Start dev server with draft blog posts visible (local only — never deploy this)
dev-draft:
$(NVM) DRAFTS=1 npm run dev

# Build for production → dist/
build:
$(NVM) npm run build

# Build including draft blog posts (local preview only — drafts must NOT ship)
build-draft:
$(NVM) DRAFTS=1 npm run build

# Preview production build locally
preview: build
$(NVM) npm run preview
Expand All @@ -41,7 +49,9 @@ help:
@echo "Usage: make <target>"
@echo ""
@echo " dev Start dev server"
@echo " dev-draft Start dev server with draft blog posts visible"
@echo " build Build for production → dist/"
@echo " build-draft Build including draft posts (local preview only)"
@echo " preview Build and preview locally"
@echo " sync Copy assets from wavekat-brand submodule"
@echo " screenshots Refresh framed app screenshots → public/screenshots/"
Expand Down
Binary file added public/screenshots/in-call-hold/de.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-hold/en.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-hold/es.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-hold/fr.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-hold/it.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-hold/ja.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-hold/ko.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-hold/zh-Hans.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-hold/zh-Hant.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-transfer/de.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-transfer/en.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-transfer/es.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-transfer/fr.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-transfer/it.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-transfer/ja.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-transfer/ko.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-transfer/zh-Hans.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-transfer/zh-Hant.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-waiting/de.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-waiting/en.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-waiting/es.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-waiting/fr.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-waiting/it.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-waiting/ja.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-waiting/ko.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/in-call-waiting/zh-Hans.webp
Binary file added public/screenshots/in-call-waiting/zh-Hant.webp
3 changes: 3 additions & 0 deletions scripts/sync-screenshots.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ const destRoot = join(root, "public/screenshots");
// the Mac build. Defaults to "ubuntu" when omitted.
const SCENES = [
{ name: "in-call", source: "voice", frame: "ubuntu" }, // a placed call
{ name: "in-call-hold", source: "voice", frame: "ubuntu" }, // parked call, amber banner
{ name: "in-call-waiting", source: "voice", frame: "ubuntu" }, // switcher, two held
{ name: "in-call-transfer", source: "voice", frame: "ubuntu" }, // Complete transfer
{ name: "settings-automation", source: "voice", frame: "ubuntu" }, // enable CLI
{ name: "settings-automation-agents", source: "voice", frame: "ubuntu" }, // connect
{ name: "provider-picker-2talk", source: "voice", frame: "ubuntu" }, // pick 2talk (providers/2talk)
Expand Down
85 changes: 85 additions & 0 deletions src/content/blog/de/hold-switch-and-transfer-calls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
title: "Anrufe halten, wechseln und weiterleiten wie eine Telefonzentrale"
description: "WaveKat Voice kann Anrufe jetzt halten, einen zweiten Anruf während des ersten annehmen und Anrufer weiterleiten — ohne oder mit Rückfrage — auf Mac und Linux, und die Aufnahme pausiert beim Halten automatisch."
date: 2026-07-05
author: Eason Guo
tags: [Sprach-KI, Anrufe]
lang: "de"
---

WaveKat Voice — das SIP-Softphone für Mac und Linux, das jeden Anruf aufzeichnet und transkribiert — kann jetzt die drei Dinge, die ein Empfang den ganzen Tag tut: einen Anrufer in die Warteschleife legen, einen zweiten Anruf annehmen, während der erste wartet, und einen Anrufer an jemand anderen weiterleiten — entweder sofort oder erst nach Rücksprache. Sie erscheinen mit WaveKat Voice [0.0.42](/de/voice/changelog/#0.0.42).

Das ist der bislang wörtlichste Schritt hin zu unserem Ziel, [jedem kleinen Unternehmen die Stimme eines großen zu geben](/de/blog/hello-world/). Wer bei einem großen Unternehmen anruft, hört „Einen Moment, ich stelle Sie durch" — und es funktioniert, weil dort eine Empfangskraft mit einer Telefonzentrale sitzt. Jetzt gibt [WaveKat Voice](/de/voice/) — das bereits jeden Anruf aufzeichnet und transkribiert — einem Ein-Personen-Betrieb dieselben Handgriffe. Halten, Anklopfen und Weiterleiten sind die Bedienelemente, nach denen am Empfang ständig gegriffen wird, und jedes ernstzunehmende [Softphone](/de/voice/alternatives/) muss sie haben — WaveKat Voice hat sie jetzt, mit einer eigenen Besonderheit: Was während des Haltens passiert, bleibt aus der Aufnahme heraus.

## Einen Anrufer halten

Wer in WaveKat Voice einen Anrufer hält, pausiert die Aufnahme und das Live-Transkript für die gesamte Zeit, in der der Anruf geparkt ist — nichts, was während eines Haltens neben Ihrem Schreibtisch gesagt wird, landet in der Aufnahme oder im Transkript. Auf dem Anrufbildschirm gibt es einen **Halten**-Knopf, zwischen Stummschalten und Wähltastatur; ein Druck darauf pausiert den Anruf in beide Richtungen, sodass Sie die Gegenseite nicht hören und sie Sie nicht hört.

WaveKat Voice teilt dem Telefonsystem der Gegenseite auf dem SIP-Standardweg mit, dass der Anruf gehalten wird, sodass die meisten Systeme ihre eigene Wartemusik vorspielen — der Anrufer hört „Sie werden gehalten", keine Totenstille. Das ist mehr als beidseitiges lokales Stummschalten: Das entfernte System weiß, dass der Anruf geparkt ist, nicht nur still. Mit **Fortsetzen** laufen Gespräch, Aufnahme und Transkript weiter.

![WaveKat Voice auf Ubuntu — ein gehaltener Anruf: das Warteschleifen-Banner mit Fortsetzen, die Transkription pausiert.](/screenshots/in-call-hold/de.webp)

## Anklopfen: einen zweiten Anruf annehmen, während Sie telefonieren

Wenn ein zweiter Anruf klingelt, während Sie schon sprechen, müssen Sie sich nicht mehr entscheiden. Nehmen Sie ihn an, und der erste Anruf legt sich von selbst in die Warteschleife — genau wie das Anklopfen auf dem Mobiltelefon. Eine Umschaltleiste auf dem Anrufbildschirm listet jeden laufenden Anruf, und Sie wechseln per Klick zwischen ihnen.

Nur ein Anruf ist jeweils live. Der Anruf, den Sie gerade ansehen, ist der, auf dem Sie sprechen; jeder andere Anruf wird gehalten, und seine Aufnahme und sein Live-Transkript pausieren, bis Sie zu ihm zurückwechseln. Standardmäßig wird ein gehaltener Anruf beim Umschalten sofort fortgesetzt; wer jeden Anruf lieber bewusst fortsetzt, findet in **Einstellungen → Allgemein** einen Schalter („Anruf beim Umschalten fortsetzen"), und jeder gehaltene Anruf trägt ein deutliches Banner, damit eine stille Leitung nie wie eine abgebrochene wirkt.

![WaveKat Voice auf Ubuntu — ein laufender Anruf, während zwei weitere Anrufer in der Umschaltleiste in der Warteschleife warten.](/screenshots/in-call-waiting/de.webp)

## Einen Anrufer weiterleiten — sofort oder nach Rückfrage

Der **Weiterleiten**-Knopf schickt einen laufenden Anrufer zu jemand anderem — eine andere Nummer, eine andere Durchwahl, eine andere SIP-Adresse. Es gibt zwei Wege, und WaveKat Voice beherrscht beide:

| | Ohne Rückfrage (Blind Transfer) | Mit Rückfrage (Attended Transfer) |
|---|---|---|
| **Was passiert** | Der Anrufer wird sofort zur neuen Nummer geschickt | Sie rufen die neue Person zuerst an, sprechen mit ihr und verbinden dann beide |
| **Sprechen Sie zuerst mit dem Empfänger?** | Nein | Ja — der Anrufer wartet in der Warteschleife, während Sie nachfragen |
| **Wenn der Empfänger nicht abnimmt** | Der Anrufer bleibt bei Ihnen; nichts geht verloren | Sie legen das Rückfrage-Gespräch auf und kehren zum Anrufer zurück |
| **Wann sinnvoll** | Sie wissen, dass die Person übernehmen soll: „Ich stelle Sie zur Buchhaltung durch" | Sie wollen den Anrufer ankündigen oder sind unsicher, ob jemand erreichbar ist |

Für eine Weiterleitung ohne Rückfrage drücken Sie Weiterleiten, geben das Ziel ein — fertig; sobald die neue Person abnimmt, endet Ihre Seite des Anrufs. Für eine Weiterleitung mit Rückfrage wählen Sie **Erst sprechen**: Der Anrufer wird gehalten, WaveKat Voice ruft das Ziel als zweiten Anruf an, und Sie sprechen ungestört („Ich habe einen Kunden mit einer Frage zur Rechnung — können Sie übernehmen?"). Wenn alles passt, drücken Sie **Weiterleitung abschließen**, die beiden werden verbunden, und Sie steigen aus. Ist die Person beschäftigt, lehnt ab oder ist doch die falsche, legen Sie einfach das Rückfrage-Gespräch auf und setzen Ihren Anrufer fort — er erfährt nie, dass der erste Versuch nicht geklappt hat.

![WaveKat Voice auf Ubuntu — eine Weiterleitung mit Rückfrage: der Anrufer gehalten, daneben der Knopf Weiterleitung abschließen.](/screenshots/in-call-transfer/de.webp)

Auch in Ihrer Anrufliste bleiben Weiterleitungen ehrlich. Ein weitergeleiteter Anruf endet als **Weitergeleitet**, und die Detailseite zeigt genau, wohin er ging — „Weitergeleitet an …" — statt so zu tun, als hätten Sie aufgelegt.

## Häufige Fragen

### Wie leite ich in WaveKat Voice einen Anruf weiter?

Drücken Sie auf dem Anrufbildschirm Weiterleiten und geben Sie eine Nummer, Durchwahl oder SIP-Adresse ein. Sofortiges Senden ist eine Weiterleitung ohne Rückfrage; **Erst sprechen** hält den Anrufer und ruft das Ziel an, damit Sie ihn ankündigen können — **Weiterleitung abschließen** verbindet dann beide.

### Was ist der Unterschied zwischen einer Weiterleitung mit und ohne Rückfrage?

Ohne Rückfrage (Blind Transfer) wird der Anrufer sofort zum neuen Ziel geschickt, ohne dass Sie mit dem Empfänger sprechen. Mit Rückfrage (Attended Transfer) bleibt der Anrufer in der Warteschleife, während Sie den Empfänger selbst anrufen, und erst nach Ihrer Bestätigung werden beide verbunden — Sie können also zurück, wenn der Empfänger beschäftigt ist oder ablehnt. WaveKat Voice unterstützt beides.

### Braucht die Person, an die ich weiterleite, WaveKat Voice?

Nein. WaveKat Voice nutzt den SIP-Standardmechanismus für Weiterleitungen (ein REFER, RFC 3515), das Ziel erhält also einen ganz gewöhnlichen Anruf — jedes Telefon, jedes Softphone, jede Durchwahl, die Ihr Anbieter erreicht.

### Funktionieren Halten und Weiterleiten mit jedem SIP-Anbieter?

Ja. Halten nutzt das SIP-Standard-re-INVITE (RFC 3264) und Weiterleiten das SIP-REFER (RFC 3515), sodass beide mit jedem SIP-konformen Anbieter oder jeder SIP-konformen Telefonanlage funktionieren — mit dem Konto, das Sie bereits haben, ohne anbieterspezifische Einrichtung.

### Kann ich zwei Anrufe zu einer Telefonkonferenz zusammenführen?

Noch nicht. WaveKat Voice kann zwei oder mehr Anrufe gleichzeitig halten und zwischen ihnen wechseln, aber nur einer ist jeweils live. Dreierkonferenzen sind eine eigene Funktion, die wir noch nicht gebaut haben.

### Was hört ein Anrufer in der Warteschleife?

Ein in WaveKat Voice gehaltener Anrufer hört das, was sein eigenes Telefonsystem für die Warteschleife abspielt — meist dessen Wartemusik, nicht Stille oder einen Ton von WaveKat Voice. WaveKat Voice signalisiert das Halten auf dem SIP-Standardweg (ein re-INVITE, RFC 3264) und überlässt so das Warteschleifen-Erlebnis dem eigenen System des Anrufers, sodass er hört, was er erwartet.

### Wird ein Anruf aufgezeichnet, während er gehalten wird?

Nein. Aufnahme und Live-Transkript pausieren für die Dauer des Haltens auf beiden Seiten und laufen mit dem Anruf weiter. Die Zeitleiste der gespeicherten Aufnahme bleibt korrekt — gehaltene Zeit erscheint als Stille, nicht als Schnitt.

### Auf welchen Plattformen gibt es Halten, Anklopfen und Weiterleiten?

WaveKat Voice läuft heute auf Mac und Linux; Windows folgt, wenn die Nachfrage da ist. Halten, Anklopfen und beide Arten der Weiterleitung funktionieren auf beiden unterstützten Plattformen, mit dem SIP-Konto, das Sie schon nutzen — ohne Aufpreis, ohne Einrichtung.

## Probieren Sie es aus

[Laden Sie WaveKat Voice herunter](/de/voice/download/) — oder aktualisieren Sie auf [0.0.42](/de/voice/changelog/#0.0.42) — und die Bedienelemente sind auf jedem Anrufbildschirm: Halten neben Stummschalten, Weiterleiten daneben, und Anklopfen, das einfach passiert, wenn der zweite Anruf klingelt. Nichts zu konfigurieren, nichts extra zu bezahlen.

Legen Sie einen Anrufer in die Warteschleife, nehmen Sie die zweite Leitung an und stellen Sie jemanden durch, als gäbe es einen Empfang — denn jetzt gibt es einen.
Loading
Loading