@master/internal-site 0.1.3 → 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 (104) hide show
  1. package/README.md +37 -0
  2. package/dist/astro.mjs +92 -41
  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 +9 -5
  10. package/src/astro/localized-mdx.ts +45 -0
  11. package/src/astro/routes.template.ts +3 -0
  12. package/src/astro/site-context.ts +10 -2
  13. package/src/benchmarks/BenchmarkChart.astro +4 -1
  14. package/src/benchmarks/BenchmarkMetricTable.astro +1 -1
  15. package/src/benchmarks/BenchmarkStackedBars.astro +2 -2
  16. package/src/benchmarks/BenchmarkStatus.astro +3 -1
  17. package/src/benchmarks/localization.ts +22 -0
  18. package/src/blog/article/BlogArticleEnd.astro +6 -9
  19. package/src/blog/article/BlogArticleMasthead.astro +5 -8
  20. package/src/config.ts +3 -0
  21. package/src/css/foundation-data/specimen-content.ts +2 -2
  22. package/src/css/markdown.ts +52 -11
  23. package/src/demo/Demo.svelte +6 -4
  24. package/src/demo/DemoAsset.astro +5 -3
  25. package/src/demo/DemoAxes.svelte +4 -2
  26. package/src/demo/DemoCatalog.astro +8 -6
  27. package/src/demo/DemoContainer.svelte +5 -3
  28. package/src/demo/DemoControls.svelte +3 -1
  29. package/src/demo/DemoCopyButton.svelte +6 -4
  30. package/src/demo/DemoCopyGroup.svelte +4 -2
  31. package/src/demo/DemoExample.astro +3 -1
  32. package/src/demo/DemoFeatureSupport.svelte +3 -1
  33. package/src/demo/DemoGeneratedCSS.svelte +3 -1
  34. package/src/demo/DemoIndex.astro +5 -3
  35. package/src/demo/DemoLegend.svelte +3 -1
  36. package/src/demo/DemoMeasure.svelte +4 -2
  37. package/src/demo/DemoMedia.svelte +3 -1
  38. package/src/demo/DemoMotion.svelte +5 -3
  39. package/src/demo/DemoPaletteGroups.svelte +4 -2
  40. package/src/demo/DemoStyleComparison.astro +5 -3
  41. package/src/demo/DemoThemeComparison.astro +4 -2
  42. package/src/demo/DemoTokenTable.astro +4 -2
  43. package/src/demo/DemoViewTransition.astro +3 -1
  44. package/src/demo/DemoViewport.svelte +13 -11
  45. package/src/demo/DemoWaterfall.astro +3 -1
  46. package/src/demo/ProjectStyleExample.astro +4 -1
  47. package/src/demo/foundations/FoundationDialog.astro +5 -2
  48. package/src/demo/foundations/FoundationTokensView.astro +16 -5
  49. package/src/demo/foundations/Palette.svelte +9 -7
  50. package/src/demo/foundations/dialog-copy.d.ts +1 -0
  51. package/src/demo/foundations/dialog-copy.js +5 -0
  52. package/src/docs-shell/common/i18n.config.d.ts +1 -1
  53. package/src/docs-shell/common/i18n.config.js +4 -12
  54. package/src/docs-shell/common/locales.d.ts +8 -0
  55. package/src/docs-shell/common/locales.js +18 -0
  56. package/src/docs-shell/common/terminology.json +32 -0
  57. package/src/docs-shell/common/translations.d.ts +3 -0
  58. package/src/docs-shell/common/translations.js +294 -0
  59. package/src/docs-shell/components/ArticleHeader.astro +3 -3
  60. package/src/docs-shell/components/CodeCopyButton.astro +3 -1
  61. package/src/docs-shell/components/CodeTabBar.astro +5 -3
  62. package/src/docs-shell/components/DocHeader.svelte +3 -4
  63. package/src/docs-shell/components/DocHeading.astro +3 -1
  64. package/src/docs-shell/components/DocMenuButton.svelte +4 -5
  65. package/src/docs-shell/components/DocSidebar.svelte +1 -2
  66. package/src/docs-shell/components/DocumentCodeTable.astro +3 -1
  67. package/src/docs-shell/components/DocumentIllustration.astro +3 -1
  68. package/src/docs-shell/components/DocumentKeyList.astro +5 -3
  69. package/src/docs-shell/components/DocumentNamespaceTable.astro +4 -2
  70. package/src/docs-shell/components/DocumentOptions.astro +3 -1
  71. package/src/docs-shell/components/DocumentationIndex.svelte +6 -7
  72. package/src/docs-shell/components/GeneratedCSS.astro +3 -1
  73. package/src/docs-shell/components/Link.svelte +3 -2
  74. package/src/docs-shell/components/NotFound.astro +3 -4
  75. package/src/docs-shell/components/PageNavs.astro +2 -3
  76. package/src/docs-shell/components/SearchDialog.svelte +13 -16
  77. package/src/docs-shell/components/TimeAgo.svelte +6 -6
  78. package/src/docs-shell/contexts/search.ts +1 -1
  79. package/src/docs-shell/factories/Page.astro +10 -5
  80. package/src/docs-shell/factories/create-page.ts +33 -6
  81. package/src/docs-shell/layouts/BlogArticle.astro +3 -11
  82. package/src/docs-shell/remark/slug-and-toc.js +5 -7
  83. package/src/docs-shell/scripts/code.ts +5 -2
  84. package/src/docs-shell/site.ts +9 -1
  85. package/src/docs-shell/utils/document-copy.d.ts +2 -0
  86. package/src/docs-shell/utils/document-copy.js +18 -0
  87. package/src/docs-shell/utils/generate-metadata.ts +2 -0
  88. package/src/docs-shell/utils/i18n-pathname.ts +13 -2
  89. package/src/docs-shell/utils/i18n.ts +4 -1
  90. package/src/docs-shell/utils/localized-metadata.ts +22 -0
  91. package/src/docs-shell/utils/markdown-links.ts +61 -0
  92. package/src/docs-shell/utils/markdown-tree.ts +19 -6
  93. package/src/docs-shell/utils/resolve-heading.d.ts +1 -0
  94. package/src/docs-shell/utils/resolve-heading.js +21 -0
  95. package/src/docs-shell/utils/search-pages.ts +75 -22
  96. package/src/home/hero.css +5 -0
  97. package/src/layouts/Document.astro +1 -1
  98. package/src/scripts/localization.ts +239 -0
  99. package/src/scripts/mdx-sample.ts +12 -0
  100. package/src/scripts/static-social.ts +17 -3
  101. package/src/scripts/style-delivery.mjs +14 -4
  102. package/src/sections/guide.astro +3 -1
  103. package/src/virtual.d.ts +5 -1
  104. package/src/blog/article/language.ts +0 -29
@@ -28,6 +28,8 @@
28
28
  </script>
29
29
 
30
30
  <script lang="ts">
31
+ import { getSite } from '@master/internal-site/site'
32
+ const { t } = getSite()
31
33
  import '@master/internal-site/styles/interface.css'
32
34
  import '@master/internal-site/styles/system/browser.css'
33
35
  import '@master/internal-site/styles/system/range.css'
@@ -292,38 +294,38 @@
292
294
  {#if chrome}<div class="ui-browser-bar" aria-hidden="true"><span class="ui-browser-dots"><i></i><i></i><i></i></span></div>{/if}
293
295
  <div bind:this={body} class="ui-browser-body">
294
296
  <div class="ui-browser-viewport" style:width={width ? `${width}px` : `min(100%, ${maxWidth}px)`}>
295
- <iframe bind:this={frame} {title} srcdoc={source} {src} loading="lazy" data-ready={ready} data-sizing={sizing}
297
+ <iframe bind:this={frame} title={t(title)} srcdoc={source} {src} loading="lazy" data-ready={ready} data-sizing={sizing}
296
298
  style:height={`${sizing === 'content' ? contentHeight ?? height : height}px`} onload={load}></iframe>
297
299
  {#if responsive}<span class="ui-resize" aria-hidden="true" onpointerdown={drag}></span>{/if}
298
300
  </div>
299
301
  </div>
300
302
  </div>
301
303
  {#if responsive || motion || print || theme || Boolean(inspect?.length)}
302
- <div class="ui-browser-tools" role="group" aria-label={`${title} controls`}>
304
+ <div class="ui-browser-tools" role="group" aria-label={t('{label} controls').replace('{label}', t(title))}>
303
305
  {#if responsive}
304
- {#if widthPresets?.length}<div class="ui-segmented" role="group" aria-label={`${rangeLabel} presets`}>
306
+ {#if widthPresets?.length}<div class="ui-segmented" role="group" aria-label={t('{label} presets').replace('{label}', t(rangeLabel))}>
305
307
  {#each widthPresets as preset (preset.label)}<button type="button" disabled={!ready}
306
- aria-pressed={width === preset.width} onclick={() => { width = preset.width }}>{preset.label}</button>{/each}
308
+ aria-pressed={width === preset.width} onclick={() => { width = preset.width }}>{t(preset.label)}</button>{/each}
307
309
  </div>{/if}
308
310
  <span class="ui-browser-range">
309
- <label class="ui-label" data-case="as-is" for={widthId}>{rangeLabel}</label>
311
+ <label class="ui-label" data-case="as-is" for={widthId}>{t(rangeLabel)}</label>
310
312
  <input id={widthId} class="ui-range" disabled={!ready} type="range" min={range.min} max={range.max} step="1" value={range.value}
311
313
  style:--value={`${((range.value - range.min) / Math.max(1, range.max - range.min)) * 100}%`}
312
314
  oninput={event => { width = Number(event.currentTarget.value) }} />
313
315
  {#if ready}<output class="ui-browser-size" for={widthId}>{fitWidth} px</output>{/if}
314
316
  </span>
315
- <button type="button" disabled={!ready} class="ui-button" data-size="sm" onclick={() => { width = undefined }}>Fit</button>
317
+ <button type="button" disabled={!ready} class="ui-button" data-size="sm" onclick={() => { width = undefined }}>{t('Fit')}</button>
316
318
  {/if}
317
- {#if theme}<button type="button" disabled={!ready} class="ui-button" data-size="sm" aria-pressed={isDark} title="Toggle dark theme" onclick={() => { dark = !isDark }}>
318
- {#if isDark}<IconMoon aria-hidden="true" />{:else}<IconSun aria-hidden="true" />{/if}Theme
319
+ {#if theme}<button type="button" disabled={!ready} class="ui-button" data-size="sm" aria-pressed={isDark} title={t('Toggle dark theme')} onclick={() => { dark = !isDark }}>
320
+ {#if isDark}<IconMoon aria-hidden="true" />{:else}<IconSun aria-hidden="true" />{/if}{t('Theme')}
319
321
  </button>{/if}
320
322
  {#if motion}
321
323
  <button type="button" disabled={!ready} class="ui-button" data-size="sm" onclick={() => animate(playing ? 'pause' : 'play')}>
322
- {#if playing}<IconPlayerPause aria-hidden="true" />{:else}<IconPlayerPlay aria-hidden="true" />{/if}{playing ? 'Pause' : 'Play'}
324
+ {#if playing}<IconPlayerPause aria-hidden="true" />{:else}<IconPlayerPlay aria-hidden="true" />{/if}{playing ? t('Pause') : t('Play')}
323
325
  </button>
324
- <button type="button" disabled={!ready} class="ui-button" data-size="sm" data-variant="quiet" onclick={() => animate('restart')}><IconRefresh aria-hidden="true" />Replay</button>
326
+ <button type="button" disabled={!ready} class="ui-button" data-size="sm" data-variant="quiet" onclick={() => animate('restart')}><IconRefresh aria-hidden="true" />{t('Replay')}</button>
325
327
  {/if}
326
- {#if print}<button type="button" disabled={!ready} class="ui-button" data-size="sm" onclick={() => frame?.contentWindow?.print()}><IconPrinter aria-hidden="true" />Print preview</button>{/if}
328
+ {#if print}<button type="button" disabled={!ready} class="ui-button" data-size="sm" onclick={() => frame?.contentWindow?.print()}><IconPrinter aria-hidden="true" />{t('Print preview')}</button>{/if}
327
329
  {#if properties}<output aria-live="polite">{properties}</output>{/if}
328
330
  </div>
329
331
  {/if}
@@ -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/waterfall.css'
3
5
  import Demo from '@master/internal-site/demo/Demo.svelte'
4
6
  import type { DemoWaterfallProps } from '@master/internal-site/demo/demo-waterfall.ts'
@@ -15,7 +17,7 @@ for (const { label, start, end } of rows) {
15
17
 
16
18
  <Demo title={title} description={description} caption={caption} padding="sm" aria-label={title}>
17
19
  <div class="w-full ui-waterfall">
18
- <div class="ui-waterfall-axis" aria-hidden="true"><span /><div class="relative"><span>Earlier</span>{marker && <span class="absolute font-medium fg-text-strong (translate:-50%)" style={{ left: `${marker.position}%` }}>{marker.label}</span>}<span>Later</span></div></div>
20
+ <div class="ui-waterfall-axis" aria-hidden="true"><span /><div class="relative"><span>{t("Earlier")}</span>{marker && <span class="absolute font-medium fg-text-strong (translate:-50%)" style={{ left: `${marker.position}%` }}>{marker.label}</span>}<span>{t("Later")}</span></div></div>
19
21
  {/* Preserve WebKit list semantics without list markers. */}
20
22
  <ul role="list" class="grid gap-2xs m(0) p(0) (list-style:none)">
21
23
  {rows.map(({ label, start, end, tone = 'blue' }) => <li class="ui-waterfall-row">
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  import DemoConfiguredExample from '@master/internal-site/demo/DemoConfiguredExample.astro'
3
3
  import { projectStyleExample, type ProjectStyleName } from '@master/internal-site/demo/project-style-examples.ts'
4
+ import { documentCopyOf, siteOf } from '@master/internal-site/astro/site-context'
5
+ import { demoCopy } from '@master/internal-site/utils/document-copy.js'
4
6
 
5
7
  interface Props {
6
8
  name: ProjectStyleName
@@ -8,6 +10,7 @@ interface Props {
8
10
  }
9
11
 
10
12
  const { name, code = true } = Astro.props
13
+ const example = demoCopy(documentCopyOf(Astro), siteOf(Astro).locale, `ProjectStyleExample:${name}`, projectStyleExample(name))
11
14
  ---
12
15
 
13
- <DemoConfiguredExample name={name} {...projectStyleExample(name)} code={code} />
16
+ <DemoConfiguredExample name={name} {...example} code={code} />
@@ -1,9 +1,12 @@
1
1
  ---
2
2
  import '@master/internal-site/styles/interface.css'
3
3
  import FoundationSpecimen from '@master/internal-site/demo/foundations/FoundationSpecimen.astro'
4
+ import { dialogCopy } from './dialog-copy.js'
5
+ interface Props { title?: string; caption?: string }
6
+ const { title = dialogCopy.title, caption = dialogCopy.caption } = Astro.props
4
7
  ---
5
8
 
6
- <FoundationSpecimen name="dialog-entrance" title="Motion follows a real action" scene={{ systemButtons: true, sizing: 'viewport', height: 340, css: `@theme {
9
+ <FoundationSpecimen name="dialog-entrance" title={title} scene={{ systemButtons: true, sizing: 'viewport', height: 340, css: `@theme {
7
10
  --duration-enter: 180ms;
8
11
  --easing-emphasized: cubic-bezier(.16, 1, .3, 1);
9
12
  --animate-dialog-in: dialog-in var(--duration-enter) var(--easing-emphasized) both;
@@ -12,4 +15,4 @@ import FoundationSpecimen from '@master/internal-site/demo/foundations/Foundatio
12
15
  from { translate: 0 0.5rem; opacity: 0; }
13
16
  to { translate: 0; opacity: 1; }
14
17
  }
15
- `, html: "<article class=\"p-md r-sm b(1) b-solid b-line-divider bg-surface-raised\"><h2 class=\"m(0) text-lg font-regular\">Collection settings</h2><p class=\"mt-xs mb-md fg-text-muted\">Open a native dialog to preview its entrance.</p><button id=\"open\" type=\"button\" class=\"ui-button\">Open details</button></article>\n<dialog id=\"details\" aria-labelledby=\"dialog-title\" aria-describedby=\"dialog-description\" class=\"(width:calc(100%-32px)) max-w-xs m-auto p-md r-lg b(1) b-solid b-line-divider bg-surface-floating fg-text-body shadow-xl animate-dialog-in@media(screen)@motion-safe\">\n<h2 id=\"dialog-title\" class=\"m(0) text-lg font-regular fg-text-strong\">Collection details</h2><p id=\"dialog-description\" class=\"mt-sm mb-md\">This dialog has a finite entrance. Closing it is immediate.</p><form method=\"dialog\"><button class=\"ui-button\" autofocus>Close details</button></form>\n</dialog><script>const dialog = document.getElementById('details'); const openButton = document.getElementById('open'); openButton.addEventListener('click', () => dialog.showModal()); dialog.addEventListener('close', () => openButton.focus());</script>", caption: 'The dialog enters once on opening. Escape or Close details returns focus to its trigger. Reduced motion opens it immediately. Its modal boundary stays inside this preview.' }} />
18
+ `, html: "<article class=\"p-md r-sm b(1) b-solid b-line-divider bg-surface-raised\"><h2 class=\"m(0) text-lg font-regular\">Collection settings</h2><p class=\"mt-xs mb-md fg-text-muted\">Open a native dialog to preview its entrance.</p><button id=\"open\" type=\"button\" class=\"ui-button\">Open details</button></article>\n<dialog id=\"details\" aria-labelledby=\"dialog-title\" aria-describedby=\"dialog-description\" class=\"(width:calc(100%-32px)) max-w-xs m-auto p-md r-lg b(1) b-solid b-line-divider bg-surface-floating fg-text-body shadow-xl animate-dialog-in@media(screen)@motion-safe\">\n<h2 id=\"dialog-title\" class=\"m(0) text-lg font-regular fg-text-strong\">Collection details</h2><p id=\"dialog-description\" class=\"mt-sm mb-md\">This dialog has a finite entrance. Closing it is immediate.</p><form method=\"dialog\"><button class=\"ui-button\" autofocus>Close details</button></form>\n</dialog><script>const dialog = document.getElementById('details'); const openButton = document.getElementById('open'); openButton.addEventListener('click', () => dialog.showModal()); dialog.addEventListener('close', () => openButton.focus());</script>", caption }} />
@@ -1,4 +1,6 @@
1
1
  ---
2
+ import { siteOf } from '@master/internal-site/astro/site-context.ts'
3
+ const { t } = siteOf(Astro)
2
4
  import Demo from '@master/internal-site/demo/Demo.svelte'
3
5
  import DemoViewport from '@master/internal-site/demo/DemoViewport.svelte'
4
6
  import DemoThemeComparison from '@master/internal-site/demo/DemoThemeComparison.astro'
@@ -17,6 +19,13 @@ interface Props {
17
19
  const { namespace, keys, presentation = 'reference' } = Astro.props
18
20
  const tokens = namespace === 'breakpoints' ? [] : specimenTokens(namespace, keys)
19
21
  const tokenKeys = tokens.map(token => token.key).join(' ')
22
+ const namespaceTitles: Record<string, string> = {
23
+ 'color-surface': t('Surface colors'), 'color-line': t('Line colors'), 'color-text': t('Text colors'),
24
+ shadow: t('Shadow scale'), radius: t('Corner radius scale'), container: t('Container widths'),
25
+ 'font-family': t('Font families'), 'font-size': t('Font sizes'), 'font-weight': t('Font weights'),
26
+ text: t('Text styles'), leading: t('Line heights'), tracking: t('Letter spacing'),
27
+ animate: t('Animations'), duration: t('Animation durations'), easing: t('Timing functions'),
28
+ }
20
29
 
21
30
  function tokenScene(tokens: FoundationToken[], title: string, compare = false) {
22
31
  const scene = foundationScene(namespace, tokens.map(token => token.key))
@@ -39,22 +48,24 @@ if (namespace === 'color') {
39
48
  const aliases = rest.filter(token => defaultValue(token).startsWith('var('))
40
49
  const specials = rest.filter(token => !aliases.includes(token))
41
50
  scenes = [
42
- aliases.length > 0 ? tokenScene(aliases, 'Hue aliases', true) : undefined,
43
- specials.length > 0 ? tokenScene(specials, 'Special colors', true) : undefined,
51
+ aliases.length > 0 ? tokenScene(aliases, t('Hue aliases'), true) : undefined,
52
+ specials.length > 0 ? tokenScene(specials, t('Special colors'), true) : undefined,
44
53
  ].filter(scene => scene !== undefined)
45
54
  } else if (namespace !== 'breakpoints' && namespace !== 'spacing') {
46
- const scene = tokenScene(tokens, namespace === 'shadow' ? 'Shadow scale' : namespace === 'radius' ? 'Corner radius scale' : namespace.replaceAll('-', ' '), namespace.startsWith('color-') || (namespace === 'shadow' && presentation === 'reference'))
55
+ const title = namespaceTitles[namespace]
56
+ if (!title) throw new Error(`Missing foundation title: ${namespace}`)
57
+ const scene = tokenScene(tokens, title, namespace.startsWith('color-') || (namespace === 'shadow' && presentation === 'reference'))
47
58
  scenes = scene ? [scene] : []
48
59
  }
49
60
  ---
50
61
 
51
62
  {namespace === 'breakpoints' ? <ThemeNumberVariableTable namespace="breakpoint" />
52
63
  : namespace === 'color' ? <div data-foundation-tokens={namespace} data-token-keys={tokenKeys}>
53
- {palette.length > 0 && <Demo title="Fixed color steps" padding="sm" caption={specimenCaption.color}><Palette client:idle={{ timeout: 500 }} groups={palette} /></Demo>}
64
+ {palette.length > 0 && <Demo title={t('Fixed color steps')} padding="sm" caption={specimenCaption.color}><Palette client:idle={{ timeout: 500 }} groups={palette} /></Demo>}
54
65
  {scenes.map(({ scene, title }) => <DemoThemeComparison name={`tokens-${namespace}`} title={title} html={scene.html!} css={scene.css} caption={scene.caption} />)}
55
66
  </div>
56
67
  : <div data-foundation-tokens={namespace} data-token-keys={tokenKeys}>
57
- {namespace === 'spacing' ? <><ThemeNumberVariableTable namespace="spacing" keys={keys} representation="spacing" referenceUnits={presentation !== 'guide'} /><p class="m(0) font-xs fg-text-muted">{presentation === 'guide' ? 'The striped area is the actual gap between two equal blocks.' : specimenCaption.spacing}</p></>
68
+ {namespace === 'spacing' ? <><ThemeNumberVariableTable namespace="spacing" keys={keys} representation="spacing" referenceUnits={presentation !== 'guide'} /><p class="m(0) font-xs fg-text-muted">{presentation === 'guide' ? t('The striped area is the actual gap between two equal blocks.') : t(specimenCaption.spacing)}</p></>
58
69
  : scenes.map(({ scene, title, compare }) => compare
59
70
  ? <DemoThemeComparison name={`tokens-${namespace}`} title={title} html={scene.html!} css={scene.css} caption={scene.caption} />
60
71
  : <Demo title={title} frame="none" caption={scene.caption}>
@@ -3,6 +3,8 @@
3
3
  </script>
4
4
 
5
5
  <script lang="ts">
6
+ import { getSite } from '@master/internal-site/site'
7
+ const { t } = getSite()
6
8
  import '@master/internal-site/styles/system/fields.css'
7
9
 
8
10
  import '@master/internal-site/styles/system/specimens.css'
@@ -17,23 +19,23 @@
17
19
  const endId = `${id}-end`
18
20
  </script>
19
21
 
20
- <div class="w-full"><DemoCopyGroup instruction={format === 'value' ? 'Choose a swatch to copy its original CSS color value.' : 'Choose a swatch to copy its CSS variable reference.'}>
21
- <label class="w-fit ui-field" for={id}>Copy as
22
+ <div class="w-full"><DemoCopyGroup instruction={format === 'value' ? t('Choose a swatch to copy its original CSS color value.') : t('Choose a swatch to copy its CSS variable reference.')}>
23
+ <label class="w-fit ui-field" for={id}>{t('Copy as')}
22
24
  <select {id} class="ui-select" data-variant="raised" bind:value={format}>
23
- <option value="value">CSS value</option><option value="variable">CSS variable</option>
25
+ <option value="value">{t('CSS value')}</option><option value="variable">{t('CSS variable')}</option>
24
26
  </select>
25
27
  </label>
26
- <a class="ui-skip" href={`#${endId}`}>Skip color swatches</a>
28
+ <a class="ui-skip" href={`#${endId}`}>{t('Skip color swatches')}</a>
27
29
  <div class="ui-palette">
28
- {#each groups as group (group.family)}<div class="ui-palette-row" role="group" aria-label={`${group.family} palette`}>
30
+ {#each groups as group (group.family)}<div class="ui-palette-row" role="group" aria-label={t('{family} palette').replace('{family}', group.family)}>
29
31
  <span>{group.family}</span>
30
32
  {#each group.colors as color (color.name)}<div class="ui-palette-cell">
31
33
  <DemoCopyButton icon={null} class="ui-palette-chip" style={`background-color:${color.value}`}
32
34
  value={format === 'value' ? color.value : `var(--${color.name})`}
33
- label={`Copy ${color.name} ${format === 'value' ? 'CSS value' : 'CSS variable'}`} title={`${color.name}: ${color.value}`} />
35
+ label={format === 'value' ? t('Copy {name} CSS value').replace('{name}', color.name) : t('Copy {name} CSS variable').replace('{name}', color.name)} title={`${color.name}: ${color.value}`} />
34
36
  <span aria-hidden="true">{color.step}</span>
35
37
  </div>{/each}
36
38
  </div>{/each}
37
39
  </div>
38
- <div id={endId} tabindex="-1"><span class="sr-only">End of color palette</span></div>
40
+ <div id={endId} tabindex="-1"><span class="sr-only">{t('End of color palette')}</span></div>
39
41
  </DemoCopyGroup></div>
@@ -0,0 +1 @@
1
+ export declare const dialogCopy: { title: string; caption: string }
@@ -0,0 +1,5 @@
1
+ /** Reader-facing copy; the executable dialog specimen stays English. */
2
+ export const dialogCopy = {
3
+ title: 'Motion follows a real action',
4
+ caption: 'The dialog enters once on opening. Escape or Close details returns focus to its trigger. Reduced motion opens it immediately. Its modal boundary stays inside this preview.'
5
+ }
@@ -1,2 +1,2 @@
1
- declare const config: { defaultLocale: string; locales: readonly string[]; hreflangOfLocale: Record<string, string>; nameOfLocale: Record<string, string> }
1
+ declare const config: { defaultLocale: string; locales: readonly string[]; hreflangOfLocale: Record<string, string>; nameOfLocale: Record<string, string>; localizableOrigins: Record<string, readonly string[]> }
2
2
  export default config
@@ -1,13 +1,5 @@
1
- const i18n = {
2
- defaultLocale: 'en',
3
- locales: ['en', 'tw'],
4
- hreflangOfLocale: {
5
- tw: 'zh-TW'
6
- },
7
- nameOfLocale: {
8
- tw: '中文 (臺灣)',
9
- en: 'English'
10
- }
11
- }
1
+ import { defaultLocale, locales, hreflangOfLocale, nameOfLocale, localizableOrigins } from './locales.js'
12
2
 
13
- export default i18n
3
+ const i18n = { defaultLocale, locales, hreflangOfLocale, nameOfLocale, localizableOrigins }
4
+
5
+ export default i18n
@@ -0,0 +1,8 @@
1
+ export declare const locales: readonly ['en', 'tw', 'ja', 'ko', 'cn']
2
+ export declare const defaultLocale: 'en'
3
+ export type Locale = typeof locales[number]
4
+ export declare const hreflangOfLocale: Record<string, string>
5
+ export declare const nameOfLocale: Record<string, string>
6
+ export declare const localizableOrigins: Record<string, readonly string[]>
7
+ export declare function languageOf(locale: string): string
8
+ export declare function formatDate(value: string | number | Date, locale: string, options?: Intl.DateTimeFormatOptions): string
@@ -0,0 +1,18 @@
1
+ /** URL prefixes stay stable; document language tags use standard BCP 47 values. */
2
+ export const locales = ['en', 'tw', 'ja', 'ko', 'cn']
3
+ export const defaultLocale = 'en'
4
+ export const hreflangOfLocale = { en: 'en', tw: 'zh-TW', ja: 'ja', ko: 'ko', cn: 'zh-CN' }
5
+ export const nameOfLocale = { en: 'English', tw: '繁體中文(台灣)', ja: '日本語', ko: '한국어', cn: '简体中文' }
6
+ /** Only first-party page links inherit the reader's language across sites. */
7
+ export const localizableOrigins = {
8
+ 'https://master.co': ['blog', 'brand', 'design-system'],
9
+ 'https://css.master.co': ['guide', 'reference', 'benchmarks', 'roadmap'],
10
+ 'https://rc.css.master.co': ['guide', 'reference', 'benchmarks', 'roadmap']
11
+ }
12
+
13
+ export function languageOf(locale) { return hreflangOfLocale[locale] ?? locale }
14
+
15
+ /** UTC keeps publication dates identical in static builds and hydrated islands. */
16
+ export function formatDate(value, locale, options = {}) {
17
+ return new Intl.DateTimeFormat(languageOf(locale), { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC', ...options }).format(new Date(value))
18
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "stylesheet": { "tw": "樣式表", "ja": "スタイルシート", "ko": "스타일시트", "cn": "样式表" },
3
+ "declaration": { "tw": "宣告", "ja": "宣言", "ko": "선언", "cn": "声明" },
4
+ "selector": { "tw": "選擇器", "ja": "セレクター", "ko": "선택자", "cn": "选择器" },
5
+ "cascade": { "tw": "階層串接", "ja": "カスケード", "ko": "캐스케이드", "cn": "层叠" },
6
+ "cascade layer": { "tw": "串接層", "ja": "カスケードレイヤー", "ko": "캐스케이드 레이어", "cn": "层叠层" },
7
+ "specificity": { "tw": "選擇器權重", "ja": "詳細度", "ko": "명시도", "cn": "选择器优先级" },
8
+ "theme token": { "tw": "主題 token", "ja": "テーマトークン", "ko": "테마 토큰", "cn": "主题令牌" },
9
+ "utility": { "tw": "utility", "ja": "ユーティリティ", "ko": "유틸리티", "cn": "工具类" },
10
+ "custom property": { "tw": "自訂屬性", "ja": "カスタムプロパティ", "ko": "사용자 지정 속성", "cn": "自定义属性" },
11
+ "build time": { "tw": "建置階段", "ja": "ビルド時", "ko": "빌드 시점", "cn": "构建时" },
12
+ "runtime": { "tw": "執行階段", "ja": "ランタイム", "ko": "런타임", "cn": "运行时" },
13
+ "compiler": { "tw": "編譯器", "ja": "コンパイラー", "ko": "컴파일러", "cn": "编译器" },
14
+ "rendering": { "tw": "渲染", "ja": "レンダリング", "ko": "렌더링", "cn": "渲染" },
15
+ "extraction": { "tw": "擷取", "ja": "抽出", "ko": "추출", "cn": "提取" },
16
+ "scope": { "tw": "作用範圍", "ja": "スコープ", "ko": "범위", "cn": "作用域" },
17
+ "inheritance": { "tw": "繼承", "ja": "継承", "ko": "상속", "cn": "继承" },
18
+ "fallback": { "tw": "備援方式", "ja": "フォールバック", "ko": "폴백", "cn": "回退方案" },
19
+ "breakpoint": { "tw": "斷點", "ja": "ブレークポイント", "ko": "중단점", "cn": "断点" },
20
+ "container query": { "tw": "容器查詢", "ja": "コンテナークエリー", "ko": "컨테이너 쿼리", "cn": "容器查询" },
21
+ "media query": { "tw": "媒體查詢", "ja": "メディアクエリー", "ko": "미디어 쿼리", "cn": "媒体查询" },
22
+ "viewport": { "tw": "檢視區", "ja": "ビューポート", "ko": "뷰포트", "cn": "视口" },
23
+ "accessibility": { "tw": "無障礙", "ja": "アクセシビリティ", "ko": "접근성", "cn": "无障碍" },
24
+ "screen reader": { "tw": "螢幕閱讀器", "ja": "スクリーンリーダー", "ko": "스크린 리더", "cn": "屏幕阅读器" },
25
+ "reduced motion": { "tw": "減少動態效果", "ja": "動きを抑える設定", "ko": "동작 줄이기", "cn": "减少动态效果" },
26
+ "source file": { "tw": "原始檔", "ja": "ソースファイル", "ko": "소스 파일", "cn": "源文件" },
27
+ "package": { "tw": "套件", "ja": "パッケージ", "ko": "패키지", "cn": "软件包" },
28
+ "configuration": { "tw": "設定", "ja": "設定", "ko": "설정", "cn": "配置" },
29
+ "default": { "tw": "預設", "ja": "既定", "ko": "기본값", "cn": "默认" },
30
+ "interface": { "tw": "介面", "ja": "インターフェース", "ko": "인터페이스", "cn": "界面" },
31
+ "mouse": { "tw": "滑鼠", "ja": "マウス", "ko": "마우스", "cn": "鼠标" }
32
+ }
@@ -0,0 +1,3 @@
1
+ export declare const translations: Record<string, Record<string, string>>
2
+ export default translations
3
+ export declare function translateShell(locale: string, text: string): string