Skip to content

VitePress Package (@i18n-micro/vitepress)

Runtime dictionaries, $t / <I18nT> inside markdown, and an optional <I18nSwitcher> — built on @i18n-micro/vue, synced with VitePress path locales.

Positioning

NeedSolution
Duplicate .md, URL prefixesVitePress locales
Runtime $t / components / path sync / SEO head@i18n-micro/vitepress
Default theme chrome labels (docFooter, search UI)Your own themeConfig per locale

Playground

packages/vitepress/playgroundpnpm -C packages/vitepress/playground dev

Install

bash
pnpm add @i18n-micro/vitepress
bash
npm install @i18n-micro/vitepress
bash
yarn add @i18n-micro/vitepress

Peers: vitepress, vue. Node scripts also use the workspace dependency @i18n-micro/node (re-exported through /node).

Setup

locales/en.json
locales/fr.json
locales/pages/guide/demo/en.json   # optional page-scoped
locales/pages/guide/demo/fr.json

Page files map to route names the same way as Nuxt/Astro (guide/demoguide-demo).

1. Config — @i18n-micro/vitepress/config

ts
// .vitepress/config.mts
import { defineConfig } from 'vitepress'
import { withI18n, buildVitePressLocales } from '@i18n-micro/vitepress/config'

const locales = [
  { code: 'en', iso: 'en-US', displayName: 'English', og: 'en_US' },
  { code: 'fr', iso: 'fr-FR', displayName: 'Français', og: 'fr_FR' },
]
const defaultLocale = 'en'

export default defineConfig(
  withI18n(
    {
      locales: buildVitePressLocales(locales, defaultLocale),
      // themeConfig.i18nRouting is injected automatically
    },
    {
      locale: defaultLocale,
      defaultLocale,
      locales,
      translationDir: 'locales',
      metaBaseUrl: 'https://example.com', // optional SEO head
    },
  ),
)

2. Theme — @i18n-micro/vitepress/theme

ts
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import { defineI18nTheme } from '@i18n-micro/vitepress/theme'

export default defineI18nTheme(DefaultTheme)

Initial locale is taken from the current path before install (no default-locale flash on /fr/…).

Advanced: manual createI18n

One factory for the client: Vue plugin + path methods + enhanceApp (no separate router helper exports).

ts
import { createI18n, messagesFromGlob } from '@i18n-micro/vitepress'

const i18n = createI18n({
  locale: 'en',
  defaultLocale: 'en',
  locales: [{ code: 'en' }, { code: 'fr' }],
  messages: messagesFromGlob(import.meta.glob('../../locales/*.json', { eager: true })),
})

i18n.localizePath('/guide', 'fr')
// in theme enhanceApp:
i18n.enhanceApp({ app, router })

Node scripts — @i18n-micro/vitepress/node

Uses @i18n-micro/node createI18n / loadTranslations (same as Astro & CLI), then attaches VitePress path methods with BaseI18n.extend:

ts
import { createI18n } from '@i18n-micro/vitepress/node'

const i18n = createI18n({
  locale: 'en',
  fallbackLocale: 'en',
  translationDir: './locales',
  locales: ['en', 'fr'],
  defaultLocale: 'en',
})
await i18n.loadTranslations()
i18n.t('cta.readMore')
i18n.localizePath('/guide', 'fr')

Plain Node without paths: import { createI18n } from '@i18n-micro/node'.

In-page translations

Each VitePress markdown file is a Vue SFC.

createI18n / defineI18nTheme install @i18n-micro/vue, which registers I18nT / I18nLink / I18nGroup / I18nSwitcher and $t / $tc / $ts.

md
{{ $t('cta.readMore') }}

<I18nT keypath="greeting" :params="{ name: 'VitePress' }" />

<I18nLink to="/guide/demo">Demo</I18nLink>

useI18n in <script setup>

md
<script setup>
import { useI18n } from '@i18n-micro/vitepress'
const { t, tc, locale, localePath, switchLocale } = useI18n()
</script>

# {{ t('section.title') }}

Language dropdown

When locales has more than one entry, VitePress shows VPNavBarTranslations. withI18n wires themeConfig.i18nRouting so the globe matches <I18nSwitcher> / path helpers.

Use <I18nSwitcher> only in custom layouts — not next to the default globe.

SSR notes

  • Locale on prerender comes from the URL path, not cookies.
  • Theme code must not touch window at import time.
  • Import defineI18nTheme from /theme so Node config evaluation never loads virtual:i18n-micro/*.

API surface

ExportRole
createI18n (.)Client: Vue plugin + path methods + enhanceApp
getLocaleFromPath / stripSiteBase / routeNameFromPath (.)Manual locale sync when syncWithVitePress: false
createI18n (/node)Node: @i18n-micro/node + path methods
defineI18nTheme (/theme)Zero-boilerplate theme
withI18n (/config)Config + virtual modules + optional SEO
buildVitePressLocales (/config)Build VitePress locales from Locale[]
buildVitePressLocaleHead (/config)Manual SEO head (also used by metaBaseUrl)
messagesFromGlobOptional glob → messages map for createI18n
useI18n / I18nT / …Re-exported from @i18n-micro/vue
BaseI18n.extend (@i18n-micro/core)Attach custom methods on any i18n instance

OpenAPI-style sites (integration locks)

Regression suite: packages/vitepress/tests/integration-openapi.test.ts (deep paths, base=/openapi_docs/, sync off + manual locale, Node generators, SEO).

PatternDo this
Deep /…/{spec}/{op} pages, only root UI JSONPrefer default syncWithVitePress: true (keeps setRoute('index') when no page dict) or syncWithVitePress: false + getLocaleFromPath
Never call setRoute(deepName) without page dictionariesRoot UI keys disappear until setRoute('index')
Generators / getTranslationcreateI18n from /node + await loadTranslations() + i18n.t(key) (set i18n.locale)
Custom canonical in transformPageDataPass meta: false to withI18n (avoid double tags)
Package SEO with site basemetaBaseUrl + VitePress base → absolute URLs include base
Rename from older package buildswithI18nMicrowithI18n, createVitePressI18ncreateI18n, loadMessages → node createI18n

Resources

License

MIT

Released under the MIT License.