@bndynet/vue-site 1.0.2 → 1.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.
- package/README.md +229 -10
- package/README.zh.md +538 -0
- package/bin/vue-site.mjs +212 -18
- package/dist/components/LocaleSwitch.vue.d.ts +10 -0
- package/dist/composables/useLocale.d.ts +19 -0
- package/dist/composables/useLocalize.d.ts +18 -0
- package/dist/i18n-messages.d.ts +9 -0
- package/dist/i18n-utils.d.ts +81 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.es.js +10993 -10562
- package/dist/router.d.ts +9 -3
- package/dist/style.css +1 -1
- package/dist/theme/presets.d.ts +7 -0
- package/dist/theme/resolve-palettes.d.ts +7 -1
- package/dist/types.d.ts +129 -17
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ Configurable Vue 3 site framework: one package, `site.config.ts`, and Markdown p
|
|
|
10
10
|
- Hash or HTML5 (`web`) router history, configurable in `site.config.ts`
|
|
11
11
|
- Markdown (`?raw`) or Vue pages
|
|
12
12
|
- highlight.js, light/dark theme + localStorage
|
|
13
|
+
- Built-in multi-language support (locale switcher, `LocalizedString` config, per-locale pages) — see [Internationalization](#internationalization-i18n)
|
|
13
14
|
- Project `README.md` as Home
|
|
14
15
|
- Full TypeScript types
|
|
15
16
|
|
|
@@ -69,13 +70,15 @@ Add `"dev": "vue-site dev"` (or `vs dev`) in `package.json` scripts if you like.
|
|
|
69
70
|
|
|
70
71
|
| Property | Description |
|
|
71
72
|
|----------|-------------|
|
|
72
|
-
| `title` | Site title (sidebar + tab) |
|
|
73
|
+
| `title` | Site title (sidebar + tab). `LocalizedString` |
|
|
73
74
|
| `nav` | `NavItem[]` |
|
|
75
|
+
| `defaultPath` | Path the site opens at; `/` and unknown paths redirect here. Must match a registered route (a `nav` item's resolved path or a `pages` entry's `path`). Defaults to the first top-level `nav` item |
|
|
74
76
|
| `logo` | Logo URL or imported image |
|
|
75
|
-
| `theme` | See `ThemeConfig` below |
|
|
76
|
-
| `
|
|
77
|
+
| `theme` | See `ThemeConfig` below; set to `false` to disable theming (hides the switcher, forces a fixed `light` palette, no localStorage persistence) |
|
|
78
|
+
| `i18n` | Multi-language config (`I18nConfig`) — see [Internationalization](#internationalization-i18n) |
|
|
79
|
+
| `footer` | Footer text. `LocalizedString` |
|
|
77
80
|
| `readme` | Raw Home content if no `README.md` |
|
|
78
|
-
| `links` | Header links: Lucide `icon` + `link`, optional `title` |
|
|
81
|
+
| `links` | Header links: Lucide `icon` + `link`, optional `title` (`LocalizedString`) |
|
|
79
82
|
| `pages` | `StandalonePage[]` — full-screen routes outside the `nav` tree (no top bar/sidebar/footer) |
|
|
80
83
|
| `auth` | Central authorization policy (`AuthConfig`) — see [Per-page authorization](#per-page-authorization-auth) |
|
|
81
84
|
| `router` | History mode (`RouterConfig`) — `hash` (default) or HTML5 `web`; see [Router history](#router-history-router) |
|
|
@@ -88,10 +91,10 @@ Add `"dev": "vue-site dev"` (or `vs dev`) in `package.json` scripts if you like.
|
|
|
88
91
|
|
|
89
92
|
| Property | Description |
|
|
90
93
|
|----------|-------------|
|
|
91
|
-
| `label` | Sidebar text |
|
|
94
|
+
| `label` | Sidebar text. `LocalizedString` |
|
|
92
95
|
| `icon` | [Lucide](https://lucide.dev/icons) name |
|
|
93
|
-
| `page` | `() => import('./
|
|
94
|
-
| `path` | Route path (derived from `label` if omitted) |
|
|
96
|
+
| `page` | Page content. Simplest is a **file-path string** like `'./pages/AdminView.vue'` or `'./README.md'` (auto-loads per-locale siblings, falls back to the base file). Also accepts a loader (`() => import('./Page.vue')` / `() => import('./page.md?raw')`) or a `localizedPage(...)` result. See [Per-locale page content](#per-locale-page-content) and [Advanced page loaders](#advanced-page-loaders) |
|
|
97
|
+
| `path` | Route path (derived from `label`'s default-locale value if omitted; stays stable across languages) |
|
|
95
98
|
| `children` | Nested group |
|
|
96
99
|
| `link` | Render as a hyperlink (internal route path or external URL) instead of a page route |
|
|
97
100
|
| `visible` | `() => boolean \| Promise<boolean>`, awaited once at startup. Return `false` to hide the item from the nav and skip its route (not reachable by direct URL). A hidden parent hides its subtree; a group with no remaining children is pruned. Not reactive to later changes. |
|
|
@@ -99,12 +102,228 @@ Add `"dev": "vue-site dev"` (or `vs dev`) in `package.json` scripts if you like.
|
|
|
99
102
|
|
|
100
103
|
### `ThemeConfig`
|
|
101
104
|
|
|
105
|
+
Built-in themes are `light`, `dark`, plus the always-on extras `sepia` and `ocean` (shown in the switcher for every site). Set `theme: false` on `SiteConfig` to disable theming entirely.
|
|
106
|
+
|
|
102
107
|
| Property | Default | Description |
|
|
103
108
|
|----------|---------|-------------|
|
|
104
|
-
| `default` | `light` | `light`, `dark`, or an `extraThemes[].id` |
|
|
109
|
+
| `default` | `light` | `light`, `dark`, a built-in extra id (`sepia`, `ocean`), or an `extraThemes[].id` |
|
|
105
110
|
| `colors` | — | Global CSS variable overrides |
|
|
106
111
|
| `palettes` | — | Partial overrides for built-in light/dark only |
|
|
107
|
-
| `extraThemes` | — | Extra themes: `id`, `label`, `icon`, optional `basedOn`, `palette`;
|
|
112
|
+
| `extraThemes` | — | Extra themes: `id`, `label`, `icon`, optional `basedOn`, `palette`; reuse a built-in id (`sepia`/`ocean`) to override it. Import `builtinThemePalettes` for full defaults |
|
|
113
|
+
|
|
114
|
+
## Internationalization (`i18n`)
|
|
115
|
+
|
|
116
|
+
Set `i18n` to enable multi-language support. The framework adds a locale switcher to the header,
|
|
117
|
+
resolves every `LocalizedString` field (`title`, `nav[].label`, `footer`, `links[].title`) against
|
|
118
|
+
the active locale, and exposes the current locale via `useLocale()` / `useLocalize()`.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { defineConfig } from '@bndynet/vue-site'
|
|
122
|
+
|
|
123
|
+
export default defineConfig({
|
|
124
|
+
i18n: {
|
|
125
|
+
locales: [
|
|
126
|
+
{ code: 'en', label: 'English' },
|
|
127
|
+
{ code: 'zh', label: '简体中文', icon: 'languages' },
|
|
128
|
+
],
|
|
129
|
+
defaultLocale: 'en',
|
|
130
|
+
},
|
|
131
|
+
title: { en: 'My Site', zh: '我的站点' }, // a LocalizedString
|
|
132
|
+
footer: { en: '© 2026', zh: '© 2026 版权所有' },
|
|
133
|
+
nav: [
|
|
134
|
+
{ label: { en: 'Home', zh: '首页' }, icon: 'home', page: '../README.md' },
|
|
135
|
+
{
|
|
136
|
+
label: { en: 'Guide', zh: '指南' },
|
|
137
|
+
icon: 'book',
|
|
138
|
+
// Per-locale content: ./pages/guide.md (base) + guide.zh.md, auto-discovered by the CLI.
|
|
139
|
+
page: './pages/guide.md',
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
})
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Per-locale page content
|
|
146
|
+
|
|
147
|
+
Point `page` at a **file-path string** and the framework serves the right file for the active
|
|
148
|
+
locale — no extra wiring:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { defineConfig, tk } from '@bndynet/vue-site'
|
|
152
|
+
|
|
153
|
+
export default defineConfig({
|
|
154
|
+
i18n: { locales: [{ code: 'en' }, { code: 'zh' }], defaultLocale: 'en' },
|
|
155
|
+
nav: [
|
|
156
|
+
// Loads ../README.md, and auto-uses ../README.zh.md when the active locale is `zh`.
|
|
157
|
+
{ label: tk('nav.home'), icon: 'home', page: '../README.md' },
|
|
158
|
+
// Vue pages work the same way: Dashboard.vue + Dashboard.zh.vue.
|
|
159
|
+
{ label: tk('nav.dash'), icon: 'gauge', page: './pages/Dashboard.vue' },
|
|
160
|
+
],
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- Name the variants `name.<code>.<ext>` next to the base file — `README.zh.md`, `Dashboard.zh.vue`, …
|
|
165
|
+
- A locale with no matching file falls back to the **base file** (`README.md`). Resolution order:
|
|
166
|
+
exact → primary-subtag (`zh-TW` → `zh`) → base file.
|
|
167
|
+
- **Add a language by dropping in a `name.<code>` file — no config changes.**
|
|
168
|
+
- Works for Markdown (`.md`) and Vue (`.vue`). The base name must not contain dots (`README.md` ✓,
|
|
169
|
+
`my.page.md` ✗).
|
|
170
|
+
|
|
171
|
+
> The string form is resolved by the `vue-site` CLI at build time. If you embed the library yourself
|
|
172
|
+
> (no CLI — see [library mode](#library-mode)), use a loader from
|
|
173
|
+
> [Advanced page loaders](#advanced-page-loaders) instead.
|
|
174
|
+
|
|
175
|
+
### Advanced page loaders
|
|
176
|
+
|
|
177
|
+
> **Rarely needed.** The file-path string above covers most sites. Reach for these only when you
|
|
178
|
+
> want a plain single-file loader, files that **don't** share a base name, or you run **without** the
|
|
179
|
+
> CLI (library mode).
|
|
180
|
+
|
|
181
|
+
Besides a string, `page` accepts a **loader function** or a `localizedPage(...)` result:
|
|
182
|
+
|
|
183
|
+
- **Single file, no localization** — a plain dynamic import:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
page: () => import('./pages/Dashboard.vue') // or () => import('./guide.md?raw') for Markdown
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
- **Explicit locale map** — for files that don't share a base name (so the string form can't infer
|
|
190
|
+
them):
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { localizedPage } from '@bndynet/vue-site'
|
|
194
|
+
|
|
195
|
+
page: localizedPage({
|
|
196
|
+
en: () => import('./pages/guide-en.md?raw'),
|
|
197
|
+
zh: () => import('./pages/guide-zh.md?raw'),
|
|
198
|
+
})
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
- **Glob (library mode)** — the same auto-discovery as the string form, written out so it works
|
|
202
|
+
without the CLI:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
page: localizedPage(import.meta.glob(['../README.md', '../README.*.md'], { query: '?raw' }))
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`page: '../README.md'` is exactly this, generated for you by the CLI. (For Vue pages drop the
|
|
209
|
+
`{ query: '?raw' }`.)
|
|
210
|
+
|
|
211
|
+
All `localizedPage` forms fall back when the active locale has no file (exact → primary-subtag →
|
|
212
|
+
base file → first entry).
|
|
213
|
+
|
|
214
|
+
### How it works
|
|
215
|
+
|
|
216
|
+
- **Initial locale**: stored choice (localStorage) > browser language (`navigator.language`, when
|
|
217
|
+
`detectBrowser` is on) > `defaultLocale` > first entry.
|
|
218
|
+
- **`LocalizedString`** is `string | Record<LocaleCode, string> | MessageRef`. A plain string is
|
|
219
|
+
returned as-is (single-language configs keep working), a locale map holds inline per-language
|
|
220
|
+
text, and a `MessageRef` (built with `tk('id')`) references a key from a central message file —
|
|
221
|
+
see [Centralized message files](#centralized-message-files-tk--t).
|
|
222
|
+
- **Stable URLs**: route paths derive from the **default-locale** label (or an explicit `path`), so
|
|
223
|
+
switching language never changes URLs.
|
|
224
|
+
- **Reactive**: switching language updates labels, the title, the footer, and page content live
|
|
225
|
+
(page content reloads via `localizedPage`). The switcher only appears when `locales.length > 1`.
|
|
226
|
+
- **UI strings**: the framework ships built-in strings (currently `en`, `zh`) for the theme/locale
|
|
227
|
+
switchers and page errors; override or extend them per locale via `i18n.messages`.
|
|
228
|
+
|
|
229
|
+
### `I18nConfig`
|
|
230
|
+
|
|
231
|
+
| Property | Default | Description |
|
|
232
|
+
|----------|---------|-------------|
|
|
233
|
+
| `locales` | discovered `locales/*.json` | `{ code, label, icon? }[]` — supported languages, in display order. Optional: derived from the auto-loaded file names (with built-in labels) when omitted. First entry is the fallback |
|
|
234
|
+
| `defaultLocale` | `locales[0].code` | Initial locale when nothing is stored and detection finds no match |
|
|
235
|
+
| `detectBrowser` | `true` | Detect the initial locale from `navigator.language(s)` on first visit |
|
|
236
|
+
| `storageKey` | `vue-site-locale` | localStorage key for the chosen locale |
|
|
237
|
+
| `messages` | auto-loaded from `locales/<code>.json` | `Record<LocaleCode, Record<string, string>>` — extra/override translations, merged over the auto-loaded files and built-in UI strings |
|
|
238
|
+
|
|
239
|
+
### Message files & keys (`tk` / `t`)
|
|
240
|
+
|
|
241
|
+
Instead of inlining `{ en, zh }` everywhere, keep all translations in plain JSON — **one file per
|
|
242
|
+
language** — and reference them by key. This is zero-config: the CLI auto-discovers
|
|
243
|
+
`locales/<code>.json` next to your `site.config.ts`. You don't write any glue code (no `index.ts`,
|
|
244
|
+
no `messages` field) and you don't even have to list the languages.
|
|
245
|
+
|
|
246
|
+
```jsonc
|
|
247
|
+
// locales/en.json — nested groups (recommended), flattened to dotted ids
|
|
248
|
+
{
|
|
249
|
+
"site": { "title": "My Site" },
|
|
250
|
+
"nav": { "home": "Home", "guide": "Guide" }
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
```jsonc
|
|
255
|
+
// locales/zh.json
|
|
256
|
+
{
|
|
257
|
+
"site": { "title": "我的站点" },
|
|
258
|
+
"nav": { "home": "首页", "guide": "指南" }
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
> Files may be **nested** (above) or **flat** (`{ "site.title": "My Site" }`) — nested groups are
|
|
263
|
+
> flattened to dotted ids, so `tk('site.title')` / `t('site.title')` work either way.
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// site.config.ts — reference keys with tk() in config and t() in pages
|
|
267
|
+
import { defineConfig, tk } from '@bndynet/vue-site'
|
|
268
|
+
|
|
269
|
+
export default defineConfig({
|
|
270
|
+
// `i18n` can be omitted entirely: the language list is derived from the file names
|
|
271
|
+
// (en, zh, ...) with friendly built-in labels. Declare it only to customize label/icon/order.
|
|
272
|
+
i18n: {
|
|
273
|
+
locales: [
|
|
274
|
+
{ code: 'en', label: 'English' },
|
|
275
|
+
{ code: 'zh', label: '简体中文', icon: 'languages' },
|
|
276
|
+
],
|
|
277
|
+
defaultLocale: 'en',
|
|
278
|
+
},
|
|
279
|
+
title: tk('site.title'),
|
|
280
|
+
nav: [
|
|
281
|
+
{ label: tk('nav.home'), icon: 'home', page: '../README.md' },
|
|
282
|
+
{
|
|
283
|
+
label: tk('nav.guide'),
|
|
284
|
+
icon: 'book',
|
|
285
|
+
page: './pages/guide.md',
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
})
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Key resolution falls back through the active locale's primary subtag → `defaultLocale` → `en` → the
|
|
292
|
+
id itself, and `{name}` placeholders are interpolated. `tk()` and inline `{ en, zh }` maps can be
|
|
293
|
+
mixed freely — use `tk()` for shared/centrally managed text and an inline map for one-off strings.
|
|
294
|
+
|
|
295
|
+
**Auto-discovery details & overrides**
|
|
296
|
+
|
|
297
|
+
- The convention is `locales/<code>.json` (e.g. `locales/en.json`, `locales/zh.json`), resolved
|
|
298
|
+
relative to the config directory. `code` is the file name (a `LocaleCode` like `en` or `zh-TW`).
|
|
299
|
+
- An explicit `i18n.messages` is still supported and **overrides** auto-loaded keys (per id); an
|
|
300
|
+
explicit `i18n.locales` controls the label/icon/order. Both are optional.
|
|
301
|
+
- Auto-discovery is a **CLI** feature. If you embed the library yourself (calling `createSiteApp`
|
|
302
|
+
without the `vue-site` CLI), pass `i18n.messages` directly — e.g. build it from JSON with explicit
|
|
303
|
+
imports (avoid `import.meta.glob` inside `site.config.ts`, which the CLI pre-loads in Node).
|
|
304
|
+
|
|
305
|
+
### Localizing in your own pages
|
|
306
|
+
|
|
307
|
+
`useLocalize()` returns `t(id, params?)` (resolve a message id from the central catalog with
|
|
308
|
+
`{name}` interpolation), `localize(value)` (resolve any `LocalizedString` — a `tk()` ref, an inline
|
|
309
|
+
map, or a plain string), and the reactive `locale` ref. `useLocale()` returns
|
|
310
|
+
`{ locale, setLocale, locales }` for building a custom switcher. All work inside any component
|
|
311
|
+
rendered by `createSiteApp`.
|
|
312
|
+
|
|
313
|
+
```vue
|
|
314
|
+
<script setup lang="ts">
|
|
315
|
+
import { useLocalize } from '@bndynet/vue-site'
|
|
316
|
+
|
|
317
|
+
const { t, localize, locale } = useLocalize()
|
|
318
|
+
</script>
|
|
319
|
+
|
|
320
|
+
<template>
|
|
321
|
+
<!-- key from the central message file -->
|
|
322
|
+
<h1>{{ t('nav.home') }}</h1>
|
|
323
|
+
<!-- or inline, for one-off text -->
|
|
324
|
+
<p>{{ localize({ en: 'Hello', zh: '你好' }) }} — {{ locale }}</p>
|
|
325
|
+
</template>
|
|
326
|
+
```
|
|
108
327
|
|
|
109
328
|
## Per-page authorization (`auth`)
|
|
110
329
|
|
|
@@ -242,7 +461,7 @@ app.mount('#app')
|
|
|
242
461
|
|
|
243
462
|
Use a top-level `await` in your entry (or an async IIFE): `createSiteApp` is async and **awaits** `configureApp` when it returns a `Promise`. If you set optional `bootstrap` in config, that module loads before the app is created; if you omit `bootstrap`, that step is skipped.
|
|
244
463
|
|
|
245
|
-
Exports: `createSiteApp`, `defineConfig`, `useTheme`, `useSiteConfig`, `themeRefKey`. Types: `SiteConfig`, `SiteEnvConfig`, `SiteViteConfig`, `SiteExternalLink`, `NavItem`, `StandalonePage`, `AuthRule`, `AuthContext`, `AuthConfig`, `RouterConfig`, `ThemeConfig`, `ThemeOption`, `ThemePaletteVars`, `ResolvedNavItem`.
|
|
464
|
+
Exports: `createSiteApp`, `defineConfig`, `useTheme`, `useSiteConfig`, `useLocale`, `useLocalize`, `tk`, `resolveLocalized`, `resolveField`, `resolveMessage`, `mergeCatalog`, `flattenMessages`, `isMessageRef`, `localizedPage`, `builtinMessages`, `themeRefKey`, `localeRefKey`. Types: `SiteConfig`, `SiteEnvConfig`, `SiteViteConfig`, `SiteExternalLink`, `NavItem`, `StandalonePage`, `AuthRule`, `AuthContext`, `AuthConfig`, `RouterConfig`, `ThemeConfig`, `ThemeOption`, `ThemePaletteVars`, `ResolvedNavItem`, `I18nConfig`, `LocaleOption`, `LocaleCode`, `LocalizedString`, `MessageRef`, `MessageTree`, `MessageCatalog`, `PageLoader`, `LocalizedPageOptions`.
|
|
246
465
|
|
|
247
466
|
### Theme in Vue pages (`useTheme`)
|
|
248
467
|
|