Skip to content

Commit 74c4c74

Browse files
s00dcursoragent
andcommitted
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>
1 parent d823aa2 commit 74c4c74

20 files changed

Lines changed: 440 additions & 96 deletions

docs/news/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ outline: 'deep'
1010

1111
**Date**: 2026-08-05
1212

13-
**Package**: `@i18n-micro/vitepress@1.0.1`
13+
**Package**: `@i18n-micro/vitepress@1.0.0`
1414

1515
New workspace package for VitePress docs sites: JSON dictionaries, `$t` / `<I18nT>` in markdown, path sync with VitePress `locales`, and optional `<I18nSwitcher>`. Complements (does not replace) built-in multi-locale markdown and `vitepress-i18n` theme chrome helpers.
1616

packages/vitepress/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@i18n-micro/vitepress",
3-
"version": "1.0.1",
3+
"version": "1.0.0",
44
"description": "VitePress bindings for i18n-micro — runtime dictionaries, MD components, and locale switcher.",
55
"keywords": [
66
"i18n",

packages/vitepress/playground/.vitepress/config.mts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@ export default defineConfig(
4949
link: '/fr/',
5050
themeConfig: {
5151
nav: [
52-
{ text: 'Accueil', link: '/fr/' },
53-
{ text: 'Démo', link: '/fr/guide/demo' },
52+
{ text: 'Accueil', link: '/' },
53+
{ text: 'Démo', link: '/guide/demo' },
5454
],
5555
sidebar: [
5656
{
5757
text: 'Guide',
58-
items: [{ text: 'Démo in-page', link: '/fr/guide/demo' }],
58+
items: [{ text: 'Démo in-page', link: '/guide/demo' }],
5959
},
6060
],
6161
},

packages/vitepress/playground/fr/guide/demo.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ Locale actuelle : **{{ locale }}**
2929

3030
## Groupe
3131

32-
<I18nGroup prefix="cta">
33-
<I18nT keypath="readMore" />
32+
<I18nGroup prefix="cta" v-slot="{ t: tg }">
33+
{{ tg('readMore') }}
3434
</I18nGroup>
3535

3636
## `<I18nSwitcher>` optionnel (thèmes custom)

packages/vitepress/playground/fr/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Chaînes UI runtime (pas un remplacement du markdown dupliqué).
1313
</p>
1414

1515
<p>
16-
<a href="/fr/guide/demo">{{ $t('cta.readMore') }}</a>
16+
<I18nLink to="/guide/demo">{{ $t('cta.readMore') }}</I18nLink>
1717
</p>
1818

1919
Utilisez le menu de langue **intégré** (icône globe) pour changer de locale.

packages/vitepress/playground/guide/demo.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ Current locale: **{{ locale }}**
2929

3030
## Group
3131

32-
<I18nGroup prefix="cta">
33-
<I18nT keypath="readMore" />
32+
<I18nGroup prefix="cta" v-slot="{ t: tg }">
33+
{{ tg('readMore') }}
3434
</I18nGroup>
3535

3636
## Optional `<I18nSwitcher>` (custom themes)

packages/vitepress/playground/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Runtime UI strings (not a replacement for duplicated markdown prose).
1313
</p>
1414

1515
<p>
16-
<a href="/guide/demo">{{ $t('cta.readMore') }}</a>
16+
<I18nLink to="/guide/demo">{{ $t('cta.readMore') }}</I18nLink>
1717
</p>
1818

1919
Use the **built-in** language menu (globe icon) to switch locale.

packages/vitepress/src/create.ts

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ export function createVitePressI18n(options: VitePressI18nOptions): CreateVitePr
9393
applyRouteMessages(plugin, options.messages, options.routeMessages)
9494

9595
let adapter: VitePressRouterAdapter | null = null
96+
let installed = false
97+
let boundSyncHandler: ((to: string) => unknown) | null = null
98+
let chainedPrevious: ((to: string) => unknown) | undefined
9699

97100
const enhanceApp = (ctx: {
98101
app: App
@@ -101,40 +104,56 @@ export function createVitePressI18n(options: VitePressI18nOptions): CreateVitePr
101104
}) => {
102105
const { app, router } = ctx
103106

104-
adapter = createVitePressRouterAdapter({
105-
locales,
106-
defaultLocale,
107-
localeKeyToCode: options.localeKeyToCode,
108-
getPath: () => router.route.path,
109-
go: (href, navOptions) => router.go(href, navOptions),
110-
})
111-
112-
plugin.setRoutingStrategy(adapter)
113-
app.use(plugin)
107+
if (!installed) {
108+
adapter = createVitePressRouterAdapter({
109+
locales,
110+
defaultLocale,
111+
localeKeyToCode: options.localeKeyToCode,
112+
getPath: () => {
113+
const route = router.route
114+
return `${route.path}${route.query || ''}${route.hash || ''}`
115+
},
116+
go: (href, navOptions) => router.go(href, navOptions),
117+
})
118+
119+
plugin.setRoutingStrategy(adapter)
120+
app.use(plugin)
121+
installed = true
122+
}
114123

115-
if (!syncWithVitePress) return
124+
if (!syncWithVitePress || !adapter) return
116125

117126
const sync = (path = router.route.path) => {
118127
if (!adapter) return
119128
const nextLocale = adapter.getLocaleFromPath(path)
120129
if (plugin.global.getLocale() !== nextLocale) {
121130
plugin.global.locale = nextLocale
122131
}
123-
plugin.global.setRoute(routeNameFromPath(path, adapter.localeCodes))
132+
plugin.global.setRoute(
133+
routeNameFromPath(path, adapter.localeCodes, defaultLocale, options.localeKeyToCode),
134+
)
124135
}
125136

126137
sync()
127138

128-
const previous = router.onAfterRouteChange
129-
router.onAfterRouteChange = async (to: string) => {
130-
if (typeof previous === 'function') {
131-
await previous(to)
139+
// Stay outermost: if base/user enhanceApp overwrote the hook, re-wrap on a later call.
140+
const current = router.onAfterRouteChange
141+
if (current !== boundSyncHandler) {
142+
chainedPrevious = typeof current === 'function' ? current : undefined
143+
}
144+
boundSyncHandler = async (to: string) => {
145+
if (typeof chainedPrevious === 'function') {
146+
await chainedPrevious(to)
132147
}
133148
const path = to.startsWith('http')
134-
? new URL(to).pathname
135-
: to.split(/[?#]/)[0] || '/'
149+
? (() => {
150+
const url = new URL(to)
151+
return url.pathname + url.search + url.hash
152+
})()
153+
: to
136154
sync(path)
137155
}
156+
router.onAfterRouteChange = boundSyncHandler
138157
}
139158

140159
return {

packages/vitepress/src/define-theme.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ export function defineI18nTheme<T extends Theme>(base: T, options: DefineI18nThe
4949
return {
5050
...base,
5151
async enhanceApp(ctx: EnhanceAppContext) {
52-
if (!byApp.has(ctx.app)) {
52+
let installed = byApp.get(ctx.app)
53+
if (!installed) {
5354
const [{ config }, messagesMod] = await Promise.all([
5455
import('virtual:i18n-micro/config') as Promise<{ config: VirtualI18nConfig }>,
5556
import('virtual:i18n-micro/messages') as Promise<VirtualMessagesModule>,
@@ -58,13 +59,15 @@ export function defineI18nTheme<T extends Theme>(base: T, options: DefineI18nThe
5859
const localeCodes = config.localeCodes.length
5960
? config.localeCodes
6061
: config.locales.map((l) => l.code)
62+
const localeKeyToCode = options.localeKeyToCode ?? config.localeKeyToCode
6163
const initialLocale = getLocaleFromPath(
6264
ctx.router.route.path,
6365
localeCodes,
6466
config.defaultLocale,
67+
localeKeyToCode,
6568
)
6669

67-
const installed = createVitePressI18n({
70+
installed = createVitePressI18n({
6871
locale: initialLocale,
6972
defaultLocale: config.defaultLocale,
7073
fallbackLocale: config.fallbackLocale,
@@ -73,17 +76,19 @@ export function defineI18nTheme<T extends Theme>(base: T, options: DefineI18nThe
7376
routeMessages: messagesMod.routeMessages,
7477
missingWarn: config.missingWarn,
7578
syncWithVitePress: config.syncWithVitePress,
76-
localeKeyToCode: options.localeKeyToCode ?? config.localeKeyToCode,
79+
localeKeyToCode,
7780
plural: options.plural,
7881
missingHandler: options.missingHandler,
7982
})
80-
// Vue peer version may differ between vitepress and @i18n-micro/vue — ctx shape is compatible at runtime.
81-
installed.enhanceApp(ctx as unknown as Parameters<typeof installed.enhanceApp>[0])
8283
byApp.set(ctx.app, installed)
8384
}
8485

86+
// Install plugin first so base/user enhanceApp can use $t / components.
87+
installed.enhanceApp(ctx as unknown as Parameters<typeof installed.enhanceApp>[0])
8588
if (baseEnhance) await baseEnhance(ctx)
8689
if (userEnhance) await userEnhance(ctx)
90+
// Re-run so route sync wraps any onAfterRouteChange set by base/user.
91+
installed.enhanceApp(ctx as unknown as Parameters<typeof installed.enhanceApp>[0])
8792
},
8893
}
8994
}

packages/vitepress/src/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ export { createI18nRoutingFromAdapter } from './router/i18n-routing'
2525
/** Types only — runtime `withI18nMicro` lives in `@i18n-micro/vitepress/config` (Node). */
2626
export type { VirtualI18nConfig, VitePressUserConfigLike, WithI18nMicroOptions } from './with-i18n-micro'
2727

28-
/// <reference path="./virtual.d.ts" />
29-
3028
// Re-export Vue surface for a single import path in VitePress themes / MD pages
3129
export {
3230
createI18n,

0 commit comments

Comments
 (0)