为MKdocs添加多语言翻译

173 阅读4分钟

为 MkDocs 添加多语言翻译功能

在全球化的今天,为网站添加多语言支持已成为提升用户体验的重要手段。本文将详细介绍如何为 MkDocs 网站添加基于 JavaScript 的客户端翻译功能,支持多种语言的实时切换。

个人博客:wcowin.work/


特别感谢Aaron对翻译方案的提议与实践👋

🌍 方案概述

我们采用的翻译方案具有以下特点:

  • 🚀 客户端翻译:使用 translate.js 库实现前端翻译
  • 🌐 多语言支持:支持中文、英文、日文、韩文、阿拉伯文等 9 种语言
  • ⚡ 实时切换:无需刷新页面即可切换语言
  • 🎨 样式优化:针对不同语言进行样式适配
  • 📱 响应式支持:移动端和桌面端都有优化

📋 实现步骤

1. 引入翻译库

首先在页脚或头部模板(例如:docs/overrides/partials/footer.html)中引入 translate.js 库:

<!-- 引入翻译库 -->
<script src="https://cdn.staticfile.net/translate.js/3.12.0/translate.js"></script>

2. 配置翻译参数

创建翻译配置脚本,设置基本参数:

<script>
(function() {
  // 设置不翻译的元素类名
  translate.ignore.class.push(
    'md-select',           // Material 选择框
    'footer-highlight',    // 页脚高亮文本
    'md-footer-copyright', // 版权信息
    'no-translate'         // 通用不翻译类
  );
  
  // 设置本地语种(默认中文简体)
  translate.language.setLocal('chinese_simplified');
  
  // 自动识别用户首选语言
  translate.setAutoDiscriminateLocalLanguage();
  
  // 隐藏默认语言选择框
  translate.selectLanguageTag.show = false;
  
  // 设置翻译服务通道
  translate.service.use('client.edge');
  
  // 执行翻译初始化
  translate.execute();
  
  console.log('翻译功能已初始化');
})();
</script>

目录结构如下:

$ tree -a
.
├── .github
│   ├── .DS_Store
│   └── workflows
│       └── ci.yml
├── docs
│   └── index.md
│   └──overrides
│       └──assets
│       └──partials
│          └──footer.html
│
└── mkdocs.yml

tip "重点提示" 请参考下方的footer.html示例代码

页脚教程: [Mkdocs页脚设计](../../blog/websitebeauty/footer.md)

footer.html示例代码(无需修改,直接复制粘贴即可使用):

{#-
  This file was automatically generated - do not edit
-#}
<footer class="md-footer">
  {% if "navigation.footer" in features %}
    {% if page.previous_page or page.next_page %}
      {% if page.meta and page.meta.hide %}
        {% set hidden = "hidden" if "footer" in page.meta.hide %}
      {% endif %}
      <nav class="md-footer__inner md-grid" aria-label="{{ lang.t('footer') }}" {{ hidden }}>
        {% if page.previous_page %}
          {% set direction = lang.t("footer.previous") %}
          <a href="{{ page.previous_page.url | url }}" class="md-footer__link md-footer__link--prev" aria-label="{{ direction }}: {{ page.previous_page.title | e }}">
            <div class="md-footer__button md-icon">
              {% set icon = config.theme.icon.previous or "material/arrow-left" %}
              {% include ".icons/" ~ icon ~ ".svg" %}
            </div>
            <div class="md-footer__title">
              <span class="md-footer__direction">
                {{ direction }}
              </span>
              <div class="md-ellipsis">
                {{ page.previous_page.title }}
              </div>
            </div>
          </a>
        {% endif %}
        {% if page.next_page %}
          {% set direction = lang.t("footer.next") %}
          <a href="{{ page.next_page.url | url }}" class="md-footer__link md-footer__link--next" aria-label="{{ direction }}: {{ page.next_page.title | e }}">
            <div class="md-footer__title">
              <span class="md-footer__direction">
                {{ direction }}
              </span>
              <div class="md-ellipsis">
                {{ page.next_page.title }}
              </div>
            </div>
            <div class="md-footer__button md-icon">
              {% set icon = config.theme.icon.next or "material/arrow-right" %}
              {% include ".icons/" ~ icon ~ ".svg" %}
            </div>
          </a>
        {% endif %}
      </nav>
    {% endif %}
  {% endif %}
  <div class="md-footer-meta md-typeset">
    <div class="md-footer-meta__inner md-grid">
      {% include "partials/copyright.html" %}
            <script src="https://cdn.staticfile.net/translate.js/3.12.0/translate.js"></script>
      <script>
        // 翻译配置 - 包装在立即执行函数中避免全局污染
        (function() {
          // 通过 class 设置不翻译的元素
          translate.ignore.class.push('md-select', 'footer-highlight', 'md-footer-copyright');
          // 自定义术语库,纠正翻译结果
          translate.nomenclature.append('chinese_simplified','english',`
                快讯=Newsflash
                访问量=Page Views
                本站已经运行=Site has been running
                天=days
                时=hours
                分=minutes
                秒=seconds
                萌ICP备=Moe ICP
                版权所有=Copyright
                制作工具=Made with
        `);
          // 设置本地语种
          translate.language.setLocal('chinese_simplified');     
          // 设置首次使用时自动识别语种
          translate.setAutoDiscriminateLocalLanguage();
          // 不显示 select 语言选择框
          translate.selectLanguageTag.show = false;
          // 设置机器翻译服务通道
          translate.service.use('client.edge');
          // 执行翻译
          translate.execute();
          // 监听语言切换事件,确保动态内容也能被翻译
          window.addEventListener('translate.languagechange', function() {
            // 延迟重新翻译动态更新的内容
            setTimeout(() => {
              translate.execute();
            }, 500);
          });
          console.log('翻译功能已初始化');
        })();
      </script>
      {% if config.extra.social %}
        {% include "partials/social.html" %}
      {% endif %}
    </div>
  </div>
</footer>

3. 在 mkdocs.yml 中配置语言切换

首先在mkdocs.yml文件中添加custom_dir:

theme:
  name: material
  custom_dir: docs/overrides #覆写路径

然后在配置文件中添加多语言切换选项:


theme:
  name: material
  language: zh
  features:
    - navigation.tabs
    - navigation.sections
  
extra:
  alternate:
    - name: 中文
      link: "javascript:translate.changeLanguage('chinese_simplified');"
      lang: zh
    - name: English
      link: "javascript:translate.changeLanguage('english');"
      lang: en
    - name: 한국어
      link: "javascript:translate.changeLanguage('korean');"
      lang: ko
    - name: 日本語
      link: "javascript:translate.changeLanguage('japanese');"
      lang: ja
    - name: بالعربية
      link: "javascript:translate.changeLanguage('arabic');"
      lang: ar
    - name: Deutsch
      link: "javascript:translate.changeLanguage('german');"
      lang: de
    - name: Français
      link: "javascript:translate.changeLanguage('french');"
      lang: fr
    - name: Español
      link: "javascript:translate.changeLanguage('spanish');"
      lang: es
    - name: português
      link: "javascript:translate.changeLanguage('portuguese');"
      lang: pt

至此已经配置完成。简单!快速!



后续步骤皆为可选,根据需求进行配置。可以不看。

4. 添加自定义术语库

为了提高翻译准确性,添加专业术语映射:

// 自定义术语库配置
const nomenclatures = {
  english: `
访问量=Page Views
本站已经运行=Site has been running for
天=days
时=hours
分=minutes
秒=seconds
版权所有=Copyright
制作工具=Made with
统计中=Loading
`,
  japanese: `
访问量=アクセス数
本站已经运行=サイト運営期間
天=日
时=時間
分=分
秒=秒
版权所有=著作権
制作工具=制作ツール
`,
  korean: `
访问量=방문수
本站已经运行=사이트 운영 기간
天=일
时=시간
分=분
秒=초
版权所有=저작권
制作工具=제작 도구
`
  // ... 其他语言配置
};

// 批量添加术语库
Object.entries(nomenclatures).forEach(([lang, terms]) => {
  translate.nomenclature.append('chinese_simplified', lang, terms);
});

5. 处理动态内容翻译

对于实时更新的内容(如计时器、访问量等),需要特殊处理:

// 监听语言切换事件
window.addEventListener('translate.languagechange', function() {
  // 延迟重新翻译动态内容
  setTimeout(() => {
    translate.execute();
  }, 500);
});

// 在动态内容更新时触发翻译
function updateDynamicContent() {
  // 更新内容...
  
  // 如果当前不是中文,重新执行翻译
  if (window.translate && translate.currentLanguage !== 'chinese_simplified') {
    setTimeout(() => translate.execute(), 100);
  }
}

🎨 多语言样式优化

1. 基础响应式样式

为不同语言文本长度进行适配:

/* 为翻译后的元素添加基础样式 */
[data-translate-lang]:not([data-translate-lang="chinese_simplified"]) .footer-item {
  text-align: center;
  justify-content: center;
  min-height: 2.5rem;
  padding: 0.5rem 1rem;
  line-height: 1.4;
}

/* 移动端长文本优化 */
@media (max-width: 768px) {
  [data-translate-lang]:not([data-translate-lang="chinese_simplified"]) .footer-visit-count-mobile {
    flex-direction: column;
    gap: 0.4em;
    line-height: 1.6;
  }
}

2. 特定语言样式优化

针对不同语言的特殊需求:

/* 英文样式优化 */
[data-translate-lang="english"] .footer-item {
  font-size: 0.75rem;
  word-spacing: 0.15em;
}

/* 日文样式优化 */
[data-translate-lang="japanese"] .footer-item {
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  line-height: 1.5;
}

/* 阿拉伯文 RTL 支持 */
[data-translate-lang="arabic"] .footer-wrapper {
  direction: rtl;
  text-align: center;
}

[data-translate-lang="arabic"] .footer-item {
  direction: rtl;
  text-align: center;
  word-spacing: 0.2em;
}

3. 深色模式兼容

确保所有语言在深色模式下的显示效果:

/* 深色模式下的翻译优化 */
[data-md-color-scheme="slate"] .footer-counter-value,
[data-md-color-scheme="slate"] .no-translate {
  color: #e2e8f0 !important;
}

/* 高对比度模式支持 */
@media (prefers-contrast: high) {
  [data-translate-lang]:not([data-translate-lang="chinese_simplified"]) .footer-item {
    border-width: 2px;
    font-weight: 600;
  }
}

🔧 高级功能

1. 语言检测与样式应用

function applyLanguageStyles(lang) {
  // 设置语言属性
  document.documentElement.setAttribute('data-translate-lang', lang || 'chinese_simplified');
  
  // RTL 语言处理
  if (lang === 'arabic') {
    document.documentElement.setAttribute('dir', 'rtl');
    document.body.classList.add('rtl-language');
  } else {
    document.documentElement.setAttribute('dir', 'ltr');
    document.body.classList.remove('rtl-language');
  }
}

2. 数字和特殊内容保护

确保数字、链接等不被误翻译:

function protectSpecialContent() {
  // 保护数字不被翻译
  const highlightElements = document.querySelectorAll('.footer-highlight');
  highlightElements.forEach(el => {
    if (!el.classList.contains('no-translate')) {
      el.classList.add('no-translate');
    }
  });

  // 保护 ICP 号码
  const icpLinks = document.querySelectorAll('.icp-link');
  icpLinks.forEach(el => {
    const icpNumber = el.textContent.match(/\d+/);
    if (icpNumber) {
      el.innerHTML = el.innerHTML.replace(
        /(\d+)/g, 
        '<span class="no-translate">$1</span>'
      );
    }
  });
}

🚀 最佳实践

1. 性能优化

  • 延迟执行:翻译操作延迟执行,避免阻塞页面渲染
  • 避免重复翻译:检查是否已翻译过相同内容
  • 清理资源:页面切换时正确清理翻译相关资源

2. 用户体验优化

  • 记住用户选择:使用 localStorage 保存用户的语言偏好
  • 平滑过渡:添加过渡动画使语言切换更自然
  • 加载状态:在翻译过程中显示加载提示

3. 可访问性考虑

  • 屏幕阅读器支持:正确设置 lang 属性
  • 键盘导航:确保语言切换按钮可通过键盘访问
  • 对比度:保证翻译后文本的可读性

📱 移动端适配

1. 响应式布局

@media (max-width: 768px) {
  /* 长语言文本换行优化 */
  [data-translate-lang="german"] .footer-visit-count-mobile,
  [data-translate-lang="french"] .footer-visit-count-mobile {
    gap: 0.6em;
    line-height: 1.7;
  }
  
  /* 移动端语言切换优化 */
  .md-header__option {
    min-width: auto;
  }
}

2. 触摸友好设计

确保语言切换按钮在移动设备上易于点击:

/* 移动端按钮优化 */
@media (max-width: 768px) {
  .md-header__option {
    padding: 0.5rem;
    min-height: 44px; /* iOS 推荐的最小触摸目标 */
  }
}

🔍 故障排除

常见问题及解决方案

  1. 翻译不生效

    • 检查 translate.js 是否正确加载
    • 确认没有 JavaScript 错误阻止执行
  2. 动态内容不翻译

    • 在内容更新后调用 translate.execute()
    • 检查动态元素是否被忽略类名覆盖
  3. 样式错乱

    • 为不同语言添加对应的 CSS 规则
    • 检查文本长度差异导致的布局问题
  4. RTL 语言显示异常

    • 正确设置 dir="rtl" 属性
    • 为 RTL 语言添加特殊样式

📊 效果展示

实现后的效果:

2.gif

  • 多语言切换:支持 9 种语言实时切换
  • 样式适配:每种语言都有优化的显示效果
  • 响应式设计:桌面端和移动端都完美适配
  • 性能优良:客户端翻译,服务器无额外负担
  • 用户友好:无需刷新页面,切换流畅

🎯 总结

通过以上步骤,我们成功为 MkDocs 网站添加了完整的多语言翻译功能。这个方案具有以下优势:

  1. 实现简单:仅需几个文件修改即可完成
  2. 功能完整:支持多种语言和样式适配
  3. 性能优秀:客户端翻译,不增加服务器负担
  4. 用户体验佳:切换流畅,样式美观

虽然客户端翻译在 SEO 方面不如服务端多语言方案,但对于技术文档、个人博客等场景,这种方案提供了极佳的性价比和用户体验。


🔗 相关资源

希望这个教程能帮助你为自己的 MkDocs 网站添加多语言支持!如果遇到问题,欢迎在评论区讨论。