SwLangSwitch

SwLangSwitch is the built-in language switcher: it reads @nuxtjs/i18n's i18n.locales, auto-lists the available locales, and renders a "globe icon + <select>". Each option's label uses the locale's name field, falling back to code.

Self-gating: the component detects $i18n at runtime (the framework only registers that module when swift.config.json's i18n.enabled is set) — when i18n is disabled, or when there's ≤ 1 available locale, it renders nothing. So you just drop <SwLangSwitch /> into your template; change i18n.locales (the languages picked at sw create time) and the switcher appears / disappears and tracks the locale count automatically.

Switching calls $i18n.setLocale(code): when the strategy is not no_prefix (i.e. prefix / prefix_except_default, the framework's recommendation), setLocale navigates to that locale's prefixed URL (e.g. /en/…), so hreflang / canonical follow along correctly — this is an SEO-correct whole-site language switch, not just a localStorage write.

⚠️ Precisely because a real switch navigates to a prefixed route and switches the whole site's language, the two Demos on this page use the default scoped slot for display only and do NOT wire up the real setLocale (otherwise a single click inside the docs site would switch the docs site's language away). The real default usage of <SwLangSwitch /> is shown as a code example below (not made into a navigating live Demo).

Three customization tiers (headless escape hatch)

From lightest to heaviest:

  1. Reskin — the --sw-lang-switch-* CSS tokens (color / background / radius / hover / gap / height / font-size).
  2. Swap icons — the #icon named slot takes an icon from your own icon library in place of the default globe (scope { locale }, default UI only).
  3. Replace the whole interaction — the default scoped slot: once provided, you self-render the entire UI, and the component hands you { locales, current, setLocale, canSwitch } while still reusing the self-gate (renders nothing when i18n is off / single locale, so you don't have to check yourself).

Basic usage (default "globe + select")

Just drop the real component into your template; it hides itself when i18n is off / single locale. Note: the following is a code example, not a live Demo — a real click calls $i18n.setLocale and navigates to a prefixed URL (switching the whole site's language), which isn't suitable to run directly inside the docs site.

<template>
  <!-- Renders when i18n is enabled and there are ≥ 2 available locales; empty otherwise -->
  <SwLangSwitch />
</template>

Swapping the icon works the same way (#icon overrides the default globe):

<script setup lang="ts">
import { PhTranslate } from '@phosphor-icons/vue'
</script>

<template>
  <SwLangSwitch>
    <template #icon><PhTranslate :size="18" /></template>
  </SwLangSwitch>
</template>

Default scoped slot (self-render · display-only)

Providing the default scoped slot takes over the entire UI, and the component hands you the normalized locales ({ code, label }[]) and current (the current locale code). The Demo below only displays this data: clicking merely updates a local demo state and does NOT call the slot's setLocale (to avoid switching the docs site's language away).

The scoped slot exposes locales / current (clicking updates local state only, no switch)

加载中...
<script setup lang="ts">
import { ref } from 'vue'
// Demo-only local state; in a real project the click should call the slot's setLocale (navigates to a prefixed route)
const pick = ref('')
</script>

<template>
  <SwLangSwitch v-slot="{ locales, current }">
    <SwButton
      v-for="l in locales"
      :key="l.code"
      :class="{ 'is-current': l.code === current }"
      @click="pick = l.code"
    >
      {{ l.label }}
    </SwButton>
  </SwLangSwitch>
</template>

A read-only rendering of the locale list (also no switching — purely showing the shape of locales / current):

Read-only render of the locales list + highlighted current

加载中...

In a real project, the self-rendered UI's click should call the setLocale exposed by the slot:

<template>
  <SwLangSwitch v-slot="{ locales, current, setLocale }">
    <button
      v-for="l in locales"
      :key="l.code"
      :aria-current="l.code === current"
      @click="setLocale(l.code)"
    >
      {{ l.label }}
    </button>
  </SwLangSwitch>
</template>

Props

NameTypeRequiredDefaultDescription
sizenumber18Default globe icon size (px). Has no effect when self-rendering via the default scoped slot

Slots

NameScopeDescription
default{ locales, current, setLocale, canSwitch }Self-render the entire switcher UI; locales is the normalized { code, label }[], current is the current locale code, setLocale(code) switches the locale (prefixed route), canSwitch is always true (the component already gated on it). Still gated by i18n on & ≥ 2 locales
icon{ locale }Language icon (overrides the default globe; default UI only); locale is the current locale code

Skin tokens

TokenPurpose
--sw-lang-switch-color / --sw-lang-switch-hover-colorText / icon color / hover color
--sw-lang-switch-bg / --sw-lang-switch-hover-bgBackground / hover background
--sw-lang-switch-radiusCorner radius
--sw-lang-switch-gap / --sw-lang-switch-padding / --sw-lang-switch-heightGap between icon and select / padding / height
--sw-lang-switch-font-sizeSelect font size

Caveats

  • Self-gate controls visibility: when i18n is off or there's only one locale, the component renders nothing — no need to write a "show only when multilingual" condition yourself.
  • Switching means navigating: under the prefix / prefix_except_default strategies, setLocale navigates to a prefixed URL (SEO-correct), not a pure front-end state switch; no_prefix loses multilingual SEO and is not recommended by the framework (see i18n).
  • size only affects the default globe icon: when using #icon or self-rendering via the default scoped slot, the size is up to your own content.
  • No emitted events: switching goes straight through $i18n.setLocale; to run logic after a locale change, use @nuxtjs/i18n's own API / routing.

See Also