1つのQMDで日英を切り替える:Quartoの手順

Read this page in English (EN)

何ができるようになるか

1ページにつき1つのQMDに、英語の文章、日本語の文章、共通のRコードを置きます。 そこから日英それぞれのHTMLを作り、JP/ENボタンで対応するページへ移動します。 計算を修正する場所は1か所で済み、次の描画で両言語に反映されます。 文章の翻訳と内容の照合は、人が行います。

このサイトとleaf-mechanics-dehydrationで 採用した方法です。まず、サイトを作る段階と読む段階を分けて捉えると理解しやすくなります。

1つの共通QMDから日英のHTMLを作り、読む人はJP/ENリンクで両ページを移動する。

読む段階では、作成済みの別ページを開くだけです。ブラウザーでRを実行したり、 Quartoを描画したり、自動翻訳したりするわけではありません。そのため、日英両方のHTMLを用意します。

小さな練習用プロジェクトを作る

以下は、新しい練習用フォルダで動かす最小例です。 このリポジトリの気候解析設定を丸ごと置き換えるための設定ではありません。 Quartoを導入し、共通Rコードの実行例も試す場合はR・knitr・rmarkdownを用意します。 このリポジトリでは既存のrenv::restore()を使い、別のパッケージ管理を追加する必要はありません。

次の構成にします。docs/は自動生成されるので、そこへ直接翻訳文を書かないでください。

practice/
  _quarto.yml
  _quarto-en.yml
  _quarto-ja.yml
  index.qmd
  language-switch.html
  filters/
    language-title.lua
  docs/                       # generated / 自動生成
    index.html
    ja/
      index.html

手順1:共通設定を _quarto.yml に書く

「プロファイル」は、名前を付けた描画設定の組です。以下ではenとjaを切り替える組にし、 先頭のenを標準にします。目次と見た目は共通にし、日本語版だけ出力先を上書きします。

_quarto.yml
profile:
  group:
    - [en, ja]

project:
  type: book
  execute-dir: project
  output-dir: docs

book:
  title: "My bilingual notebook"
  chapters:
    - index.qmd

filters:
  - filters/language-title.lua

format:
  html:
    theme: cosmo
    include-after-body: language-switch.html

手順2:言語別の設定ファイルを作る

langは、目次などQuartoの表示言語を指定します。本文を翻訳する設定ではありません。 出力先を分けることで、日本語版の描画で英語HTMLが上書きされるのを防ぎます。

ナビゲーションのリンクは、最初はトップページへ向けます。 複数の章を追加したときも同じ章へ移動できるようにする仕組みは、手順5で加えます。

_quarto-en.yml
lang: en

book:
  navbar:
    right:
      - text: "JP"
        href: ja/index.html
_quarto-ja.yml
lang: ja

project:
  output-dir: docs/ja

book:
  title: "私の日英ノート"
  navbar:
    right:
      - text: "EN"
        href: ../index.html

手順3:1つの index.qmd に日英の文章を書く

unless-profile="ja"は日本語プロファイル以外で、when-profile="ja"は日本語プロファイルで 表示する文章です。::::でブロックを開閉します。区切りは独立した行に置き、前後に空行を入れます。

実行するRコードは、両方の言語ブロックの外へ置きます。 以下では、どちらの言語も同じmean(c(1, 2, 3))を使い、結果は2です。 #language-switchは、後でナビゲーションの移動先を決めるための目印です。 描画された各ページに、この目印を持つリンクが1個だけ残るようにします。

index.qmd
---
title: "A small calculation"
title-ja: "小さな計算例"
---

:::: {.content-visible unless-profile="ja"}

[日本語はこちら(JP)](ja/index.html){#language-switch hreflang="ja" lang="ja"}

## Mean of three numbers

We calculate the arithmetic mean of 1, 2 and 3.

::::

:::: {.content-visible when-profile="ja"}

[Read this page in English (EN)](../index.html){#language-switch hreflang="en" lang="en"}

## 3つの数の平均

1、2、3の算術平均を計算します。

::::

```{r}
#| label: shared-mean
mean(c(1, 2, 3))
```
重要文章の表示とコードの実行は別です

文章を非表示にする設定は、コードの実行を止める設定ではありません。 重い処理やデータ読込を言語ブロックへ入れて、実行を回避しようとしないでください。 実行するかどうかは、チャンクのeval: falseなどで明示します。 共通コードであっても、通常は言語ごとの描画でそれぞれ実行されます。 コードを1つにすることと、計算回数が全体で1回になることは別です。

手順4:小さなLuaフィルターでタイトルを切り替える

title-jaは、この方法で独自に設けたメタデータです。 次のフィルターはlangがjaのときだけtitleを差し替えます。 任意のsubtitle-jaによる副題の切替にも対応しています。 LuaはQuartoに含まれているので、このためのRパッケージ追加は不要です。

filters/language-title.lua
function Meta(meta)
  if pandoc.utils.stringify(meta.lang) == "ja" then
    for _, field in ipairs({"title", "subtitle"}) do
      if meta[field .. "-ja"] then
        meta[field] = meta[field .. "-ja"]
      end
    end
  end
  return meta
end

手順5:ナビゲーションも同じページへ切り替える

ページ本文の言語リンクは、JavaScriptがなくても動きます。 次のスクリプトは、そのページの言語リンクをナビゲーションへ転記します。 そのため、ET₀章からJP/ENを押したときも、トップへ戻らず相手の言語のET₀章を開けます。

language-switch.htmlとして保存します。手順1のinclude-after-bodyで読み込むファイルです。 スクリプトはJPとENを探すので、ナビゲーションのラベルもこの表記に揃えます。 日の丸は任意です。このサイトの language-switch.cssは、 環境によって表示が変わる旗の絵文字を避け、埋め込みSVGを使っています。 同じ見た目にする場合はCSSをコピーし、共通設定のformat: html:に css: language-switch.cssを追加します。

language-switch.html
<script>
document.addEventListener("DOMContentLoaded", () => {
  const target = document.querySelector("#language-switch");
  if (!target) return;
  for (const link of document.querySelectorAll(".navbar-nav .nav-link")) {
    if (["JP", "EN"].includes(link.textContent.trim())) {
      link.href = target.href;
      link.hreflang = target.hreflang;
      link.setAttribute("aria-label", target.textContent.trim());
    }
  }
});
</script>

手順6:両言語を描画し、ブラウザーで確認する

練習用フォルダで、次のコマンドを順番に実行します。 最小例には自動描画のフックを付けていないので、2つとも必要です。

quarto render --profile en
quarto render --profile ja
quarto preview --profile en

quarto previewが表示したローカルURLを開き、本文リンクとナビゲーションの両方で往復します。 両ページで結果が2になり、タイトルと説明がそれぞれの言語になれば成功です。 その後に日本語の文章を編集した場合は、日本語プロファイルも再描画して確認します。

notebooks/に章を置く場合のリンクは次のとおりです。 リンクの基準は、リポジトリの入口ではなく生成HTMLの置かれたフォルダです。 表のexampleは追加する章の仮名で、このサイトに実在するページではありません。

今開いているページ 相手の言語へのリンク
英語トップ:docs/index.html ja/index.html
日本語トップ:docs/ja/index.html ../index.html
英語の章:docs/notebooks/example.html ../ja/notebooks/example.html
日本語の章:docs/ja/notebooks/example.html ../../notebooks/example.html

手順7:このリポジトリでは自動描画も利用する

このサイトでは、最小例に次の補助処理を加えています。

  1. _quarto-en.ymlから、英語の描画後にscripts/render-japanese.Rを呼びます。
  2. スクリプトは、ソースに変更があれば日本語版も描画し、その間は英語のナビゲーション索引を保護します。
  3. 従来の6個の*_ja.htmlを新日本語ページへ転送し、GitHub Pages用の.nojekyllも作ります。 この移行処理は、このサイト固有のものです。

したがって、このリポジトリではquarto renderだけで両言語を作れます。 日本語だけならquarto render --profile jaです。 日本語を呼ぶフックを共通設定や日本語設定にも書くと、描画の繰り返しを招くので避けます。 別プロジェクトへコピーする場合は、スクリプト内のフォルダ名や旧URL一覧を合わせて変更してください。

ここへ新しい章を追加するときの順序は、次のとおりです。

  1. 日英の文章、共通コード、両言語のタイトルを持つQMDを1つ作ります。
  2. 両方のプロファイルの目次・付録へ追加します。
  3. tests/check_bilingual.cjsのpages一覧へファイル名を追加します。
  4. 下のコマンドで描画・リンク検査を行い、ブラウザーでも言語切替を試します。
  5. 公開する際は、ソースの変更と生成されたdocs/を揃えてコミットします。
quarto render
node tests/check_bilingual.cjs

編集しながら参照するファイル

このリポジトリのファイル 役割
_quarto.yml サイト全体・配布資源・見た目の共通設定
_quarto-en.yml / _quarto-ja.yml 言語・目次・出力先
calculate_ET0.qmd 計算を共有して説明を切り替える実例
language-title.lua 日本語タイトル・副題の選択
language-switch.html ナビゲーションを現在のページに対応させる処理
render-japanese.R 日本語版の自動描画と旧URL転送

うまく動かないとき

症状 確認するところ
本文に両方の言語が出る content-visibleの属性、プロファイル名、閉じるコロン、前後の空行
本文は日本語なのにタイトルが英語 title-ja、Luaファイルへのパス、lang: ja
JP/ENで毎回トップへ戻る 本文の#language-switchリンクと、スクリプトの読込
相手の言語が404になる 両言語のHTMLが存在するか、相対パス、両方の公開先への配置
日本語版だけ古い 編集後に日本語プロファイルも再描画したか
非表示のコードがデータを読む 表示と実行は別なので、必要ならeval: falseを明記
docs/*.htmlを直しても元に戻る QMD・設定を編集してから再描画する

GitHub Pagesでは、pushの成功とデプロイの成功は別です。 pushしたコミットのデプロイ結果を確認し、公開された日英両方のURLを開きます。 描画中のフォルダを公開せず、完成して検証した出力を使ってください。 気象データの外部共有条件は、言語切替の仕組みとは別に守る必要があります。

公式資料

基盤となる機能は、QuartoのProject Profilesと Conditional Contentです。 タイトル用Lua、ナビゲーション用JavaScript、日本語版の自動描画は、これらへこのプロジェクトで 追加した小さな補助処理です。