Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7b4fd19
docs: SEO titles for the 67 English pages whose title tag was two wor…
objectstack-fleet[bot] Oct 6, 2026
50f7841
Merge remote-tracking branch 'origin/main' into ff/issue-301
claude Oct 6, 2026
b421fbd
docs: muted-text and code-comment contrast, phone table affordance, F…
claude Oct 6, 2026
c5740f9
docs: record the measured search cold start and the smoke search requ…
claude Oct 6, 2026
21ef209
docs: state the measured contrast ratios in the theme and code-theme …
claude Oct 6, 2026
911a174
docs(views): declare views in a defineView container; list only the s…
claude Oct 6, 2026
c49dc6d
Merge remote-tracking branch 'origin/main' into ff/issue-311
claude Oct 6, 2026
5e666c3
fix(docs): run the awaited fumadocs-mdx write after next typegen in t…
claude Oct 6, 2026
5ad14ce
Merge remote-tracking branch 'origin/main' into ff/issue-305
claude Oct 6, 2026
41a1994
docs: localize fumadocs' accessible names, mark English page-tree ent…
claude Oct 6, 2026
c47c544
Merge remote-tracking branch 'origin/main' into ff/issue-307
claude Oct 6, 2026
69f7f1e
docs: make the views and flows code samples parse with @objectstack/s…
claude Oct 6, 2026
9709c50
Merge remote-tracking branch 'origin/main' into ff/issue-308
claude Oct 6, 2026
7c161c3
docs: a main landmark, a language switcher named by its text, a decor…
claude Oct 6, 2026
cc14430
ci: read the built pages for English chrome names and unmarked page-t…
claude Oct 6, 2026
77ad6ba
docs: the docs page's table-of-contents bar is not a second banner
claude Oct 6, 2026
5a07fbc
docs: the heading-anchor and code-copy buttons keep their own languag…
claude Oct 6, 2026
fa2390f
ci: the built-pages guard counts docs pages only
claude Oct 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
833 changes: 826 additions & 7 deletions .github/scripts/check-locale-surface.mjs

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions apps/docs/app/[lang]/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,18 @@ export default async function Page(props: {
dangerouslySetInnerHTML={{ __html: jsonLdHtml(item) }}
/>
))}
{/*
`role="main"`: the page's main landmark (#308). Fumadocs 16.8.12
renders none anywhere in the docs layout, so a screen reader's "main"
jump went nowhere. DocsPage passes its other props to the `<article>`
it renders (breadcrumb, title, body, footer; not the sidebar or the
table of contents), and ARIA in HTML allows `main` on an article.
Upgrade note: fumadocs-ui 16.16.2 wraps that article in a `<main>` of
its own. On a version that does, delete this prop, or the page has two
main landmarks (`patches/fumadocs-ui@16.8.12.patch` says the same).
*/}
<DocsPage
role="main"
toc={isFallback ? tocInLanguage(loaded.toc, contentLang) : loaded.toc}
full={page.data.full}
>
Expand All @@ -371,6 +382,7 @@ export default async function Page(props: {
openInGitHub: text.openInGitHub,
openInChatGPT: text.openInChatGPT,
openInClaude: text.openInClaude,
openInLLMPrompt: text.pageActionsOpenInLLMPrompt,
}}
/>
</div>
Expand Down
10 changes: 9 additions & 1 deletion apps/docs/components/ai/page-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,20 @@ export function ViewOptions({
openInGitHub: string;
openInChatGPT: string;
openInClaude: string;
/**
* The sentence the assistant is opened with, in the route locale, with
* `{url}` where the read URL goes (#308). Upstream fumadocs-ui 16.9.0's
* `pageActionsOpenInLLMPrompt`, same placeholder and same English.
*/
openInLLMPrompt: string;
};
}) {
const items = useMemo(() => {
const fullReadUrl =
typeof window !== 'undefined' ? new URL(readUrl, window.location.origin) : 'loading';
const q = `Read ${fullReadUrl}, I want to ask questions about it.`;
// A replacer function, not a replacement string: in a replacement string
// `$&` and its kin are patterns, and nothing stops a URL containing `$`.
const q = labels.openInLLMPrompt.replace('{url}', () => String(fullReadUrl));

return [
{
Expand Down
46 changes: 38 additions & 8 deletions apps/docs/lib/ui-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ const en = {
openInGitHub: 'Open in GitHub',
openInChatGPT: 'Open in ChatGPT',
openInClaude: 'Open in Claude',
/**
* What "Open in ChatGPT / Claude" asks the assistant (#308): `{url}` is
* replaced by the page's read URL. The key, the placeholder and the English
* are upstream fumadocs-ui 16.9.0's own. It is an app key, not passed to
* fumadocs: the page actions are this app's component.
*/
pageActionsOpenInLLMPrompt: 'Read {url}, I want to ask questions about it.',
/**
* The notice on a locale URL whose page has no source file in that locale,
* so the body below it is the English page. Never rendered in English — an
Expand Down Expand Up @@ -103,6 +110,11 @@ export type FumadocsTranslations = { [K in keyof Translations]: Translations[K]

type Locale = (typeof i18n.languages)[number];

/** The `{name}` placeholders a string carries, sorted, one entry per occurrence. */
function placeholders(value: string): string[] {
return (value.match(/\{[A-Za-z]+\}/g) ?? []).sort();
}

/**
* A locale table, held to EXACTLY the English key set.
*
Expand All @@ -111,8 +123,26 @@ type Locale = (typeof i18n.languages)[number];
* parameter type closes that: any key English does not have must be `never`,
* which a JSON string value is not, so a stale or misspelled key is a type
* error instead of dead weight nobody reads.
*
* Placeholders are held to English too, which a type cannot do for a JSON
* string. A translation that drops `{url}` from `pageActionsOpenInLLMPrompt`
* would open the assistant on a prompt naming no page, so a mismatch throws
* while this module loads, which fails `next build` on the first page that
* renders.
*/
function table<T extends UiText>(text: T & Record<Exclude<keyof T, keyof UiText>, never>): UiText {
function table<T extends UiText>(
locale: string,
text: T & Record<Exclude<keyof T, keyof UiText>, never>,
): UiText {
for (const key of Object.keys(en) as (keyof UiText)[]) {
const want = placeholders(en[key]).join(' ');
const got = placeholders(text[key]).join(' ');
if (got !== want) {
throw new Error(
`lib/ui-text/${locale}.json: "${key}" has placeholders [${got}], English has [${want}]`,
);
}
}
return text;
}

Expand All @@ -123,13 +153,13 @@ function table<T extends UiText>(text: T & Record<Exclude<keyof T, keyof UiText>
*/
const TEXT: Record<Locale, UiText> = {
en,
'zh-Hans': table(zhHans),
'zh-Hant': table(zhHant),
ja: table(ja),
de: table(de),
es: table(es),
fr: table(fr),
ko: table(ko),
'zh-Hans': table('zh-Hans', zhHans),
'zh-Hant': table('zh-Hant', zhHant),
ja: table('ja', ja),
de: table('de', de),
es: table('es', es),
fr: table('fr', fr),
ko: table('ko', ko),
};

function isLocale(lang: string): lang is Locale {
Expand Down
1 change: 1 addition & 0 deletions apps/docs/lib/ui-text/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"openInGitHub": "In GitHub öffnen",
"openInChatGPT": "In ChatGPT öffnen",
"openInClaude": "In Claude öffnen",
"pageActionsOpenInLLMPrompt": "Lies {url}, ich möchte Fragen dazu stellen.",
"notTranslated": "Diese Seite ist noch nicht übersetzt; angezeigt wird die englische Fassung.",
"legalNav": "Rechtliches",
"privacy": "Datenschutz",
Expand Down
1 change: 1 addition & 0 deletions apps/docs/lib/ui-text/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"openInGitHub": "Abrir en GitHub",
"openInChatGPT": "Abrir en ChatGPT",
"openInClaude": "Abrir en Claude",
"pageActionsOpenInLLMPrompt": "Lee {url}, quiero hacer preguntas sobre su contenido.",
"notTranslated": "Esta página aún no está traducida; se muestra en inglés.",
"legalNav": "Información legal",
"privacy": "Privacidad",
Expand Down
1 change: 1 addition & 0 deletions apps/docs/lib/ui-text/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"openInGitHub": "Ouvrir dans GitHub",
"openInChatGPT": "Ouvrir dans ChatGPT",
"openInClaude": "Ouvrir dans Claude",
"pageActionsOpenInLLMPrompt": "Lis {url}, je voudrais poser des questions à son sujet.",
"notTranslated": "Cette page n'est pas encore traduite. Elle s'affiche en anglais.",
"legalNav": "Mentions légales",
"privacy": "Confidentialité",
Expand Down
1 change: 1 addition & 0 deletions apps/docs/lib/ui-text/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"openInGitHub": "GitHub で開く",
"openInChatGPT": "ChatGPT で開く",
"openInClaude": "Claude で開く",
"pageActionsOpenInLLMPrompt": "{url} を読んでください。この内容について質問したいです。",
"notTranslated": "このページはまだ翻訳されていません。英語版を表示しています。",
"legalNav": "法的情報",
"privacy": "プライバシー",
Expand Down
1 change: 1 addition & 0 deletions apps/docs/lib/ui-text/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"openInGitHub": "GitHub에서 열기",
"openInChatGPT": "ChatGPT에서 열기",
"openInClaude": "Claude에서 열기",
"pageActionsOpenInLLMPrompt": "{url} 문서를 읽어 주세요. 이 내용에 대해 질문하고 싶습니다.",
"notTranslated": "이 페이지는 아직 번역되지 않았습니다. 영어 원문을 표시합니다.",
"legalNav": "법적 고지",
"privacy": "개인정보 보호",
Expand Down
1 change: 1 addition & 0 deletions apps/docs/lib/ui-text/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"openInGitHub": "在 GitHub 中打开",
"openInChatGPT": "在 ChatGPT 中打开",
"openInClaude": "在 Claude 中打开",
"pageActionsOpenInLLMPrompt": "请阅读 {url},我想就其中的内容提问。",
"notTranslated": "本页尚未翻译,以下显示英文原文。",
"legalNav": "法律信息",
"privacy": "隐私",
Expand Down
1 change: 1 addition & 0 deletions apps/docs/lib/ui-text/zh-Hant.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"openInGitHub": "在 GitHub 中開啟",
"openInChatGPT": "在 ChatGPT 中開啟",
"openInClaude": "在 Claude 中開啟",
"pageActionsOpenInLLMPrompt": "請閱讀 {url},我想就其中的內容提問。",
"notTranslated": "本頁尚未翻譯,以下顯示英文原文。",
"legalNav": "法律資訊",
"privacy": "隱私",
Expand Down
Loading
Loading