多くのテキストユーティリティはブラウザ内で完全に実行するのに十分シンプルです。

文カウンターはその好例です。ブラウザは既にテキストを受け入れ、解析し、有用な統計を表示するために必要なすべてを備えています。入力をリモートサーバーに送信することは、多くの場合不要です。

私は日常の執筆・編集向けの無料テキストユーティリティ集である TextFixHub の一環として、小さなブラウザベースの文カウンターを構築しました。

こちらでお試しください:

TextFixHub 文カウンター

テキストをローカルで処理する理由

テキスト入力には機密情報が含まれることがあります。たとえば:

  • 下書き記事
  • 学校の課題
  • 社内メモ
  • 顧客メッセージ
  • コードコメント
  • 未公開コンテンツ

基本的なカウントツールの場合、このテキストをサーバーにアップロードしても追加価値はほとんどありません。ローカル処理にはいくつかの有用な特性があります:

  • 入力がユーザーのデバイスから離れる必要がない
  • テキスト処理用 API を維持する必要がない
  • ネットワークリクエストを待たずにツールが応答できる
  • ユーザーアカウントなしでアプリケーションを利用できる
  • サーバーが送信されたテキストを保存・処理する必要がない

これはすべてのブラウザアプリケーションがデフォルトでプライベートであることを意味するものではありません。アナリティクス、サードパーティスクリプト、エラーレポート、ホスティング設定は依然として重要です。単にコアとなるテキスト解析をローカルに留められるということです。

ツールが計算するもの

文カウンターは単なる文の総数以上のものを表示します。次の項目も計算します:

  • 文の数
  • 単語数
  • スペースを含む文字数
  • スペースを含まない文字数
  • 段落数
  • 行数
  • 平均文長
  • 平均単語長
  • 推定読了時間
  • 推定発話時間

これらの追加統計により、ツールは「何文あるか」という質問に答えるだけでなく、編集・レビュー用途にも役立ちます。

シンプルな解析パイプライン

ブラウザ側の処理パイプラインは意図的にシンプルにしています:

  1. テキスト入力から現在の値を取得する
  2. 関連する空白を正規化する
  3. 文の境界を検出する
  4. テキストを単語・段落・行に分割する
  5. 派生統計を計算する
  6. 結果を即座に描画する

重要な設計選択は、入力がこの処理全体を通じてブラウザ内に留まることです。

アイデアの簡易版は次のようになります:

type TextStats = {
  sentences: number
  words: number
  characters: number
  charactersWithoutSpaces: number
  paragraphs: number
  lines: number
}

function countWords(text: string): number {
  const normalized = text.trim()

  if (!normalized) {
    return 0
  }

  return normalized.split(/\s+/).length
}

function countCharactersWithoutSpaces(text: string): number {
  return text.replace(/\s/g, '').length
}

Enter fullscreen mode Exit fullscreen mode

実際の実装では、空の入力、空白のみの入力、段落の境界、表示文字と空白の区別なども扱う必要があります。

文の境界は完全に自明ではない

文を数えるのは簡単そうに見えますが、現実のテキストが現れると複雑になります。

単純な方法では .!? などの句読点を数えることができます。しかし、ピリオドは次のような場所にも出現します:

  • 略語
  • 小数
  • ドメイン名
  • メールアドレス
  • バージョン番号
  • イニシャル

つまり、文カウンターは完全な言語理解を謳うべきではありません。定義されたヒューリスティックを持つ実用的なテキストユーティリティです。

軽量なブラウザツールでは、大規模な自然言語処理ライブラリに依存するよりも、透明性のあるヒューリスティックの方が有用な場合が多いです。ツールは、何を数えているのかを明示し、近似結果を科学的な言語学的測定値として提示しないようにすべきです。

読了時間と発話時間

読了時間と発話時間は単語数に基づく推定値です。

たとえば、アプリケーションは 1 分あたりの単語数の仮定を別々に使用できます:

const READING_WORDS_PER_MINUTE = 200
const SPEAKING_WORDS_PER_MINUTE = 130

const readingMinutes =
  wordCount / READING_WORDS_PER_MINUTE

const speakingMinutes =
  wordCount / SPEAKING_WORDS_PER_MINUTE

Enter fullscreen mode Exit fullscreen mode

これらの数値はすべての読者や話者に普遍的に当てはまる事実ではありません。テキストのおおよその規模をユーザーが把握するためのシンプルな参考推定値です。

インターフェースは誤った精度を暗示しないようにすべきです。「約 2 分」と表示する方が、小数点以下数桁まで表示するよりも一般的に有用です。

静的デプロイメントを採用した理由

このアプリケーションは Next.js と TypeScript で構築され、静的ウェブサイトとしてデプロイされています。

テキストを解析するためにデータベースやアプリケーションサーバーは必要ありません。これによりアーキテクチャを小さく保てます:

  • UI はブラウザで実行される
  • テキスト処理関数は通常の TypeScript モジュールである
  • ページは静的に生成可能である
  • デプロイメントの攻撃面がサーバー型実装より小さい

アーキテクチャを小さくすることで、プライバシーに関する説明も検証しやすくなります。主なテキスト処理はクライアントサイドのアプリケーションで確認でき、API の背後に隠されていません。

テキスト処理関数のテスト

UI はツールの一部に過ぎません。テキスト処理関数は独立してテストする必要があります。テストケース例:

  • 空の入力
  • 空白のみの入力
  • 1 文
  • 複数段落
  • 複数改行
  • 文末の句読点
  • 数字を含むテキスト
  • Unicode 文字を含むテキスト
  • 連続するスペースを含むテキスト

カウントロジックを UI から分離することで、これらのケースをテストしやすくなり、見た目の変更が基盤となる計算を破壊する可能性を低減できます。

学んだこと

主な教訓は、小さなツールでも明確な境界が必要だということです。

文カウンターにはアカウントシステム、データベース、テキスト処理 API は必要ありません。必要なのは:

  • 明確に定義されたカウント方法
  • 1 つの数値以上の有用な出力
  • 空や異常な入力に対する妥当な処理
  • コア関数のテスト
  • ユーザーのテキストに何が起こるかの明確な説明

ローカルファースト処理は、ウェブサイト全体の完全なプライバシー保証ではありません。コアタスクに対する実用的なアーキテクチャ上の決定です。つまり、ツールに入力されたテキストは、ツールが動作するためにアップロードされる必要がないということです。

完成したツールはこちらで利用できます:

TextFixHub 文カウンターをお試しください

その他の TextFixHub ツールは https://www.textfixhub.com/ で利用できます。