# General Translation Knowledge Base: 如何在 React 中处理复数形式？
URL: https://generaltranslation.com/zh/kb/how-to-handle-plurals-in-react.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: 不要用 n === 1 这类判断硬编码复数逻辑。如果应用只支持英语，一个简单的辅助函数就够用了；但多语言应用需要符合区域设置的复数规则，而 gt-react 或 gt-next 中的 `<Plural>` 等 i18n 组件能够为每种语言处理这些规则。
Category: 按区域设置格式化
Last updated: 2025-02-17
Authors: Archie McKenzie
Published: 2025-02-17

## Video

The video is narrated in English, with English captions.

[How do I handle plurals in React?](https://caxecubb9c64cezv.public.blob.vercel-storage.com/films/kb-how-to-handle-plurals-in-react.mp4) (48秒)

General Translation handles plurals in React with the `Plural` component. Run `npx gt init` and sign in, give `Plural` the count and an English sentence for each form, wrap it in a `T` component with the count in `Num`, then run `npx gt translate` to write the forms each language needs.

## 为什么在 React 中复数处理很重要

我们经常看到一些应用显示这样别扭的提示：

```text
You have 1 new message(s)
```

这一看就是开发者没有认真考虑过用户体验。

React 应用经常需要处理复数，例如通知数量、列表长度或搜索结果。
**而且要正确实现复数处理并不难，尤其是当你的应用只需要支持英语时。**
但新手开发者很容易踩进各种坑，尤其是在构建多语言界面时。

本指南将介绍如何优雅地处理英语复数形式、如何针对不同区域设置格式化数字，以及如何在 React 和 Next.js 中构建完整的多语言复数系统。

### 硬编码复数形式的问题

许多项目都把复数逻辑写死在代码里，其中甚至不乏一些规模庞大、举足轻重的项目，这一点着实令人意外。

```jsx
export default function Example({ n }) {
  return (
    <p>
      Displaying {n} item{n === 1 ? '' : 's'}
    </p>
  )
}
```

但复数处理往往不只是在单词末尾加一个 &quot;s&quot; 那么简单。
有些名词的复数形式是不规则的，比如 &quot;child&quot; 和 &quot;children&quot;。
有时，句子中的其他成分也需要随名词变化，比如根据数量选用
&quot;is&quot; 或 &quot;are&quot;。

下表列出了一些常见场景：

| 场景   | 示例                                                                                          | 备注                                                           |
| ---- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| 观看人数 | &quot;1 person is watching&quot; <br /> &quot;2 people are watching&quot;                   | 名词不规则变化 (&quot;person&quot; → &quot;people&quot;) ，动词也需随之变化。 |
| 删除条目 | &quot;Delete this message?&quot; <br /> &quot;Delete these 2 messages?&quot;                | 指示代词需要变化 (&quot;this&quot; 与 &quot;these&quot;) ，名词也要变为复数。   |
| 搜索结果 | &quot;No results&quot; <br /> &quot;1 result found&quot; <br /> &quot;2 results found&quot; | 零个、一个和多个结果各需不同的表述。                                           |

用条件表达式来处理，代码很快就会变得臃肿难维护。

而一旦需要将应用发布到其他语言，这简直就是一场噩梦。
在英语中行得通的写法，换到波兰语或阿拉伯语等语言中往往完全失效，因为这些语言处理数量的规则截然不同。
**与我们合作的许多公司，正是因为重构这类硬编码 UI 太过痛苦，
才迟迟没有推进国际化。**

***

## 如何处理英语中的复数形式

在英语中，让应用正确使用复数形式通常并不复杂。

对于简单的名词复数处理，可以编写一个工具函数：

```jsx
export function pluralize(
  count: number,
  singular: string,
  plural: string = singular + 's'
) {
  return `${count === 1 ? singular : plural}`;
}
```

现在，只需一个函数就能处理所有复数逻辑，
连不规则复数形式也同样适用：

```js
pluralize(2, 'user') // "users"
pluralize(2, 'person', 'people') // "people"
pluralize(2, 'child', 'children') // "children"
```

但如果你需要更复杂的逻辑呢？比如：

```text
"No one is watching"
"1 person is watching"
"2 people are watching"
```

到了这一步，你应该认真考虑引入一个低维护成本的国际化库 (&quot;i18n&quot;)。

虽然开发者常常认为 国际化库只适用于多语言界面，
但即使是单语言应用，它们在复数和变量格式化方面也大有用处。
大多数 国际化库在底层都使用 JavaScript 内置的 `Intl.PluralRules` API，来确定任意语言的正确复数形式。

React 的 国际化库有很多，其中也包括我们的 [`gt-react`](/docs/react/overview) (如果你使用 Next.js，则可以选择 [`gt-next`](/docs/react/nextjs-quickstart)) 。
用 gt-react 显示英文复数非常简单：

```jsx
import { Plural } from 'gt-react'

function Example({ count }) {
  return (
    <Plural n={count} zero={'No one is watching'} one={`${count} person is watching`}>
      {count} people are watching
    </Plural>
  )
}
```

UI 会根据 `n` 的值进行条件渲染。

大多数库都使用 JavaScript 的 [`Intl.PluralRules`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules) API 来决定显示哪种复数形式。
也就是说，在英语中，你需要用 `"one"` 表示单数，用 `"other"` 表示复数。
我们的 [`<Plural>`](/docs/react/reference/components/plural) 组件会先检查精确匹配的复数类别，再检查通用的 `plural` 和 `other` 属性。只有当这些属性都未提供时，才会回退到其子元素。

即使应用只支持英语，在这里使用库也是最佳实践，而且能让日后的国际化工作轻松许多。

***

## 多语言 React 应用中的复数处理 (i18n) 

如果你的应用只面向英语用户，上述方法可能就已足够——但大多数生产环境中的应用迟早都需要支持多种语言。这时问题就变得有意思了。

* 在阿拉伯语中，名词会根据数量是零、一、二还是多个而采用不同的形式
* 在 `de-DE`、`es-ES` 和 `it-IT` 等区域设置中，大数字用句点而非逗号分隔，因此 1,000,000 会写作 1.000.000
* 在印地语中，数字按两位一组分隔，因此 1,000,000 会写作 10,00,000

**对于国际化应用，应当使用专门的国际化库**，这类库都会提供相应的文档，说明如何处理复数形式和数字格式化。

### 针对不同区域设置格式化数字

你可以使用 `Intl` 对象，按任意区域设置[格式化数字](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat)。
最简单的做法是使用内置的 `toLocaleString()` 方法。

默认情况下，该方法会使用运行时的当前区域设置：

```javascript
const n = 1000000
n.toLocaleString() // 当运行时区域设置为 "en-US"（美式英语）时，显示为 1,000,000
n.toLocaleString('de') // 显示为 1.000.000，因为已将区域设置指定为 "de"（德语）
```

gt-react 还提供了一个 [`<Num>`](/docs/react/reference/components/num) 组件，其底层基于 `Intl.NumberFormat` 实现。

```jsx
import { Num } from 'gt-react'

// 当语言为 "en-US" 时显示 1,000,000
// 当语言为 "de" 时显示 1.000.000
// 当语言为 "hi" 时显示 10,00,000
export default function Example() {
  return <Num>1000000</Num>
}
```

### 理解不同语言中的复数形式

JavaScript 的 `Intl.PluralRules` API 支持六种复数形式：`zero`、`one`、`two`、`few`、`many`、`other`。
英语只用到 `one` (&quot;单数&quot;) 和 `other` (&quot;复数&quot;) ，
而阿拉伯语、波兰语等语言的复数形式则不止这两种。

例如，英语用户可能希望看到：

```text
"No one is watching"
"1 person is watching"
"2 people are watching"
```

而讲阿拉伯语的用户则可能期望单数、
双数 (数量恰好为两个时) ，以及少量复数和多量复数形式各有不同的表达：

```text
"لا أحد يشاهد"
"1 شخص يشاهد"
"2 شخصان يشاهدان"
"3 أشخاص يشاهدون"
"11 شخصاً يشاهدون"
```

这正是国际化库不可或缺的地方。
每种语言在何时以及如何显示复数形式上都有各自的规则，
因此最好借助专门的库来正确处理。

一个好的国际化库会做两件事：

1. 根据区域设置决定使用哪种复数形式 (`one`、`many`、`other` 等) 
2. 找到该形式在目标语言中对应的翻译

**如果你已经在使用某个国际化库，
请查阅其文档中有关复数格式化的说明。**
几乎所有库都提供了关于渲染复数形式的专门文档。

### 完整示例：多语言 React 应用中的复数形式

如果你还没有使用国际化库，不妨试试 [`gt-react`](/docs/react/react-quickstart)！

gt-react 的 [`<Plural>`](/docs/react/reference/components/plural) 组件：

* 以简单的声明式方式，在 React 中正确渲染复数形式
* 原生支持 [`<Num>`](/docs/react/reference/components/num) 格式化组件
* 原生支持 [`<T>`](/docs/react/reference/components/t) 翻译组件，该组件与我们的[翻译服务](https://generaltranslation.com)集成，可自动生成复数形式

把这些组成部分组合起来，就得到了一个完整的多语言组件：

```jsx
import { T, Plural, Num } from 'gt-react'

// 开箱即用，支持 100 多种语言
function Example({ count }) {
  return (
    <T>
      <Plural
        n={count}
        zero={'No one is watching'}
        one={
          <>
            <Num>{count}</Num> person is watching
          </>
        }
      >
        <Num>{count}</Num> people are watching
      </Plural>
    </T>
  )
}
```

***

如需设置复数处理，React 应用请参阅 [`gt-react` 快速入门](/docs/react/react-quickstart)，Next.js 应用请参阅 [`gt-next` 快速入门](/docs/react/nextjs-quickstart)。完整的组件 API 请参阅 [`<Plural>` API 参考](/docs/react/reference/components/plural)。

复数处理是 React 中最常见的 i18n 难题之一。从一开始就把它做好，能为日后省去大量重构工作。

## Related questions

- [如何在 JSX 中翻译条件文本？](https://generaltranslation.com/zh/kb/translate-conditional-jsx.mdx): 与其分别翻译三元表达式的两个分支，不如将整句包裹在 `<T>` 组件中，并用 `<Branch>` 表达条件。这样译者能看到完整的上下文，并能根据每种语言调整句子语序。
- [不用翻译文件也能做 i18n 吗？](https://generaltranslation.com/zh/kb/i18n-without-translation-files.mdx): 可以。使用 gt-react 或 gt-next 时，内容以内联方式直接写在组件中的 `<T>` 组件内，译文在构建时作为输出生成，因此无需编写键，也无需为每个区域设置编写 JSON 文件并保持同步。
- [应该自建 i18n，还是使用 General Translation？](https://generaltranslation.com/zh/kb/general-translation-vs-in-house-i18n.mdx): 手写的 i18n（每种语言一个字典，外加一个简单的 t() 辅助函数）足以应对一两种语言、少量文案的场景。随着应用规模扩大，你会发现自己在重新实现复数规则、后备内容和文件同步，而且译文仍然得自己产出。General Translation 的开源库负责渲染，General Translation 则根据你的代码生成并更新译文。

## Previous and next questions

- Next question: [如何在 JSX 中翻译条件文本？](https://generaltranslation.com/zh/kb/translate-conditional-jsx.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/zh/kb/sitemap.md) for every article.
