Hugoにデフォルトで実装されているReadingTimeだと欧文基準で時間の計算がされるので、 日本語基準で計算できるよう変更した。


.ReadingTime

Hugoには.ReadingTime1というPage Variableが標準で実装されており、 config.tomlshowReadingTime = trueと記載するだけで、記事を読むのにかかる時間が表示されるようになる。

調べたところによると、記事内の単語数を数えてそれを1分で読める平均単語数で割ったものを出しているらしいのだが、 この数値がだいぶ大味だったので、デフォルトのReadingTimeを使わずに、自前で用意することにした。

やり方

単語数ではなく、見た目上の文字数をカウントする。 HugoにはCJK(Chinese, Japanese, Korean) languageにも対応した、文字数(コードポイント)をカウントできる countrunes2という関数が用意されているので、これを用いる。

{{ $readTime := mul (div ( countrunes .Content) 500.0) 60 }}
{{ $minutes := math.Floor (div $readTime 60) }}
{{ $seconds := mod $readTime 60 }}
<span class="post-read-time">— 
  <span style="vertical-align: -3px;">
    <ion-icon name="timer-outline"></ion-icon>
  </span> {{ $minutes }} {{ cond (eq $minutes 1.0) "minute" "minutes" }} and {{ $seconds }} {{ cond (eq $seconds 1) "second" "seconds" }}.
</span>

コンテンツの内容をcountrunesに渡して、かかる分数、秒数をそれぞれ計算して表示する上記の処理を、 利用しているtheme配下の

  • layouts/_default/index.html
  • layouts/_default/list.html
  • layouts/_default/single.html
  • layouts/archive/list.html

にそれぞれ記載する。

上の500というのは、 日本人は日本語を平均400~600文字読めるというネットで調べた何の根拠もない値を参考に設定した数字だ。

このままだとReadingTimeを表示したくないページ(たとえばAboutとか)にもReadingTimeが表示されるので、 上の処理を書いた箇所の条件文を少し変更する。

{{ if $.Site.Params.ShowReadingTime }}
↓
{{ if (and ($.Site.Params.ShowReadingTime) (eq .Section "posts")) }}

2022/11/03 追記

記事内のコードの量が増えると、体感より大きくReadingTimeが長くなってしまうことがわかったので、 記事からコードブロックのみ抜き出して別途重み付けして計算するようなロジックに変更した。

変更点は以下

+ {{ $pattern := "(?s)```.*?```[\r\n]+" }}
+ {{ $codeBlock := findRE $pattern .RawContent }}
+ {{ $trimmedLength := countrunes (replaceRE $pattern "" .RawContent) }}
+ {{ $codeBlockLength := 0 }}
+ {{ range $codeBlock }}
+   {{ $codeBlockLength = add $codeBlockLength (countrunes .) }}
+ {{ end }}
~   {{ $readTime := mul (div (add $trimmedLength (mul $codeBlockLength 0.2)) 500.0) 60 }}
  • 1行目:バッククォート3つで挟まれるテキストに改行無視で最小マッチするパターン
  • 2行目:パターンにマッチした文字列のリストを$codeBlockに代入
  • 3行目:.RawContentからコードブロックを削除した文字列のコードポイントをカウントして$trimmedLengthに代入
  • 4-7行目:コードブロック部分のコードポイントをカウントして$codeBlockLengthに代入
  • 8行目:コードブロックは本文の文字の0.2倍の重み付けをしてreadTimeを計算

仕上げに、以下のように計算に利用するパラメタを変数にすれば、config.tomlから制御できるようになる。

{{ $readTime := mul (div (add $trimmedLength (mul $codeBlockLength $.Site.Params.codeBlockWeight)) $.Site.Params.runesPerMinute) 60 }}
config.toml
[params]
  runesPerMinute = 500.0
  codeBlockWeight = 0.2

2022/12/02 追記

秒数を切り上げて分数のみ表示するようにした。

おわりに

これで記事を読むのにかかる時間をいい感じに表示できるようになった。

もし実際に読む時間と乖離があれば、テンプレートに記載したパラメタをいじるだけで修正が可能だ。

参考文献