i18n - 使用 Crowdin
Docusaurus 的 i18n 系统是与翻译软件解耦的。
只需要把翻译文件放置在正确的位置,就可以将 Docusaurus 与你所选择的任何工具或 SaaS 集成。
本文中,我们将使用 Crowdin 作为一种可行的集成示例。
这不代表我们为 Crowdin 背书,也不代表这是翻译 Docusaurus 站点的唯一选择,不过 Facebook 已经成功用它翻译了包括 Jest、Docusaurus 和 ReasonML 在内的诸多文档项目。
请参见 **Crowdin 文档**及 **Crowdin 支持**寻求帮助。
请用这个**社区主导的 GitHub 讨论**来讨论和 Docusaurus + Crowdin 有关的内容。
Crowdin 概述
Crowdin 是一款翻译 SaaS,为开源项目提供了免费套餐。
我们推荐以下的翻译流程:
- 上传源文件至 Crowdin(未翻译文件)
- 使用 Crowdin 来翻译内容
- 从 Crowdin 下载译文(本地化的翻译文件)
Crowdin 提供 CLI 工具来上传资源和下载译文,帮助你实现翻译流程自动化。
crowdin.yml
配置文件可以方便地把已翻译的文件下载到正确位置(i18n/[语言]/..
处)。
你可以阅读**官方文档**来了解高级功能,以及不同的翻译流程。
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.
最终结果可参见 docusaurus-crowdin-example.netlify.app(代码仓库)。
准备 Docusaurus 站点
初始化新的 Docusaurus 站点:
npx create-docusaurus@latest website classic
添加简体中文版网站的配置:
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'zh-Hans'],
},
themeConfig: {
navbar: {
items: [
// ...
{
type: 'localeDropdown',
position: 'left',
},
// ...
],
},
},
// ...
};
翻译首页:
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 CLI 能理解的映射条件:
- 何处寻找要上传的源文件(JSON 及 Markdown)
- 将翻译后的文件下载至何处(
i18n/[语言]
处)
在 website
中创建 crowdin.yml
:
project_id: '123456'
api_token_env: CROWDIN_PERSONAL_TOKEN
preserve_hierarchy: true
files:
# 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)
一类的花哨 glob 模式(无法使用)
访问令牌
api_token_env
属性定义了 Crowdin CLI 所读取的环境变量名称。
你可以在你的个人资料页获得你的个人访问令牌
。
你也可以保留 CROWDIN_PERSONAL_TOKEN
的默认值,然后在你的电脑和 CI 服务器上将此环境变量设置为你的访问令牌。
个人访问令牌有读写你的所有 Crowdin 项目的权限。
你不能提交这个令牌,同时我们推荐你创建专门的 Crowdin 公司账户,而不是使用你的个人账户。
其他配置字段
project_id
:可以硬编码,可以在https://crowdin.com/project/<项目名称>/settings#api
处找到preserve_hierarchy
:是否在 Crowdin 界面上保留你的文档目录结构,而不是将所有文件放在同一个文件夹中
安装 Crowdin CLI
此教程中的 CLI 版本为 3.5.2
,但应该也适用于更新的 3.x
的版本。
在你的 Docusaurus 站点中安装 Crowdin CLI 的 npm 包:
- npm
- Yarn
- pnpm
npm install @crowdin/cli@3
yarn add @crowdin/cli@3
pnpm add @crowdin/cli@3
添加 crowdin
脚本:
{
"scripts": {
// ...
"write-translations": "docusaurus write-translations",
"crowdin": "crowdin"
}
}
测试是否可以运行 Crowdin CLI:
- npm
- Yarn
- pnpm
npm run crowdin -- --version
yarn crowdin --version
pnpm run crowdin -- --version
在你的计算机上设置 CROWDIN_PERSONAL_TOKEN
环境变量,让 CLI 通过 Crowdin API 进行认证。
你可以暂时在 crowdin.yml
中写入 api_token: '我的令牌'
,硬代码你的个人令牌。
上传源文件
在 website/i18n/en
中生成默认语言的 JSON 翻译文件:
- npm
- Yarn
- pnpm
npm run write-translations
yarn write-translations
pnpm run write-translations
上传所有 JSON 和 Markdown 翻译文件:
- npm
- Yarn
- pnpm
npm run crowdin upload
yarn crowdin upload
pnpm run crowdin upload
现在可以在 Crowdin 界面上看到你的源文件了:https://crowdin.com/project/<项目名称>/settings#files
翻译源文件
在 https://crowdin.com/project/<项目名称>
处,点击简体中文语言。
翻译 Markdown 文件。
使用隐藏字符串
以确保翻译人员不翻译不该被翻译的内容:
- 前言:
id
、slug
、tags
等 - 告示:
:::
、:::note
、:::tip
等
翻译 JSON 文件。
JSON 翻译文件的 description
属性在 Crowdin 上可见,可以用来协助翻译字符串。
预翻译你的网站,再手动修复预翻译的错误(请先在设置中启用全局翻译存储)。
要先使用隐藏字符串
功能,因为 Crowdin 在预翻译上太激进了。
下载译文
用 Crowdin CLI 下载翻译好的 JSON 和 Markdown文件。
- npm
- Yarn
- pnpm
npm run crowdin download
yarn crowdin download
pnpm run crowdin download
翻译好的内容应被下载到 i18n/zh-Hans
。
使用简体中文启动你的网站:
- npm
- Yarn
- pnpm
npm run start -- --locale zh-Hans
yarn run start --locale zh-Hans
pnpm run start -- --locale zh-Hans
Make sure that your website is now translated in French at http://localhost:3000/fr/
.
使用持续集成 (CI) 来自动化翻译
我们接下来要配置 CI,让它在构建时下载 Crowdin 翻译,并将其保存在 Git 外。
把 website/i18n
添加到 .gitignore
中。
在你的 CI 服务上设置 CROWDIN_PERSONAL_TOKEN
环境变量。
创建一个 npm 脚本,完成 crowdin:sync
同步(提取源数据、上传源数据、下载翻译):
{
"scripts": {
"crowdin:sync": "docusaurus write-translations && crowdin upload && crowdin download"
}
}
在构建 Docusaurus 站点之前,在 CI 中调用 npm run crowdin:sync
脚本。
在部署预览中,为了维持构建速度,不要下载翻译,并且在功能分支上使用 npm run build --locale en
。
Crowdin 对多个并行上传/下载的支持不太好:最好只将翻译内容包含到生产部署中,并且在部署预览时不要翻译。
Crowdin 进阶主题
MDX
在 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-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 语法高亮、Prettier 等)
文档分版
为 website/versioned_docs
文件夹配置翻译。
创建新版本时,源字符串通常与当前版本 (website/docs
) 非常相似,并且没人想一次次地翻译新版本的文档。
Crowdin 提供了重复字符串
设置。
我们建议使用「隐藏」(Hide
),但最理想的设置取决于你的版本之间有多大不同。
不使用 Hide
会导致配额中包含更多的 源字符串
,从而影响 Crowdin 的收费。
多实例插件
你需要为每个插件实例的文件配置翻译。
如果你有一个 id=ios
的文档插件实例,你还会需要配置下列源文件:
website/ios
website/ios_versioned_docs
(如果分版本)
维护网站
有些时候,你需要在 Git 上移除或重命名源文件,此时 Crowdin CLI 会打印警告:
当你重构完源码后,你应该在 Crowdin 界面上手动更新 Crowdin 文件:
VCS (Git) 集成
Crowdin 拥有多个版本管理系统的集成,如 GitHub、GitLab 和 Bitbucket。
我们不推荐你使用。
在 Git 和 Crowdin 中同时编辑翻译文件,达成双向同步的效果可能对你有所帮助。
但实际上,这种做法并不可取,原因如下:
- Crowdin -> Git 同步没有问题(通过合并请求)
- Git -> Crowdin 同步需要手动操作(你需要手动点一个按钮)
- Crowdin 无法做到百分百准确地将已有的 Markdown 源文件和它的译文关联起来,所以在从 Git 同步到 Crowdin 后,你需要前往 Crowdin 界面验证结果
- 多名用户同时在 Git 和 Crowdin 编辑可能会造成翻译丢失
- 需要将
crowdin.yml
放置在仓库根目录
基于语境的本地化
Crowdin 支持基于语境的本地化功能。
遗憾的是,由于技术原因,此功能还不能使用。但我们很有信心这个问题能被解决。
Crowdin 会将 Markdown 字符串使用形如 crowdin:id12345
的编号替代,但它替换得太激进了,也会替换掉包括隐藏字符串在 内的特殊字符串,而且会弄乱前言、告示及 JSX 等内容。
本地化编辑链接
当用户浏览位于 /zh-Hans/doc1
的页面时,编辑按钮会默认指向 website/docs/doc1.md
处的未翻译文档。
你可能更希望将编辑按钮指向 Crowdin 界面。你可以用 editUrl
函数来自定义每种语言的翻译网址。
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/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}`;
},
},
},
],
],
};
Crowdin 暂不支持链接到特定文件。
示例配置
Docusaurus v2 配置文件就是一个使用文档分版和多实例功能的好例子:
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