blume 1.6.2 → 1.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/dist/cli/index.js +307 -68
- package/dist/cli/index.js.map +28 -23
- package/dist/types/ai/component-markdown.d.ts +14 -0
- package/docs/01-quickstart.mdx +2 -2
- package/docs/02-deployment.mdx +5 -5
- package/docs/{07-faq.mdx → 08-faq.mdx} +7 -7
- package/docs/advanced/blog.mdx +3 -3
- package/docs/advanced/changelog.mdx +2 -2
- package/docs/advanced/custom-pages.mdx +4 -4
- package/docs/advanced/meta.ts +1 -1
- package/docs/configuration/ask-ai.mdx +179 -0
- package/docs/configuration/index.mdx +8 -7
- package/docs/configuration/meta.ts +1 -2
- package/docs/configuration/search.mdx +1 -1
- package/docs/configuration/theming.mdx +1 -1
- package/docs/content/components.mdx +1 -1
- package/docs/content/i18n.mdx +7 -1
- package/docs/content/index.mdx +1 -1
- package/docs/content/navigation.mdx +2 -2
- package/docs/content/syntax.mdx +1 -1
- package/docs/discoverability/agent-discovery.mdx +196 -0
- package/docs/discoverability/index.mdx +48 -0
- package/docs/discoverability/json-api.mdx +58 -0
- package/docs/discoverability/llms-txt.mdx +68 -0
- package/docs/discoverability/markdown.mdx +76 -0
- package/docs/discoverability/mcp.mdx +64 -0
- package/docs/discoverability/meta.ts +18 -0
- package/docs/discoverability/metadata.mdx +82 -0
- package/docs/discoverability/open-graph.mdx +113 -0
- package/docs/discoverability/rss.mdx +24 -0
- package/docs/discoverability/sitemap-and-robots.mdx +95 -0
- package/docs/discoverability/structured-data.mdx +51 -0
- package/docs/index.mdx +5 -5
- package/docs/reference/eval.mdx +1 -1
- package/docs/reference/meta.ts +1 -1
- package/docs/reference/translate.mdx +1 -0
- package/package.json +18 -18
- package/src/ai/component-markdown.ts +17 -2
- package/src/ai/llms.ts +3 -10
- package/src/ai/markdown.ts +3 -10
- package/src/ai/openapi-components.ts +123 -0
- package/src/ai/serializers.ts +24 -0
- package/src/astro/templates.ts +42 -12
- package/src/audit/checks/links.ts +1 -8
- package/src/audit/checks/llms.ts +5 -4
- package/src/audit/redirects.ts +4 -3
- package/src/audit/run.ts +6 -8
- package/src/audit/url.ts +33 -0
- package/src/cli/commands/validate.ts +1 -0
- package/src/components/content/Component.astro +65 -68
- package/src/components/content/Tabs.astro +24 -9
- package/src/components/content/example-pane.ts +6 -0
- package/src/components/layout/LocaleLinks.astro +42 -0
- package/src/components/layout/PageLayout.astro +5 -3
- package/src/components/layout/ReferenceLayout.astro +5 -0
- package/src/components/layout/RootLayout.astro +110 -39
- package/src/components/layout/search-locale.ts +13 -0
- package/src/components/openapi/ApiOverview.astro +7 -39
- package/src/components/openapi/ApiTagOperations.astro +2 -1
- package/src/components/openapi/AsyncApiOperation.astro +3 -2
- package/src/components/openapi/GraphqlOperation.astro +3 -2
- package/src/components/openapi/Operation.astro +3 -2
- package/src/core/i18n.ts +13 -2
- package/src/core/links.ts +33 -1
- package/src/core/locale-links.ts +163 -0
- package/src/core/sources/normalize.ts +57 -7
- package/src/markdown/package-commands.ts +27 -3
- package/src/openapi/graphql.ts +29 -0
- package/src/openapi/model.ts +69 -0
- package/src/openapi/render-mdx.ts +3 -2
- package/src/openapi/signature.ts +18 -0
- package/src/search/documents.ts +4 -9
- package/src/theme/code-block-padding.ts +0 -8
- package/src/theme/entry.ts +33 -27
- package/src/translate/anchors.ts +91 -0
- package/src/translate/validate.ts +8 -3
- package/docs/configuration/ai.mdx +0 -613
- package/docs/configuration/seo.mdx +0 -364
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { scanBody } from "../core/sources/normalize.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Keep heading anchors identical across languages. A translated heading slugs
|
|
5
|
+
* to a different id (`## Ordering` → `#ordering`, `## Reihenfolge` →
|
|
6
|
+
* `#reihenfolge`), so a `/guide#ordering` link — which the runtime keeps
|
|
7
|
+
* inside the reader's locale — would land at the top of the translated page.
|
|
8
|
+
* After a translation validates, every heading whose rendered id would differ
|
|
9
|
+
* from its source counterpart gets the source id pinned as a trailing
|
|
10
|
+
* `[#id]` marker, so the anchor index (and every `#fragment` link) agrees in
|
|
11
|
+
* every locale. Headings the translation already pins are left alone: the
|
|
12
|
+
* agent is told to copy markers verbatim, and a hand-authored pin is the
|
|
13
|
+
* translator's choice.
|
|
14
|
+
*
|
|
15
|
+
* Headings pair positionally, so the translation must mirror the source's
|
|
16
|
+
* heading structure (the prompt demands exactly that); when it doesn't, no
|
|
17
|
+
* pins are added and the text is returned unchanged with a reason.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export interface PinResult {
|
|
21
|
+
/** Number of headings that received a pin. */
|
|
22
|
+
pinned: number;
|
|
23
|
+
/** Why no pins were added, when the structures don't line up. */
|
|
24
|
+
skipped?: string;
|
|
25
|
+
text: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** An ATX heading's optional closing hash run, which a marker must precede. */
|
|
29
|
+
const ATX_CLOSE = /\s+#+$/u;
|
|
30
|
+
|
|
31
|
+
const structureOf = (headings: readonly { depth: number }[]): string =>
|
|
32
|
+
headings.map((heading) => heading.depth).join(",");
|
|
33
|
+
|
|
34
|
+
/** Append `[#id]` to a heading line, ahead of any closing `##` and line end. */
|
|
35
|
+
const pinLine = (line: string, id: string): string => {
|
|
36
|
+
const eol = line.endsWith("\r") ? "\r" : "";
|
|
37
|
+
const body = line.slice(0, line.length - eol.length).trimEnd();
|
|
38
|
+
const close = body.match(ATX_CLOSE)?.[0] ?? "";
|
|
39
|
+
const head = body.slice(0, body.length - close.length);
|
|
40
|
+
return `${head} [#${id}]${close}${eol}`;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const pinHeadingAnchors = (
|
|
44
|
+
sourceText: string,
|
|
45
|
+
translatedText: string
|
|
46
|
+
): PinResult => {
|
|
47
|
+
const source = scanBody(sourceText);
|
|
48
|
+
const translated = scanBody(translatedText);
|
|
49
|
+
if (structureOf(source.headings) !== structureOf(translated.headings)) {
|
|
50
|
+
return {
|
|
51
|
+
pinned: 0,
|
|
52
|
+
skipped: `heading structure differs (source has ${source.headings.length} headings, translation has ${translated.headings.length})`,
|
|
53
|
+
text: translatedText,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const lines = translatedText.split("\n");
|
|
58
|
+
let pinned = 0;
|
|
59
|
+
for (const [index, heading] of translated.headings.entries()) {
|
|
60
|
+
const site = translated.sites[index];
|
|
61
|
+
const sourceId = source.headings[index]?.slug;
|
|
62
|
+
if (
|
|
63
|
+
!site ||
|
|
64
|
+
site.pinned ||
|
|
65
|
+
sourceId === undefined ||
|
|
66
|
+
heading.slug === sourceId
|
|
67
|
+
) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
lines[site.line - 1] = pinLine(lines[site.line - 1] ?? "", sourceId);
|
|
71
|
+
pinned += 1;
|
|
72
|
+
}
|
|
73
|
+
if (pinned === 0) {
|
|
74
|
+
return { pinned, text: translatedText };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// The pins must reproduce the source ids exactly once rendered; anything
|
|
78
|
+
// else (a heading the line-based rewrite couldn't reach) keeps the text as
|
|
79
|
+
// the agent wrote it rather than shipping a half-pinned page.
|
|
80
|
+
const text = lines.join("\n");
|
|
81
|
+
const rendered = scanBody(text).headings.map((heading) => heading.slug);
|
|
82
|
+
const expected = source.headings.map((heading) => heading.slug);
|
|
83
|
+
if (rendered.join("\n") !== expected.join("\n")) {
|
|
84
|
+
return {
|
|
85
|
+
pinned: 0,
|
|
86
|
+
skipped: "pinned anchors did not reproduce the source heading ids",
|
|
87
|
+
text: translatedText,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return { pinned, text };
|
|
91
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import matter from "../core/frontmatter.ts";
|
|
2
|
+
import { pinHeadingAnchors } from "./anchors.ts";
|
|
2
3
|
import { TRANSLATABLE_KEY_PATHS } from "./prompts.ts";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -123,13 +124,13 @@ export const validateTranslation = (
|
|
|
123
124
|
return { ok: false, reason: "frontmatter does not parse as YAML" };
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
const
|
|
127
|
-
if (
|
|
127
|
+
const rawBody = ensureTrailingNewline(parsed.content.replace(/^\r?\n/u, ""));
|
|
128
|
+
if (rawBody.trim() === "") {
|
|
128
129
|
return { ok: false, reason: "translation has an empty body" };
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
const sourceFences = countFenceLines(source.content);
|
|
132
|
-
const candidateFences = countFenceLines(
|
|
133
|
+
const candidateFences = countFenceLines(rawBody);
|
|
133
134
|
if (sourceFences !== candidateFences) {
|
|
134
135
|
return {
|
|
135
136
|
ok: false,
|
|
@@ -137,6 +138,10 @@ export const validateTranslation = (
|
|
|
137
138
|
};
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
// Anchors stay identical across languages: each translated heading is
|
|
142
|
+
// pinned to its source heading's id (see `anchors.ts`).
|
|
143
|
+
const body = pinHeadingAnchors(source.content, rawBody).text;
|
|
144
|
+
|
|
140
145
|
if (!sourceHasFrontmatter) {
|
|
141
146
|
// A frontmatter-less source writes the body alone; any frontmatter the
|
|
142
147
|
// agent invented is dropped with it.
|