blume 1.3.0 → 1.4.0
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 +59 -0
- package/dist/cli/index.js +3290 -254
- package/dist/cli/index.js.map +79 -67
- package/dist/types/core/base-path.d.ts +5 -0
- package/dist/types/core/config-input.d.ts +82 -6
- package/dist/types/core/i18n-ui.d.ts +2 -0
- package/dist/types/core/schema.d.ts +19 -2
- package/dist/types/core/sources/types.d.ts +5 -0
- package/dist/types/core/types.d.ts +4 -3
- package/docs/02-deployment.mdx +1 -1
- package/docs/configuration/ai.mdx +16 -2
- package/docs/configuration/index.mdx +26 -0
- package/docs/configuration/search.mdx +1 -3
- package/docs/content/i18n.mdx +13 -1
- package/docs/content/navigation.mdx +11 -0
- package/docs/reference/cli.mdx +4 -0
- package/docs/reference/frontmatter.mdx +33 -0
- package/docs/reference/meta.ts +1 -1
- package/docs/reference/translate.mdx +80 -0
- package/package.json +1 -1
- package/src/ai/agent-readability.ts +7 -4
- package/src/ai/ask-context.ts +3 -6
- package/src/ai/link-headers.ts +4 -3
- package/src/ai/llms.ts +4 -2
- package/src/ai/markdown.ts +34 -1
- package/src/ai/mcp/data.ts +10 -4
- package/src/ai/mcp/server.ts +74 -3
- package/src/ai/mcp/tools.ts +2 -2
- package/src/astro/generate.ts +2 -2
- package/src/astro/integration.ts +6 -2
- package/src/astro/markdown-negotiation.ts +5 -0
- package/src/astro/templates.ts +74 -21
- package/src/audit/url.ts +5 -10
- package/src/cli/commands/build.ts +145 -34
- package/src/cli/commands/translate.ts +300 -0
- package/src/cli/index.ts +2 -0
- package/src/components/Icon.astro +2 -7
- package/src/components/content/Step.astro +3 -8
- package/src/components/content/Tab.astro +20 -1
- package/src/components/layout/LanguageSwitcher.astro +2 -1
- package/src/components/layout/Logo.astro +4 -4
- package/src/components/layout/PageActions.astro +12 -7
- package/src/components/layout/Search.astro +15 -20
- package/src/components/layout/search/orama.ts +3 -1
- package/src/core/base-path.ts +9 -0
- package/src/core/config-input.ts +84 -6
- package/src/core/graph.ts +46 -2
- package/src/core/i18n-ui.ts +2 -0
- package/src/core/i18n.ts +31 -0
- package/src/core/nav-diagnostics.ts +13 -34
- package/src/core/project-graph.ts +13 -2
- package/src/core/schema.ts +174 -74
- package/src/core/sources/normalize.ts +25 -12
- package/src/core/sources/types.ts +5 -0
- package/src/core/types.ts +4 -3
- package/src/core/ui-packs/ar.ts +42 -1
- package/src/core/ui-packs/bg.ts +42 -1
- package/src/core/ui-packs/bn.ts +42 -1
- package/src/core/ui-packs/ca.ts +44 -1
- package/src/core/ui-packs/cs.ts +42 -1
- package/src/core/ui-packs/da.ts +42 -1
- package/src/core/ui-packs/de.ts +42 -1
- package/src/core/ui-packs/el.ts +44 -1
- package/src/core/ui-packs/es.ts +44 -1
- package/src/core/ui-packs/fa.ts +42 -1
- package/src/core/ui-packs/fi.ts +42 -1
- package/src/core/ui-packs/fr.ts +44 -1
- package/src/core/ui-packs/he.ts +42 -1
- package/src/core/ui-packs/hi.ts +42 -1
- package/src/core/ui-packs/hr.ts +42 -1
- package/src/core/ui-packs/hu.ts +42 -1
- package/src/core/ui-packs/id.ts +42 -1
- package/src/core/ui-packs/it.ts +44 -1
- package/src/core/ui-packs/ja.ts +44 -1
- package/src/core/ui-packs/ko.ts +44 -1
- package/src/core/ui-packs/nl.ts +42 -1
- package/src/core/ui-packs/no.ts +42 -1
- package/src/core/ui-packs/pl.ts +42 -1
- package/src/core/ui-packs/pt-br.ts +44 -1
- package/src/core/ui-packs/pt.ts +44 -1
- package/src/core/ui-packs/ro.ts +42 -1
- package/src/core/ui-packs/ru.ts +42 -1
- package/src/core/ui-packs/sk.ts +42 -1
- package/src/core/ui-packs/sr.ts +42 -1
- package/src/core/ui-packs/sv.ts +42 -1
- package/src/core/ui-packs/th.ts +44 -1
- package/src/core/ui-packs/tr.ts +42 -1
- package/src/core/ui-packs/uk.ts +42 -1
- package/src/core/ui-packs/vi.ts +44 -1
- package/src/core/ui-packs/zh-tw.ts +44 -1
- package/src/core/ui-packs/zh.ts +44 -1
- package/src/deploy/adapter-output.ts +44 -5
- package/src/deploy/cloudflare-negotiation.ts +527 -0
- package/src/deploy/redirects.ts +13 -0
- package/src/deploy/vercel-negotiation.ts +30 -13
- package/src/eval/agents.ts +1 -1
- package/src/search/documents.ts +11 -0
- package/src/search/facets.ts +33 -0
- package/src/search/orama-index.ts +48 -6
- package/src/search/popular-icon.ts +33 -0
- package/src/theme/fonts.ts +3 -1
- package/src/theme/icon-kind.ts +20 -0
- package/src/translate/agents.ts +51 -0
- package/src/translate/ledger.ts +148 -0
- package/src/translate/meta.ts +149 -0
- package/src/translate/prompts.ts +95 -0
- package/src/translate/report.ts +360 -0
- package/src/translate/run.ts +376 -0
- package/src/translate/validate.ts +171 -0
- package/src/translate/work-list.ts +0 -0
package/src/core/schema.ts
CHANGED
|
@@ -141,6 +141,37 @@ export const pageMetaSchema = pageMetaBaseSchema;
|
|
|
141
141
|
export type PageMeta = z.infer<typeof pageMetaBaseSchema>;
|
|
142
142
|
export type PageMetaInput = z.input<typeof pageMetaBaseSchema>;
|
|
143
143
|
|
|
144
|
+
/**
|
|
145
|
+
* A map of custom frontmatter keys to user-supplied validation schemas,
|
|
146
|
+
* consumed through the Standard Schema `~standard` contract — never Zod's own
|
|
147
|
+
* API — so the consumer's zod (any version), Valibot, or ArkType all work
|
|
148
|
+
* (see `standard-schema.ts`). Shared by the site-wide `frontmatter.extend`
|
|
149
|
+
* and the per-type `content.types.<type>.frontmatter` maps. Built-in
|
|
150
|
+
* frontmatter fields can't be redeclared — they're load-bearing (routing,
|
|
151
|
+
* sidebar, SEO), and shadowing one would silently change its semantics.
|
|
152
|
+
*/
|
|
153
|
+
const customKeySchemaRecord = (where: string) =>
|
|
154
|
+
z
|
|
155
|
+
.record(
|
|
156
|
+
z.string(),
|
|
157
|
+
z.custom<StandardSchema>(isStandardSchema, {
|
|
158
|
+
message:
|
|
159
|
+
"Expected a Standard Schema (e.g. a Zod schema — any Zod version works).",
|
|
160
|
+
})
|
|
161
|
+
)
|
|
162
|
+
.default({})
|
|
163
|
+
.superRefine((value, ctx) => {
|
|
164
|
+
for (const key of Object.keys(value)) {
|
|
165
|
+
if (Object.hasOwn(pageMetaBaseSchema.shape, key)) {
|
|
166
|
+
ctx.addIssue({
|
|
167
|
+
code: z.ZodIssueCode.custom,
|
|
168
|
+
message: `"${key}" is a built-in frontmatter field and cannot be redeclared via ${where}.`,
|
|
169
|
+
path: [key],
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
144
175
|
// ---------------------------------------------------------------------------
|
|
145
176
|
// Folder meta (meta.ts)
|
|
146
177
|
// ---------------------------------------------------------------------------
|
|
@@ -345,6 +376,30 @@ const contentSourceSchema = z.discriminatedUnion("type", [
|
|
|
345
376
|
/** A resolved content-source config entry (post-defaults). */
|
|
346
377
|
export type ContentSourceConfig = z.infer<typeof contentSourceSchema>;
|
|
347
378
|
|
|
379
|
+
/**
|
|
380
|
+
* Per-type content definition. An object (rather than a bare frontmatter map)
|
|
381
|
+
* so type-scoped concerns added later — search facets, templates — have a
|
|
382
|
+
* home without a breaking config change.
|
|
383
|
+
*/
|
|
384
|
+
const contentTypeConfigSchema = z.strictObject({
|
|
385
|
+
/**
|
|
386
|
+
* Custom frontmatter keys whose values become filterable facets for pages
|
|
387
|
+
* of this type — surfaced in search documents and filterable through the
|
|
388
|
+
* MCP tools' `filters` input. Each name must be a custom key declared for
|
|
389
|
+
* the type (in its `frontmatter` map or the site-wide `frontmatter.extend`),
|
|
390
|
+
* checked at the config level where both maps are visible. Only string
|
|
391
|
+
* (or number/boolean, stringified) values ever facet.
|
|
392
|
+
*/
|
|
393
|
+
facets: z.array(z.string()).default([]),
|
|
394
|
+
/**
|
|
395
|
+
* Custom frontmatter keys for pages of this type, layered on top of the
|
|
396
|
+
* site-wide `frontmatter.extend`. Every declared key is validated on every
|
|
397
|
+
* page of the type — absent ones included — so a required schema enforces
|
|
398
|
+
* the key type-wide while leaving other types untouched.
|
|
399
|
+
*/
|
|
400
|
+
frontmatter: customKeySchemaRecord("content.types"),
|
|
401
|
+
});
|
|
402
|
+
|
|
348
403
|
const contentConfigSchema = z.strictObject({
|
|
349
404
|
defaultType: z.string().default("doc"),
|
|
350
405
|
exclude: z.array(z.string()).default(["**/_*", "**/.*"]),
|
|
@@ -357,8 +412,32 @@ const contentConfigSchema = z.strictObject({
|
|
|
357
412
|
* existing projects are unchanged.
|
|
358
413
|
*/
|
|
359
414
|
sources: z.array(contentSourceSchema).optional(),
|
|
415
|
+
/**
|
|
416
|
+
* Per-type content definitions, keyed by the frontmatter `type` they apply
|
|
417
|
+
* to (including `defaultType`, for pages that set none).
|
|
418
|
+
*/
|
|
419
|
+
types: z.record(z.string(), contentTypeConfigSchema).default({}),
|
|
360
420
|
});
|
|
361
421
|
|
|
422
|
+
/**
|
|
423
|
+
* A header label that may localize: a plain string, or a map of locale code to
|
|
424
|
+
* label (`{ en: "Docs", ja: "ドキュメント" }`). Resolved when each locale's
|
|
425
|
+
* navigation is built — the active locale's entry wins, then the default
|
|
426
|
+
* locale's, then the map's first entry — so a single-locale site can keep
|
|
427
|
+
* plain strings and an i18n site can translate its header without forking the
|
|
428
|
+
* config.
|
|
429
|
+
*/
|
|
430
|
+
const localizableLabelSchema = z.union([
|
|
431
|
+
z.string(),
|
|
432
|
+
z
|
|
433
|
+
.record(z.string(), z.string())
|
|
434
|
+
.refine((value) => Object.keys(value).length > 0, {
|
|
435
|
+
message: "Provide at least one locale's label.",
|
|
436
|
+
}),
|
|
437
|
+
]);
|
|
438
|
+
|
|
439
|
+
export type LocalizableLabel = z.infer<typeof localizableLabelSchema>;
|
|
440
|
+
|
|
362
441
|
const navTabSchema = z.strictObject({
|
|
363
442
|
// Rejected empty rather than accepted: an empty `href` would render a link to
|
|
364
443
|
// nowhere, and it can't mean "resolve it for me" either — that's what
|
|
@@ -370,13 +449,13 @@ const navTabSchema = z.strictObject({
|
|
|
370
449
|
z.strictObject({
|
|
371
450
|
description: z.string().optional(),
|
|
372
451
|
icon: iconName.optional(),
|
|
373
|
-
label:
|
|
452
|
+
label: localizableLabelSchema,
|
|
374
453
|
path: z.string(),
|
|
375
454
|
tag: z.string().optional(),
|
|
376
455
|
})
|
|
377
456
|
)
|
|
378
457
|
.optional(),
|
|
379
|
-
label:
|
|
458
|
+
label: localizableLabelSchema,
|
|
380
459
|
path: z.string(),
|
|
381
460
|
});
|
|
382
461
|
|
|
@@ -857,6 +936,12 @@ const localeSchema = z.strictObject({
|
|
|
857
936
|
/** Text direction; drives `<html dir>` and a future RTL pass. */
|
|
858
937
|
dir: z.enum(["ltr", "rtl"]).default("ltr"),
|
|
859
938
|
label: z.string(),
|
|
939
|
+
/**
|
|
940
|
+
* Freeform style guidance for `blume translate`, e.g. "Brazilian
|
|
941
|
+
* Portuguese, informal você". Pins register and dialect from the first
|
|
942
|
+
* translation and wins over an existing translation's style on reruns.
|
|
943
|
+
*/
|
|
944
|
+
style: z.string().optional(),
|
|
860
945
|
});
|
|
861
946
|
|
|
862
947
|
/**
|
|
@@ -1390,26 +1475,7 @@ const asyncapiConfigSchema = z.strictObject({
|
|
|
1390
1475
|
* sidebar, SEO), and shadowing one would silently change its semantics.
|
|
1391
1476
|
*/
|
|
1392
1477
|
const frontmatterConfigSchema = z.strictObject({
|
|
1393
|
-
extend:
|
|
1394
|
-
.record(
|
|
1395
|
-
z.string(),
|
|
1396
|
-
z.custom<StandardSchema>(isStandardSchema, {
|
|
1397
|
-
message:
|
|
1398
|
-
"Expected a Standard Schema (e.g. a Zod schema — any Zod version works).",
|
|
1399
|
-
})
|
|
1400
|
-
)
|
|
1401
|
-
.default({})
|
|
1402
|
-
.superRefine((value, ctx) => {
|
|
1403
|
-
for (const key of Object.keys(value)) {
|
|
1404
|
-
if (Object.hasOwn(pageMetaBaseSchema.shape, key)) {
|
|
1405
|
-
ctx.addIssue({
|
|
1406
|
-
code: z.ZodIssueCode.custom,
|
|
1407
|
-
message: `"${key}" is a built-in frontmatter field and cannot be redeclared via frontmatter.extend.`,
|
|
1408
|
-
path: [key],
|
|
1409
|
-
});
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
}),
|
|
1478
|
+
extend: customKeySchemaRecord("frontmatter.extend"),
|
|
1413
1479
|
});
|
|
1414
1480
|
|
|
1415
1481
|
/** Full user-facing config schema. All fields optional with defaults. */
|
|
@@ -1444,58 +1510,92 @@ const tocConfigSchema = z
|
|
|
1444
1510
|
"toc.minHeadingLevel must be less than or equal to toc.maxHeadingLevel.",
|
|
1445
1511
|
});
|
|
1446
1512
|
|
|
1447
|
-
export const blumeConfigSchema = z
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1513
|
+
export const blumeConfigSchema = z
|
|
1514
|
+
.strictObject({
|
|
1515
|
+
ai: aiConfigSchema.prefault({}),
|
|
1516
|
+
analytics: analyticsConfigSchema.optional(),
|
|
1517
|
+
asyncapi: asyncapiConfigSchema.prefault({}),
|
|
1518
|
+
banner: bannerConfigSchema.optional(),
|
|
1519
|
+
/**
|
|
1520
|
+
* Site-wide mount point prepended to every generated route (e.g. `/docs`),
|
|
1521
|
+
* while staying invisible to the sidebar/nav tree. Distinct from a per-source
|
|
1522
|
+
* `prefix` (which creates a group) and from `deployment.base` (Astro's
|
|
1523
|
+
* host-subdirectory base); the two compose. Normalized to `""` or `/seg`.
|
|
1524
|
+
*/
|
|
1525
|
+
basePath: z
|
|
1526
|
+
.string()
|
|
1527
|
+
.optional()
|
|
1528
|
+
.transform((value) => normalizeBasePath(value)),
|
|
1529
|
+
content: contentConfigSchema.prefault({}),
|
|
1530
|
+
/**
|
|
1531
|
+
* Date presentation for the "last updated" stamp and the changelog timeline.
|
|
1532
|
+
* Pass-through `Intl.DateTimeFormat` options; defaults to `{ dateStyle: "long" }`.
|
|
1533
|
+
*/
|
|
1534
|
+
dateFormat: dateFormatConfigSchema.default({ dateStyle: "long" }),
|
|
1535
|
+
deployment: deploymentConfigSchema.prefault({}),
|
|
1536
|
+
description: z.string().optional(),
|
|
1537
|
+
/**
|
|
1538
|
+
* Where `<Component path>` resolves live previews and their source from.
|
|
1539
|
+
* A string is shorthand for `{ source }` — the directory (or glob, for
|
|
1540
|
+
* colocated registry layouts) under the project root that holds example
|
|
1541
|
+
* files. The object form adds `css`: a stylesheet injected into every
|
|
1542
|
+
* preview frame (design tokens, shadcn variables, `@theme` mappings).
|
|
1543
|
+
*/
|
|
1544
|
+
examples: examplesConfigSchema.prefault("examples"),
|
|
1545
|
+
export: exportConfigSchema.prefault(false),
|
|
1546
|
+
feedback: z.boolean().default(true),
|
|
1547
|
+
/** Opt-in custom frontmatter keys, validated by user-supplied schemas. */
|
|
1548
|
+
frontmatter: frontmatterConfigSchema.prefault({}),
|
|
1549
|
+
github: githubConfigSchema.optional(),
|
|
1550
|
+
i18n: i18nConfigSchema.optional(),
|
|
1551
|
+
image: imageConfigSchema.prefault({}),
|
|
1552
|
+
integrations: z.array(z.custom<AstroIntegration>()).default([]),
|
|
1553
|
+
lastModified: lastModifiedConfigSchema.default(false),
|
|
1554
|
+
logo: logoConfigSchema.optional(),
|
|
1555
|
+
markdown: markdownConfigSchema.prefault({}),
|
|
1556
|
+
navigation: navigationConfigSchema.prefault({}),
|
|
1557
|
+
openapi: openapiConfigSchema.prefault({}),
|
|
1558
|
+
react: reactConfigSchema.prefault({}),
|
|
1559
|
+
redirects: z.array(redirectSchema).default([]),
|
|
1560
|
+
search: searchConfigSchema.prefault({}),
|
|
1561
|
+
seo: seoConfigSchema.prefault({}),
|
|
1562
|
+
theme: themeConfigSchema.prefault({}),
|
|
1563
|
+
title: z.string().default("Documentation"),
|
|
1564
|
+
toc: tocConfigSchema,
|
|
1565
|
+
})
|
|
1566
|
+
.superRefine((config, ctx) => {
|
|
1567
|
+
// A custom key is declared site-wide (`frontmatter.extend`) or per-type
|
|
1568
|
+
// (`content.types`), never both — two schemas for one key would make
|
|
1569
|
+
// precedence on pages of that type ambiguous.
|
|
1570
|
+
for (const [typeName, typeConfig] of Object.entries(config.content.types)) {
|
|
1571
|
+
for (const key of Object.keys(typeConfig.frontmatter)) {
|
|
1572
|
+
if (Object.hasOwn(config.frontmatter.extend, key)) {
|
|
1573
|
+
ctx.addIssue({
|
|
1574
|
+
code: z.ZodIssueCode.custom,
|
|
1575
|
+
message: `"${key}" is already declared in frontmatter.extend and cannot be redeclared for type "${typeName}".`,
|
|
1576
|
+
path: ["content", "types", typeName, "frontmatter", key],
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
// A facet with no declared key could never carry a value — pages can't
|
|
1581
|
+
// even set the key — so it's a config mistake, caught here where both
|
|
1582
|
+
// the per-type map and the site-wide extend are visible.
|
|
1583
|
+
for (const [position, facet] of typeConfig.facets.entries()) {
|
|
1584
|
+
if (
|
|
1585
|
+
!(
|
|
1586
|
+
Object.hasOwn(typeConfig.frontmatter, facet) ||
|
|
1587
|
+
Object.hasOwn(config.frontmatter.extend, facet)
|
|
1588
|
+
)
|
|
1589
|
+
) {
|
|
1590
|
+
ctx.addIssue({
|
|
1591
|
+
code: z.ZodIssueCode.custom,
|
|
1592
|
+
message: `Facet "${facet}" for type "${typeName}" is not a declared custom frontmatter key — add it to the type's frontmatter map or frontmatter.extend.`,
|
|
1593
|
+
path: ["content", "types", typeName, "facets", position],
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
});
|
|
1499
1599
|
|
|
1500
1600
|
/** Resolved config: every field present after defaults are applied. */
|
|
1501
1601
|
export type ResolvedConfig = z.infer<typeof blumeConfigSchema>;
|
|
@@ -516,13 +516,13 @@ const segmentKey = (
|
|
|
516
516
|
};
|
|
517
517
|
|
|
518
518
|
/**
|
|
519
|
-
* Validate the opt-in custom frontmatter keys (`frontmatter.extend`
|
|
520
|
-
* the Standard Schema contract —
|
|
521
|
-
* Valibot, or ArkType, never Blume's
|
|
522
|
-
* Every declared key is checked,
|
|
523
|
-
* enforces its key on every page
|
|
524
|
-
*
|
|
525
|
-
* business awaiting I/O.
|
|
519
|
+
* Validate the opt-in custom frontmatter keys (`frontmatter.extend` and
|
|
520
|
+
* `content.types.<type>.frontmatter`) through the Standard Schema contract —
|
|
521
|
+
* the consumer's own Zod (any version), Valibot, or ArkType, never Blume's
|
|
522
|
+
* bundled zod (see `standard-schema.ts`). Every declared key is checked,
|
|
523
|
+
* absent ones included, so a required schema enforces its key on every page
|
|
524
|
+
* it applies to. Async schemas are rejected with a diagnostic: this funnel is
|
|
525
|
+
* synchronous, and frontmatter validation has no business awaiting I/O.
|
|
526
526
|
*/
|
|
527
527
|
const validateCustomKeys = (
|
|
528
528
|
data: Record<string, unknown>,
|
|
@@ -534,7 +534,7 @@ const validateCustomKeys = (
|
|
|
534
534
|
const outcome = schema["~standard"].validate(data[key]);
|
|
535
535
|
if (outcome instanceof Promise) {
|
|
536
536
|
issues.push({
|
|
537
|
-
message: "Async schemas are not supported
|
|
537
|
+
message: "Async schemas are not supported for custom frontmatter keys.",
|
|
538
538
|
path: [key],
|
|
539
539
|
});
|
|
540
540
|
continue;
|
|
@@ -562,9 +562,11 @@ const validateCustomKeys = (
|
|
|
562
562
|
|
|
563
563
|
/**
|
|
564
564
|
* Parse an entry's frontmatter: built-in keys through the strict page schema,
|
|
565
|
-
* custom keys (`frontmatter.extend`
|
|
566
|
-
*
|
|
567
|
-
*
|
|
565
|
+
* custom keys (`frontmatter.extend` plus the page type's
|
|
566
|
+
* `content.types.<type>.frontmatter`) through their user-supplied schemas.
|
|
567
|
+
* The custom keys are carved out before the strict parse, so the page schema
|
|
568
|
+
* stays strict for everything else and unknown-key typo catching is unchanged
|
|
569
|
+
* — a key declared only for some other type stays unknown here.
|
|
568
570
|
* Returns diagnostics instead of meta when either side rejects.
|
|
569
571
|
*/
|
|
570
572
|
const parseEntryMeta = (
|
|
@@ -573,7 +575,18 @@ const parseEntryMeta = (
|
|
|
573
575
|
):
|
|
574
576
|
| { meta: PageMeta; custom?: Record<string, unknown>; diagnostics?: never }
|
|
575
577
|
| { meta?: never; diagnostics: Diagnostic[] } => {
|
|
576
|
-
|
|
578
|
+
// Resolved the same way `contentType` is after parsing (`meta.type` falling
|
|
579
|
+
// back to `defaultType`); a non-string `type` fails the strict parse below,
|
|
580
|
+
// so which per-type map was merged for that entry never matters.
|
|
581
|
+
const entryType =
|
|
582
|
+
typeof entry.data.type === "string" ? entry.data.type : ctx.defaultType;
|
|
583
|
+
const typeExtend = ctx.typeFrontmatter?.[entryType];
|
|
584
|
+
// Config validation rejects a key declared both site-wide and per-type, so
|
|
585
|
+
// this merge never has to pick a winner.
|
|
586
|
+
const extend =
|
|
587
|
+
ctx.frontmatterExtend || typeExtend
|
|
588
|
+
? { ...ctx.frontmatterExtend, ...typeExtend }
|
|
589
|
+
: undefined;
|
|
577
590
|
const known = extend
|
|
578
591
|
? Object.fromEntries(
|
|
579
592
|
Object.entries(entry.data).filter(
|
|
@@ -112,4 +112,9 @@ export interface NormalizeContext {
|
|
|
112
112
|
/** Opt-in custom frontmatter keys (`frontmatter.extend`), schema per key. */
|
|
113
113
|
frontmatterExtend?: FrontmatterExtend;
|
|
114
114
|
i18n?: ResolvedI18nConfig;
|
|
115
|
+
/**
|
|
116
|
+
* Per-type custom frontmatter keys (`content.types.<type>.frontmatter`),
|
|
117
|
+
* applied to a page only when its resolved `type` matches.
|
|
118
|
+
*/
|
|
119
|
+
typeFrontmatter?: Record<string, FrontmatterExtend>;
|
|
115
120
|
}
|
package/src/core/types.ts
CHANGED
|
@@ -146,9 +146,10 @@ export interface PageRecord {
|
|
|
146
146
|
contentType: string;
|
|
147
147
|
meta: PageMeta;
|
|
148
148
|
/**
|
|
149
|
-
* Custom frontmatter values declared via `frontmatter.extend
|
|
150
|
-
* the user-supplied
|
|
151
|
-
*
|
|
149
|
+
* Custom frontmatter values declared via `frontmatter.extend` or the page
|
|
150
|
+
* type's `content.types.<type>.frontmatter`, validated by the user-supplied
|
|
151
|
+
* schemas (schema output, so transforms apply). Present only when the
|
|
152
|
+
* project opts in and the page carries at least one value.
|
|
152
153
|
*/
|
|
153
154
|
custom?: Record<string, unknown>;
|
|
154
155
|
headings: Heading[];
|
package/src/core/ui-packs/ar.ts
CHANGED
|
@@ -9,25 +9,40 @@ export const ar: UIStringsOverride = {
|
|
|
9
9
|
connectMcp: "الاتصال بـ MCP",
|
|
10
10
|
copied: "تم النسخ!",
|
|
11
11
|
copyClaudeCode: "نسخ أمر Claude Code",
|
|
12
|
+
copyCode: "نسخ الكود",
|
|
13
|
+
copyCodex: "نسخ أمر Codex",
|
|
12
14
|
copyMarkdown: "نسخ بصيغة Markdown",
|
|
13
15
|
copyServerUrl: "نسخ عنوان URL للخادم",
|
|
14
16
|
edit: "التعديل على GitHub",
|
|
17
|
+
export: "تصدير",
|
|
18
|
+
exportEpub: "التصدير إلى EPUB",
|
|
19
|
+
exportPdf: "التصدير إلى PDF",
|
|
20
|
+
generating: "جارٍ الإنشاء…",
|
|
21
|
+
openIn: "فتح في {name}",
|
|
15
22
|
openInChat: "فتح في المحادثة",
|
|
16
23
|
scrollToTop: "العودة إلى الأعلى",
|
|
17
24
|
},
|
|
18
25
|
ask: {
|
|
26
|
+
ai: "الذكاء الاصطناعي",
|
|
27
|
+
clear: "مسح المحادثة",
|
|
28
|
+
close: "إغلاق",
|
|
29
|
+
copy: "نسخ المحادثة",
|
|
19
30
|
empty: "اطرح سؤالاً حول الوثائق.",
|
|
20
31
|
error: "عذراً، حدث خطأ ما.",
|
|
21
32
|
label: "اطرح سؤالاً",
|
|
22
33
|
placeholder: "اطرح سؤالاً…",
|
|
23
34
|
send: "إرسال",
|
|
35
|
+
tip: "نصيحة: يمكنك فتح المحادثة وإغلاقها باستخدام",
|
|
24
36
|
title: "اسأل الذكاء الاصطناعي",
|
|
37
|
+
you: "أنت",
|
|
25
38
|
},
|
|
26
39
|
banner: { dismiss: "إغلاق الإعلان" },
|
|
27
40
|
changelog: {
|
|
28
41
|
description: "تحديثات المنتج وملاحظات الإصدارات.",
|
|
42
|
+
showReleases: "عرض إصدارات {version}",
|
|
29
43
|
title: "سجل التغييرات",
|
|
30
44
|
},
|
|
45
|
+
content: { diagramError: "تعذّر عرض هذا المخطط." },
|
|
31
46
|
feedback: {
|
|
32
47
|
no: "لا",
|
|
33
48
|
question: "هل كانت هذه الصفحة مفيدة؟",
|
|
@@ -35,7 +50,24 @@ export const ar: UIStringsOverride = {
|
|
|
35
50
|
yes: "نعم",
|
|
36
51
|
},
|
|
37
52
|
languageSwitcher: { label: "اللغة", untranslated: "غير مترجم" },
|
|
38
|
-
nav: {
|
|
53
|
+
nav: {
|
|
54
|
+
back: "رجوع",
|
|
55
|
+
breadcrumb: "مسار التنقل",
|
|
56
|
+
closeNavigation: "إغلاق التنقل",
|
|
57
|
+
deprecated: "مهمل",
|
|
58
|
+
featured: "مميز",
|
|
59
|
+
githubRepository: "مستودع GitHub",
|
|
60
|
+
navigation: "التنقل",
|
|
61
|
+
primary: "رئيسي",
|
|
62
|
+
sections: "الأقسام",
|
|
63
|
+
toggleNavigation: "تبديل التنقل",
|
|
64
|
+
toggleTheme: "تبديل سمة الألوان",
|
|
65
|
+
},
|
|
66
|
+
notFound: {
|
|
67
|
+
description: "لم نتمكن من العثور على الصفحة التي تبحث عنها.",
|
|
68
|
+
home: "العودة إلى الصفحة الرئيسية",
|
|
69
|
+
title: "الصفحة غير موجودة",
|
|
70
|
+
},
|
|
39
71
|
page: {
|
|
40
72
|
lastUpdated: "آخر تحديث في",
|
|
41
73
|
next: "التالي",
|
|
@@ -45,11 +77,20 @@ export const ar: UIStringsOverride = {
|
|
|
45
77
|
},
|
|
46
78
|
search: {
|
|
47
79
|
all: "الكل",
|
|
80
|
+
allLanguages: "جميع اللغات",
|
|
81
|
+
askAi: "اسأل الذكاء الاصطناعي",
|
|
82
|
+
askAiHint: "احصل على إجابة فورية من الذكاء الاصطناعي",
|
|
48
83
|
button: "بحث",
|
|
49
84
|
devOnly: "البحث متاح في إصدار الإنتاج.",
|
|
85
|
+
error: "حدث خطأ ما. يرجى المحاولة مرة أخرى.",
|
|
50
86
|
label: "البحث في الوثائق",
|
|
87
|
+
navigate: "تنقل",
|
|
51
88
|
noResults: "لم يتم العثور على نتائج.",
|
|
89
|
+
open: "فتح",
|
|
52
90
|
placeholder: "البحث في الوثائق…",
|
|
91
|
+
popular: "شائع",
|
|
92
|
+
preview: "معاينة",
|
|
93
|
+
results: "النتائج",
|
|
53
94
|
},
|
|
54
95
|
toc: { title: "في هذه الصفحة" },
|
|
55
96
|
};
|
package/src/core/ui-packs/bg.ts
CHANGED
|
@@ -9,25 +9,40 @@ export const bg: UIStringsOverride = {
|
|
|
9
9
|
connectMcp: "Свържи се с MCP",
|
|
10
10
|
copied: "Копирано!",
|
|
11
11
|
copyClaudeCode: "Копирай командата на Claude Code",
|
|
12
|
+
copyCode: "Копирай кода",
|
|
13
|
+
copyCodex: "Копирай командата на Codex",
|
|
12
14
|
copyMarkdown: "Копирай като Markdown",
|
|
13
15
|
copyServerUrl: "Копирай URL на сървъра",
|
|
14
16
|
edit: "Редактирай в GitHub",
|
|
17
|
+
export: "Експортирай",
|
|
18
|
+
exportEpub: "Експортирай в EPUB",
|
|
19
|
+
exportPdf: "Експортирай в PDF",
|
|
20
|
+
generating: "Генериране…",
|
|
21
|
+
openIn: "Отвори в {name}",
|
|
15
22
|
openInChat: "Отвори в чата",
|
|
16
23
|
scrollToTop: "Нагоре",
|
|
17
24
|
},
|
|
18
25
|
ask: {
|
|
26
|
+
ai: "ИИ",
|
|
27
|
+
clear: "Изчисти разговора",
|
|
28
|
+
close: "Затвори",
|
|
29
|
+
copy: "Копирай разговора",
|
|
19
30
|
empty: "Задайте въпрос за документацията.",
|
|
20
31
|
error: "Съжаляваме, нещо се обърка.",
|
|
21
32
|
label: "Задайте въпрос",
|
|
22
33
|
placeholder: "Задайте въпрос…",
|
|
23
34
|
send: "Изпрати",
|
|
35
|
+
tip: "Съвет: Можете да отваряте и затваряте чата с",
|
|
24
36
|
title: "Попитай ИИ",
|
|
37
|
+
you: "Вие",
|
|
25
38
|
},
|
|
26
39
|
banner: { dismiss: "Затваряне на съобщението" },
|
|
27
40
|
changelog: {
|
|
28
41
|
description: "Актуализации на продукта и бележки към изданията.",
|
|
42
|
+
showReleases: "Покажи изданията на {version}",
|
|
29
43
|
title: "Дневник на промените",
|
|
30
44
|
},
|
|
45
|
+
content: { diagramError: "Тази диаграма не можа да бъде показана." },
|
|
31
46
|
feedback: {
|
|
32
47
|
no: "Не",
|
|
33
48
|
question: "Беше ли полезна тази страница?",
|
|
@@ -35,7 +50,24 @@ export const bg: UIStringsOverride = {
|
|
|
35
50
|
yes: "Да",
|
|
36
51
|
},
|
|
37
52
|
languageSwitcher: { label: "Език", untranslated: "Непреведено" },
|
|
38
|
-
nav: {
|
|
53
|
+
nav: {
|
|
54
|
+
back: "Назад",
|
|
55
|
+
breadcrumb: "Навигационна пътека",
|
|
56
|
+
closeNavigation: "Затвори навигацията",
|
|
57
|
+
deprecated: "остаряло",
|
|
58
|
+
featured: "Препоръчано",
|
|
59
|
+
githubRepository: "Хранилище в GitHub",
|
|
60
|
+
navigation: "Навигация",
|
|
61
|
+
primary: "Основно",
|
|
62
|
+
sections: "Раздели",
|
|
63
|
+
toggleNavigation: "Превключи навигацията",
|
|
64
|
+
toggleTheme: "Превключи цветовата тема",
|
|
65
|
+
},
|
|
66
|
+
notFound: {
|
|
67
|
+
description: "Не успяхме да намерим страницата, която търсите.",
|
|
68
|
+
home: "Обратно към началната страница",
|
|
69
|
+
title: "Страницата не е намерена",
|
|
70
|
+
},
|
|
39
71
|
page: {
|
|
40
72
|
lastUpdated: "Последна актуализация",
|
|
41
73
|
next: "Напред",
|
|
@@ -45,11 +77,20 @@ export const bg: UIStringsOverride = {
|
|
|
45
77
|
},
|
|
46
78
|
search: {
|
|
47
79
|
all: "Всички",
|
|
80
|
+
allLanguages: "Всички езици",
|
|
81
|
+
askAi: "Попитай ИИ",
|
|
82
|
+
askAiHint: "Получете незабавен отговор от ИИ",
|
|
48
83
|
button: "Търсене",
|
|
49
84
|
devOnly: "Търсенето е достъпно в производствената компилация.",
|
|
85
|
+
error: "Нещо се обърка. Моля, опитайте отново.",
|
|
50
86
|
label: "Търсене в документацията",
|
|
87
|
+
navigate: "навигация",
|
|
51
88
|
noResults: "Няма намерени резултати.",
|
|
89
|
+
open: "отвори",
|
|
52
90
|
placeholder: "Търсене в документацията…",
|
|
91
|
+
popular: "Популярни",
|
|
92
|
+
preview: "преглед",
|
|
93
|
+
results: "Резултати",
|
|
53
94
|
},
|
|
54
95
|
toc: { title: "На тази страница" },
|
|
55
96
|
};
|
package/src/core/ui-packs/bn.ts
CHANGED
|
@@ -9,25 +9,40 @@ export const bn: UIStringsOverride = {
|
|
|
9
9
|
connectMcp: "MCP-তে সংযোগ করুন",
|
|
10
10
|
copied: "অনুলিপি করা হয়েছে!",
|
|
11
11
|
copyClaudeCode: "Claude Code কমান্ড অনুলিপি করুন",
|
|
12
|
+
copyCode: "কোড অনুলিপি করুন",
|
|
13
|
+
copyCodex: "Codex কমান্ড অনুলিপি করুন",
|
|
12
14
|
copyMarkdown: "Markdown হিসেবে অনুলিপি করুন",
|
|
13
15
|
copyServerUrl: "সার্ভার URL অনুলিপি করুন",
|
|
14
16
|
edit: "GitHub-এ সম্পাদনা করুন",
|
|
17
|
+
export: "এক্সপোর্ট করুন",
|
|
18
|
+
exportEpub: "EPUB-এ এক্সপোর্ট করুন",
|
|
19
|
+
exportPdf: "PDF-এ এক্সপোর্ট করুন",
|
|
20
|
+
generating: "তৈরি হচ্ছে…",
|
|
21
|
+
openIn: "{name}-এ খুলুন",
|
|
15
22
|
openInChat: "চ্যাটে খুলুন",
|
|
16
23
|
scrollToTop: "উপরে যান",
|
|
17
24
|
},
|
|
18
25
|
ask: {
|
|
26
|
+
ai: "AI",
|
|
27
|
+
clear: "কথোপকথন মুছুন",
|
|
28
|
+
close: "বন্ধ করুন",
|
|
29
|
+
copy: "কথোপকথন অনুলিপি করুন",
|
|
19
30
|
empty: "ডকুমেন্টেশন সম্পর্কে একটি প্রশ্ন করুন।",
|
|
20
31
|
error: "দুঃখিত, কিছু একটা ভুল হয়েছে।",
|
|
21
32
|
label: "একটি প্রশ্ন করুন",
|
|
22
33
|
placeholder: "একটি প্রশ্ন করুন…",
|
|
23
34
|
send: "পাঠান",
|
|
35
|
+
tip: "টিপ: চ্যাট খুলতে ও বন্ধ করতে চাপুন",
|
|
24
36
|
title: "AI-কে জিজ্ঞাসা করুন",
|
|
37
|
+
you: "আপনি",
|
|
25
38
|
},
|
|
26
39
|
banner: { dismiss: "ঘোষণা বন্ধ করুন" },
|
|
27
40
|
changelog: {
|
|
28
41
|
description: "পণ্য আপডেট এবং রিলিজ নোট।",
|
|
42
|
+
showReleases: "{version} রিলিজ দেখান",
|
|
29
43
|
title: "পরিবর্তন লগ",
|
|
30
44
|
},
|
|
45
|
+
content: { diagramError: "এই ডায়াগ্রামটি রেন্ডার করা যায়নি।" },
|
|
31
46
|
feedback: {
|
|
32
47
|
no: "না",
|
|
33
48
|
question: "এই পৃষ্ঠাটি কি সহায়ক ছিল?",
|
|
@@ -35,7 +50,24 @@ export const bn: UIStringsOverride = {
|
|
|
35
50
|
yes: "হ্যাঁ",
|
|
36
51
|
},
|
|
37
52
|
languageSwitcher: { label: "ভাষা", untranslated: "অনূদিত নয়" },
|
|
38
|
-
nav: {
|
|
53
|
+
nav: {
|
|
54
|
+
back: "ফিরে যান",
|
|
55
|
+
breadcrumb: "ব্রেডক্রাম্ব",
|
|
56
|
+
closeNavigation: "নেভিগেশন বন্ধ করুন",
|
|
57
|
+
deprecated: "অপ্রচলিত",
|
|
58
|
+
featured: "ফিচার্ড",
|
|
59
|
+
githubRepository: "GitHub রিপোজিটরি",
|
|
60
|
+
navigation: "নেভিগেশন",
|
|
61
|
+
primary: "প্রধান",
|
|
62
|
+
sections: "বিভাগসমূহ",
|
|
63
|
+
toggleNavigation: "নেভিগেশন টগল করুন",
|
|
64
|
+
toggleTheme: "রঙের থিম টগল করুন",
|
|
65
|
+
},
|
|
66
|
+
notFound: {
|
|
67
|
+
description: "আপনি যে পৃষ্ঠাটি খুঁজছেন তা আমরা খুঁজে পাইনি।",
|
|
68
|
+
home: "হোমে ফিরে যান",
|
|
69
|
+
title: "পৃষ্ঠা পাওয়া যায়নি",
|
|
70
|
+
},
|
|
39
71
|
page: {
|
|
40
72
|
lastUpdated: "সর্বশেষ আপডেট",
|
|
41
73
|
next: "পরবর্তী",
|
|
@@ -45,11 +77,20 @@ export const bn: UIStringsOverride = {
|
|
|
45
77
|
},
|
|
46
78
|
search: {
|
|
47
79
|
all: "সব",
|
|
80
|
+
allLanguages: "সব ভাষা",
|
|
81
|
+
askAi: "AI-কে জিজ্ঞাসা করুন",
|
|
82
|
+
askAiHint: "AI থেকে তাৎক্ষণিক উত্তর পান",
|
|
48
83
|
button: "অনুসন্ধান",
|
|
49
84
|
devOnly: "অনুসন্ধান প্রোডাকশন বিল্ডে উপলব্ধ।",
|
|
85
|
+
error: "কিছু একটা ভুল হয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন।",
|
|
50
86
|
label: "ডকুমেন্টেশন অনুসন্ধান করুন",
|
|
87
|
+
navigate: "নেভিগেট",
|
|
51
88
|
noResults: "কোনো ফলাফল পাওয়া যায়নি।",
|
|
89
|
+
open: "খুলুন",
|
|
52
90
|
placeholder: "ডকুমেন্টেশন অনুসন্ধান করুন…",
|
|
91
|
+
popular: "জনপ্রিয়",
|
|
92
|
+
preview: "প্রিভিউ",
|
|
93
|
+
results: "ফলাফল",
|
|
53
94
|
},
|
|
54
95
|
toc: { title: "এই পৃষ্ঠায়" },
|
|
55
96
|
};
|