i18n - 使用 Crowdin
Docusaurus 的 i18n 系统与其他翻译软件脱钩。
只要您将翻译文件放置在正确的位置,您可以将 Docusaurus 与您所选择的任何工具或 SaaS 集成。
本文中,我们将使用 Crowdin 作为其中一个可能的集成示例。
caution
这不代表我们为 Crowdin 背书,也不代表这是翻译 Docusaurus 站点的唯一选择,但 Facebook 已成功使用它来翻译包括 Jest、Docusaurus 和 ReasonML 在内的诸多文档项目。
请参见 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
添加简体中文版网站的配置:
module.exports = { i18n: { defaultLocale: 'en', locales: ['en', 'zh-cn'], }, 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: 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%
:翻译文件将保留原始文件夹/文件结构
info
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
- Yarn
npm install @crowdin/cli@3
yarn add @crowdin/cli@3
添加 crowdin
脚本:
{ "scripts": { "crowdin": "crowdin" }}
测试您是否可以运行 Crowdin CLI:
- npm
- Yarn
npm run crowdin -- --version
yarn run crowdin -- --version
在您的计算机上设置 CROWDIN_PERSONAL_TOKEN
环境变量来让 CLI 通过 Crowdin API 进行认证。
tip
您可以暂时在 crowdin.yml
中写入 api_token: '我的令牌'
来硬代码您的个人令牌。
#
上传源文件在 website/i18n/en
中生成默认语言的 JSON 翻译文件:
- npm
- Yarn
npm run write-translations
yarn run write-translations
上传所有 JSON 和 Markdown 翻译文件:
- npm
- Yarn
npm run crowdin upload
yarn run crowdin upload
您可在 Crowdin 界面上找到您的源文件:https://crowdin.com/project/<我的项目名称>/settings#files
#
翻译源文件在 https://crowdin.com/project/<我的项目名称>
,点击简体中文语言。
翻译 Markdown 文件。
tip
使用隐藏字符串
以确保翻译人员不翻译不该被翻译的内容:
- Frontmatter:
id
,slug
,tags
... - 警告:
:::
,:::note
,:::tip
...
翻译 JSON 文件。
信息
JSON 翻译文件的 description
属性在 Crowdin 上可见,可以用来协助翻译字符串。
tip
预翻译您的网站,再手动修复预翻译错误(请先在设置中启用全局翻译存储)。
预先使用隐藏字符串
功能,因为 Crowdin 在预翻译上对自己太自信了。
#
下载译文使用 Crowdin CLI 下载已翻译的 JSON 和 Markdown文件。
- npm
- Yarn
npm run crowdin download
yarn run crowdin download
翻译的内容应该在 i18n/zh-CN
中下载。
使用简体中文启动您的网站:
- npm
- Yarn
npm run start -- --locale zh-cn
yarn run start -- --locale zh-cn
确保你的网站现在在这里已经被翻译成简体中文:http://localhost:3000/zh-cn/
。
#
使用持续集成 (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
。
caution
Crowdin 对多个并行上传/下载的支持不太好:最好只将翻译内容包含到生产部署中,并且在部署预览时不要翻译。
#
Crowdin 进阶议题#
MDXcaution
在 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
设置。
我们建议使用 Hide
,但最理想的设置取决于你的版本之间有多大不同。
caution
不使用 Hide
会导致配额中包含更多的 源字符串
,从而影响 Crowdin 的价格。
#
多实例插件您需要为每个插件实例配置翻译文件。
若您有 id=ios
的文档插件实例,您也依然需要配置下列源文件:
website/ios
website/ios_versioned_docs
(若分版)
#
维护网站有些时候,您需要在 Git 上移除或重命名源文件,此时 Crowdin CLI 会打印警告:
当您重构完源码后,您应使用 Crowdin UI 来手动更新 Crowdin 文件:
#
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
函数来自定义每种语言的翻译网址。
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 配置文件就是一个使用文档分版和多实例功能的好例子:
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, }, ]