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


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

Jekyllテーマ色々

テーマをピックアップ - Jekyll 日本語サイトに複数のギャラリーリンクが存在する。有料のものも存在する。

私のサイトで採用しているテーマはMinimal Mistakes。一応、検索したら他にも利用している日本人がいる(T-potk11i.bizJekyllでMinimal Mistakesのテーマを使う - Qiita)。どうやら他テーマに比べて拡張しやすいらしい(他のテーマをまともに見ていないので知らない)。

本題:日本語文字数カウント

【Jekyll】日本語記事の「この記事は何分で読めます」を表示する | T-Potを参考にした。

元の状態

Jekyllテーマを管理しているディレクトリ上でnumber_of_wordsを検索すると、該当ファイルが見つかるはず。Minimal mistakesの場合は、./_includes/page__meta.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で定義している。

words_per_minute: 200

200は、英語話者の平均が200-300らしいので。あなたは1分間に英文を何語読めますか?WPMとは? | マスターイングリッシュ 富山市の英会話教室・英検・TOEIC講座

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

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

改善案

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

words_per_minuteとは別にcharacters_per_minuteを定義

words_per_minute: 200
characters_per_minute: 500

500は、日本語話者の平均が400-600らしいので。人は1分間に何文字読めるの?スキマ時間で読まれる文字数を推測してみよう。 | WEBマスターの手帳

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

元の状態にならって、assignで変数を定義する。

{% 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に詳しい。

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

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

せっかく?なので、words_per_minutecharacters_per_minuteどちらを使うかで、元の状態つまり英語対応か、上の定義で日本語対応にするかをif分岐制御する。別にcharacters_per_minuteのみならif分岐は不要なので適宜削除してほしい。

{% 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_minuteassign 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では使わない方をコメントアウトする。

# words_per_minute: 200
characters_per_minute: 500

まとめ

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