@master/internal-site 0.1.2 → 0.2.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 (239) hide show
  1. package/README.md +43 -0
  2. package/dist/astro.mjs +233 -47
  3. package/dist/config.d.mts +6 -0
  4. package/dist/localization-LfIbIysE.mjs +356 -0
  5. package/dist/localization.d.mts +69 -0
  6. package/dist/localization.mjs +1 -0
  7. package/package.json +12 -9
  8. package/src/astro/content-pages.ts +30 -11
  9. package/src/astro/index.ts +11 -5
  10. package/src/astro/localized-mdx.ts +45 -0
  11. package/src/astro/routes.template.ts +4 -0
  12. package/src/astro/site-context.ts +10 -2
  13. package/src/astro/style-assets.ts +107 -0
  14. package/src/benchmarks/BenchmarkBarRows.astro +3 -2
  15. package/src/benchmarks/BenchmarkBars.astro +1 -1
  16. package/src/benchmarks/BenchmarkChart.astro +5 -1
  17. package/src/benchmarks/BenchmarkChartGroup.astro +1 -1
  18. package/src/benchmarks/BenchmarkDataTable.astro +1 -1
  19. package/src/benchmarks/BenchmarkFigure.astro +2 -1
  20. package/src/benchmarks/BenchmarkMetricTable.astro +2 -2
  21. package/src/benchmarks/BenchmarkMetrics.astro +1 -1
  22. package/src/benchmarks/BenchmarkSampleSummary.astro +1 -1
  23. package/src/benchmarks/BenchmarkScrollRegion.svelte +2 -1
  24. package/src/benchmarks/BenchmarkSource.astro +1 -1
  25. package/src/benchmarks/BenchmarkStackedBars.astro +4 -3
  26. package/src/benchmarks/BenchmarkStatus.astro +3 -1
  27. package/src/benchmarks/localization.ts +22 -0
  28. package/src/benchmarks/types.ts +2 -0
  29. package/src/blog/article/BlogArticleContents.svelte +1 -1
  30. package/src/blog/article/BlogArticleEnd.astro +6 -9
  31. package/src/blog/article/BlogArticleMasthead.astro +17 -9
  32. package/src/config.ts +3 -0
  33. package/src/css/NamespaceUtilityTable.astro +1 -1
  34. package/src/css/ThemeNumberVariableTable.astro +1 -1
  35. package/src/css/foundation-data/specimen-content.ts +2 -2
  36. package/src/css/markdown.ts +64 -14
  37. package/src/demo/Demo.svelte +10 -4
  38. package/src/demo/DemoAsset.astro +6 -3
  39. package/src/demo/DemoAxes.svelte +4 -2
  40. package/src/demo/DemoCatalog.astro +9 -6
  41. package/src/demo/DemoComparison.svelte +3 -0
  42. package/src/demo/DemoContainer.svelte +8 -3
  43. package/src/demo/DemoControls.svelte +5 -1
  44. package/src/demo/DemoCopyButton.svelte +9 -4
  45. package/src/demo/DemoCopyGroup.svelte +6 -2
  46. package/src/demo/DemoExample.astro +3 -1
  47. package/src/demo/DemoFeatureSupport.svelte +5 -1
  48. package/src/demo/DemoGeneratedCSS.svelte +5 -1
  49. package/src/demo/DemoIndex.astro +6 -3
  50. package/src/demo/DemoLegend.svelte +3 -1
  51. package/src/demo/DemoMeasure.svelte +4 -2
  52. package/src/demo/DemoMedia.svelte +3 -1
  53. package/src/demo/DemoMotion.svelte +6 -3
  54. package/src/demo/DemoPaletteGroups.svelte +4 -2
  55. package/src/demo/DemoScrollArea.svelte +2 -0
  56. package/src/demo/DemoStyleComparison.astro +6 -3
  57. package/src/demo/DemoSurface.svelte +2 -0
  58. package/src/demo/DemoThemeComparison.astro +5 -2
  59. package/src/demo/DemoTokenTable.astro +5 -3
  60. package/src/demo/DemoViewTransition.astro +3 -1
  61. package/src/demo/DemoViewport.svelte +18 -11
  62. package/src/demo/DemoWaterfall.astro +4 -2
  63. package/src/demo/ProjectStyleExample.astro +4 -1
  64. package/src/demo/foundations/FoundationDialog.astro +6 -2
  65. package/src/demo/foundations/FoundationMedia.astro +1 -0
  66. package/src/demo/foundations/FoundationShrink.astro +1 -0
  67. package/src/demo/foundations/FoundationTokensView.astro +16 -5
  68. package/src/demo/foundations/Palette.svelte +11 -7
  69. package/src/demo/foundations/dialog-copy.d.ts +1 -0
  70. package/src/demo/foundations/dialog-copy.js +5 -0
  71. package/src/demo/illustration-materials.ts +1 -1
  72. package/src/demo/illustration.css +218 -0
  73. package/src/docs-shell/common/i18n.config.d.ts +1 -1
  74. package/src/docs-shell/common/i18n.config.js +4 -12
  75. package/src/docs-shell/common/locales.d.ts +8 -0
  76. package/src/docs-shell/common/locales.js +18 -0
  77. package/src/docs-shell/common/terminology.json +32 -0
  78. package/src/docs-shell/common/translations.d.ts +3 -0
  79. package/src/docs-shell/common/translations.js +294 -0
  80. package/src/docs-shell/components/ArticleHeader.astro +5 -3
  81. package/src/docs-shell/components/Callout.astro +1 -0
  82. package/src/docs-shell/components/CodeCopyButton.astro +5 -1
  83. package/src/docs-shell/components/CodeTabBar.astro +7 -3
  84. package/src/docs-shell/components/CodeTabs.astro +2 -1
  85. package/src/docs-shell/components/CodeView.astro +1 -1
  86. package/src/docs-shell/components/DocHeader.svelte +13 -17
  87. package/src/docs-shell/components/DocHeading.astro +3 -1
  88. package/src/docs-shell/components/DocMenuButton.svelte +126 -61
  89. package/src/docs-shell/components/DocSidebar.svelte +3 -2
  90. package/src/docs-shell/components/DocVersionSelect.svelte +2 -0
  91. package/src/docs-shell/components/DocumentAPIIndex.astro +1 -1
  92. package/src/docs-shell/components/DocumentChoices.astro +1 -1
  93. package/src/docs-shell/components/DocumentCodeExampleView.astro +3 -1
  94. package/src/docs-shell/components/DocumentCodeTable.astro +4 -2
  95. package/src/docs-shell/components/DocumentCopyButton.svelte +3 -0
  96. package/src/docs-shell/components/DocumentDisclosure.astro +2 -0
  97. package/src/docs-shell/components/DocumentFileTree.astro +3 -0
  98. package/src/docs-shell/components/DocumentFlow.astro +4 -1
  99. package/src/docs-shell/components/DocumentIllustration.astro +8 -12
  100. package/src/docs-shell/components/DocumentKeyList.astro +6 -4
  101. package/src/docs-shell/components/DocumentNamespaceTable.astro +5 -3
  102. package/src/docs-shell/components/DocumentOptionEntry.astro +0 -1
  103. package/src/docs-shell/components/DocumentOptionList.astro +1 -1
  104. package/src/docs-shell/components/DocumentOptions.astro +3 -2
  105. package/src/docs-shell/components/DocumentParameters.astro +1 -1
  106. package/src/docs-shell/components/DocumentPrompt.astro +2 -0
  107. package/src/docs-shell/components/DocumentValueList.astro +1 -1
  108. package/src/docs-shell/components/DocumentationIndex.svelte +8 -7
  109. package/src/docs-shell/components/ExpandContent.svelte +3 -0
  110. package/src/docs-shell/components/ExplorerView.astro +1 -0
  111. package/src/docs-shell/components/GeneratedCSS.astro +4 -1
  112. package/src/docs-shell/components/Header.svelte +1 -1
  113. package/src/docs-shell/components/InlineCode.astro +1 -0
  114. package/src/docs-shell/components/InteractingIndicator.astro +1 -0
  115. package/src/docs-shell/components/LanguageButton.svelte +1 -0
  116. package/src/docs-shell/components/LanguageSelect.svelte +2 -0
  117. package/src/docs-shell/components/Link.svelte +3 -2
  118. package/src/docs-shell/components/NotFound.astro +6 -4
  119. package/src/docs-shell/components/PMAction.astro +1 -0
  120. package/src/docs-shell/components/PageContent.svelte +1 -1
  121. package/src/docs-shell/components/PageContentDisclosure.astro +2 -1
  122. package/src/docs-shell/components/PageContentList.svelte +2 -0
  123. package/src/docs-shell/components/PageNavs.astro +3 -3
  124. package/src/docs-shell/components/SearchButton.svelte +3 -0
  125. package/src/docs-shell/components/SearchDialog.svelte +18 -16
  126. package/src/docs-shell/components/Segments.svelte +2 -0
  127. package/src/docs-shell/components/StepSection.astro +1 -2
  128. package/src/docs-shell/components/Tabs.astro +1 -0
  129. package/src/docs-shell/components/ThemeSelect.svelte +2 -0
  130. package/src/docs-shell/components/TimeAgo.svelte +6 -6
  131. package/src/docs-shell/components/markdown/Table.astro +1 -0
  132. package/src/docs-shell/components/markdown-components.ts +0 -1
  133. package/src/docs-shell/contexts/search.ts +1 -1
  134. package/src/docs-shell/factories/Page.astro +10 -5
  135. package/src/docs-shell/factories/create-page.ts +33 -6
  136. package/src/docs-shell/layouts/BlogArticle.astro +3 -11
  137. package/src/docs-shell/remark/markdown-components.js +28 -7
  138. package/src/docs-shell/remark/slug-and-toc.js +5 -7
  139. package/src/docs-shell/scripts/code.ts +5 -2
  140. package/src/docs-shell/scripts/document.ts +1 -1
  141. package/src/docs-shell/site.ts +9 -1
  142. package/src/docs-shell/types/Metadata.ts +11 -0
  143. package/src/docs-shell/utils/document-copy.d.ts +2 -0
  144. package/src/docs-shell/utils/document-copy.js +18 -0
  145. package/src/docs-shell/utils/generate-metadata.ts +5 -1
  146. package/src/docs-shell/utils/head.ts +5 -2
  147. package/src/docs-shell/utils/i18n-pathname.ts +13 -2
  148. package/src/docs-shell/utils/i18n.ts +4 -1
  149. package/src/docs-shell/utils/localized-metadata.ts +22 -0
  150. package/src/docs-shell/utils/markdown-links.ts +61 -0
  151. package/src/docs-shell/utils/markdown-tree.ts +19 -6
  152. package/src/docs-shell/utils/resolve-heading.d.ts +1 -0
  153. package/src/docs-shell/utils/resolve-heading.js +21 -0
  154. package/src/docs-shell/utils/search-pages.ts +75 -22
  155. package/src/examples/components/ArticleTransitionScene.svelte +4 -0
  156. package/src/examples/layout-system/page.astro +0 -1
  157. package/src/home/HomeBentoFrame.astro +1 -0
  158. package/src/home/HomeBentoGrid.astro +1 -0
  159. package/src/home/bento.css +167 -0
  160. package/src/home/design.css +1 -1
  161. package/src/home/hero.css +5 -0
  162. package/src/home/home.css +4 -576
  163. package/src/home/segmented-control.css +94 -0
  164. package/src/layouts/Document.astro +4 -2
  165. package/src/public/icons/json.svg +1 -0
  166. package/src/scripts/atomic-class-authoring.ts +98 -0
  167. package/src/scripts/localization.ts +239 -0
  168. package/src/scripts/mdx-sample.ts +12 -0
  169. package/src/scripts/static-social.ts +17 -3
  170. package/src/scripts/style-delivery.mjs +120 -0
  171. package/src/sections/blog.astro +1 -1
  172. package/src/sections/guide.astro +4 -3
  173. package/src/styles/blog-article.css +10 -7
  174. package/src/styles/blog-materials.css +13 -0
  175. package/src/styles/demo.css +0 -1
  176. package/src/styles/docs-shell/code.css +5 -61
  177. package/src/styles/docs-shell/documentation-search.css +1 -0
  178. package/src/styles/docs-shell/header.css +2 -73
  179. package/src/styles/docs-shell/menu.css +37 -17
  180. package/src/styles/docs-shell/page-content.css +2 -37
  181. package/src/styles/docs-shell/prose.css +18 -9
  182. package/src/styles/docs-shell/steps.css +2 -39
  183. package/src/styles/document-comparison.css +5 -2
  184. package/src/styles/documentation-illustration.css +1 -9
  185. package/src/styles/documentation-index.css +2 -1
  186. package/src/styles/documents.css +2 -4
  187. package/src/styles/interface.css +5 -3
  188. package/src/styles/system/DESIGN.md +34 -14
  189. package/src/styles/system/browser.css +21 -0
  190. package/src/styles/system/button-feedback.css +13 -0
  191. package/src/styles/system/callout.css +14 -0
  192. package/src/styles/system/chart.css +18 -0
  193. package/src/styles/system/checks.css +18 -0
  194. package/src/styles/system/code-panel.css +82 -0
  195. package/src/styles/system/code-tabs.css +40 -0
  196. package/src/styles/system/code.css +8 -85
  197. package/src/styles/system/compare.css +7 -0
  198. package/src/styles/system/controls.css +10 -109
  199. package/src/styles/system/copy.css +32 -0
  200. package/src/styles/system/data.css +10 -134
  201. package/src/styles/system/dialog.css +16 -0
  202. package/src/styles/system/disclosure.css +19 -0
  203. package/src/styles/system/expand.css +11 -0
  204. package/src/styles/system/fields.css +25 -0
  205. package/src/styles/system/header-core.css +101 -0
  206. package/src/styles/system/hint.css +5 -0
  207. package/src/styles/system/index.css +13 -0
  208. package/src/styles/system/inline-code.css +4 -0
  209. package/src/styles/system/kbd.css +4 -0
  210. package/src/styles/system/logos.css +10 -0
  211. package/src/styles/system/menu.css +14 -0
  212. package/src/styles/system/metrics.css +22 -0
  213. package/src/styles/system/pagehead.css +13 -0
  214. package/src/styles/system/pager.css +13 -0
  215. package/src/styles/system/parameters.css +17 -0
  216. package/src/styles/system/prompt.css +7 -0
  217. package/src/styles/system/prose.css +4 -75
  218. package/src/styles/system/range.css +12 -0
  219. package/src/styles/system/reading.css +44 -0
  220. package/src/styles/system/scroll.css +6 -0
  221. package/src/styles/system/search-trigger.css +10 -0
  222. package/src/styles/system/segmented.css +22 -0
  223. package/src/styles/system/sheet.css +8 -0
  224. package/src/styles/system/shell.css +8 -102
  225. package/src/styles/system/sidebar.css +14 -0
  226. package/src/styles/system/source.css +5 -0
  227. package/src/styles/system/specimens.css +3 -0
  228. package/src/styles/system/stage.css +18 -0
  229. package/src/styles/system/steps.css +51 -0
  230. package/src/styles/system/surfaces.css +8 -104
  231. package/src/styles/system/table.css +23 -0
  232. package/src/styles/system/toc.css +59 -0
  233. package/src/styles/system/tooltip.css +6 -0
  234. package/src/styles/system/tray-code.css +6 -0
  235. package/src/styles/system/tray.css +20 -0
  236. package/src/styles/system/tree.css +15 -0
  237. package/src/styles/system/waterfall.css +14 -0
  238. package/src/virtual.d.ts +5 -1
  239. package/src/blog/article/language.ts +0 -29
package/README.md CHANGED
@@ -27,6 +27,12 @@ Optional `header.communityNavs` selects the links shown beside search without re
27
27
 
28
28
  The app module exports its logo component, navigation, version links and source/feedback URLs using the `SiteApp` contract. Consumers retain their own content and homepage composition. Ordinary documents use `content/<section>/<slug>/content.mdx` and `metadata.ts`; a local `page.astro` can override the generated entry. Dynamic routes enumerate params from lightweight `params.ts` files. The integration owns generated routes, page asset collection and the Astro/Svelte renderer setup.
29
29
 
30
+ Blog articles with a cover declare `cover: { src, alt, width, height }` in their
31
+ metadata. The shared article layout renders it below the byline and uses it for
32
+ Open Graph and Twitter images. An optional `socialImage` points to a PNG/JPEG
33
+ version of the same artwork; a cover takes precedence over separate social-card
34
+ images. Articles without a cover retain their existing layout and social cards.
35
+
30
36
  ```css
31
37
  /* styles/globals.css */
32
38
  @import '@master/css';
@@ -62,6 +68,43 @@ Import individual component subpaths, including `.astro` and `.svelte` extension
62
68
 
63
69
  The CSS-specific subpaths require the matching optional compiler, tooling, schema, preset and language-service peers. Keep them out of browser-only imports. Shared public assets are served in development and copied into build output; consumer public files take precedence. No install hook writes consumer source files.
64
70
 
71
+ ## Local MDX translations
72
+
73
+ English uses the root URL. The shared locale definitions map `ja`, `tw`, `ko`
74
+ and `cn` to matching URL prefixes and the HTML language tags `ja`, `zh-TW`,
75
+ `ko` and `zh-CN`. Keep complete sibling editions in `content.<locale>.mdx`;
76
+ `metadata.ts` retains English identity, dates, authors and ordering. Translated
77
+ frontmatter supplies titles, summaries, image descriptions and article-specific
78
+ demonstration copy. Keep explicit heading IDs identical across editions.
79
+
80
+ `build/localization.ts` supplies filesystem discovery, localized document reads,
81
+ dependency fingerprints, explicit review operations and completeness checks.
82
+ Configure `i18n.validation` with the consumer's resource-loader and review-index
83
+ URLs. The integration validates them on every production build, including direct
84
+ `astro build` calls. Source fingerprints include metadata and imported local or
85
+ shared source files. A missing edition or changed source/translation fails the
86
+ gate until independently reviewed. Builds never refresh review records.
87
+
88
+ The resource loader also exposes local and shared UI dictionaries to the same
89
+ gate. Common controls use `common/translations.js`; product copy belongs to each
90
+ consumer. Both sites follow `common/terminology.json`. Use `sharedInterfaceKeys()`
91
+ to include newly introduced literal interface requests in the UI inventory.
92
+
93
+ The route registry imports each selected MDX edition through Astro's propagated
94
+ asset boundary. Nested MDX imports inherit their parent's language through Vite;
95
+ missing prose editions and draft includes throw. Prose-free fenced companion
96
+ examples stay shared. Do not import every edition eagerly or reimplement the
97
+ resolver in a consumer. Per-request `documentCopyOf(Astro)` and `demoCopy` let
98
+ shared demonstrations read localized prose while retaining one executable model.
99
+
100
+ Consumers derive navigation, search, Markdown, feeds and llms exports from their
101
+ public inventory and the selected local edition. Use `extractSearchNodesFromMdx`
102
+ for authored MDX and `extractSearchNodesFromMarkdown` for portable output; the
103
+ latter retains explicit anchor targets and GFM tables. `transformMarkdownLinks`
104
+ edits reader links without changing inline or fenced examples. Withdraw stale
105
+ owned exports when a source becomes a draft or is removed. Required Reference
106
+ inputs reject draft status instead of publishing a partial catalog.
107
+
65
108
  ## Development and releases
66
109
 
67
110
  From the Master repository, run `bun run build:packages`, `bun run --cwd packages/internal-site type-check` and `bun run --cwd packages/internal-site test`. Astro checks `.astro` implementations and Svelte checks interactive components separately. The root `build` builds the Node entrypoints before the site.
package/dist/astro.mjs CHANGED
@@ -1,17 +1,18 @@
1
+ import { d as validateSiteTranslations, f as isSharedMdxSample, h as createHeadingResolver, m as locales } from "./localization-LfIbIysE.mjs";
1
2
  import { svgoOptimizer } from "astro/config";
2
3
  import svelte from "@astrojs/svelte";
3
4
  import mdx from "@astrojs/mdx";
4
- import { unified } from "@astrojs/markdown-remark";
5
+ import { parseFrontmatter, unified } from "@astrojs/markdown-remark";
5
6
  import masterCSS from "@master/css-astro";
6
7
  import { fileURLToPath, pathToFileURL } from "node:url";
7
- import { dirname, extname, join, relative, resolve, sep } from "node:path";
8
+ import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
8
9
  import { cp, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
9
10
  import { createReadStream, existsSync } from "node:fs";
10
11
  import { isCSSRequest, searchForWorkspaceRoot, transformWithOxc } from "vite";
12
+ import { createHash } from "node:crypto";
13
+ import { parse, parseExpressionAt } from "acorn";
11
14
  import { optimize } from "svgo";
12
- import Slugger from "github-slugger";
13
15
  import { SKIP, visit } from "unist-util-visit";
14
- import { parse, parseExpressionAt } from "acorn";
15
16
  // src/astro/module-id.ts
16
17
  /** A Vite module id without its query or hash. */
17
18
  function cleanId(id) {
@@ -56,6 +57,33 @@ async function prepareContentPages(site, root, contentPrefix) {
56
57
  const destination = join(root, ".astro/master-site/pages");
57
58
  const pages = {};
58
59
  const wanted = /* @__PURE__ */ new Set();
60
+ const localizedDocuments = {};
61
+ const localizedMetadata = {};
62
+ const availableLocales = {};
63
+ for (const file of files) {
64
+ if (!file.endsWith("/content.mdx")) continue;
65
+ const directory = dirname(file);
66
+ const route = relative(content, directory).replaceAll("\\", "/");
67
+ const pathname = "/" + route.split("/").filter((part) => !/^\(.+\)$/.test(part)).join("/");
68
+ const key = `${contentPrefix}/${route ? route + "/" : ""}page.astro`;
69
+ const section = route.split("/").find((part) => !/^\(.+\)$/.test(part)) ?? "home";
70
+ const locales = site.i18n.sectionLocales?.[section] ?? site.i18n.locales;
71
+ const metadataSource = await readFile(join(directory, "metadata.ts"), "utf8").catch(() => "");
72
+ const draft = parseFrontmatter(await readFile(file, "utf8")).frontmatter.draft === true || /\bdraft\s*:\s*true\b/.test(metadataSource);
73
+ const documents = {};
74
+ const metadata = {};
75
+ for (const locale of draft ? [] : locales) {
76
+ const variant = locale === site.i18n.defaultLocale ? file : join(directory, `content.${locale}.mdx`);
77
+ if (!files.has(variant)) continue;
78
+ const { frontmatter } = parseFrontmatter(await readFile(variant, "utf8"));
79
+ if (frontmatter.draft === true) continue;
80
+ documents[locale] = pathToFileURL(variant);
81
+ metadata[locale] = frontmatter;
82
+ }
83
+ localizedDocuments[pathname] = documents;
84
+ localizedMetadata[pathname] = metadata;
85
+ availableLocales[key] = Object.keys(documents);
86
+ }
59
87
  for (const file of files) {
60
88
  if (!file.endsWith("/content.mdx")) continue;
61
89
  const directory = dirname(file);
@@ -86,15 +114,6 @@ export const generateMetadata = createPageMetadata(metadata, dictionaries)
86
114
  pages[`${contentPrefix}/${route ? route + "/" : ""}page.astro`] = pathToFileURL(output);
87
115
  }
88
116
  for (const stale of await contentFiles(destination).catch(() => [])) if (stale.endsWith("/page.astro") && !wanted.has(stale)) await unlink(stale);
89
- const availableLocales = {};
90
- for (const file of files) {
91
- if (!file.endsWith("/content.mdx")) continue;
92
- const directory = dirname(file);
93
- const route = relative(content, directory).replaceAll("\\", "/");
94
- const section = route.split("/").find((segment) => !/^\(.+\)$/.test(segment)) ?? "home";
95
- if (!site.i18n.translatedOnly?.includes(section)) continue;
96
- availableLocales[`${contentPrefix}/${route ? route + "/" : ""}page.astro`] = site.i18n.locales.filter((locale) => locale === site.i18n.defaultLocale || files.has(join(directory, `content.${locale}.mdx`)));
97
- }
98
117
  for (const section of site.i18n.translatedOnly ?? []) {
99
118
  const key = `${contentPrefix}/${section}/page.astro`;
100
119
  if (!files.has(join(content, section, "page.astro"))) continue;
@@ -103,7 +122,9 @@ export const generateMetadata = createPageMetadata(metadata, dictionaries)
103
122
  }
104
123
  return {
105
124
  pages,
106
- availableLocales
125
+ availableLocales,
126
+ localizedDocuments,
127
+ localizedMetadata
107
128
  };
108
129
  }
109
130
  // src/astro/server-styles.ts
@@ -156,41 +177,171 @@ function svgMarkup() {
156
177
  }
157
178
  };
158
179
  }
159
- // src/docs-shell/utils/resolve-heading.js
160
- function resolveHeading(title, id) {
161
- const explicit = typeof title === "string" && title.match(/\s+\{#([\w-]+)\}$/);
162
- if (explicit) {
163
- title = title.slice(0, explicit.index);
164
- id = explicit[1];
165
- }
166
- const words = title?.split?.(" ");
167
- if (words?.pop() === "[sr-only]") return {
168
- title: words.join(" "),
169
- id: id?.replace("-sr-only", "")?.replace("-[sr-only]", ""),
170
- className: "sr-only"
171
- };
180
+ // src/astro/localized-mdx.ts
181
+ /** Nested article MDX follows its importer; executable examples keep one source. */
182
+ function localizedMdx({ locales: locales$1 = locales, defaultLocale = "en" } = {}) {
172
183
  return {
173
- title,
174
- id
184
+ name: "@master/internal-site:localized-mdx",
185
+ enforce: "pre",
186
+ async resolveId(source, importer, options) {
187
+ if (!importer || !cleanId(importer).endsWith(".mdx")) return;
188
+ const language = cleanId(importer).match(/\.([a-z]{2})\.mdx$/)?.[1];
189
+ const locale = language && locales$1.includes(language) ? language : defaultLocale;
190
+ const resolved = await this.resolve(source, importer, {
191
+ ...options,
192
+ skipSelf: true
193
+ });
194
+ if (!resolved || resolved.external) return;
195
+ const id = cleanId(resolved.id);
196
+ if (!id.endsWith(".mdx") || id.startsWith("\0")) return;
197
+ const suffix = resolved.id.slice(id.length) || source.slice(cleanId(source).length);
198
+ const file = id.startsWith("file:") ? fileURLToPath(id) : id.startsWith("/@fs/") ? id.slice(5) : id;
199
+ if (!isAbsolute(file)) return;
200
+ const original = file.replace(/\.([a-z]{2})\.mdx$/, (match, language) => locales$1.includes(language) ? ".mdx" : match);
201
+ this.addWatchFile(original);
202
+ const originalSource = await readFile(original, "utf8");
203
+ const originalMetadata = parseFrontmatter(originalSource).frontmatter;
204
+ if (originalMetadata.draft === true || originalMetadata.disabled === true) throw new Error(`Unpublished source MDX include: ${original}`);
205
+ if (locale === defaultLocale || !originalSource.trim() || isSharedMdxSample(original, originalSource)) return {
206
+ ...resolved,
207
+ id: original + suffix
208
+ };
209
+ const translated = original.replace(/\.mdx$/, `.${locale}.mdx`);
210
+ this.addWatchFile(translated);
211
+ let translatedSource;
212
+ try {
213
+ translatedSource = await readFile(translated, "utf8");
214
+ } catch (cause) {
215
+ throw new Error(`Missing translated MDX include: ${translated}`, { cause });
216
+ }
217
+ const metadata = parseFrontmatter(translatedSource).frontmatter;
218
+ if (metadata.draft === true || metadata.disabled === true) throw new Error(`Unpublished translated MDX include: ${translated}`);
219
+ return {
220
+ ...resolved,
221
+ id: translated + suffix
222
+ };
223
+ }
175
224
  };
176
225
  }
226
+ // src/astro/style-assets.ts
227
+ /** Collapse only the compiler's unconditional, same-directory asset links.
228
+ * This is concatenation of already compiled CSS, not a second CSS compiler.
229
+ * Keep conditions, layers, URLs, repeated occurrences and unrelated imports intact.
230
+ */
231
+ async function inlineGeneratedStyles(file, ancestors = []) {
232
+ if (ancestors.includes(file)) throw new Error(`Cyclic generated stylesheet: ${file}`);
233
+ const source = await readFile(file, "utf8");
234
+ let code = "", cursor = 0;
235
+ while (cursor < source.length) {
236
+ const rest = source.slice(cursor);
237
+ const trivia = /^(?:\s+|\/\*[\s\S]*?\*\/|@layer\s+[\w,\s-]+;)/.exec(rest);
238
+ if (trivia) {
239
+ code += trivia[0];
240
+ cursor += trivia[0].length;
241
+ continue;
242
+ }
243
+ const match = /^@import\s+["'](\.\/master-css-[\w-]+\.css)["']\s*;/.exec(rest);
244
+ if (!match) break;
245
+ code += await inlineGeneratedStyles(resolve(dirname(file), match[1]), [...ancestors, file]) + "\n";
246
+ cursor += match[0].length;
247
+ }
248
+ const expanded = code + source.slice(cursor);
249
+ return /@(?:import|charset|namespace)\b/.test(expanded) ? source : expanded;
250
+ }
251
+ /** Preserve HTML link order and each reusable entry's cache identity. Originals
252
+ * remain available to any JS/CSS references; no client loading logic is added. */
253
+ async function compactStyleAssets(output) {
254
+ const root = resolve(output), replacements = /* @__PURE__ */ new Map();
255
+ const documents = /* @__PURE__ */ new Map(), users = /* @__PURE__ */ new Map();
256
+ const boundaries = /* @__PURE__ */ new Set();
257
+ async function emit(code) {
258
+ const href = `/_astro/site-style-${createHash("sha256").update(code).digest("hex").slice(0, 20)}.css`;
259
+ await writeFile(resolve(root, "." + href), code);
260
+ return href;
261
+ }
262
+ async function walk(directory) {
263
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
264
+ const file = resolve(directory, entry.name);
265
+ if (entry.isDirectory()) {
266
+ await walk(file);
267
+ continue;
268
+ }
269
+ if (!entry.name.endsWith(".html")) continue;
270
+ let html = await readFile(file, "utf8");
271
+ const links = [...html.matchAll(/<link\b(?=[^>]*\brel="stylesheet")(?=[^>]*\bhref="(\/_astro\/[^"/]+\.css)")[^>]*>/g)];
272
+ for (const match of links) {
273
+ const href = match[1];
274
+ if (!replacements.has(href)) {
275
+ const input = resolve(root, "." + href);
276
+ const source = await readFile(input, "utf8");
277
+ const code = await inlineGeneratedStyles(input);
278
+ const next = source === code ? href : await emit(code);
279
+ replacements.set(href, next);
280
+ }
281
+ const next = replacements.get(href);
282
+ if (/\/(site|interface|documents)\./.test(href)) boundaries.add(next);
283
+ const pages = users.get(next) ?? [];
284
+ if (!pages.includes(file)) pages.push(file);
285
+ users.set(next, pages);
286
+ html = html.replace(match[0], match[0].replace(`href="${href}"`, `href="${next}"`));
287
+ }
288
+ documents.set(file, html);
289
+ }
290
+ }
291
+ await walk(root);
292
+ const signatures = new Map([...users].map(([href, pages]) => [href, pages.sort().join("\n")]));
293
+ const adjacent = /* @__PURE__ */ new Map();
294
+ for (const [file, html] of documents) for (const match of html.matchAll(/(?=<link rel="stylesheet" href="([^"\s]+)">\s*<link rel="stylesheet" href="([^"\s]+)">)/g)) {
295
+ const key = `${match[1]}\n${match[2]}`;
296
+ const pages = adjacent.get(key) ?? /* @__PURE__ */ new Set();
297
+ pages.add(file);
298
+ adjacent.set(key, pages);
299
+ }
300
+ for (const [file, source] of documents) {
301
+ let html = source;
302
+ const runs = [...source.matchAll(/(?:<link rel="stylesheet" href="\/_astro\/[^"/]+\.css">\s*){2,}/g)];
303
+ for (const run of runs) {
304
+ const links = [...run[0].matchAll(/<link rel="stylesheet" href="([^"\s]+)">\s*/g)];
305
+ let replacement = "", group = [];
306
+ async function flush() {
307
+ if (group.length > 1) {
308
+ const code = (await Promise.all(group.map((link) => readFile(resolve(root, "." + link[1]), "utf8")))).join("\n");
309
+ replacement += code.includes("@import") ? group.map((link) => link[0]).join("") : `<link rel="stylesheet" href="${await emit(code)}">`;
310
+ } else replacement += group.map((link) => link[0]).join("");
311
+ group = [];
312
+ }
313
+ for (const link of links) {
314
+ if (boundaries.has(link[1])) {
315
+ await flush();
316
+ replacement += link[0];
317
+ continue;
318
+ }
319
+ if (group.length && (signatures.get(group[0][1]) !== signatures.get(link[1]) || adjacent.get(`${group.at(-1)[1]}\n${link[1]}`)?.size !== users.get(link[1])?.length)) await flush();
320
+ group.push(link);
321
+ }
322
+ await flush();
323
+ html = html.replace(run[0], replacement);
324
+ }
325
+ await writeFile(file, html);
326
+ }
327
+ }
177
328
  // src/docs-shell/remark/slug-and-toc.js
178
329
  function remarkSlugAndTOC() {
179
330
  return (tree) => {
180
- const slugs = new Slugger();
331
+ const resolveHeading = createHeadingResolver();
181
332
  const toc = [];
182
333
  visit(tree, "heading", (node) => {
183
334
  const depth = node.depth;
184
- if (depth !== 2 && depth !== 3) return;
335
+ if (depth < 2 || depth > 6) return;
185
336
  const title = node.children.filter((n) => n.type === "text" || n.type === "inlineCode").map((n) => n.value).join(" ").trim();
186
- const resolved = resolveHeading(title, slugs.slug(title));
337
+ const resolved = resolveHeading(title, depth);
187
338
  const lastText = node.children.findLast((child) => child.type === "text");
188
339
  if (lastText) lastText.value = lastText.value.replace(/\s+\{#[\w-]+\}$/, "");
189
340
  node.data ??= {};
190
341
  node.data.hProperties ??= {};
191
342
  node.data.id = resolved.id;
192
343
  node.data.hProperties.id = resolved.id;
193
- toc.push({
344
+ if (depth === 2 || depth === 3) toc.push({
194
345
  id: resolved.id,
195
346
  title: resolved.title,
196
347
  level: depth
@@ -301,8 +452,38 @@ function fenceProps(node) {
301
452
  if (!props.lang) props.lang = node.lang ?? "";
302
453
  return props;
303
454
  }
304
- const componentsExport = `import { markdownComponents as __markdownComponents } from '@master/internal-site/components/markdown-components'
305
- export const components = __markdownComponents`;
455
+ const componentPaths = {
456
+ a: "markdown/Anchor.astro",
457
+ h2: "markdown/Heading2.astro",
458
+ h3: "markdown/Heading3.astro",
459
+ img: "Image.svelte",
460
+ Image: "Image.svelte",
461
+ table: "markdown/Table.astro",
462
+ Code: "Code.astro",
463
+ InlineCode: "InlineCode.astro",
464
+ PMAction: "PMAction.astro",
465
+ Do: "Do.astro",
466
+ DoNot: "DoNot.astro",
467
+ Info: "Info.astro",
468
+ Warn: "Warn.astro",
469
+ InteractingIndicator: "InteractingIndicator.astro"
470
+ };
471
+ function documentComponents(tree) {
472
+ const used = /* @__PURE__ */ new Set();
473
+ visit(tree, (node) => {
474
+ if (node.type === "link" || node.type === "linkReference") used.add("a");
475
+ if (node.type === "image" || node.type === "imageReference") used.add("img");
476
+ if (node.type === "table") used.add("table");
477
+ if (node.type === "heading" && [2, 3].includes(node.depth)) used.add(`h${node.depth}`);
478
+ if (["mdxJsxFlowElement", "mdxJsxTextElement"].includes(node.type) && node.name in componentPaths) used.add(node.name);
479
+ });
480
+ const entries = [...used].sort().map((name, index) => ({
481
+ name,
482
+ local: `__masterComponent${index}`,
483
+ path: componentPaths[name]
484
+ }));
485
+ return entries.map(({ local, path }) => `import ${local} from '@master/internal-site/components/${path}'`).join("\n") + `\nexport const components = {${entries.map(({ name, local }) => `${name}: ${local}`).join(", ")}}`;
486
+ }
306
487
  /**
307
488
  * Markdown constructs that render through site components: fences become `Code` (or a
308
489
  * package-manager `PMAction`), inline code becomes `InlineCode`, and marked paragraphs become
@@ -310,14 +491,6 @@ export const components = __markdownComponents`;
310
491
  */
311
492
  function remarkMarkdownComponents() {
312
493
  return (tree) => {
313
- tree.children.unshift({
314
- type: "mdxjsEsm",
315
- value: componentsExport,
316
- data: { estree: parse(componentsExport, {
317
- ecmaVersion: "latest",
318
- sourceType: "module"
319
- }) }
320
- });
321
494
  visit(tree, (node, index, parent) => {
322
495
  if (!parent || index === void 0) return;
323
496
  if (node.type === "code") {
@@ -352,6 +525,15 @@ function remarkMarkdownComponents() {
352
525
  return index;
353
526
  }
354
527
  });
528
+ const source = documentComponents(tree);
529
+ tree.children.unshift({
530
+ type: "mdxjsEsm",
531
+ value: source,
532
+ data: { estree: parse(source, {
533
+ ecmaVersion: "latest",
534
+ sourceType: "module"
535
+ }) }
536
+ });
355
537
  };
356
538
  }
357
539
  // src/astro/index.ts
@@ -364,7 +546,8 @@ function masterSite(site) {
364
546
  return {
365
547
  name: "@master/internal-site",
366
548
  hooks: {
367
- "astro:config:setup": async ({ config, updateConfig, addRenderer, injectRoute }) => {
549
+ "astro:config:setup": async ({ config, command, updateConfig, addRenderer, injectRoute }) => {
550
+ if (command === "build") await validateSiteTranslations(site.i18n);
368
551
  const root = fileURLToPath(config.root);
369
552
  const contentRelative = relative(root, fileURLToPath(site.content)).split(sep).join("/");
370
553
  if (contentRelative.startsWith("..")) throw new Error("Site content must be inside the Astro project root");
@@ -382,7 +565,8 @@ function masterSite(site) {
382
565
  "virtual:master-site/config": `export default ${JSON.stringify(site)}`,
383
566
  "virtual:master-site/app": moduleImport(site.app),
384
567
  "virtual:master-site/highlight": moduleImport(site.highlighter ?? new URL("../docs-shell/utils/highlight-code-core.ts", new URL("../src/astro/index.ts", import.meta.url).href)),
385
- "virtual:master-site/translations": `export default import.meta.glob(${JSON.stringify(`${contentPrefix}/**/content.*.mdx`)})`
568
+ "virtual:master-site/translations": `export default {${Object.entries(prepared.localizedDocuments).map(([pathname, documents]) => `${JSON.stringify(pathname)}: {${Object.entries(documents).map(([locale, url]) => `${JSON.stringify(locale)}: ${propagated(url)}`).join(",")}}`).join(",")}}`,
569
+ "virtual:master-site/localized-metadata": `export default ${JSON.stringify(prepared.localizedMetadata)}`
386
570
  };
387
571
  const plugin = {
388
572
  name: "@master/internal-site:modules",
@@ -420,10 +604,10 @@ function masterSite(site) {
420
604
  server.restart();
421
605
  }, 100);
422
606
  };
423
- server.watcher.on("add", refresh).on("unlink", refresh);
607
+ server.watcher.on("add", refresh).on("unlink", refresh).on("change", refresh);
424
608
  server.httpServer?.once("close", () => {
425
609
  clearTimeout(restart);
426
- server.watcher.off("add", refresh).off("unlink", refresh);
610
+ server.watcher.off("add", refresh).off("unlink", refresh).off("change", refresh);
427
611
  });
428
612
  server.middlewares.use((request, response, next) => {
429
613
  let pathname;
@@ -479,6 +663,7 @@ function masterSite(site) {
479
663
  vite: {
480
664
  plugins: [
481
665
  plugin,
666
+ localizedMdx(site.i18n),
482
667
  sourceURL(),
483
668
  serverStyles([root, packageSource]),
484
669
  svgMarkup()
@@ -503,6 +688,7 @@ function masterSite(site) {
503
688
  recursive: true,
504
689
  force: false
505
690
  });
691
+ await compactStyleAssets(fileURLToPath(destination));
506
692
  }
507
693
  }
508
694
  };
package/dist/config.d.mts CHANGED
@@ -31,10 +31,16 @@ export interface SiteI18nConfig {
31
31
  hreflangOfLocale: Record<string, string>;
32
32
  nameOfLocale: Record<string, string>;
33
33
  localizablePathnameRoots: string[];
34
+ localizableOrigins?: Record<string, readonly string[]>;
34
35
  translations: Record<string, Record<string, string>>;
35
36
  sectionLocales?: Record<string, readonly string[]>;
36
37
  /** These sections emit translated URLs only when translated MDX exists. */
37
38
  translatedOnly?: readonly string[];
39
+ /** Build-only resource loader and version-controlled explicit translation reviews. */
40
+ validation?: {
41
+ reviews: URL;
42
+ resources: URL;
43
+ };
38
44
  }
39
45
  export interface DocumentConfig {
40
46
  layout: URL;