i18n - 教程
本教程将为你阐述 Docusaurus i18n 系统的基础。
我们将为新建的英文 Docusaurus 站点添加简体中文翻译。
首先,用 npx [email protected] init website classic
来创建新站点。(就像这个一样)
配置你的站点
编辑 docusaurus.config.js
,添加对简体中文语言的 i18n 支持。
站点配置
使用站点的 i18n 配置来声明要国际化的语言:
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
类型的导航栏下拉框,让用户选择语言:
module.exports = {
themeConfig: {
navbar: {
items: [
{
type: 'localeDropdown',
position: 'left',
},
],
},
},
};
启动网站
使用你所选择的语言,以开发模式启动本地化站点:
- 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/ 访问了。
我们还没有添加任何译文,所以网站的大部分内容都没有被翻译。
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.
- 翻译前
- 翻译后
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>
);
}
import React from 'react';
import Layout from '@theme/Layout';
import Link from '@docusaurus/Link';
import Translate, {translate} from '@docusaurus/Translate';
export default function Home() {
return (
<Layout>
<h1>
<Translate>Welcome to my website</Translate>
</h1>
<main>
<Translate
id="homepage.visitMyBlog"
description="The homepage message to ask the user to visit my blog"
values={{
blogLink: (
<Link to="https://docusaurus.io/blog">
<Translate
id="homepage.visitMyBlog.linkLabel"
description="The label for the link to my blog">
blog
</Translate>
</Link>
),
}}>
{'You can also visit my {blogLink}'}
</Translate>
<img
src="/img/home.png"
alt={
translate({
message: 'Home icon',
description: 'The homepage icon alt message',
})
}
/>
</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
时,你会注意到一些标签有复数形式:
{
// ...
"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
- Yarn
- pnpm
npm run write-translations -- --locale zh-Hans
yarn write-translations --locale zh-Hans
pnpm run write-translations -- --locale zh-Hans
这个命令会提取并初始化待翻译的 JSON 翻译文件。 根目录下的 code.json
文件包含了从源代码中提取的所有翻译 API 调用,包括你编写的,以及主题提供的代码,有一些可能已经被默认翻译了。
{
// <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 翻译文件,比如:
{
"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 文件翻译好了。
Markdown 标题 ### Hello World
会默认自动生成 ID hello-world
。 其他文档可以用 [链接](#hello-world)
的形式链接到这个标题。 但是,翻译后的标题变成了 ### 你好世界
,ID 也随即变成了 你好世界
。
这样,你就得本地化所有锚点链接。所以,自动生成 ID 通常不适合本地化站点。
- [链接](#hello-world)
+ [链接](#你好世界)
对于本地化站点,我们推荐使用显式标题 ID。
部署站点
你可以选择是将站点部署在一个域名还是多个(子)域名下。
单域名部署
运行以下命令:
- npm
- Yarn
- pnpm
npm run build
yarn build
pnpm 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
- Yarn
- pnpm
npm run build -- --locale zh-Hans
yarn build --locale zh-Hans
pnpm 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。