diff --git a/web/public/sitemap.xml b/web/public/sitemap.xml index 7eda680..b3252ed 100644 --- a/web/public/sitemap.xml +++ b/web/public/sitemap.xml @@ -141,7 +141,7 @@ https://vocalinux.com/autostart/ - 2026-02-22 + 2026-11-01 monthly 0.7 @@ -187,4 +187,100 @@ monthly 0.8 + + https://vocalinux.com/parakeet/ + 2026-11-01 + monthly + 0.7 + + + https://vocalinux.com/faster-whisper/ + 2026-11-01 + monthly + 0.7 + + + https://vocalinux.com/dictation-pad/ + 2026-11-01 + monthly + 0.7 + + + https://vocalinux.com/transcribe-audio-files/ + 2026-11-01 + monthly + 0.7 + + + https://vocalinux.com/custom-dictionary/ + 2026-11-01 + monthly + 0.7 + + + https://vocalinux.com/multilingual-dictation/ + 2026-11-01 + monthly + 0.7 + + + https://vocalinux.com/pipewire/ + 2026-11-01 + monthly + 0.7 + + + https://vocalinux.com/global-shortcuts/ + 2026-11-01 + monthly + 0.7 + + + https://vocalinux.com/hyprland-sway/ + 2026-11-01 + monthly + 0.7 + + + https://vocalinux.com/local-gateway/ + 2026-11-01 + monthly + 0.7 + + + https://vocalinux.com/flatpak/ + 2026-11-01 + monthly + 0.7 + + + https://vocalinux.com/install/nixos/ + 2026-11-01 + monthly + 0.8 + + + https://vocalinux.com/install/debian/ + 2026-11-01 + monthly + 0.8 + + + https://vocalinux.com/install/opensuse/ + 2026-11-01 + monthly + 0.8 + + + https://vocalinux.com/install/linux-mint/ + 2026-11-01 + monthly + 0.8 + + + https://vocalinux.com/install/pop-os/ + 2026-11-01 + monthly + 0.8 + diff --git a/web/src/app/__tests__/seo-assets.test.ts b/web/src/app/__tests__/seo-assets.test.ts index 7cfb864..82cf125 100644 --- a/web/src/app/__tests__/seo-assets.test.ts +++ b/web/src/app/__tests__/seo-assets.test.ts @@ -50,10 +50,57 @@ describe("SEO Assets", () => { "https://vocalinux.com/advanced-settings/", "https://vocalinux.com/desktop-reliability/", "https://vocalinux.com/screenshots/", + "https://vocalinux.com/parakeet/", + "https://vocalinux.com/faster-whisper/", + "https://vocalinux.com/dictation-pad/", + "https://vocalinux.com/transcribe-audio-files/", + "https://vocalinux.com/custom-dictionary/", + "https://vocalinux.com/multilingual-dictation/", + "https://vocalinux.com/pipewire/", + "https://vocalinux.com/global-shortcuts/", + "https://vocalinux.com/hyprland-sway/", + "https://vocalinux.com/local-gateway/", + "https://vocalinux.com/flatpak/", + "https://vocalinux.com/install/nixos/", + "https://vocalinux.com/install/debian/", + "https://vocalinux.com/install/opensuse/", + "https://vocalinux.com/install/linux-mint/", + "https://vocalinux.com/install/pop-os/", ]; expectedUrls.forEach((url) => { expect(sitemapContent).toContain(`${url}`); }); }); + + it("lists every static route in the sitemap exactly once", () => { + const appDir = path.join(process.cwd(), "src", "app"); + const routes: string[] = []; + const walk = (dir: string, prefix: string) => { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + if ( + !entry.isDirectory() || + entry.name.startsWith("__") || + entry.name.startsWith("[") + ) { + continue; + } + const routePath = `${prefix}/${entry.name}`; + if (fs.existsSync(path.join(dir, entry.name, "page.tsx"))) { + routes.push(`https://vocalinux.com${routePath}/`); + } + walk(path.join(dir, entry.name), routePath); + } + }; + walk(appDir, ""); + + const locs = Array.from( + sitemapContent.matchAll(/([^<]+)<\/loc>/g), + (match) => match[1], + ); + expect(new Set(locs).size).toBe(locs.length); + routes.forEach((url) => { + expect(locs).toContain(url); + }); + }); }); diff --git a/web/src/app/autostart/page.tsx b/web/src/app/autostart/page.tsx index d1d275a..64c6ddc 100644 --- a/web/src/app/autostart/page.tsx +++ b/web/src/app/autostart/page.tsx @@ -59,7 +59,8 @@ const howItWorks = [ { step: 1, title: "Enable in Vocalinux", - description: "Open Vocalinux and enable 'Start on Login' via the tray menu or Settings dialog.", + description: + "Answer yes on the first-run prompt, or open Settings → General and turn on 'Start on Login'.", code: null, }, { @@ -78,8 +79,8 @@ const howItWorks = [ const managementMethods = [ { - method: "System Tray", - description: "Right-click the Vocalinux tray icon → Start on Login", + method: "First-run prompt", + description: "The welcome dialog asks once whether to start on login", icon: Power, }, { @@ -117,7 +118,7 @@ export default function AutostartPage() { headline: "Autostart Voice Dictation on Linux Login", description: "How to configure Vocalinux to start automatically on Linux login. XDG autostart guide for all desktop environments.", - dateModified: "2026-02-22", + dateModified: "2026-11-01", author: { "@type": "Person", name: "Jatin K Malik", @@ -235,6 +236,15 @@ export default function AutostartPage() { +
+

Moved out of the tray in v0.18.1

+

+ Older guides say to right-click the tray icon. Since v0.18.1 the tray menu is slimmer: + Start on Login lives only in Settings → General, where the settings search also finds it + as “autostart”. +

+
+

Not a systemd Service diff --git a/web/src/app/custom-dictionary/page.tsx b/web/src/app/custom-dictionary/page.tsx new file mode 100644 index 0000000..a649e45 --- /dev/null +++ b/web/src/app/custom-dictionary/page.tsx @@ -0,0 +1,451 @@ +import Link from "next/link"; +import { type Metadata } from "next"; +import { + BookOpen, + CheckCircle2, + ChevronRight, + Code, + Terminal, +} from "lucide-react"; +import { SeoSubpageShell } from "@/components/seo-subpage-shell"; +import { TerminalBlock } from "@/components/terminal-block"; +import { absoluteUrl, buildPageMetadata } from "@/lib/seo"; + +const PAGE_PATH = "/custom-dictionary"; + +const dictionaryFileCommand = + "vocalinux --dictionary-file /path/to/dictionary.txt"; + +const howToSteps = [ + { + name: "Open the Custom Dictionary page", + text: "Open Settings from the tray menu and choose Custom Dictionary in the sidebar.", + }, + { + name: "Add custom terms", + text: "Switch on Use custom terms, type a name or term into the Add a term field and click Add term. Or point Terms file at an existing UTF-8 file.", + }, + { + name: "Add transcript corrections", + text: "Under Transcript corrections, enter what the engine writes (for example super base) and what you want instead (for example Supabase), then click Add correction.", + }, + { + name: "Optionally set a post-processing script", + text: "Open the Post-Processing page, enter the path to an executable script in Script Path or click Browse…, and leave the field empty to turn it off.", + }, + { + name: "Dictate", + text: "Both dictionary files are re-read before each completed segment, so changes apply to your next dictation without a restart.", + }, +]; + +const pipeline = [ + "The engine transcribes your speech, biased by custom terms on Whisper, whisper.cpp and Faster Whisper.", + "Transcript corrections replace known mishearings. This works with every engine.", + "Voice commands are interpreted, if Voice Commands is on.", + "The post-processing script, if set, rewrites the final text.", + "The result is typed into the focused app, or appended to the Dictation Pad.", +]; + +const faqs = [ + { + q: "Which engines use custom terms?", + a: "Vocabulary bias works with Whisper, whisper.cpp and Faster Whisper. Transcript corrections work with every engine, including VOSK, Parakeet and the Remote API.", + }, + { + q: "Are corrections added to the vocabulary bias automatically?", + a: "No. Corrections and terms are separate. If you want the engine to lean toward a spelling as well as fix it afterwards, add it as a term too.", + }, + { + q: "My correction triggered a voice command. Why?", + a: "Corrections run before voice commands. If a replacement is itself a command phrase, it will act as one. Choose replacement text that is not a voice command unless that is what you want.", + }, + { + q: "My post-processing script runs but nothing changes. What should I check?", + a: "Vocalinux falls back to the original text when the script exits non-zero, cannot be started, or runs longer than 10 seconds. Make sure the file is executable, the path is absolute, and the script writes to standard output. The Vocalinux log records the exit code and standard error.", + }, + { + q: "Why did a segment disappear after I added a script?", + a: "If the script exits successfully but prints nothing, Vocalinux types nothing for that segment. Make sure every code path writes the text back out.", + }, +]; + +export const metadata: Metadata = buildPageMetadata({ + title: "Custom Dictionary, Corrections and Post-Processing | Vocalinux", + description: + "Teach Vocalinux your jargon: custom terms that bias Whisper, transcript corrections for every engine, and a stdin/stdout post-processing script hook.", + path: PAGE_PATH, + keywords: [ + "custom dictionary voice dictation Linux", + "Whisper custom vocabulary", + "whisper.cpp initial prompt terms", + "speech to text corrections", + "dictation post-processing script", + "Vocalinux custom dictionary", + ], +}); + +export default function CustomDictionaryPage() { + const articleJsonLd = { + "@context": "https://schema.org", + "@type": "TechArticle", + headline: + "Custom Dictionary, Transcript Corrections and Post-Processing in Vocalinux", + description: + "How Vocalinux custom terms, transcript corrections and the post-processing script hook work, with exact file formats and safe example scripts.", + dateModified: "2026-11-01", + author: { + "@type": "Person", + name: "Jatin K Malik", + url: "https://github.com/jatinkrmalik", + }, + publisher: { + "@type": "Organization", + name: "Vocalinux", + logo: { + "@type": "ImageObject", + url: absoluteUrl("/vocalinux.png"), + }, + }, + mainEntityOfPage: absoluteUrl(PAGE_PATH), + }; + + const howToJsonLd = { + "@context": "https://schema.org", + "@type": "HowTo", + name: "Set up a custom dictionary in Vocalinux", + step: howToSteps.map((step, index) => ({ + "@type": "HowToStep", + position: index + 1, + name: step.name, + text: step.text, + })), + }; + + const faqJsonLd = { + "@context": "https://schema.org", + "@type": "FAQPage", + mainEntity: faqs.map((item) => ({ + "@type": "Question", + name: item.q, + acceptedAnswer: { "@type": "Answer", text: item.a }, + })), + }; + + return ( + +