# General Translation Knowledge Base: 言語切り替え機能のベストプラクティス：UX、SEO、アクセシビリティ
URL: https://generaltranslation.com/ja/kb/language-switcher.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: 各言語は Deutsch や 日本語 のようにその言語自身の名称で表記し、国を表す国旗は使わないでください。各選択肢からは、クロール可能なリンクで同じページの該当言語版へ遷移させ、選択内容はクッキーに保存します。ローカライズされた URL は決して自動リダイレクトしないでください。
Category: 国際化
Last updated: 2026-10-20
Authors: Kevin Wu, Jackie Chen
Published: 2026-10-20

言語切り替え機能を最も必要としているのは、開いたページを読めない人です。そのため、以下で取り上げる判断はすべて、ひとつの原則に基づいています。つまり、サイトのデフォルト言語を読めない人にも使い方がわかるコントロールでなければならない、ということです。

## 優れた言語切り替え機能の条件とは？

### 1. 各言語はその言語自身の名称で表記する

英語のページで日本語話者が探すのは &quot;Japanese&quot; ではなく「日本語」であり、ドイツ語話者が探すのは &quot;Deutsch&quot; です。W3C の[言語メニューに関するガイダンス](https://www.w3.org/International/questions/qa-navigation-select)でも、&quot;French&quot; ではなく &quot;français&quot; と表記するよう推奨されています。このような、その言語自身による言語名はエンドニム (endonym) と呼ばれ、JavaScript の [`Intl.DisplayNames`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames) を使えばロケールコードから生成できます。

```js
const locales = ['en', 'fr', 'de', 'ja', 'ar', 'pt-BR', 'zh-Hant'];

for (const code of locales) {
  // 各言語の名前をその言語自身で表記する（エンドニム）
  const own = new Intl.DisplayNames([code], { type: 'language' }).of(code);
  // 比較用に、同じ言語名を英語で表記したもの
  const english = new Intl.DisplayNames(['en'], { type: 'language' }).of(code);
  console.log(`${code}: ${own} (${english})`);
}
```

Node.js 24 (ICU 78.3、CLDR 48.0) での出力：

```text
en: English (English)
fr: français (French)
de: Deutsch (German)
ja: 日本語 (Japanese)
ar: العربية (Arabic)
pt-BR: português (Brasil) (Brazilian Portuguese)
zh-Hant: 繁體中文 (Traditional Chinese)
```

フランス語やポルトガル語では文中の言語名を小文字で書くため、CLDR は「français」を返します。メニューではそれぞれの名前が単独で表示されるので、先頭の文字を大文字にしてください。リージョンを付けるのは、同じ言語で 2 つのバリエーションを提供する場合だけにしましょう。たとえば、「Português (Brasil)」と「Português (Portugal)」を並べて表示するような場合です。

### 2. 言語を国旗で表さない

W3C の[言語宣言に関するベストプラクティス](https://www.w3.org/TR/i18n-html-tech-lang/)は、「言語を示すために国旗アイコンを使用しないでください」と明言しています。その[理由](https://www.w3.org/International/questions/qa-link-lang)は、「国旗が表すのは国であって、言語ではない」からです。1 つの言語が複数の国で話されていることもあれば (スペイン、メキシコ、アルゼンチンのスペイン語など) 、1 つの国に複数の公用語があることもあります (スイスなど) 。さらに、国旗には特定の国のイメージがつきまとうため、同じ言語を話す他国の人々に好ましくない印象を与えるおそれがあります。国旗が適しているのは、リージョンを選ぶための国やストアのセレクターです。その場合も、そこで利用できる言語名を併記しましょう。

### 3. ユーザーが目を向ける場所に、ひと目でわかるトリガーとともに配置する

W3C は、コントロールを「ページの最上部またはその付近」に配置することを[推奨しています](https://www.w3.org/International/questions/qa-navigation-select)。また、テキストラベルは言語に依存するため、その横にひと目でわかる画像を添えることも勧めています。現時点で万国共通に認識されている記号はありませんが、W3C は地球儀アイコンが普及しつつあると指摘しています。現在の言語名の横に地球儀アイコンを置けば、どの言語の読者にも伝わります。多くのサイトでは 2 つ目のエントリポイントとしてフッターにも言語切り替え機能を配置していますが、フッターだけに置くのは避けてください。

### 4. ホームページではなく同じページに切り替える

英語で料金ページを読んでいたユーザーがフランス語に切り替えたなら、求めているのはフランス語の料金ページです。フランス語のホームページに飛ばしてしまうと、読んでいた場所を見失わせるうえ、ようやく読めるようになったばかりの言語で改めてページを探し直させることになります。各言語の選択肢は、現在のURLに対応する翻訳版にリンクさせましょう。まだ翻訳されていないページの場合は、最も近い翻訳済みの親ページにリンクし、その旨を明示してください。

### 5. `hreflang` と `lang` を付けたクロール可能なリンクを使う

Google は原則として、[`href` を持つ `<a>` 要素でなければリンクをクロールできません](https://developers.google.com/search/docs/crawling-indexing/links-crawlable)。また多言語サイトに対しては、ユーザーがクリックして言語を選べるよう、[他の言語版へのハイパーリンクを追加する](https://developers.google.com/search/docs/specialty/international/managing-multi-regional-sites)ことを推奨しています。変更ハンドラーで `window.location` を設定する `<select>` はリンクではないため、クローラーがたどる手がかりになりません。

各リンクには 2 つの属性を追加します。`<a>` の `hreflang` 属性は[リンク先ページの言語を示すヒント](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a)です。ブラウザ側の組み込み動作はなく、検索エンジンが読み取る `<link rel="alternate" hreflang>` 注釈とも別物です ([hreflang とは](/kb/what-is-hreflang)を参照) 。`lang` 属性はリンクテキストの言語を示すもので、これによりスクリーンリーダーはページを読み上げる際に「Deutsch」をドイツ語の規則で発音できます。WCAG の [Language of Parts](https://www.w3.org/WAI/WCAG22/Understanding/language-of-parts.html) 達成基準では、各言語自身の表記で書かれた言語リンクが例として挙げられています。ただし、限界についても触れられています。ページ内リンクの一覧表示などで、支援技術がリンクをアクセシブルネームのみで読み上げる場合は、発音が切り替わりません。

### 6. 明示的な選択を記憶する

ユーザーが言語を選んだら、その選択をクッキーまたはアカウントに保存し、次回以降の訪問時に活用しましょう。具体的には、`/` のような中立的なルートにアクセスされた際の遷移先の決定や、言語切り替え機能での現在の選択の表示に使います。ただし、ユーザーが開いたローカライズ済みの URL よりもクッキーを優先させてはいけません。W3C は「後の訪問に備えて、結果をデータベースまたはクッキーに保存する」ことを[推奨しており](https://www.w3.org/International/questions/qa-accept-lang-locales)、[MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Language) も、サーバーは「ユーザーによる明示的な言語選択を決して上書きすべきではない」と述べています。

### 7. 訪問のたびに自動で切り替えない

リクエストのたびにブラウザの言語設定でリダイレクトすると、旅行者や共用コンピューターの利用者、特定の言語のリンクから訪れたユーザーが、意図しない言語から抜け出せなくなります。ブラウザの設定は、閉じられる提案 (「Diese Seite gibt es auch auf Deutsch」) を表示するためだけに使い、どうするかはユーザーに任せましょう。どのシグナルを優先すべきか、またリダイレクトしても問題ない唯一のケースについては、[ロケールルーティングと SEO](/kb/locale-routing-seo) で解説しています。

### 8. キーボードとスクリーンリーダーで操作できるようにする

単純なリンクのリストやネイティブの `<select>` であれば、特別な対応をしなくてもキーボードや支援技術で操作できます。ドロップダウンを自作する場合は、WAI-ARIA の[ディスクロージャーパターン](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/)に従ってください。具体的には、`<button>` でリストの表示を切り替え、Enter キーと Space キーで開閉できるようにし、`aria-expanded` で開閉状態を伝えます。コントロールにはページの言語でアクセシブルネームを付け (ドイツ語のページなら `aria-label="Sprache"`) 、現在の言語には `aria-current="page"` を指定してください。

## 言語切り替え機能を作成するには？

### プレーンな HTML

このメニューには、8 つのプラクティスがすべて取り入れられています。自称言語名 (エンドニム) の使用、国旗ではなく地球儀アイコンの使用、同じページへのリンク、各リンクへの `hreflang` と `lang` の指定、開閉ボタン、そしてユーザーが明示的に選んだ言語を保存するクッキーです。

```html title="On /de/pricing"
<nav aria-label="Sprache">
  <button type="button" aria-expanded="false" aria-controls="language-menu">
    <svg aria-hidden="true" width="16" height="16"><!-- 地球儀アイコン --></svg>
    Deutsch
  </button>
  <ul id="language-menu" hidden>
    <li><a href="/en/pricing" hreflang="en" lang="en">English</a></li>
    <li><a href="/fr/pricing" hreflang="fr" lang="fr">Français</a></li>
    <li><a href="/de/pricing" hreflang="de" lang="de" aria-current="page">Deutsch</a></li>
    <li><a href="/ja/pricing" hreflang="ja" lang="ja">日本語</a></li>
  </ul>
</nav>
```

```js title="switcher.js"
const button = document.querySelector('[aria-controls="language-menu"]');
const menu = document.getElementById('language-menu');

// メニューを開閉する（<button> は Enter キーと Space キーでネイティブに動作する）
button.addEventListener('click', () => {
  const open = button.getAttribute('aria-expanded') === 'true';
  button.setAttribute('aria-expanded', String(!open));
  menu.hidden = open;
});

// Escape キーでディスクロージャーを閉じ、フォーカスをボタンに戻す。
button.closest('nav').addEventListener('keydown', (event) => {
  if (event.key === 'Escape' && !menu.hidden) {
    menu.hidden = true;
    button.setAttribute('aria-expanded', 'false');
    button.focus();
  }
});

// 次回以降の訪問に備えて、ユーザーが明示的に選んだ言語を記憶する
menu.addEventListener('click', (event) => {
  const link = event.target.closest('a[hreflang]');
  if (link) {
    document.cookie = `locale=${link.hreflang}; path=/; max-age=31536000; samesite=lax`;
  }
});
```

### React

このコンポーネントは通常のリンクを使用します。現在のパス、クエリ文字列、フラグメントを `currentHref` として、現在のロケールと一緒に渡してください。このコンポーネントは、すべてのロケールに URL プレフィックスが付いていること、すべてのページが指定したすべてのロケールに存在すること、そしてスラッグがどの言語でも同一であることを前提としています。スラッグを翻訳している場合や、一部のロケールにページが存在しない場合は、プレフィックスを置き換えるのではなく、ルートや CMS のデータから各リンク先を解決してください。

```jsx title="LanguageSwitcher.jsx"
const LOCALES = ['en', 'fr', 'de', 'ja'];

// パス、末尾のスラッシュ、クエリ、フラグメントを維持したままロケールを切り替える
function hrefForLocale(currentHref, locale) {
  const url = new URL(currentHref, 'https://example.invalid');
  const trailingSlash = url.pathname.endsWith('/');
  const segments = url.pathname.split('/').filter(Boolean);
  if (LOCALES.includes(segments[0])) segments.shift();
  url.pathname = `/${[locale, ...segments].join('/')}${trailingSlash ? '/' : ''}`;
  return `${url.pathname}${url.search}${url.hash}`;
}

// 言語名をその言語自身の表記で取得し、メニュー表示用に先頭を大文字にする
function endonym(locale) {
  const name = new Intl.DisplayNames([locale], { type: 'language' }).of(locale);
  return name.charAt(0).toLocaleUpperCase(locale) + name.slice(1);
}

function rememberChoice(locale) {
  document.cookie = `locale=${locale}; path=/; max-age=31536000; samesite=lax`;
}

export function LanguageSwitcher({ currentHref, currentLocale, label }) {
  return (
    <nav aria-label={label}>
      <ul>
        {LOCALES.map((locale) => (
          <li key={locale}>
            <a
              href={hrefForLocale(currentHref, locale)}
              hrefLang={locale}
              lang={locale}
              aria-current={locale === currentLocale ? 'page' : undefined}
              onClick={() => rememberChoice(locale)}
            >
              {endonym(locale)}
            </a>
          </li>
        ))}
      </ul>
    </nav>
  );
}
```

`currentHref="/fr/pricing/?plan=pro#faq"`、`currentLocale="fr"`、`label="Langue"` を指定した場合、ドイツ語のリンクは `/de/pricing/?plan=pro#faq` となり、フランス語のリンクには `aria-current="page"` が付与されます。クエリパラメーターとフラグメントは、遷移先のページでも同じ意味を持つ場合にのみ引き継いでください。アンカー ID が翻訳されている場合は、対応するフラグメントも解決する必要があります。

### General Translation を使う場合

公開されている Next.js ページでは、GT の [`<Link>`](/docs/react/nextjs/link) を使うとロケール対応のリンクを生成できます。`prefixDefaultLocale: true` を指定すると、これらのリンクの遷移先は `/en/pricing` や `/de/pricing` になります。この例では、英語版とドイツ語版の料金ページが公開済みであることを前提としています。現在のロケールと、ローカライズ済みのナビゲーションラベルを渡します。

```tsx
import Link from 'gt-next/link';

export function PricingLanguages({ currentLocale, label }: { currentLocale: string; label: string }) {
  return (
    <nav aria-label={label}>
      <Link href="/pricing" locale="en" hrefLang="en" lang="en" aria-current={currentLocale === 'en' ? 'page' : undefined}>English</Link>
      {' · '}
      <Link href="/pricing" locale="de" hrefLang="de" lang="de" aria-current={currentLocale === 'de' ? 'page' : undefined}>Deutsch</Link>
    </nav>
  );
}
```

現在のページのナビゲーションラベルをローカライズします。認証済みアプリ内など、コンパクトなネイティブコントロールを使いたい場合は、ロケールセレクターを使用します。

[`<LocaleSelector>`](/docs/react/reference/components/locale-selector) は `gt-react`、`gt-next`、および実験的な `gt-tanstack-start` で利用できます。設定済みのロケールをネイティブの `<select>` としてレンダリングし、各ロケールをその言語自身の表記で表示して、選択内容をクッキーに保存します。標準の `<select>` 属性はそのまま渡されるため、[`useGT`](/docs/react/reference/hooks/use-gt) で翻訳した `aria-label` を指定できます。

```tsx
'use client';
import { LocaleSelector, useGT } from 'gt-next';

export function Header() {
  const gt = useGT();
  return <LocaleSelector aria-label={gt('Language')} />;
}
```

公開ページでは、クロール可能なナビゲーションとして上記のリンクを使用し、[多言語 Next.js の SEO](/kb/multilingual-nextjs-seo) で説明しているように、ページの head で hreflang の代替リンクを宣言してください。デザインを独自に作り込む場合は、[`useLocaleSelector`](/docs/react/reference/hooks/use-locale-selector) を使うと、アクティブなロケール、ロケールの一覧、`setLocale` を取得できます。Vue では、[Vue のロケールガイド](/docs/vue/guides/managing-locales#switcher)に沿って、[`useLocale`](/docs/vue/reference/composables/use-locale) と [`useSetLocale`](/docs/vue/reference/composables/use-set-locale) で言語切り替え機能を構築します。

## 重要なポイント

* 選択肢のラベルには Français や 日本語 のような自称 (エンドニム) を使います。これらは `Intl.DisplayNames` で生成でき、フランス語とポルトガル語では小文字表記になります。
* 国旗ではなく地球儀アイコンを使いましょう。W3C も「言語を示すために国旗アイコンを使用しないこと」と明言しています。
* 各言語の同じページへのリンクは、`hreflang` と `lang` を付けたシンプルな `<a href>` リンクで提供します。こうすることで、クローラーがリンクをたどれるようになり、スクリーンリーダーも各言語名を正しく発音できます。
* ユーザーが明示的に選んだ言語を保存し、ブラウザの言語設定に基づいてローカライズされた URL から別の URL へリダイレクトすることは避けてください。
* General Translation の [`<LocaleSelector>`](/docs/react/reference/components/locale-selector) は、各言語を母語で表記したラベル付きのネイティブ `<select>` をレンダリングし、選択内容をクッキーに保存します。クローラー向けには、別途シンプルなリンクを追加してください。

## Related questions

- [ユーザーのブラウザ言語を検出するには？](https://generaltranslation.com/ja/kb/detect-user-language.mdx): ブラウザが持つ優先順位付きの言語リスト（JavaScript では navigator.languages、サーバー側では q 値で並べ替えた Accept-Language ヘッダー）を読み取り、フォールバックを用意したうえで対応ロケールとマッチングします。なお、Safari や Chrome のシークレットモードでは 1 つの言語しか通知されません。また、IP ジオロケーションでわかるのは所在地であって、言語ではありません。
- [言語リダイレクトと SEO：URL、クッキー、Accept-Language のどれを使うべきか](https://generaltranslation.com/ja/kb/locale-routing-seo.mdx): Googlebot は Accept-Language ヘッダーを送信せず、クロールの大半を米国の IP アドレスから行います。そのため、インデックスさせたいすべてのページの言語は URL で決めましょう。保存済みのクッキーの選択、次いで Accept-Language を使うのは、/ のような中立的なルートのリダイレクト先を決める場合だけにとどめ、その際は一時的な 302 または 307 を使います。
- [hreflang とは？設定方法を解説](https://generaltranslation.com/ja/kb/what-is-hreflang.mdx): hreflang は、どの URL が同じページの言語版または地域版であるかを Google に伝え、検索結果に適切なページを表示させるための仕組みです。HTML の link タグ、HTTP の Link ヘッダー、またはサイトマップで追加し、すべてのリンクを相互に参照させたうえで、en-UK ではなく en-GB のような有効なコードを使用してください。
- [ウェブサイトを多言語化するにはどうすればよいですか？](https://generaltranslation.com/ja/kb/make-website-multilingual.mdx): 翻訳をどこで管理するか（プロキシ、CMSプラグイン、サイトビルダー、または自前のコード）を選び、Googleの推奨どおり言語ごとに専用のURLを用意します。ページ全体に加えてタイトルとメタディスクリプションも翻訳し、hreflangで各言語版を関連付けます。また、訪問者を自動でリダイレクトするのではなく、言語切り替え機能を設置しましょう。

## Previous and next questions

- Previous question: [言語リダイレクトと SEO：URL、クッキー、Accept-Language のどれを使うべきか](https://generaltranslation.com/ja/kb/locale-routing-seo.mdx)
- Next question: [Cursor や Claude Code でアプリを国際化するには？](https://generaltranslation.com/ja/kb/ai-agent-i18n-setup.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/ja/kb/sitemap.md) for every article.
