Skip to content

国际化实战案例

本教程将带你从零搭建一个完整的中英文双语 VitePress 站点,包含翻译管理、自动翻译工具集成和多语言 SEO 优化。

项目结构

目录结构

docs/
├── .vitepress/
│   ├── config.mts          # 主配置(含多语言设置)
│   ├── locales/            # 语言配置
│   │   ├── zh.ts           # 中文配置
│   │   └── en.ts           # 英文配置
│   └── theme/
│       └── index.ts
├── zh/                     # 中文文档
│   ├── index.md            # 中文首页
│   ├── guide/
│   │   └── getting-started.md
│   └── about.md
├── en/                     # 英文文档
│   ├── index.md            # 英文首页
│   ├── guide/
│   │   └── getting-started.md
│   └── about.md
└── public/
    └── images/

VitePress 国际化机制

VitePress 通过 locales 配置实现多语言:

  • root - 默认语言(本例为中文)
  • en - 英文语言(路径前缀 /en/

配置文件

主配置

ts
// docs/.vitepress/config.mts
import { defineConfig } from 'vitepress'
import { zh } from './locales/zh'
import { en } from './locales/en'

export default defineConfig({
  title: 'My Docs',

  // 多语言配置
  locales: {
    root: {
      label: '简体中文',
      lang: 'zh-CN',
      link: '/',
      ...zh
    },
    en: {
      label: 'English',
      lang: 'en-US',
      link: '/en/',
      ...en
    }
  },

  // 通用配置
  lastUpdated: true,
  cleanUrls: true,

  head: [
    ['meta', { name: 'theme-color', content: '#6366f1' }]
  ]
})

中文配置

ts
// docs/.vitepress/locales/zh.ts
import type { DefaultTheme } from 'vitepress'

export const zh: DefaultTheme.Config = {
  description: '中文文档描述',
  themeConfig: {
    nav: [
      { text: '首页', link: '/' },
      { text: '指南', link: '/guide/getting-started' },
      { text: '关于', link: '/about' }
    ],

    sidebar: {
      '/guide/': [
        {
          text: '指南',
          items: [
            { text: '快速开始', link: '/guide/getting-started' },
            { text: '进阶配置', link: '/guide/advanced' }
          ]
        }
      ]
    },

    editLink: {
      pattern: 'https://github.com/your-repo/edit/main/docs/:path',
      text: '编辑此页'
    },

    docFooter: {
      prev: '上一页',
      next: '下一页'
    },

    outline: {
      label: '页面导航'
    },

    lastUpdated: {
      text: '最后更新于',
      formatOptions: {
        dateStyle: 'short',
        timeStyle: 'medium'
      }
    },

    returnToTopLabel: '返回顶部',
    sidebarMenuLabel: '菜单',
    darkModeSwitchLabel: '主题',

    footer: {
      message: '基于 MIT 许可发布',
      copyright: 'Copyright © 2024-present'
    }
  }
}

英文配置

ts
// docs/.vitepress/locales/en.ts
import type { DefaultTheme } from 'vitepress'

export const en: DefaultTheme.Config = {
  description: 'English documentation description',
  themeConfig: {
    nav: [
      { text: 'Home', link: '/en/' },
      { text: 'Guide', link: '/en/guide/getting-started' },
      { text: 'About', link: '/en/about' }
    ],

    sidebar: {
      '/en/guide/': [
        {
          text: 'Guide',
          items: [
            { text: 'Getting Started', link: '/en/guide/getting-started' },
            { text: 'Advanced', link: '/en/guide/advanced' }
          ]
        }
      ]
    },

    editLink: {
      pattern: 'https://github.com/your-repo/edit/main/docs/:path',
      text: 'Edit this page'
    },

    docFooter: {
      prev: 'Previous',
      next: 'Next'
    },

    outline: {
      label: 'On this page'
    },

    lastUpdated: {
      text: 'Last updated',
      formatOptions: {
        dateStyle: 'short',
        timeStyle: 'medium'
      }
    },

    returnToTopLabel: 'Return to top',
    sidebarMenuLabel: 'Menu',
    darkModeSwitchLabel: 'Theme',

    footer: {
      message: 'Released under the MIT License.',
      copyright: 'Copyright © 2024-present'
    }
  }
}

页面内容

中文首页

markdown
---
layout: home

hero:
  name: "我的文档"
  text: "开发指南"
  tagline: 简洁高效的技术文档
  actions:
    - theme: brand
      text: 快速开始
      link: /guide/getting-started
    - theme: alt
      text: 查看 GitHub
      link: https://github.com/your-repo

features:
  - title: 🚀 快速
    details: 基于 Vite 构建,极速的开发体验
  - title: 🎨 美观
    details: 默认主题优雅美观,支持自定义
  - title: 🌐 国际化
    details: 内置多语言支持,轻松构建多语言站点
---

英文首页

markdown
---
layout: home

hero:
  name: "My Docs"
  text: "Developer Guide"
  tagline: Simple and efficient technical documentation
  actions:
    - theme: brand
      text: Get Started
      link: /en/guide/getting-started
    - theme: alt
      text: View on GitHub
      link: https://github.com/your-repo

features:
  - title: 🚀 Fast
    details: Built on Vite for lightning fast development
  - title: 🎨 Beautiful
    details: Elegant default theme with customization support
  - title: 🌐 i18n
    details: Built-in i18n support for multilingual sites
---

指南页面(中英对照)

中文版本 docs/zh/guide/getting-started.md

markdown
---
title: 快速开始
---

# 快速开始

本指南将帮助你快速上手项目开发。

## 环境要求

- Node.js 18+
- npm 9+

## 安装

\`\`\`bash
npm install
\`\`\`

## 启动开发服务器

\`\`\`bash
npm run dev
\`\`\`

访问 `http://localhost:5173` 查看文档。

::: tip 提示
建议使用 VS Code 并安装 Volar 插件以获得最佳开发体验。
:::

英文版本 docs/en/guide/getting-started.md

markdown
---
title: Getting Started
---

# Getting Started

This guide will help you get started with the project development.

## Prerequisites

- Node.js 18+
- npm 9+

## Installation

\`\`\`bash
npm install
\`\`\`

## Start Development Server

\`\`\`bash
npm run dev
\`\`\`

Visit `http://localhost:5173` to view the documentation.

::: tip
We recommend using VS Code with the Volar extension for the best development experience.
:::

语言切换组件

添加语言切换下拉菜单

VitePress 默认提供语言切换功能,但可以自定义:

vue
<!-- docs/.vitepress/components/LanguageSwitcher.vue -->
<script setup lang="ts">
import { useData } from 'vitepress'

const { lang, locales } = useData()

const languages = [
  { label: '简体中文', lang: 'zh-CN', link: '/' },
  { label: 'English', lang: 'en-US', link: '/en/' }
]
</script>

<template>
  <div class="language-switcher">
    <select
      :value="lang"
      @change="(e) => {
        const target = e.target as HTMLSelectElement
        const lang = languages.find(l => l.lang === target.value)
        if (lang) window.location.href = lang.link
      }"
    >
      <option v-for="lang in languages" :key="lang.lang" :value="lang.lang">
        {{ lang.label }}
      </option>
    </select>
  </div>
</template>

<style scoped>
.language-switcher select {
  background: var(--vp-c-bg);
  border: 1px solid var(--vp-c-divider);
  border-radius: 4px;
  padding: 4px 8px;
  font-size: 14px;
  cursor: pointer;
}
</style>

自动翻译工具

方案一:使用 AI 翻译

创建翻译脚本,使用 OpenAI API 进行翻译:

ts
// scripts/translate.ts
import fs from 'fs'
import path from 'path'
import OpenAI from 'openai'

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY
})

async function translate(content: string): Promise<string> {
  const response = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [
      {
        role: 'system',
        content: `You are a professional translator. Translate the following Markdown content from Chinese to English.
Keep all Markdown syntax, code blocks, and frontmatter unchanged.
Only translate the text content. Maintain the original formatting.`
      },
      { role: 'user', content }
    ]
  })

  return response.choices[0].message.content || ''
}

async function translateFile(inputPath: string, outputPath: string) {
  const content = fs.readFileSync(inputPath, 'utf-8')
  const translated = await translate(content)
  fs.mkdirSync(path.dirname(outputPath), { recursive: true })
  fs.writeFileSync(outputPath, translated, 'utf-8')
  console.log(`✅ Translated: ${inputPath} -> ${outputPath}`)
}

// 批量翻译
async function translateAll() {
  const zhDir = 'docs/zh'
  const enDir = 'docs/en'

  const files = getAllMarkdownFiles(zhDir)

  for (const file of files) {
    const relativePath = path.relative(zhDir, file)
    const outputPath = path.join(enDir, relativePath)
    await translateFile(file, outputPath)
  }
}

function getAllMarkdownFiles(dir: string): string[] {
  const files: string[] = []
  const entries = fs.readdirSync(dir, { withFileTypes: true })

  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name)
    if (entry.isDirectory()) {
      files.push(...getAllMarkdownFiles(fullPath))
    } else if (entry.name.endsWith('.md')) {
      files.push(fullPath)
    }
  }

  return files
}

translateAll()

方案二:使用翻译平台 API

DeepL 集成:

ts
// scripts/deepl-translate.ts
import fs from 'fs'
import axios from 'axios'

const DEEPL_API_KEY = process.env.DEEPL_API_KEY

async function translateWithDeepL(text: string): Promise<string> {
  const response = await axios.post(
    'https://api-free.deepl.com/v2/translate',
    new URLSearchParams({
      auth_key: DEEPL_API_KEY!,
      text,
      source_lang: 'ZH',
      target_lang: 'EN'
    })
  )

  return response.data.translations[0].text
}

package.json 脚本

json
{
  "scripts": {
    "i18n:translate": "tsx scripts/translate.ts",
    "i18n:sync": "tsx scripts/sync-translations.ts",
    "i18n:check": "tsx scripts/check-translations.ts"
  }
}

翻译一致性检查

检查脚本

ts
// scripts/check-translations.ts
import fs from 'fs'
import path from 'path'

function checkTranslationStatus() {
  const zhDir = 'docs/zh'
  const enDir = 'docs/en'

  const zhFiles = getAllMarkdownFiles(zhDir)
  const enFiles = getAllMarkdownFiles(enDir)

  const zhRelative = zhFiles.map(f => path.relative(zhDir, f))
  const enRelative = enFiles.map(f => path.relative(enDir, f))

  const missing = zhRelative.filter(f => !enRelative.includes(f))
  const extra = enRelative.filter(f => !zhRelative.includes(f))

  console.log('📊 Translation Status Report\n')
  console.log(`Chinese files: ${zhFiles.length}`)
  console.log(`English files: ${enFiles.length}`)

  if (missing.length) {
    console.log('\n❌ Missing English translations:')
    missing.forEach(f => console.log(`  - ${f}`))
  }

  if (extra.length) {
    console.log('\n⚠️  Extra English files (no Chinese source):')
    extra.forEach(f => console.log(`  - ${f}`))
  }

  if (!missing.length && !extra.length) {
    console.log('\n✅ All translations are in sync!')
  }
}

function getAllMarkdownFiles(dir: string): string[] {
  if (!fs.existsSync(dir)) return []

  const files: string[] = []
  const entries = fs.readdirSync(dir, { withFileTypes: true })

  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name)
    if (entry.isDirectory()) {
      files.push(...getAllMarkdownFiles(fullPath))
    } else if (entry.name.endsWith('.md')) {
      files.push(fullPath)
    }
  }

  return files
}

checkTranslationStatus()

多语言 SEO

hreflang 标签

config.mts 中添加 transformHead 钩子:

ts
// docs/.vitepress/config.mts
export default defineConfig({
  transformHead({ pageData }) {
    const url = 'https://your-domain.com'
    const path = pageData.relativePath.replace(/\.md$/, '')

    // 判断当前页面语言
    const isEnglish = path.startsWith('en/')
    const zhPath = isEnglish ? path.replace('en/', '') : path
    const enPath = isEnglish ? path : `en/${path}`

    return [
      // 中文版本的 hreflang
      ['link', { rel: 'alternate', hreflang: 'zh-CN', href: `${url}/${zhPath}` }],
      // 英文版本的 hreflang
      ['link', { rel: 'alternate', hreflang: 'en', href: `${url}/${enPath}` }],
      // 默认语言
      ['link', { rel: 'alternate', hreflang: 'x-default', href: `${url}/${zhPath}` }]
    ]
  }
})

sitemap 多语言

VitePress 自动生成的 sitemap 包含多语言页面。如需自定义:

ts
// docs/.vitepress/config.mts
export default defineConfig({
  sitemap: {
    hostname: 'https://your-domain.com',
    transformItems(items) {
      return items.map(item => {
        // 添加多语言链接
        if (item.url.startsWith('/en/')) {
          item.links = [
            { lang: 'zh-CN', url: item.url.replace('/en/', '/') }
          ]
        } else {
          item.links = [
            { lang: 'en', url: `/en${item.url}` }
          ]
        }
        return item
      })
    }
  }
})

进阶技巧

条件渲染多语言内容

vue
<!-- docs/.vitepress/components/LocalizedContent.vue -->
<script setup lang="ts">
import { useData } from 'vitepress'

const { lang } = useData()
</script>

<template>
  <div v-if="lang === 'zh-CN'">
    这是中文内容
  </div>
  <div v-else>
    This is English content
  </div>
</template>

共享组件翻译

ts
// docs/.vitepress/locales/messages.ts
export const messages = {
  'zh-CN': {
    readMore: '阅读更多',
    lastUpdated: '最后更新',
    contributors: '贡献者'
  },
  'en-US': {
    readMore: 'Read More',
    lastUpdated: 'Last Updated',
    contributors: 'Contributors'
  }
}
vue
<!-- 使用翻译 -->
<script setup lang="ts">
import { useData } from 'vitepress'
import { messages } from '../locales/messages'

const { lang } = useData()
const t = (key: string) => messages[lang as keyof typeof messages]?.[key as keyof typeof messages['zh-CN']] || key
</script>

<template>
  <span>{{ t('readMore') }}</span>
</template>

进阶挑战

挑战难度说明
自动翻译 CI 集成⭐⭐⭐PR 更新中文时自动翻译并提交英文版本
翻译记忆系统⭐⭐⭐⭐使用 PO 文件或 JSON 管理翻译,避免重复翻译
实时预览翻译⭐⭐⭐⭐⭐开发时实时调用翻译 API 预览效果

下一步

贡献者

加载中...

想要成为贡献者?

在 CNB 上参与贡献