返回

如何在 React 中处理复数形式

Archie McKenzie avatarArchie McKenzie
guidepluralsinternationalizationreactnextjsi18nintl

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

我们经常看到一些应用会显示很生硬的提示,例如:

您有 1 条新消息

这清楚地说明,这位开发者没有认真考虑过用户体验。

React 应用通常都需要处理复数形式——比如通知数量、列表长度或搜索结果。 而且,要把复数处理好并不难,尤其是当你的应用只需要支持英文时。 但很多新手开发者仍然会陷入一些不良实践之中,特别是在构建多语言界面时。

本指南将介绍如何优雅地处理英文复数、为不同的语言环境格式化数字,以及在 React 和 Next.js 中构建一个完整的多语言复数系统。

硬编码复数逻辑的问题

许多项目——包括一些规模巨大、业务关键的重要项目——都会在代码中直接硬编码复数逻辑。

export default function Example({ n }) {
  return (
    <p>
      显示 {n} 个项目
    </p>
  )
}

但复数化往往比在单词末尾加一个 "s" 要复杂得多。 有些名词有不规则复数形式,比如 "child" 和 "children"。 有时,句子的其他部分也需要随之改变以反映名词的变化,比如 "is" 和 "are" 会根据数量不同而变化。

下表展示了一些常见场景:

ScenarioExamplesNotes
Viewer count"1 person is watching"
"2 people are watching"
不规则名词("person" → "people")并且需要修改动词。
Item deletion"Delete this message?"
"Delete these 2 messages?"
指示代词变化("this" vs. "these")以及名词复数化。
Search results"No results"
"1 result found"
"2 results found"
针对零、一个和多个结果使用不同的措辞。

使用条件表达式很快就会变得难以维护。

而当你需要将应用发布到其他语言环境时,这会变成一场噩梦。 在英语中可行的方法在波兰语或阿拉伯语等语言中往往完全失效,因为它们在处理数量时有完全不同的规则。 与我们合作的公司常常因为重构这类硬编码 UI 的痛苦而推迟国际化。


如何在英语中处理复数

在英语中,在应用中正确处理复数通常比较简单。

对于简单的名词复数变化,可以写一个工具函数:

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

现在你只需使用一个函数就能处理所有的复数逻辑, 而且也适用于不规则复数形式:

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

那如果你需要更复杂的逻辑呢,例如:

"No one is watching"
"1 person is watching"
"2 人正在观看"

在这个阶段,你应该认真考虑使用一个低维护成本的国际化(“i18n”)库。

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

有很多用于 React 的 i18n 库,包括我们的 gt-react(如果你使用 Next.js,则可以使用 gt-next)。 使用 gt-react 处理英文复数非常简单:

import { Plural } from 'gt-react'

function Example({ count }) {
  return (
    <Plural n={count} zero={'无人观看'} one={`${count} 人正在观看`}>
      {count} 人正在观看
    </Plural>
  )
}

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

大多数库会使用 JavaScript 的 Intl.PluralRules API 来决定显示哪一种复数形式。 这意味着在英语中,你会使用名称 "one" 表示单数,使用 "other" 表示复数。 当传给 n 的数字与任何已提供的 prop 都不匹配时,我们的 <Plural> 组件会回退为渲染其子元素。

即使是仅支持英文的应用,在这里使用库也是最佳实践,并且能让后续的国际化工作变得容易得多。


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

如果你的应用只面向讲英语的用户,上面的做法可能就足够了——但大多数生产环境中的应用最终都需要支持多种语言。也正是在这里,事情开始变得有趣。

  • 在阿拉伯语中,名词会根据数量是零、一个、两个还是多个而采用不同形式
  • 在西班牙语、德语和意大利语中,大数字使用句号而不是逗号,因此 1,000,000 会写成 1.000.000
  • 在印地语中,数字按两位一组进行分组,因此 1,000,000 会写成 10,00,000

对于国际化应用,你应该使用专门的 i18n 库,这类库会在其文档中说明如何处理复数形式和数字格式化。

为不同语言环境格式化数字

你可以使用 Intl 对象为任意语言环境格式化数字。 最简单的方式是使用内置的 toLocaleString() 方法。

默认情况下,它会使用运行时当前的本地化设置(locale):

const n = 1000000
n.toLocaleString() // 当运行时语言环境为 "en-US"(美式英语)时显示 1,000,000
n.toLocaleString('de') // 1.000.000,因为语言环境已指定为 "de"(德语)

gt-react 还提供了一个 <Num> 组件,该组件在内部依赖 Intl.NumberFormat 进行格式化

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 支持的六种复数形式为:zeroonetwofewmanyother。 虽然英语只使用 one(“单数”)和 other(“复数”), 但像阿拉伯语和波兰语这样的语言有多于这两种的复数形式。

例如,说英语的用户可能会期望:

"No one is watching"
"1 person is watching"
"2 人正在观看"

比如,说阿拉伯语的用户可能会期望:单数、 双数(当数量恰好为两个时),以及小量复数和大量复数形式都使用不同的表达方式:

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

这正是国际化库发挥关键作用的地方。 每种语言在何时以及如何处理复数都有自己的规则, 因此最好依赖专门的库来保证正确实现。

一个优秀的 i18n 库会完成两件事:

  1. 根据 locale 决定应使用哪种复数形式(onemanyother 等)
  2. 在对应语言中找到与该形式匹配的翻译

如果你已经在使用国际化库, 请查阅其文档中关于复数格式的说明。 几乎所有库都有专门介绍如何处理复数渲染的文档。

完整示例:多语言 React 应用中的复数处理

如果你还没有使用国际化库,可以考虑试试 gt-react

gt-react 的 <Plural> 组件:

  • 提供一种在 React 中正确渲染复数形式的简单、声明式方式
  • 可与 <Num> 格式化组件无缝配合使用
  • 可与 <T> 翻译组件无缝配合使用,该组件集成了我们的免费翻译服务,可自动生成复数形式

将所有构建模块组合在一起后,下面是一个完整的多语言组件:

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

// Works out of the box in 100+ languages
function Example({ count }) {
  return (
    <T>
      <Plural
        n={count}
        zero={'No one is watching'}
        one={
          <>
            <Num>{count}</Num> 人正在观看
          </>
        }
      >
        <Num>{count}</Num> people are watching
      </Plural>
    </T>
  )
}

下一步

准备好在 React 应用中正确处理复数了吗?请查看我们的快速入门指南:

复数处理是 React 中最常见的 i18n 挑战之一——从一开始就处理得当,可以大幅减少后续重构的工作量。