diff --git a/website/docs/i18n/i18n-tutorial.md b/website/docs/i18n/i18n-tutorial.md
index 3d20fa3d865c..2fa6975c75bc 100644
--- a/website/docs/i18n/i18n-tutorial.md
+++ b/website/docs/i18n/i18n-tutorial.md
@@ -214,11 +214,11 @@ The `docusaurus write-translations` command will statically analyze all React co
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_:
-```tsx
+```jsx
const items = [
{id: 1, title: 'Hello'},
{id: 2, title: 'World'},
-]
+];
function ItemsList() {
return (
@@ -236,11 +236,11 @@ function ItemsList() {
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:
-```tsx
+```jsx
const items = [
{id: 1, title: Hello},
{id: 2, title: World},
-]
+];
function ItemsList() {
return (
@@ -258,6 +258,52 @@ You can see the calls to the translation APIs as purely _markers_ that tell Docu
:::
+#### Pluralization {#pluralization}
+
+When you run `write-translations`, you will notice that some labels are pluralized:
+
+```json title="i18n/en/code.json"
+{
+ // ...
+ "theme.blog.post.plurals": "One post|{count} posts"
+ // ...
+}
+```
+
+Every language will have a list of [possible plural categories](https://unicode-org.github.io/cldr-staging/charts/37/supplemental/language_plural_rules.html). 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:
+
+```jsx
+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 (
+ <>
+
{message}
+ {items.map((item) => - {item.title}
)}
+ >
+ );
+}
+```
+
+:::note
+
+Docusaurus uses [`Intl.PluralRules`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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 {#translate-plugin-data}
JSON translation files are used for everything that is interspersed in your code: