Назад

Плюрализация в React: основы

Archie McKenzie avatarArchie McKenzie
guidepluralsinternationalizationreactnextjs

Что такое множественное число?

Я часто встречаю приложения, которые отображают корявые сообщения вроде:

У вас 1 новое сообщение

Это явный признак разработчика, который всерьёз не задумывался о пользовательском опыте.

Приложения на React часто нуждаются в поддержке множественного числа — для счетчиков уведомлений, длины списков или результатов поиска. И корректно реализовать работу с формами множественного числа не так уж сложно, особенно если вам нужен интерфейс только на английском. Но существует множество плохих практик, в которые скатываются начинающие разработчики, особенно при создании многоязычных интерфейсов.

Жёстко закодированные формы множественного числа

Многие проекты — в том числе удивительно крупные и важные — жёстко прошивают логику множественного числа прямо в код.

export default function Example({ n }) {
  return (
    <p>
      Отображается {n} элемент{n === 1 ? '' : (n >= 2 && n <= 4 ? 'а' : 'ов')}
    </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"

Но что, если вам нужна более сложная логика, например:

"Никто не смотрит"
"Смотрит 1 человек"
"Смотрят 2 человека"

На этом этапе вам стоит всерьёз задуматься о библиотеке интернационализации («i18n») с низкими затратами на поддержку.

Хотя разработчики часто думают, что библиотеки i18n нужны только для многоязычных интерфейсов, они могут быть очень полезны для работы с формами множественного числа и форматированием переменных даже в однозначных приложениях.

Существует множество библиотек i18n для React, включая нашу, gt-react (или gt-next, если вы используете Next.js). Отобразить форму множественного числа в английском языке с помощью gt-react очень просто:

import { Plural } from 'gt-react'

function Example({ count }) {
  return (
    <Plural n={count} zero={'Никто не смотрит'} one={`Смотрит ${count} человек`}>
      Смотрят {count} человек
    </Plural>
  )
}

Интерфейс условно рендерится в зависимости от значения n.

Большинство библиотек используют объект JavaScript Intl, чтобы определить, какую форму множественного числа отображать. Это означает, что в английском вы будете использовать ключ "one" для единственного числа и "other" для множественного. Наш компонент <Plural> по умолчанию рендерит своих потомков, если число, переданное в n, не соответствует ни одному из переданных пропсов.

Использование библиотеки здесь является наилучшей практикой даже для приложений только на английском и значительно упрощает будущую интернационализацию.


Интернационализация (i18n) и формы множественного числа

Поддержка многоязычного интерфейса значительно усложняет работу с формами множественного числа.

  • В арабском языке существительные имеют разные формы в зависимости от того, ноль, одна, две или много единиц
  • В испанском, немецком и итальянском языках в больших числах используются точки вместо запятых, поэтому 1,000,000 записывается как 1.000.000
  • В хинди цифры группируются по две, поэтому 1,000,000 записывается как 10,00,000

В интернационализированном приложении следует использовать специализированную библиотеку, у которой будет собственная документация по работе с формами множественного числа и форматированием чисел.

Форматирование чисел для разных языков

Вы также можете использовать объект Intl, чтобы форматировать числа. Самый простой способ сделать это — воспользоваться встроенным методом toLocaleString().

По умолчанию он будет использовать текущую локаль среды выполнения:

const n = 1000000
n.toLocaleString() // выводит 1,000,000, когда локаль среды выполнения — "en-US" (американский английский)
n.toLocaleString('de') // 1.000.000, так как локаль указана как "de" (немецкий)

gt-react также предоставляет компонент <Num>, который использует Intl.NumberFormat под капотом.

import { Num } from 'gt-react'

// отображает 1,000,000, когда язык — "en-US"
// отображает 1.000.000, когда язык — "de"
// отображает 10,00,000, когда язык — "hi"
export default function Example() {
  return <Num>1000000</Num>
}

Отображение альтернативных форм множественного числа

Объект Intl в JavaScript поддерживает шесть форм множественного числа: zero, one, two, few, many, other. Хотя в английском используются только one («единственное число») и other («множественное число»), в таких языках, как арабский и польский, форм больше, чем две.

Например, англоязычный пользователь может ожидать:

"Никто не смотрит"
"Смотрит 1 человек"
"Смотрят 2 человека"

В то время как арабоязычный пользователь может ожидать разных форм для единственного числа, двойственного числа (когда количество ровно два объекта), а также для малых и больших множественных форм:

"Никто не смотрит"
"Смотрит 1 человек"
"Смотрят 2 человека"
"Смотрят 3 человека"
"Смотрят 11 человек"

Именно здесь библиотека для интернационализации становится незаменимой. У каждого языка своя логика того, когда и как отображать формы множественного числа, поэтому лучше полагаться на специализированную библиотеку, чтобы всё работало корректно.

Хорошая библиотека для интернационализации делает две вещи:

  1. Определяет, какую форму множественного числа (one, many, other и т. д.) использовать на основе локали
  2. Находит перевод на нужном языке, который соответствует этой форме

Если у вас уже есть библиотека для интернационализации, изучите её документацию по форматированию множественного числа. Почти у всех библиотек есть отдельные разделы документации, посвящённые рендерингу форм множественного числа.

Собираем все блоки воедино

Если у вас ещё нет библиотеки для интернационализации, присмотритесь к gt-react!

Компонент <Plural> из gt-react:

  • простой и удобный способ корректно выводить формы множественного числа
  • нативно работает с форматирующим компонентом <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>
  )
}

Хотите узнать больше? Ознакомьтесь с документацией по настройке gt-react или gt-next.