Files
voculinux/web/src/lib/seo.ts
T
xavierk c9be45d3c3 fix(web): link the fork's own source host and drop the upstream star count
The header badge fetched the star count of VocaHQ/vocalinux at build time
and labelled it as Voculinux's. The fetch and the count are gone; the
header, footer and social "source" links now go to the fork repository at
git.bongbetic.com/xavierk/voculinux, as do the issue links, the docs
anchors, the home page JSON-LD and the xbps release card.

Upstream GitHub stays only where it is the real artefact or provenance and
is labelled as such: the install script, the AppImage release card, the
upstream card and the changelog release links. The changelog now says its
entries are upstream history, the open-source page no longer claims
VocaHQ membership or an active GitHub community, and the organization
JSON-LD drops the upstream author as founder. DESIGN.md and PRODUCT.md
follow.
2026-11-06 18:22:17 +05:30

101 lines
2.7 KiB
TypeScript

import { type Metadata } from "next";
export const SITE_URL = "https://voculinux.com";
export const SITE_NAME = "Voculinux";
export const DEFAULT_OG_IMAGE_PATH = "/og-image.png";
export const DEFAULT_OG_IMAGE_ALT =
"Dictate locally. In any app. Offline voice dictation for Linux.";
export const TWITTER_HANDLE = "@vocahq";
export const VOCAHQ_URL = "https://vocahq.com";
// The fork's own source, issue and release host. Everything that says "our
// source", "our issues" or "our releases" resolves here.
export const SOURCE_REPO_URL = "https://git.bongbetic.com/xavierk/voculinux";
// Upstream Vocalinux on GitHub. Retained only as labelled provenance (the
// upstream install script, the AppImage and packages published there, and the
// upstream release history behind the changelog).
export const UPSTREAM_REPO_URL = "https://github.com/VocaHQ/vocalinux";
export const X_URL = "https://x.com/vocahq";
export const DISCORD_URL = "https://discord.gg/t6muquAJbm";
export const EMAIL_URL = "mailto:hello@vocahq.com";
interface PageMetadataOptions {
title: string;
description: string;
path?: string;
keywords?: string[];
}
const normalizePath = (path: string): string => {
if (path === "/") {
return "/";
}
const trimmed = path.replace(/^\/+|\/+$/g, "");
// Files such as /voculinux.png must not get the page trailing slash:
// /voculinux.png/ is a 404 on GitHub Pages.
if (/\.[a-z0-9]+$/i.test(trimmed)) {
return `/${trimmed}`;
}
return `/${trimmed}/`;
};
export const absoluteUrl = (path = "/"): string => {
return new URL(normalizePath(path), SITE_URL).toString();
};
export const buildPageMetadata = ({
title,
description,
path = "/",
keywords = [],
}: PageMetadataOptions): Metadata => {
const canonical = absoluteUrl(path);
// The root layout applies the "%s | Voculinux" template. Titles that already
// carry the brand suffix are emitted as-is so search results do not show
// "| Voculinux | Voculinux".
const pageTitle: Metadata["title"] = new RegExp(
`[|\\-–—]\\s*${SITE_NAME}$`,
).test(title)
? { absolute: title }
: title;
return {
title: pageTitle,
description,
keywords,
alternates: {
canonical,
languages: {
"en-US": canonical,
},
},
openGraph: {
type: "website",
locale: "en_US",
siteName: SITE_NAME,
url: canonical,
title,
description,
images: [
{
url: DEFAULT_OG_IMAGE_PATH,
width: 1200,
height: 630,
alt: DEFAULT_OG_IMAGE_ALT,
},
],
},
twitter: {
card: "summary_large_image",
title,
description,
images: [DEFAULT_OG_IMAGE_PATH],
creator: TWITTER_HANDLE,
site: TWITTER_HANDLE,
},
};
};