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:
- Reskin — the
--sw-lang-switch-*CSS tokens (color / background / radius / hover / gap / height / font-size). - Swap icons — the
#iconnamed slot takes an icon from your own icon library in place of the default globe (scope{ locale }, default UI only). - 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
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
size | number | 18 | Default globe icon size (px). Has no effect when self-rendering via the default scoped slot |
Slots
| Name | Scope | Description |
|---|---|---|
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
| Token | Purpose |
|---|---|
--sw-lang-switch-color / --sw-lang-switch-hover-color | Text / icon color / hover color |
--sw-lang-switch-bg / --sw-lang-switch-hover-bg | Background / hover background |
--sw-lang-switch-radius | Corner radius |
--sw-lang-switch-gap / --sw-lang-switch-padding / --sw-lang-switch-height | Gap between icon and select / padding / height |
--sw-lang-switch-font-size | Select 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_defaultstrategies,setLocalenavigates to a prefixed URL (SEO-correct), not a pure front-end state switch;no_prefixloses multilingual SEO and is not recommended by the framework (see i18n). sizeonly affects the default globe icon: when using#iconor 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.