stack-site-builder 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +112 -0
- package/README.md +20 -1
- package/index.mjs +43 -29
- package/markdown.mjs +24 -12
- package/package.json +9 -3
- package/src/components/ArticleLink.astro +2 -2
- package/src/components/ConceptDetail.astro +2 -2
- package/src/components/ConceptLink.astro +2 -2
- package/src/components/ConceptTools.astro +2 -2
- package/src/components/LanguageSwitcher.astro +7 -7
- package/src/components/PageDetail.astro +52 -0
- package/src/components/ProjectViewer.astro +2 -2
- package/src/components/SampleIndex.astro +2 -2
- package/src/components/SampleProject.astro +3 -3
- package/src/components/StackCard.astro +3 -3
- package/src/components/StackDetail.astro +3 -3
- package/src/components/ThemeToggle.astro +1 -3
- package/src/components/ToolMeta.astro +2 -2
- package/src/content.ts +29 -1
- package/src/i18n/ui.ts +70 -13
- package/src/layouts/BaseLayout.astro +118 -57
- package/src/lib/dates.ts +2 -2
- package/src/lib/facets.ts +2 -2
- package/src/lib/locales.ts +16 -0
- package/src/lib/pages.ts +24 -0
- package/src/pages/[...lang]/[page].astro +30 -0
- package/src/pages/{ko → [...lang]}/article/[...id].astro +11 -5
- package/src/pages/[...lang]/article/category/[id].astro +25 -0
- package/src/pages/[...lang]/article/index.astro +18 -0
- package/src/pages/[...lang]/categories/[id].astro +28 -0
- package/src/pages/{ko → [...lang]}/concept/[...id].astro +11 -5
- package/src/pages/[...lang]/concept/category/[id].astro +25 -0
- package/src/pages/[...lang]/concept/index.astro +18 -0
- package/src/pages/[...lang]/glossary.astro +18 -0
- package/src/pages/[...lang]/index.astro +16 -0
- package/src/pages/[...lang]/sample/[folder].astro +25 -0
- package/src/pages/[...lang]/sample/index.astro +18 -0
- package/src/pages/[...lang]/slides/[deck].astro +22 -0
- package/src/pages/[...lang]/slides/index.astro +18 -0
- package/src/pages/{ko → [...lang]}/stack/[...id].astro +20 -13
- package/src/pages/[...lang]/tags/[tag].astro +24 -0
- package/src/pages/[...lang]/vendors/[vendor].astro +24 -0
- package/src/styles/global.css +7 -0
- package/src/pages/article/[...id].astro +0 -24
- package/src/pages/article/category/[id].astro +0 -17
- package/src/pages/article/index.astro +0 -12
- package/src/pages/categories/[id].astro +0 -25
- package/src/pages/concept/[...id].astro +0 -24
- package/src/pages/concept/category/[id].astro +0 -17
- package/src/pages/concept/index.astro +0 -12
- package/src/pages/glossary.astro +0 -12
- package/src/pages/index.astro +0 -9
- package/src/pages/ko/article/category/[id].astro +0 -17
- package/src/pages/ko/article/index.astro +0 -12
- package/src/pages/ko/categories/[id].astro +0 -24
- package/src/pages/ko/concept/category/[id].astro +0 -17
- package/src/pages/ko/concept/index.astro +0 -12
- package/src/pages/ko/glossary.astro +0 -12
- package/src/pages/ko/index.astro +0 -9
- package/src/pages/ko/sample/[folder].astro +0 -16
- package/src/pages/ko/sample/index.astro +0 -12
- package/src/pages/ko/slides/[deck].astro +0 -14
- package/src/pages/ko/slides/index.astro +0 -12
- package/src/pages/ko/tags/[tag].astro +0 -18
- package/src/pages/ko/vendors/[vendor].astro +0 -18
- package/src/pages/sample/[folder].astro +0 -16
- package/src/pages/sample/index.astro +0 -12
- package/src/pages/slides/[deck].astro +0 -14
- package/src/pages/slides/index.astro +0 -12
- package/src/pages/stack/[...id].astro +0 -51
- package/src/pages/tags/[tag].astro +0 -18
- package/src/pages/vendors/[vendor].astro +0 -18
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are recorded in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
`stack-site-builder` is the Astro theme behind the awesome-\*-stack catalog
|
|
9
|
+
sites — it ships every route, component, style, the markdown pipeline and the
|
|
10
|
+
content schema, while a consuming site supplies only content, taxonomy data and
|
|
11
|
+
config. Sites track the theme with `pnpm up stack-site-builder`, so each release
|
|
12
|
+
here is a plain version bump they pull in.
|
|
13
|
+
|
|
14
|
+
## [1.12.0] - 2026-07-20
|
|
15
|
+
|
|
16
|
+
Locales are now **site-configurable**. The theme was wired to exactly two
|
|
17
|
+
languages (English at the root, Korean under `/ko/`, plus a hand-written `ko/`
|
|
18
|
+
copy of every route); a consuming site could not add a third. Now the theme
|
|
19
|
+
reads the locales from the site's own astro.config `i18n` and renders every
|
|
20
|
+
route for each of them from one source, so a site adds a language (e.g.
|
|
21
|
+
Japanese) by editing its config and data — no theme changes, no per-locale route
|
|
22
|
+
files. Existing en/ko sites are unaffected: the same URLs build, and the theme
|
|
23
|
+
still defaults to en/ko when a site declares nothing.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **Site-configurable locales** — the header language switcher, route
|
|
28
|
+
generation, date formatting, wikilink resolution and sitemap hreflang all
|
|
29
|
+
derive from the site's configured locales. A site declares them in
|
|
30
|
+
astro.config `i18n` (routing) and, optionally, `site.locales` in
|
|
31
|
+
`src/data/site.ts` (display name + date format per locale); it supplies each
|
|
32
|
+
extra locale's UI strings via `site.ui.<code>`, with any missing key falling
|
|
33
|
+
back to the default locale. The playground ships a third locale (`ja`) to
|
|
34
|
+
demonstrate the path.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- **One route tree instead of a per-locale mirror** — the hand-maintained
|
|
39
|
+
`src/pages/ko/` copy of every page is gone; all pages now live once under
|
|
40
|
+
`src/pages/[...lang]/` and enumerate the site's locales in `getStaticPaths`.
|
|
41
|
+
This is internal to the theme, but it is what makes adding a locale free.
|
|
42
|
+
- **`Lang` is now `string`** (was the `'en' | 'ko'` union) since locales are no
|
|
43
|
+
longer fixed at build time. Locale-keyed maps (`pricingLabels`, category
|
|
44
|
+
labels, …) resolve through helpers that fall back to the default locale rather
|
|
45
|
+
than assuming a key exists.
|
|
46
|
+
|
|
47
|
+
## [1.11.0] - 2026-07-20
|
|
48
|
+
|
|
49
|
+
The center of this release is **standalone pages**: a site can now add a
|
|
50
|
+
first-class top-level section — an About/소개 page, contact, terms — that is
|
|
51
|
+
just one Markdown file, rendered on its own and linked from the header
|
|
52
|
+
navigation, without the listing and taxonomy of the catalog collections.
|
|
53
|
+
Alongside it, the header collapses into a menu on phones, the language switcher
|
|
54
|
+
becomes a globe icon, and two rendering bugs on the new pages are fixed.
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- **Standalone `pages` collection** — author a top-level page as one MDX file
|
|
59
|
+
(`src/content/pages/{en,ko}/<slug>.mdx`); the theme renders it at `/<slug>/`
|
|
60
|
+
in both locales. Frontmatter drives a hero image, description, and header-nav
|
|
61
|
+
placement (`nav`, `navLabel`, `order`, `draft`). Ships the `PageDetail`
|
|
62
|
+
component, the `getPages` / `getNavPages` / `pageSlugOf` helpers, and a
|
|
63
|
+
per-locale route injected by the integration.
|
|
64
|
+
- **Mobile navigation menu** — below the `sm` breakpoint the section links
|
|
65
|
+
collapse into a labelled dropdown menu (hamburger), so a phone header stays to
|
|
66
|
+
a few controls instead of a long icon run. The icon row (≥`sm`) and the menu
|
|
67
|
+
render from one `navItems` source, so they never drift, and nav pages appear
|
|
68
|
+
in both automatically.
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
|
|
72
|
+
- **Globe icon for the language switcher** — the switcher shows a globe instead
|
|
73
|
+
of the locale code/name, and the dropdown chevrons are removed from both the
|
|
74
|
+
language and theme switchers, leaving a single clean control each. Adds
|
|
75
|
+
`nav.language` and `nav.menu` UI strings (en/ko).
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
|
|
79
|
+
- **In-locale wikilinks on standalone pages** — wikilink targets were emitted as
|
|
80
|
+
`../../glossary/` (and `../../stack/…`, etc.), which assumes the source sits
|
|
81
|
+
two levels deep within its locale, true for the collection detail routes
|
|
82
|
+
(`/ko/stack/<slug>/`) but not for a one-deep standalone page (`/ko/about/`).
|
|
83
|
+
The extra `../` overshot the locale prefix and linked to the default-locale
|
|
84
|
+
glossary; the depth is now derived from where the source actually sits.
|
|
85
|
+
- **In-body images no longer force horizontal scroll** — content images had no
|
|
86
|
+
width cap and Astro's `<Image>` emits intrinsic `width`/`height`, so an image
|
|
87
|
+
wider than the viewport (e.g. a screenshot on a phone) stretched the body and
|
|
88
|
+
scrolled the whole page sideways. `.prose img` is now capped to
|
|
89
|
+
`max-width: 100%; height: auto`.
|
|
90
|
+
|
|
91
|
+
## [1.10.0] - 2026-07-20
|
|
92
|
+
|
|
93
|
+
First public release. The rendering engine of
|
|
94
|
+
[awesome-ai-stack](https://github.com/codecompose7/awesome-ai-stack) — routes,
|
|
95
|
+
components, styles, the markdown pipeline and the content schema — extracted
|
|
96
|
+
into a reusable, MIT-licensed Astro theme so the same engine can drive other
|
|
97
|
+
catalog sites from a thin content-only repository.
|
|
98
|
+
|
|
99
|
+
### Added
|
|
100
|
+
|
|
101
|
+
- **The awesome-\*-stack theme** — an integration that injects every route
|
|
102
|
+
(catalog, concepts, articles, slides, samples, glossary, tags, vendors, in
|
|
103
|
+
en/ko), the components and styles, the shared markdown pipeline
|
|
104
|
+
(`[[wikilink]]` glossary, slides, mermaid), and the content schema via
|
|
105
|
+
`defineAasCollections`. A consuming site provides content, taxonomy data
|
|
106
|
+
(`categories`, `glossary`, `site`) and config.
|
|
107
|
+
- **Standalone development setup** — a devcontainer and a minimal `playground/`
|
|
108
|
+
consuming site for developing and previewing the theme on its own.
|
|
109
|
+
|
|
110
|
+
[1.12.0]: https://github.com/CodeCompose7/stack-site-builder/compare/v1.11.0...v1.12.0
|
|
111
|
+
[1.11.0]: https://github.com/CodeCompose7/stack-site-builder/compare/v1.10.0...v1.11.0
|
|
112
|
+
[1.10.0]: https://github.com/CodeCompose7/stack-site-builder/releases/tag/v1.10.0
|
package/README.md
CHANGED
|
@@ -34,12 +34,31 @@ export const collections = defineAasCollections({ categoryMap });
|
|
|
34
34
|
|
|
35
35
|
| Where | What |
|
|
36
36
|
| --- | --- |
|
|
37
|
-
| `src/data/site.ts` | Site identity: name, repo URL, per-locale UI string overrides |
|
|
37
|
+
| `src/data/site.ts` | Site identity: name, repo URL, the `locales` it ships, per-locale UI string overrides |
|
|
38
38
|
| `src/data/categories.ts` | The tool-catalog category tree (validated against content) |
|
|
39
39
|
| `src/data/concept-categories.ts` · `article-categories.ts` | Taxonomies for concepts / articles |
|
|
40
40
|
| `src/data/glossary.mjs` | `[[Term]]` wikilink targets |
|
|
41
41
|
| `src/content/{stacks,concepts,articles,slides}/` | The content, one MDX file per locale |
|
|
42
|
+
| `src/content/pages/` | Standalone top-level pages (e.g. an About/소개), rendered at `/<slug>/` and optionally linked in the header nav |
|
|
42
43
|
| `public/` · `samples/` | Logos/favicons and runnable sample projects |
|
|
43
44
|
|
|
44
45
|
The theme reaches the site's data through the `@aas-data/*` alias (set up by
|
|
45
46
|
the integration), so everything above is swappable per site.
|
|
47
|
+
|
|
48
|
+
## Locales
|
|
49
|
+
|
|
50
|
+
The theme defaults to English (at the root) and Korean (under `/ko/`), but the
|
|
51
|
+
locale set is the site's to choose — it renders every route for each configured
|
|
52
|
+
locale from one source. To add a language (say Japanese):
|
|
53
|
+
|
|
54
|
+
1. List it in astro.config `i18n.locales` (this drives routing):
|
|
55
|
+
`i18n: { locales: ['en', 'ko', 'ja'], defaultLocale: 'en', routing: { prefixDefaultLocale: false } }`.
|
|
56
|
+
The first/`defaultLocale` is served at the root; the others under `/<code>/`.
|
|
57
|
+
2. Add it to `locales` in `src/data/site.ts` — `{ code, label, dateLocale? }` —
|
|
58
|
+
so it appears (named) in the language switcher and formats dates correctly.
|
|
59
|
+
3. Supply its UI strings in `src/data/site.ts` under `site.ui.<code>`; any key
|
|
60
|
+
you omit falls back to the default locale. Add the `<code>` translations to
|
|
61
|
+
your content (`src/content/<collection>/<code>/…`), glossary and category
|
|
62
|
+
labels the same way you did for the built-in locales.
|
|
63
|
+
|
|
64
|
+
No theme files change — adding a locale is entirely site config and content.
|
package/index.mjs
CHANGED
|
@@ -21,11 +21,14 @@ import tailwindcss from '@tailwindcss/vite';
|
|
|
21
21
|
import { aasMarkdown } from './markdown.mjs';
|
|
22
22
|
import { localSamples } from './dev/local-samples.mjs';
|
|
23
23
|
|
|
24
|
-
// Every page the theme provides,
|
|
25
|
-
//
|
|
26
|
-
//
|
|
24
|
+
// Every page the theme provides, relative to the `src/pages/[...lang]/` tree.
|
|
25
|
+
// Each is injected once at `/[...lang]/…`; its getStaticPaths enumerates the
|
|
26
|
+
// site's locales, emitting the default at the root and others under `/<code>/`.
|
|
27
27
|
const PAGES = [
|
|
28
28
|
'index.astro',
|
|
29
|
+
// Standalone top-level pages (the `pages` collection), e.g. an About/소개
|
|
30
|
+
// page. A single dynamic route per locale renders every entry at `/<slug>/`.
|
|
31
|
+
'[page].astro',
|
|
29
32
|
'article/index.astro',
|
|
30
33
|
'article/[...id].astro',
|
|
31
34
|
'article/category/[id].astro',
|
|
@@ -43,12 +46,12 @@ const PAGES = [
|
|
|
43
46
|
'vendors/[vendor].astro',
|
|
44
47
|
];
|
|
45
48
|
|
|
46
|
-
/** `article/[...id].astro` → `/article/[...id]`,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
/** `[...lang]/article/[...id].astro` → `/[...lang]/article/[...id]`,
|
|
50
|
+
* `[...lang]/index.astro` → `/[...lang]` */
|
|
51
|
+
/** @param {string} file */
|
|
52
|
+
function patternOf(file) {
|
|
49
53
|
const p = file.replace(/\.astro$/, '').replace(/\/?index$/, '');
|
|
50
|
-
|
|
51
|
-
return full || '/';
|
|
54
|
+
return `/${p}`.replace(/\/$/, '') || '/';
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
/**
|
|
@@ -63,23 +66,43 @@ export default function aasTheme({ glossary }) {
|
|
|
63
66
|
name: 'stack-site-builder',
|
|
64
67
|
hooks: {
|
|
65
68
|
'astro:config:setup': ({ config, injectRoute, updateConfig }) => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
// Locales the site configured (astro.config `i18n`). Everything locale
|
|
70
|
+
// aware derives from here — route injection, wikilink detection, and the
|
|
71
|
+
// sitemap hreflang — so a site adds a language by editing its config, not
|
|
72
|
+
// the theme. `locales` entries may be strings or `{ path }` objects.
|
|
73
|
+
const rawLocales = config.i18n?.locales ?? ['en'];
|
|
74
|
+
const locales = rawLocales.map((l) => (typeof l === 'string' ? l : l.path));
|
|
75
|
+
const defaultLocale = config.i18n?.defaultLocale ?? locales[0];
|
|
76
|
+
|
|
77
|
+
// A single physical page tree under `[...lang]/` serves every locale: the
|
|
78
|
+
// default at the root, each other under `/<code>/`. Each page's
|
|
79
|
+
// getStaticPaths enumerates the locales, so adding one needs no new files.
|
|
80
|
+
for (const file of PAGES) {
|
|
81
|
+
injectRoute({
|
|
82
|
+
pattern: patternOf(`[...lang]/${file}`),
|
|
83
|
+
entrypoint: `stack-site-builder/pages/[...lang]/${file}`,
|
|
84
|
+
});
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
updateConfig({
|
|
77
|
-
markdown: aasMarkdown({ glossary }),
|
|
88
|
+
markdown: aasMarkdown({ glossary, locales, defaultLocale }),
|
|
78
89
|
|
|
79
90
|
// Bind the dev server to 0.0.0.0 so it's reachable from a browser on
|
|
80
91
|
// the host (outside the Docker container).
|
|
81
92
|
server: { host: true },
|
|
82
93
|
|
|
94
|
+
// hreflang alternates so search engines pair each page with its twin in
|
|
95
|
+
// the other locales. Built from the site's configured locales (added
|
|
96
|
+
// here rather than in the returned array, where `config` isn't known).
|
|
97
|
+
integrations: [
|
|
98
|
+
sitemap({
|
|
99
|
+
i18n: {
|
|
100
|
+
defaultLocale,
|
|
101
|
+
locales: Object.fromEntries(locales.map((l) => [l, l])),
|
|
102
|
+
},
|
|
103
|
+
}),
|
|
104
|
+
],
|
|
105
|
+
|
|
83
106
|
vite: {
|
|
84
107
|
plugins: [tailwindcss(), localSamples()],
|
|
85
108
|
|
|
@@ -109,16 +132,7 @@ export default function aasTheme({ glossary }) {
|
|
|
109
132
|
},
|
|
110
133
|
};
|
|
111
134
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// i18n option emits hreflang alternates so search engines associate each
|
|
116
|
-
// page with its twin in the other locale (/stack/x/ ↔ /ko/stack/x/).
|
|
117
|
-
sitemap({
|
|
118
|
-
i18n: {
|
|
119
|
-
defaultLocale: 'en',
|
|
120
|
-
locales: { en: 'en', ko: 'ko' },
|
|
121
|
-
},
|
|
122
|
-
}),
|
|
123
|
-
];
|
|
135
|
+
// The sitemap integration is added from the core hook's updateConfig (above),
|
|
136
|
+
// where the site's configured locales are known.
|
|
137
|
+
return [core, mdx()];
|
|
124
138
|
}
|
package/markdown.mjs
CHANGED
|
@@ -230,12 +230,16 @@ function remarkSlideDirectives() {
|
|
|
230
230
|
|
|
231
231
|
// Turn `[[Term]]` (and `[[Term|display text]]`) wikilinks into links, resolving
|
|
232
232
|
// each against the site's central glossary (passed as an option). Internal targets
|
|
233
|
-
// emit
|
|
234
|
-
//
|
|
233
|
+
// emit a `../stack|concept/<slug>/` relative form (locale- and base-agnostic), with
|
|
234
|
+
// the number of `../` set by how deep the source page sits in its locale (see `up`
|
|
235
|
+
// below); external `href` entries pass through and get
|
|
235
236
|
// target="_blank" from rehype-external-links downstream. An unknown term throws,
|
|
236
237
|
// failing the build so a typo can't silently degrade to plain text. Code spans
|
|
237
238
|
// and fenced blocks are untouched (mdast `inlineCode`/`code` carry no children).
|
|
238
|
-
function remarkGlossary({ glossary }) {
|
|
239
|
+
function remarkGlossary({ glossary, locales = ['en', 'ko'], defaultLocale = 'en' }) {
|
|
240
|
+
// Non-default locales carry a `/<code>/` segment in their content path; the
|
|
241
|
+
// default locale has none. Match the path against them to pick the locale.
|
|
242
|
+
const prefixed = locales.filter((l) => l !== defaultLocale);
|
|
239
243
|
const RE = /\[\[\s*([^\]|]+?)\s*(?:\|\s*([^\]]+?)\s*)?\]\]/g;
|
|
240
244
|
/** @param {string} s */
|
|
241
245
|
const norm = (s) => s.trim().toLowerCase().replace(/\s+/g, '-');
|
|
@@ -262,7 +266,13 @@ function remarkGlossary({ glossary }) {
|
|
|
262
266
|
/** @param {any} tree @param {any} file */
|
|
263
267
|
return (tree, file) => {
|
|
264
268
|
const path = (file && (file.path || (file.history && file.history[0]))) || '';
|
|
265
|
-
const lang =
|
|
269
|
+
const lang = prefixed.find((l) => new RegExp(`[/\\\\]${l}[/\\\\]`).test(path)) ?? defaultLocale;
|
|
270
|
+
// How far the relative wikilink targets below must climb to reach the locale
|
|
271
|
+
// root. The collection detail routes sit two deep within their locale
|
|
272
|
+
// (/ko/stack/<slug>/), but a standalone `pages` entry is only one deep
|
|
273
|
+
// (/ko/about/). Using the wrong count overshoots the locale prefix — from
|
|
274
|
+
// /ko/about/ a `../../` lands on the default-locale glossary, not /ko/'s.
|
|
275
|
+
const up = /[/\\]content[/\\]pages[/\\]/.test(path) ? '../' : '../../';
|
|
266
276
|
/** @param {any} l */
|
|
267
277
|
const labelOf = (l) => (typeof l === 'string' ? l : l[lang]);
|
|
268
278
|
// Same-document section links ([[#anchor]]) resolve their display text to
|
|
@@ -329,15 +339,15 @@ function remarkGlossary({ glossary }) {
|
|
|
329
339
|
const def = entry.def ? labelOf(entry.def) : undefined;
|
|
330
340
|
// A def-only term (no page) links to its entry on the glossary page.
|
|
331
341
|
let url = entry.stack
|
|
332
|
-
?
|
|
342
|
+
? `${up}stack/${entry.stack}/`
|
|
333
343
|
: entry.concept
|
|
334
|
-
?
|
|
344
|
+
? `${up}concept/${entry.concept}/`
|
|
335
345
|
: entry.article
|
|
336
|
-
?
|
|
346
|
+
? `${up}article/${entry.article}/`
|
|
337
347
|
: entry.href
|
|
338
348
|
? entry.href
|
|
339
349
|
: def
|
|
340
|
-
?
|
|
350
|
+
? `${up}glossary/#${id}`
|
|
341
351
|
: null;
|
|
342
352
|
if (!url)
|
|
343
353
|
throw new Error(
|
|
@@ -373,17 +383,19 @@ function remarkGlossary({ glossary }) {
|
|
|
373
383
|
|
|
374
384
|
/**
|
|
375
385
|
* The full markdown config for `defineConfig({ markdown })`.
|
|
376
|
-
* @param {{ glossary: Record<string, any
|
|
377
|
-
*
|
|
386
|
+
* @param {{ glossary: Record<string, any>, locales?: string[], defaultLocale?: string }} opts
|
|
387
|
+
* `glossary` — the site's wikilink targets (pass `{}` for none). `locales` /
|
|
388
|
+
* `defaultLocale` come from the site's astro.config `i18n` so wikilink locale
|
|
389
|
+
* detection matches whatever locales the site ships (defaults to en/ko).
|
|
378
390
|
*/
|
|
379
|
-
export function aasMarkdown({ glossary }) {
|
|
391
|
+
export function aasMarkdown({ glossary, locales = ['en', 'ko'], defaultLocale = 'en' }) {
|
|
380
392
|
return {
|
|
381
393
|
remarkPlugins: [
|
|
382
394
|
remarkHeadingIds,
|
|
383
395
|
remarkMermaid,
|
|
384
396
|
remarkDirective,
|
|
385
397
|
remarkSlideDirectives,
|
|
386
|
-
[remarkGlossary, { glossary }],
|
|
398
|
+
[remarkGlossary, { glossary, locales, defaultLocale }],
|
|
387
399
|
],
|
|
388
400
|
rehypePlugins: [
|
|
389
401
|
rehypeSlug,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stack-site-builder",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.12.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "The engine behind the awesome-*-stack catalog sites: an Astro theme with the catalog/concepts/articles/slides/samples routes, components, styles and markdown pipeline. Sites provide content, taxonomy data and config.",
|
|
7
7
|
"repository": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"index.d.ts",
|
|
14
14
|
"markdown.mjs",
|
|
15
15
|
"src",
|
|
16
|
-
"dev"
|
|
16
|
+
"dev",
|
|
17
|
+
"CHANGELOG.md"
|
|
17
18
|
],
|
|
18
19
|
"exports": {
|
|
19
20
|
".": {
|
|
@@ -48,5 +49,10 @@
|
|
|
48
49
|
"@types/markdown-it": "^14.1.2",
|
|
49
50
|
"astro": "^5.7.0",
|
|
50
51
|
"typescript": "^6.0.3"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"dev": "pnpm --filter playground dev",
|
|
55
|
+
"build": "pnpm --filter playground build",
|
|
56
|
+
"check": "pnpm --filter playground check"
|
|
51
57
|
}
|
|
52
|
-
}
|
|
58
|
+
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { getRelativeLocaleUrl } from 'astro:i18n';
|
|
9
9
|
import { getArticles, articleSlugOf, type ArticleEntry } from '../lib/articles';
|
|
10
10
|
import { formatDate } from '../lib/dates';
|
|
11
|
-
import { useTranslations, type Lang } from '../i18n/ui';
|
|
11
|
+
import { useTranslations, defaultLang, type Lang } from '../i18n/ui';
|
|
12
12
|
import { inlineMd } from '../lib/inline-md';
|
|
13
13
|
|
|
14
14
|
interface Props {
|
|
@@ -16,7 +16,7 @@ interface Props {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const { slug } = Astro.props;
|
|
19
|
-
const lang: Lang = Astro.currentLocale
|
|
19
|
+
const lang: Lang = (Astro.currentLocale ?? defaultLang) as Lang;
|
|
20
20
|
const t = useTranslations(lang);
|
|
21
21
|
const articles = await getArticles(lang);
|
|
22
22
|
const target = articles.find((a) => articleSlugOf(a) === slug) as ArticleEntry | undefined;
|
|
@@ -5,7 +5,7 @@ import { getArticles, articleSlugOf, type ArticleEntry } from '../lib/articles';
|
|
|
5
5
|
import { getConcepts, conceptSlugOf, conceptToolSlugs, type ConceptEntry } from '../lib/concepts';
|
|
6
6
|
import { inlineMd } from '../lib/inline-md';
|
|
7
7
|
import { Image } from 'astro:assets';
|
|
8
|
-
import { useTranslations, type Lang } from '../i18n/ui';
|
|
8
|
+
import { useTranslations, dateLocaleOf, type Lang } from '../i18n/ui';
|
|
9
9
|
import MermaidLoader from './MermaidLoader.astro';
|
|
10
10
|
import TocRail from './TocRail.astro';
|
|
11
11
|
import Breadcrumb from './Breadcrumb.astro';
|
|
@@ -41,7 +41,7 @@ const relatedConcepts = data.related
|
|
|
41
41
|
.filter(Boolean) as ConceptEntry[];
|
|
42
42
|
|
|
43
43
|
const fmtDate = (d: Date) =>
|
|
44
|
-
new Intl.DateTimeFormat(lang
|
|
44
|
+
new Intl.DateTimeFormat(dateLocaleOf(lang), {
|
|
45
45
|
year: 'numeric',
|
|
46
46
|
month: 'long',
|
|
47
47
|
day: 'numeric',
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// from the current page (concepts are served under /<lang>/...).
|
|
8
8
|
import { getRelativeLocaleUrl } from 'astro:i18n';
|
|
9
9
|
import { getConcepts, conceptSlugOf, type ConceptEntry } from '../lib/concepts';
|
|
10
|
-
import { useTranslations, type Lang } from '../i18n/ui';
|
|
10
|
+
import { useTranslations, defaultLang, type Lang } from '../i18n/ui';
|
|
11
11
|
import { inlineMd } from '../lib/inline-md';
|
|
12
12
|
|
|
13
13
|
interface Props {
|
|
@@ -15,7 +15,7 @@ interface Props {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const { slug } = Astro.props;
|
|
18
|
-
const lang: Lang = Astro.currentLocale
|
|
18
|
+
const lang: Lang = (Astro.currentLocale ?? defaultLang) as Lang;
|
|
19
19
|
const t = useTranslations(lang);
|
|
20
20
|
const concepts = await getConcepts(lang);
|
|
21
21
|
const target = concepts.find((c) => conceptSlugOf(c) === slug) as ConceptEntry | undefined;
|
|
@@ -7,7 +7,7 @@ import { getRelativeLocaleUrl } from 'astro:i18n';
|
|
|
7
7
|
import { getStacks, slugOf, type StackEntry } from '../lib/stacks';
|
|
8
8
|
import { categoryMap } from '@aas-data/categories';
|
|
9
9
|
import ToolAvatar from './ToolAvatar.astro';
|
|
10
|
-
import type
|
|
10
|
+
import { defaultLang, type Lang } from '../i18n/ui';
|
|
11
11
|
|
|
12
12
|
interface Props {
|
|
13
13
|
slugs: string[] | string;
|
|
@@ -21,7 +21,7 @@ const list = Array.isArray(slugs)
|
|
|
21
21
|
.map((s) => s.trim())
|
|
22
22
|
.filter(Boolean);
|
|
23
23
|
|
|
24
|
-
const lang: Lang = Astro.currentLocale
|
|
24
|
+
const lang: Lang = (Astro.currentLocale ?? defaultLang) as Lang;
|
|
25
25
|
const stacks = await getStacks(lang);
|
|
26
26
|
const bySlug = new Map(stacks.map((s) => [slugOf(s), s]));
|
|
27
27
|
const tools = list.map((s) => bySlug.get(s)).filter(Boolean) as StackEntry[];
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getRelativeLocaleUrl } from 'astro:i18n';
|
|
3
|
-
import
|
|
4
|
-
import { languages, type Lang } from '../i18n/ui';
|
|
3
|
+
import { languages, useTranslations, type Lang } from '../i18n/ui';
|
|
5
4
|
|
|
6
5
|
interface Props {
|
|
7
6
|
lang: Lang;
|
|
@@ -10,17 +9,18 @@ interface Props {
|
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
const { lang, path } = Astro.props;
|
|
12
|
+
const t = useTranslations(lang);
|
|
13
13
|
const codes = Object.keys(languages) as Lang[];
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
<details class="aas-lang">
|
|
17
17
|
<summary
|
|
18
|
-
class="flex cursor-pointer list-none items-center
|
|
18
|
+
class="flex cursor-pointer list-none items-center rounded-lg border border-[var(--aas-border)] px-2.5 py-1 text-[var(--aas-text)] select-none"
|
|
19
|
+
aria-label={t('nav.language')}
|
|
20
|
+
title={t('nav.language')}
|
|
19
21
|
>
|
|
20
|
-
{/*
|
|
21
|
-
<
|
|
22
|
-
<span class="hidden sm:inline">{languages[lang]}</span>
|
|
23
|
-
<ChevronDown class="text-[var(--aas-muted)]" />
|
|
22
|
+
{/* A globe stands in for the current language; the dropdown lists names. */}
|
|
23
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg>
|
|
24
24
|
</summary>
|
|
25
25
|
<ul
|
|
26
26
|
class="absolute right-0 z-10 mt-1 min-w-[8rem] overflow-hidden rounded-lg border border-[var(--aas-border)] bg-[var(--aas-panel)] py-1 shadow-lg"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { render } from 'astro:content';
|
|
3
|
+
import { Image } from 'astro:assets';
|
|
4
|
+
import type { PageEntry } from '../lib/pages';
|
|
5
|
+
import type { Lang } from '../i18n/ui';
|
|
6
|
+
import { inlineMd } from '../lib/inline-md';
|
|
7
|
+
import TocRail from './TocRail.astro';
|
|
8
|
+
import MermaidLoader from './MermaidLoader.astro';
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
entry: PageEntry;
|
|
12
|
+
lang: Lang;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { entry, lang } = Astro.props;
|
|
16
|
+
const { Content, headings } = await render(entry);
|
|
17
|
+
const data = entry.data;
|
|
18
|
+
const tocItems = headings.filter((h) => h.depth >= 2 && h.depth <= 3);
|
|
19
|
+
// A standalone page (and any <SampleProject/> README it embeds) may use Mermaid;
|
|
20
|
+
// load the client renderer only when the body actually does.
|
|
21
|
+
const bodyHasMermaid = (entry.body ?? '').includes('```mermaid');
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
<header class="pb-6">
|
|
25
|
+
<h1 class="text-3xl font-bold tracking-tight">{data.title}</h1>
|
|
26
|
+
{
|
|
27
|
+
data.description && (
|
|
28
|
+
<p
|
|
29
|
+
class="mt-2 aas-md text-lg whitespace-pre-line text-[var(--aas-muted)]"
|
|
30
|
+
set:html={inlineMd(data.description)}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
{
|
|
35
|
+
data.image && (
|
|
36
|
+
<Image
|
|
37
|
+
src={data.image}
|
|
38
|
+
alt={data.imageAlt ?? data.title}
|
|
39
|
+
class="mt-5 aspect-[2/1] w-full rounded-2xl border border-[var(--aas-border)] object-cover"
|
|
40
|
+
/>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
</header>
|
|
44
|
+
|
|
45
|
+
<div class="mt-4 flex items-start gap-8">
|
|
46
|
+
<article class="prose max-w-none aas-reading min-w-0 flex-1">
|
|
47
|
+
<Content />
|
|
48
|
+
</article>
|
|
49
|
+
<TocRail lang={lang} items={tocItems} />
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{bodyHasMermaid && <MermaidLoader />}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { useTranslations, type Lang } from '../i18n/ui';
|
|
2
|
+
import { useTranslations, dateLocaleOf, type Lang } from '../i18n/ui';
|
|
3
3
|
import type { RenderedProject } from '../lib/project';
|
|
4
4
|
import ChevronDown from './ChevronDown.astro';
|
|
5
5
|
import ProjectFileList from './ProjectFileList.astro';
|
|
@@ -32,7 +32,7 @@ function splitReadme(html?: string): { titleInner?: string; body?: string } {
|
|
|
32
32
|
function formatDate(iso: string): string {
|
|
33
33
|
const [y, m, d] = iso.split('-').map(Number);
|
|
34
34
|
if (!y || !m || !d) return iso;
|
|
35
|
-
return new Intl.DateTimeFormat(lang
|
|
35
|
+
return new Intl.DateTimeFormat(dateLocaleOf(lang), {
|
|
36
36
|
year: 'numeric',
|
|
37
37
|
month: 'long',
|
|
38
38
|
day: 'numeric',
|
|
@@ -10,7 +10,7 @@ import ChevronDown from './ChevronDown.astro';
|
|
|
10
10
|
import { listProjects } from '../lib/project';
|
|
11
11
|
import { getStacks, slugOf } from '../lib/stacks';
|
|
12
12
|
import { getArticles } from '../lib/articles';
|
|
13
|
-
import { useTranslations, type Lang } from '../i18n/ui';
|
|
13
|
+
import { useTranslations, dateLocaleOf, type Lang } from '../i18n/ui';
|
|
14
14
|
|
|
15
15
|
interface Props {
|
|
16
16
|
lang: Lang;
|
|
@@ -76,7 +76,7 @@ const scopeOptions = [
|
|
|
76
76
|
function formatDate(iso: string): string {
|
|
77
77
|
const [y, m, d] = iso.split('-').map(Number);
|
|
78
78
|
if (!y || !m || !d) return iso;
|
|
79
|
-
return new Intl.DateTimeFormat(lang
|
|
79
|
+
return new Intl.DateTimeFormat(dateLocaleOf(lang), {
|
|
80
80
|
year: 'numeric',
|
|
81
81
|
month: 'long',
|
|
82
82
|
day: 'numeric',
|
|
@@ -7,21 +7,21 @@
|
|
|
7
7
|
// on the linked sample page instead of being embedded in the article.
|
|
8
8
|
import { getRelativeLocaleUrl } from 'astro:i18n';
|
|
9
9
|
import { listProjects } from '../lib/project';
|
|
10
|
-
import { useTranslations, type Lang } from '../i18n/ui';
|
|
10
|
+
import { useTranslations, dateLocaleOf, defaultLang, type Lang } from '../i18n/ui';
|
|
11
11
|
|
|
12
12
|
interface Props {
|
|
13
13
|
folder: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const { folder } = Astro.props;
|
|
17
|
-
const lang: Lang = Astro.currentLocale
|
|
17
|
+
const lang: Lang = (Astro.currentLocale ?? defaultLang) as Lang;
|
|
18
18
|
const t = useTranslations(lang);
|
|
19
19
|
const target = listProjects(lang).find((p) => p.folder === folder);
|
|
20
20
|
|
|
21
21
|
function formatDate(iso: string): string {
|
|
22
22
|
const [y, m, d] = iso.split('-').map(Number);
|
|
23
23
|
if (!y || !m || !d) return iso;
|
|
24
|
-
return new Intl.DateTimeFormat(lang
|
|
24
|
+
return new Intl.DateTimeFormat(dateLocaleOf(lang), {
|
|
25
25
|
year: 'numeric',
|
|
26
26
|
month: 'long',
|
|
27
27
|
day: 'numeric',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getRelativeLocaleUrl } from 'astro:i18n';
|
|
3
|
-
import {
|
|
3
|
+
import { pricingLabel, useTranslations, type Lang } from '../i18n/ui';
|
|
4
4
|
import { getRepoStats, formatStars, stalenessOf } from '../lib/github';
|
|
5
5
|
import { pricingTags, COMPLETELY_FREE } from '../lib/pricing';
|
|
6
6
|
import { isImageRef, resolveImageUrl } from '../lib/assets';
|
|
@@ -155,7 +155,7 @@ const searchText = `${name} ${description} ${tags.join(' ')}`.toLowerCase();
|
|
|
155
155
|
pricingTags(pricing).map((p) => (
|
|
156
156
|
<span
|
|
157
157
|
data-pricing-tag={p}
|
|
158
|
-
title={
|
|
158
|
+
title={pricingLabel(lang, p)}
|
|
159
159
|
class:list={[p === COMPLETELY_FREE ? 'aas-free' : 'aas-pricing', 'rounded-lg px-2.5 py-1']}
|
|
160
160
|
>
|
|
161
161
|
{p === 'open-source' && (
|
|
@@ -175,7 +175,7 @@ const searchText = `${name} ${description} ${tags.join(' ')}`.toLowerCase();
|
|
|
175
175
|
<polyline points="8 6 2 12 8 18" />
|
|
176
176
|
</svg>
|
|
177
177
|
)}
|
|
178
|
-
<span data-chip-label>{
|
|
178
|
+
<span data-chip-label>{pricingLabel(lang, p)}</span>
|
|
179
179
|
</span>
|
|
180
180
|
))
|
|
181
181
|
}
|
|
@@ -3,7 +3,7 @@ import { render } from 'astro:content';
|
|
|
3
3
|
import { Image } from 'astro:assets';
|
|
4
4
|
import { getRelativeLocaleUrl } from 'astro:i18n';
|
|
5
5
|
import { pathOf } from '@aas-data/categories';
|
|
6
|
-
import {
|
|
6
|
+
import { pricingLabel, useTranslations, type Lang } from '../i18n/ui';
|
|
7
7
|
import { pricingTags, COMPLETELY_FREE } from '../lib/pricing';
|
|
8
8
|
import StarIcon from './StarIcon.astro';
|
|
9
9
|
import { slugOf, slugifyName, toolResolver, type StackEntry } from '../lib/stacks';
|
|
@@ -203,7 +203,7 @@ const relatedToolsByFolder = new Map(projectRail.map((p) => [p.folder, p.tools])
|
|
|
203
203
|
pricingTags(data.pricing).map((p) => (
|
|
204
204
|
<span
|
|
205
205
|
data-pricing-tag={p}
|
|
206
|
-
title={
|
|
206
|
+
title={pricingLabel(lang, p)}
|
|
207
207
|
class:list={[p === COMPLETELY_FREE ? 'aas-free' : 'aas-pricing', 'inline-flex items-center gap-1 rounded-lg px-2.5 py-1']}
|
|
208
208
|
>
|
|
209
209
|
{p === 'open-source' && (
|
|
@@ -223,7 +223,7 @@ const relatedToolsByFolder = new Map(projectRail.map((p) => [p.folder, p.tools])
|
|
|
223
223
|
<polyline points="8 6 2 12 8 18" />
|
|
224
224
|
</svg>
|
|
225
225
|
)}
|
|
226
|
-
<span>{
|
|
226
|
+
<span>{pricingLabel(lang, p)}</span>
|
|
227
227
|
</span>
|
|
228
228
|
))
|
|
229
229
|
}
|