WordPress多言語化を自作した。そして62本一括公開で壊れた

WordPressから英語・スペイン語・フランス語の記事が大量展開され、途中で公開パイプラインが混線して警告が出る様子を描いた水彩画。 MCPでつくる自作AIサーバー

WordPressで運営しているサイトを多言語化しました。使ったのはWPMLでもPolylangでもWeglotでもありません。最終的には、多言語化の仕組みそのものを自作することになりました。

そして、既存の英語記事31本からスペイン語版とフランス語版を作り、合計62本を一括公開しようとして壊れました。

正確には、壊れたというより「壊れそうな状態を検出して、システム自身が公開を止めた」です。一度直して再実行したら、今度は別の理由でまた止まりました。最終的には62本を公開でき、現在は新しい記事も英語・スペイン語・フランス語の3言語で運用しています。

振り返ってみると、多言語化で難しかったのは翻訳ではありませんでした。

最初は普通に多言語化したかった

対象は、海外向けに日本の日常を紹介している「Ordinary Japan」です。当初は英語サイトでしたが、せっかく記事を作るならスペイン語やフランス語にも展開したい。現在はAIでかなり自然な翻訳ができますし、記事制作自体も自作のPublishing MCPからWordPressへ送れるようになっていました。

ならば、記事を書いたらAIで翻訳して、そのまま各言語版を投稿すればよさそうです。

ところがWordPressの多言語化は、それほど単純ではありません。

WordPressには長年使われてきた多言語化手段があります。WPMLは投稿やカテゴリなどに言語とtranslation groupを持たせ、独自テーブルで関係を管理します。言語別URLもディレクトリ、別ドメイン、URLパラメータなどから選べます。 TranslatePressは別のアプローチで、翻訳を文字列単位でWordPressデータベースに保存します。 MultilingualPressは1言語をWordPress Multisite上の1サイトとして分離します。 WeglotはさらにWordPressの外側へ寄せ、専用URL、サーバーサイド翻訳、hreflangなどを自動的に処理します。

どれも、それぞれ合理的な設計です。普通にWordPressを多言語化するだけなら、自作などしない方がいいと思います。

問題は、私がやりたかったことが途中から「WordPressを多言語表示する」ではなくなっていたことでした。

AIに任せると、翻訳以外も管理しなければならない

人間が管理画面を開いて1記事ずつ翻訳するなら、多少複雑でも何とかなります。しかし、記事作成から翻訳、公開までAIに任せると話が変わります。

例えば英語記事からスペイン語版を作る場合、本文だけ翻訳して終わりではありません。スペイン語用のslugを決め、正しいURLへ配置し、カテゴリやタグもスペイン語版へ対応させ、元記事との関係を記録し、canonicalやhreflangを設定し、言語切り替え先も一致させる必要があります。さらにサイトマップやナビゲーションもあります。

一つひとつは難しくありません。ただし、数十記事、数言語を機械的に扱い始めると、「どの記事の、どの言語版が、WordPress上のどの実体なのか」を絶対に間違えられなくなります。

そこで、WordPressの中に多言語管理の中心を置くのではなく、Publishing MCP側で管理することにしました。

大まかには、こういう構成です。

AI / Codex
    ↓
Publishing MCP
    ↓
Multilingual Manifest
    ↓
WordPress
 ├─ English post
 ├─ Spanish post
 └─ French post

WordPressには、それぞれ独立した普通の記事が存在します。その記事同士が同じ内容の言語違いであることや、locale、slug、URL、taxonomyなどの関係は外側のmanifestでも管理します。

ただし、公開サイトを見るたびにPublishing MCPへ問い合わせる構成にはしませんでした。必要なlocaleやroutingの情報はWordPress側へ配置し、閲覧時は通常のWordPressとPHPだけで動きます。Publishing MCPや自宅のAI環境が止まっても、公開済みサイトはそのまま表示できます。

この方式は自由度が高い一方で、それまで多言語プラグインがやってくれていた仕事を自分で実装することになります。

そして、その代償を62本一括公開で払うことになりました。

英語31記事から62記事を作った

最初の大規模移行では、Ordinary Japanにあった英語記事31本を対象にしました。

各記事についてスペイン語版とフランス語版を作ったので、新規記事は62本です。翻訳してWordPressの下書きとして作成し、それぞれのカテゴリやタグ、元記事との対応関係まで設定しました。

ここまでは問題なく進みました。

そこで31組、62記事をまとめて公開する処理を実行しました。

止まりました。

公開後のURLを検査するpostflightで、WordPressのnative routeが想定した形へ正規化されていないことを検出したためです。システムはそこでfail-closedになりました。

つまり、「たぶん大丈夫だろう」と残りを公開するのではなく、失敗扱いにしました。変更したmanifestを元に戻し、公開しかけた翻訳記事も下書きへ戻し、62本すべてを再実行前の状態まで戻しました。

この時点では、少なくとも設計したrollbackは機能しました。

原因を調べ、postflightを修正しました。全グループを一度確認し、問題があったグループだけを一定回数再確認する方式へ変更しました。

もう一度62本を公開しました。

また止まりました。

今度はタイムアウトした

2回目はURLそのものではありませんでした。公開後の確認中にHTTP readがタイムアウトしました。

これは判断が難しい状態です。

タイムアウトしたからといって、そのURLが間違っているとは限りません。しかし、正常だったとも確認できません。さらに62本をまとめて処理しているため、どこまで正しく公開されたのか曖昧な状態で「もう一度全部実行」すると、別の事故を起こす可能性があります。

そのため、また失敗扱いにしました。そして、また62本を元の状態まで戻しました。

ここで処理を変更しました。タイムアウトを単なる例外として全体終了させるのではなく、postflight_read_timeoutという「検証できなかった状態」に変換し、その記事だけを再確認するようにしました。

重要なのは、タイムアウトした記事を成功扱いにしたわけではないことです。

確認できなかったので、確認できるまで再確認する。一定回数試しても確認できなければ公開処理全体を成功にしない。この方針にしました。

その後、31記事から作ったスペイン語31本、フランス語31本の公開は完了しました。

公開できたら完成、でもなかった

これで終わりだと思っていましたが、実際に運用すると次の問題が出ました。

公開済み翻訳記事を修正したくなります。

例えば、現在Ordinary Japanで公開している車検の記事では、フランス語版の表現を一か所修正しました。文章だけならWordPress管理画面で直せば数十秒です。しかし、それをやるとPublishing側が保持している記事の状態とWordPressの実体がずれます。

そこで「公開済み多言語記事を正式に改訂する経路」も作ることになりました。

修正できるのは本文、タイトル、抜粋などです。一方で、WordPressのpost ID、slug、公開URL、カテゴリ、タグ、アイキャッチ、翻訳関係などは固定します。変更前の状態を保持してから同じWordPress記事を更新し、manifestを再生成し、最後にcanonical、hreflang、言語切り替え、redirect、sitemapまで確認します。

車検記事で実際にフランス語だけを修正した際も、英語・スペイン語・フランス語の記事IDやURLはそのままで、更新後の検証まで通りました。

多言語化システムは、翻訳記事を作れた時点では完成ではありませんでした。公開後に直すところまで考える必要がありました。

結局、何を作っていたのか

当初は「WordPressの多言語化システムを作っている」と考えていました。

今は少し違います。

作っていたのは、AIが多言語コンテンツを継続的に作成、公開、更新するためのライフサイクル管理システムだったのだと思います。

翻訳はその中の一工程にすぎません。

どの言語の記事がどの記事に対応するのか。どのWordPress objectが正しい実体なのか。公開先はどこなのか。SEO上の関係は正しいか。途中で失敗したらどこまで戻すのか。元記事を修正したら翻訳版をどう扱うのか。

AIに大量の作業を任せるほど、この地味な部分の方が重要になりました。

自作して正解だったのか

これは用途次第です。

普通のWordPressサイトを2~3言語にしたいのであれば、私はこの方法を勧めません。WPML、Polylang、TranslatePress、Weglotなど、実績のある仕組みを使う方がはるかに簡単です。

今回も、自作したことで実装しなければならないものはかなり増えました。translation identity、locale、routing、taxonomy、canonical、hreflang、manifest、stale-state検出、idempotency、postflight、rollback、公開後revisionなど、既存製品なら意識しなくてもよい部分まで自分たちで管理しています。

それでも自作した意味はありました。

私はすでにPublishing MCPを使って複数のWordPressサイトを運用しており、記事制作そのものをAIへ寄せています。今後、1記事から複数言語の記事を継続的に生成し、数百、数千URLを管理するのであれば、人間がWordPressの管理画面で翻訳関係を面倒見る構成には戻しにくいからです。

現在のOrdinary Japanには英語、スペイン語、フランス語の記事が実際に公開されており、既存記事の一括移行後に作った新しい記事も3言語で投稿しています。カテゴリも各言語版が分離されています。実験用のPoCではなく、すでに日常の出版経路になりました。

そして62本の一括公開で二度止まった経験から、ひとつ認識が変わりました。

AI時代の多言語化で難しいのは、翻訳精度ではありません。

間違った記事を作らないこと、間違った場所へ公開しないこと、途中で失敗しても元に戻せることです。

62本一括公開で「壊れた」のは予定外でしたが、そこで中途半端に62本を残さず全部戻せたことまで含めると、自作したシステムが最初に受けた本番試験だったのかもしれません。

コメント

タイトルとURLをコピーしました