Jekyllで日本語の文字カウントに対応させる

おそらく多くのJekyllテーマでは日本語表記に対応はしていても、1つの記事を読むのにかかる時間を計算するときに、日本語をできるだけ正確にカウントできる仕様になっていないと思う。かくいう私のブログで採用したフリーテーマも仕様になかったので、改善した。

## Jekyllテーマ色々

[テーマをピックアップ - Jekyll 日本語サイト](http://jekyllrb-ja.github.io/docs/themes/#%E3%83%86%E3%83%BC%E3%83%9E%E3%82%92%E3%83%94%E3%83%83%E3%82%AF%E3%82%A2%E3%83%83%E3%83%97)に複数のギャラリーリンクが存在する。有料のものも存在する。

[私のサイト](https://nyshiyama.github.io/mtnishi/)で採用しているテーマは[Minimal Mistakes](https://mmistakes.github.io/minimal-mistakes/docs/quick-start-guide/)。一応、検索したら他にも利用している日本人がいる（[T-pot](https://www.t-pot.me/)、[k11i.biz](https://k11i.biz/)、[JekyllでMinimal Mistakesのテーマを使う - Qiita](https://qiita.com/naoyukisugi/items/42f16b7004eb0ef6ba85)）。どうやら他テーマに比べて拡張しやすいらしい（他のテーマをまともに見ていないので知らない）。

## 本題：日本語文字数カウント

[【Jekyll】日本語記事の「この記事は何分で読めます」を表示する | T-Pot](https://www.t-pot.me/tech/jekyllRT/)を参考にした。

### 元の状態

Jekyllテーマを管理しているディレクトリ上で`number_of_words`を検索すると、該当ファイルが見つかるはず。Minimal mistakesの場合は、`./_includes/page__meta.html`。以下、ファイル内のコード抜粋。

```html
{% assign document = post | default: page %}

{% if document.read_time %}
    {% assign words_per_minute = document.words_per_minute | default: site.words_per_minute | default: 200 %}
    {% assign words = document.content | strip_html | number_of_words %}
    <span class="page__meta-readtime">
        <i class="far {% if include.type == 'grid' and document.read_time and document.show_date %}fa-fw {% endif %}fa-clock" aria-hidden="true"></i>
        {% if words < words_per_minute %}
            {{ site.data.ui-text[site.locale].less_than | default: "less than" }} 1 {{ site.data.ui-text[site.locale].minute_read | default: "minute read" }}
        {% elsif words == words_per_minute %}
            1 {{ site.data.ui-text[site.locale].minute_read | default: "minute read" }}
        {% else %}
            {{ words | divided_by: words_per_minute }} {{ site.data.ui-text[site.locale].minute_read | default: "minute read" }}
        {% endif %}
    </span>
{% endif %}
```

おおざっぱに説明すると、`assign`で変数を定義しており、`read_time = true`つまり読了予想時間を表示させる設定にしている場合は、別ファイルで定義した`words_per_minute`（つまり1分間に何単語読めるか）よりもドキュメントの単語数が少ないか、同じか、多いかでif分岐して`数字 minute read`のように表示させている。

`words_per_minute`は`./_config.yml`で定義している。

```yml
words_per_minute: 200
```

`200`は、英語話者の平均が200-300らしいので。[あなたは1分間に英文を何語読めますか？WPMとは？ | マスターイングリッシュ 富山市の英会話教室・英検・TOEIC講座](https://www.ma-english.com/%E3%81%82%E3%81%AA%E3%81%9F%E3%81%AF1%E5%88%86%E9%96%93%E3%81%AB%E8%8B%B1%E6%96%87%E3%82%92%E4%BD%95%E8%AA%9E%E8%AA%AD%E3%82%81%E3%81%BE%E3%81%99%E3%81%8B%EF%BC%9Fwpm%E3%81%A8%E3%81%AF%EF%BC%9F/)

`read_time: true`も`./_config.yml`に記述しているが`defaults:`に含めてポストに共通のFront matterとして定義している。つまり、各ポスト（`_posts/hogehoge.md`）の先頭に`---`で囲って記述しているあれを、YAMLファイルで定義していちいち書く必要をなくしているだけ。

```yml
defaults:
  # _posts
  - scope:
      path: ""
      type: posts
    values:
      read_time: true
```

### 改善案

`number_of_words`は英単語数をカウントするので、そのままでは日本語に合わない。

#### `words_per_minute`とは別に`characters_per_minute`を定義

```yml
words_per_minute: 200
characters_per_minute: 500
```

`500`は、日本語話者の平均が400-600らしいので。[人は１分間に何文字読めるの？スキマ時間で読まれる文字数を推測してみよう。 | WEBマスターの手帳](https://homepage-reborn.com/2015/06/10/%E4%BA%BA%E3%81%AF%EF%BC%91%E5%88%86%E9%96%93%E3%81%AB%E4%BD%95%E6%96%87%E5%AD%97%E8%AA%AD%E3%82%81%E3%82%8B%E3%81%AE%EF%BC%9F%E3%82%B9%E3%82%AD%E3%83%9E%E6%99%82%E9%96%93%E3%81%A7%E8%AA%AD%E3%81%BE/#:~:text=%E6%B8%AC%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86%EF%BC%81-,%E6%97%A5%E6%9C%AC%E4%BA%BA%E3%81%AE%EF%BC%91%E5%88%86%E9%96%93%E3%81%AB%E8%AA%AD%E3%82%81%E3%82%8B%E6%96%87%E5%AD%97%E6%95%B0%E3%81%AE%E5%B9%B3%E5%9D%87,600%E5%AD%97%E3%81%A0%E3%81%9D%E3%81%86%E3%81%A7%E3%81%99%E3%80%82)

#### 日本語用のカウント数を定義

[元の状態](#元の状態)にならって、`assign`で変数を定義する。

```html
{% assign words_per_minute = site.characters_per_minute | default: 500 %}
{% assign words = document.content | strip_html | strip_newlines | size %}
```

上は`characters_per_minute`の設定内容をそのまま`words_per_minute`に代入しているだけ、下は`document.content`でポスト本文、あとはオプション

- `strip_html`：htmlに変換されて追加されるタグ（`<p>`等）は除く
- `strip_newlines`：行が空いていたらそれは除く
- `size`：文字数をカウント

各オプションは[Liquid template language](https://shopify.github.io/liquid/)に詳しい。

ここで、`size`とするか、`number_of_words: "auto"`とするかは読者ターゲットにもよると考えられる。`size`だと英単語もアルファベット1文字=1（wordなら4）としてカウントし、後者は、英単語は1単語=1（wordなら1）、漢字かなカナは1文字=1としてカウントする。広く一般向けなら`size`で、技術記事が多くて英語をある程度嗜んでいる人向けなら`number_of_words: "auto"`で良いのでは。（[Liquidフィルタ | Jekyll • シンプルで、ブログのような、静的サイト](http://jekyllrb-ja.github.io/docs/liquid/filters/)の"Number of Words"の項目参照）

#### if分岐で英単語、日本語の切り替えを可能にしておく

せっかく？なので、`words_per_minute`、`characters_per_minute`どちらを使うかで、[元の状態](#元の状態)つまり英語対応か、[上の定義](#日本語用のカウント数を定義)で日本語対応にするかをif分岐制御する。別に`characters_per_minute`のみならif分岐は不要なので適宜削除してほしい。

```html
{% assign document = post | default: page %}

{% if document.read_time %}
    {% if site.words_per_minute %}
        {% assign can_read_per_minute = site.words_per_minute | default: 200 %}
        {% assign words = document.content | strip_html | number_of_words %}
    {% elsif site.characters_per_minute %}
        {% assign can_read_per_minute = site.characters_per_minute | default: 500 %}
        {% assign words = document.content | strip_html | strip_newlines | size %}
    {% endif %}
    <span class="page__meta-readtime">
    <i class="far {% if include.type == 'grid' and document.read_time and document.show_date %}fa-fw {% endif %}fa-clock" aria-hidden="true"></i>
    {% if words < can_read_per_minute %}
        {{ site.data.ui-text[site.locale].less_than | default: "less than" }} 1 {{ site.data.ui-text[site.locale].minute_read | default: "minute read" }}
    {% elsif words == can_read_per_minute %}
        1 {{ site.data.ui-text[site.locale].minute_read | default: "minute read" }}
    {% else %}
        {{ words | divided_by: can_read_per_minute }} {{ site.data.ui-text[site.locale].minute_read | default: "minute read" }}
    {% endif %}
    </span>
{% endif %}
```

ここで、両方に共通した意味合いを持たせるために、`assign words_per_minute`を`assign can_read_per_minute`に変数名を変更しているので、注意してもらうために[元の状態](#元の状態)をすべて書き直したものを載せている。

また、[元の状態](#元の状態)では`document.words_per_minute | default: site.words_per_minute`のように書かれていたのを、書き直したコードの5行目では`site.words_per_minute`としている。もしポストごとに英語記事か日本語記事かを区別しているならいいが、サイト全体でしか今は考えないので、`site`のみにしている（そのままでも問題はたぶんない）。

あと、サイト全体でしか今は`words(characters)_per_minute`変数を考えていないことから、付け加えたif分岐では`site.words(characters)_per_minute`を条件に使う必要がある。

#### if分岐を使う場合

`./_config.yml`では使わない方をコメントアウトする。

```yml
# words_per_minute: 200
characters_per_minute: 500
```

## まとめ

今のところ日本語ばかりの記事しか書かないので、日本語話者が読了するのに必要な時間を出せるようにした。一応英語記事を書く段になった際スムーズに事が進むようにと、Jekyllファイルでif分岐に取り組んだことがなかったのでif分岐を使った。if分岐で`site`を使わなければいけないことに気付くまで時間がかかったことはナイショ。