Skip to content

Commit 872e0b4

Browse files
s00dcursoragent
andauthored
feat(vitepress): add @i18n-micro/vitepress package (#247)
* feat(vitepress): add @i18n-micro/vitepress package Runtime dictionaries for VitePress MD/themes via defineI18nTheme + withI18nMicro, path sync, page locales, and serializable i18nRouting. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(vitepress): address cubic review on #247 Map VP locale keys vs i18n codes, keep disk roots with routeMessages, harden load/glob, re-chain enhanceApp, CI lint. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(vitepress): unblock CI typecheck Cast virtual imports past Astro ambient clash; loosen test router setters; cast dts plugin for duplicate Vite types. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(vitepress): address second cubic pass on #247 Per-app plugin install, stricter glob unwrap, routeName 2-arg, skip full-inline disk watch; I18nLink active ignores hash. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(vitepress): bump 1.0.1 after vue dep bump check: versions requires dependents republish when vue pins change. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(scripts): skip version cascade for new packages New packages have no baseline; first publish already pins workspace deps — cascade must not demand an extra bump. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(vitepress): do not re-set routing on re-enhance vue setRoutingStrategy targets last-installed app; calling it again from app1 after app2 install clobbers app2's adapter. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 931ba25 commit 872e0b4

45 files changed

Lines changed: 3389 additions & 1068 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/.vitepress/config.mts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ new MutationObserver(() => {
177177
{ text: 'Preact Package', link: '/integrations/preact-package' },
178178
{ text: 'Solid Package', link: '/integrations/solid-package' },
179179
{ text: 'Astro Package', link: '/integrations/astro-package' },
180+
{ text: 'VitePress Package', link: '/integrations/vitepress-package' },
180181
{ text: 'Node.js Package', link: '/integrations/nodejs-package' },
181182
{ text: 'Types Generator', link: '/integrations/types-generator' },
182183
{ text: 'Devtools Package', link: '/integrations/devtools-ui-package' },
@@ -244,6 +245,7 @@ new MutationObserver(() => {
244245
{ text: 'Preact Package', link: '/preact-package' },
245246
{ text: 'Solid Package', link: '/solid-package' },
246247
{ text: 'Astro Package', link: '/astro-package' },
248+
{ text: 'VitePress Package', link: '/vitepress-package' },
247249
{ text: 'Node.js Package', link: '/nodejs-package' },
248250
{ text: 'Types Generator', link: '/types-generator' },
249251
{ text: 'Devtools Package', link: '/devtools-ui-package' },

docs/integrations/index.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Unlike the full Nuxt module, these packages:
3333
- **[Preact Package](./preact-package.md)** (`@i18n-micro/preact`) - For Preact applications
3434
- **[Solid Package](./solid-package.md)** (`@i18n-micro/solid`) - For SolidJS applications
3535
- **[Astro Package](./astro-package.md)** (`@i18n-micro/astro`) - For Astro applications with SSR support
36+
- **[VitePress Package](./vitepress-package.md)** (`@i18n-micro/vitepress`) - For VitePress docs (runtime `$t` / components / switcher)
3637

3738
### Utility Packages
3839

@@ -286,6 +287,7 @@ For detailed setup instructions, see the documentation for your specific package
286287
- [React Package Documentation](./react-package.md)
287288
- [Solid Package Documentation](./solid-package.md)
288289
- [Astro Package Documentation](./astro-package.md)
290+
- [VitePress Package Documentation](./vitepress-package.md)
289291

290292
## Comparison with Nuxt Module
291293

@@ -294,11 +296,11 @@ For detailed setup instructions, see the documentation for your specific package
294296
| Translation methods |||
295297
| Components || ✅ (subset) |
296298
| Router integration | ✅ (automatic) | ✅ (via adapter) |
297-
| SSR support || ✅ (Astro only) |
299+
| SSR support || ✅ (Astro / VitePress prerender) |
298300
| DevTools |||
299-
| Auto locale detection || ✅ (Astro only) |
300-
| SEO meta tags || ✅ (Astro only) |
301-
| File-based routing || |
301+
| Auto locale detection || ✅ (Astro primarily) |
302+
| SEO meta tags || ✅ (Astro primarily) |
303+
| File-based routing ||(VitePress uses its own `locales`) |
302304
| Nuxt-specific features |||
303305

304306
## License
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
---
2+
title: 'VitePress Package (`@i18n-micro/vitepress`)'
3+
description: 'Runtime i18n for VitePress themes and markdown pages.'
4+
outline: 'deep'
5+
---
6+
7+
# VitePress Package (`@i18n-micro/vitepress`)
8+
9+
Runtime dictionaries, `$t` / `<I18nT>` inside markdown, and an optional `<I18nSwitcher>` — built on `@i18n-micro/vue`, synced with VitePress path locales.
10+
11+
## Positioning
12+
13+
VitePress i18n is three separate problems. This package only solves **runtime UI strings**.
14+
15+
| Layer | Need | Solution |
16+
|-------|------|----------|
17+
| A. Site structure | Duplicate `.md`, URL prefixes, `locales` | **VitePress built-in** |
18+
| B. Default theme chrome | `docFooter`, search UI, outline labels | **`vitepress-i18n`**, hand-written `themeConfig`, or tools like **ai-i18n-tools** |
19+
| C. Runtime UI in MD / custom theme | `t` / plural / components / custom dropdown | **`@i18n-micro/vitepress`** |
20+
21+
```
22+
@i18n-micro/vitepress ≠ theme chrome translator
23+
@i18n-micro/vitepress ≠ markdown copier
24+
```
25+
26+
### Compose with other helpers
27+
28+
`withI18n` is already used by [`vitepress-i18n`](https://www.npmjs.com/package/vitepress-i18n). Our helper is intentionally named **`withI18nMicro`**:
29+
30+
```ts
31+
import { defineConfig } from 'vitepress'
32+
import { withI18n } from 'vitepress-i18n' // optional: chrome labels
33+
import { withI18nMicro } from '@i18n-micro/vitepress/config'
34+
35+
export default defineConfig(
36+
withI18n(
37+
withI18nMicro(vitePressConfig, i18nMicroOptions),
38+
vitePressI18nOptions,
39+
),
40+
)
41+
```
42+
43+
## Install
44+
45+
::: code-group
46+
47+
```bash [pnpm]
48+
pnpm add @i18n-micro/vitepress
49+
```
50+
51+
```bash [npm]
52+
npm install @i18n-micro/vitepress
53+
```
54+
55+
```bash [yarn]
56+
yarn add @i18n-micro/vitepress
57+
```
58+
59+
:::
60+
61+
Peers: `vitepress`, `vue`.
62+
63+
## Setup
64+
65+
Put dictionaries under the site root, e.g.:
66+
67+
```
68+
locales/en.json
69+
locales/fr.json
70+
locales/pages/guide/demo/en.json # optional page-scoped
71+
locales/pages/guide/demo/fr.json
72+
```
73+
74+
Page files map to route names the same way as Nuxt/Astro (`guide/demo``guide-demo`). On `/guide/demo` / `/fr/guide/demo`, `setRoute('guide-demo')` loads them.
75+
76+
### 1. Config — `@i18n-micro/vitepress/config`
77+
78+
```ts
79+
// .vitepress/config.mts
80+
import { defineConfig } from 'vitepress'
81+
import {
82+
withI18nMicro,
83+
createI18nRoutingFromAdapter,
84+
} from '@i18n-micro/vitepress/config'
85+
86+
const locales = [
87+
{ code: 'en', iso: 'en-US', displayName: 'English' },
88+
{ code: 'fr', iso: 'fr-FR', displayName: 'Français' },
89+
]
90+
91+
export default defineConfig(
92+
withI18nMicro(
93+
{
94+
locales: {
95+
root: { label: 'English', lang: 'en-US' },
96+
fr: { label: 'Français', lang: 'fr-FR', link: '/fr/' },
97+
},
98+
themeConfig: {
99+
// Self-contained function (safe for VitePress site-data serialization)
100+
i18nRouting: createI18nRoutingFromAdapter({
101+
defaultLocale: 'en',
102+
localeCodes: locales.map((l) => l.code),
103+
}),
104+
},
105+
},
106+
{
107+
locale: 'en',
108+
defaultLocale: 'en',
109+
locales,
110+
translationDir: 'locales',
111+
},
112+
),
113+
)
114+
```
115+
116+
`withI18nMicro` injects Vite virtual modules:
117+
118+
- `virtual:i18n-micro/config` — locales / defaults / `localeKeyToCode`
119+
- `virtual:i18n-micro/messages` — per-file JSON imports from `translationDir` (+ `routeMessages`)
120+
121+
Import the helper from **`/config`** (Node + `fs`). Do not import it from the client theme.
122+
123+
### 2. Theme — `defineI18nTheme` (recommended)
124+
125+
```ts
126+
// .vitepress/theme/index.ts
127+
import DefaultTheme from 'vitepress/theme'
128+
import { defineI18nTheme } from '@i18n-micro/vitepress'
129+
130+
export default defineI18nTheme(DefaultTheme)
131+
```
132+
133+
Initial locale is taken from the current path before install (no default-locale flash on `/fr/…`).
134+
135+
#### Advanced: own messages / `createVitePressI18n`
136+
137+
```ts
138+
import { createVitePressI18n, messagesFromGlob } from '@i18n-micro/vitepress'
139+
140+
const messages = messagesFromGlob(
141+
import.meta.glob('../../locales/*.json', { eager: true }),
142+
)
143+
const { enhanceApp } = createVitePressI18n({ /**/, messages })
144+
```
145+
146+
Node FS helpers (scripts / config tooling):
147+
148+
```ts
149+
import { loadMessages, loadTranslationBuckets } from '@i18n-micro/vitepress/node'
150+
// or from '@i18n-micro/vitepress/config'
151+
```
152+
153+
## In-page translations
154+
155+
Each VitePress markdown file is a Vue SFC. Three patterns:
156+
157+
### Global components + `$t` (recommended in MD)
158+
159+
`createVitePressI18n` installs `@i18n-micro/vue`, which registers `I18nT` / `I18nLink` / `I18nGroup` / `I18nSwitcher` and `$t` / `$tc` / `$ts`.
160+
161+
```md
162+
{{ $t('cta.readMore') }}
163+
164+
<I18nT keypath="greeting" :params="{ name: 'VitePress' }" />
165+
166+
<I18nLink to="/guide/demo">Demo</I18nLink>
167+
```
168+
169+
Component names must be PascalCase or contain a hyphen (VitePress hydration rule).
170+
171+
### `useI18n` in `<script setup>`
172+
173+
```md
174+
<script setup>
175+
import { useI18n } from '@i18n-micro/vitepress'
176+
const { t, tc, locale } = useI18n()
177+
</script>
178+
179+
# {{ t('section.title') }}
180+
```
181+
182+
### Prose vs JSON keys
183+
184+
| Content | How to localize |
185+
|---------|-----------------|
186+
| Long guides / articles | Duplicate `.md` per locale (VitePress) |
187+
| Shared UI phrases, CTA, plurals | JSON + `$t` / `<I18nT>` |
188+
| Page-only UI keys | `locales/pages/<path>/<locale>.json` |
189+
| Tip/warning container titles | VitePress `locales.*.markdown.container` |
190+
191+
## Language dropdown
192+
193+
### Built-in (default theme)
194+
195+
When `locales` has more than one entry, VitePress shows **`VPNavBarTranslations`** (globe flyout). Wire `themeConfig.i18nRouting` (boolean or function) so corresponding pages resolve correctly. Use `createI18nRoutingFromAdapter` so the built-in menu matches `<I18nSwitcher>` paths.
196+
197+
### `<I18nSwitcher>` (custom themes only)
198+
199+
Use in custom layouts / page content — **not** in the default navbar next to the globe.
200+
201+
```ts
202+
import { h } from 'vue'
203+
import DefaultTheme from 'vitepress/theme'
204+
import { defineI18nTheme, I18nSwitcher } from '@i18n-micro/vitepress'
205+
206+
export default defineI18nTheme({
207+
...DefaultTheme,
208+
Layout: () =>
209+
h(DefaultTheme.Layout, null, {
210+
// custom theme example — avoid with default VPNavBarTranslations
211+
'doc-footer-before': () => h(I18nSwitcher),
212+
}),
213+
})
214+
```
215+
216+
Navigation always changes the URL via the VitePress router (`router.go`). Client-only locale without a path change is not supported (against VitePress’ model).
217+
218+
::: warning Do not mount both in the navbar
219+
With the default theme, the globe menu (`VPNavBarTranslations`) is enough.
220+
Putting `<I18nSwitcher>` in `nav-bar-content-*` duplicates the control. Prefer page/footer slots or a fully custom theme.
221+
:::
222+
223+
| Scenario | Use |
224+
|----------|-----|
225+
| Default theme docs | Built-in language menu |
226+
| Custom theme / hero / footer | `<I18nSwitcher>` |
227+
| Both in navbar | Avoid — pick one |
228+
229+
## SSR notes
230+
231+
- Locale on prerender comes from the URL path (adapter), not cookies.
232+
- Theme code must not touch `window` at import time.
233+
- `$t` / components used in MD become dynamic Vue nodes (static prose stays static).
234+
235+
## API surface
236+
237+
| Export | Role |
238+
|--------|------|
239+
| `@i18n-micro/vitepress/config``withI18nMicro` | Config helper + virtual modules (Node) |
240+
| `defineI18nTheme` | Zero-boilerplate theme (`enhanceApp`) |
241+
| `createVitePressI18n` | Manual `enhanceApp` installer + path sync |
242+
| `createVitePressRouterAdapter` | Path ↔ locale (prefix except default) |
243+
| `createI18nRoutingFromAdapter` | Serializable `themeConfig.i18nRouting` |
244+
| `messagesFromGlob` | Optional glob → messages map |
245+
| `I18nT` / `I18nLink` / `I18nGroup` / `I18nSwitcher` / `useI18n` | Re-exported from `@i18n-micro/vue` |
246+
| `/node``loadMessages` / `loadTranslationBuckets` | Node FS loaders (scripts; prefer `/config` for site config) |
247+
248+
## License
249+
250+
MIT

packages/vitepress/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2016
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/vitepress/README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# `@i18n-micro/vitepress`
2+
3+
VitePress bindings for [i18n-micro](https://github.com/s00d/nuxt-i18n-micro): runtime JSON dictionaries, `$t` / `<I18nT>` inside markdown, and an optional `<I18nSwitcher>` — on top of VitePress built-in `locales`.
4+
5+
## What this is / is not
6+
7+
| Need | Use |
8+
|------|-----|
9+
| Duplicate markdown per language, URL prefixes | **VitePress `locales`** |
10+
| Default theme chrome labels (docFooter, search UI) | **`vitepress-i18n`** or hand-written `themeConfig` |
11+
| Runtime `t` / plural / components in MD & custom theme | **`@i18n-micro/vitepress`** (this package) |
12+
13+
## Quick start
14+
15+
```bash
16+
pnpm add @i18n-micro/vitepress
17+
```
18+
19+
### Config (`/config` — Node)
20+
21+
```ts
22+
import { defineConfig } from 'vitepress'
23+
import { withI18nMicro, createI18nRoutingFromAdapter } from '@i18n-micro/vitepress/config'
24+
25+
const locales = [
26+
{ code: 'en', iso: 'en-US', displayName: 'English' },
27+
{ code: 'fr', iso: 'fr-FR', displayName: 'Français' },
28+
]
29+
30+
export default defineConfig(
31+
withI18nMicro(
32+
{
33+
locales: {
34+
root: { label: 'English', lang: 'en' },
35+
fr: { label: 'Français', lang: 'fr', link: '/fr/' },
36+
},
37+
themeConfig: {
38+
i18nRouting: createI18nRoutingFromAdapter({
39+
defaultLocale: 'en',
40+
localeCodes: locales.map((l) => l.code),
41+
}),
42+
},
43+
},
44+
{
45+
locale: 'en',
46+
defaultLocale: 'en',
47+
locales,
48+
translationDir: 'locales', // root + optional pages/**
49+
},
50+
),
51+
)
52+
```
53+
54+
### Theme
55+
56+
```ts
57+
import DefaultTheme from 'vitepress/theme'
58+
import { defineI18nTheme } from '@i18n-micro/vitepress'
59+
60+
export default defineI18nTheme(DefaultTheme)
61+
```
62+
63+
### In markdown
64+
65+
```md
66+
{{ $t('cta.readMore') }}
67+
68+
<I18nT keypath="greeting" :params="{ name: 'VitePress' }" />
69+
<I18nLink to="/guide/demo">Demo</I18nLink>
70+
```
71+
72+
## License
73+
74+
MIT

0 commit comments

Comments
 (0)