国际化 - 教程
¥i18n - Tutorial
本教程将引导你了解 Docusaurus i18n 系统的基础知识。
¥This tutorial will walk you through the basics of the Docusaurus i18n system.
我们将在新初始化的英文 Docusaurus 网站上添加法语翻译。
¥We will add French translations to a newly initialized English Docusaurus website.
使用 npx create-docusaurus@latest website classic
初始化一个新站点(如 这个)。
¥Initialize a new site with npx create-docusaurus@latest website classic
(like this one).
配置你的站点
¥Configure your site
修改 docusaurus.config.js
添加对法语的 i18n 支持。
¥Modify docusaurus.config.js
to add the i18n support for the French language.
站点配置
¥Site configuration
使用 站点 i18n 配置 声明 i18n 语言环境:
¥Use the site i18n configuration to declare the i18n locales:
export default {
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr', 'fa'],
localeConfigs: {
en: {
htmlLang: 'en-GB',
},
// You can omit a locale (e.g. fr) if you don't need to override the defaults
fa: {
direction: 'rtl',
},
},
},
};
语言环境名称用于翻译文件 '位置以及你翻译的区域设置' 基本 URL。构建所有语言环境时,只有默认语言环境的名称才会在基本 URL 中省略。
¥The locale names are used for the translation files' locations, as well as your translated locales' base URL. When building all locales, only the default locale will have its name omitted in the base URL.
Docusaurus 使用区域设置名称来提供合理的默认值:<html lang="...">
属性、区域设置标签、日历格式等。你可以使用 localeConfigs
自定义这些默认值。
¥Docusaurus uses the locale names to provide sensible defaults: the <html lang="...">
attribute, locale label, calendar format, etc. You can customize these defaults with the localeConfigs
.
主题配置
¥Theme configuration
添加 localeDropdown
类型的导航栏项,以便用户可以选择他们想要的区域设置:
¥Add a navbar item of type localeDropdown
so that users can select the locale they want:
export default {
themeConfig: {
navbar: {
items: [
{
type: 'localeDropdown',
position: 'left',
},
],
},
},
};
你可以传递一个查询参数,当用户使用下拉菜单更改区域设置时,该参数将附加到 URL(例如 queryString: '?persistLocale=true'
)。
¥You can pass a query parameter that will be appended to the URL when a user changes the locale using the dropdown (e.g. queryString: '?persistLocale=true'
).
这对于在服务器上实现自动区域设置检测非常有用。例如,你可以使用此参数将用户的首选区域设置存储在 cookie 中。
¥This is useful for implementing an automatic locale detection on your server. For example, you can use this parameter to store the user's preferred locale in a cookie.
启动你的网站
¥Start your site
使用你选择的区域设置以开发模式启动本地化站点:
¥Start your localized site in dev mode, using the locale of your choice:
- npm
- Yarn
- pnpm
npm run start -- --locale fr
yarn run start --locale fr
pnpm run start --locale fr
你的网站可通过 http://localhost:3000/fr/
访问。
¥Your site is accessible at http://localhost:3000/fr/
.
我们尚未提供任何翻译,因此该网站大部分未翻译。
¥We haven't provided any translation yet, so the site is mostly untranslated.
Docusaurus 为通用主题标签提供默认翻译,例如分页的 "下一个" 和 "以前的"。
¥Docusaurus provides default translations for generic theme labels, such as "Next" and "Previous" for the pagination.
请帮助我们完成那些 默认翻译。
¥Please help us complete those default translations.
每个语言环境都是一个独特的独立单页应用:不可能同时在所有区域设置中启动 Docusaurus 站点。
¥Each locale is a distinct standalone single-page application: it is not possible to start the Docusaurus sites in all locales at the same time.
翻译你的网站
¥Translate your site
法语语言环境的所有翻译数据都存储在 website/i18n/fr
中。每个插件在相应的文件夹下获取自己的翻译内容,而 code.json
文件定义了 React 代码中使用的所有文本标签。
¥All translation data for the French locale is stored in website/i18n/fr
. Each plugin sources its own translated content under the corresponding folder, while the code.json
file defines all text labels used in the React code.
复制文件后,使用 npm run start -- --locale fr
重新启动站点。编辑现有文件时,热重载会更好地工作。
¥After copying files around, restart your site with npm run start -- --locale fr
. Hot-reload will work better when editing existing files.
翻译你的 React 代码
¥Translate your React code
对于你自己编写的任何 React 代码:React 页面、React 组件等,你将使用 翻译 API。
¥For any React code you've written yourself: React pages, React components, etc., you will use the translation APIs.
找到 React 代码中用户可见的所有文本标签,并使用翻译 API 标记它们。有两种 API:
¥Locate all text labels in your React code that will be visible to your users, and mark them with the translation APIs. There are two kinds of APIs:
-
<Translate>
组件将字符串封装为 JSX 元素;¥The
<Translate>
component wraps a string as a JSX element; -
translate()
回调接受一条消息并返回一个字符串。¥The
translate()
callback takes a message and returns a string.
使用在语义上更适合上下文的一种。例如,<Translate>
可以用作 React 子项,而对于需要字符串的 props,可以使用回调。
¥Use the one that better fits the context semantically. For example, the <Translate>
can be used as React children, while for props that expect a string, the callback can be used.
JSX 元素是一个对象,而不是字符串。在需要字符串的上下文中(例如 <option>
的子级)使用它会得到 将其强制为字符串,它会返回 "[object Object]"
。虽然我们鼓励你使用 <Translate>
作为 JSX 子项,但仅在实际工作时才使用元素形式。
¥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.
- Before
- After
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.nodejs.cn/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.nodejs.cn/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 消息格式 的子集。
¥Docusaurus provides a very small and lightweight translation runtime on purpose, and only supports basic placeholders interpolation, using a subset of the ICU Message Format.
大多数文档网站通常是静态的,不需要高级 i18n 功能(复数、性别等)。使用像 react-intl 这样的库来实现更高级的用例。
¥Most documentation websites are generally static and don't need advanced i18n features (plurals, genders, etc.). Use a library like react-intl for more advanced use-cases.
docusaurus write-translations
命令将静态分析站点中使用的所有 React 代码文件,提取对这些 API 的调用,并将它们聚合到 code.json
文件中。翻译文件将存储为从 ID 到翻译消息对象(包括翻译的标签和标签的描述)的映射。在对翻译 API(<Translate>
或 translate()
)的调用中,你需要指定默认的未翻译消息或 ID,以便 Docusaurus 正确地将每个翻译条目与 API 调用关联起来。
¥The docusaurus write-translations
command will statically analyze all React code files used in your site, extract calls to these APIs, and aggregate them in the code.json
file. The translation files will be stored as maps from IDs to translation message objects (including the translated label and the description of the label). In your calls to the translation APIs (<Translate>
or translate()
), you need to specify either the default untranslated message or the ID, in order for Docusaurus to correctly correlate each translation entry to the API call.
docusaurus write-translations
命令仅对代码进行静态分析。它实际上并不运行你的网站。因此,无法提取动态消息,因为该消息是表达式,而不是字符串:
¥The docusaurus write-translations
command only does static analysis of your code. It doesn't actually run your site. Therefore, dynamic messages can't be extracted, as the message is an expression, not a string:
const items = [
{id: 1, title: 'Hello'},
{id: 2, title: 'World'},
];
function ItemsList() {
return (
<ul>
{/* DON'T DO THIS: doesn't work with the write-translations command */}
{items.map((item) => (
<li key={item.id}>
<Translate>{item.title}</Translate>
</li>
))}
<ul>
);
}
这在运行时仍然表现正确。不过,未来我们可能会提供 "no-runtime" 机制,允许通过 Babel 转换直接将翻译内联到 React 代码中,而不是在运行时调用 API。因此,为了面向未来,你应该始终更喜欢可静态分析的消息。例如,我们可以将上面的代码重构为:
¥This still behaves correctly at runtime. However, in the future, we may provide a "no-runtime" mechanism, allowing the translations to be directly inlined in the React code through Babel transformations, instead of calling the APIs at runtime. Therefore, to be future-proof, you should always prefer statically analyzable messages. For example, we can refactor the code above to:
const items = [
{id: 1, title: <Translate>Hello</Translate>},
{id: 2, title: <Translate>World</Translate>},
];
function ItemsList() {
return (
<ul>
{/* The titles are now already translated when rendering! */}
{items.map((item) => (
<li key={item.id}>{item.title}</li>
))}
<ul>
);
}
你可以将对翻译 API 的调用视为纯粹的标记,告诉 Docusaurus "这是一个要替换为翻译消息的文本标签"。
¥You can see the calls to the translation APIs as purely markers that tell Docusaurus that "here's a text label to be replaced with a translated message".
复数化
¥Pluralization
当你运行 write-translations
时,你会注意到一些标签是复数的:
¥When you run write-translations
, you will notice that some labels are pluralized:
{
// ...
"theme.blog.post.plurals": "One post|{count} posts"
// ...
}
每种语言都会有一个 可能的复数类别 的列表。Docusaurus 会将它们按照 ["zero", "one", "two", "few", "many", "other"]
的顺序排列。例如,由于英语 (en
) 有两种复数形式("one" 和 "other"),因此翻译消息具有两个由竖线 (|
) 分隔的标签。对于具有三种复数形式("one"、"few" 和 "many")的波兰语 (pl
),你将按顺序提供三个标签,并用竖线连接。
¥Every language will have a list of possible plural categories. Docusaurus will arrange them in the order of ["zero", "one", "two", "few", "many", "other"]
. For example, because English (en
) has two plural forms ("one" and "other"), the translation message has two labels separated by a pipe (|
). For Polish (pl
) which has three plural forms ("one", "few", and "many"), you would provide three labels in that order, joined by pipes.
你也可以将自己的代码消息复数化:
¥You can pluralize your own code's messages as well:
import {translate} from '@docusaurus/Translate';
import {usePluralForm} from '@docusaurus/theme-common';
function ItemsList({items}) {
// `usePluralForm` will provide the plural selector for the current locale
const {selectMessage} = usePluralForm();
// Select the appropriate pluralized label based on `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
发挥作用,以正确的顺序提供正确数量的复数形式非常重要。
¥Docusaurus uses Intl.PluralRules
to resolve and select plural forms. It is important to provide the right number of plural forms in the right order for selectMessage
to work.
翻译插件数据
¥Translate plugin data
JSON 翻译文件用于散布在代码中的所有内容:
¥JSON translation files are used for everything that is interspersed in your code:
-
React 代码,包括你在上面标记的翻译标签
¥React code, including the translated labels you have marked above
-
主题配置中的导航栏和页脚标签
¥Navbar and footer labels in theme config
-
sidebars.js
中的文档侧边栏类别标签¥Docs sidebar category labels in
sidebars.js
-
插件选项中的博客侧边栏标题
¥Blog sidebar title in plugin options
-
...
运行 write-translations 命令:
¥Run the write-translations command:
- npm
- Yarn
- pnpm
npm run write-translations -- --locale fr
yarn write-translations --locale fr
pnpm run write-translations --locale fr
它将提取并初始化你需要翻译的 JSON 翻译文件。根目录下的 code.json
文件包含从源代码中提取的所有翻译 API 调用,这些调用可以由你编写,也可以由主题提供,其中一些可能已默认翻译。
¥It will extract and initialize the JSON translation files that you need to translate. The code.json
file at the root includes all translation API calls extracted from the source code, which could either be written by you or provided by the themes, some of which may already be translated by default.
{
// No ID for the <Translate> component: the default message is used as 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 翻译文件,例如:
¥Plugins and themes will also write their own JSON translation files, such as:
{
"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/fr
的 JSON 文件中的 message
属性,你的站点布局和主页现在应该已翻译。
¥Translate the message
attribute in the JSON files of i18n/fr
, and your site layout and homepage should now be translated.
翻译 Markdown 文件
¥Translate Markdown files
官方 Docusaurus 内容插件广泛使用 Markdown/MDX 文件并允许你翻译它们。
¥Official Docusaurus content plugins extensively use Markdown/MDX files and allow you to translate them.
翻译文档
¥Translate the docs
将你的文档 Markdown 文件从 docs/
复制到 i18n/fr/docusaurus-plugin-content-docs/current
,然后翻译它们:
¥Copy your docs Markdown files from docs/
to i18n/fr/docusaurus-plugin-content-docs/current
, and translate them:
mkdir -p i18n/fr/docusaurus-plugin-content-docs/current
cp -r docs/** i18n/fr/docusaurus-plugin-content-docs/current
请注意,docusaurus-plugin-content-docs
插件始终按版本划分其内容。./docs
文件夹中的数据将被翻译到 current
子文件夹和 current.json
文件中。有关 "current" 含义的更多信息,请参阅 文档版本控制指南。
¥Notice that the docusaurus-plugin-content-docs
plugin always divides its content by versions. The data in ./docs
folder will be translated in the current
subfolder and current.json
file. See the doc versioning guide for more information about what "current" means.
翻译博客
¥Translate the blog
将你的博客 Markdown 文件复制到 i18n/fr/docusaurus-plugin-content-blog
,并翻译它们:
¥Copy your blog Markdown files to i18n/fr/docusaurus-plugin-content-blog
, and translate them:
mkdir -p i18n/fr/docusaurus-plugin-content-blog
cp -r blog/** i18n/fr/docusaurus-plugin-content-blog
翻译页面
¥Translate the pages
将页面 Markdown 文件复制到 i18n/fr/docusaurus-plugin-content-pages
,然后翻译它们:
¥Copy your pages Markdown files to i18n/fr/docusaurus-plugin-content-pages
, and translate them:
mkdir -p i18n/fr/docusaurus-plugin-content-pages
cp -r src/pages/**.md i18n/fr/docusaurus-plugin-content-pages
cp -r src/pages/**.mdx i18n/fr/docusaurus-plugin-content-pages
我们只复制 .md
和 .mdx
文件,因为 React 页面已经通过 JSON 翻译文件进行翻译。
¥We only copy .md
and .mdx
files, as React pages are translated through JSON translation files already.
默认情况下,Markdown 标题 ### Hello World
将具有生成的 ID hello-world
。其他文档可以将其与 [link](#hello-world)
链接。但是,翻译后,标题变为 ### Bonjour le Monde
,ID 为 bonjour-le-monde
。
¥By default, a Markdown heading ### Hello World
will have a generated ID hello-world
. Other documents can link it with [link](#hello-world)
. However, after translation, the heading becomes ### Bonjour le Monde
, with ID bonjour-le-monde
.
生成的 ID 并不总是适合本地化网站,因为它要求你本地化所有锚链接:
¥Generated IDs are not always a good fit for localized sites, as it requires you to localize all the anchor links:
- [link](#hello-world).
+ [link](#bonjour-le-monde)
对于本地化站点,建议使用 显式标题 ID。
¥For localized sites, it is recommended to use explicit heading IDs.
部署你的网站
¥Deploy your site
你可以选择在单个域下部署站点或使用多个(子)域。
¥You can choose to deploy your site under a single domain or use multiple (sub)domains.
单域部署
¥Single-domain deployment
运行以下命令:
¥Run the following command:
- npm
- Yarn
- pnpm
npm run build
yarn build
pnpm run build
Docusaurus 将为每个语言环境构建一个单页应用:
¥Docusaurus will build one single-page application per locale:
-
website/build
:默认语言为英语¥
website/build
: for the default, English language -
website/build/fr
:对于法语¥
website/build/fr
: for the French language
你现在可以将 deploy 和 build
文件夹添加到你选择的静态托管解决方案中。
¥You can now deploy the build
folder to the static hosting solution of your choice.
Docusaurus 网站使用以下策略:
¥The Docusaurus website uses this strategy:
静态托管提供商通常按照惯例将 /unknown/url
重定向到 /404.html
,始终显示英文 404 页面。
¥Static hosting providers generally redirect /unknown/url
to /404.html
by convention, always showing an English 404 page.
通过配置你的主机将 /fr/*
重定向到 /fr/404.html
来本地化你的 404 页面。
¥Localize your 404 pages by configuring your host to redirect /fr/*
to /fr/404.html
.
这并不总是可能的,并且取决于你的主机:GitHub Pages 做不到这一点,Netlify 可以。
¥This is not always possible, and depends on your host: GitHub Pages can't do this, Netlify can.
多域部署
¥Multi-domain deployment
你还可以为单一区域设置构建站点:
¥You can also build your site for a single locale:
- npm
- Yarn
- pnpm
npm run build -- --locale fr
yarn build --locale fr
pnpm run build --locale fr
Docusaurus 不会添加 /fr/
URL 前缀。
¥Docusaurus will not add the /fr/
URL prefix.
在你的 静态托管提供商 上:
¥On your static hosting provider:
-
为每个区域设置创建一个部署
¥create one deployment per locale
-
使用
--locale
选项配置适当的构建命令¥configure the appropriate build command, using the
--locale
option -
为每个部署配置你选择的(子)域
¥configure the (sub)domain of your choice for each deployment
此策略不适用于 GitHub Pages,因为它只能进行单一部署。
¥This strategy is not possible with GitHub Pages, as it is only possible to have a single deployment.
混合
¥Hybrid
某些语言环境可能使用子路径,而另一些语言环境则使用子域。
¥It is possible to have some locales using sub-paths, and others using subdomains.
还可以将每个语言环境部署为单独的子域,将子域组装在 CDN 级别的单个统一域中:
¥It is also possible to deploy each locale as a separate subdomain, assemble the subdomains in a single unified domain at the CDN level:
-
将你的站点部署为
fr.docusaurus.io
¥Deploy your site as
fr.docusaurus.io
-
配置 CDN 以从
docusaurus.io/fr
提供服务¥Configure a CDN to serve it from
docusaurus.io/fr
管理翻译
¥Managing translations
Docusaurus 不关心你如何管理翻译:它所需要的只是在构建过程中所有翻译文件(JSON、Markdown 或其他数据文件)在文件系统中可用。但是,作为网站创建者,你需要考虑如何管理翻译,以便你的翻译贡献者能够良好协作。
¥Docusaurus doesn't care about how you manage your translations: all it needs is that all translation files (JSON, Markdown, or other data files) are available in the file system during building. However, as site creators, you would need to consider how translations are managed so your translation contributors could collaborate well.
我们将分享两种常见的翻译协作策略:使用 git 和 使用 Crowdin。
¥We will share two common translation collaboration strategies: using git and using Crowdin.