1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【MkDocs】1つのmdファイルから言語別にmdファイルを生成するプラグインを作る

Posted at

MkDocs で言語切替を 1 つのファイルで行うためのプラグインをつくる

概要

MkDocs で言語切替を行う場合は、mkdocs-static-i18n を使用することが多いかと思います。
mkdocs-static-i18n を使用することで簡単に言語切替を行うことができますが、言語ごとにファイルを用意する必要があります。
複数ファイルがあると更新忘れが発生する可能性があるため、1 つのファイルで言語切替を行うプラグインを作成することにしました。

前提条件

以下の環境を前提とします。

  • MkDocs の環境が構築済みであること
  • mkdocs-static-i18n がインストール済みであること
  • 環境は以下の通りです。
    • Python 3.12.3
    • MkDocs 1.6.0
    • mkdocs-static-i18n 1.2.3

また筆者は Python 経験がほぼなく、ChatGPT や GitHub Copilot に頼りながら Python 実装を行いました。
そのため、Python のコードが冗長であったり、無駄な処理があるかもしれませんが、ご容赦ください。

プラグインの仕様

利用者は、XXXX.multilang.md という拡張子が付いたファイルを作成し、そのファイル内に言語切替を行いたい文章を記述します。言語切替を行いたい箇所には、以下のようにコメントアウトを記述します。

<!-- lang:ja -->

日本語の文章

<!-- lang:en -->

英語の文章

<!-- lang:common -->

日本語、英語どちらも共通の文章

そして mkdocs をビルドすると、もととなる XXXX.multilang.md と同じ階層に「XXXX.ja.md」「XXXX.en.md」 というファイルが自動生成されます。

プラグインの作成

最初に、MkDocs がビルドしたタイミングで処理を行うプラグインを実装します。プラグインの作り方は以下の記事の通りに行うと作成できます。

1 点、記事の中では省略されていますが別途setuptoolsをインストールする必要があります。

pip install setuptools

言語切替の処理の実装

上記記事で作成したsample.pyに、言語切替の処理を追加します。

まずdocsフォルダ内に存在する、拡張子が.multilang.mdのファイルを全て探します。
ファイルを見つけたら、言語切替の処理を行うseparate_markdown関数を実行します。

class SamplePlugin(BasePlugin):
    def on_pre_build(self, config):
        # /docsフォルダ内にある全ての.multilang.mdファイルを探す
        for root, dirs, files in os.walk('./docs'):
            for file in files:
                if file.endswith(".multilang.md"):
                    file_path = os.path.join(root, file)
                    separate_markdown(file_path)

次に、separate_markdown関数を実装します。この関数は、.multilang.mdファイルを読み込み、コメントアウトされた言語ごとの文章を取得します。そして言語ごとの文章を別々のファイルに書き込みます。

コードは以下の通りです。

def separate_markdown(path):
    with open(path, 'r', encoding='utf-8') as file:
        content = file.read()

    japanese_content = []
    english_content = []

    in_japanese = False
    in_english = False
    lines = content.split('\n')
    for line in lines:
        if line.strip().startswith("<!-- lang:ja -->"):
            in_japanese = True
            in_english = False
            japanese_content.append(line)
        elif line.strip().startswith("<!-- lang:en -->"):
            in_japanese = False
            in_english = True
            english_content.append(line)
        elif line.strip().startswith("<!-- lang:common -->"):
            in_japanese = True
            in_english = True
            english_content.append(line)
            japanese_content.append(line)
        elif in_japanese & in_english:
            english_content.append(line)
            japanese_content.append(line)
        elif in_japanese:
            japanese_content.append(line)
        elif in_english:
            english_content.append(line)
        else:
            japanese_content.append(line)
            english_content.append(line)

    japanese_content = '\n'.join(japanese_content)
    english_content = '\n'.join(english_content)

    # 日本語の内容を書き込む前に、既存のファイルの内容と比較し、差分があれば更新する
    ja_file_path = path.replace('.multilang.md', '.ja.md')
    if not os.path.exists(ja_file_path) or open(ja_file_path, 'r', encoding='utf-8').read() != japanese_content:
        with open(ja_file_path, 'w', encoding='utf-8') as file:
            file.write(japanese_content)

    # 英語の内容を書き込む前に、既存のファイルの内容と比較し、差分があれば更新する
    en_file_path = path.replace('.multilang.md', '.en.md')
    if not os.path.exists(en_file_path) or open(en_file_path, 'r', encoding='utf-8').read() != english_content:
        with open(en_file_path, 'w', encoding='utf-8') as file:
            file.write(english_content)

詰まった点としては、言語ごとにファイルへ書き込むところで、最初は以下のように実装していました。

# folder内に日本語と英語のコンテンツを書き出す
with open(file_path.replace('.multilang.md', '.ja.md'), 'w', encoding='utf-8') as file:
    file.write(japanese_content)
with open(file_path.replace('.multilang.md', '.en.md'), 'w', encoding='utf-8') as file:
    file.write(english_content)

この処理だと、プラグインが実行されるたびに、md ファイルの上書きが行われます。
すると MkDocs 側で md ファイルの更新が発生したと判断され、再度ビルドが走る、という無限ループが発生してしまいます。
これを防ぐために、md ファイルが存在しない、もしくは md ファイル内の記述に変更があった場合のみ、ファイルへの書き込みを行うようにしました。

# 日本語の内容を書き込む前に、既存のファイルの内容と比較します
ja_file_path = file_path.replace('.multilang.md', '.ja.md')
if not os.path.exists(ja_file_path) or open(ja_file_path, 'r', encoding='utf-8').read() != japanese_content:
    with open(ja_file_path, 'w', encoding='utf-8') as file:
        file.write(japanese_content)

# 英語の内容を書き込む前に、既存のファイルの内容と比較します
en_file_path = file_path.replace('.multilang.md', '.en.md')
if not os.path.exists(en_file_path) or open(en_file_path, 'r', encoding='utf-8').read() != english_content:
    with open(en_file_path, 'w', encoding='utf-8') as file:
        file.write(english_content)

以上で実装は完了です。

結果と考察

実装後、mkdocs serveを実行すると、multilang.md ファイルがあるフォルダに、言語ごとのファイルが生成されます。

ビルド前
ビルド前のフォルダ構成

ビルド後
ビルド前のフォルダ構成

また multilang.md ファイルを更新して保存すると、言語ごとの md ファイルの中身も更新されます。

今回のプラグインを作成することで、通常の MkDocs を書いているときと同じ操作のまま、1 つのファイルで言語切替を行うことができました。これにより、複数ファイルを管理する手間が省けるため、更新漏れが発生する可能性が低くなります。

その他詰まったところ

他の方に開発したプラグインを使ってもらおうとしたところ、プラグインをインストールする際にエラーが発生しました。
エラーの内容は以下の GitHub の issue と同様で、Permission denied と表示されました。

プラグイン作成で参考にした記事ではpython setup.py developを実行して実装したプラグインをインストールしていましたが、この方法だと管理者権限が必要なディレクトリにインストールしようとするため、エラーが発生するようです。

この解決策として、プラグインをインストールする際にpython setup.py install --userを実行することで、エラーを回避することができました。--userオプションをつけることで、管理者権限が不要なディレクトリにインストールされるためです。
ただし、プラグインの内容を変更した場合は、一度python setup.py install --userでインストールしたプラグインをアンインストールし、再度インストールする必要があります。

参考文献

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?