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 教程
本文是一份手把手教程,使用 Crowdin 将一个新建的英文版 Docusaurus 站点翻译至简体中文。本文假设你已经完成了 i18n 教程。
最终结果可参见 docusaurus-crowdin-example.netlify.app(代码仓库)。
准备 Docusaurus 站点
初始化新的 Docusaurus 站点:
npx create-docusaurus@latest website classic
添加简体中文版网站的配置:
export default {
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr'],
},
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
确保位于 http://localhost:3000/zh-Hans/
的网站现在已经被翻译成简体中文。
使用持续集成 (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
「安全出口」语法:
# 如何部署 Docusaurus
要部署 Docusaurus,请运行以下命令:
````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';
export default {
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 配置文件就是一个使用文档分版和多实例功能的好例子:
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
Machine Translation (MT) issue: links/image handling
Crowdin recently rolled out some major changes to the markdown file format and now the links are treated differently than they were before. Before they were considered as tags, but now they appear as plain text. Because of these changes the plain text links are passed to the MT engine which attempts to translate the target, thus breaking the translation (for instance: this string Allez voir [ma merveilleuse page](/ma-merveilleuse-page)
is translated Check out [my wonderful page](/my-wonderful-page)
, and this breaks docusaurus i18n workflow as the page name should not be translated).
As of 2023 Dec.7, they are not planning to change the logic of how links are treated, so you should have this in mind if you plan to use Crowdin with MT.