戻る

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" と "these")に加えて名詞の複数形が必要。
Search results"No results"
"1 result found"
"2 results found"
0件、1件、複数件で表現を変える必要がある。

if などの条件分岐だけで対応しようとすると、すぐに手に負えなくなります。

そして、アプリを他の言語にも対応させて出荷しなければならないとなると、事態は悪夢のようになります。 英語でうまく動く実装は、数量の扱い方がまったく異なるポーランド語やアラビア語のような言語では、ほとんどの場合破綻してしまいます。 私たちが一緒に取り組んでいる企業の多くは、 このようなハードコードされた UI を書き換えるつらさのせいで、国際化対応をつい先送りにしてしまいます。


英語での複数形の扱い方

英語では、アプリで適切な複数形を使うのは、たいていの場合それほど難しくありません。

単純な名詞の複数形を扱うには、ユーティリティ関数を用意します。

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

これで、すべての複数形ロジックを1つの関数で扱えるようになりました。 不規則な複数形にも対応します。

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" というカテゴリ名を使います。 <Plural> コンポーネントは、n に指定された数値が、指定されたどの props とも一致しない場合、子要素の内容をフォールバックとして使用します。

このようなライブラリを使うことは、英語のみのアプリケーションであってもベストプラクティスであり、将来の国際化対応を大幅に容易にします。


多言語対応 React アプリにおける複数形 (i18n)

アプリの利用者が英語話者だけであれば、上記のアプローチだけで十分かもしれませんが、ほとんどの本番アプリケーションでは、最終的に複数の言語をサポートする必要が出てきます。そうなると話は一気に複雑になります。

  • アラビア語では、名詞は 0 個、1 個、2 個、またはそれ以上かによって異なる形になります
  • スペイン語、ドイツ語、イタリア語では、大きな数値でカンマの代わりにピリオドを使うため、1,000,000 は 1.000.000 になります
  • ヒンディー語では、桁が 2 桁ずつで区切られるため、1,000,000 は 10,00,000 になります

国際化対応したアプリでは、複数形や数値フォーマットの扱い方法について独自のドキュメントを備えた、専用の i18n ライブラリを使用する必要があります。

ロケールごとの数値の書式設定

Intl オブジェクトを使って、任意のロケールに合わせて数値をフォーマットできます。 これを行う最も簡単な方法は、組み込みの toLocaleString() メソッドを使うことです。

デフォルトでは、実行環境の現在のロケールが使用されます。

const n = 1000000
n.toLocaleString() // ランタイムロケールが "en-US" (アメリカ英語) の場合、1,000,000 を表示
n.toLocaleString('de') // ロケールが "de" (ドイツ語) に指定されているため、1.000.000 を表示

gt-react には、内部で Intl.NumberFormat使用する <Num> コンポーネントも用意されています。

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 がサポートする複数形フォームは 6 種類あり、zeroonetwofewmanyother です。 英語では one(「単数形」)と other(「複数形」)だけが使われますが、 アラビア語やポーランド語のような言語では、これら 2 つ以外にも複数のフォームが存在します。

たとえば、英語話者のユーザーは次のような表示を期待します。

"誰も見ていません"
"1人が見ています"
"2人が見ています"

アラビア語話者のユーザーは、単数形、双数形(数がちょうど 2 のとき)、さらに少数の複数形と多数の複数形で、それぞれ異なる表現を期待するかもしれません。

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

ここで、国際化ライブラリが重要になってきます。 言語ごとに、複数形をいつ・どのように表示するかのロジックが異なるため、 正しく処理するには、専用のライブラリに任せるほうがよいでしょう。

優れた i18n ライブラリは、次の2つを行います。

  1. ロケールに基づいて、どの複数形(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 の課題の 1 つです。最初から正しく実装しておけば、後から大規模なリファクタリングを行わずに済みます。