当サイトは先日、日本語に加えて英語(/en/)と簡体字中国語(/zh/)に対応しました。トップページにアクセスすると、ブラウザの言語設定を読み取って、英語設定の方には英語ページを、中国語設定の方には中国語ページを自動的に表示します。
この記事では、その中心にある navigator.language / navigator.languages というブラウザのAPIについて、実際に当サイトで動いているコードを例に解説します。サーバー側の処理を持たない静的サイトでも、数十行のJavaScriptで実現できます。
ブラウザは「言語の希望リスト」を持っている
Chrome や Safari などのブラウザには、「どの言語でコンテンツを見たいか」という設定があります(Chrome では「設定 → 言語」)。通常は OS の言語設定を引き継いでいるため、意識したことがない方も多いかもしれません。
JavaScript からは、この設定を次の2つのプロパティで読み取れます。
navigator.language // 最優先の言語ひとつ(例: "ja")
navigator.languages // 優先順位つきの言語リスト(例: ["ja", "en-US", "en"])
MDN の解説にあるとおり、navigator.languages は先頭ほど優先度が高い読み取り専用の配列で、navigator.language はその先頭の値と一致します。判定には、候補を順に見ていける navigator.languages のほうが便利です。
値の形式は「言語タグ」 — RFC 5646(BCP 47)
これらのプロパティが返す "ja" や "en-US" という文字列は、RFC 5646(通称 BCP 47)で定められた言語タグです。ハイフン区切りで、次のような構造をしています。
ja— 言語のみ(日本語)en-US— 言語+地域(アメリカの英語)zh-CN— 言語+地域(中国本土の中国語)zh-Hans— 言語+文字体系(簡体字の中国語)
ポイントは、同じ言語でも届くタグにはバリエーションがあることです。たとえば中国語は zh・zh-CN・zh-TW・zh-Hans など、環境によってさまざまな形で届きます。そのため後述のコードでは、タグの完全一致ではなく「先頭の主言語部分だけを見る」方針にしています。
なお、同じ情報は HTTP リクエストの Accept-Language ヘッダー(RFC 9110 §12.5.4)としてサーバーにも送られています。サーバー側で判定してリダイレクトする方法もありますが、当サイトのように CDN から静的ファイルを配信するサイトでは、キャッシュとの相性の問題があるため、ブラウザ側で判定するのが定石です。
実際のコード
当サイトのトップページの <head> には、次のスクリプトを埋め込んでいます。
;(() => {
try {
// ① サイト内からトップへ戻ってきた場合は何もしない
if (document.referrer && new URL(document.referrer).origin === location.origin) return
const target = (l) => (l === 'en' ? '/en/' : l === 'zh' ? '/zh/' : null)
// ② 言語切替で明示的に選ばれた言語があれば最優先
const stored = localStorage.getItem('preferred-lang')
if (stored) {
const t = target(stored)
if (t) location.replace(t)
return
}
// ③ ブラウザの優先言語リストを上から順に判定
for (const l of navigator.languages || [navigator.language]) {
const s = (l || '').toLowerCase()
if (s.startsWith('ja')) return // 日本語が上位なら、このまま表示
const t = target(s.slice(0, 2)) // "en-US" → "en"、"zh-CN" → "zh"
if (t) { location.replace(t); return }
}
} catch {}
})()
あわせて、フッターの言語切替リンクには「クリックした言語を記憶する」仕掛けを入れています。
document.querySelectorAll('[data-lang-switch]').forEach((a) => {
a.addEventListener('click', () => {
try { localStorage.setItem('preferred-lang', a.dataset.langSwitch) } catch {}
})
})
コードのポイント
利用者の意思を自動判定より優先する(②) — 「英語のブラウザを使っているけれど日本語で読みたい」という方は必ずいらっしゃいます。言語切替リンクで選んだ言語を localStorage に保存しておき、次回以降は自動判定より優先します。自動リダイレクトを入れるなら、この仕組みとセットにするのがおすすめです。
サイト内の移動では発動させない(①) — document.referrer で「どこから来たか」を確認し、自サイト内からの遷移なら判定をスキップします。これがないと、英語設定の方が日本語ページを読んでいてロゴをクリックした瞬間、英語トップへ飛ばされてしまいます。
主言語だけで判定する(③) — 言語タグにはバリエーションがあるため、slice(0, 2) で主言語(en や zh)だけを取り出して比較します。これで zh-TW の方にも中国語ページをご案内できます。
location.replace() を使う — location.href への代入と違ってリダイレクト元が履歴に残らないため、「戻る」を押すたびにまたリダイレクトされる、というループが起きません。
try...catch で包む — プライベートブラウジングなど localStorage が使えない環境でもエラーで止まらないようにします。仮にこのスクリプトが一切動かなくても、日本語ページが表示されて手動の言語リンクが使えるだけなので、実害はありません。
検索エンジンへの配慮
言語による自動リダイレクトは、やりすぎると検索エンジンが各言語のページを見つけられなくなる恐れがあります。そこで当サイトでは、次の3点をあわせて行っています。
- 自動リダイレクトはトップページだけに限定する(下層ページへのリンクはそのまま表示する)
- 全ページに
hreflangを出力し、各言語版の対応関係を明示する(Google のドキュメント参照) - どの言語にも当てはまらない訪問者向けの既定ページを
x-defaultで伝える
<link rel="alternate" hreflang="ja" href="https://kobayaxi.com/" />
<link rel="alternate" hreflang="en" href="https://kobayaxi.com/en/" />
<link rel="alternate" hreflang="zh-CN" href="https://kobayaxi.com/zh/" />
<link rel="alternate" hreflang="x-default" href="https://kobayaxi.com/" />
まとめ
- ブラウザの言語設定は
navigator.languagesで読み取れます(値は RFC 5646 の言語タグ) - 静的サイトでも、ブラウザ側の JavaScript だけで言語の自動振り分けを実現できます
- 「利用者の明示的な選択を最優先する」「サイト内遷移では発動しない」「対象はトップページのみ」の3つのガードを添えると、押しつけがましくない自動判定になります
当社のデジタル支援事業では、こうしたWebサイトの多言語対応のご相談も承っています。お気軽にお問い合わせください。
