mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-19 14:50:38 -07:00
feat: 0.5.0 Capture release — dictation, MCP, personalities (#544)
* feat(capture): dictation, personalities, 0.5.0 Ships the Capture release end to end. Global-hotkey dictation with synthetic paste into the focused app on macOS and Windows, an on-screen pill across recording / transcribing / refining, customizable push-to- talk and toggle chords, and an accessibility-permission prompt scoped to Settings → Captures with inline re-check feedback. Voice profiles gain optional personalities that power compose / rewrite / respond actions via a local Qwen3 LLM — shared with refinement, so there is one local LLM in the app, not two. Refinement hardened with deterministic Whisper-loop collapse before the LLM sees the transcript, per-capture flag snapshots for re-runs, and a ten-transcript evaluation harness across every bundled refinement size. Version bump 0.4.5 → 0.5.0. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * feat(mcp): local MCP server exposes voicebox.* tools to AI agents Mounts FastMCP at /mcp (Streamable HTTP) so Claude Code, Cursor, Windsurf, and the VS Code MCP extensions can call voicebox.speak, voicebox.transcribe, voicebox.list_captures, and voicebox.list_profiles against the running Voicebox server. Backend - new backend/mcp_server package (tools, middleware, profile resolve, pub/sub events); named mcp_server to avoid shadowing the installed mcp PyPI package FastMCP imports internally - app.py migrated from @app.on_event to lifespan= so FastMCP's session manager cohabits with Voicebox's startup/shutdown - new MCPClientBinding table + /mcp/bindings CRUD; ClientIdMiddleware reads X-Voicebox-Client-Id into a ContextVar and stamps last_seen_at - profile resolution precedence: explicit -> per-client binding -> capture_settings.default_playback_voice_id - POST /speak REST wrapper for non-MCP callers (shell, ACP, A2A) - GET /events/speak SSE broadcasts speak-start / speak-end so the pill surfaces agent-initiated speech - backend/mcp_shim proxy (plain httpx) for stdio-only MCP clients - PyInstaller spec updates + new --shim build target (~18 MB) Frontend - Settings -> MCP page with HTTP / stdio / claude-mcp-add copy snippets, default voice picker, per-client bindings table, connection status - useMCPBindings, useSpeakEvents hooks - CapturePill gains 'speaking' state; DictateWindow subscribes to SSE and emits dictate:show so the Rust side surfaces the pill window Native - tauri.conf.json externalBin now includes voicebox-mcp - show_dictate_window helper + dictate:show listener in main.rs - (also in this commit: InputMonitoringGate UX, hotkey_monitor tweaks, landing footer/navbar updates, new overview docs for captures / dictation / mcp-server / voice-personalities) Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * feat(mcp): Rust-owned speaking pill with self-contained audio playback The pill window now surfaces for agent-initiated speech without main-window involvement. Rust subscribes to /events/speak via a tokio task + reqwest streaming body (speak_monitor.rs), shows the pill, and forwards events to the dictate webview over Tauri's event bus. The pill plays audio via a plain HTMLAudioElement and emits dictate:hide when playback ends. The pill stays hidden through the ~1 s generation wait and only surfaces when audio actually starts, with the counter armed at that moment. Fixes a shared-dict mutation in mcp_server/events.publish() that caused the second subscriber (Rust speak_monitor) to receive `event: message` instead of named speak-start/speak-end frames. Also teaches the speak_monitor parser to handle CRLF framing (sse-starlette default). Main-window AudioPlayer now skips autoplay for source in {mcp, rest} to avoid double-play when both windows are alive. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * readme and dev script * feat(capture): gate global hotkey on dictation readiness checklist Stops the "stuck pill" failure where pressing the chord with missing STT/LLM models triggers a recording that has nowhere to land. The hotkey now stays disarmed until every gate (models downloaded, Input Monitoring + Accessibility granted) is green; the empty-state checklist in CapturesTab surfaces each unmet gate with a one-click action and auto-arms the chord once everything turns green. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * color * model download status * progress * personality: bool API, i18n across the app - Collapse intent tri-state (respond/rewrite/compose) to `personality: bool` on /generate, /speak, and voicebox.speak. Drop respond entirely; keep compose as a standalone button via /profiles/{id}/compose. Remove /rewrite, /respond, and /speak profile endpoints. - FloatingGenerateBox: Wand2 persona toggle + Dices compose button appear when the selected profile has a personality. ProfileCard badges Wand2 alongside the effects Sparkles. - MCP bindings: default_intent column → default_personality: bool. Migration drops the legacy column. - i18n: en / ja / zh-CN / zh-TW translation files filled out and wired through the capture, server, and profile UI. ```ts voicebox.speak({ text: "Deploy complete.", profile: "Morgan", personality: true, // rewrite through the profile's personality LLM }); ``` * i18n: GenerationPage sidebar copy * fix: BOOL import for windows crate 0.62 BOOL moved from Win32::Foundation to windows::core in 0.62. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(capture): layout-aware V keycode for synthetic paste on macOS macOS apps match Cmd+V against the layout-translated character via NSMenu key equivalents, so posting kVK_ANSI_V (= 9, the QWERTY V position) on Dvorak produces Cmd+. and never triggers Paste. New keyboard_layout module resolves the active layout's V keycode via TISCopyCurrentKeyboardLayoutInputSource + UCKeyTranslate, caches it in an AtomicU16, and refreshes on kTISNotifySelectedKeyboardInputSourceChanged. All TIS calls run on the main thread (init from Tauri setup; observer callback delivered to the main runloop); synthetic_keys::send_paste reads the cached value once per paste. Falls back to kVK_ANSI_V when resolution fails or the active input source carries no Unicode key layout data. Windows is intentionally left on hardcoded VK_V — SendInput delivers WM_KEYDOWN with wParam = VK_V to the target regardless of the active layout, which is why `Send "^v"` works for AutoHotkey on Dvorak Windows. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(capture): cooperative app activation for synthetic paste on macOS 14+ macOS 14 deprecated NSRunningApplication.activateWithOptions: in favour of a cooperative-activation pattern: the caller first yields activation rights to the target, then the target activate()s against the tightened Sonoma foreground rules. Without the yield, activate() on 14+ sometimes silently fails or only bounces the dock icon — the exact "paste lands in the wrong app" symptom we were previously one API break away from. activate_pid now discovers the 14+ selector via respondsToSelector: and branches: on 14+ it yieldActivationToApplication:'s from NSRunningApplication.current then calls -activate on the target; on 11–13 it stays on -activateWithOptions: (still the only option). Both branches propagate the BOOL return — if activation is refused we error out before clobbering the clipboard instead of silently proceeding. The respondsToSelector: result is cached in a OnceLock so the probe isn't repeated on every paste. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(capture): conditional clipboard restore + always-attempt on paste failure Two bugs in paste_final_text' clipboard handling: 1. Restore was unconditional. If the user ⌘C'd in the target app during the 400 ms paste-consume window — or a clipboard history tool (Paste, Pastebot, Maccy) or Universal Clipboard sync snapshotted our staged text — the blind restore overwrote their newer content with the pre-paste snapshot, silently losing user data. 2. send_paste' errors were propagated with ? before the restore, so a CGEventPost / SendInput failure left the user's clipboard stuck on the transcript. Fix folds both into one pattern: capture the post-write change count, re-read it after paste-consume, restore only when they match (plus treat a change-count read failure as "unknown, don't overwrite"). Isolate send_paste's error so the restore runs regardless of paste success, then propagate the paste error after. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * chore(deps): pin rdev to jamiepine/rdev fork Upstream Narsil/rdev has shipped no release since 2023-06 (crates.io still serves 0.5.3), so the Sonoma main-thread fix we depend on — PR #147, applied at hotkey_monitor.rs:184 — is only reachable via a git pin. A pin to a third-party repo breaks the build whenever the remote force-pushes, renames, or is taken down, and Cargo does not durably cache git-dep archives the way it does crates.io tarballs. Forking to jamiepine/rdev at the same SHA removes that failure mode without changing crate behavior and gives us a place to cherry-pick future OS-compatibility fixes on our own timeline. The SHA was verified to exist on the fork before re-pinning. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(mcp): idle timeout + escalating backoff for speak-SSE monitor Two reliability gaps in the /events/speak subscriber: 1. resp.chunk().await had no idle timeout. A backend that accepts the TCP connection but stops producing frames (deadlocked SSE endpoint, zombie process) would block the task forever without reconnecting. The pill window would never surface for agent-initiated speech and there would be nothing to log. Backend emits a `:ping` heartbeat every 15 s, so 45 s without any data is now treated as a dead stream — the task errors out and the reconnect loop takes over. 2. Flat 2 s backoff escalates nowhere. Logs fill with reconnect lines when the backend is down for minutes, and a backend that accepts + immediately closes connections (no data) spins the loop tightly. Backoff now escalates 500 ms → 30 s on unproductive rounds and resets only when at least one frame arrives (the connection was genuinely productive, not just accepted). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(refinement): character-level loop collapse + pytest coverage The word-level pass catches single-word Whisper loops ("URL URL URL…") but misses two common hallucination patterns the PR had to claim as "edge cases": 1. Multi-word English loops — "thanks for watching thanks for watching…" × 6 sails through because no two consecutive tokens are identical after text.split(). 2. CJK loops — "謝謝觀看" × 7 sails through because text.split() returns a single unsplit token for the whole loop (no whitespace between characters). Add a character-level second pass: a non-greedy regex finds any 2–60 char substring that repeats min_run+ times immediately after itself and strips the run. The 2-char floor keeps emphasised single-letter runs ("wooooooow") intact. The 60-char ceiling covers every observed Whisper tail hallucination ("Please like and subscribe to my channel.", "Subtitles by the Amara.org community") while staying short enough that coincidental long-phrase repetition in legitimate speech doesn't hit the threshold. Whitespace normalisation only runs when the pass actually stripped something, so untouched transcripts keep their original spacing. New test_refinement_collapse.py gives the pre-processor its first deterministic unit-test coverage: 17 tests pinning the word-level legacy behaviour plus the new multi-word English / CJK / Japanese / emphasis-preservation cases. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(db): graceful fallback when SQLite < 3.35 on MCP bindings migration SQLite gained ALTER TABLE … DROP COLUMN in 3.35 (Mar 2021). Production PyInstaller builds bundle Python 3.12 which links to SQLite 3.40+ so that path is always safe, but a dev running the backend directly on Ubuntu 20.04 (3.31) or Debian 11 (3.34) would crash on first startup trying to drop the legacy default_intent column. Add _supports_drop_column(engine) — returns True on non-SQLite dialects (Postgres / MySQL have supported DROP COLUMN for decades) and gates on the runtime sqlite_version for SQLite. When unsupported, log a warning and leave the unused column in place: SQLAlchemy only maps declared columns, so a stray default_intent column does no reads or writes and can't interfere with runtime behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(mcp): correct lifespan shutdown order — drain MCP before unloading models The inline lifespan ran _run_shutdown inside the MCP context, so the TTS / Whisper / LLM models were unloaded *before* FastMCP's __aexit__ got a chance to cancel its in-flight session tasks. Any MCP request mid-generate at shutdown time would crash on "model unloaded" instead of receiving a clean session-cancelled error. Rewire via compose_lifespan (which was already defined in mcp_server.server for exactly this purpose but never used): AsyncExitStack enters factories in order and exits in LIFO, so MCP teardown fires first — cancelling sessions — and _run_shutdown runs after nothing is holding the models. Smoke test shows the log order flipped as expected: Ready StreamableHTTP session manager started ... running ... StreamableHTTP session manager shutting down ← was last, now first Voicebox server shutting down... ← was first, now last As a side benefit, _run_shutdown is now paired with _run_startup via try/finally inside voicebox_lifespan, so a partial startup (models half-loaded, MCP __aenter__ fails) still unloads whatever was loaded instead of leaking it to process exit. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(mcp): stamp last_seen_at on /speak too + tighten path predicate POST /speak is a REST wrapper around voicebox.speak for agents that don't talk MCP (shell scripts, ACP, A2A). It reads X-Voicebox-Client-Id and uses it for the same per-client profile resolution + default personality lookup the MCP tool does (speak.py:39-64), so its callers are first-class clients — but the ClientIdMiddleware only stamped last_seen_at on /mcp* paths. REST speak callers showed up as "never seen" in Settings → MCP despite actively acting on their bindings. Widen the stamp predicate to an explicit ("/mcp", "/speak") prefix list, and require a path boundary on match so future routes named /mcpfoo or /speakers don't silently inherit the stamp via the prefix. New test_client_id_middleware.py pins the scope with 17 parametrised cases (both the allowed set and the overlap cases that must not match). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * feat(captures): scrubbable WaveSurfer player for capture detail view Replace the placeholder fake-waveform + play button in CapturesTab's audio card with a real CaptureInlinePlayer (wavesurfer.js). The player renders the actual waveform, lets users scrub through the clip, and shows a proper current/total timestamp pair in place of the duration-only label. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * feat(ui): persist selectedProfileId across sessions Wrap useUIStore in zustand/middleware's persist under the key voicebox-ui. partialize only selectedProfileId so volatile UI state (dialog open flags, form drafts, engine/voice pickers, sidebar) stays in-memory as before — but reopening the app no longer loses whichever profile the user was last working with. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * feat(captures): mirror readiness checklist into the settings sidebar The six-gate checklist only rendered in the CapturesTab empty state, so a user already on the settings page had no single surface showing which gate was red — the inline InputMonitoringNotice covered one, the model pickers covered another, and Accessibility was only hinted at by the auto-paste toggle. Mirror the same component into the right sidebar of the settings page so every gate (STT model, LLM model, Input Monitoring, Accessibility, plus the hotkey toggle in the main column) is always visible while the user configures dictation. New compact prop on DictationReadinessChecklist drops the centered header and empty-state max-width so it fits the 280 px sidebar next to the existing About / Differences blocks. Callers in compact mode own the heading — CapturesPage reuses the existing captures.readiness.title key (present in en / ja / zh-CN / zh-TW already) as an h3 matching the sibling sidebar sections. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * feat(captures): move sidebar checklist below differences + hide when all green Two small follow-ups to the sidebar checklist placement. Move it below the What's different section so the sticky top of the sidebar stays the page's narrative context (About → differences) and the checklist reads as a status panel rather than preamble. Gate the whole block on !readiness.allReady so once every gate is green the sidebar drops back to just About + What's different — no value in real estate full of checkmarks. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(captures): refetch readiness immediately after STT/LLM model swap useCaptureSettings updated its own cache optimistically but never invalidated ['capture-readiness'], so for up to 5 s (the poll interval) after switching stt_model or llm_model the checklist kept showing the previous model's ready/missing state. The backend endpoint resolves the model live on each call — it was just the frontend cache that lagged. Invalidate in onSettled only when the patch touched a model field, so unrelated updates (chord keys, toggles) don't pay for a refetch. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(captures): hide macOS-only copy when running on Windows / Linux Two surfaces leaked macOS-specific copy onto other platforms: 1. The Input Monitoring + Accessibility rows in the readiness checklist rendered everywhere. On Windows/Linux the Rust permission stubs return true, so the rows showed as permanent green checkmarks with copy like "macOS allows Voicebox to detect your global shortcut." — nonsense when you're on Windows. Gate both rows on a userAgent-based isMacOS check so they only render where the underlying TCC permission actually exists. 2. The global-shortcut setting description ended with "macOS will ask for Input Monitoring permission the first time you turn this on." That sentence rendered on every platform. The readiness checklist already surfaces the TCC requirement at the right moment on macOS, so the description doesn't need the platform note — drop it from en / ja / zh-CN / zh-TW. Other macOS strings (AccessibilityNotice, InputMonitoringNotice, their "stillMissing" hints) are already gated behind the Rust permission booleans returning false, which never happens on Windows/Linux, so they stay inert without further changes. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * feat(capture): swap the rdev fork for keytap 0.2, delete local chord state machine Dep swap: - Drop the git-pinned jamiepine/rdev fork we were carrying since the upstream crate is abandoned. - Depend on keytap 0.2 from crates.io — our own cross-platform global keyboard tap crate. Clean shutdown via Drop, Sonoma-safe by design (no TSMGetInputSourceProperty calls off the main thread, so `set_is_main_thread(false)` is gone), and properly versioned. Chord engine rewrite: - Delete hotkey_monitor.rs's internal Chord state machine (Match enum, KeyEvent enum, step()/classify() methods, associated unit tests). keytap's ChordMatcher subsumes it: Momentary chord for PTT, add_toggle() for Toggle-to-talk, longest-match resolution, sticky-end for Toggle. Net: -80 LOC in hotkey_monitor.rs; the remaining module is the dispatcher loop + Effect→Tauri translation. - Preserve the PTT→Toggle "RestartRecording" upgrade signal. keytap emits End(PTT)+Start(Toggle) atomically (same Instant) when the held set upgrades from a shorter chord to a longer superset. The dispatcher peeks at the matcher with a 5 ms recv_timeout after any End and coalesces the pair into Effect::RestartRecording so the frontend still gets the "discard the transition-moment audio" signal instead of an unrelated Stop+Start pair. - HotkeyMonitor::update_bindings now actually tears down the tap on empty bindings instead of leaving an idle CGEventTap around. New bindings rebuild the matcher and the dispatcher thread from scratch. key_codes.rs: - Rewrite the browser-code → Key table against keytap's cleaner Key variant names (`A`..`Z` not `KeyA`..`KeyZ`, `Digit0`..`Digit9` not `Num0`..`Num9`, `ArrowUp` not `UpArrow`, `AltLeft`/`AltRight` instead of `Alt`/`AltGr`, `Period` not `Dot`, …). On-disk chord string format (W3C `KeyboardEvent.code` identifiers) is unchanged, so capture_settings rows written before the swap round-trip identically. Legacy aliases (`Alt`, `AltGr`, `Num0`, `UpArrow`, `Dot`, …) kept for forward-compat on old rows. main.rs / input_monitoring.rs: - Update the few doc comments that referenced `rdev::listen` to describe keytap's Tap; no behavioural change. - build_chord_bindings now imports from keytap::Key. - enable_hotkey / disable_hotkey / update_chord_bindings reach into HotkeyMonitor via &mut since apply()/update_bindings() now mutate. Tests live in keytap now (22 chord-related tests in keytap 0.2, including the PTT→Toggle upgrade scenario that used to be tested in hotkey_monitor.rs). Voicebox's hotkey_monitor.rs is thin enough that local testing would be trivia. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * chore(deps): bump keytap 0.2 → 0.4 for macOS modifier-events fix 0.2 read CGEventFlags via CGEventGetIntegerValueField(event, 0x81), which is not a valid CGEventField id — macOS silently returned 0, so FlagsChanged events produced no KeyDown / KeyUp for any modifier key and the PTT / toggle chords never armed on macOS. 0.4 uses the documented CGEventGetFlags(event) API. 0.3 (tracing / serde / Fn / IntlBackslash) is picked up as a free consequence; no API surface we depend on changed. * perf(captures): stop polling readiness once both models are green useQuery was firing GET /capture/readiness every 5s forever, and also on every window focus. Once stt.ready and llm.ready are both true the answer can only change when the user swaps a model in settings, and useSettings already invalidates the query on that path — the polling was pure noise. Gate both refetchInterval and refetchOnWindowFocus on "not fully ready" so we fall silent once the checklist is green. * feat(ui): theme settings, stories polish, track editor restructure - add dark/light/system theme with persisted choice + OS change listener - restyle stories sidebar (search, item layout, border) to match captures - move floating generate box to right column of stories, add top fade mask - story track editor: sticky track labels aligned via flex rows, custom scrollbar with left/right zoom handles - capture pill light mode pass, fix inline waveform progress color - pull mcp_server hidden imports into the pyinstaller spec - notarization doc draft * fix mlx llm bundling * feat(ui): shared ListPane primitive + misc polish ListPane is a compound component (Header / TitleRow / Title / Actions / Search / Scroll) that owns the relative wrapper, faded right divider (50px top fade), top scroll mask, and absolute-positioned header used by every list-detail tab. Wires up CapturesTab, StoryList, and EffectsList. EffectsTab gets -mx-8 / pr-8 to match the edge-to-edge layout used elsewhere. Other changes: - MCPPage: native <select> → shadcn <Select> for default voice and per-binding voice pickers - Button outline variant: add hover:border-accent - Drop hover:text-destructive from trailing delete buttons (HistoryTable, GpuAcceleration, GpuPage, EffectsChainEditor, EffectsDetail) - HistoryTable empty state moved behind t('history.empty') - StoryContent scroll padding pt-14 → pt-16 - backend health reports the captures dir - landing CapturesMockup: "Send to" → "Export" with Download icon - CHANGELOG: drop [Unreleased] personality section * fix(captures): Play As autoplay + default voice + orphan recovery - Hand /generate ids to the global SSE watcher so playback fires on completion. The mutation onSuccess was checking audio_path on a queued row, which is always empty — autoplay never ran. - Bind the Play As voice selection to capture_settings.default_playback_voice_id, kept in sync with the Settings → Captures and Settings → MCP pickers. Picking from the split-button dropdown writes back to settings. - Extract AudioBars from HistoryTable into a shared component; use it for the Play As generating state in place of Loader2. - Stop the active-state hover from flashing white text when the button is in its lighter accent/10 fill. - Drop the gradient avatar swatches from the Settings → Captures voice dropdown. - Backend: when the gen worker exits without writing a terminal status (e.g. SQLite lock racing the failed-status write inside its own exception handler), the cancel endpoint now flips the row to failed instead of 409-ing. Worker also force-fails on its way out as a belt-and-suspenders. * fix(captures+chord): Stop button stops, ChordPicker accepts shorter chords Two unrelated correctness bugs caught in PR review: - The Play As "Stop" button was wired to handlePlayAs() unconditionally, so clicking it during playback kicked a fresh generation instead of halting. Now pauses the player when the click came from the main button while playbackState is 'playing'. Picking a different voice from the dropdown still kicks a new generation as before. - ChordPicker tracked the peak set of held keys but seeded the peak from initialKeys, so a user who opened the picker with a 3-key chord saved couldn't replace it with a 2-key chord — the candidate length never beat the seed. The peak now resets on the first press of a fresh sequence (when no keys were held immediately prior), then grows monotonically within that hold. * fix(settings): honor explicit null on nullable fields, ignore on the rest Routes were calling model_dump(exclude_none=True), which drops every client-sent null before it reaches the service. The service then layered on its own `if value is not None` guard. Net effect: setting a nullable column back to null was a no-op — the MCPPage default-voice picker sends null when the user picks "no default" and the row was silently keeping whatever was there before. Switched the routes to exclude_unset=True so absent fields stay absent but explicit nulls survive the dump, and centralised the per-field nullability check in the service. The check inspects the SQLAlchemy column metadata so non-nullable columns (stt_model, llm_model, the chord key lists) still drop nulls instead of crashing the request, while default_playback_voice_id can finally be cleared. * fix(captures): clean up audio files when create_capture fails The create flow wrote raw audio (and a transcoded .wav for non-wav sources) to data/captures before the DB row was committed, so any failure between the write and the commit — a webm that decoded to a 0-length array, a whisper model that errored mid-transcribe, a SQLite contention on the commit — left the audio on disk with nothing pointing at it. Over enough flaky uploads the directory grows without bound. Now every path written before the commit is tracked in a list, and the whole stretch from the first write to db.commit() runs inside a try/except that unlinks each tracked file on raise and re-raises. The transcode branch removes the raw file from the cleanup list only when the unlink actually succeeds, so an OSError on the raw-path delete still hands cleanup the original blob to retry. * fix(mcp): restrict voicebox.transcribe(audio_path=...) to loopback audio_path mode took any absolute filesystem path and returned its decoded contents as transcribed text with no caller verification beyond the existence/size checks. The X-Voicebox-Client-Id middleware records the header but never rejects an absent or fake one, so a Voicebox bound to 0.0.0.0 (the documented "remote access" mode) was effectively an unauthenticated arbitrary-local-file read primitive. The middleware now stashes the request's remote address in a ContextVar alongside the existing client_id, and audio_path mode refuses anything that doesn't parse as a loopback address (IPv4 127.0.0.0/8, IPv6 ::1). audio_base64 mode is unchanged — that path was always bounded to bytes the caller already has. Loopback callers (the Tauri webview, local CLI scripts, MCP clients on the same machine) keep working. Remote callers now have to send the audio over the wire if they want it transcribed. * fix: PR review nits — response shape, landing copy, form reset - /llm/generate's "model is downloading" branch was raising HTTPException(202, detail={...}), which wraps the payload in {"detail": ...} and forces clients to parse a success status as if it were an error. Switched to JSONResponse so the payload sits at the top level. - The landing page's "Language Models" card advertised "Qwen 3.5" with sizes 4B/2B/0.8B; we ship Qwen3 at 0.6B/1.7B/4B. Aligned to what's actually in the binary. - ProfileForm's discard-draft button reset the form without touching `personality` or `avatarFile`, so stale persona text and an attached avatar would survive the discard. The other three resets in the file already include both fields — this brings the discard path in line. * perf(mcp): move last_seen_at stamp off the request path ClientIdMiddleware was running the SQLAlchemy SELECT/INSERT/UPDATE/COMMIT inline on the event loop after every /mcp/* and /speak request. SQLite serialises writes, so concurrent MCP traffic queued behind the stamp write — the response sat waiting on a side-effect that the client never needs in band, and SSE streams would stall briefly per request. The middleware now hands the stamp to asyncio.to_thread via a fire-and- forget create_task so the response returns immediately and the write runs on the default executor. A module-level set keeps strong refs to in-flight tasks (per asyncio docs) so the GC can't collect them mid- write. The fallback path runs the stamp inline if no loop is available (tests/oddball callers) rather than silently dropping it. * fix(dictate): force-dismiss the speaking pill when SSE never comes back The pill subscribed to /generation/{id}/status to know when to start playback, but EventSource.onerror was a no-op — auto-reconnect was the intended recovery for transient drops. The gap: if the backend deletes the gen row mid-flight or the connection silently dies in a way the browser keeps retrying without ever getting a status event, the pill sits in 'speaking' forever and the user has no way to clear it. Added a 60-second hard cap that arms when the SSE opens and clears the moment any real status event lands. If it fires while the pill is still on the same id and audio never started, it force-dismisses. Same idea as the existing post-speak-end 15s grace, but covers the case where the backend never says anything at all. * fix: i18n cleanup + readiness checklist effect cadence + ChordPicker shadow - DictationReadinessChecklist was constructing downloadByModel as a fresh Map every render and listing it in the cleanup effect's deps. With the 1 s polling cadence and arbitrary parent rerenders the effect ran more often than it needed to. Memoised the Map on activeTasks; the effect now keys off the memo's identity. - zh-CN persona tooltipActive/ariaLabelActive matched their inactive twins byte-for-byte ("以人物设定朗读"). The other locales differentiate the active state with a -ing / -中 suffix; zh-CN now reads "正以人物设定朗读" when active. - personalityPlaceholder was a ~290-character paragraph that doubled as both the example text and the explanation, repeating most of what personalityHint already said. Trimmed to the example only and folded the explanation + leave-blank consequence into the hint, across all four locales. - Refinement model size keys were size06 / size17 / size4. Renamed the 4B variant to size40 so the decimal padding is consistent. - ChordPicker's open-effect bound a window.setTimeout id to a local `t`, shadowing the i18n `t` from useTranslation. Renamed to timeoutId. * perf(settings): persist generation sliders on release, not per pointer-move Both sliders on the generation settings page were calling update() — which is a React Query mutation that PATCHes /settings/generation — inside onValueChange. Dragging the chunk-limit slider from 800 to 3000 fired a request per pointer-move pixel, and a mid-drag failure plus optimistic rollback would leave persisted state visibly out of sync with the thumb position. Local state now mirrors each slider during a drag and the persist happens once on Radix's onValueCommit (pointer-up / keyboard-release). useEffects keep the local state in sync if the persisted value changes out-of-band — another window editing the same setting still updates the slider position cleanly. * chore(backend): Ruff lint pass — deprecated APIs, exception leaks, dead patterns Mechanical sweep of items called out in the PR review: - qwen_llm_backend: AutoModelForCausalLM.from_pretrained(torch_dtype=…) is deprecated in transformers ≥4.41 in favor of dtype=. Renamed. - routes/llm: try/except around backend.generate() raised HTTPException(500, detail=str(e)) which leaks stack traces / paths to clients and trips Ruff B904. Now logs the original exception server-side and hands the client a generic message; chained via `from e` to preserve traceback context. - mcp_bindings + mcp_server/context: datetime.utcnow() is deprecated since 3.12. Switched the two assignment sites to datetime.now(timezone.utc). The schema-level `default=datetime.utcnow` defaults in database/models.py are left for a later schema-aware pass. - routes/generations: `logger = …` sat between two import blocks (Ruff E402). Moved below imports. - mcp_server/server + tests/test_refinement_samples: typing.Callable / typing.Iterable have been preferred-via collections.abc since 3.9 (Ruff UP035). - routes/events: `except asyncio.TimeoutError` aliases plain `TimeoutError` since 3.11 (UP041). - services/captures: hoisted WHISPER_NATIVE_FORMATS to module scope (was a function-local UPPER_SNAKE that tripped N806) and replaced the raw_path.unlink try/except OSError-pass with contextlib.suppress (SIM105). Semantic equivalence preserved — written_files.remove(raw_path) still only runs when unlink succeeds because it sits inside the suppressed block after the unlink call. - database/migrations: hoisted the duplicate `import sqlite3` from inside two helper bodies to a single module-level import. * feat(stories): regenerate action on clips and the chat list dropdown The track editor's clip toolbar now has a regenerate icon next to Delete; clicking it kicks a fresh take of the selected clip's underlying generation through the same /generate/{id}/regenerate path the History table uses, and pushes the id into the global pending set so the SSE watcher picks it up. The chat list's per-item dropdown gets the same action between Play-from-here and Remove. Translation keys added under storyContent.itemActions / storyContent.toast across all four locales. * feat(stories): import external audio into the timeline (drag-drop + picker) You can now drop a music file onto the story content area or pick one through the new "Import audio" button in the add-clip popover. Both call POST /generate/import which writes the file to data/generations/<id>.<ext>, probes duration via librosa, and inserts a Generation row pointing at a singleton "Imported Audio" profile (created lazily on first import). The existing addStoryItem flow takes over from there — the timeline doesn't care that the row didn't come out of TTS. Engine field on the row is "import"; it's surfaced on StoryItemDetail so the chat list shows a music icon instead of the (missing) profile avatar and both the dropdown and the track-editor toolbar hide the Regenerate action — there's nothing to regenerate. Accepted formats: wav/mp3/flac/ogg/m4a/aac/webm, capped at 200 MB. Translation keys added across en/ja/zh-CN/zh-TW. * fix(audio): serve real Content-Type so imports decode in WaveSurfer /audio/{id} and /audio/version/{id} hardcoded media_type="audio/wav" on the FileResponse. That was a no-op when every generation came out of TTS (everything on disk was a .wav anyway), but imported audio keeps its source format — .mp3 / .m4a / .ogg — and the WaveSurfer MediaElement backend uses an <audio> tag that checks Content-Type before letting the clip play, so an MP3 announced as audio/wav silently failed to load. Both endpoints now derive the type via mimetypes.guess_type and fall back to audio/wav for unknown suffixes. Download filenames also keep the real extension instead of always saying ".wav". * feat(stories): zoom bar bounds tracked to project length, default 60s scope The track editor's zoom was clamped to a hardcoded [10, 200] pixels-per-second range, which had no relationship to the project — on a 4-minute story a "max zoom out" of 200 px/s still required scrolling, and on a 5-second story you could zoom all the way in to where every clip was a tiny sliver. Reframed the bounds in the unit the user actually thinks in: how many seconds of timeline are visible at once. Min scope is 10 s (most zoomed in), max scope is the entire project, and the default lands on a 60 s scope (or the full project, whichever is shorter) once the editor measures its visible track width on first mount. The pixels-per-second value still lives in component state (because every downstream calculation already uses it) but minPps/maxPps are computed from `containerWidth − LABEL_COL_WIDTH` and the project's effective duration, so the +/- buttons and the edge-drag handles on the scrollbar all clamp to bounds that move with the project. Re-clamping fires whenever those bounds shift — adding a long clip or resizing the window pulls the current zoom inside the new range instead of leaving the user parked outside it. * fix(stories): show the source filename on imported clips Imports were rendering as "Imported Audio" everywhere because every import points at the singleton voice profile. The filename was already being stored on the generation row (in the `text` field), so the chat item title and the timeline clip label now read from `text` when `engine === 'import'` and fall back to the profile name otherwise. The chat item also drops the language pill (always "en" on imports — not informative) and skips the transcript textarea since imports have no spoken text to show. * fix(stories): round split_time_ms before posting handleSplit was sending currentTimeMs - item.start_time_ms straight to the backend, which rejects it because StoryItemSplit.split_time_ms is typed as int and the playhead's currentTimeMs is a float (it's driven from HTMLAudioElement.currentTime, which carries sub-millisecond precision). Pydantic surfaced the mismatch as "Input should be a valid integer, got a number with a fractional part" and the toast read "Failed to split clip". Math.round at the call site, matching what the trim and move handlers already do. * feat(stories): per-clip volume control on the timeline Each story item now carries a volume column (linear gain, default 1.0, clamped 0.0–2.0 server-side). New PUT /stories/{}/items/{}/volume route + useUpdateStoryItemVolume hook + a Volume2 icon in the clip-edit toolbar that opens a popover with a 0–200% slider. Local slider state drives the visual during a drag; the persist fires once on onValueCommit, mirroring the generation-page slider pattern. Web Audio playback inserts a per-clip GainNode between source and master so volume changes apply live without re-decoding the buffer (source -> clipGain -> masterGain -> destination). Server-side mixdown in export multiplies the trimmed clip by its volume before summing into the timeline. Split + duplicate carry the volume forward to the new clips so trimming a faded section keeps the level you set. Migration adds the volume column with default 1.0 so existing rows read as full volume. * fix(stories): mute the clip waveform's media element so it can't bleed audio The clip waveforms drawn inside each timeline track use WaveSurfer with the default MediaElement backend, which creates an internal <audio> element to drive playback timing. Web Audio in useStoryPlayback is what actually produces sound, but WaveSurfer's element was happily preloading and — after the first user gesture unlocked browser autoplay — playing the source URL through the page output too. For TTS clips it was masked: they're short, both sources start at the same time, and stopping the BufferSourceNode at pause coincides with the natural end of the audio element. For long imports (a four-minute MP3) the BufferSourceNode stops on pause but WaveSurfer's element keeps going on its own track — which is exactly the "music keeps playing when I pause" symptom. Hand WaveSurfer a muted <audio> element via the `media` option so the visual still loads peaks but the element itself can never produce sound. preload="metadata" keeps the load lightweight. * fix(stories): hard-cut the audio graph on stop so long imports actually halt source.stop() was the only thing happening when a clip was halted, and on long imported buffers (multi-minute MP3s scheduled via source.start with a duration argument) it was silently failing to halt the buffer in some browsers — pause left the music playing and seek stacked another source on top of the original. The mute-the-WaveSurfer-element fix was a different bug along the same path; this is the one that actually addresses the duplicated audio. ActiveSource now carries the per-clip GainNode alongside the source, and stopSource detaches the onended handler before calling stop() (so the natural-end callback can't race with explicit teardown and re-delete a freshly rescheduled entry at the same id), then disconnects both nodes inside their own try/catch blocks. Even when stop() doesn't actually halt the buffer the graph is severed — no path from source to destination, no audio. * feat(stories): add empty tracks above/below the timeline Tiny + strips sit at the top of the topmost label cell and the bottom of the bottommost one, sticky-positioned in the label column so they follow horizontal scroll. Clicking either extends the visible track stack in that direction by one — above adds max(existing)+1, below adds min(existing)-1. Both compute against the full set (defaults + item-derived + previously-added) so successive clicks keep extending instead of fighting over the same number. Empty extras live in component state because a track only earns its keep once a clip lands on it. Once one does, item.track carries the number forward and the row keeps deriving from items naturally; if nothing lands there before reload, the empty row simply isn't there next time, which matches what the user expects of an unused affordance. * fix(mcp): bundle stdio shim sidecar * fix(captures): allow dictation without paste permission * fix(mcp): preserve speak engine defaults * fix(captures): use platform hotkey defaults * fix(mcp): preload speak pill window * fix(captures): hide unwired storage settings * feat(sponsors): add /sponsors page, homepage promo, and in-app strip * style(landing): drop pill chrome from /download maintainer kicker * changelog * better naming for sponsors * windows keybind note --------- Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
627d40b42d
commit
7df366d0c8
@@ -14,6 +14,7 @@
|
||||
"nav": {
|
||||
"generate": "Generate",
|
||||
"stories": "Stories",
|
||||
"captures": "Captures",
|
||||
"voices": "Voices",
|
||||
"effects": "Effects",
|
||||
"audio": "Audio",
|
||||
@@ -21,6 +22,149 @@
|
||||
"settings": "Settings",
|
||||
"updateBadge": "Update"
|
||||
},
|
||||
"captures": {
|
||||
"title": "Captures",
|
||||
"beta": "Beta",
|
||||
"searchPlaceholder": "Search transcripts…",
|
||||
"snippetEmpty": "(no transcript)",
|
||||
"noTranscriptError": "Capture has no transcript yet",
|
||||
"captureCardLabel": "Capture · {{when}}",
|
||||
"header": {
|
||||
"modelSummary": "Whisper {{stt}} · Qwen3 · {{llm}}"
|
||||
},
|
||||
"source": {
|
||||
"dictation": "Dictation",
|
||||
"recording": "Recording",
|
||||
"file": "File"
|
||||
},
|
||||
"transcript": {
|
||||
"refined": "Refined",
|
||||
"raw": "Raw",
|
||||
"refinedHint": "Refined with Qwen3 · {{model}}",
|
||||
"rawHint": "Transcribed with Whisper {{model}}"
|
||||
},
|
||||
"actions": {
|
||||
"configure": "Configure",
|
||||
"import": "Import",
|
||||
"importing": "Uploading…",
|
||||
"dictate": "Dictate",
|
||||
"stop": "Stop",
|
||||
"copy": "Copy",
|
||||
"refine": "Refine",
|
||||
"reRefine": "Re-refine",
|
||||
"export": "Export",
|
||||
"exportDropdownLabel": "Export capture as",
|
||||
"exportAudio": "Audio (WAV)",
|
||||
"exportTranscript": "Transcript (TXT)",
|
||||
"exportMarkdown": "Markdown (MD)",
|
||||
"delete": "Delete",
|
||||
"playAs": "Play as {{name}}",
|
||||
"playAsFallback": "Play as…",
|
||||
"playAsGenerating": "Generating…",
|
||||
"playAsStop": "Stop · {{name}}",
|
||||
"playAsStopFallback": "Stop · Voice",
|
||||
"playAsDropdownLabel": "Play transcript as"
|
||||
},
|
||||
"empty": {
|
||||
"noMatches": "No captures match \"{{query}}\"",
|
||||
"none": "No captures yet.",
|
||||
"loading": "Loading captures…",
|
||||
"pickOne": "Pick a capture to see the transcript.",
|
||||
"holdToRecord": "Hold to record",
|
||||
"toggleHandsFree": "Toggle hands-free",
|
||||
"pressShortcut": "Press the shortcut anywhere on your machine to start your first capture.",
|
||||
"turnOnShortcut": "Turn on the global shortcut to dictate from anywhere — or click Dictate above for an in-app capture.",
|
||||
"openSettings": "Open Captures settings"
|
||||
},
|
||||
"deleteDialog": {
|
||||
"title": "Delete capture",
|
||||
"description": "This will permanently delete the capture, its audio, and its transcript. This cannot be undone.",
|
||||
"deleting": "Deleting…"
|
||||
},
|
||||
"toast": {
|
||||
"deleteFailed": "Delete failed",
|
||||
"playAsFailed": "Play-as failed",
|
||||
"noVoice": "No voice profile",
|
||||
"noVoiceDescription": "Create a voice profile before using Play as.",
|
||||
"transcriptCopied": "Transcript copied",
|
||||
"copyFailed": "Copy failed",
|
||||
"exportSuccess": "Exported to {{path}}",
|
||||
"exportFailed": "Export failed",
|
||||
"exportEmpty": "Nothing to export",
|
||||
"shortcutNotArmed": "Shortcut on, but not yet armed",
|
||||
"shortcutNotArmedDescription_one": "{{names}} still needs to download. Open the Captures tab to start.",
|
||||
"shortcutNotArmedDescription_other": "{{names}} still need to download. Open the Captures tab to start."
|
||||
},
|
||||
"pill": {
|
||||
"recording": "Recording",
|
||||
"transcribing": "Transcribing",
|
||||
"refining": "Refining",
|
||||
"speaking": "Speaking",
|
||||
"completed": "Done",
|
||||
"stopAria": "Stop recording",
|
||||
"errorFallback": "Something went wrong",
|
||||
"errorCopyTooltip": "Click to copy error"
|
||||
},
|
||||
"chord": {
|
||||
"capturing": "Capturing…",
|
||||
"pressShortcut": "Press your shortcut",
|
||||
"noKeys": "No keys yet",
|
||||
"unsupported": "\"{{key}}\" isn't supported in chords. Try a modifier or letter key.",
|
||||
"notSet": "Not set"
|
||||
},
|
||||
"readiness": {
|
||||
"title": "A few things before you can dictate",
|
||||
"subheading": "The shortcut stays off until everything below is ready.",
|
||||
"downloadButton": "Download",
|
||||
"downloading": "Downloading…",
|
||||
"downloadingPercent": "Downloading… {{pct}}%",
|
||||
"downloadStarted": "Download started",
|
||||
"downloadStartedDescription": "{{name}} is downloading. The shortcut will arm itself when it finishes.",
|
||||
"downloadFailed": "Download failed",
|
||||
"stt": {
|
||||
"label": "{{name}} (speech-to-text)",
|
||||
"ready": "Model downloaded.",
|
||||
"missing": "Needed to transcribe your audio",
|
||||
"missingWithSize": "Needed to transcribe your audio · {{size}}"
|
||||
},
|
||||
"llm": {
|
||||
"label": "{{name}} (refinement)",
|
||||
"ready": "Model downloaded.",
|
||||
"missing": "Cleans up the raw transcript before paste",
|
||||
"missingWithSize": "Cleans up the raw transcript before paste · {{size}}"
|
||||
},
|
||||
"inputMonitoring": {
|
||||
"label": "Input Monitoring permission",
|
||||
"ready": "macOS allows Voicebox to detect your global shortcut.",
|
||||
"missing": "macOS needs to allow Voicebox to detect the global shortcut.",
|
||||
"openSettings": "Open Settings"
|
||||
},
|
||||
"accessibility": {
|
||||
"label": "Accessibility permission",
|
||||
"ready": "Voicebox can paste transcriptions into other apps.",
|
||||
"missing": "Required so transcriptions can paste into the focused app.",
|
||||
"openSettings": "Open Settings"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"accessibility": {
|
||||
"title": "Grant Accessibility permission to enable auto-paste",
|
||||
"body": "Voicebox needs <path>System Settings → Privacy & Security → Accessibility</path> to paste transcriptions into other apps. Your dictation still lands in the Captures tab without it.",
|
||||
"openSettings": "Open Settings",
|
||||
"recheck": "I've enabled it",
|
||||
"rechecking": "Checking…",
|
||||
"stillMissing": "Still not detected. macOS usually requires quitting and reopening Voicebox after toggling the permission."
|
||||
},
|
||||
"inputMonitoring": {
|
||||
"title": "Grant Input Monitoring to enable the global shortcut",
|
||||
"body": "Voicebox needs <path>System Settings → Privacy & Security → Input Monitoring</path> to detect your dictation chord. The toggle is on, but macOS is blocking key events until you allow it.",
|
||||
"openSettings": "Open Settings",
|
||||
"recheck": "I've enabled it",
|
||||
"rechecking": "Checking…",
|
||||
"stillMissing": "Still not detected. macOS usually requires quitting and reopening Voicebox after toggling the permission."
|
||||
}
|
||||
}
|
||||
},
|
||||
"voicesTab": {
|
||||
"title": "Voices",
|
||||
"loading": "Loading voices…",
|
||||
@@ -125,7 +269,10 @@
|
||||
"noPreference": "No preference",
|
||||
"defaultEngineHint": "Auto-selects this engine when the profile is chosen.",
|
||||
"defaultEffects": "Default Effects",
|
||||
"defaultEffectsHint": "Effects applied automatically to all new generations with this voice."
|
||||
"defaultEffectsHint": "Effects applied automatically to all new generations with this voice.",
|
||||
"personalityLabel": "Personality",
|
||||
"personalityPlaceholder": "e.g. \"a grumpy pirate who only speaks in nautical metaphors\"",
|
||||
"personalityHint": "Who this voice is and how they talk. Drives the Compose button and the in-character rewrite toggle on the generate page. Leave blank to hide both."
|
||||
},
|
||||
"avatar": {
|
||||
"alt": "Avatar preview"
|
||||
@@ -415,9 +562,11 @@
|
||||
"title": "Stories",
|
||||
"newStory": "New Story",
|
||||
"loading": "Loading stories…",
|
||||
"searchPlaceholder": "Search stories…",
|
||||
"empty": {
|
||||
"title": "No stories yet",
|
||||
"hint": "Create your first story to get started"
|
||||
"hint": "Create your first story to get started",
|
||||
"noMatches": "No stories match \"{{query}}\""
|
||||
},
|
||||
"row": {
|
||||
"itemCount_one": "{{count}} item",
|
||||
@@ -480,16 +629,23 @@
|
||||
},
|
||||
"itemActions": {
|
||||
"playFromHere": "Play from here",
|
||||
"regenerate": "Regenerate",
|
||||
"removeFromStory": "Remove from Story"
|
||||
},
|
||||
"importAudio": "Import audio…",
|
||||
"importing": "Importing…",
|
||||
"dropToImport": "Drop audio to import",
|
||||
"toast": {
|
||||
"removeFailed": "Failed to remove item",
|
||||
"reorderFailed": "Failed to reorder items",
|
||||
"exportFailed": "Failed to export audio",
|
||||
"addFailed": "Failed to add generation"
|
||||
"addFailed": "Failed to add generation",
|
||||
"regenerateFailed": "Failed to regenerate",
|
||||
"importFailed": "Failed to import audio"
|
||||
}
|
||||
},
|
||||
"history": {
|
||||
"empty": "No voice generations, yet…",
|
||||
"actions": {
|
||||
"menu": "Actions",
|
||||
"play": "Play",
|
||||
@@ -550,6 +706,18 @@
|
||||
"effects": {
|
||||
"none": "No effects",
|
||||
"profileDefault": "Profile default"
|
||||
},
|
||||
"compose": {
|
||||
"tooltip": "Compose",
|
||||
"ariaLabel": "Compose a line in character",
|
||||
"failedTitle": "Compose failed",
|
||||
"failedDescription": "Could not generate text from this personality."
|
||||
},
|
||||
"persona": {
|
||||
"tooltipActive": "Speaking in character",
|
||||
"tooltipInactive": "Speak in character",
|
||||
"ariaLabelActive": "Speaking in character",
|
||||
"ariaLabelInactive": "Speak in character"
|
||||
}
|
||||
},
|
||||
"main": {
|
||||
@@ -571,6 +739,8 @@
|
||||
"tabs": {
|
||||
"general": "General",
|
||||
"generation": "Generation",
|
||||
"captures": "Captures",
|
||||
"mcp": "MCP",
|
||||
"gpu": "GPU",
|
||||
"logs": "Logs",
|
||||
"changelog": "Changelog",
|
||||
@@ -580,6 +750,15 @@
|
||||
"label": "Language",
|
||||
"description": "Choose the display language for Voicebox."
|
||||
},
|
||||
"theme": {
|
||||
"label": "Theme",
|
||||
"description": "Match your system, or pick a fixed light or dark appearance.",
|
||||
"options": {
|
||||
"system": "System",
|
||||
"light": "Light",
|
||||
"dark": "Dark"
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"docs": { "title": "Read the Docs" },
|
||||
"discord": { "title": "Join the Discord", "subtitle": "Get help & share voices" },
|
||||
@@ -676,6 +855,233 @@
|
||||
"title": "Generations folder",
|
||||
"description": "Where generated audio files are stored on disk.",
|
||||
"open": "Open"
|
||||
},
|
||||
"sidebar": {
|
||||
"aboutTitle": "About voice generation",
|
||||
"aboutBody": "Clone a voice from a short sample, then generate speech in any voice across any language. Ship TTS into AI agents, games, podcasts, or long-form narration.",
|
||||
"differencesTitle": "What's different",
|
||||
"clone": {
|
||||
"title": "Clone any voice in seconds.",
|
||||
"body": "A few seconds of reference audio is enough. Multi-sample support for higher quality when you want it."
|
||||
},
|
||||
"engines": {
|
||||
"title": "Seven engines, 23 languages.",
|
||||
"body": "Pick the tradeoff that fits — quality, speed, or multilingual coverage."
|
||||
},
|
||||
"agentReady": {
|
||||
"title": "Agent-ready.",
|
||||
"body": "REST API with per-profile control — give any AI a voice you've cloned."
|
||||
}
|
||||
}
|
||||
},
|
||||
"captures": {
|
||||
"dictation": {
|
||||
"title": "Dictation",
|
||||
"description": "Capture from anywhere on your machine with a global shortcut.",
|
||||
"globalShortcut": {
|
||||
"title": "Global shortcut",
|
||||
"description": "Hold the shortcut to record from anywhere on your machine. Release to transcribe."
|
||||
},
|
||||
"pushToTalk": {
|
||||
"title": "Push-to-talk shortcut",
|
||||
"description": "Hold these keys anywhere on your system to record. Release to stop and transcribe.",
|
||||
"change": "Change"
|
||||
},
|
||||
"toggle": {
|
||||
"title": "Toggle shortcut",
|
||||
"description": "Press once to start a hands-free recording. Press again to stop. Usually push-to-talk plus Space.",
|
||||
"change": "Change"
|
||||
},
|
||||
"chordPicker": {
|
||||
"pttTitle": "Set push-to-talk shortcut",
|
||||
"pttDescription": "Hold the keys you want to use, then release and click Save. The right-hand modifier badge shows whether a key is the left or right variant.",
|
||||
"toggleTitle": "Set toggle shortcut",
|
||||
"toggleDescription": "Hold the keys you want to use, then release and click Save. Pick something distinct from your push-to-talk chord."
|
||||
},
|
||||
"preview": {
|
||||
"title": "Preview",
|
||||
"description": "What appears on screen while you're holding the shortcut."
|
||||
},
|
||||
"copyToClipboard": {
|
||||
"title": "Copy transcript to clipboard",
|
||||
"description": "The cleaned transcript lands on your clipboard when the capture finishes."
|
||||
},
|
||||
"autoPaste": {
|
||||
"title": "Auto-paste into focused text field",
|
||||
"description": "If a text input is focused in another app, paste directly into it. Voicebox saves and restores whatever was on your clipboard."
|
||||
}
|
||||
},
|
||||
"transcription": {
|
||||
"title": "Transcription",
|
||||
"description": "Pick which speech-to-text model runs on your captures.",
|
||||
"model": {
|
||||
"title": "Transcription model",
|
||||
"description": "Whisper ships with Voicebox and runs entirely on your machine.",
|
||||
"base": "Whisper Base · 74M · {{tail}}",
|
||||
"small": "Whisper Small · 244M · {{tail}}",
|
||||
"medium": "Whisper Medium · 769M · {{tail}}",
|
||||
"large": "Whisper Large · 1.5B · {{tail}}",
|
||||
"turbo": "Whisper Turbo · Pruned Large v3 · {{tail}}",
|
||||
"tail": {
|
||||
"fast": "Fast",
|
||||
"balanced": "Balanced",
|
||||
"higher": "Higher accuracy",
|
||||
"best": "Best accuracy",
|
||||
"nearBest": "Near-best, fast"
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"title": "Language",
|
||||
"description": "Auto-detect works for most captures. Lock it if you're always speaking the same language.",
|
||||
"auto": "Auto-detect",
|
||||
"en": "English",
|
||||
"es": "Spanish",
|
||||
"fr": "French",
|
||||
"de": "German",
|
||||
"ja": "Japanese",
|
||||
"zh": "Chinese",
|
||||
"hi": "Hindi"
|
||||
},
|
||||
"archive": {
|
||||
"title": "Archive audio",
|
||||
"description": "Keep the original recording alongside every transcript."
|
||||
}
|
||||
},
|
||||
"refinement": {
|
||||
"title": "Refinement",
|
||||
"description": "Optionally run a local LLM over transcripts to clean filler words, punctuation, and self-corrections.",
|
||||
"auto": {
|
||||
"title": "Refine transcripts automatically",
|
||||
"description": "Runs after every capture. You can still toggle between raw and refined in the Captures tab."
|
||||
},
|
||||
"model": {
|
||||
"title": "Refinement model",
|
||||
"description": "Larger models are slower but handle subtle self-corrections and technical vocabulary better.",
|
||||
"size06": "Qwen3 · 0.6B · 400 MB · {{tail}}",
|
||||
"size17": "Qwen3 · 1.7B · 1.1 GB · {{tail}}",
|
||||
"size40": "Qwen3 · 4B · 2.5 GB · {{tail}}",
|
||||
"tail": {
|
||||
"veryFast": "Very fast",
|
||||
"fast": "Fast",
|
||||
"fullQuality": "Full quality"
|
||||
}
|
||||
},
|
||||
"smartCleanup": {
|
||||
"title": "Smart cleanup",
|
||||
"description": "Remove filler words (um, uh, like), restore punctuation, and fix capitalization without rephrasing."
|
||||
},
|
||||
"selfCorrection": {
|
||||
"title": "Remove self-corrections",
|
||||
"description": "When you change your mind mid-sentence (\"actually, no...\", \"wait, I meant...\"), drop the retracted part and keep the final intent."
|
||||
},
|
||||
"preserveTechnical": {
|
||||
"title": "Preserve technical terms",
|
||||
"description": "Keep code identifiers, command names, and acronyms exactly as spoken. Turn on when you dictate into a code prompt."
|
||||
}
|
||||
},
|
||||
"playback": {
|
||||
"title": "Playback",
|
||||
"description": "Default voice for the \"Play as\" action in the Captures tab.",
|
||||
"defaultVoice": {
|
||||
"title": "Default voice",
|
||||
"description": "Used when you click Play as without picking a voice first. You can change it per capture.",
|
||||
"noClonedVoices": "No cloned voices yet",
|
||||
"noneSelected": "None selected",
|
||||
"clonedVoices": "Cloned voices"
|
||||
}
|
||||
},
|
||||
"storage": {
|
||||
"title": "Storage",
|
||||
"description": "Captures are saved as paired audio and transcript files in your Voicebox data directory.",
|
||||
"retention": {
|
||||
"title": "Retention",
|
||||
"description": "How long to keep captures. Applies to both audio and transcripts.",
|
||||
"forever": "Keep forever",
|
||||
"d90": "90 days",
|
||||
"d30": "30 days",
|
||||
"d7": "7 days"
|
||||
},
|
||||
"folder": {
|
||||
"title": "Captures folder",
|
||||
"description": "Where capture audio and transcripts are stored on disk.",
|
||||
"open": "Open"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"aboutTitle": "About Captures",
|
||||
"aboutBody": "Hold a shortcut anywhere on your machine, speak, and Voicebox turns your voice into text. Replay it in any cloned voice, paste it into any app, or pipe it into your coding agent.",
|
||||
"differencesTitle": "What's different",
|
||||
"local": {
|
||||
"title": "Fully local.",
|
||||
"body": "Whisper and the refinement LLM run on your hardware. No cloud, no accounts, your voice never leaves the machine."
|
||||
},
|
||||
"playAs": {
|
||||
"title": "Play as any voice.",
|
||||
"body": "Transcripts can be read back in any profile you've cloned."
|
||||
},
|
||||
"crossPlatform": {
|
||||
"title": "Cross-platform.",
|
||||
"body": "Same shortcut, same flow on macOS, Windows, and Linux."
|
||||
},
|
||||
"windowsCaveat": {
|
||||
"title": "Heads-up on Windows",
|
||||
"body": "The shortcut won't fire while Voicebox itself or any app running as administrator is focused. Working on it."
|
||||
}
|
||||
}
|
||||
},
|
||||
"mcp": {
|
||||
"install": {
|
||||
"title": "Install into your agent",
|
||||
"description": "Voicebox exposes a local MCP server whenever the app is open. Paste one of these snippets into your agent's MCP config.",
|
||||
"http": {
|
||||
"title": "HTTP (recommended)",
|
||||
"description": "For clients that speak HTTP MCP — Claude Code, Cursor, Windsurf, VS Code."
|
||||
},
|
||||
"claudeCode": {
|
||||
"title": "Claude Code one-liner",
|
||||
"description": "Registers via the Claude Code CLI."
|
||||
},
|
||||
"stdio": {
|
||||
"title": "Stdio (fallback)",
|
||||
"description": "For clients that only spawn stdio processes. The shim binary ships with the app."
|
||||
},
|
||||
"copy": "Copy",
|
||||
"copied": "Copied"
|
||||
},
|
||||
"defaultVoice": {
|
||||
"title": "Default voice",
|
||||
"description": "Used when an agent calls voicebox.speak without a specific profile and has no per-client binding.",
|
||||
"label": "Default playback voice",
|
||||
"labelHint": "Shared with the Captures-tab 'Play as voice' dropdown — one default voice for passive playback.",
|
||||
"none": "(none)"
|
||||
},
|
||||
"bindings": {
|
||||
"title": "Per-agent voice",
|
||||
"description": "Bind specific agents to specific voices so you can tell who's speaking without looking. The agent identifies itself by the X-Voicebox-Client-Id header (or VOICEBOX_CLIENT_ID env for stdio).",
|
||||
"empty": "No bindings yet. Add one below, then configure your MCP client to send the matching <code>X-Voicebox-Client-Id</code>.",
|
||||
"lastSeen": "last seen {{when}}",
|
||||
"lastSeenTitle": "Last seen {{when}}",
|
||||
"neverConnected": "never connected",
|
||||
"defaultOption": "(default)",
|
||||
"removeAria": "Remove binding for {{client}}",
|
||||
"add": {
|
||||
"title": "Add a binding",
|
||||
"clientIdPlaceholder": "client id (e.g. claude-code)",
|
||||
"labelPlaceholder": "label (optional)",
|
||||
"action": "Add binding"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"aboutTitle": "About MCP",
|
||||
"aboutBody": "Model Context Protocol lets your AI coding agent — Claude Code, Cursor, Windsurf — call Voicebox tools. Speak in a cloned voice, transcribe audio, browse captures.",
|
||||
"toolsTitle": "Available tools",
|
||||
"tools": {
|
||||
"speak": "Speak text in a voice profile.",
|
||||
"transcribe": "Whisper STT on a clip.",
|
||||
"listCaptures": "Recent dictations / recordings.",
|
||||
"listProfiles": "Available voice profiles."
|
||||
},
|
||||
"postSpeak": "Also exposed as <code>POST /speak</code> for shell scripts, ACP, A2A."
|
||||
}
|
||||
},
|
||||
"gpu": {
|
||||
@@ -752,7 +1158,8 @@
|
||||
"unknownSize": "Unknown size",
|
||||
"sections": {
|
||||
"voiceGeneration": "Voice Generation",
|
||||
"transcription": "Transcription"
|
||||
"transcription": "Transcription",
|
||||
"languageModels": "Language Models"
|
||||
},
|
||||
"status": {
|
||||
"loaded": "Loaded"
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"nav": {
|
||||
"generate": "生成",
|
||||
"stories": "ストーリー",
|
||||
"captures": "キャプチャ",
|
||||
"voices": "ボイス",
|
||||
"effects": "エフェクト",
|
||||
"audio": "オーディオ",
|
||||
@@ -21,6 +22,149 @@
|
||||
"settings": "設定",
|
||||
"updateBadge": "更新"
|
||||
},
|
||||
"captures": {
|
||||
"title": "キャプチャ",
|
||||
"beta": "ベータ",
|
||||
"searchPlaceholder": "文字起こしを検索…",
|
||||
"snippetEmpty": "(文字起こしなし)",
|
||||
"noTranscriptError": "このキャプチャにはまだ文字起こしがありません",
|
||||
"captureCardLabel": "キャプチャ · {{when}}",
|
||||
"header": {
|
||||
"modelSummary": "Whisper {{stt}} · Qwen3 · {{llm}}"
|
||||
},
|
||||
"source": {
|
||||
"dictation": "ディクテーション",
|
||||
"recording": "録音",
|
||||
"file": "ファイル"
|
||||
},
|
||||
"transcript": {
|
||||
"refined": "整形済み",
|
||||
"raw": "生テキスト",
|
||||
"refinedHint": "Qwen3 · {{model}} で整形",
|
||||
"rawHint": "Whisper {{model}} で文字起こし"
|
||||
},
|
||||
"actions": {
|
||||
"configure": "設定",
|
||||
"import": "インポート",
|
||||
"importing": "アップロード中…",
|
||||
"dictate": "ディクテーション",
|
||||
"stop": "停止",
|
||||
"copy": "コピー",
|
||||
"refine": "整形",
|
||||
"reRefine": "再整形",
|
||||
"export": "エクスポート",
|
||||
"exportDropdownLabel": "形式を選択",
|
||||
"exportAudio": "音声 (WAV)",
|
||||
"exportTranscript": "文字起こし (TXT)",
|
||||
"exportMarkdown": "Markdown (MD)",
|
||||
"delete": "削除",
|
||||
"playAs": "{{name}} で再生",
|
||||
"playAsFallback": "ボイスで再生…",
|
||||
"playAsGenerating": "生成中…",
|
||||
"playAsStop": "停止 · {{name}}",
|
||||
"playAsStopFallback": "停止 · ボイス",
|
||||
"playAsDropdownLabel": "文字起こしを次のボイスで再生"
|
||||
},
|
||||
"empty": {
|
||||
"noMatches": "「{{query}}」に一致するキャプチャはありません",
|
||||
"none": "キャプチャはまだありません。",
|
||||
"loading": "キャプチャを読み込み中…",
|
||||
"pickOne": "キャプチャを選択して文字起こしを表示します。",
|
||||
"holdToRecord": "押し続けて録音",
|
||||
"toggleHandsFree": "ハンズフリーを切り替え",
|
||||
"pressShortcut": "マシン上のどこからでもショートカットを押すと、最初のキャプチャを開始できます。",
|
||||
"turnOnShortcut": "グローバルショートカットを有効にしてどこからでもディクテーション — または上の「ディクテーション」をクリックしてアプリ内でキャプチャします。",
|
||||
"openSettings": "キャプチャ設定を開く"
|
||||
},
|
||||
"deleteDialog": {
|
||||
"title": "キャプチャを削除",
|
||||
"description": "このキャプチャと、その音声・文字起こしを完全に削除します。元に戻せません。",
|
||||
"deleting": "削除中…"
|
||||
},
|
||||
"toast": {
|
||||
"deleteFailed": "削除に失敗しました",
|
||||
"playAsFailed": "ボイスでの再生に失敗しました",
|
||||
"noVoice": "ボイスプロファイルがありません",
|
||||
"noVoiceDescription": "「ボイスで再生」を使う前にボイスプロファイルを作成してください。",
|
||||
"transcriptCopied": "文字起こしをコピーしました",
|
||||
"copyFailed": "コピーに失敗しました",
|
||||
"exportSuccess": "{{path}} に書き出しました",
|
||||
"exportFailed": "書き出しに失敗しました",
|
||||
"exportEmpty": "書き出す内容がありません",
|
||||
"shortcutNotArmed": "ショートカットは有効ですが、まだ準備が完了していません",
|
||||
"shortcutNotArmedDescription_one": "{{names}} のダウンロードがまだ必要です。キャプチャタブを開いて開始してください。",
|
||||
"shortcutNotArmedDescription_other": "{{names}} のダウンロードがまだ必要です。キャプチャタブを開いて開始してください。"
|
||||
},
|
||||
"pill": {
|
||||
"recording": "録音中",
|
||||
"transcribing": "文字起こし中",
|
||||
"refining": "整形中",
|
||||
"speaking": "発話中",
|
||||
"completed": "完了",
|
||||
"stopAria": "録音を停止",
|
||||
"errorFallback": "問題が発生しました",
|
||||
"errorCopyTooltip": "クリックでエラーをコピー"
|
||||
},
|
||||
"chord": {
|
||||
"capturing": "取得中…",
|
||||
"pressShortcut": "ショートカットを押してください",
|
||||
"noKeys": "まだキーがありません",
|
||||
"unsupported": "「{{key}}」はコードに対応していません。修飾キーまたは文字キーを試してください。",
|
||||
"notSet": "未設定"
|
||||
},
|
||||
"readiness": {
|
||||
"title": "ディクテーションを使う前にいくつか準備があります",
|
||||
"subheading": "下の項目がすべて整うまでショートカットは無効のままです。",
|
||||
"downloadButton": "ダウンロード",
|
||||
"downloading": "ダウンロード中…",
|
||||
"downloadingPercent": "ダウンロード中… {{pct}}%",
|
||||
"downloadStarted": "ダウンロードを開始しました",
|
||||
"downloadStartedDescription": "{{name}} をダウンロード中です。完了するとショートカットが自動的に有効になります。",
|
||||
"downloadFailed": "ダウンロードに失敗しました",
|
||||
"stt": {
|
||||
"label": "{{name}}(音声認識)",
|
||||
"ready": "モデルをダウンロード済みです。",
|
||||
"missing": "音声を文字起こしするために必要です",
|
||||
"missingWithSize": "音声を文字起こしするために必要です · {{size}}"
|
||||
},
|
||||
"llm": {
|
||||
"label": "{{name}}(整形)",
|
||||
"ready": "モデルをダウンロード済みです。",
|
||||
"missing": "貼り付け前に生の文字起こしを整形します",
|
||||
"missingWithSize": "貼り付け前に生の文字起こしを整形します · {{size}}"
|
||||
},
|
||||
"inputMonitoring": {
|
||||
"label": "入力監視の権限",
|
||||
"ready": "macOS が Voicebox にグローバルショートカットの検出を許可しています。",
|
||||
"missing": "macOS で Voicebox にグローバルショートカットの検出を許可する必要があります。",
|
||||
"openSettings": "設定を開く"
|
||||
},
|
||||
"accessibility": {
|
||||
"label": "アクセシビリティの権限",
|
||||
"ready": "Voicebox が他のアプリに文字起こしを貼り付けできます。",
|
||||
"missing": "フォーカス中のアプリに文字起こしを貼り付けるために必要です。",
|
||||
"openSettings": "設定を開く"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"accessibility": {
|
||||
"title": "自動貼り付けを有効にするためアクセシビリティの権限を付与してください",
|
||||
"body": "他のアプリに文字起こしを貼り付けるには、Voicebox に <path>「システム設定」→「プライバシーとセキュリティ」→「アクセシビリティ」</path> の許可が必要です。許可がなくてもディクテーションはキャプチャタブに保存されます。",
|
||||
"openSettings": "設定を開く",
|
||||
"recheck": "有効にしました",
|
||||
"rechecking": "確認中…",
|
||||
"stillMissing": "まだ検出されません。macOS では権限を切り替えた後、Voicebox を終了して再起動する必要があります。"
|
||||
},
|
||||
"inputMonitoring": {
|
||||
"title": "グローバルショートカットを有効にするため入力監視の権限を付与してください",
|
||||
"body": "ディクテーションのコードを検出するには、Voicebox に <path>「システム設定」→「プライバシーとセキュリティ」→「入力監視」</path> の許可が必要です。トグルは有効ですが、許可されるまで macOS がキーイベントをブロックしています。",
|
||||
"openSettings": "設定を開く",
|
||||
"recheck": "有効にしました",
|
||||
"rechecking": "確認中…",
|
||||
"stillMissing": "まだ検出されません。macOS では権限を切り替えた後、Voicebox を終了して再起動する必要があります。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"voicesTab": {
|
||||
"title": "ボイス",
|
||||
"loading": "ボイスを読み込み中…",
|
||||
@@ -125,7 +269,10 @@
|
||||
"noPreference": "指定なし",
|
||||
"defaultEngineHint": "このプロファイルが選ばれたとき、このエンジンを自動で選択します。",
|
||||
"defaultEffects": "デフォルトエフェクト",
|
||||
"defaultEffectsHint": "このボイスで新しく生成するすべてのものに自動適用されるエフェクトです。"
|
||||
"defaultEffectsHint": "このボイスで新しく生成するすべてのものに自動適用されるエフェクトです。",
|
||||
"personalityLabel": "パーソナリティ",
|
||||
"personalityPlaceholder": "例:「航海の比喩でしか話さない不機嫌な海賊」",
|
||||
"personalityHint": "このボイスがどんな人物で、どのように話すか。生成ページの「Compose」ボタンとキャラクター書き換えトグルに反映されます。空欄にすると両方とも表示されません。"
|
||||
},
|
||||
"avatar": {
|
||||
"alt": "アバタープレビュー"
|
||||
@@ -415,9 +562,11 @@
|
||||
"title": "ストーリー",
|
||||
"newStory": "新しいストーリー",
|
||||
"loading": "ストーリーを読み込み中…",
|
||||
"searchPlaceholder": "ストーリーを検索…",
|
||||
"empty": {
|
||||
"title": "ストーリーがまだありません",
|
||||
"hint": "最初のストーリーを作成して始めましょう"
|
||||
"hint": "最初のストーリーを作成して始めましょう",
|
||||
"noMatches": "「{{query}}」に一致するストーリーはありません"
|
||||
},
|
||||
"row": {
|
||||
"itemCount_one": "{{count}} 項目",
|
||||
@@ -480,16 +629,23 @@
|
||||
},
|
||||
"itemActions": {
|
||||
"playFromHere": "ここから再生",
|
||||
"regenerate": "再生成",
|
||||
"removeFromStory": "ストーリーから削除"
|
||||
},
|
||||
"importAudio": "オーディオをインポート…",
|
||||
"importing": "インポート中…",
|
||||
"dropToImport": "ドロップしてオーディオをインポート",
|
||||
"toast": {
|
||||
"removeFailed": "項目の削除に失敗しました",
|
||||
"reorderFailed": "項目の並び替えに失敗しました",
|
||||
"exportFailed": "オーディオのエクスポートに失敗しました",
|
||||
"addFailed": "生成の追加に失敗しました"
|
||||
"addFailed": "生成の追加に失敗しました",
|
||||
"regenerateFailed": "再生成に失敗しました",
|
||||
"importFailed": "オーディオのインポートに失敗しました"
|
||||
}
|
||||
},
|
||||
"history": {
|
||||
"empty": "音声生成はまだありません…",
|
||||
"actions": {
|
||||
"menu": "操作",
|
||||
"play": "再生",
|
||||
@@ -550,6 +706,18 @@
|
||||
"effects": {
|
||||
"none": "エフェクトなし",
|
||||
"profileDefault": "プロファイルのデフォルト"
|
||||
},
|
||||
"compose": {
|
||||
"tooltip": "Compose",
|
||||
"ariaLabel": "キャラクターになりきって一文を生成",
|
||||
"failedTitle": "Compose に失敗しました",
|
||||
"failedDescription": "このパーソナリティからテキストを生成できませんでした。"
|
||||
},
|
||||
"persona": {
|
||||
"tooltipActive": "キャラクターとして発話中",
|
||||
"tooltipInactive": "キャラクターとして発話",
|
||||
"ariaLabelActive": "キャラクターとして発話中",
|
||||
"ariaLabelInactive": "キャラクターとして発話"
|
||||
}
|
||||
},
|
||||
"main": {
|
||||
@@ -571,6 +739,8 @@
|
||||
"tabs": {
|
||||
"general": "一般",
|
||||
"generation": "生成",
|
||||
"captures": "キャプチャ",
|
||||
"mcp": "MCP",
|
||||
"gpu": "GPU",
|
||||
"logs": "ログ",
|
||||
"changelog": "変更履歴",
|
||||
@@ -580,6 +750,15 @@
|
||||
"label": "言語",
|
||||
"description": "Voicebox の表示言語を選択します。"
|
||||
},
|
||||
"theme": {
|
||||
"label": "テーマ",
|
||||
"description": "システム設定に合わせるか、ライト / ダークを固定します。",
|
||||
"options": {
|
||||
"system": "システム",
|
||||
"light": "ライト",
|
||||
"dark": "ダーク"
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"docs": { "title": "ドキュメントを読む" },
|
||||
"discord": { "title": "Discord に参加", "subtitle": "ヘルプやボイスの共有" },
|
||||
@@ -676,6 +855,233 @@
|
||||
"title": "生成物の保存先フォルダ",
|
||||
"description": "生成されたオーディオファイルをディスク上に保存する場所。",
|
||||
"open": "開く"
|
||||
},
|
||||
"sidebar": {
|
||||
"aboutTitle": "音声生成について",
|
||||
"aboutBody": "短いサンプルからボイスをクローンし、あらゆる言語のあらゆるボイスで音声を生成できます。TTS を AI エージェント、ゲーム、ポッドキャスト、長尺ナレーションに組み込めます。",
|
||||
"differencesTitle": "ここが違います",
|
||||
"clone": {
|
||||
"title": "数秒でどのボイスでもクローン。",
|
||||
"body": "数秒のリファレンス音声があれば十分です。より高い品質を求めるときは複数サンプルにも対応します。"
|
||||
},
|
||||
"engines": {
|
||||
"title": "7 つのエンジン、23 言語。",
|
||||
"body": "品質、速度、多言語対応 — 用途に合ったトレードオフを選べます。"
|
||||
},
|
||||
"agentReady": {
|
||||
"title": "エージェント対応。",
|
||||
"body": "プロファイル単位で制御できる REST API — クローンしたボイスをどの AI にも渡せます。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"captures": {
|
||||
"dictation": {
|
||||
"title": "ディクテーション",
|
||||
"description": "グローバルショートカットでマシン上のどこからでもキャプチャできます。",
|
||||
"globalShortcut": {
|
||||
"title": "グローバルショートカット",
|
||||
"description": "ショートカットを押し続けるとマシン上のどこからでも録音できます。離すと文字起こしが行われます。"
|
||||
},
|
||||
"pushToTalk": {
|
||||
"title": "プッシュトゥトーク用ショートカット",
|
||||
"description": "システム上のどこからでもこれらのキーを押し続けると録音します。離すと録音を停止し、文字起こしが行われます。",
|
||||
"change": "変更"
|
||||
},
|
||||
"toggle": {
|
||||
"title": "トグル用ショートカット",
|
||||
"description": "一度押すとハンズフリー録音を開始します。もう一度押すと停止します。通常はプッシュトゥトーク + Space を使います。",
|
||||
"change": "変更"
|
||||
},
|
||||
"chordPicker": {
|
||||
"pttTitle": "プッシュトゥトーク用ショートカットを設定",
|
||||
"pttDescription": "使いたいキーを押し続け、離してから「保存」をクリックします。右側の修飾キーバッジは、左右どちらの変種かを示します。",
|
||||
"toggleTitle": "トグル用ショートカットを設定",
|
||||
"toggleDescription": "使いたいキーを押し続け、離してから「保存」をクリックします。プッシュトゥトークのコードと区別できるものを選んでください。"
|
||||
},
|
||||
"preview": {
|
||||
"title": "プレビュー",
|
||||
"description": "ショートカットを押している間、画面に表示される内容です。"
|
||||
},
|
||||
"copyToClipboard": {
|
||||
"title": "文字起こしをクリップボードにコピー",
|
||||
"description": "キャプチャが終わると、整形済みの文字起こしがクリップボードに保存されます。"
|
||||
},
|
||||
"autoPaste": {
|
||||
"title": "フォーカス中のテキストフィールドに自動貼り付け",
|
||||
"description": "他のアプリでテキスト入力欄がフォーカスされている場合、直接そこに貼り付けます。Voicebox はクリップボードの内容を一旦保存し、後で復元します。"
|
||||
}
|
||||
},
|
||||
"transcription": {
|
||||
"title": "文字起こし",
|
||||
"description": "キャプチャに使う音声認識モデルを選びます。",
|
||||
"model": {
|
||||
"title": "文字起こしモデル",
|
||||
"description": "Whisper は Voicebox に同梱されており、すべてマシン上で動作します。",
|
||||
"base": "Whisper Base · 74M · {{tail}}",
|
||||
"small": "Whisper Small · 244M · {{tail}}",
|
||||
"medium": "Whisper Medium · 769M · {{tail}}",
|
||||
"large": "Whisper Large · 1.5B · {{tail}}",
|
||||
"turbo": "Whisper Turbo · Pruned Large v3 · {{tail}}",
|
||||
"tail": {
|
||||
"fast": "高速",
|
||||
"balanced": "バランス",
|
||||
"higher": "高精度",
|
||||
"best": "最高精度",
|
||||
"nearBest": "ほぼ最高精度かつ高速"
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"title": "言語",
|
||||
"description": "ほとんどのキャプチャでは自動検出が機能します。常に同じ言語で話すなら固定してください。",
|
||||
"auto": "自動検出",
|
||||
"en": "英語",
|
||||
"es": "スペイン語",
|
||||
"fr": "フランス語",
|
||||
"de": "ドイツ語",
|
||||
"ja": "日本語",
|
||||
"zh": "中国語",
|
||||
"hi": "ヒンディー語"
|
||||
},
|
||||
"archive": {
|
||||
"title": "音声をアーカイブ",
|
||||
"description": "文字起こしと一緒に元の録音も保持します。"
|
||||
}
|
||||
},
|
||||
"refinement": {
|
||||
"title": "整形",
|
||||
"description": "ローカル LLM を任意で実行し、フィラー語、句読点、自己修正を文字起こしから整理します。",
|
||||
"auto": {
|
||||
"title": "文字起こしを自動で整形",
|
||||
"description": "キャプチャごとに実行されます。キャプチャタブで生テキストと整形済みを切り替えることもできます。"
|
||||
},
|
||||
"model": {
|
||||
"title": "整形モデル",
|
||||
"description": "大きなモデルは遅くなりますが、微妙な自己修正や専門用語をより適切に処理します。",
|
||||
"size06": "Qwen3 · 0.6B · 400 MB · {{tail}}",
|
||||
"size17": "Qwen3 · 1.7B · 1.1 GB · {{tail}}",
|
||||
"size40": "Qwen3 · 4B · 2.5 GB · {{tail}}",
|
||||
"tail": {
|
||||
"veryFast": "超高速",
|
||||
"fast": "高速",
|
||||
"fullQuality": "高品質"
|
||||
}
|
||||
},
|
||||
"smartCleanup": {
|
||||
"title": "スマートクリーンアップ",
|
||||
"description": "言い回しを変えずに、フィラー語(えーと、あの、みたいな)を削除し、句読点を補い、大文字小文字を整えます。"
|
||||
},
|
||||
"selfCorrection": {
|
||||
"title": "自己修正を削除",
|
||||
"description": "途中で言い直したとき(「やっぱり違う…」「いや、こうじゃなくて…」)、撤回した部分を削除して最終的な意図のみを残します。"
|
||||
},
|
||||
"preserveTechnical": {
|
||||
"title": "専門用語を保持",
|
||||
"description": "コードの識別子、コマンド名、頭字語を発話どおりに保持します。コード入力欄にディクテーションするときに有効にしてください。"
|
||||
}
|
||||
},
|
||||
"playback": {
|
||||
"title": "再生",
|
||||
"description": "キャプチャタブの「ボイスで再生」アクションで使うデフォルトのボイス。",
|
||||
"defaultVoice": {
|
||||
"title": "デフォルトボイス",
|
||||
"description": "ボイスを選ばずに「ボイスで再生」をクリックしたときに使われます。キャプチャごとに変更できます。",
|
||||
"noClonedVoices": "クローンしたボイスはまだありません",
|
||||
"noneSelected": "未選択",
|
||||
"clonedVoices": "クローンしたボイス"
|
||||
}
|
||||
},
|
||||
"storage": {
|
||||
"title": "ストレージ",
|
||||
"description": "キャプチャは Voicebox のデータディレクトリに、音声と文字起こしのペアファイルとして保存されます。",
|
||||
"retention": {
|
||||
"title": "保持期間",
|
||||
"description": "キャプチャを保持する期間です。音声と文字起こしの両方に適用されます。",
|
||||
"forever": "永久に保持",
|
||||
"d90": "90 日",
|
||||
"d30": "30 日",
|
||||
"d7": "7 日"
|
||||
},
|
||||
"folder": {
|
||||
"title": "キャプチャフォルダ",
|
||||
"description": "キャプチャの音声と文字起こしをディスクに保存する場所。",
|
||||
"open": "開く"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"aboutTitle": "キャプチャについて",
|
||||
"aboutBody": "マシン上のどこからでもショートカットを押し続けて話すと、Voicebox があなたの声をテキストに変換します。クローンしたどのボイスでも再生でき、任意のアプリに貼り付けたり、コーディングエージェントに渡したりできます。",
|
||||
"differencesTitle": "ここが違います",
|
||||
"local": {
|
||||
"title": "完全にローカル。",
|
||||
"body": "Whisper と整形用 LLM はあなたのハードウェア上で動作します。クラウドもアカウントも不要で、声がマシンの外に出ることはありません。"
|
||||
},
|
||||
"playAs": {
|
||||
"title": "どのボイスでも再生。",
|
||||
"body": "クローンしたどのプロファイルでも文字起こしを読み上げできます。"
|
||||
},
|
||||
"crossPlatform": {
|
||||
"title": "クロスプラットフォーム。",
|
||||
"body": "macOS、Windows、Linux で同じショートカットと同じフローを利用できます。"
|
||||
},
|
||||
"windowsCaveat": {
|
||||
"title": "Windows での注意点",
|
||||
"body": "Voicebox 自体や管理者として実行中のアプリにフォーカスがあるあいだは、ショートカットが反応しません。現在対応中です。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mcp": {
|
||||
"install": {
|
||||
"title": "エージェントにインストール",
|
||||
"description": "アプリが開いている間、Voicebox はローカルで MCP サーバーを公開します。以下のスニペットを、お使いのエージェントの MCP 設定に貼り付けてください。",
|
||||
"http": {
|
||||
"title": "HTTP(推奨)",
|
||||
"description": "HTTP MCP に対応するクライアント向け — Claude Code、Cursor、Windsurf、VS Code。"
|
||||
},
|
||||
"claudeCode": {
|
||||
"title": "Claude Code 用ワンライナー",
|
||||
"description": "Claude Code CLI 経由で登録します。"
|
||||
},
|
||||
"stdio": {
|
||||
"title": "Stdio(フォールバック)",
|
||||
"description": "stdio プロセスのみを起動するクライアント向け。シムバイナリはアプリに同梱されています。"
|
||||
},
|
||||
"copy": "コピー",
|
||||
"copied": "コピーしました"
|
||||
},
|
||||
"defaultVoice": {
|
||||
"title": "デフォルトボイス",
|
||||
"description": "エージェントが特定のプロファイルを指定せず、クライアントごとのバインディングもない状態で voicebox.speak を呼び出したときに使われます。",
|
||||
"label": "デフォルトの再生ボイス",
|
||||
"labelHint": "キャプチャタブの「ボイスで再生」ドロップダウンと共有 — パッシブ再生用に 1 つのデフォルトボイスを設定します。",
|
||||
"none": "(なし)"
|
||||
},
|
||||
"bindings": {
|
||||
"title": "エージェントごとのボイス",
|
||||
"description": "特定のエージェントに特定のボイスを割り当てて、見なくても誰が話しているか分かるようにします。エージェントは X-Voicebox-Client-Id ヘッダー(stdio の場合は VOICEBOX_CLIENT_ID 環境変数)で自身を識別します。",
|
||||
"empty": "バインディングはまだありません。下から追加し、対応する <code>X-Voicebox-Client-Id</code> を送信するように MCP クライアントを設定してください。",
|
||||
"lastSeen": "最終接続 {{when}}",
|
||||
"lastSeenTitle": "最終接続 {{when}}",
|
||||
"neverConnected": "未接続",
|
||||
"defaultOption": "(デフォルト)",
|
||||
"removeAria": "{{client}} のバインディングを削除",
|
||||
"add": {
|
||||
"title": "バインディングを追加",
|
||||
"clientIdPlaceholder": "クライアント ID(例:claude-code)",
|
||||
"labelPlaceholder": "ラベル(任意)",
|
||||
"action": "バインディングを追加"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"aboutTitle": "MCP について",
|
||||
"aboutBody": "Model Context Protocol を使うと、Claude Code、Cursor、Windsurf などの AI コーディングエージェントから Voicebox のツールを呼び出せます。クローンしたボイスで発話したり、音声を文字起こししたり、キャプチャを参照したりできます。",
|
||||
"toolsTitle": "利用可能なツール",
|
||||
"tools": {
|
||||
"speak": "ボイスプロファイルでテキストを発話します。",
|
||||
"transcribe": "クリップに対して Whisper STT を実行します。",
|
||||
"listCaptures": "最近のディクテーション/録音。",
|
||||
"listProfiles": "利用可能なボイスプロファイル。"
|
||||
},
|
||||
"postSpeak": "シェルスクリプト、ACP、A2A 用に <code>POST /speak</code> としても公開されています。"
|
||||
}
|
||||
},
|
||||
"gpu": {
|
||||
@@ -752,7 +1158,8 @@
|
||||
"unknownSize": "サイズ不明",
|
||||
"sections": {
|
||||
"voiceGeneration": "音声生成",
|
||||
"transcription": "文字起こし"
|
||||
"transcription": "文字起こし",
|
||||
"languageModels": "言語モデル"
|
||||
},
|
||||
"status": {
|
||||
"loaded": "読み込み済み"
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"nav": {
|
||||
"generate": "生成",
|
||||
"stories": "故事",
|
||||
"captures": "捕获",
|
||||
"voices": "声音",
|
||||
"effects": "效果",
|
||||
"audio": "音频",
|
||||
@@ -21,6 +22,149 @@
|
||||
"settings": "设置",
|
||||
"updateBadge": "更新"
|
||||
},
|
||||
"captures": {
|
||||
"title": "捕获",
|
||||
"beta": "Beta",
|
||||
"searchPlaceholder": "搜索转录文本……",
|
||||
"snippetEmpty": "(暂无转录)",
|
||||
"noTranscriptError": "此次捕获尚无转录文本",
|
||||
"captureCardLabel": "捕获 · {{when}}",
|
||||
"header": {
|
||||
"modelSummary": "Whisper {{stt}} · Qwen3 · {{llm}}"
|
||||
},
|
||||
"source": {
|
||||
"dictation": "听写",
|
||||
"recording": "录制",
|
||||
"file": "文件"
|
||||
},
|
||||
"transcript": {
|
||||
"refined": "精修",
|
||||
"raw": "原始",
|
||||
"refinedHint": "由 Qwen3 · {{model}} 精修",
|
||||
"rawHint": "由 Whisper {{model}} 转录"
|
||||
},
|
||||
"actions": {
|
||||
"configure": "配置",
|
||||
"import": "导入",
|
||||
"importing": "上传中…",
|
||||
"dictate": "听写",
|
||||
"stop": "停止",
|
||||
"copy": "复制",
|
||||
"refine": "精修",
|
||||
"reRefine": "重新精修",
|
||||
"export": "导出",
|
||||
"exportDropdownLabel": "导出格式",
|
||||
"exportAudio": "音频 (WAV)",
|
||||
"exportTranscript": "文字稿 (TXT)",
|
||||
"exportMarkdown": "Markdown (MD)",
|
||||
"delete": "删除",
|
||||
"playAs": "以 {{name}} 播放",
|
||||
"playAsFallback": "播放为……",
|
||||
"playAsGenerating": "生成中…",
|
||||
"playAsStop": "停止 · {{name}}",
|
||||
"playAsStopFallback": "停止 · 声音",
|
||||
"playAsDropdownLabel": "将转录播放为"
|
||||
},
|
||||
"empty": {
|
||||
"noMatches": "没有捕获匹配 \"{{query}}\"",
|
||||
"none": "暂无捕获。",
|
||||
"loading": "加载捕获中…",
|
||||
"pickOne": "选择一项捕获以查看转录。",
|
||||
"holdToRecord": "按住以录制",
|
||||
"toggleHandsFree": "切换免提模式",
|
||||
"pressShortcut": "在系统的任何位置按下快捷键以开始第一次捕获。",
|
||||
"turnOnShortcut": "开启全局快捷键以在任何位置进行听写——或点击上方的「听写」在应用内进行捕获。",
|
||||
"openSettings": "打开「捕获」设置"
|
||||
},
|
||||
"deleteDialog": {
|
||||
"title": "删除捕获",
|
||||
"description": "这将永久删除该捕获及其音频和转录。此操作不可撤销。",
|
||||
"deleting": "删除中…"
|
||||
},
|
||||
"toast": {
|
||||
"deleteFailed": "删除失败",
|
||||
"playAsFailed": "播放失败",
|
||||
"noVoice": "暂无声音档案",
|
||||
"noVoiceDescription": "使用「播放为」之前请先创建声音档案。",
|
||||
"transcriptCopied": "转录已复制",
|
||||
"copyFailed": "复制失败",
|
||||
"exportSuccess": "已导出到 {{path}}",
|
||||
"exportFailed": "导出失败",
|
||||
"exportEmpty": "无可导出的内容",
|
||||
"shortcutNotArmed": "快捷键已开启,但尚未就绪",
|
||||
"shortcutNotArmedDescription_one": "{{names}} 仍需下载。打开「捕获」标签页开始下载。",
|
||||
"shortcutNotArmedDescription_other": "{{names}} 仍需下载。打开「捕获」标签页开始下载。"
|
||||
},
|
||||
"pill": {
|
||||
"recording": "录制中",
|
||||
"transcribing": "转录中",
|
||||
"refining": "精修中",
|
||||
"speaking": "朗读中",
|
||||
"completed": "完成",
|
||||
"stopAria": "停止录制",
|
||||
"errorFallback": "出现了错误",
|
||||
"errorCopyTooltip": "点击以复制错误"
|
||||
},
|
||||
"chord": {
|
||||
"capturing": "捕获中…",
|
||||
"pressShortcut": "按下您的快捷键",
|
||||
"noKeys": "尚无按键",
|
||||
"unsupported": "「{{key}}」不支持用于组合键。请尝试修饰键或字母键。",
|
||||
"notSet": "未设置"
|
||||
},
|
||||
"readiness": {
|
||||
"title": "听写前还需准备几项",
|
||||
"subheading": "在以下所有项目就绪之前,快捷键将保持关闭。",
|
||||
"downloadButton": "下载",
|
||||
"downloading": "下载中…",
|
||||
"downloadingPercent": "下载中… {{pct}}%",
|
||||
"downloadStarted": "下载已开始",
|
||||
"downloadStartedDescription": "{{name}} 正在下载。下载完成后快捷键会自动就绪。",
|
||||
"downloadFailed": "下载失败",
|
||||
"stt": {
|
||||
"label": "{{name}}(语音转文本)",
|
||||
"ready": "模型已下载。",
|
||||
"missing": "用于转录您的音频",
|
||||
"missingWithSize": "用于转录您的音频 · {{size}}"
|
||||
},
|
||||
"llm": {
|
||||
"label": "{{name}}(精修)",
|
||||
"ready": "模型已下载。",
|
||||
"missing": "在粘贴前清理原始转录文本",
|
||||
"missingWithSize": "在粘贴前清理原始转录文本 · {{size}}"
|
||||
},
|
||||
"inputMonitoring": {
|
||||
"label": "「输入监控」权限",
|
||||
"ready": "macOS 允许 Voicebox 检测您的全局快捷键。",
|
||||
"missing": "macOS 需要允许 Voicebox 检测全局快捷键。",
|
||||
"openSettings": "打开设置"
|
||||
},
|
||||
"accessibility": {
|
||||
"label": "「辅助功能」权限",
|
||||
"ready": "Voicebox 可以将转录粘贴到其他应用中。",
|
||||
"missing": "需要此权限,转录才能粘贴到当前焦点应用。",
|
||||
"openSettings": "打开设置"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"accessibility": {
|
||||
"title": "授予「辅助功能」权限以启用自动粘贴",
|
||||
"body": "Voicebox 需要在 <path>系统设置 → 隐私与安全性 → 辅助功能</path> 中获得权限,才能将转录粘贴到其他应用。即使没有此权限,听写仍会保存到「捕获」标签页。",
|
||||
"openSettings": "打开设置",
|
||||
"recheck": "我已启用",
|
||||
"rechecking": "检查中…",
|
||||
"stillMissing": "仍未检测到。切换权限后,macOS 通常需要退出并重新打开 Voicebox。"
|
||||
},
|
||||
"inputMonitoring": {
|
||||
"title": "授予「输入监控」权限以启用全局快捷键",
|
||||
"body": "Voicebox 需要在 <path>系统设置 → 隐私与安全性 → 输入监控</path> 中获得权限,才能检测您的听写组合键。开关已开启,但在您允许之前 macOS 会拦截按键事件。",
|
||||
"openSettings": "打开设置",
|
||||
"recheck": "我已启用",
|
||||
"rechecking": "检查中…",
|
||||
"stillMissing": "仍未检测到。切换权限后,macOS 通常需要退出并重新打开 Voicebox。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"voicesTab": {
|
||||
"title": "声音",
|
||||
"loading": "加载声音中…",
|
||||
@@ -125,7 +269,10 @@
|
||||
"noPreference": "无偏好",
|
||||
"defaultEngineHint": "选择该档案时自动使用此引擎。",
|
||||
"defaultEffects": "默认效果",
|
||||
"defaultEffectsHint": "自动应用于使用此声音的所有新生成的效果。"
|
||||
"defaultEffectsHint": "自动应用于使用此声音的所有新生成的效果。",
|
||||
"personalityLabel": "人物设定",
|
||||
"personalityPlaceholder": "例如:「一位脾气暴躁的海盗,只会用航海比喻说话」",
|
||||
"personalityHint": "这个声音是谁、说话方式如何。会驱动生成页面上的「撰写」按钮和入戏改写开关。留空则两者都隐藏。"
|
||||
},
|
||||
"avatar": {
|
||||
"alt": "头像预览"
|
||||
@@ -415,9 +562,11 @@
|
||||
"title": "故事",
|
||||
"newStory": "新建故事",
|
||||
"loading": "加载故事中…",
|
||||
"searchPlaceholder": "搜索故事…",
|
||||
"empty": {
|
||||
"title": "暂无故事",
|
||||
"hint": "创建您的第一个故事以开始"
|
||||
"hint": "创建您的第一个故事以开始",
|
||||
"noMatches": "没有故事匹配 “{{query}}”"
|
||||
},
|
||||
"row": {
|
||||
"itemCount_one": "{{count}} 项",
|
||||
@@ -480,16 +629,23 @@
|
||||
},
|
||||
"itemActions": {
|
||||
"playFromHere": "从此处播放",
|
||||
"regenerate": "重新生成",
|
||||
"removeFromStory": "从故事中移除"
|
||||
},
|
||||
"importAudio": "导入音频…",
|
||||
"importing": "正在导入…",
|
||||
"dropToImport": "拖放以导入音频",
|
||||
"toast": {
|
||||
"removeFailed": "移除项目失败",
|
||||
"reorderFailed": "重新排序项目失败",
|
||||
"exportFailed": "导出音频失败",
|
||||
"addFailed": "添加生成失败"
|
||||
"addFailed": "添加生成失败",
|
||||
"regenerateFailed": "重新生成失败",
|
||||
"importFailed": "导入音频失败"
|
||||
}
|
||||
},
|
||||
"history": {
|
||||
"empty": "暂无语音生成…",
|
||||
"actions": {
|
||||
"menu": "操作",
|
||||
"play": "播放",
|
||||
@@ -550,6 +706,18 @@
|
||||
"effects": {
|
||||
"none": "无效果",
|
||||
"profileDefault": "档案默认"
|
||||
},
|
||||
"compose": {
|
||||
"tooltip": "撰写",
|
||||
"ariaLabel": "以人物设定撰写一句台词",
|
||||
"failedTitle": "撰写失败",
|
||||
"failedDescription": "无法根据此人物设定生成文本。"
|
||||
},
|
||||
"persona": {
|
||||
"tooltipActive": "正以人物设定朗读",
|
||||
"tooltipInactive": "以人物设定朗读",
|
||||
"ariaLabelActive": "正以人物设定朗读",
|
||||
"ariaLabelInactive": "以人物设定朗读"
|
||||
}
|
||||
},
|
||||
"main": {
|
||||
@@ -571,6 +739,8 @@
|
||||
"tabs": {
|
||||
"general": "常规",
|
||||
"generation": "生成",
|
||||
"captures": "捕获",
|
||||
"mcp": "MCP",
|
||||
"gpu": "GPU",
|
||||
"logs": "日志",
|
||||
"changelog": "更新日志",
|
||||
@@ -580,6 +750,15 @@
|
||||
"label": "语言",
|
||||
"description": "选择 Voicebox 的显示语言。"
|
||||
},
|
||||
"theme": {
|
||||
"label": "主题",
|
||||
"description": "跟随系统外观,或固定为浅色 / 深色模式。",
|
||||
"options": {
|
||||
"system": "跟随系统",
|
||||
"light": "浅色",
|
||||
"dark": "深色"
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"docs": { "title": "阅读文档" },
|
||||
"discord": { "title": "加入 Discord", "subtitle": "获取帮助 & 分享声音" },
|
||||
@@ -676,6 +855,233 @@
|
||||
"title": "生成文件夹",
|
||||
"description": "生成的音频文件在磁盘上的存储位置。",
|
||||
"open": "打开"
|
||||
},
|
||||
"sidebar": {
|
||||
"aboutTitle": "关于语音生成",
|
||||
"aboutBody": "用一段简短样本克隆声音,然后用任意声音、任意语言生成语音。把 TTS 接入 AI 代理、游戏、播客或长篇旁白。",
|
||||
"differencesTitle": "不同之处",
|
||||
"clone": {
|
||||
"title": "几秒内克隆任意声音。",
|
||||
"body": "几秒钟的参考音频就足够了。需要更高质量时,支持多样本克隆。"
|
||||
},
|
||||
"engines": {
|
||||
"title": "七种引擎,23 种语言。",
|
||||
"body": "选择最合适的取舍——质量、速度,或多语言覆盖。"
|
||||
},
|
||||
"agentReady": {
|
||||
"title": "面向代理。",
|
||||
"body": "REST API 支持按档案控制——给任何 AI 一个您克隆的声音。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"captures": {
|
||||
"dictation": {
|
||||
"title": "听写",
|
||||
"description": "使用全局快捷键在系统的任何位置进行捕获。",
|
||||
"globalShortcut": {
|
||||
"title": "全局快捷键",
|
||||
"description": "按住快捷键即可在系统的任何位置录制。松开后进行转录。"
|
||||
},
|
||||
"pushToTalk": {
|
||||
"title": "按住说话快捷键",
|
||||
"description": "在系统任何位置按住这些键以录制。松开即可停止并转录。",
|
||||
"change": "更改"
|
||||
},
|
||||
"toggle": {
|
||||
"title": "切换快捷键",
|
||||
"description": "按一次开始免提录制,再按一次停止。通常是按住说话的快捷键加上空格。",
|
||||
"change": "更改"
|
||||
},
|
||||
"chordPicker": {
|
||||
"pttTitle": "设置按住说话快捷键",
|
||||
"pttDescription": "按住您要使用的按键,然后松开并点击「保存」。右侧的修饰键徽章会显示按键是左侧还是右侧的变体。",
|
||||
"toggleTitle": "设置切换快捷键",
|
||||
"toggleDescription": "按住您要使用的按键,然后松开并点击「保存」。请选择与按住说话组合键不同的按键。"
|
||||
},
|
||||
"preview": {
|
||||
"title": "预览",
|
||||
"description": "按住快捷键时屏幕上显示的内容。"
|
||||
},
|
||||
"copyToClipboard": {
|
||||
"title": "将转录复制到剪贴板",
|
||||
"description": "捕获完成后,清理过的转录会出现在剪贴板上。"
|
||||
},
|
||||
"autoPaste": {
|
||||
"title": "自动粘贴到当前焦点的文本字段",
|
||||
"description": "如果其他应用中有焦点输入框,则直接粘贴进去。Voicebox 会保存并恢复您剪贴板原有的内容。"
|
||||
}
|
||||
},
|
||||
"transcription": {
|
||||
"title": "转录",
|
||||
"description": "选择捕获时使用哪个语音转文本模型。",
|
||||
"model": {
|
||||
"title": "转录模型",
|
||||
"description": "Whisper 随 Voicebox 一同发布,完全在您的设备上运行。",
|
||||
"base": "Whisper Base · 74M · {{tail}}",
|
||||
"small": "Whisper Small · 244M · {{tail}}",
|
||||
"medium": "Whisper Medium · 769M · {{tail}}",
|
||||
"large": "Whisper Large · 1.5B · {{tail}}",
|
||||
"turbo": "Whisper Turbo · 精简版 Large v3 · {{tail}}",
|
||||
"tail": {
|
||||
"fast": "快速",
|
||||
"balanced": "均衡",
|
||||
"higher": "更高准确度",
|
||||
"best": "最佳准确度",
|
||||
"nearBest": "接近最佳,速度快"
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"title": "语言",
|
||||
"description": "自动检测适用于大多数捕获。如果您总是说同一种语言,可以将其锁定。",
|
||||
"auto": "自动检测",
|
||||
"en": "英语",
|
||||
"es": "西班牙语",
|
||||
"fr": "法语",
|
||||
"de": "德语",
|
||||
"ja": "日语",
|
||||
"zh": "中文",
|
||||
"hi": "印地语"
|
||||
},
|
||||
"archive": {
|
||||
"title": "归档音频",
|
||||
"description": "在每次转录旁保留原始录音。"
|
||||
}
|
||||
},
|
||||
"refinement": {
|
||||
"title": "精修",
|
||||
"description": "可选择在转录上运行本地 LLM,以清理填充词、标点和自我纠正。",
|
||||
"auto": {
|
||||
"title": "自动精修转录",
|
||||
"description": "每次捕获后运行。您仍可以在「捕获」标签页中切换原始和精修视图。"
|
||||
},
|
||||
"model": {
|
||||
"title": "精修模型",
|
||||
"description": "更大的模型速度较慢,但能更好地处理细微的自我纠正和技术词汇。",
|
||||
"size06": "Qwen3 · 0.6B · 400 MB · {{tail}}",
|
||||
"size17": "Qwen3 · 1.7B · 1.1 GB · {{tail}}",
|
||||
"size40": "Qwen3 · 4B · 2.5 GB · {{tail}}",
|
||||
"tail": {
|
||||
"veryFast": "非常快",
|
||||
"fast": "快速",
|
||||
"fullQuality": "完整质量"
|
||||
}
|
||||
},
|
||||
"smartCleanup": {
|
||||
"title": "智能清理",
|
||||
"description": "去除填充词(嗯、呃、那个),还原标点和大小写,但不重新措辞。"
|
||||
},
|
||||
"selfCorrection": {
|
||||
"title": "去除自我纠正",
|
||||
"description": "当您说到一半改变想法时(「其实不对……」「等等,我是说……」),丢弃被收回的部分,只保留最终意图。"
|
||||
},
|
||||
"preserveTechnical": {
|
||||
"title": "保留技术术语",
|
||||
"description": "完全按原样保留代码标识符、命令名称和缩写。在向代码提示词中听写时建议开启。"
|
||||
}
|
||||
},
|
||||
"playback": {
|
||||
"title": "播放",
|
||||
"description": "「捕获」标签页中「播放为」操作的默认声音。",
|
||||
"defaultVoice": {
|
||||
"title": "默认声音",
|
||||
"description": "未选择声音直接点击「播放为」时使用。可对每次捕获单独更改。",
|
||||
"noClonedVoices": "暂无克隆的声音",
|
||||
"noneSelected": "未选择",
|
||||
"clonedVoices": "克隆的声音"
|
||||
}
|
||||
},
|
||||
"storage": {
|
||||
"title": "存储",
|
||||
"description": "捕获以配对的音频和转录文件保存在您的 Voicebox 数据目录中。",
|
||||
"retention": {
|
||||
"title": "保留",
|
||||
"description": "捕获保留多久。同时适用于音频和转录。",
|
||||
"forever": "永久保留",
|
||||
"d90": "90 天",
|
||||
"d30": "30 天",
|
||||
"d7": "7 天"
|
||||
},
|
||||
"folder": {
|
||||
"title": "捕获文件夹",
|
||||
"description": "捕获的音频和转录在磁盘上的存储位置。",
|
||||
"open": "打开"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"aboutTitle": "关于「捕获」",
|
||||
"aboutBody": "在系统的任何位置按住快捷键说话,Voicebox 就会把您的声音转换成文本。可用任何克隆的声音回放、粘贴到任何应用,或导入到您的编程代理中。",
|
||||
"differencesTitle": "不同之处",
|
||||
"local": {
|
||||
"title": "完全本地。",
|
||||
"body": "Whisper 和精修 LLM 都在您的硬件上运行。无云端、无账号,您的声音不会离开本机。"
|
||||
},
|
||||
"playAs": {
|
||||
"title": "以任何声音播放。",
|
||||
"body": "转录可以用您克隆的任何档案朗读出来。"
|
||||
},
|
||||
"crossPlatform": {
|
||||
"title": "跨平台。",
|
||||
"body": "在 macOS、Windows 和 Linux 上使用相同的快捷键和流程。"
|
||||
},
|
||||
"windowsCaveat": {
|
||||
"title": "Windows 上的提示",
|
||||
"body": "当 Voicebox 自身或任何以管理员身份运行的应用处于焦点时,快捷键不会触发。我们正在解决这个问题。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mcp": {
|
||||
"install": {
|
||||
"title": "安装到您的代理",
|
||||
"description": "只要应用打开,Voicebox 就会暴露一个本地 MCP 服务器。将以下任一片段粘贴到您的代理 MCP 配置中。",
|
||||
"http": {
|
||||
"title": "HTTP(推荐)",
|
||||
"description": "适用于支持 HTTP MCP 的客户端——Claude Code、Cursor、Windsurf、VS Code。"
|
||||
},
|
||||
"claudeCode": {
|
||||
"title": "Claude Code 一行命令",
|
||||
"description": "通过 Claude Code CLI 注册。"
|
||||
},
|
||||
"stdio": {
|
||||
"title": "Stdio(备选)",
|
||||
"description": "适用于仅启动 stdio 进程的客户端。垫片二进制随应用一同发布。"
|
||||
},
|
||||
"copy": "复制",
|
||||
"copied": "已复制"
|
||||
},
|
||||
"defaultVoice": {
|
||||
"title": "默认声音",
|
||||
"description": "当代理调用 voicebox.speak 但未指定具体档案、且没有按客户端绑定时使用。",
|
||||
"label": "默认播放声音",
|
||||
"labelHint": "与「捕获」标签页的「播放为」下拉菜单共享——被动播放的统一默认声音。",
|
||||
"none": "(无)"
|
||||
},
|
||||
"bindings": {
|
||||
"title": "按代理设置声音",
|
||||
"description": "将特定代理绑定到特定声音,这样不用看也能分辨谁在说话。代理通过 X-Voicebox-Client-Id 请求头(stdio 则用 VOICEBOX_CLIENT_ID 环境变量)来标识自己。",
|
||||
"empty": "暂无绑定。在下方添加一个,然后将您的 MCP 客户端配置为发送匹配的 <code>X-Voicebox-Client-Id</code>。",
|
||||
"lastSeen": "最后活跃 {{when}}",
|
||||
"lastSeenTitle": "最后活跃 {{when}}",
|
||||
"neverConnected": "从未连接",
|
||||
"defaultOption": "(默认)",
|
||||
"removeAria": "移除 {{client}} 的绑定",
|
||||
"add": {
|
||||
"title": "添加绑定",
|
||||
"clientIdPlaceholder": "客户端 ID(例如 claude-code)",
|
||||
"labelPlaceholder": "标签(可选)",
|
||||
"action": "添加绑定"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"aboutTitle": "关于 MCP",
|
||||
"aboutBody": "Model Context Protocol 让您的 AI 编程代理——Claude Code、Cursor、Windsurf——可以调用 Voicebox 工具。以克隆的声音朗读、转录音频、浏览捕获。",
|
||||
"toolsTitle": "可用工具",
|
||||
"tools": {
|
||||
"speak": "用声音档案朗读文本。",
|
||||
"transcribe": "对音频片段运行 Whisper 转录。",
|
||||
"listCaptures": "最近的听写 / 录制。",
|
||||
"listProfiles": "可用的声音档案。"
|
||||
},
|
||||
"postSpeak": "也以 <code>POST /speak</code> 暴露,可用于 shell 脚本、ACP、A2A。"
|
||||
}
|
||||
},
|
||||
"gpu": {
|
||||
@@ -752,7 +1158,8 @@
|
||||
"unknownSize": "未知大小",
|
||||
"sections": {
|
||||
"voiceGeneration": "语音生成",
|
||||
"transcription": "语音转录"
|
||||
"transcription": "语音转录",
|
||||
"languageModels": "语言模型"
|
||||
},
|
||||
"status": {
|
||||
"loaded": "已加载"
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"nav": {
|
||||
"generate": "生成",
|
||||
"stories": "故事",
|
||||
"captures": "擷取",
|
||||
"voices": "聲音",
|
||||
"effects": "效果",
|
||||
"audio": "音訊",
|
||||
@@ -21,6 +22,149 @@
|
||||
"settings": "設定",
|
||||
"updateBadge": "更新"
|
||||
},
|
||||
"captures": {
|
||||
"title": "擷取",
|
||||
"beta": "Beta",
|
||||
"searchPlaceholder": "搜尋轉錄文字……",
|
||||
"snippetEmpty": "(無轉錄文字)",
|
||||
"noTranscriptError": "此擷取尚無轉錄文字",
|
||||
"captureCardLabel": "擷取 · {{when}}",
|
||||
"header": {
|
||||
"modelSummary": "Whisper {{stt}} · Qwen3 · {{llm}}"
|
||||
},
|
||||
"source": {
|
||||
"dictation": "口述",
|
||||
"recording": "錄音",
|
||||
"file": "檔案"
|
||||
},
|
||||
"transcript": {
|
||||
"refined": "精修",
|
||||
"raw": "原始",
|
||||
"refinedHint": "由 Qwen3 · {{model}} 精修",
|
||||
"rawHint": "由 Whisper {{model}} 轉錄"
|
||||
},
|
||||
"actions": {
|
||||
"configure": "設定",
|
||||
"import": "匯入",
|
||||
"importing": "上傳中…",
|
||||
"dictate": "口述",
|
||||
"stop": "停止",
|
||||
"copy": "複製",
|
||||
"refine": "精修",
|
||||
"reRefine": "重新精修",
|
||||
"export": "匯出",
|
||||
"exportDropdownLabel": "匯出格式",
|
||||
"exportAudio": "音訊 (WAV)",
|
||||
"exportTranscript": "文字稿 (TXT)",
|
||||
"exportMarkdown": "Markdown (MD)",
|
||||
"delete": "刪除",
|
||||
"playAs": "以 {{name}} 播放",
|
||||
"playAsFallback": "以聲音播放……",
|
||||
"playAsGenerating": "生成中…",
|
||||
"playAsStop": "停止 · {{name}}",
|
||||
"playAsStopFallback": "停止 · 聲音",
|
||||
"playAsDropdownLabel": "以聲音播放轉錄文字"
|
||||
},
|
||||
"empty": {
|
||||
"noMatches": "找不到符合 \"{{query}}\" 的擷取",
|
||||
"none": "尚無擷取。",
|
||||
"loading": "載入擷取中…",
|
||||
"pickOne": "選擇一個擷取以檢視其轉錄文字。",
|
||||
"holdToRecord": "按住以錄音",
|
||||
"toggleHandsFree": "切換免持模式",
|
||||
"pressShortcut": "在您的電腦上任何位置按下快捷鍵以開始第一次擷取。",
|
||||
"turnOnShortcut": "開啟全域快捷鍵以從任何地方口述——或點選上方的「口述」進行 App 內擷取。",
|
||||
"openSettings": "開啟擷取設定"
|
||||
},
|
||||
"deleteDialog": {
|
||||
"title": "刪除擷取",
|
||||
"description": "這將永久刪除該擷取及其音訊與轉錄。此操作無法復原。",
|
||||
"deleting": "刪除中…"
|
||||
},
|
||||
"toast": {
|
||||
"deleteFailed": "刪除失敗",
|
||||
"playAsFailed": "以聲音播放失敗",
|
||||
"noVoice": "無聲音檔案",
|
||||
"noVoiceDescription": "使用「以聲音播放」前請先建立聲音檔案。",
|
||||
"transcriptCopied": "已複製轉錄文字",
|
||||
"copyFailed": "複製失敗",
|
||||
"exportSuccess": "已匯出至 {{path}}",
|
||||
"exportFailed": "匯出失敗",
|
||||
"exportEmpty": "沒有可匯出的內容",
|
||||
"shortcutNotArmed": "快捷鍵已開啟,但尚未就緒",
|
||||
"shortcutNotArmedDescription_one": "{{names}} 仍需下載。請開啟「擷取」分頁開始下載。",
|
||||
"shortcutNotArmedDescription_other": "{{names}} 仍需下載。請開啟「擷取」分頁開始下載。"
|
||||
},
|
||||
"pill": {
|
||||
"recording": "錄音中",
|
||||
"transcribing": "轉錄中",
|
||||
"refining": "精修中",
|
||||
"speaking": "發話中",
|
||||
"completed": "完成",
|
||||
"stopAria": "停止錄音",
|
||||
"errorFallback": "發生錯誤",
|
||||
"errorCopyTooltip": "點選複製錯誤訊息"
|
||||
},
|
||||
"chord": {
|
||||
"capturing": "擷取中…",
|
||||
"pressShortcut": "請按下您的快捷鍵",
|
||||
"noKeys": "尚未設定按鍵",
|
||||
"unsupported": "「{{key}}」無法用於組合鍵。請改用修飾鍵或字母鍵。",
|
||||
"notSet": "未設定"
|
||||
},
|
||||
"readiness": {
|
||||
"title": "口述前還需要幾項準備",
|
||||
"subheading": "在下列項目全部就緒前,快捷鍵將維持關閉。",
|
||||
"downloadButton": "下載",
|
||||
"downloading": "下載中…",
|
||||
"downloadingPercent": "下載中… {{pct}}%",
|
||||
"downloadStarted": "已開始下載",
|
||||
"downloadStartedDescription": "{{name}} 正在下載。下載完成後快捷鍵會自動就緒。",
|
||||
"downloadFailed": "下載失敗",
|
||||
"stt": {
|
||||
"label": "{{name}}(語音轉文字)",
|
||||
"ready": "模型已下載。",
|
||||
"missing": "用於轉錄您的音訊",
|
||||
"missingWithSize": "用於轉錄您的音訊 · {{size}}"
|
||||
},
|
||||
"llm": {
|
||||
"label": "{{name}}(精修)",
|
||||
"ready": "模型已下載。",
|
||||
"missing": "在貼上前清理原始轉錄文字",
|
||||
"missingWithSize": "在貼上前清理原始轉錄文字 · {{size}}"
|
||||
},
|
||||
"inputMonitoring": {
|
||||
"label": "輸入監控權限",
|
||||
"ready": "macOS 允許 Voicebox 偵測您的全域快捷鍵。",
|
||||
"missing": "macOS 需要允許 Voicebox 偵測全域快捷鍵。",
|
||||
"openSettings": "開啟設定"
|
||||
},
|
||||
"accessibility": {
|
||||
"label": "輔助使用權限",
|
||||
"ready": "Voicebox 可將轉錄文字貼到其他 App。",
|
||||
"missing": "需要此權限才能將轉錄文字貼到目前作用中的 App。",
|
||||
"openSettings": "開啟設定"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"accessibility": {
|
||||
"title": "授予輔助使用權限以啟用自動貼上",
|
||||
"body": "Voicebox 需要 <path>系統設定 → 私隱與安全性 → 輔助使用</path> 才能將轉錄文字貼到其他 App。即使沒有此權限,口述內容仍會出現在「擷取」分頁。",
|
||||
"openSettings": "開啟設定",
|
||||
"recheck": "我已啟用",
|
||||
"rechecking": "檢查中…",
|
||||
"stillMissing": "仍未偵測到。macOS 通常需要在切換權限後結束並重新開啟 Voicebox。"
|
||||
},
|
||||
"inputMonitoring": {
|
||||
"title": "授予輸入監控權限以啟用全域快捷鍵",
|
||||
"body": "Voicebox 需要 <path>系統設定 → 私隱與安全性 → 輸入監控</path> 才能偵測您的口述組合鍵。功能已開啟,但 macOS 在您允許前會封鎖按鍵事件。",
|
||||
"openSettings": "開啟設定",
|
||||
"recheck": "我已啟用",
|
||||
"rechecking": "檢查中…",
|
||||
"stillMissing": "仍未偵測到。macOS 通常需要在切換權限後結束並重新開啟 Voicebox。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"voicesTab": {
|
||||
"title": "聲音",
|
||||
"loading": "載入聲音中…",
|
||||
@@ -125,7 +269,10 @@
|
||||
"noPreference": "無偏好",
|
||||
"defaultEngineHint": "選擇此檔案時自動使用此引擎。",
|
||||
"defaultEffects": "預設效果",
|
||||
"defaultEffectsHint": "自動套用於使用此聲音所有新生成的效果。"
|
||||
"defaultEffectsHint": "自動套用於使用此聲音所有新生成的效果。",
|
||||
"personalityLabel": "個性",
|
||||
"personalityPlaceholder": "例如:「一位脾氣暴躁的海盜,只會用航海比喻說話」",
|
||||
"personalityHint": "這個聲音是誰以及他們如何說話。會驅動生成頁面上的「撰寫」按鈕和角色化重寫切換。留空則兩者都隱藏。"
|
||||
},
|
||||
"avatar": {
|
||||
"alt": "頭像預覽"
|
||||
@@ -415,9 +562,11 @@
|
||||
"title": "故事",
|
||||
"newStory": "新增故事",
|
||||
"loading": "載入故事中…",
|
||||
"searchPlaceholder": "搜尋故事…",
|
||||
"empty": {
|
||||
"title": "尚無故事",
|
||||
"hint": "建立您的第一個故事以開始"
|
||||
"hint": "建立您的第一個故事以開始",
|
||||
"noMatches": "沒有故事符合「{{query}}」"
|
||||
},
|
||||
"row": {
|
||||
"itemCount_one": "{{count}} 項",
|
||||
@@ -480,16 +629,23 @@
|
||||
},
|
||||
"itemActions": {
|
||||
"playFromHere": "從此處播放",
|
||||
"regenerate": "重新生成",
|
||||
"removeFromStory": "從故事中移除"
|
||||
},
|
||||
"importAudio": "匯入音訊…",
|
||||
"importing": "匯入中…",
|
||||
"dropToImport": "拖放以匯入音訊",
|
||||
"toast": {
|
||||
"removeFailed": "移除項目失敗",
|
||||
"reorderFailed": "重新排序項目失敗",
|
||||
"exportFailed": "匯出音訊失敗",
|
||||
"addFailed": "新增生成失敗"
|
||||
"addFailed": "新增生成失敗",
|
||||
"regenerateFailed": "重新生成失敗",
|
||||
"importFailed": "匯入音訊失敗"
|
||||
}
|
||||
},
|
||||
"history": {
|
||||
"empty": "尚無語音生成…",
|
||||
"actions": {
|
||||
"menu": "操作",
|
||||
"play": "播放",
|
||||
@@ -550,6 +706,18 @@
|
||||
"effects": {
|
||||
"none": "無效果",
|
||||
"profileDefault": "檔案預設"
|
||||
},
|
||||
"compose": {
|
||||
"tooltip": "撰寫",
|
||||
"ariaLabel": "以角色撰寫一句台詞",
|
||||
"failedTitle": "撰寫失敗",
|
||||
"failedDescription": "無法從此個性生成文字。"
|
||||
},
|
||||
"persona": {
|
||||
"tooltipActive": "以角色發話中",
|
||||
"tooltipInactive": "以角色發話",
|
||||
"ariaLabelActive": "以角色發話中",
|
||||
"ariaLabelInactive": "以角色發話"
|
||||
}
|
||||
},
|
||||
"main": {
|
||||
@@ -571,6 +739,8 @@
|
||||
"tabs": {
|
||||
"general": "一般",
|
||||
"generation": "生成",
|
||||
"captures": "擷取",
|
||||
"mcp": "MCP",
|
||||
"gpu": "GPU",
|
||||
"logs": "日誌",
|
||||
"changelog": "更新日誌",
|
||||
@@ -580,6 +750,15 @@
|
||||
"label": "語言",
|
||||
"description": "選擇 Voicebox 的顯示語言。"
|
||||
},
|
||||
"theme": {
|
||||
"label": "佈景主題",
|
||||
"description": "跟隨系統外觀,或固定為淺色 / 深色模式。",
|
||||
"options": {
|
||||
"system": "跟隨系統",
|
||||
"light": "淺色",
|
||||
"dark": "深色"
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"docs": { "title": "閱讀文件" },
|
||||
"discord": { "title": "加入 Discord", "subtitle": "取得協助與分享聲音" },
|
||||
@@ -676,6 +855,233 @@
|
||||
"title": "生成資料夾",
|
||||
"description": "生成的音訊檔案在磁碟上的儲存位置。",
|
||||
"open": "開啟"
|
||||
},
|
||||
"sidebar": {
|
||||
"aboutTitle": "關於語音生成",
|
||||
"aboutBody": "從一段簡短的樣本複製聲音,然後以任何聲音、跨任何語言生成語音。將 TTS 送進 AI 代理、遊戲、Podcast 或長篇旁白。",
|
||||
"differencesTitle": "有何不同",
|
||||
"clone": {
|
||||
"title": "幾秒內複製任何聲音。",
|
||||
"body": "幾秒鐘的參考音訊就夠了。需要更高品質時也支援多樣本。"
|
||||
},
|
||||
"engines": {
|
||||
"title": "七種引擎、23 種語言。",
|
||||
"body": "選擇最符合需求的取捨——品質、速度,或多語言覆蓋。"
|
||||
},
|
||||
"agentReady": {
|
||||
"title": "代理就緒。",
|
||||
"body": "REST API 提供逐一聲音檔案的控制——讓任何 AI 擁有您複製過的聲音。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"captures": {
|
||||
"dictation": {
|
||||
"title": "口述",
|
||||
"description": "使用全域快捷鍵從電腦上任何位置進行擷取。",
|
||||
"globalShortcut": {
|
||||
"title": "全域快捷鍵",
|
||||
"description": "按住快捷鍵以從電腦上任何位置錄音。放開後進行轉錄。"
|
||||
},
|
||||
"pushToTalk": {
|
||||
"title": "按住說話快捷鍵",
|
||||
"description": "在系統任何位置按住這些按鍵即可錄音。放開後停止並轉錄。",
|
||||
"change": "變更"
|
||||
},
|
||||
"toggle": {
|
||||
"title": "切換快捷鍵",
|
||||
"description": "按一次開始免持錄音。再按一次停止。通常為按住說話加上 Space。",
|
||||
"change": "變更"
|
||||
},
|
||||
"chordPicker": {
|
||||
"pttTitle": "設定按住說話快捷鍵",
|
||||
"pttDescription": "按住您要使用的按鍵,然後放開並點選「儲存」。右側修飾鍵徽章會顯示按鍵是左側或右側的變體。",
|
||||
"toggleTitle": "設定切換快捷鍵",
|
||||
"toggleDescription": "按住您要使用的按鍵,然後放開並點選「儲存」。請選擇與按住說話組合鍵不同的按鍵。"
|
||||
},
|
||||
"preview": {
|
||||
"title": "預覽",
|
||||
"description": "按住快捷鍵時螢幕上顯示的內容。"
|
||||
},
|
||||
"copyToClipboard": {
|
||||
"title": "將轉錄文字複製到剪貼簿",
|
||||
"description": "擷取完成時,清理過的轉錄文字會出現在您的剪貼簿。"
|
||||
},
|
||||
"autoPaste": {
|
||||
"title": "自動貼到目前作用中的文字欄位",
|
||||
"description": "若另一個 App 中已聚焦於文字輸入,直接貼進去。Voicebox 會儲存並還原您原本剪貼簿上的內容。"
|
||||
}
|
||||
},
|
||||
"transcription": {
|
||||
"title": "轉錄",
|
||||
"description": "選擇用於擷取的語音轉文字模型。",
|
||||
"model": {
|
||||
"title": "轉錄模型",
|
||||
"description": "Whisper 隨 Voicebox 提供,完全在您的電腦上執行。",
|
||||
"base": "Whisper Base · 74M · {{tail}}",
|
||||
"small": "Whisper Small · 244M · {{tail}}",
|
||||
"medium": "Whisper Medium · 769M · {{tail}}",
|
||||
"large": "Whisper Large · 1.5B · {{tail}}",
|
||||
"turbo": "Whisper Turbo · 精簡版 Large v3 · {{tail}}",
|
||||
"tail": {
|
||||
"fast": "快速",
|
||||
"balanced": "平衡",
|
||||
"higher": "較高準確度",
|
||||
"best": "最高準確度",
|
||||
"nearBest": "接近最佳,快速"
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"title": "語言",
|
||||
"description": "自動偵測適用於大多數擷取。若您總是說同一種語言,可以鎖定它。",
|
||||
"auto": "自動偵測",
|
||||
"en": "英文",
|
||||
"es": "西班牙文",
|
||||
"fr": "法文",
|
||||
"de": "德文",
|
||||
"ja": "日文",
|
||||
"zh": "中文",
|
||||
"hi": "印地文"
|
||||
},
|
||||
"archive": {
|
||||
"title": "封存音訊",
|
||||
"description": "在每筆轉錄文字旁保留原始錄音。"
|
||||
}
|
||||
},
|
||||
"refinement": {
|
||||
"title": "精修",
|
||||
"description": "可選擇在轉錄文字上執行本地 LLM,以清除贅詞、補上標點與修正自我更正。",
|
||||
"auto": {
|
||||
"title": "自動精修轉錄文字",
|
||||
"description": "每次擷取後執行。您仍可在「擷取」分頁中切換原始與精修版本。"
|
||||
},
|
||||
"model": {
|
||||
"title": "精修模型",
|
||||
"description": "較大的模型較慢,但對於細微的自我更正與專業詞彙處理得更好。",
|
||||
"size06": "Qwen3 · 0.6B · 400 MB · {{tail}}",
|
||||
"size17": "Qwen3 · 1.7B · 1.1 GB · {{tail}}",
|
||||
"size40": "Qwen3 · 4B · 2.5 GB · {{tail}}",
|
||||
"tail": {
|
||||
"veryFast": "非常快",
|
||||
"fast": "快速",
|
||||
"fullQuality": "完整品質"
|
||||
}
|
||||
},
|
||||
"smartCleanup": {
|
||||
"title": "智慧清理",
|
||||
"description": "移除贅詞(嗯、呃、那個之類),還原標點符號,修正大小寫,且不重新改寫。"
|
||||
},
|
||||
"selfCorrection": {
|
||||
"title": "移除自我更正",
|
||||
"description": "當您說到一半改變想法時(「其實不對……」、「等等,我是想說……」),刪掉收回的部分,只保留最終意圖。"
|
||||
},
|
||||
"preserveTechnical": {
|
||||
"title": "保留技術術語",
|
||||
"description": "完整保留所說的程式碼識別字、指令名稱與縮寫。當您要對程式碼提示進行口述時請開啟。"
|
||||
}
|
||||
},
|
||||
"playback": {
|
||||
"title": "播放",
|
||||
"description": "「擷取」分頁中「以聲音播放」動作的預設聲音。",
|
||||
"defaultVoice": {
|
||||
"title": "預設聲音",
|
||||
"description": "當您點選「以聲音播放」但未先選擇聲音時使用。每筆擷取仍可個別變更。",
|
||||
"noClonedVoices": "尚無複製聲音",
|
||||
"noneSelected": "未選擇",
|
||||
"clonedVoices": "複製聲音"
|
||||
}
|
||||
},
|
||||
"storage": {
|
||||
"title": "儲存",
|
||||
"description": "擷取會以成對的音訊與轉錄文字檔形式,儲存在您的 Voicebox 資料目錄中。",
|
||||
"retention": {
|
||||
"title": "保留期限",
|
||||
"description": "擷取保留的時間長度。同時適用於音訊與轉錄文字。",
|
||||
"forever": "永久保留",
|
||||
"d90": "90 天",
|
||||
"d30": "30 天",
|
||||
"d7": "7 天"
|
||||
},
|
||||
"folder": {
|
||||
"title": "擷取資料夾",
|
||||
"description": "擷取的音訊與轉錄在磁碟上的儲存位置。",
|
||||
"open": "開啟"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"aboutTitle": "關於擷取",
|
||||
"aboutBody": "在電腦上任何位置按住快捷鍵說話,Voicebox 會將您的聲音轉成文字。可以用任何複製的聲音重播、貼到任何 App,或送進您的程式碼代理。",
|
||||
"differencesTitle": "有何不同",
|
||||
"local": {
|
||||
"title": "完全在本機。",
|
||||
"body": "Whisper 與精修 LLM 都在您的硬體上執行。沒有雲端、沒有帳號,您的聲音永遠不會離開電腦。"
|
||||
},
|
||||
"playAs": {
|
||||
"title": "以任何聲音播放。",
|
||||
"body": "轉錄文字可以用您複製過的任何聲音檔案讀回。"
|
||||
},
|
||||
"crossPlatform": {
|
||||
"title": "跨平台。",
|
||||
"body": "在 macOS、Windows 與 Linux 上享有相同的快捷鍵與相同的流程。"
|
||||
},
|
||||
"windowsCaveat": {
|
||||
"title": "Windows 上的提醒",
|
||||
"body": "當 Voicebox 本身或任何以系統管理員身分執行的應用程式取得焦點時,快捷鍵不會觸發。我們正在處理中。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mcp": {
|
||||
"install": {
|
||||
"title": "安裝到您的代理",
|
||||
"description": "App 開啟時 Voicebox 會提供本地 MCP 伺服器。將以下其中一段程式碼貼到您的代理 MCP 設定中。",
|
||||
"http": {
|
||||
"title": "HTTP(建議)",
|
||||
"description": "適用於支援 HTTP MCP 的客戶端——Claude Code、Cursor、Windsurf、VS Code。"
|
||||
},
|
||||
"claudeCode": {
|
||||
"title": "Claude Code 一行指令",
|
||||
"description": "透過 Claude Code CLI 註冊。"
|
||||
},
|
||||
"stdio": {
|
||||
"title": "Stdio(備用)",
|
||||
"description": "適用於只能啟動 stdio 程序的客戶端。Shim 二進位檔隨 App 提供。"
|
||||
},
|
||||
"copy": "複製",
|
||||
"copied": "已複製"
|
||||
},
|
||||
"defaultVoice": {
|
||||
"title": "預設聲音",
|
||||
"description": "當代理呼叫 voicebox.speak 卻未指定聲音檔案,且沒有對應客戶端綁定時使用。",
|
||||
"label": "預設播放聲音",
|
||||
"labelHint": "與「擷取」分頁的「以聲音播放」下拉選單共用——一個用於被動播放的預設聲音。",
|
||||
"none": "(無)"
|
||||
},
|
||||
"bindings": {
|
||||
"title": "個別代理聲音",
|
||||
"description": "將特定代理綁定到特定聲音,讓您不用看就能聽出是誰在說話。代理透過 X-Voicebox-Client-Id 標頭(stdio 則用 VOICEBOX_CLIENT_ID 環境變數)識別自己。",
|
||||
"empty": "尚無綁定。請在下方新增,然後將您的 MCP 客戶端設定為傳送對應的 <code>X-Voicebox-Client-Id</code>。",
|
||||
"lastSeen": "最後出現於 {{when}}",
|
||||
"lastSeenTitle": "最後出現於 {{when}}",
|
||||
"neverConnected": "從未連線",
|
||||
"defaultOption": "(預設)",
|
||||
"removeAria": "移除 {{client}} 的綁定",
|
||||
"add": {
|
||||
"title": "新增綁定",
|
||||
"clientIdPlaceholder": "客戶端 ID(例如 claude-code)",
|
||||
"labelPlaceholder": "標籤(選填)",
|
||||
"action": "新增綁定"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"aboutTitle": "關於 MCP",
|
||||
"aboutBody": "Model Context Protocol 讓您的 AI 程式碼代理——Claude Code、Cursor、Windsurf——可以呼叫 Voicebox 工具。以複製的聲音說話、轉錄音訊、瀏覽擷取。",
|
||||
"toolsTitle": "可用工具",
|
||||
"tools": {
|
||||
"speak": "以聲音檔案說出文字。",
|
||||
"transcribe": "對片段執行 Whisper STT。",
|
||||
"listCaptures": "近期口述 / 錄音。",
|
||||
"listProfiles": "可用的聲音檔案。"
|
||||
},
|
||||
"postSpeak": "也提供 <code>POST /speak</code> 介面,供 shell 指令稿、ACP、A2A 使用。"
|
||||
}
|
||||
},
|
||||
"gpu": {
|
||||
@@ -752,7 +1158,8 @@
|
||||
"unknownSize": "未知大小",
|
||||
"sections": {
|
||||
"voiceGeneration": "語音生成",
|
||||
"transcription": "語音轉錄"
|
||||
"transcription": "語音轉錄",
|
||||
"languageModels": "語言模型"
|
||||
},
|
||||
"status": {
|
||||
"loaded": "已載入"
|
||||
|
||||
Reference in New Issue
Block a user