The fork takes its own name everywhere a user can see it: binaries (voculinux, voculinux-gui), desktop entry, autostart, tray, D-Bus namespace (com.voculinux.Voculinux, no alias for the old name), config/data/cache dirs, single-instance lock, IBus engine identity, tray icons, resource prefixes, VOCULINUX_* environment names, and the update-checker user agent. The internal Python module stays vocalinux and the distribution metadata keeps the upstream URLs as provenance. On first run a previous Vocalinux install's config.json, custom dictionary, and portal restore token are copied once into the new locations (new config always wins, no merge, no write-back); models re-resolve through the pinned-checksum download path. An offline naming-sweep test proves no old-name strings remain on user-visible surfaces outside the allowlist. Refs #18
6.8 KiB
Custom Dictionary Support Design
Scope
Custom dictionary support has two separate jobs:
- Custom terms bias local Whisper, whisper.cpp, and Faster Whisper recognition toward names and jargon.
- Transcript corrections deterministically replace a known misheard phrase after transcription.
They appear together on the Custom Dictionary Settings page, but are not interchangeable. A correction replacement does not implicitly become a prompt term. That coupling would make recognition bias depend on correction order and would make the external terms contract surprising.
File contracts
Both files live in Voculinux's XDG-aware configuration directory:
$XDG_CONFIG_HOME/voculinux (or ~/.config/voculinux when unset).
| File | Contract | Consumer |
|---|---|---|
dictionary.txt |
UTF-8 (BOM accepted), one term per line. Blank lines and lines beginning with # are ignored. Terms are de-duplicated case-insensitively while retaining the first spelling. |
Voculinux and an external accessibility scanner. |
custom-dictionary-corrections.json |
UTF-8 JSON object: {"version": 1, "corrections": [{"heard": "super base", "replacement": "Supabase"}]}. |
Voculinux only. |
The terms file remains deliberately simple and scanner-friendly. Corrections need two unambiguous fields, a version, and exact replacement casing, so JSON is used rather than overloading the line file with an ambiguous delimiter grammar. The UI writes either file by atomic replacement; a failed UI save preserves the old file and reports an error.
The application reads both files for each completed transcript. An externally edited file therefore takes effect on the next dictation segment without an app restart. Missing, unreadable, malformed, or unsupported correction files are ignored safely; dictation continues without their entries.
The terms file may contain any number of lines, but Voculinux limits a single prompt to the first 200 valid terms and 2,000 characters. This bounds decoding work while leaving the scanner's complete line-file contract intact. The Custom Dictionary page keeps both an add/remove editor and a validated file chooser for the terms file.
Processing and engine behavior
For every completed audio segment, Voculinux applies this order:
- The selected engine transcribes audio.
- Live phrase corrections run against the raw transcript.
- Voice-command interpretation runs on that corrected text.
- Remaining text and actions are dispatched.
Corrections run before commands so a correction can prevent a command-like misrecognition from being acted on. This also means users must not create a replacement that deliberately creates a voice command unless that is intended.
Corrections match literal phrases case-insensitively, only where the adjacent characters are not Unicode word characters (or common combining marks). Longer phrases win over shorter overlaps; equal-length entries retain JSON order. Replacement text is inserted exactly as written. Inputs are normalized to NFC for matching, so composed/decomposed accents are handled consistently.
| Engine | Custom terms | Corrections |
|---|---|---|
| OpenAI Whisper | initial_prompt |
Yes |
| whisper.cpp | Advanced initial prompt followed by custom terms; explicit empty prompt clears reused native state | Yes |
| Faster Whisper | initial_prompt |
Yes |
| VOSK | No prompt-bias API | Yes |
| Parakeet | No prompt-bias API | Yes |
| Remote API | No prompt is added to current request formats | Yes |
--dictionary-file PATH preserves the #767 session-only behavior: PATH is
used as the custom terms file, it enables terms for that session, and Settings
does not modify the saved terms file or enablement. Corrections remain loaded
from the standard JSON file. The previous experimental #768
text_injection.custom_dictionary config list is read only as a fallback until
the JSON corrections file exists; it is never silently deleted or migrated.
The #767 persisted configuration keys are retained unchanged:
dictionary.enabled, dictionary.file_path (default
~/.config/voculinux/dictionary.txt), and dictionary.max_words. Existing
Peony settings therefore continue to select the scanner's dictionary.txt
file. New configured paths are accepted only when they can be expanded and are
either absent (so the UI can create them) or readable regular files; a failed
save leaves the prior setting in place. Invalid configured and --dictionary-file
paths are safely ignored and shown as unavailable rather than raising.
Alternatives rejected
- One mixed line file: cannot express arbitrary phrases and replacements without reserving/escaping delimiters, which weakens the scanner contract.
- Corrections in
config.json: makes a scanner compete with application settings writes and offers no durable external-file schema. - Post-command correction: allows command-like transcription errors to be acted on before they can be fixed.
- Automatically adding correction replacements to prompts: rejected because replacements are not automatically included in the recognition prompt; corrections work post-transcription on every engine.
Human test checklist
- Add
Voculinux,PyGObject, and a non-ASCII term in Custom terms; confirmdictionary.txtis UTF-8, one line per term, and an external edit changes the next Whisper, whisper.cpp, and Faster Whisper dictation segment. - Add
super base→Supabase; test lowercase, uppercase, punctuation, an overlapping short correction,C++, and non-ASCII text. Confirm replacement casing is exact and embedded text (for examplesupersize) is not replaced. - With voice commands enabled, add a correction that changes a command-like misrecognition into ordinary text. Confirm it is injected rather than acted on. Also verify an intentionally command-like replacement is avoided.
- Edit each file externally while Voculinux is running. Test an invalid JSON correction file and invalid UTF-8 terms file; confirm dictation continues and the Settings status/save feedback is clear.
- Test the UI with keyboard only: Add buttons via Enter, empty validation, update/remove feedback, narrow window, long entries, and high-contrast theme.
- Test all engines: Whisper, whisper.cpp, and Faster Whisper should use terms and corrections; VOSK, Parakeet, and remote API should use corrections only, with the VOSK terms warning.
- Start with
--dictionary-file /path/to/terms.txt; confirm the session uses that file, Settings disables terms editing, saved settings remain unchanged, and corrections still apply. - Start with an unresolved path such as
--dictionary-file ~missing-user/terms.txtand configure an unreadable path through the file chooser. Confirm Settings reports the path safely, dictation continues without terms, and the prior saveddictionary.file_pathremains intact.