跳到主要内容
版本:2.1.0

i18n - 教程

本教程将为你阐述 Docusaurus i18n 系统的基础。

我们将为新建的英文 Docusaurus 站点添加简体中文翻译。

首先,用 npx [email protected] init website classic 来创建新站点。(就像这个一样

配置你的站点

编辑 docusaurus.config.js,添加对简体中文语言的 i18n 支持。

站点配置

使用站点的 i18n 配置来声明要国际化的语言:

docusaurus.config.js
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'zh-Hans', 'fa'],
localeConfigs: {
en: {
htmlLang: 'en-GB',
},
// 如果你不需要覆盖默认值,你可以忽略这个语言(比如 zh-Hans)
fa: {
direction: 'rtl',
},
},
},
};

语言名称会被用于翻译文件的位置以及你的本地化网站的 base URL。 构建所有语言时,只有默认语言才会在 base URL 中省略它的名字。

Docusaurus 会用语言名称提供合理默认值<html lang="..."> 属性、语言标签、日历格式等。 你可以用 localeConfigs 自定义这些默认值。

主题配置

添加 localeDropdown 类型的导航栏下拉框,让用户选择语言:

docusaurus.config.js
module.exports = {
themeConfig: {
navbar: {
items: [
{
type: 'localeDropdown',
position: 'left',
},
],
},
},
};

启动网站

使用你所选择的语言,以开发模式启动本地化站点:

npm run start -- --locale zh-Hans

你的站点可以在 http://localhost:3000/zh-Hans/ 访问了。

我们还没有添加任何译文,所以网站的大部分内容都没有被翻译。

提示

Docusaurus 会为「下一页」「上一页」等通用主题标签提供默认翻译

请帮助我们完善这些默认译文

注意

每个语言版本都是一个独立的单页应用程序:不能同时启动所有语言的 Docusaurus 站点。

翻译你的站点

所有简体中文的翻译数据都存放在 website/i18n/zh-Hans 处。 每个插件会在它自己的对应文件夹下寻找翻译内容,而 code.json 文件定义了 React 代码中使用的所有文本标签。

备注

复制文件后,请用 npm run start -- --locale zh-Hans 重新启动站点。 热加载功能在编辑已有的文件时会有更好的表现。

翻译 React 代码

对于你自己编写的任何 React 代码:React 页面、React 组件等等,你应该使用translation APIs

在你的 React 代码中找到所有用户可见的文本标签,用翻译 API 标记它们。 API 有两种:

  • The <Translate> component wraps a string as a JSX element;
  • translate() 函数接受一条文本,返回一个字符串。

使用在上下文中更符合语义的那个 API。 比如,<Translate> 可以作为 React children,而对于那些接受字符串的属性,就需要用函数形式。

注意

A JSX element is an object, not a string. Using it in contexts expecting strings (such as the children of <option>) would coerce it to a string, which returns "[object Object]". While we encourage you to use <Translate> as JSX children, only use the element form when it actually works.

src/pages/index.js
import React from 'react';
import Layout from '@theme/Layout';
import Link from '@docusaurus/Link';

export default function Home() {
return (
<Layout>
<h1>Welcome to my website</h1>
<main>
You can also visit my
<Link to="https://docusaurus.io/blog">blog</Link>
<img
src="/img/home.png"
alt="Home icon"
/>
</main>
</Layout>
);
}
信息

Docusaurus 有意仅提供轻量级的翻译环境,且仅支持基础的、使用 ICU 信息格式子集的占位符改写功能

文档网站基本都是静态的,因此不需要高级的 i18n 功能(复数性别等)。 如果有更高级的需要,请使用如 react-intl 一类的库。

docusaurus write-translations 命令会静态分析你的站点中使用的所有 React 代码文件,提取对这些 API 的调用,并将其汇总到 code.json 文件中。 翻译文件会被存储为一个从 ID 到翻译信息对象的映射表。每个信息包含了要翻译的文本标签和标签的描述。 在调用翻译 API(<Translate>translate())时,需要指定默认的未翻译消息或 ID 之一,以确保 Docusaurus 能正确地把每个翻译条目与 API 的调用联系起来。

文本标签必须是静态的

docusaurus write-translations 命令只会对你的代码做静态分析。 它不会真的运行你的网站。 因此,动态信息无法被提取,因为信息是表达式,不是字符串

const items = [
{id: 1, title: 'Hello'},
{id: 2, title: 'World'},
];

function ItemsList() {
return (
<ul>
{/* 别这么做:write-translations 命令识别不到这个 */}
{items.map((item) => (
<li key={item.id}>
<Translate>{item.title}</Translate>
</li>
))}
<ul>
);
}

这在运行时仍然能正确工作。 然而,未来我们可能会提供一种「零运行时」机制,使得翻译会通过 Babel 转换被直接内联在 React 代码中,而不是在运行时调用 API。 因此,为了保证你的代码在未来仍然能工作,你应该始终保持你的信息可以被静态分析。 比如,我们可以把上面的代码重构成:

const items = [
{id: 1, title: <Translate>Hello</Translate>},
{id: 2, title: <Translate>World</Translate>},
];

function ItemsList() {
return (
<ul>
{/* 现在这些文本就可以在渲染时被成功翻译了! */}
{items.map((item) => (
<li key={item.id}>{item.title}</li>
))}
<ul>
);
}

你可以把翻译 API 的调用看作是纯标记,告诉 Docusaurus「这里有一个文本标签,把它替换成翻译好的信息」。

复数形式

当你运行 write-translations 时,你会注意到一些标签有复数形式:

i18n/en/code.json
{
// ...
"theme.blog.post.plurals": "One post|{count} posts"
// ...
}

每种语言都有一个可能的复数类别的列表。 Docusaurus 会按照 ["zero", "one", "two", "few", "many", "other"] 的顺序排列它们。 比如英语 (en) 有两种复数形式("one" 和 "other"), 因此翻译信息包含两个标签,用竖线 (|) 隔开。 对于波兰语 (pl) 来说,它有三种复数形式("one"、"few"、"many"),所以你会按照这个顺序提供三个标签,并用竖线连接。

你也可以给你自己的代码提供复数形式:

import {translate} from '@docusaurus/Translate';
import {usePluralForm} from '@docusaurus/theme-common';

function ItemsList({items}) {
// `usePluralForm` 会提供当前语言的复数选择器
const {selectMessage} = usePluralForm();
// 根据 `items.length` 选择恰当的复数标签
const message = selectMessage(
items.length,
translate(
{message: 'One item|{count} items'},
{count: items.length},
),
);
return (
<>
<h2>{message}</h2>
<ul>{items.map((item) => <li key={item.id}>{item.title}</li>)}<ul>
</>
);
}
备注

Docusaurus 使用 Intl.PluralRules 来解析并选择复数形式。 提供的复数形式的数量和顺序都必须正确,selectMessage 才能正常工作。

翻译插件数据

JSON 翻译文件用于所有散落在你的代码中的内容:

  • React 代码,包括你在上面标记的待翻译文本
  • 主题配置中的导航栏和页脚标签
  • sidebars.js 中的文档侧边栏分类标签
  • 插件选项中的博客侧边栏标题
  • ……

运行 写入翻译 命令:

npm run write-translations -- --locale zh-Hans

这个命令会提取并初始化待翻译的 JSON 翻译文件。 根目录下的 code.json 文件包含了从源代码中提取的所有翻译 API 调用,包括你编写的,以及主题提供的代码,有一些可能已经被默认翻译了。

i18n/fr/code.json
{
// <Translate> 组件没有 ID:默认翻译信息会被用作 ID
"Welcome to my website": {
"message": "Welcome to my website"
},
"home.visitMyBlog": {
"message": "You can also visit my {blog}",
"description": "The homepage message to ask the user to visit my blog"
},
"homepage.visitMyBlog.linkLabel": {
"message": "Blog",
"description": "The label for the link to my blog"
},
"Home icon": {
"message": "Home icon",
"description": "The homepage icon alt message"
}
}

插件和主题也会写入它们自己的 JSON 翻译文件,比如:

i18n/fr/docusaurus-theme-classic/navbar.json
{
"title": {
"message": "My Site",
"description": "The title in the navbar"
},
"item.label.Docs": {
"message": "Docs",
"description": "Navbar item with label Docs"
},
"item.label.Blog": {
"message": "Blog",
"description": "Navbar item with label Blog"
},
"item.label.GitHub": {
"message": "GitHub",
"description": "Navbar item with label GitHub"
}
}

逐个翻译 i18n/zh-Hans 中 JSON 文件的 message 属性,然后你的站点布局及首页就应该是已翻译状态了。

翻译 Markdown 文件

Docusaurus 官方内容插件大量使用 Markdown/MDX 文件,并允许你翻译它们。

翻译文档

把你的 Markdown 文档从 docs/ 复制进 i18n/zh-Hans/docusaurus-plugin-content-docs/current,然后翻译它们:

mkdir -p i18n/zh-Hans/docusaurus-plugin-content-docs/current
cp -r docs/** i18n/zh-Hans/docusaurus-plugin-content-docs/current
信息

要注意, docusaurus-plugin-content-docs 插件总是会按照版本划分它的内容。 ./docs 文件夹中的数据会在 current 子文件夹和 current.json 文件中被翻译。 见文档版本化教程了解更多关于「当前版本」含义的信息。

翻译博客

把你的博客的 Markdown 文件复制进 i18n/zh-Hans/docusaurus-plugin-content-blog,然后翻译它们:

mkdir -p i18n/zh-Hans/docusaurus-plugin-content-blog
cp -r blog/** i18n/zh-Hans/docusaurus-plugin-content-blog

翻译页面

把你的页面的 Markdown 文件复制进 i18n/zh-Hans/docusaurus-plugin-content- pages,然后翻译它们:

mkdir -p i18n/zh-Hans/docusaurus-plugin-content-pages
cp -r src/pages/**.md i18n/zh-Hans/docusaurus-plugin-content-pages
cp -r src/pages/**.mdx i18n/zh-Hans/docusaurus-plugin-content-pages
注意

我们只复制了 .md.mdx 文件,因为 React 页面已经在前文中用 JSON 文件翻译好了。

使用显式标题 ID

Markdown 标题 ### Hello World 会默认自动生成 ID hello-world。 其他文档可以用 [链接](#hello-world) 的形式链接到这个标题。 但是,翻译后的标题变成了 ### 你好世界,ID 也随即变成了 你好世界

这样,你就得本地化所有锚点链接。所以,自动生成 ID 通常不适合本地化站点。

- [链接](#hello-world)
+ [链接](#你好世界)

对于本地化站点,我们推荐使用显式标题 ID

部署站点

你可以选择是将站点部署在一个域名还是多个(子)域名下。

单域名部署

运行以下命令:

npm run build

Docusaurus 会为每个语言构建一个单页应用

  • website/build:默认语言英文
  • website/build/zh-Hans:简体中文语言

你现在可以把 build 文件夹部署到你选择的静态托管方案上了。

备注

Docusaurus v2 网站使用这个方案:

提示

静态托管商通常会按惯例把 /unknown/url 重定向到 /404.html,这样就会始终显示英文版 404 错误页面

配置你的主机,将 /zh-Hans/* 重定向到 /zh-Hans/404.html,以确保 404 错误页被本地化

但这不是总是可行的,取决于你的托管商:比如 GitHub Pages 就无法配置,但 Netlify 可以。

多域名部署

你可以为某个语言单独构建站点:

npm run build -- --locale zh-Hans

Docusaurus 将不会添加 /zh-Hans/ URL 前缀。

在你的静态托管商上:

  • 为每种语言做一份部署
  • 配置构建指令,使用相应的 --locale 选项
  • 为每份部署配置一个(子)域名
注意

因为 Github Pages 只能做一份部署,它不支持这种部署策略。

混合部署

你可以把某些语言部署在子路径下,同时把另一些语言部署在子域名上。

你也可以把每个语言部署在单独的子域名上,然后在 CDN 层面把子域名合并成单一域名:

  • 把你的网站部署为 zh-Hans.docusaurus.io
  • 配置 CDN,让它在 docusaurus.io/zh-Hans 提供服务

管理翻译

Docusaurus 不关心你的翻译是如何管理的:它唯一关心的就是所有翻译文件(JSON、Markdown、其他数据文件)都在构建时存在在文件系统中。 然而,作为网站创建者,你需要考虑如何管理翻译,这样你的翻译贡献者才能顺利合作。

我们将分享两种常见的翻译协作策略:使用 git使用 Crowdin