blume 1.5.2 → 1.6.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.
Files changed (194) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/dist/cli/index.js +3639 -1377
  3. package/dist/cli/index.js.map +103 -91
  4. package/dist/types/ai/component-markdown.d.ts +79 -0
  5. package/dist/types/components/layout/nav-utils.d.ts +60 -0
  6. package/dist/types/core/base-path.d.ts +9 -0
  7. package/dist/types/core/config-input.d.ts +206 -4
  8. package/dist/types/core/config.d.ts +6 -4
  9. package/dist/types/core/data.d.ts +23 -1
  10. package/dist/types/core/github.d.ts +35 -0
  11. package/dist/types/core/i18n-ui.d.ts +8 -0
  12. package/dist/types/core/navigation.d.ts +69 -0
  13. package/dist/types/core/schema.d.ts +117 -1
  14. package/dist/types/core/sources/types.d.ts +31 -6
  15. package/dist/types/core/types.d.ts +23 -2
  16. package/dist/types/markdown/features.d.ts +21 -0
  17. package/dist/types/openapi/references.d.ts +21 -1
  18. package/dist/types/seo/jsonld.d.ts +105 -0
  19. package/dist/types/theme/fonts.d.ts +34 -4
  20. package/docs/_snippets/include-demo.mdx +7 -0
  21. package/docs/advanced/api-reference.mdx +3 -3
  22. package/docs/advanced/custom-pages.mdx +1 -1
  23. package/docs/advanced/graphql.mdx +84 -0
  24. package/docs/advanced/meta.ts +8 -1
  25. package/docs/configuration/ai.mdx +21 -3
  26. package/docs/configuration/index.mdx +24 -0
  27. package/docs/configuration/search.mdx +13 -1
  28. package/docs/configuration/seo.mdx +27 -0
  29. package/docs/configuration/theming.mdx +17 -0
  30. package/docs/content/components.mdx +7 -0
  31. package/docs/content/includes.mdx +68 -0
  32. package/docs/content/meta.ts +1 -0
  33. package/docs/content/navigation.mdx +25 -0
  34. package/docs/content/sources.mdx +42 -1
  35. package/docs/content/syntax.mdx +69 -1
  36. package/docs/content/versioning.mdx +15 -9
  37. package/docs/reference/cli.mdx +2 -1
  38. package/package.json +23 -14
  39. package/skills/blume-migrate/SKILL.md +16 -7
  40. package/skills/blume-migrate/references/docusaurus.md +5 -3
  41. package/skills/blume-migrate/references/fumadocs.md +10 -2
  42. package/skills/blume-migrate/references/mintlify.md +3 -2
  43. package/skills/blume-migrate/references/nextra.md +2 -2
  44. package/skills/blume-migrate/references/starlight.md +1 -1
  45. package/src/ai/agent-readability.ts +2 -1
  46. package/src/ai/ask-data.ts +2 -1
  47. package/src/ai/component-markdown.ts +199 -36
  48. package/src/ai/llms.ts +93 -6
  49. package/src/ai/markdown.ts +2 -2
  50. package/src/ai/mcp/discovery.ts +10 -2
  51. package/src/ai/mcp/server.ts +74 -2
  52. package/src/astro/generate.ts +183 -116
  53. package/src/astro/include-hmr.ts +81 -0
  54. package/src/astro/include-refresh.ts +0 -0
  55. package/src/astro/index.ts +3 -5
  56. package/src/astro/templates.ts +125 -76
  57. package/src/cli/commands/build.ts +84 -15
  58. package/src/cli/init/questions.ts +1 -0
  59. package/src/cli/init/scaffold.ts +27 -4
  60. package/src/components/colors.ts +142 -0
  61. package/src/components/content/Badge.astro +5 -12
  62. package/src/components/content/Callout.astro +19 -36
  63. package/src/components/content/Card.astro +15 -21
  64. package/src/components/content/Component.astro +10 -1
  65. package/src/components/content/GithubInfo.astro +28 -9
  66. package/src/components/content/Tabs.astro +27 -5
  67. package/src/components/content/github-info.ts +20 -5
  68. package/src/components/dropdown-dismiss.ts +122 -0
  69. package/src/components/layout/Fonts.astro +15 -8
  70. package/src/components/layout/Header.astro +44 -0
  71. package/src/components/layout/LanguageSwitcher.astro +9 -1
  72. package/src/components/layout/NavSelector.astro +12 -3
  73. package/src/components/layout/NavTree.astro +6 -18
  74. package/src/components/layout/PageActions.astro +29 -8
  75. package/src/components/layout/PageLayout.astro +10 -1
  76. package/src/components/layout/ReferenceLayout.astro +6 -1
  77. package/src/components/layout/RootLayout.astro +46 -15
  78. package/src/components/layout/Search.astro +36 -4
  79. package/src/components/layout/TableOfContents.astro +8 -2
  80. package/src/components/layout/head-scripts.ts +53 -1
  81. package/src/components/openapi/ApiOverview.astro +13 -3
  82. package/src/components/openapi/AsyncApiOperation.astro +7 -14
  83. package/src/components/openapi/GraphqlChip.astro +33 -0
  84. package/src/components/openapi/GraphqlFieldsTable.astro +111 -0
  85. package/src/components/openapi/GraphqlOperation.astro +186 -0
  86. package/src/components/openapi/GraphqlType.astro +154 -0
  87. package/src/components/openapi/MethodBadge.astro +3 -14
  88. package/src/components/openapi/Operation.astro +12 -5
  89. package/src/components/openapi/OperationPanel.astro +43 -0
  90. package/src/components/openapi/RequestPanel.astro +5 -10
  91. package/src/components/openapi/Responses.astro +1 -16
  92. package/src/components/openapi/graphql-helpers.ts +466 -0
  93. package/src/components/openapi/playground-client.ts +15 -0
  94. package/src/components/openapi/sample-panels.ts +45 -0
  95. package/src/components/openapi/snippets.ts +13 -35
  96. package/src/core/base-path.ts +11 -0
  97. package/src/core/config-input.ts +209 -2
  98. package/src/core/config.ts +6 -4
  99. package/src/core/content-assets.ts +15 -4
  100. package/src/core/data.ts +18 -2
  101. package/src/core/diagnostics.ts +8 -0
  102. package/src/core/frontmatter.ts +20 -8
  103. package/src/core/github.ts +71 -0
  104. package/src/core/graph.ts +22 -8
  105. package/src/core/heading-markers.ts +96 -0
  106. package/src/core/i18n-ui.ts +11 -0
  107. package/src/core/includes.ts +632 -0
  108. package/src/core/last-modified.ts +36 -11
  109. package/src/core/links.ts +79 -13
  110. package/src/core/meta.ts +2 -1
  111. package/src/core/nav-diagnostics.ts +11 -2
  112. package/src/core/navigation.ts +27 -6
  113. package/src/core/project-graph.ts +61 -9
  114. package/src/core/schema.ts +226 -35
  115. package/src/core/server-features.ts +5 -9
  116. package/src/core/sources/github-releases.ts +2 -2
  117. package/src/core/sources/normalize.ts +502 -115
  118. package/src/core/sources/notion.ts +43 -8
  119. package/src/core/sources/obsidian.ts +1038 -0
  120. package/src/core/sources/read.ts +36 -1
  121. package/src/core/sources/resolve.ts +34 -1
  122. package/src/core/sources/types.ts +28 -6
  123. package/src/core/sources/watch.ts +12 -8
  124. package/src/core/tsconfig-aliases.ts +48 -35
  125. package/src/core/types.ts +25 -2
  126. package/src/core/ui-packs/ar.ts +1 -0
  127. package/src/core/ui-packs/bg.ts +2 -0
  128. package/src/core/ui-packs/bn.ts +1 -0
  129. package/src/core/ui-packs/ca.ts +2 -0
  130. package/src/core/ui-packs/cs.ts +1 -0
  131. package/src/core/ui-packs/da.ts +1 -0
  132. package/src/core/ui-packs/de.ts +2 -0
  133. package/src/core/ui-packs/el.ts +2 -0
  134. package/src/core/ui-packs/es.ts +2 -0
  135. package/src/core/ui-packs/fa.ts +1 -0
  136. package/src/core/ui-packs/fi.ts +1 -0
  137. package/src/core/ui-packs/fr.ts +2 -0
  138. package/src/core/ui-packs/he.ts +1 -0
  139. package/src/core/ui-packs/hi.ts +1 -0
  140. package/src/core/ui-packs/hr.ts +2 -0
  141. package/src/core/ui-packs/hu.ts +2 -0
  142. package/src/core/ui-packs/id.ts +2 -0
  143. package/src/core/ui-packs/it.ts +1 -0
  144. package/src/core/ui-packs/ja.ts +2 -0
  145. package/src/core/ui-packs/ko.ts +2 -0
  146. package/src/core/ui-packs/nl.ts +2 -0
  147. package/src/core/ui-packs/no.ts +2 -0
  148. package/src/core/ui-packs/pl.ts +2 -0
  149. package/src/core/ui-packs/pt-br.ts +2 -0
  150. package/src/core/ui-packs/pt.ts +2 -0
  151. package/src/core/ui-packs/ro.ts +2 -0
  152. package/src/core/ui-packs/ru.ts +2 -0
  153. package/src/core/ui-packs/sk.ts +1 -0
  154. package/src/core/ui-packs/sr.ts +1 -0
  155. package/src/core/ui-packs/sv.ts +2 -0
  156. package/src/core/ui-packs/th.ts +1 -0
  157. package/src/core/ui-packs/tr.ts +2 -0
  158. package/src/core/ui-packs/uk.ts +2 -0
  159. package/src/core/ui-packs/vi.ts +1 -0
  160. package/src/core/ui-packs/zh-tw.ts +1 -0
  161. package/src/core/ui-packs/zh.ts +1 -0
  162. package/src/core/version-cut.ts +21 -3
  163. package/src/core/yaml.ts +26 -0
  164. package/src/deploy/function-bundle.ts +251 -0
  165. package/src/eval/schema.ts +3 -1
  166. package/src/markdown/code-title.ts +22 -16
  167. package/src/markdown/features.ts +21 -0
  168. package/src/markdown/fence-meta.ts +50 -0
  169. package/src/markdown/heading-anchors.ts +198 -37
  170. package/src/markdown/include.ts +247 -0
  171. package/src/markdown/index.ts +43 -34
  172. package/src/markdown/language-icon.ts +2 -2
  173. package/src/markdown/mdast.ts +7 -3
  174. package/src/markdown/ts2js.ts +264 -0
  175. package/src/openapi/asyncapi.ts +4 -1
  176. package/src/openapi/graphql-build.ts +293 -0
  177. package/src/openapi/graphql.ts +212 -0
  178. package/src/openapi/model.ts +38 -5
  179. package/src/openapi/parse.ts +34 -0
  180. package/src/openapi/proxy.ts +30 -5
  181. package/src/openapi/references.ts +89 -13
  182. package/src/openapi/render-mdx.ts +48 -8
  183. package/src/openapi/scalar.ts +5 -12
  184. package/src/openapi/source.ts +91 -23
  185. package/src/registry/eject.ts +11 -0
  186. package/src/search/documents.ts +229 -37
  187. package/src/search/orama-index.ts +9 -5
  188. package/src/seo/jsonld.ts +293 -51
  189. package/src/theme/code-block-padding.ts +16 -0
  190. package/src/theme/entry.ts +65 -11
  191. package/src/theme/fonts.ts +189 -16
  192. package/src/translate/prompts.ts +2 -0
  193. package/src/translate/run.ts +7 -0
  194. package/src/translate/work-list.ts +0 -0
@@ -28,6 +28,11 @@ export interface RemoteFontConfig {
28
28
  provider?: RemoteFontProvider;
29
29
  /** Weights (or variable ranges like `"100..900"`) to load. Defaults to `[400, 500, 600, 700]`. */
30
30
  weights?: (number | string)[];
31
+ /**
32
+ * Character subsets to load (`"latin"`, `"vietnamese"`, `"cyrillic"`, …).
33
+ * Defaults to `latin` plus whatever the site's configured locales need.
34
+ */
35
+ subsets?: string[];
31
36
  /** Fallback stack category. Defaults to `"mono"` for the mono role, `"sans"` otherwise. */
32
37
  fallback?: FontCategory;
33
38
  }
@@ -72,6 +77,7 @@ export type FontEntry =
72
77
  cssVariable: string;
73
78
  fallbacks: string[];
74
79
  name: string;
80
+ subsets: string[];
75
81
  weights: (number | string)[];
76
82
  }
77
83
  | {
@@ -205,6 +211,129 @@ export const isFontSlug = (value: string): value is FontSlug =>
205
211
  /** Weights loaded for a remote family when the config does not pin them. */
206
212
  const DEFAULT_REMOTE_WEIGHTS: (number | string)[] = [400, 500, 600, 700];
207
213
 
214
+ /**
215
+ * The subsets a language needs beyond `latin`, keyed by BCP 47 language
216
+ * subtag. Names are Google Fonts subsets (Bunny and Fontsource use the same
217
+ * ones). Languages whose alphabet fits Latin-1 — English, French, German,
218
+ * Spanish, … — need only `latin` and are absent. Keep keys alphabetical.
219
+ */
220
+ const LOCALE_SUBSETS = {
221
+ az: ["latin-ext"],
222
+ ba: ["cyrillic", "cyrillic-ext"],
223
+ be: ["cyrillic"],
224
+ bg: ["cyrillic"],
225
+ bs: ["latin-ext"],
226
+ cs: ["latin-ext"],
227
+ cv: ["cyrillic", "cyrillic-ext"],
228
+ cy: ["latin-ext"],
229
+ el: ["greek"],
230
+ eo: ["latin-ext"],
231
+ et: ["latin-ext"],
232
+ ha: ["latin-ext"],
233
+ hr: ["latin-ext"],
234
+ hu: ["latin-ext"],
235
+ ig: ["latin-ext"],
236
+ kk: ["cyrillic", "cyrillic-ext"],
237
+ ku: ["latin-ext"],
238
+ ky: ["cyrillic", "cyrillic-ext"],
239
+ lt: ["latin-ext"],
240
+ lv: ["latin-ext"],
241
+ mk: ["cyrillic"],
242
+ mn: ["cyrillic", "cyrillic-ext"],
243
+ mt: ["latin-ext"],
244
+ pl: ["latin-ext"],
245
+ ro: ["latin-ext"],
246
+ ru: ["cyrillic"],
247
+ sk: ["latin-ext"],
248
+ sl: ["latin-ext"],
249
+ sr: ["cyrillic", "latin-ext"],
250
+ tg: ["cyrillic", "cyrillic-ext"],
251
+ tk: ["latin-ext"],
252
+ tr: ["latin-ext"],
253
+ tt: ["cyrillic", "cyrillic-ext"],
254
+ uk: ["cyrillic"],
255
+ uz: ["cyrillic", "latin-ext"],
256
+ vi: ["vietnamese"],
257
+ yo: ["latin-ext"],
258
+ } satisfies Record<string, string[]>;
259
+
260
+ /** Type guard: does `language` have an entry in the subset table? */
261
+ const isSubsetLanguage = (
262
+ language: string
263
+ ): language is keyof typeof LOCALE_SUBSETS =>
264
+ Object.hasOwn(LOCALE_SUBSETS, language);
265
+
266
+ /**
267
+ * Subsets a script subtag (`sr-Latn`, `az-Cyrl`) asks for on its own, so a
268
+ * locale written in a script its language doesn't default to still loads it.
269
+ */
270
+ const SCRIPT_SUBSETS = {
271
+ cyrl: ["cyrillic"],
272
+ latn: ["latin-ext"],
273
+ } satisfies Record<string, string[]>;
274
+
275
+ /** Type guard: does `script` have an entry in the script table? */
276
+ const isSubsetScript = (
277
+ script: string
278
+ ): script is keyof typeof SCRIPT_SUBSETS =>
279
+ Object.hasOwn(SCRIPT_SUBSETS, script);
280
+
281
+ /** Google's listing order; keeps generated configs stable across runs. */
282
+ const SUBSET_ORDER = [
283
+ "latin",
284
+ "latin-ext",
285
+ "vietnamese",
286
+ "cyrillic",
287
+ "cyrillic-ext",
288
+ "greek",
289
+ "greek-ext",
290
+ ];
291
+
292
+ /** The shape of a config's `i18n` block the font builders read. */
293
+ export interface FontLocaleSource {
294
+ locales: { code: string }[];
295
+ }
296
+
297
+ /** The locale codes an optional `i18n` block declares (none when absent). */
298
+ export const fontLocaleCodes = (i18n: FontLocaleSource | undefined): string[] =>
299
+ i18n?.locales.map((locale) => locale.code) ?? [];
300
+
301
+ /**
302
+ * The subsets the site's locales need: `latin` always, plus each locale's
303
+ * script. A site with no `i18n` block (or only Latin-1 languages) gets just
304
+ * `latin` — Astro's own default — so its CSS and preloads don't change.
305
+ */
306
+ export const localeFontSubsets = (locales: string[]): string[] => {
307
+ const wanted = new Set(["latin"]);
308
+ for (const locale of locales) {
309
+ const [language = "", ...subtags] = locale.toLowerCase().split("-");
310
+ if (isSubsetLanguage(language)) {
311
+ for (const subset of LOCALE_SUBSETS[language]) {
312
+ wanted.add(subset);
313
+ }
314
+ }
315
+ for (const subtag of subtags) {
316
+ if (isSubsetScript(subtag)) {
317
+ for (const subset of SCRIPT_SUBSETS[subtag]) {
318
+ wanted.add(subset);
319
+ }
320
+ }
321
+ }
322
+ }
323
+ return SUBSET_ORDER.filter((subset) => wanted.has(subset));
324
+ };
325
+
326
+ /**
327
+ * Providers whose faces carry subset metadata, so `<Font preload>` can be
328
+ * filtered by subset. Fontshare serves whole files with no subset split; a
329
+ * subset filter there would match nothing and drop every preload.
330
+ */
331
+ const SUBSET_PROVIDERS = new Set<RemoteFontProvider>([
332
+ "google",
333
+ "bunny",
334
+ "fontsource",
335
+ ]);
336
+
208
337
  /** Kebab-case a family name into a slug (`"Noto Sans JP"` -> `"noto-sans-jp"`). */
209
338
  export const slugifyFontName = (name: string): string =>
210
339
  name
@@ -227,10 +356,15 @@ const slotCategory = (slot: FontSlot): FontCategory =>
227
356
  const isSlugValue = (value: FontValue): value is string =>
228
357
  typeof value === "string";
229
358
 
230
- /** A slot value normalized into an entry, or null for an unknown slug string. */
359
+ /**
360
+ * A slot value normalized into an entry, or null for an unknown slug string.
361
+ * `subsets` is the locale-derived default a remote family loads when the
362
+ * config does not pin its own.
363
+ */
231
364
  const resolveFontValue = (
232
365
  slot: FontSlot,
233
- value: FontValue
366
+ value: FontValue,
367
+ subsets: string[]
234
368
  ): FontEntry | null => {
235
369
  if (isSlugValue(value)) {
236
370
  if (!isFontSlug(value)) {
@@ -243,6 +377,7 @@ const resolveFontValue = (
243
377
  kind: "remote",
244
378
  name: def.family,
245
379
  provider: "google",
380
+ subsets,
246
381
  weights: def.weights,
247
382
  };
248
383
  }
@@ -263,6 +398,7 @@ const resolveFontValue = (
263
398
  kind: "remote",
264
399
  name: value.name,
265
400
  provider: value.provider ?? "google",
401
+ subsets: value.subsets ?? subsets,
266
402
  weights: value.weights ?? DEFAULT_REMOTE_WEIGHTS,
267
403
  };
268
404
  };
@@ -282,6 +418,7 @@ const mergeFontEntries = (current: FontEntry, next: FontEntry): FontEntry => {
282
418
  ) {
283
419
  return {
284
420
  ...current,
421
+ subsets: [...new Set([...current.subsets, ...next.subsets])],
285
422
  weights: [...new Set([...current.weights, ...next.weights])],
286
423
  };
287
424
  }
@@ -298,18 +435,26 @@ const mergeFontEntries = (current: FontEntry, next: FontEntry): FontEntry => {
298
435
  );
299
436
  };
300
437
 
301
- /** The unique Astro `fonts:` entries for the configured roles (deduped). */
302
- export const buildFontEntries = (fonts: FontsConfig): FontEntry[] => {
438
+ /**
439
+ * The unique Astro `fonts:` entries for the configured roles (deduped).
440
+ * `locales` are the site's configured locale codes; they pick the subsets a
441
+ * remote family loads unless its config pins `subsets` itself.
442
+ */
443
+ export const buildFontEntries = (
444
+ fonts: FontsConfig,
445
+ locales: string[] = []
446
+ ): FontEntry[] => {
303
447
  if (!fonts) {
304
448
  return [];
305
449
  }
450
+ const subsets = localeFontSubsets(locales);
306
451
  const entries = new Map<string, FontEntry>();
307
452
  for (const slot of SLOTS) {
308
453
  const value = fonts[slot];
309
454
  if (value === undefined) {
310
455
  continue;
311
456
  }
312
- const entry = resolveFontValue(slot, value);
457
+ const entry = resolveFontValue(slot, value, subsets);
313
458
  if (!entry) {
314
459
  continue;
315
460
  }
@@ -365,9 +510,15 @@ const PRELOAD_WEIGHTS = {
365
510
  mono: [400],
366
511
  } satisfies Record<FontSlot, number[]>;
367
512
 
368
- /** One `<Font>` render in the head: its CSS variable + weights to preload. */
513
+ /**
514
+ * One `<Font>` render in the head: its CSS variable + the weights (and, for
515
+ * providers that split faces by subset, the subsets) to preload. Local and
516
+ * Fontshare faces carry no subset, so those entries leave `preloadSubsets`
517
+ * unset and preload by weight alone.
518
+ */
369
519
  export interface FontHead {
370
520
  cssVariable: string;
521
+ preloadSubsets?: string[];
371
522
  preloadWeights: number[];
372
523
  }
373
524
 
@@ -402,28 +553,50 @@ const preloadWeightsFor = (slot: FontSlot, entry: FontEntry): number[] => {
402
553
  * by CSS variable with preload weights unioned across the roles that share a
403
554
  * family (so `display` and `body` both set to Inter preload 400/500/600 once).
404
555
  */
405
- export const configuredFonts = (fonts: FontsConfig): FontHead[] => {
556
+ export const configuredFonts = (
557
+ fonts: FontsConfig,
558
+ locales: string[] = []
559
+ ): FontHead[] => {
406
560
  if (!fonts) {
407
561
  return [];
408
562
  }
409
- const heads = new Map<string, Set<number>>();
563
+ const defaultSubsets = localeFontSubsets(locales);
564
+ const heads = new Map<
565
+ string,
566
+ { subsets: Set<string> | null; weights: Set<number> }
567
+ >();
410
568
  for (const slot of SLOTS) {
411
569
  const value = fonts[slot];
412
570
  if (value === undefined) {
413
571
  continue;
414
572
  }
415
- const entry = resolveFontValue(slot, value);
573
+ const entry = resolveFontValue(slot, value, defaultSubsets);
416
574
  if (!entry) {
417
575
  continue;
418
576
  }
419
- const weights = heads.get(entry.cssVariable) ?? new Set();
577
+ const head = heads.get(entry.cssVariable) ?? {
578
+ subsets: null,
579
+ weights: new Set<number>(),
580
+ };
420
581
  for (const weight of preloadWeightsFor(slot, entry)) {
421
- weights.add(weight);
582
+ head.weights.add(weight);
422
583
  }
423
- heads.set(entry.cssVariable, weights);
584
+ if (entry.kind === "remote" && SUBSET_PROVIDERS.has(entry.provider)) {
585
+ head.subsets ??= new Set();
586
+ for (const subset of entry.subsets) {
587
+ head.subsets.add(subset);
588
+ }
589
+ }
590
+ heads.set(entry.cssVariable, head);
424
591
  }
425
- return [...heads].map(([cssVariable, weights]) => ({
426
- cssVariable,
427
- preloadWeights: [...weights].toSorted((a, b) => a - b),
428
- }));
592
+ return [...heads].map(([cssVariable, head]) => {
593
+ const font: FontHead = {
594
+ cssVariable,
595
+ preloadWeights: [...head.weights].toSorted((a, b) => a - b),
596
+ };
597
+ if (head.subsets) {
598
+ font.preloadSubsets = [...head.subsets];
599
+ }
600
+ return font;
601
+ });
429
602
  };
@@ -65,6 +65,8 @@ Rules:
65
65
  - Translate the prose: headings, paragraphs, list items, table cells, admonitions, and image alt text.
66
66
  - In the YAML frontmatter, translate ONLY the values of these keys: ${KEY_LIST}. Copy every other frontmatter key and value exactly as written.
67
67
  - Never translate or alter: code blocks, inline code, import/export statements, JSX/MDX component names and their attributes, URLs, link targets, HTML tags, or frontmatter keys.
68
+ - A heading may end with anchor markers: \`[#some-id]\`, \`{#some-id}\` (also written \`\\{#some-id\\}\`), \`[!toc]\`, or \`[toc]\`. Copy every marker exactly as written, in the same position at the end of the translated heading — never translate, reorder, or drop a marker, and never change the id inside \`[#...]\` or \`{#...}\` (anchors must stay identical across languages).
69
+
68
70
  - Preserve the document structure exactly: the same headings, the same lists and tables, and the same number of code fences.${styleRule}${previousRule}
69
71
  - Output ONLY the complete translated file. Do not wrap it in a code fence. Do not add commentary before or after it.
70
72
  ${previousSection}
@@ -130,6 +130,13 @@ const runPageItem = async (
130
130
  });
131
131
 
132
132
  const sourceText = await readFile(item.sourcePath, "utf-8");
133
+ if (item.verbatim) {
134
+ // A non-markdown include partial (code embed) has no prose to translate;
135
+ // it's copied unchanged so the locale tree's relative includes resolve.
136
+ await writeTextAtomic(item.targetPath, sourceText);
137
+ stampLedger(ledger, item.sourceRel, item.locale, hashSource(sourceText));
138
+ return done("translated");
139
+ }
133
140
  // SAFETY: `targets` maps every configured locale, and work items only carry
134
141
  // configured locale codes.
135
142
  const target = context.targets.get(item.locale) as LocaleConfig;
Binary file