Skip to main content

Internationalization

VitePress has built-in support for creating multilingual documentation sites with locale-specific configuration and content.

Directory Structure

Organize content by language using locale-based directories:
docs/
├─ guide/
│  └─ index.md
├─ es/
│  └─ guide/
│     └─ index.md
└─ fr/
   └─ guide/
      └─ index.md
  • Root is the default locale (e.g., English)
  • Subdirectories for other languages

Basic Configuration

Configure locales in your VitePress config:
// .vitepress/config.ts
import { defineConfig } from 'vitepress'

export default defineConfig({
  locales: {
    root: {
      label: 'English',
      lang: 'en'
    },
    fr: {
      label: 'Français',
      lang: 'fr',
      link: '/fr/'
    },
    es: {
      label: 'Español',
      lang: 'es',
      link: '/es/'
    }
  }
})
The lang attribute is added to the <html> tag for proper language detection by browsers and screen readers.

Locale-Specific Configuration

Override site configuration per locale:
1

Title and Description

export default defineConfig({
  locales: {
    root: {
      label: 'English',
      lang: 'en',
      title: 'VitePress',
      description: 'Vite & Vue powered static site generator'
    },
    es: {
      label: 'Español',
      lang: 'es',
      title: 'VitePress',
      description: 'Generador de sitios estáticos con Vite y Vue'
    }
  }
})
2

Theme Configuration

export default defineConfig({
  locales: {
    root: {
      label: 'English',
      themeConfig: {
        nav: [
          { text: 'Guide', link: '/guide/' },
          { text: 'API', link: '/api/' }
        ],
        sidebar: {
          '/guide/': [
            { text: 'Introduction', link: '/guide/' }
          ]
        }
      }
    },
    es: {
      label: 'Español',
      themeConfig: {
        nav: [
          { text: 'Guía', link: '/es/guide/' },
          { text: 'API', link: '/es/api/' }
        ],
        sidebar: {
          '/es/guide/': [
            { text: 'Introducción', link: '/es/guide/' }
          ]
        }
      }
    }
  }
})
3

Custom Head Tags

export default defineConfig({
  locales: {
    root: {
      label: 'English',
      head: [
        ['link', { rel: 'alternate', hreflang: 'es', href: 'https://example.com/es/' }],
        ['link', { rel: 'alternate', hreflang: 'fr', href: 'https://example.com/fr/' }]
      ]
    }
  }
})

Configurable Properties

These properties can be overridden per locale:
interface LocaleSpecificConfig<ThemeConfig = any> {
  lang?: string
  dir?: string
  title?: string
  titleTemplate?: string | boolean
  description?: string
  head?: HeadConfig[]
  themeConfig?: ThemeConfig
}
  • lang: HTML language attribute (e.g., 'en', 'es', 'zh-CN')
  • dir: Text direction ('ltr' or 'rtl')
  • title: Site title for this locale
  • titleTemplate: Title format template
  • description: Meta description
  • head: Additional head tags (merged with global head)
  • themeConfig: Theme configuration (shallow merged with global config)
Don’t override themeConfig.algolia or themeConfig.carbonAds at the locale level. See Algolia i18n docs for search localization.

Separate Directory Setup

When using separate directories for all locales, configure server redirects.

Netlify Example

Create docs/public/_redirects:
/*  /es/:splat  302  Language=es
/*  /fr/:splat  302  Language=fr
/*  /en/:splat  302
Persist language choice with a cookie:
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import Layout from './Layout.vue'

export default {
  extends: DefaultTheme,
  Layout
}
This works with Netlify’s Language-based redirects using the nf_lang cookie.

Default Theme Localization

The default theme includes built-in text labels that can be localized:
export default defineConfig({
  locales: {
    es: {
      label: 'Español',
      lang: 'es',
      themeConfig: {
        // Navigation
        nav: [...],
        sidebar: {...},
        
        // Text labels
        outlineTitle: 'En esta página',
        lastUpdatedText: 'Última actualización',
        docFooter: {
          prev: 'Página anterior',
          next: 'Próxima página'
        },
        
        // Search
        darkModeSwitchLabel: 'Apariencia',
        lightModeSwitchTitle: 'Cambiar a modo claro',
        darkModeSwitchTitle: 'Cambiar a modo oscuro',
        sidebarMenuLabel: 'Menú',
        returnToTopLabel: 'Volver arriba',
        
        // Edit link
        editLink: {
          pattern: 'https://github.com/user/repo/edit/main/docs/:path',
          text: 'Editar esta página en GitHub'
        }
      }
    }
  }
})
See the VitePress docs configuration for real examples: docs/.vitepress/config.ts in the source repository.

RTL (Right-to-Left) Support

For RTL languages like Arabic or Hebrew:
1

Set Direction

export default defineConfig({
  locales: {
    ar: {
      label: 'العربية',
      lang: 'ar',
      dir: 'rtl'
    }
  }
})
2

Install PostCSS Plugin

Choose an RTL CSS plugin:
npm add -D postcss-rtlcss
3

Configure PostCSS

// postcss.config.js
import rtlcss from 'postcss-rtlcss'

export default {
  plugins: [
    rtlcss({
      ltrPrefix: ':where([dir="ltr"])',
      rtlPrefix: ':where([dir="rtl"])'
    })
  ]
}
RTL support is experimental. Test thoroughly and use :where() selectors to prevent CSS specificity issues.

Organizing Multi-Locale Config

For better organization, split configuration by locale:
// .vitepress/config/index.ts
import { defineConfig } from 'vitepress'
import { en } from './en'
import { es } from './es'
import { fr } from './fr'

export default defineConfig({
  // Shared config
  title: 'VitePress',
  base: '/',
  
  locales: {
    root: { label: 'English', ...en },
    es: { label: 'Español', ...es },
    fr: { label: 'Français', ...fr }
  }
})
// .vitepress/config/en.ts
import type { DefaultTheme, LocaleSpecificConfig } from 'vitepress'

export const en: LocaleSpecificConfig<DefaultTheme.Config> = {
  lang: 'en',
  title: 'VitePress',
  description: 'Vite & Vue powered static site generator',
  
  themeConfig: {
    nav: [
      { text: 'Guide', link: '/guide/' },
      { text: 'API', link: '/api/' }
    ],
    sidebar: {
      '/guide/': [
        {
          text: 'Introduction',
          items: [
            { text: 'What is VitePress?', link: '/guide/what-is-vitepress' },
            { text: 'Getting Started', link: '/quickstart' }
          ]
        }
      ]
    }
  }
}
// .vitepress/config/es.ts
import type { DefaultTheme, LocaleSpecificConfig } from 'vitepress'

export const es: LocaleSpecificConfig<DefaultTheme.Config> = {
  lang: 'es',
  title: 'VitePress',
  description: 'Generador de sitios estáticos con Vite y Vue',
  
  themeConfig: {
    nav: [
      { text: 'Guía', link: '/es/guide/' },
      { text: 'API', link: '/es/api/' }
    ],
    sidebar: {
      '/es/guide/': [
        {
          text: 'Introducción',
          items: [
            { text: '¿Qué es VitePress?', link: '/es/guide/what-is-vitepress' },
            { text: 'Primeros Pasos', link: '/es/quickstart' }
          ]
        }
      ]
    }
  }
}

Translation Workflow

1

Create Source Content

Write your content in the default language:
docs/
└─ guide/
   └─ getting-started.md
2

Duplicate Structure

Create the same structure for each locale:
docs/
├─ guide/
│  └─ getting-started.md
└─ es/
   └─ guide/
      └─ getting-started.md
3

Translate Content

Translate the content while preserving:
  • File names (or update links accordingly)
  • Frontmatter structure
  • Internal link paths (add locale prefix)
4

Update Configuration

Add locale-specific navigation and sidebar

Best Practices

Keep the same file structure across all locales for easier maintenance:
✅ Good
docs/
├─ guide/
│  ├─ index.md
│  └─ advanced.md
└─ es/
   └─ guide/
      ├─ index.md
      └─ advanced.md

❌ Inconsistent
docs/
├─ guide/
│  └─ index.md
└─ es/
   └─ guia/  # Different directory name
      └─ inicio.md  # Different file name
For dynamic content, consider using a translation key system:
// .vitepress/theme/translations.ts
export const translations = {
  en: {
    readMore: 'Read more',
    publishedOn: 'Published on'
  },
  es: {
    readMore: 'Leer más',
    publishedOn: 'Publicado el'
  }
}
Ensure all locales build successfully:
vitepress build docs
Check for:
  • Broken internal links
  • Missing translations
  • Layout issues with longer text
  • RTL rendering (if applicable)

Language Selector

The default theme automatically adds a language selector to the navigation when multiple locales are configured.

Customizing the Selector

Control the selector appearance:
export default defineConfig({
  locales: {
    root: {
      label: 'English',
      lang: 'en'
    },
    es: {
      label: 'Español',
      lang: 'es',
      link: '/es/'  // Shows in language menu
    }
  },
  
  themeConfig: {
    // Optional: customize selector text
    langMenuLabel: 'Change language'
  }
})

Accessing Current Locale

In Vue components, access the current locale:
<script setup>
import { useData } from 'vitepress'

const { lang, localeIndex } = useData()

console.log(lang.value) // 'en', 'es', etc.
console.log(localeIndex.value) // 'root', 'es', etc.
</script>

<template>
  <div>
    Current language: {{ lang }}
  </div>
</template>

SEO Considerations

1

hreflang Tags

Add alternate language tags:
export default defineConfig({
  transformHead({ pageData }) {
    const head: HeadConfig[] = []
    
    if (pageData.relativePath.startsWith('es/')) {
      head.push(['link', { 
        rel: 'alternate', 
        hreflang: 'en', 
        href: `https://example.com/${pageData.relativePath.replace('es/', '')}` 
      }])
    }
    
    return head
  }
})
2

Canonical URLs

Set canonical URLs to avoid duplicate content:
---
head:
  - - link
    - rel: canonical
      href: https://example.com/guide/
---
3

Sitemap

Generate locale-aware sitemaps:
export default defineConfig({
  sitemap: {
    hostname: 'https://example.com',
    transformItems(items) {
      return items.map(item => ({
        ...item,
        // Add locale info
        links: [
          { lang: 'en', url: item.url },
          { lang: 'es', url: item.url.replace(/^(\/en)?/, '/es') }
        ]
      }))
    }
  }
})
See the official VitePress site configuration for a production example: docs/.vitepress/config.ts

Build docs developers (and LLMs) love