React 中的复数处理入门
什么是复数处理?
我经常看到一些应用会显示很生硬的提示,例如:
您有 1 条新消息
这清楚地说明,这位开发者没有认真考虑过用户体验。
React 应用通常都需要处理复数形式——比如通知数量、列表长度或搜索结果。 而且,要把复数处理好并不难,尤其是当你的应用只需要支持英文时。 但很多新手开发者仍然会陷入一些不良实践之中,特别是在构建多语言界面时。
硬编码复数逻辑
许多项目——包括一些规模巨大、业务关键的重要项目——都会在代码中直接硬编码复数逻辑。
export default function Example({ n }) {
return (
<p>
显示 {n} 个项目
</p>
)
}但复数形式往往比在单词末尾加一个 "s" 要复杂得多。 有些名词有不规则复数形式,比如 "child" 和 "children"。 有时,句子的其他部分也需要随之改变以匹配变化后的单词,比如 "is" 和 "are" 会根据数量不同而变化。
下表展示了一些常见场景:
| Scenario | Examples | Notes |
|---|---|---|
| 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"但如果你需要更复杂的逻辑,比如:
"无人观看"
"1 人正在观看"
"2 人正在观看"
在这个阶段,你应该认真考虑使用一个低维护成本的国际化(“i18n”)库。
虽然开发者通常认为 i18n 库只用于多语言界面, 但即使是在单语言应用中,它们在处理复数和变量格式化方面也非常有用。
目前有许多 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 对象 来决定展示哪一种复数形式。
这意味着在英文中,你会使用 "one" 表示单数,使用 "other" 表示复数。
如果传给 n 的数值与任一已提供的 prop 都不匹配,我们的 <Plural> 组件会回退为渲染其子元素。
即使是仅支持英文的应用,在这里使用库也是最佳实践,也能让后续的国际化工作变得更加容易。
国际化(i18n)与复数形式
为界面提供多语言支持会让复数形式的展示复杂得多。
- 在阿拉伯语中,名词会根据数量是零、一个、两个还是多个而采用不同形式
- 在西班牙语、德语和意大利语中,大数字使用句号而不是逗号,因此 1,000,000 会写成 1.000.000
- 在印地语中,数字按两位一组进行分组,因此 1,000,000 会写成 10,00,000
对于一个国际化应用,你应该使用专门的库,这类库会在其文档中说明如何处理复数形式和数字格式化。
为不同语言格式化数字
你也可以使用 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 对象支持的六种复数形式是:zero、one、two、few、many、other。
虽然英语只使用 one(“单数”)和 other(“复数”),
但像阿拉伯语和波兰语这样的语言有多于这两种的复数形式。
例如,说英语的用户可能会期望:
"无人观看"
"1 人正在观看"
"2 人正在观看"
比如,说阿拉伯语的用户可能会期望:单数、 双数(当数量恰好为两个时),以及小量复数和大量复数形式都使用不同的表达方式:
"无人观看"
"1 人观看"
"2 人观看"
"3 人观看"
"11 人观看"
这正是国际化库发挥关键作用的地方。 每种语言在何时以及如何处理复数都有自己的规则, 因此最好依赖专门的库来保证正确实现。
一个优秀的国际化库会完成两件事:
- 根据 locale 决定应使用哪种复数形式(
one、many、other等) - 在对应语言中找到与该形式匹配的翻译
如果你已经在使用国际化库, 请查阅其文档中关于复数格式的说明。 几乎所有库都有专门介绍如何处理复数渲染的文档。
将所有构建模块组合在一起
如果你还没有使用国际化库,可以考虑试试 gt-react!
gt-react 的 <Plural> 组件:
- 提供一种简单实用的方式,以正确渲染复数形式
- 可与
<Num>格式化组件无缝配合使用 - 可与
<T>翻译组件无缝配合使用,该组件集成了我们的免费翻译服务,可自动生成复数形式
将所有构建模块组合在一起后,我们就拥有了一个完整的多语言界面:
import { T, Plural, Num } from 'gt-react'
// 开箱即用,支持 100+ 种语言
function Example({ count }) {
return (
<T>
<Plural
n={count}
zero={'暂无观看'}
one={
<>
<Num>{count}</Num> 人正在观看
</>
}
>
<Num>{count}</Num> 人正在观看
</Plural>
</T>
)
}