跳到主要内容
版本:Canary 🚧

i18n - 使用 Crowdin

Docusaurus 的 i18n 系统是与任何翻译软件解耦的。

您可以将 Docusaurus 与您选择的工具和 SaaS 集成在一起,只要将翻译文件放在正确的位置即可。

我们记录了Crowdin的用法,作为一个可能的集成示例。

警告

这并不是对 Crowdin 作为翻译 Docusaurus 网站的唯一选择的认可,但它被 Facebook 成功地用于翻译文档项目,如JestDocusaurusReasonML

请参考 Crowdin 文档Crowdin 支持 获取帮助。

提示

使用这个 社区驱动的 GitHub 讨论 来讨论任何与 Docusaurus + Crowdin 相关的内容。

Crowdin 概述

Crowdin 是一个翻译软件即服务(SaaS),提供开源项目的免费计划

我们推荐以下翻译流程:

  • Upload sources to Crowdin (untranslated files)
  • Use Crowdin to translate the content
  • Download translations from Crowdin (localized translation files)

Crowdin provides a CLI to upload sources and download translations, allowing you to automate the translation process.

The crowdin.yml configuration file is convenient for Docusaurus, and permits to download the localized translation files at the expected location (in i18n/[locale]/..).

Read the official documentation to know more about advanced features and different translation workflows.

Crowdin 教程

This is a walk-through of using Crowdin to translate a newly initialized English Docusaurus website into French, and assume you already followed the i18n tutorial.

The end result can be seen at docusaurus-crowdin-example.netlify.app (repository).

准备好 Docusaurus 的遗址

Initialize a new Docusaurus site:

npx create-docusaurus@latest website classic

Add the site configuration for the French language:

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

Translate the homepage:

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 项目

Sign up on Crowdin, and create a project.

Use English as the source language, and French as the target language.

Create a Crowdin project with english as source language, and french as target language

Your project is created, but it is empty for now. We will upload the files to translate in the next steps.

创建 Crowdin 配置

This configuration (doc) provides a mapping for the Crowdin CLI to understand:

  • Where to find the source files to upload (JSON and Markdown)
  • Where to download the files after translation (in i18n/[locale])

Create crowdin.yml in website:

crowdin.yml
project_id: "123456"
api_token_env: CROWDIN_PERSONAL_TOKEN
preserve_hierarchy: true
files:
# JSON translation files
- source: /i18n/en/**/*
translation: /i18n/%two_letters_code%/**/%original_file_name%
# Docs Markdown files
- source: /docs/**/*
translation: /i18n/%two_letters_code%/docusaurus-plugin-content-docs/current/**/%original_file_name%
# Blog Markdown files
- source: /blog/**/*
translation: /i18n/%two_letters_code%/docusaurus-plugin-content-blog/**/%original_file_name%

Crowdin has its own syntax for declaring source/translation paths:

  • **/*: everything in a subfolder
  • %two_letters_code%: the 2-letters variant of Crowdin target languages (fr in our case)
  • **/%original_file_name%: the translations will preserve the original folder/file hierarchy
信息

The Crowdin CLI warnings are not always easy to understand.

We advise to:

  • change one thing at a time
  • re-upload sources after any configuration change
  • use paths starting with / (./ does not work)
  • avoid fancy globbing patterns like /docs/**/*.(md|mdx) (does not work)

访问令牌

The api_token_env attribute defines the env variable name read by the Crowdin CLI.

You can obtain a Personal Access Token on your personal profile page.

提示

You can keep the default value CROWDIN_PERSONAL_TOKEN, and set this environment variable and on your computer and on the CI server to the generated access token.

警告

A Personal Access Tokens grant read-write access to all your Crowdin projects.

You should not commit it, and it may be a good idea to create a dedicated Crowdin profile for your company instead of using a personal account.

其他配置字段

  • project_id: can be hardcoded, and is found on https://crowdin.com/project/<MY_PROJECT_NAME>/settings#api
  • preserve_hierarchy: preserve the folder's hierarchy of your docs on Crowdin UI instead of flattening everything

安装 Crowdin 命令行

This tutorial uses the CLI version 3.5.2, but we expect 3.x releases to keep working.

Install the Crowdin CLI as an npm package to your Docusaurus site:

npm install @crowdin/cli@3

Add a crowdin script:

package.json
{
"scripts": {
// ...
"write-translations": "docusaurus write-translations",
"crowdin": "crowdin"
}
}

Test that you can run the Crowdin CLI:

npm run crowdin -- --version

Set the CROWDIN_PERSONAL_TOKEN env variable on your computer, to allow the CLI to authenticate with the Crowdin API.

提示

Temporarily, you can hardcode your personal token in crowdin.yml with api_token: 'MY-TOKEN'.

上传源码

Generate the JSON translation files for the default language in website/i18n/en:

npm run write-translations

Upload all the JSON and Markdown translation files:

npm run crowdin upload

Crowdin CLI uploading Docusaurus source files

Your source files are now visible on the Crowdin interface: https://crowdin.com/project/<MY_PROJECT_NAME>/settings#files

Crowdin UI showing Docusaurus source files

翻译资料

On https://crowdin.com/project/<MY_PROJECT_NAME>, click on the French target language.

Crowdin UI showing French translation files

Translate some Markdown files.

Crowdin UI to translate a Markdown file

提示

Use Hide String to make sure translators don't translate things that should not be:

  • Front matter: id, slug, tags ...
  • Admonitions: :::, :::note, :::tip ...

Crowdin UI hide string

Translate some JSON files.

Crowdin UI to translate a JSON file

信息

The description attribute of JSON translation files is visible on Crowdin to help translate the strings.

提示

Pre-translate your site, and fix pre-translation mistakes manually (enable the Global Translation Memory in settings first).

Use the Hide String feature first, as Crowdin is pre-translating things too optimistically.

下载翻译

Use the Crowdin CLI to download the translated JSON and Markdown files.

npm run crowdin download

The translated content should be downloaded in i18n/fr.

Start your site on the French locale:

npm run start -- --locale fr

Make sure that your website is now translated in French at http://localhost:3000/fr/.

使用 CI 实现自动化

We will configure the CI to download the Crowdin translations at build time and keep them outside of Git.

Add website/i18n to .gitignore.

Set the CROWDIN_PERSONAL_TOKEN env variable on your CI.

Create an npm script to sync Crowdin (extract sources, upload sources, download translations):

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

Call the npm run crowdin:sync script in your CI, just before building the Docusaurus site.

提示

Keep your deploy-previews fast: don't download translations, and use npm run build -- --locale en for feature branches.

警告

Crowdin does not support well multiple concurrent uploads/downloads: it is preferable to only include translations to your production deployment, and keep deploy previews untranslated.

高级拥挤话题

MDX

警告

Pay special attention to the JSX fragments in MDX documents!

Crowdin does not support officially MDX, but they added support for the .mdx extension, and interpret such files as Markdown (instead of plain text).

MDX 问题

Crowdin thinks that the JSX syntax is embedded HTML and can mess up with the JSX markup when you download the translations, leading to a site that fails to build due to invalid JSX.

Simple JSX fragments using simple string props like <Username name="Sebastien"/> will work fine; more complex JSX fragments using object/array props like <User person={{name: "Sebastien"}}/> are more likely to fail due to a syntax that does not look like HTML.

MDX 解决方案

我们建议将复杂的嵌入式 JSX 代码提取为单独的独立组件。我们还添加了一个mdx-code-block的逃生舱口语法:

# How to deploy Docusaurus

To deploy Docusaurus, run the following command:

````mdx-code-block 

import Tabs from '@theme/Tabs';

import TabItem from '@theme/TabItem';

<Tabs>
<TabItem value="bash" label="Bash">

```bash
GIT_USER=<GITHUB_USERNAME> yarn deploy
```

</TabItem>
<TabItem value="windows" label="Windows">

```batch
cmd /C "set "GIT_USER=<GITHUB_USERNAME>" && yarn deploy"
```

</TabItem>
</Tabs>
````

这将:

  • 被 Crowdin 解释为代码块(而不是与下载时的标记混淆)
  • 被 Docusaurus 解释为常规的 JSX(就好像它没有被任何代码块包装一样)
  • 不幸的是,选择退出 MDX 工具(IDE 语法高亮,更漂亮…)

文档版本管理

Configure translation files for the website/versioned_docs folder.

When creating a new version, the source strings will generally be quite similar to the current version (website/docs), and you don't want to translate the new version docs again and again.

Crowdin provides a Duplicate Strings setting.

Crowdin Duplicate Strings option setting

We recommend using Hide, but the ideal setting depends on how much your versions are different.

警告

Not using Hide leads to a much larger amount of source strings in quotas, and will affect the pricing.

多实例的插件

You need to configure translation files for each plugin instance.

If you have a docs plugin instance with id=ios, you will need to configure those source files as well

  • website/ios
  • website/ios_versioned_docs (if versioned)

维护您的网站

Sometimes, you will remove or rename a source file on Git, and Crowdin will display CLI warnings:

Crowdin CLI: download translation warning

When your sources are refactored, you should use the Crowdin UI to update your Crowdin files manually:

Crowdin UI: renaming a file

VCS (Git) 集成

Crowdin has multiple VCS integrations for GitHub, GitLab, Bitbucket.

TL;DR

We recommend avoiding them.

It could have been helpful to be able to edit the translations in both Git and Crowdin, and have a bi-directional sync between the 2 systems.

In practice, it didn't work very reliably for a few reasons:

  • The Crowdin -> Git sync works fine (with a pull request)
  • The Git -> Crowdin sync is manual (you have to press a button)
  • The heuristics used by Crowdin to match existing Markdown translations to existing Markdown sources are not 100% reliable, and you have to verify the result on Crowdin UI after any sync from Git
  • 2 users concurrently editing on Git and Crowdin can lead to a translation loss
  • It requires the crowdin.yml file to be at the root of the repository

语境定位

Crowdin has an In-Context localization feature.

警告

Unfortunately, it does not work yet for technical reasons, but we have good hope it can be solved.

Crowdin replaces Markdown strings with technical IDs such as crowdin:id12345, but it does so too aggressively, including hidden strings, and messes up with front matter, admonitions, JSX...

本地化编辑 URLs

When the user is browsing a page at /fr/doc1, the edit button will link by default to the unlocalized doc at website/docs/doc1.md.

You may prefer the edit button to link to the Crowdin interface instead by using the editUrl function to customize the edit URLs on a per-locale basis.

docusaurus.config.js
const DefaultLocale = "en";

module.exports = {
presets: [
[
"@docusaurus/preset-classic",
{
docs: {
editUrl: ({ locale, versionDocsDirPath, docPath }) => {
// Link to Crowdin for French docs
if (locale !== DefaultLocale) {
return `https://crowdin.com/project/docusaurus-v2/${locale}`;
}
// Link to GitHub for English docs
return `https://github.com/facebook/docusaurus/edit/main/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/main/website/${blogDirPath}/${blogPath}`;
},
},
},
],
],
};
备注

It is currently not possible to link to a specific file in Crowdin.

示例配置

The Docusaurus v2 configuration file is a good example of using versioning and multi-instance:

crowdin.yml
project_id: '428890'
api_token_env: CROWDIN_PERSONAL_TOKEN
preserve_hierarchy: true
languages_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