@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
@@ -3,6 +3,7 @@ import { mergeAssets, type PageAssets, type PropagatedModule } from '@master/int
3
3
  import { createStaticPaths, directoriesOf, localesOf, sectionOf, type ParamsModule, type RouteProps, type Section } from '@master/internal-site/astro/route-paths'
4
4
  import type { Metadata } from '@master/internal-site/types/Metadata'
5
5
  import type { Props } from '@master/internal-site/types/Props'
6
+ import { loadLocalizedContent } from '@master/internal-site/factories/create-page'
6
7
 
7
8
  export { localizePathname, sectionOf } from '@master/internal-site/astro/route-paths'
8
9
  export type { RouteProps, Section } from '@master/internal-site/astro/route-paths'
@@ -18,6 +19,7 @@ interface PageModule {
18
19
  metadata?: Partial<Metadata>
19
20
  generateMetadata?: (props: Pick<Props, 'params'>) => Promise<Partial<Metadata>> | Partial<Metadata>
20
21
  /** `false` for pages that render no document surfaces, code or prose, such as indexes. */
22
+ /** Article typography only; feature components own their CSS dependencies. */
21
23
  documentStyles?: boolean
22
24
  }
23
25
 
@@ -76,6 +78,8 @@ export async function loadRoute({ file, locale, params }: RouteProps): Promise<L
76
78
  const metadata = (module.generateMetadata ? await module.generateMetadata(props) : module.metadata) ?? {}
77
79
  const layers = [module.default]
78
80
  const propagated: PropagatedModule<unknown>[] = [page.propagated]
81
+ const localized = await loadLocalizedContent(locale, metadata)
82
+ if (localized) { await localized.getMod(); propagated.push(localized) }
79
83
  const directories = directoriesOf(file, contentRoot)
80
84
  for (let depth = directories.length; depth > 0; depth--) {
81
85
  const layoutFile = `${contentRoot}${directories.slice(0, depth).join('/')}/layout.astro`
@@ -1,5 +1,6 @@
1
1
  import config from 'virtual:master-site/config'
2
2
  import { createSite, type Site, type SiteContextValue } from '../docs-shell/site'
3
+ import sharedTranslations from '../docs-shell/common/translations'
3
4
 
4
5
  export function localeOfPathname(pathname: string): string {
5
6
  const segment = pathname.split('/')[1]
@@ -18,10 +19,17 @@ export function canonicalPathname(pathname: string) {
18
19
  export function siteContextOf(url: URL): SiteContextValue {
19
20
  const pathname = canonicalPathname(url.pathname)
20
21
  const locale = localeOfPathname(pathname)
21
- const { translations, ...i18n } = config.i18n
22
- return { locale, pathname, translations: translations[locale] ?? {}, i18n }
22
+ const { translations, defaultLocale, locales, hreflangOfLocale, nameOfLocale, localizablePathnameRoots, localizableOrigins } = config.i18n
23
+ // Only public navigation settings belong in the serialized island context.
24
+ const i18n = { defaultLocale, locales, hreflangOfLocale, nameOfLocale, localizablePathnameRoots, localizableOrigins }
25
+ return { locale, pathname, translations: { ...sharedTranslations[locale], ...translations[locale] }, i18n }
23
26
  }
24
27
 
25
28
  export function siteOf(astro: { url: URL }): Site {
26
29
  return createSite(siteContextOf(astro.url))
27
30
  }
31
+
32
+ /** Request-local authored figure captions and other article-specific copy. */
33
+ export function documentCopyOf(astro: { locals: object }): Record<string, any> {
34
+ return (astro.locals as Record<string, any>).masterDocument ?? {}
35
+ }
@@ -0,0 +1,107 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { readdir, readFile, writeFile } from 'node:fs/promises'
3
+ import { dirname, resolve } from 'node:path'
4
+
5
+ /** Collapse only the compiler's unconditional, same-directory asset links.
6
+ * This is concatenation of already compiled CSS, not a second CSS compiler.
7
+ * Keep conditions, layers, URLs, repeated occurrences and unrelated imports intact.
8
+ */
9
+ export async function inlineGeneratedStyles(file: string, ancestors: string[] = []): Promise<string> {
10
+ if (ancestors.includes(file)) throw new Error(`Cyclic generated stylesheet: ${file}`)
11
+ const source = await readFile(file, 'utf8')
12
+ let code = '', cursor = 0
13
+ // Generated imports form a leading prologue. Never search declarations or
14
+ // comments for import-looking text (for example a teaching sample's content).
15
+ while (cursor < source.length) {
16
+ const rest = source.slice(cursor)
17
+ const trivia = /^(?:\s+|\/\*[\s\S]*?\*\/|@layer\s+[\w,\s-]+;)/.exec(rest)
18
+ if (trivia) { code += trivia[0]; cursor += trivia[0].length; continue }
19
+ const match = /^@import\s+["'](\.\/master-css-[\w-]+\.css)["']\s*;/.exec(rest)
20
+ if (!match) break
21
+ code += await inlineGeneratedStyles(resolve(dirname(file), match[1]), [...ancestors, file]) + '\n'
22
+ cursor += match[0].length
23
+ }
24
+ const expanded = code + source.slice(cursor)
25
+ // A remaining import would become invalid after an inlined declaration.
26
+ // Retain this entry intact, including its original import prologue. A match
27
+ // inside a comment/string merely skips this optional optimization.
28
+ return /@(?:import|charset|namespace)\b/.test(expanded) ? source : expanded
29
+ }
30
+
31
+ /** Preserve HTML link order and each reusable entry's cache identity. Originals
32
+ * remain available to any JS/CSS references; no client loading logic is added. */
33
+ export async function compactStyleAssets(output: string) {
34
+ const root = resolve(output), replacements = new Map<string, string>()
35
+ const documents = new Map<string, string>(), users = new Map<string, string[]>()
36
+ const boundaries = new Set<string>()
37
+ async function emit(code: string) {
38
+ const hash = createHash('sha256').update(code).digest('hex').slice(0, 20)
39
+ const href = `/_astro/site-style-${hash}.css`
40
+ await writeFile(resolve(root, '.' + href), code)
41
+ return href
42
+ }
43
+ async function walk(directory: string): Promise<void> {
44
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
45
+ const file = resolve(directory, entry.name)
46
+ if (entry.isDirectory()) { await walk(file); continue }
47
+ if (!entry.name.endsWith('.html')) continue
48
+ let html = await readFile(file, 'utf8')
49
+ const links = [...html.matchAll(/<link\b(?=[^>]*\brel="stylesheet")(?=[^>]*\bhref="(\/_astro\/[^"/]+\.css)")[^>]*>/g)]
50
+ for (const match of links) {
51
+ const href = match[1]
52
+ if (!replacements.has(href)) {
53
+ const input = resolve(root, '.' + href)
54
+ const source = await readFile(input, 'utf8')
55
+ const code = await inlineGeneratedStyles(input)
56
+ const next = source === code ? href : await emit(code)
57
+ replacements.set(href, next)
58
+ }
59
+ const next = replacements.get(href)!
60
+ if (/\/(site|interface|documents)\./.test(href)) boundaries.add(next)
61
+ const pages = users.get(next) ?? []
62
+ if (!pages.includes(file)) pages.push(file)
63
+ users.set(next, pages)
64
+ html = html.replace(match[0], match[0].replace(`href="${href}"`, `href="${next}"`))
65
+ }
66
+ documents.set(file, html)
67
+ }
68
+ }
69
+ await walk(root)
70
+ // Co-delivered feature assets share a cache boundary. Merge only adjacent,
71
+ // plain links with identical route usage; never reorder a rule or pull a
72
+ // feature onto a route that did not already load it.
73
+ const signatures = new Map([...users].map(([href, pages]) => [href, pages.sort().join('\n')]))
74
+ const adjacent = new Map<string, Set<string>>()
75
+ for (const [file, html] of documents) {
76
+ for (const match of html.matchAll(/(?=<link rel="stylesheet" href="([^"\s]+)">\s*<link rel="stylesheet" href="([^"\s]+)">)/g)) {
77
+ const key = `${match[1]}\n${match[2]}`
78
+ const pages = adjacent.get(key) ?? new Set<string>()
79
+ pages.add(file); adjacent.set(key, pages)
80
+ }
81
+ }
82
+ for (const [file, source] of documents) {
83
+ let html = source
84
+ const runs = [...source.matchAll(/(?:<link rel="stylesheet" href="\/_astro\/[^"/]+\.css">\s*){2,}/g)]
85
+ for (const run of runs) {
86
+ const links = [...run[0].matchAll(/<link rel="stylesheet" href="([^"\s]+)">\s*/g)]
87
+ let replacement = '', group: typeof links = []
88
+ async function flush() {
89
+ if (group.length > 1) {
90
+ const code = (await Promise.all(group.map(link => readFile(resolve(root, '.' + link[1]), 'utf8')))).join('\n')
91
+ // An unresolved import must stay at the start of its own stylesheet.
92
+ replacement += code.includes('@import') ? group.map(link => link[0]).join('') : `<link rel="stylesheet" href="${await emit(code)}">`
93
+ } else replacement += group.map(link => link[0]).join('')
94
+ group = []
95
+ }
96
+ for (const link of links) {
97
+ if (boundaries.has(link[1])) { await flush(); replacement += link[0]; continue }
98
+ if (group.length && (signatures.get(group[0][1]) !== signatures.get(link[1])
99
+ || adjacent.get(`${group.at(-1)![1]}\n${link[1]}`)?.size !== users.get(link[1])?.length)) await flush()
100
+ group.push(link)
101
+ }
102
+ await flush()
103
+ html = html.replace(run[0], replacement)
104
+ }
105
+ await writeFile(file, html)
106
+ }
107
+ }
@@ -1,5 +1,6 @@
1
1
  ---
2
- import '@master/internal-site/styles/system/data.css'
2
+ import '@master/internal-site/styles/system/chart.css'
3
+ import '@master/internal-site/styles/system/metrics.css'
3
4
  import '@master/internal-site/styles/benchmarks.css'
4
5
  import { siteOf } from '@master/internal-site/astro/site-context'
5
6
  import { uniqueId } from '@master/internal-site/utils/unique-id'
@@ -27,7 +28,7 @@ const resolvedMax = max ?? Math.max(0, ...items.map((item) => item.max ?? item.v
27
28
  {item.detail && <small>{t(item.detail)}</small>}
28
29
  </span>
29
30
  <div class="ui-bar" role="meter" aria-labelledby={`${labelId} ${valueId}`} aria-valuemin={0} aria-valuemax={itemMax} aria-valuenow={item.value}>
30
- <i data-tone={item.color === 'neutral' ? 'muted' : undefined} style={{ width: `${clampPercent(item.value, itemMax)}%` }} />
31
+ <i data-tone={item.color === 'neutral' ? 'muted' : undefined} style={{ width: `${clampPercent(item.value, itemMax)}%`, background: item.fill }} />
31
32
  </div>
32
33
  <output id={valueId}>{valueLabel}</output>
33
34
  </div>
@@ -1,5 +1,5 @@
1
1
  ---
2
- import '@master/internal-site/styles/system/data.css'
2
+ import '@master/internal-site/styles/system/chart.css'
3
3
  import '@master/internal-site/styles/benchmarks.css'
4
4
  import BenchmarkBarRows from '@master/internal-site/benchmarks/BenchmarkBarRows.astro'
5
5
  import type { BenchmarkBarsProps } from '@master/internal-site/benchmarks/types.ts'
@@ -1,4 +1,7 @@
1
1
  ---
2
+ import { siteOf } from '@master/internal-site/astro/site-context'
3
+ import { localizeBenchmarkChart } from './localization'
4
+ import '@master/internal-site/styles/system/table.css'
2
5
  import ExpandContent from '@master/internal-site/components/ExpandContent.svelte'
3
6
  import BenchmarkBars from '@master/internal-site/benchmarks/BenchmarkBars.astro'
4
7
  import BenchmarkMetricTable from '@master/internal-site/benchmarks/BenchmarkMetricTable.astro'
@@ -10,7 +13,8 @@ interface Props {
10
13
  }
11
14
 
12
15
  /** Renders an authored chart model; `utils/benchmark-content` exports the same model as Markdown. */
13
- const { chart } = Astro.props
16
+ const { t } = siteOf(Astro)
17
+ const chart = localizeBenchmarkChart(Astro.props.chart, t)
14
18
  ---
15
19
 
16
20
  {chart.kind === 'bars' && <BenchmarkBars items={chart.items} max={chart.max} unit={chart.unit} valueFormatter={chart.valueFormatter} />}
@@ -1,5 +1,5 @@
1
1
  ---
2
- import '@master/internal-site/styles/system/data.css'
2
+ import '@master/internal-site/styles/system/chart.css'
3
3
  import { siteOf } from '@master/internal-site/astro/site-context'
4
4
  import BenchmarkBarRows from '@master/internal-site/benchmarks/BenchmarkBarRows.astro'
5
5
  import type { BenchmarkBarItem } from '@master/internal-site/benchmarks/types.ts'
@@ -1,5 +1,5 @@
1
1
  ---
2
- import '@master/internal-site/styles/system/data.css'
2
+ import '@master/internal-site/styles/system/disclosure.css'
3
3
  import '@master/internal-site/styles/benchmarks.css'
4
4
  import { siteOf } from '@master/internal-site/astro/site-context'
5
5
  import BenchmarkScrollRegion from '@master/internal-site/benchmarks/BenchmarkScrollRegion.svelte'
@@ -1,5 +1,6 @@
1
1
  ---
2
- import '@master/internal-site/styles/system/data.css'
2
+ import '@master/internal-site/styles/system/tray.css'
3
+ import '@master/internal-site/styles/system/tray-code.css'
3
4
  import { siteOf } from '@master/internal-site/astro/site-context'
4
5
  import RenderNode from '@master/internal-site/components/RenderNode.astro'
5
6
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- import '@master/internal-site/styles/system/data.css'
2
+ import '@master/internal-site/styles/system/table.css'
3
3
  import { siteOf } from '@master/internal-site/astro/site-context'
4
4
  import BenchmarkStatus from '@master/internal-site/benchmarks/BenchmarkStatus.astro'
5
5
  import type { BenchmarkMetric } from '@master/internal-site/benchmarks/types.ts'
@@ -16,7 +16,7 @@ const hasDetails = metrics.some((metric) => metric.detail)
16
16
  ---
17
17
 
18
18
  {/* Preserve keyboard access to wide metric tables. */}
19
- <div class="ui-table" role="region" aria-label={label} tabindex="0">
19
+ <div class="ui-table" role="region" aria-label={t(label)} tabindex="0">
20
20
  <table>
21
21
  <thead>
22
22
  <tr>
@@ -1,5 +1,5 @@
1
1
  ---
2
- import '@master/internal-site/styles/system/data.css'
2
+ import '@master/internal-site/styles/system/metrics.css'
3
3
  import { siteOf } from '@master/internal-site/astro/site-context'
4
4
  import BenchmarkStatus from '@master/internal-site/benchmarks/BenchmarkStatus.astro'
5
5
  import type { BenchmarkMetric } from '@master/internal-site/benchmarks/types.ts'
@@ -1,5 +1,5 @@
1
1
  ---
2
- import '@master/internal-site/styles/system/data.css'
2
+ import '@master/internal-site/styles/system/metrics.css'
3
3
  import { siteOf } from '@master/internal-site/astro/site-context'
4
4
  import type { BenchmarkSampleSummaryStats } from '@master/internal-site/benchmarks/types.ts'
5
5
  import { formatMetricValue } from '@master/internal-site/benchmarks/utils.ts'
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
- import '@master/internal-site/styles/system/data.css'
2
+ import '@master/internal-site/styles/system/table.css'
3
+
3
4
  import type { Snippet } from 'svelte'
4
5
 
5
6
  let { title, children }: { title: string; children?: Snippet } = $props()
@@ -1,5 +1,5 @@
1
1
  ---
2
- import '@master/internal-site/styles/system/data.css'
2
+ import '@master/internal-site/styles/system/source.css'
3
3
  import { siteOf } from '@master/internal-site/astro/site-context'
4
4
 
5
5
  interface Props {
@@ -1,5 +1,6 @@
1
1
  ---
2
- import '@master/internal-site/styles/system/data.css'
2
+ import '@master/internal-site/styles/system/chart.css'
3
+ import '@master/internal-site/styles/system/metrics.css'
3
4
  import '@master/internal-site/styles/system/specimens.css'
4
5
  import '@master/internal-site/styles/benchmarks.css'
5
6
  import { siteOf } from '@master/internal-site/astro/site-context'
@@ -34,11 +35,11 @@ const labelPrefix = uniqueId(Astro, 'benchmark-stacked-bars')
34
35
  <div class="ui-bar" data-variant="stacked" role="img" aria-labelledby={`${labelId} ${valueId}`}>
35
36
  {item.segments.map((segment, segmentIndex) => <i
36
37
  data-tone={benchmarkTone(segment.color ?? benchmarkColors[segmentIndex % benchmarkColors.length])}
37
- title={`${segment.label}: ${segment.valueLabel ?? formatMetricValue(segment.value, unit)}`}
38
+ title={`${t(segment.label)}: ${segment.valueLabel ?? formatMetricValue(segment.value, unit)}`}
38
39
  style={{ width: `${clampPercent(segment.value, max ?? total)}%` }} />)}
39
40
  </div>
40
41
  <output id={valueId}>{totalLabel}</output>
41
- <ul class="ui-legend" role="group" aria-label={`${item.label} breakdown`}>
42
+ <ul class="ui-legend" role="group" aria-label={t('{label} breakdown').replace('{label}', t(item.label))}>
42
43
  {item.segments.map((segment, segmentIndex) => {
43
44
  const valueLabel = segment.valueLabel ?? (valueFormatter ? valueFormatter(segment.value) : formatMetricValue(segment.value, unit))
44
45
  return <li>
@@ -1,4 +1,6 @@
1
1
  ---
2
+ import { siteOf } from '@master/internal-site/astro/site-context'
3
+ const { t } = siteOf(Astro)
2
4
  import '@master/internal-site/styles/system/specimens.css'
3
5
  import type { BenchmarkTone } from '@master/internal-site/benchmarks/types.ts'
4
6
  import { benchmarkStatusTones } from '@master/internal-site/benchmarks/utils.ts'
@@ -13,4 +15,4 @@ const names: Record<Exclude<BenchmarkTone, 'neutral'>, string> = { good: 'Good',
13
15
  const { tone } = Astro.props
14
16
  ---
15
17
 
16
- {tone && tone !== 'neutral' && <span class="ui-badge" data-tone={benchmarkStatusTones[tone]} data-variant="dot" data-size="sm">{names[tone]}</span>}
18
+ {tone && tone !== 'neutral' && <span class="ui-badge" data-tone={benchmarkStatusTones[tone]} data-variant="dot" data-size="sm">{t(names[tone])}</span>}
@@ -0,0 +1,22 @@
1
+ /** Reader copy only: numeric values, technical units, fixture rows and formatters stay canonical. */
2
+ const proseFields = new Set(['label', 'labelNote', 'title', 'detail', 'description', 'caption', 'headers'])
3
+
4
+ function mapCopy<T>(model: T, translate: (text: string) => string): T {
5
+ const visit = (value: unknown, field: string): unknown => {
6
+ if (typeof value === 'string') return proseFields.has(field) && value.trim() ? translate(value) : value
7
+ if (Array.isArray(value)) return value.map(item => visit(item, field))
8
+ if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, visit(item, key)]))
9
+ return value
10
+ }
11
+ return visit(model, '') as T
12
+ }
13
+
14
+ export function benchmarkCopyKeys(model: unknown): string[] {
15
+ const keys = new Set<string>()
16
+ mapCopy(model, text => { keys.add(text); return text })
17
+ return [...keys].sort()
18
+ }
19
+
20
+ export function localizeBenchmarkChart<T>(model: T, translate: (text: string) => string): T {
21
+ return mapCopy(model, translate)
22
+ }
@@ -20,6 +20,8 @@ export interface BenchmarkBarItem {
20
20
  detail?: string
21
21
  icon?: BenchmarkIcon
22
22
  color?: BenchmarkColor
23
+ /** Explicit series identity, such as a brand's CSS color. Overrides the neutral fill. */
24
+ fill?: string
23
25
  }
24
26
 
25
27
  export interface BenchmarkSegment {
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import '@master/internal-site/styles/docs-shell/page-content.css'
2
+ import '@master/internal-site/styles/system/toc.css'
3
3
  import PageContentList from '@master/internal-site/components/PageContentList.svelte'
4
4
  import PageContentProvider from '@master/internal-site/components/PageContentProvider.svelte'
5
5
  import type { PageContentItem } from '@master/internal-site/components/page-content.svelte'
@@ -1,11 +1,10 @@
1
1
  ---
2
- import dayjs from 'dayjs'
2
+ import { formatDate } from '../../docs-shell/common/locales'
3
3
  import IconArrowLeft from '@tabler/icons-svelte-runes/icons/arrow-left'
4
4
  import IconArrowRight from '@tabler/icons-svelte-runes/icons/arrow-right'
5
5
  import Link from '@master/internal-site/components/Link.svelte'
6
6
  import { siteOf } from '@master/internal-site/astro/site-context'
7
7
  import TitleLines from './title-lines.astro'
8
- import { sourceLanguageOf } from './language'
9
8
  import type { BlogArticleNeighbor } from './neighbors'
10
9
 
11
10
  interface Props {
@@ -20,14 +19,12 @@ interface Props {
20
19
  * article as quiet links.
21
20
  */
22
21
  const { newer, older, showCategory = true } = Astro.props
23
- const { t: $, i18n, locale } = siteOf(Astro)
24
- const tw = locale === 'tw'
25
- const languageOf = (text?: string) => sourceLanguageOf(text, { ...i18n, locale })
22
+ const { t: $, locale } = siteOf(Astro)
26
23
  // Readers who finish a post usually continue backwards through the archive; the newest post leads with the one before it.
27
24
  const lead = older ?? newer
28
25
  const other = lead === older ? newer : undefined
29
26
  // Written out in full, as on the post's own header and on the index's featured sheet.
30
- const date = (value: string) => dayjs(value).format(tw ? 'YYYY 年 M 月 D 日' : 'MMMM D, YYYY')
27
+ const date = (value: string) => formatDate(value, locale)
31
28
  ---
32
29
 
33
30
  <nav class="blog-article-end blog-rails blog-rails-rule" aria-labelledby="blog-article-end-title">
@@ -38,8 +35,8 @@ const date = (value: string) => dayjs(value).format(tw ? 'YYYY 年 M 月 D 日'
38
35
  {showCategory && lead.category && <span>{$(lead.category)}</span>}
39
36
  {lead.date && <time datetime={lead.date}>{date(lead.date)}</time>}
40
37
  </span>
41
- <span class="blog-article-next-title" lang={languageOf(lead.title)}><TitleLines title={$(lead.title)} class="blog-article-next-title-line" /><IconArrowRight class="blog-article-next-arrow" aria-hidden="true" size={22} stroke={1.25} /></span>
42
- {lead.description && <span class="blog-article-next-description" lang={languageOf(lead.description)}>{$(lead.description)}</span>}
38
+ <span class="blog-article-next-title"><TitleLines title={$(lead.title)} class="blog-article-next-title-line" /><IconArrowRight class="blog-article-next-arrow" aria-hidden="true" size={22} stroke={1.25} /></span>
39
+ {lead.description && <span class="blog-article-next-description">{$(lead.description)}</span>}
43
40
  </Link>
44
41
  )}
45
42
  <div class="blog-article-end-links">
@@ -47,7 +44,7 @@ const date = (value: string) => dayjs(value).format(tw ? 'YYYY 年 M 月 D 日'
47
44
  {other && (
48
45
  <Link class="blog-article-end-link" href={other.pathname}>
49
46
  <span class="blog-article-end-link-label">{$('Newer')}</span>
50
- <span class="blog-article-end-link-title" lang={languageOf(other.title)}>{$(other.title)}</span>
47
+ <span class="blog-article-end-link-title">{$(other.title)}</span>
51
48
  <IconArrowRight aria-hidden="true" size={14} stroke={1.75} />
52
49
  </Link>
53
50
  )}
@@ -1,14 +1,14 @@
1
1
  ---
2
- import dayjs from 'dayjs'
2
+ import { formatDate } from '../../docs-shell/common/locales'
3
3
  import authors from '@master/internal-site/data/authors'
4
4
  import Image from '@master/internal-site/components/Image.svelte'
5
5
  import Link from '@master/internal-site/components/Link.svelte'
6
6
  import { siteOf } from '@master/internal-site/astro/site-context'
7
7
  import TitleLines from './title-lines.astro'
8
- import { sourceLanguageOf } from './language'
8
+ import type { DefinedMetadata } from '@master/internal-site/types/Metadata'
9
9
 
10
10
  interface Props {
11
- metadata: any
11
+ metadata: DefinedMetadata
12
12
  }
13
13
 
14
14
  /**
@@ -17,11 +17,9 @@ interface Props {
17
17
  * falls on it from above and to the left.
18
18
  */
19
19
  const { metadata } = Astro.props
20
- const { t: $, i18n, locale } = siteOf(Astro)
21
- const tw = locale === 'tw'
20
+ const { t: $, locale } = siteOf(Astro)
22
21
  const source: string = metadata.title?.absolute || metadata.title
23
22
  const title: string = $(source)
24
- const languageOf = (text?: string) => sourceLanguageOf(text, { ...i18n, locale })
25
23
  const people = (metadata.authors ?? [])
26
24
  .map(({ name }: { name: string }) => authors.find((author) => author.name === name))
27
25
  .filter(Boolean) as typeof authors
@@ -31,11 +29,11 @@ const people = (metadata.authors ?? [])
31
29
  {(metadata.category || metadata.date) && (
32
30
  <p class="blog-article-meta">
33
31
  {metadata.category && <span>{$(metadata.category)}</span>}
34
- {metadata.date && <time datetime={metadata.date}>{dayjs(metadata.date).format(tw ? 'YYYY 年 M 月 D 日' : 'MMMM D, YYYY')}</time>}
32
+ {metadata.date && <time datetime={metadata.date}>{formatDate(metadata.date, locale)}</time>}
35
33
  </p>
36
34
  )}
37
- <h1 class="blog-article-title blog-stone-text" lang={languageOf(source)}><TitleLines title={title} class="blog-article-title-line" /></h1>
38
- {metadata.description && <p class="blog-article-dek" lang={languageOf(metadata.description)}>{$(metadata.description)}</p>}
35
+ <h1 class="blog-article-title blog-stone-text"><TitleLines title={title} class="blog-article-title-line" /></h1>
36
+ {metadata.description && <p class="blog-article-dek">{$(metadata.description)}</p>}
39
37
  {people.length > 0 && (
40
38
  <div class="blog-article-byline">
41
39
  <span class="blog-article-byline-label">{$('Written by')}</span>
@@ -58,4 +56,14 @@ const people = (metadata.authors ?? [])
58
56
  </ul>
59
57
  </div>
60
58
  )}
59
+ {metadata.cover && (
60
+ <img
61
+ class="blog-article-cover"
62
+ src={metadata.cover.src}
63
+ alt={metadata.cover.alt}
64
+ width={metadata.cover.width}
65
+ height={metadata.cover.height}
66
+ fetchpriority="high"
67
+ />
68
+ )}
61
69
  </header>
package/src/config.ts CHANGED
@@ -28,10 +28,13 @@ export interface SiteI18nConfig {
28
28
  hreflangOfLocale: Record<string, string>
29
29
  nameOfLocale: Record<string, string>
30
30
  localizablePathnameRoots: string[]
31
+ localizableOrigins?: Record<string, readonly string[]>
31
32
  translations: Record<string, Record<string, string>>
32
33
  sectionLocales?: Record<string, readonly string[]>
33
34
  /** These sections emit translated URLs only when translated MDX exists. */
34
35
  translatedOnly?: readonly string[]
36
+ /** Build-only resource loader and version-controlled explicit translation reviews. */
37
+ validation?: { reviews: URL; resources: URL }
35
38
  }
36
39
 
37
40
  export interface DocumentConfig {
@@ -1,5 +1,5 @@
1
1
  ---
2
- import '@master/internal-site/styles/system/data.css'
2
+ import '@master/internal-site/styles/system/table.css'
3
3
  import { siteOf } from '@master/internal-site/astro/site-context'
4
4
  import { uniqueId } from '@master/internal-site/utils/unique-id'
5
5
  import InlineCode from '@master/internal-site/components/InlineCode.astro'
@@ -1,5 +1,5 @@
1
1
  ---
2
- import '@master/internal-site/styles/system/data.css'
2
+ import '@master/internal-site/styles/system/table.css'
3
3
  import { siteOf } from '@master/internal-site/astro/site-context'
4
4
  import { uniqueId } from '@master/internal-site/utils/unique-id'
5
5
  import InlineCode from '@master/internal-site/components/InlineCode.astro'
@@ -4,7 +4,7 @@ import { configuredMarkupMarkdown } from '@master/internal-site/css/configured-e
4
4
  export function specimenFactsMarkdown(namespace: string, keys?: string[]) {
5
5
  return specimenTokens(namespace, keys).map(token => `- \`--${token.name}\`: ${token.values.map(value => `\`${value.value}\` (${value.path.join(' → ')})`).join('; ')}${tokenAdvice(namespace, token.key) ? ` — ${tokenAdvice(namespace, token.key)}` : ''}`).join('\n')
6
6
  }
7
- export function selectedSpecimensMarkdown(namespace: string, keys: string[]) {
7
+ export function selectedSpecimensMarkdown(namespace: string, keys: string[], t: (text: string) => string = text => text) {
8
8
  const scene = foundationScene(namespace, keys)
9
- return `${scene.caption}\n\n${specimenFactsMarkdown(namespace, keys)}${scene.html && namespace !== 'color' ? `\n\n${configuredMarkupMarkdown(scene.css ?? '', scene.html)}` : ''}`
9
+ return `${t(scene.caption ?? '')}\n\n${specimenFactsMarkdown(namespace, keys)}${scene.html && namespace !== 'color' ? `\n\n${configuredMarkupMarkdown(scene.css ?? '', scene.html)}` : ''}`
10
10
  }