日本語で書いてきた技術ブログを、英語圏からも読めるようにしたくなった。
手軽な方法としては、ページ上でGoogleの翻訳ウィジェットを動かし、同じURLのまま英語表示へ切り替えるやり方がある。別サイトではそれで足りた。
ただし今回の目的は「来た人の便宜」ではなく、英語クエリでの発見とインデックスである。ウィジェットではそこが満たせないと判断した。
ウィジェットではインデックスされない
Google Website Translatorは、公開HTMLを日本語のまま返し、ブラウザ上でDOMを英語に差し替える。URLは変わらない。
Googleが言語を判定するのは、クローラが取得したHTMLの本文である。hreflangやhtml langが主根拠ではない。Cookieで後から英語にした表示は、英語ページとしてほぼ存在しない。
hreflang自体も「別URL同士の対応」を伝える仕組みなので、同じURLにJA/ENを載せても英語版としては登録できない。
| やりたいこと | 向き |
|---|---|
| 日本語ページに来た読者が英語で読む | ウィジェットやブラウザ翻訳で足りる |
| 英語検索から流入する | クローラが読める英語HTMLを別URLで出す |
無料のWebsite Translatorは2019年に提供終了している。動いていてもサポート対象外である。
未レビューの機械翻訳を大量に別URLで公開すると、Googleの Scaled content abuse(自動変換の例に翻訳がある)にも近い。英語版は翻訳として公開するが、正本は日本語に置き、古いローカル寄りのメモまでは訳さないことにした。
Chromeはもともとページを翻訳する。ウィジェットを足しても、英語SERPからの新規流入は増えない。
Hugoの多言語で別URLを出す
正本は日本語のままルート直下。英語は/en/配下。
既存の記事ファイルをディレクトリごと移したくなかったので、言語ごとのcontentDirは使わない。Hugoのファイル名方式で対応させる。
- 日本語:
content/post/example.md→https://example.com/example/ - 英語:
content/post/example.en.md→https://example.com/en/example/
このサイトでは実際のドメインがhttps://scribble.washo3.com/ である。
hugo.tomlの核は次のとおり。
DefaultContentLanguage = "ja"
defaultContentLanguageInSubdir = false
[languages.ja]
locale = "ja-JP"
label = "日本語"
weight = 1
[languages.en]
locale = "en-US"
label = "English"
weight = 2以前はDefaultContentLanguage = "en"のままだった。本文は日本語なのにhtml langが英語になっていた。多言語化する前に、正本の言語コードをjaに合わせた。
defaultContentLanguageInSubdir = falseなので、日本語は/ja/を付けない。英語だけサブディレクトリになる。
hreflang
英語版があるページだけ、次をHTMLのheadに出す。
hreflang="ja"とhreflang="en"(自己参照を含む)hreflang="x-default"は日本語(正本)
実在しない英語URLは宣言しない。対応する翻訳が無いページにhreflangを付けると、双方向リンクが成立せずGoogle側で無視されやすい。
canonicalは各言語が自分自身を指す。英語を日本語へ向けない。
IPやAccept-Languageでの強制リダイレクトもしない。Googlebotが一方の言語しか見られなくなる。
実装は.AllTranslationsを回すだけである。自己参照も含まれるので、Googleが求める「自分自身と他言語を列挙する」条件はそのまま満たせる。
{{- if .IsTranslated }}
{{- range .AllTranslations }}
<link rel="alternate" hreflang="{{ .Lang }}" href="{{ .Permalink }}" />
{{- end }}
{{- end }}サイトマップ
多言語サイトでdefaultContentLanguageInSubdir = falseにすると、Hugo既定のsitemapindexが存在しない/ja/sitemap.xmlを列挙することがある。
ルートの/sitemap.xmlは索引ではなく、日英のURLを並べた1つのurlsetとして出した。
Googleはhreflangを HTML・HTTPヘッダ・サイトマップのどれか1つで受け取れば足りる。HTML側で出しているので、サイトマップには重複して書かない。
検索結果から外しているtaxonomy(categories / tags)は、サイトマップからも除く。
llms.txtを言語で分ける
このサイトはLLM向けにllms.txtを出している。outputFormatsのroot = trueだと、日英のホームが同じ/llms.txtを奪い合う。
root = falseにすると、日本語は/llms.txt、英語は/en/llms.txtに分かれる。テンプレート側は.Langで説明文を切り替える。
RSSも言語ごとに出るので、英語版をインデックスさせるなら言語別のフィードもそのまま使える。
言語スイッチャー
テーマ(beautifulhugo)には多言語用のリンクがもともとある。hugo.IsMultilingualかつそのページに翻訳があるときだけ、別言語への<a href>が出る。
Cookieで表示だけ切り替えるボタンは使わない。クローラが英語URLを辿れないからである。
ナビ以外のUI文言(関連記事見出し、検索プレースホルダ、プロモーション表示など)は、テーマのi18nファイルをサイト側で上書きした。英語ページに日本語の固定文言が残らないようにするためである。
どこまで訳すか
全記事を機械翻訳して載せるのは避けた。価値の薄い英語ページを大量に置く方が、スパム判定のリスクがある。
| 対象 | 判断 |
|---|---|
content/post/直下の現行記事 | 英語版を付ける。今回は129本 |
| 2011〜2018年の旧記事ディレクトリ | 訳さない(264本)。ローカルすぎるメモが多く、hreflangも出さない |
| Aboutなどの固定ページ | 英語版を付ける |
英語titleは日本語の語順を引きずらず、英語圏で検索されそうな語で書き直す。subtitleはもともと英語だったので流用または改善した。
dateは日本語版と1文字も変えない。ずれると両言語で並び順が食い違う。
コマンド、ログ、パス、URL、バージョン番号は訳さない。コードブロック内の日本語コメントだけ英語にする。
Hugoのrefショートコードは、現在の言語の中だけを探す。英語版が無い旧記事へ張るときは、言語を日本語に固定しないとビルドがREF_NOT_FOUNDで落ちる。
{{< ref path="old-post.md" lang="ja" >}}日本語を直したあとの英語
英語は翻訳であり、日本語に無い情報を足さない。
公開前に日本語を直したら、英語を数行だけ直すより、確定した日本語から英語を作り直す方が食い違いが少ない。章の追加・削除・並べ替えが英語側に残りやすい。
ビルド前に見る点は次のとおり。
- 各現行記事に対応する
.en.mdがある dateが日英で一致している- セクション区切り(
***と---)が揃っている - 英語ページのHTMLに
hreflangが双方向で入っている
hugo --quiet && echo "build ok"
hugo server -D確認するURLの形は次のとおり。
- 日本語:
/<slug>/ - 英語:
/en/<slug>/
まとめ
海外からの検索流入が目的なら、クライアント側の翻訳ウィジェットではなく、クローラが読める英語HTMLを別URLで出す。
今回採用した形は次のとおり。
| 項目 | 内容 |
|---|---|
| 正本 | 日本語。ルート直下 |
| 英語版 | /en/と*.en.md |
| hreflang | 双方向、x-defaultは日本語 |
| 翻訳範囲 | 現行記事だけ英語化し、古いアーカイブは日本語のまま |
| 言語切替 | リンクのみ。Cookieや自動リダイレクトは使わない |
Cloudflare Pagesのような静的ホスティングなら、ビルド時に両言語を出せば追加のオリジンロジックは不要である。