Internationalization
To use the built-in i18n features, one needs to create a directory structure as follows:
docs/
├─ es/
│ ├─ foo.md
├─ fr/
│ ├─ foo.md
├─ foo.mdThen in docs/.vitepress/config.ts:
import { defineConfig } from 'vitepress'
export default defineConfig({
// shared properties and other top-level stuff...
locales: {
root: {
label: 'English',
lang: 'en'
},
fr: {
label: 'French',
lang: 'fr', // optional, will be added as `lang` attribute on `html` tag
link: '/fr/guide' // default /fr/ -- shows on navbar translations menu, can be external
// other locale specific properties...
}
}
})The following properties can be overridden for each locale (including root):
interface LocaleSpecificConfig<ThemeConfig = any> {
lang?: string
dir?: string
title?: string
titleTemplate?: string | boolean
description?: string
head?: HeadConfig[] // will be merged with existing head entries, duplicate meta tags are automatically removed
themeConfig?: ThemeConfig // will be shallow merged, common stuff can be put in top-level themeConfig entry
}Refer DefaultTheme.Config interface for details on customizing the placeholder texts of the default theme. Don't override themeConfig.algolia or themeConfig.carbonAds at locale-level. Refer Algolia docs for using multilingual search.
Pro tip: Config file can be stored at docs/.vitepress/config/index.ts too. It might help you organize stuff by creating a configuration file per locale and then merge and export them from index.ts.
Per-locale Markdown Strings
Strings baked into pages by the markdown renderer - the default titles of custom containers and GitHub-flavored alerts, and the strings of the code copy button - can be overridden per locale with the markdown key of a locale entry:
import { defineConfig } from 'vitepress'
export default defineConfig({
locales: {
root: { label: 'English', lang: 'en' },
zh: {
label: '简体中文',
lang: 'zh-Hans',
markdown: {
container: {
tipLabel: '提示',
warningLabel: '警告'
// ...the other labels, and titles of `customContainers`
},
codeCopyButton: {
tooltipText: '复制代码',
copiedText: '已复制'
}
}
}
}
})Values fall back to the root-level markdown options when a locale leaves them unset. Locale entries can only override the titles of containers registered at the root level - registering new containers per locale is not supported. Also note that since the markdown renderer is created once for the whole site, these can only be declared in the main config file, not in additional configs.
Separate directory for each locale
The following is a perfectly fine structure:
docs/
├─ en/
│ ├─ foo.md
├─ es/
│ ├─ foo.md
├─ fr/
├─ foo.mdHowever, VitePress won't redirect / to /en/ by default. You'll need to configure your server for that. For example, on Netlify, you can add a docs/public/_redirects file like this:
/* /es/:splat 302 Language=es
/* /fr/:splat 302 Language=fr
/* /en/:splat 302Pro tip: If using the above approach, you can use nf_lang cookie to persist user's language choice:
import DefaultTheme from 'vitepress/theme'
import Layout from './Layout.vue'
export default {
extends: DefaultTheme,
Layout
}<script setup lang="ts">
import DefaultTheme from 'vitepress/theme'
import { useData, inBrowser } from 'vitepress'
import { watchEffect } from 'vue'
const { lang } = useData()
watchEffect(() => {
if (inBrowser) {
document.cookie = `nf_lang=${lang.value}; expires=Mon, 1 Jan 2030 00:00:00 UTC; path=/`
}
})
</script>
<template>
<DefaultTheme.Layout />
</template>RTL Support (Experimental)
For RTL support, specify dir: 'rtl' in config and use some RTLCSS PostCSS plugin like https://github.com/MohammadYounes/rtlcss, https://github.com/vkalinichev/postcss-rtl or https://github.com/elchininet/postcss-rtlcss. You'll need to configure your PostCSS plugin to use :where([dir="ltr"]) and :where([dir="rtl"]) as prefixes to prevent CSS specificity issues.