Skip to content

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.md

Then in docs/.vitepress/config.ts:

docs/.vitepress/config.ts
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):

ts
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:

docs/.vitepress/config.ts
ts
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.md

However, 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  302

Pro tip: If using the above approach, you can use nf_lang cookie to persist user's language choice:

docs/.vitepress/theme/index.ts
ts
import DefaultTheme from 'vitepress/theme'
import Layout from './Layout.vue'

export default {
  extends: DefaultTheme,
  Layout
}
docs/.vitepress/theme/Layout.vue
vue
<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.

Released under the MIT License.