跳转至主内容
Version: 2.0.0-beta.4

i18n - 使用 Crowdin

Docusaurus 的 i18n 系统与其他翻译软件脱钩

只要您将翻译文件放置在正确的位置,您可以将 Docusaurus 与您所选择的任何工具或 SaaS 集成

本文中,我们将使用 Crowdin 作为其中一个可能的集成示例

caution

这不代表我们为 Crowdin 背书,也不代表这是翻译 Docusaurus 站点的唯一选择,但 Facebook 已成功使用它来翻译包括 JestDocusaurusReasonML 在内的诸多文档项目。

请参见 Crowdin 文档Crowdin 支持来寻求帮助。

tip

请使用此社区驱动的 GitHub 议题来研讨有关 Docusaurus + Crowdin 的事务。

Crowdin 概述#

Crowdin 是一款翻译 SaaS,为开源项目提供了免费套餐

我们推荐以下的翻译流程:

  • 上传源文件至 Crowdin(未翻译文件)
  • 使用 Crowdin 来翻译内容
  • 从 Crowdin 下载译文(本地化的翻译文件)

Crowdin 提供 CLI 工具来上传资源下载译文,助您自动化翻译流程。

Docusaurus 通常需要crowdin.yml 配置文件下载已翻译的文件至正确位置i18n/<语言>/.. 中)。

您可阅读官方文档来了解进阶功能及不同的翻译流程。

Crowdin 教程#

下方是使用 Crowdin 来翻译新创建的英文版 Docusaurus 站点至简体中文的手把手教程,本文假设您已遵循了 i18n 教程

最终结果可参见 docusaurus-crowdin-example.netlify.app代码仓库)。

准备 Docusaurus 站点#

初始化新的 Docusaurus 站点:

npx @docusaurus/init@latest init website classic

添加简体中文版网站的配置:

docusaurus.config.js
module.exports = {  i18n: {    defaultLocale: 'en',    locales: ['en', 'zh-cn'],  },  themeConfig: {    navbar: {      items: [        // ...        {          type: 'localeDropdown',          position: 'left',        },        // ...      ],    },  },  // ...};

翻译首页:

src/pages/index.js
import React from 'react';import Translate from '@docusaurus/Translate';import Layout from '@theme/Layout';
export default function Home() {  return (    <Layout>      <h1 style={{margin: 20}}>        <Translate description="The homepage main heading">          Welcome to my Docusaurus translated site!        </Translate>      </h1>    </Layout>  );}

创建 Crowdin 项目#

注册 Crowdin 账户,然后创建新项目。

使用英语为源语言,设置简体中文为目标语言。

创建 Crowdin 项目,使用英语作为源语言,简体中文作为目标语言

您的项目创建好了,但暂时空空如也。 我们将在下几步中上传待译文件。

创建 Crowdin 配置#

此配置(文档)提供了 Crowdin CLI 能理解的映射条件:

  • 何处寻找要上传的源文件(JSON 及 Markdown)
  • 将翻译后的文件下载至何处(在 i18n/<语言> 中)

website 中创建 crowdin.yml

crowdin.yml
project_id: '123456'api_token_env: 'CROWDIN_PERSONAL_TOKEN'preserve_hierarchy: truefiles: [    # JSON 翻译文件    {      source: '/i18n/en/**/*',      translation: '/i18n/%two_letters_code%/**/%original_file_name%',    },    # 文档 Markdown 文件    {      source: '/docs/**/*',      translation: '/i18n/%two_letters_code%/docusaurus-plugin-content-docs/current/**/%original_file_name%',    },    # 博客 Markdown 文件    {      source: '/blog/**/*',      translation: '/i18n/%two_letters_code%/docusaurus-plugin-content-blog/**/%original_file_name%',    },  ]

Crowdin 有自己的语法来声明源/翻译路径:

  • **/*:子文件夹中的所有内容
  • %two_letters_code%:Crowdin 目标语言的两字代码变体(本例中为 zh
  • **/%original_file_name%:翻译文件将保留原始文件夹/文件结构
信息

Crowdin CLI 的警告信息有时会晦涩难懂。

我们建议您:

  • 一次改一件事情
  • 配置更改后重新上传资源
  • 使用 / 开头的路径(无法使用 ./
  • 避免像 /docs/**/*.(md|mdx) 一类的花里胡哨表达式(无法使用)

访问令牌#

api_token_env 属性定义了 Crowdin CLI 所读取的环境变量名称

您可以在您的个人资料页获得您的个人访问令牌

tip

您也可以保留 CROWDIN_PERSONAL_TOKEN 的默认值,然后在您的电脑和 CI 服务器上将此环境变量设置为您的访问令牌。

caution

个人访问令牌有读写您所有 Crowdin 项目的权限。

不应该提交此令牌,同时我们推荐您创建另外的Crowdin 公司账户来替代您的个人账户。

其他配置字段#

  • project_id:您可硬编码此字段,同时您可在 https://crowdin.com/project/<我的项目名称>/settings#api 处找到
  • preserve_hierarchy:是否在 Crowdin UI 上保留您的文档目录结构,而不将所有文件放置于同一文件夹中

安装 Crowdin CLI#

此教程中的 CLI 版本为 3.5.2,但应该也适用于 3.x 的版本。

安装 Crowdin CLI 的 NPM 包至您的 Docusaurus 网站:

npm install @crowdin/cli@3

添加 crowdin 脚本:

package.json
{  "scripts": {    "crowdin": "crowdin"  }}

测试您是否可以运行 Crowdin CLI:

npm run crowdin -- --version

在您的计算机上设置 CROWDIN_PERSONAL_TOKEN 环境变量来让 CLI 通过 Crowdin API 进行认证。

tip

您可以暂时在 crowdin.yml 中写入 api_token: '我的令牌' 来硬代码您的个人令牌。

上传源文件#

website/i18n/en 中生成默认语言的 JSON 翻译文件:

npm run write-translations

上传所有 JSON 和 Markdown 翻译文件:

npm run crowdin upload

Crowdin CLI 上传 Docusaurus 源文件

您可在 Crowdin 界面上找到您的源文件:https://crowdin.com/project/<我的项目名称>/settings#files

Crowdin UI 显示 Docusaurus 源文件

翻译源文件#

https://crowdin.com/project/<我的项目名称>,点击简体中文语言。

Crowdin UI 显示简体中文翻译文件

翻译 Markdown 文件。

Crowdin UI 翻译 Markdown 文件

tip

使用隐藏字符串以确保翻译人员不翻译不该被翻译的内容:

  • Frontmatter: id, slug, tags...
  • 警告: :::, :::note, :::tip...

Crowdin UI 隐藏字符串

翻译 JSON 文件。

Crowdin UI 翻译 JSON 文件

信息

JSON 翻译文件的 description 属性在 Crowdin 上可见,可以用来协助翻译字符串。

tip

预翻译您的网站,再手动修复预翻译错误(请先在设置中启用全局翻译存储)。

预先使用隐藏字符串功能,因为 Crowdin 在预翻译上对自己太自信了。

下载译文#

使用 Crowdin CLI 下载已翻译的 JSON 和 Markdown文件。

npm run crowdin download

翻译的内容应该在 i18n/zh-CN 中下载。

使用简体中文启动您的网站:

npm run start -- --locale zh-cn

确保你的网站现在在这里已经被翻译成简体中文:http://localhost:3000/zh-cn/

使用持续集成 (CI) 来自动化翻译#

我们将配置 CI,使它在构建时下载 Crowdin 翻译,并将其保存在 Git 外。

website/i18n 添加到 .gitignore 中。

在你的 CI 服务上设置 CROWDIN_PERSONAL_TOKEN 环境变量。

创建一个 npm 脚本,完成 crowdin:sync 同步(提取源数据,上传源数据,下载翻译):

package.json
{  "scripts": {    "crowdin:sync": "docusaurus write-translations && crowdin upload && crowdin download"  }}

在构建 Docusaurus 网站之前,在 CI 中调用 npm run crowdin:sync 脚本。

tip

为了维持你的部署预览的速度,不要下载翻译,并且在功能分支上使用 npm run build --locale en

caution

Crowdin 对多个并行上传/下载的支持不太好:最好只将翻译内容包含到生产部署中,并且在部署预览时不要翻译。

Crowdin 进阶议题#

MDX#

caution

在 MDX 文档中,要格外关注 JSX 片段!

Crowdin 缺少官方 MDX 支持, 但他们支持 .mdx 的文件后缀名,并将它们解释为 Markdown(而不是纯文本)。

MDX 问题#

Crowdin 认为 JSX 语法是嵌入的 HTML,所以可能在你下载翻译时把 JSX 标记搞得一团糟, 导致网站因无效 JSX 而构建失败。

那些使用简单字符串属性的 JSX 片段,例如 <Username name="Sebastien"/>,可以正常工作。

更复杂的使用对象/数组属性的 JSX 片段,例如 <User person={{name: "Sebastien"}}/> 更可能失败,因为它的语法看起来不像 HTML。

MDX 解决方案#

我们建议将内嵌的复杂 JSX 代码分离成单独的组件。

我们还添加了一个 mdx-code-block 「安全出口」语法:

# How to deploy Docusaurus
To deploy Docusaurus, run the following command:
````mdx-code-blockimport Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs  defaultValue="bash"  values={[    { label: 'Bash', value: 'bash' },    { label: 'Windows', value: 'windows' }]}>  <TabItem value="bash">
  ```bash  GIT_USER=<GITHUB_USERNAME> yarn deploy  ```
  </TabItem>  <TabItem value="windows">
  ```batch  cmd /C "set "GIT_USER=<GITHUB_USERNAME>" && yarn deploy"  ```
  </TabItem></Tabs>````

这会:

  • 被 Crowdin 解释为代码块(因此不会在下载时搞出乱子)
  • 被 Docusaurus 解释为常规 JSX(就像它没有被任何代码块包裹一样)
  • 然而不幸地是,也同时放弃了其他 MDX 工具(IDE 语法高亮,Prettier...)

文档分版#

website/versioned_docs 文件夹中配置翻译文件。

创建新版本时,源字符串通常与当前版本(website/docs)非常相似,并且没人想一次次地翻译新版本的文档。

Crowdin 提供了Duplicate Strings设置。

Crowdin 重复字符串选项设置

我们建议使用 Hide,但最理想的设置取决于你的版本之间有多大不同。

caution

不使用 Hide 会导致配额中包含更多的 源字符串 ,从而影响 Crowdin 的价格。

多实例插件#

您需要为每个插件实例配置翻译文件。

若您有 id=ios 的文档插件实例,您也依然需要配置下列源文件:

  • website/ios
  • website/ios_versioned_docs(若分版)

维护网站#

有些时候,您需要在 Git 上移除或重命名源文件,此时 Crowdin CLI 会打印警告:

Crowdin CLI:下载翻译警告

当您重构完源码后,您应使用 Crowdin UI 来手动更新 Crowdin 文件

Crowdin UI:重命名文件

VCS(Git)集成#

Crowdin 已集成进了多个版本管理系统,如 GitHub、GitLab 和 Bitbucket。

warning

我们不推荐您使用。

在 Git 和 Crowdin 中同时编辑翻译文件,达成双向同步的效果可能对您有所帮助。

但实际上,这种做法并不可取,原因如下:

  • Crowdin -> Git 同步没有问题(合并请求)
  • Git -> Crowdin 需要手动操作(您需要手动点击)
  • Crowdin 无法做到 100% 准确的将已有的 Markdown 源文件及其译文关联起来,您随后需要在从 Git 同步后前往 Crowdin UI 验证结果
  • 多名用户同时在 Git 和 Crowdin 编辑可能会造成翻译丢失
  • 需要将 crowdin.yml 放置在仓库根目录

语境内本地化#

Crowdin 支持语境内本地化功能。

caution

遗憾的是,由于技术原因,此功能尚不能使用。但我们这个问题觉得解决起来并不麻烦。

Crowdin 会将 Markdown 字符串使用形如 crowdin:id12345 的编号替代,但也会替代包括隐藏字符串在内的特殊字符串,而且会弄乱前言、告示及 JSX 等内容。

本地化编辑链接#

当用户浏览位于 /zh-cn/doc1 的页面时,编辑按钮则将默认指向 website/docs/doc1.md 处的未翻译文档。

您可能更偏好将编辑按钮链接至 Crowdin 界面,并使用 editUrl 函数来自定义每种语言的翻译网址。

docusaurus.config.js
const DefaultLocale = 'en';
module.exports = {  presets: [    [      '@docusaurus/preset-classic',      {        docs: {          editUrl: ({locale, versionDocsDirPath, docPath}) => {            // Crowdin 的简体中文文档链接            if (locale !== DefaultLocale) {              return `https://crowdin.com/project/docusaurus-v2/${locale}`;            }            // Github 的英文文档链接            return `https://github.com/facebook/docusaurus/edit/master/website/${versionDocsDirPath}/${docPath}`;          },        },        blog: {          editUrl: ({locale, blogDirPath, blogPath}) => {            if (locale !== DefaultLocale) {              return `https://crowdin.com/project/docusaurus-v2/${locale}`;            }            return `https://github.com/facebook/docusaurus/edit/master/website/${blogDirPath}/${blogPath}`;          },        },      },    ],  ],};
note

Crowdin 暂不支持特定文件的链接

示例配置#

Docusaurus v2 配置文件就是一个使用文档分版和多实例功能的好例子:

crowdin.yml
project_id: '428890'api_token_env: 'CROWDIN_PERSONAL_TOKEN'preserve_hierarchy: truelanguages_mapping: &languages_mapping  two_letters_code:    'pt-BR': 'pt-BR'files:  [    {      source: '/website/i18n/en/**/*',      translation: '/website/i18n/%two_letters_code%/**/%original_file_name%',      languages_mapping: *languages_mapping,    },    {      source: '/website/docs/**/*',      translation: '/website/i18n/%two_letters_code%/docusaurus-plugin-content-docs/current/**/%original_file_name%',      languages_mapping: *languages_mapping,    },    {      source: '/website/community/**/*',      translation: '/website/i18n/%two_letters_code%/docusaurus-plugin-content-docs-community/current/**/%original_file_name%',      languages_mapping: *languages_mapping,    },    {      source: '/website/versioned_docs/**/*',      translation: '/website/i18n/%two_letters_code%/docusaurus-plugin-content-docs/**/%original_file_name%',      languages_mapping: *languages_mapping,    },    {      source: '/website/blog/**/*',      translation: '/website/i18n/%two_letters_code%/docusaurus-plugin-content-blog/**/%original_file_name%',      languages_mapping: *languages_mapping,    },    {      source: '/website/src/pages/**/*',      translation: '/website/i18n/%two_letters_code%/docusaurus-plugin-content-pages/**/%original_file_name%',      ignore: ['/**/*.js', '/**/*.jsx', '/**/*.ts', '/**/*.tsx', '/**/*.css'],      languages_mapping: *languages_mapping,    },  ]