目次
言語をまたぐ複数形ルール:開発者ガイド
機能をリリースしました。翻訳も完了しています。英語では完璧に見えます。そしてバグ報告が届きます:「アプリが『1 個のアイテム』と『5 個のアイテムs』と表示している。」三項演算子でさっと修正します。6ヶ月後、ポーランドのユーザーからアプリ全体で文法的に正しくないテキストが表示されているという報告が届きます。複数形処理を壊したのではありません——そもそも正しく解決したことがなかったのです。
複数形は国際化において最も過小評価されている問題のひとつです。多くの開発者は単数形と複数形という英語の二項問題として扱います。しかし自然言語はそれよりはるかに複雑であり、本番環境のi18nではその複雑さが痛烈に返ってきます。このガイドでは、複数形が言語をまたいで実際にどう機能するか、正しく実装する方法、そしてコードレビューで毎回見逃されてしまうミスを防ぐ方法を解説します。
国際化の概念全般についてまだ馴染みがない場合は、ローカライゼーションと国際化のガイドが、言語固有の複数形メカニズムに踏み込む前の有益な基礎を提供しています。
複数形がなぜあなたの想像以上に重要か
コードベースでよく見られるパターンです:
const label = count === 1 ? 'item' : 'items';
これは英語では機能します。英語とそれに似た一握りの言語では。しかしトルコ語、アラビア語、ロシア語、ポーランド語に展開した瞬間、このアプローチは意味不明なテキストを生み出します——あるいはさらに悪いことに、ネイティブスピーカーには失礼に感じられるほど微妙に間違ったテキストになります。
本番環境での複数形エラーは実際の結果をもたらします:
- 信頼の喪失:ネイティブスピーカーはすぐに文法の誤りを見抜きます。製品が自分たちのために作られていないというシグナルになります。
- 法的リスク:一部の地域では、契約書、請求書、コンプライアンス文書における数量表現が文法的に正確である必要があります。
- アクセシビリティの失敗:スクリーンリーダーや支援技術は文法的に正しいテキストに依存しています。
根本的な原因はほぼ常に同じです:開発者が英語の複数形ロジックをハードコードし、翻訳者はどの形式を使うべきかというコンテキストのない文字列を受け取ります。
英語の複数形:見かけ上シンプル
英語には2つの複数形があります:単数形(1)と複数形(それ以外すべて)。これは三項式にきれいにマッピングできるため、開発者はそれをデフォルトとして使います。
// 英語:2つの形式、単数形と複数形
`${count} ${count === 1 ? 'file' : 'files'} uploaded`
しかし英語でさえ、エッジケースで複雑になります:
- ゼロ:「0 files」は自然に読めますが、一部のUIは「No files」を好みます。これは翻訳の決定ではなくデザインの決定ですが、それでも複数形サポートが必要です。
- 小数:「1.5 files」は文法的に曖昧です。英語では通常、非整数値に複数形を使いますが、ドメインによって異なります。
- 不規則名詞:「1 person」/「2 people」、「1 child」/「2 children」。これらは単純な接尾辞ルールでは処理できません。
英語が簡単に感じられるのは、相対的にそうであるからです。それを離れた瞬間、複雑さは劇的にスケールします。
他の言語が複数形をどう扱うか
アラビア語:6つの形式
アラビア語には数に応じて文法的に異なる6つの複数形があります:
| 形式 | 数 |
|---|---|
| zero | 0 |
| one | 1 |
| two | 2 |
| few | 3–10 |
| many | 11–99 |
| other | 100+(および小数) |
各形式には異なる単語または接尾辞が必要です。「X件のメッセージがあります」という文字列は、アラビア語では2つではなく6つの翻訳が必要です。アラビア語の翻訳者に単数形と複数形だけを送ることは、推測を求めることです——各形式でメッセージの構造が異なるため、推測は不可能です。
ロシア語とポーランド語:複雑なルールを持つ3〜4つの形式
ロシア語は3つの形式を使います:単数形(1)、few(2–4および2–4で終わる数、ただし12–14を除く)、many(それ以外すべて)。
ロシア語のルールは自明ではありません:
n % 10 === 1 && n % 100 !== 11 → 単数形 n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) → few それ以外すべて → many
つまり:「21 файл」(単数形)、「22 файла」(few)、「25 файлов」(many)、「11 файлов」(many——11は例外なので単数形ではない)。
ポーランド語は4つの形式と若干異なる境界でさらに複雑さを加えます。これを間違えると、ロシア人やポーランド人のユーザーはすぐに気づきます。これは難解なエッジケースではなく——日常的なUIに現れる数字です。
日本語、中国語、韓国語:複数形がない
これらの言語は単数形と複数形を文法的に区別しません。「1つのファイル」と「100個のファイル」は同じ名詞形を使います。代わりに、数量は数詞、助数詞(分類詞)、またはコンテキストによって表現されます。
これが意味することは:
- これらの言語の翻訳者に複数形を送らないでください——存在しない区別を翻訳するよう求めることになります。
- 数字はまだ表示されますが、名詞を活用させません。
- ここでの誤った複数形処理は通常、翻訳者が「other」形式を複製することとして現れます。技術的には問題ありませんが、冗長または不自然な表現になる可能性があります。
その他の注目すべきケース
- スラブ語全般(チェコ語、スロバキア語、クロアチア語):3〜4つの形式、複雑なモジュロルール。
- ウェールズ語:非常に不規則な境界を持つ6つの形式。
- ゲール語(スコットランドとアイルランド):1、2、3–10、11–19、20+で分かれる形式。
- ヘブライ語:単数形、双数形(正確に2)、複数形のための別々の形式。
CLDR複数形ルールの標準
Unicodeの共通ロケールデータリポジトリ(CLDR)は、主要なすべての言語の複数形ルールを定義しています。これらはブラウザ、オペレーティングシステム、i18nライブラリで使用される標準的なルールです。CLDRは複数形を6つの名前付きカテゴリに分類します:
zeroonetwofewmanyother
各言語はこれらのサブセットを使います。英語はoneとotherを使います。アラビア語は6つすべてを使います。日本語はotherのみを使います。
これらのルールは機械可読形式で利用可能であり、ほとんどの成熟したi18nライブラリにすでに組み込まれています。数学を自分で実装する必要はありません。これらのカテゴリが存在すること、そして翻訳ワークフローが特定の言語に必要なすべての形式を考慮する必要があることを理解する必要があります。
ICU MessageFormat:正しい抽象化
ICU MessageFormatは、翻訳文字列で複数形を表現するための最も広くサポートされている標準です。メッセージ自体の中に複数形ロジックを埋め込み、翻訳者が各形式を独立して扱えるようにします。
英語の構文:
{count, plural,
one {# file uploaded}
other {# files uploaded}
}
ロシア語(言語を知る翻訳者が提供した通り):
{count, plural,
one {Загружен # файл}
few {Загружено # файла}
many {Загружено # файлов}
other {Загружено # файлов}
}
#は実際の数字で置き換えられます。ライブラリはアクティブなロケールの複数形ルールを評価し、正しい形式を選択します。
このアプローチは文字列連結に比べて重要な利点があります:
- 各形式は完全な文なので、翻訳者は完全な文法的コンテキストを持ちます。
- 実行時の文字列アセンブリなし——メッセージは表示前に最終的な文字列に解決されます。
- 言語に適した形式——翻訳者はその言語が必要とする正確な形式を提供します。
- ツールサポート——リンター、抽出ツール、翻訳プラットフォームがこの形式を理解します。
i18nextで複数形を実装する
i18nextはJavaScriptエコシステムで最も広く使われているi18nライブラリのひとつであり、CLDRの複数形ルールをネイティブに処理します。
基本設定
import i18next from 'i18next';
i18next.init({
lng: 'en',
resources: {
en: {
translation: {
file_count: '{{count}} file',
file_count_other: '{{count}} files',
},
},
},
});
i18nextはキーサフィックス規則を使います:単数形にはkey、デフォルトの複数形にはkey_other、追加のCLDR形式にはkey_zero、key_one、key_two、key_few、key_many。countを補間変数として渡すと、ライブラリが自動的に正しいキーを選択します。
i18next.t('file_count', { count: 1 }); // "1 file"
i18next.t('file_count', { count: 5 }); // "5 files"
React統合
react-i18nextを使用する場合:
import { useTranslation } from 'react-i18next';
function FileCount({ count }: { count: number }) {
const { t } = useTranslation();
return <span>{t('file_count', { count })}</span>;
}
/i18n/reactセットアップでは、これが推奨パターンです。count変数は補間(数字の表示)と複数形選択の両方を制御します。
複数のCLDR形式の処理
ロシア語の場合、翻訳ファイルには必要なすべての形式が必要です:
{
"file_count_one": "{{count}} файл",
"file_count_few": "{{count}} файла",
"file_count_many": "{{count}} файлов",
"file_count_other": "{{count}} файлов"
}
i18nextはロケールの正しいCLDRルールを適用し、適切なキーを選択します。ルール評価は組み込まれています——モジュロロジックを自分で書く必要はありません。
next-i18nextを使ったNext.js
next-i18nextを使う/i18n/nextjsアプリの場合、パターンは同じですが翻訳はpublic/locales/{lng}/{ns}.jsonに置かれます:
// public/locales/ru/common.json
{
"file_count_one": "{{count}} файл",
"file_count_few": "{{count}} файла",
"file_count_many": "{{count}} файлов",
"file_count_other": "{{count}} файлов"
}
import { useTranslation } from 'next-i18next';
export function FileCount({ count }: { count: number }) {
const { t } = useTranslation('common');
return <p>{t('file_count', { count })}</p>;
}
よくある落とし穴
1. 間違った変数名
i18nextは複数形選択をトリガーするためにcountを使います。他の変数名を使うと複数形ロジックが完全にスキップされます:
// 間違い——複数形選択がトリガーされない
t('file_count', { number: 5 });
// 正しい
t('file_count', { count: 5 });
これはサイレント障害です。フォールバック形式(_other)が使われるため、英語は正常に見える一方で他の言語はサイレントに壊れます。
2. ターゲットロケールの複数形が不足している
ロシア語ロケールにkeyとkey_otherだけを定義すると、ライブラリはすべての形式でkey_otherにフォールバックします。ロシア語ユーザーは文法的に正しくないテキストを受け取り、コンソールにはエラーが表示されません。これは出荷済みソフトウェアで最も一般的な複数形バグです。
解決策は、出荷前に各ロケールに必要なすべてのCLDR形式を必須とすることです。このチェックを自動化してください——手動レビューに頼らないでください。
3. 複数形キーの代わりに文字列連結
// 間違い
const message = t('you_have') + ' ' + count + ' ' + t('messages');
// 正しい——数と周囲の言葉は1つの翻訳可能な単位
t('you_have_messages', { count });
連結は、数とともに語順、名詞形、または動詞の一致が変わる言語を構造的に処理不可能にします。数を含む句全体が1つの翻訳キーである必要があります。
4. 複数形と序数を混同する
序数(「1st」、「2nd」、「3rd」)は基数の複数形とまったく異なるルールに従います。混同しないでください。i18nextにはordinal: trueオプションによる個別の序数サポートがあります:
t('position', { count: 1, ordinal: true }); // "1st place"
t('position', { count: 2, ordinal: true }); // "2nd place"
序数ルールもロケール固有です——フランス語では、最初に「1er」を使い、その後のすべての序数に同じ接尾辞を使います。
5. ゼロを特別な複数形として扱う
一部のデザインでは「0 files」の代わりに「No files」を求めます。これは複数形ではなく、UIの決定です。翻訳関数を呼び出す前にコードで明示的に処理するか、別の翻訳キーを使ってください:
const key = count === 0 ? 'no_files' : 'file_count';
t(key, { count });
UIのコピー決定にzero CLDR形式を頼らないでください。zero形式はゼロを文法的に他の値と区別する言語のために存在し、デザインフックとしてではありません。
複数形翻訳のテスト
複数形処理は悪名高いほどテスト不足です。i18n品質プロセスの構造化についてより広い視点を持つには、i18nテストツールと自動化戦略のガイドがこれらのパターンと合わせて統合できるツールをカバーしています。
境界値テスト
出荷するロケールの各境界値をテストしてください:
const testCounts = [0, 1, 2, 3, 4, 5, 11, 12, 21, 22, 100, 101];
for (const count of testCounts) {
console.log(`${count}: ${t('file_count', { count })}`);
}
ロシア語には特に:1、2、5、11、12、21、22、25、100、101、111、121。
各ロケールのスナップショットテスト
describe('file_count 複数形', () => {
it.each([
['en', 1, '1 file'],
['en', 5, '5 files'],
['ru', 1, '1 файл'],
['ru', 2, '2 файла'],
['ru', 5, '5 файлов'],
['ru', 11, '11 файлов'],
['ru', 21, '21 файл'],
['ar', 1, '1 ملف'],
['ar', 3, '3 ملفات'],
])('ロケール %s、count %d → %s', (lng, count, expected) => {
i18next.changeLanguage(lng);
expect(i18next.t('file_count', { count })).toBe(expected);
});
});
これにより、欠落している形式、間違ったCLDR境界、翻訳者のエラーがユーザーに届く前に検出されます。
欠落した形式のLint
ソースロケールのキーを読み取り、すべての複数形キー(_otherで終わるもの)を識別し、各ターゲットロケールでそのロケールに必要なCLDR形式を確認するスクリプトを作成してください。形式が欠落している場合はCIを失敗させてください。これにより、サイレントフォールバックのバグクラスを完全に防止できます。
型安全性がゲームを変える場所
キー文字列ベースのi18nの弱点は、呼び出しサイトで正しい使用を強制するものがないことです。countの代わりにnumberを渡したり、countを完全に省略したり、存在しないキーを参照したりできます——これらはすべてランタイムでサイレントに失敗します。
型安全なi18nツールは翻訳キーからTypeScript型を生成するため、コンパイラが次を検出できます:
- 必要な補間変数の欠落
- 間違った変数名の使用(
numbervscount) - 存在しないキーの参照
Better i18nは、翻訳スキーマを理解し、公開時に複数形の完全性を強制するSDKでこのパターンの上に構築されています。新しい複数形キーを追加すると、SDKは更新された型を生成します——そして必要なcount変数を提供しない呼び出しサイトは型エラーになります。数百のキーを持つ数十のロケールを管理するチームにとって、これはコードレビューの前に複数形バグのクラス全体を捕捉します。
複数形化のために最も重要なプラットフォームの機能は:強制的な形式の完全性(公開前に必要なすべてのCLDR形式が存在する必要がある)、コンテキストを理解するAI翻訳(「これはファイル数です、適切な複数形を使ってください」)、ドメイン固有の名詞が一貫して活用されるようにするための用語集の適用です。
複数形の正確さは、適切に構造化されたウェブサイトの翻訳とローカライゼーションのワークフローとも深く結びついています——複数形が翻訳の残りと同じプロセスの一部として収集・レビューされると、不完全なままになる可能性がはるかに低くなります。
実践的なまとめ
正しく実装された複数形化には以下が必要です:
ICU MessageFormatまたはCLDRルールを実装するライブラリを使ってください。 独自の複数形ロジックを書かないでください。
i18nextでは常に
countを補間変数として使ってください。 他の名前は複数形選択をバイパスします。各ロケールのすべてのCLDR形式を提供してください。 翻訳者に文字列を送る前に、言語が必要とする形式を確認してください。
数を含む文字列を連結しないでください。 句全体が1つの翻訳キーです。
各ロケールの境界値をテストしてください。 特にロシア語、ポーランド語、アラビア語、その他の複雑なルールを持つ言語。
CIで形式の完全性を強制してください。 欠落した複数形は英語のテストでは見えないサイレントフォールバックを引き起こします。
序数と基数を区別してください。 異なるルールに従い、個別の処理が必要です。
これを正しく行うためのツールは存在します。CLDRルールは標準化されています。ライブラリはそれらを実装しています。ほとんどのコードベースでの失敗モードは技術的なものではありません——英語の複数形ロジックが一般化できると仮定し、そうでない場合を検出するワークフローを構築しないことです。
better-i18nでアプリをグローバルに
better-i18nはAI駆動の翻訳、gitネイティブなワークフロー、グローバルCDN配信を一つの開発者ファーストのプラットフォームに組み合わせています。スプレッドシートの管理をやめて、すべての言語でリリースを始めましょう。