戻る

React における複数形処理入門

Archie McKenzie avatarArchie McKenzie
guidepluralsinternationalizationreactnextjs

複数形対応とは?

次のような、ぎこちないメッセージを表示するアプリにしばしば出会います。

新しいメッセージが1件あります

これは、ユーザー体験について十分に考えられていない開発者にありがちなサインです。

React アプリでは、通知件数、リストの長さ、検索結果などで複数形への対応が必要になることがよくあります。 しかも、アプリが英語だけでよいのであれば、複数形を正しく扱うのはそれほど難しくありません。 しかし、とくに多言語インターフェースでは、新人開発者が陥りがちな悪い慣習がたくさんあります。

ハードコードされた複数形

多くのプロジェクト — 規模が大きく重要なものも驚くほど多く — が、複数形のロジックをハードコードしています。

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}`;
}

これで、すべての複数形ロジックを処理する単一の関数ができました。 不規則な複数形にも対応できます。

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

でも、次のような、より複雑なロジックが必要な場合はどうすればよいでしょうか。

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

この段階に来たら、保守負荷の低い国際化(「i18n」)ライブラリの導入を真剣に検討すべきです。

i18n ライブラリは多言語インターフェース専用のものだと開発者は考えがちですが、 単一言語のアプリケーションでも、複数形や変数のフォーマットに非常に役立ちます。

React 向けの i18n ライブラリは数多くあり、General Translation の 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" という名前を用いるということです。 <Plural> コンポーネントは、n に渡された数値が、指定されたどの props にも一致しない場合、その子要素をフォールバックとして使用します。

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


国際化 (i18n) と複数形

多言語インターフェースに対応すると、複数形の扱いははるかに複雑になります。

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

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

言語ごとの数値の書式設定

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

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

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

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

"視聴者なし"
"1人が視聴中"
"2人が視聴中"
"3人が視聴中"
"11人が視聴中"

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

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

  1. ロケールに基づいて、どの複数形(onemanyother など)を使うかを判定する
  2. その複数形に対応する、適切な言語の翻訳を取得する

すでに国際化ライブラリを導入している場合は、 複数形フォーマットについて、そのドキュメントを確認してください。 ほとんどのライブラリには、複数形のレンダリングに関する専用ドキュメントがあります。

すべての構成要素を組み合わせる

まだ国際化ライブラリをお使いでない場合は、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>
  )
}

さらに詳しく知りたい方は、gt-react または gt-next のセットアップ方法に関するドキュメントをご覧ください。