Skip to content

Navigation

LocaleSwitcher

A compact language selector for public headers, with options in their own language and an async onselect.

<script>
  import { LocaleSwitcher } from "@codeatlasdev/ui";
  import { goto } from "$app/navigation";

  const options = [
    { value: "pt-BR", label: "Português", short: "PT" },
    { value: "en", label: "English" },
  ];
  let { data } = $props();

  async function onselect(locale) {
    await fetch("/api/preferences", { method: "POST", body: JSON.stringify({ locale }) });
    document.cookie = `locale=${locale}; path=/; max-age=31536000`;
    await goto(`/${locale}/login`);
  }
</script>

<LocaleSwitcher {options} value={data.locale} {onselect} />

Options are data, the callback is yours: the component knows no router, cookie or store, so it fits an app with localized URLs (/en/login) as well as one that only sets a cookie. onselect(locale) may return a promise. While it is pending the trigger is disabled and aria-busy, and value changes only after it resolves; if it rejects the old value stays and onerror receives the error. Choosing the current language does nothing.

Each option is named in its own language and its menu item carries a lang attribute (default the option value, override with lang), so screen readers pronounce “Português” in Portuguese. The trigger shows a globe and the native name from 600px, and the short code (short, default the first two letters of value uppercased) below it; compact always shows the code. The accessible name is “Language: “ plus the visible text, so it always contains what is on screen. It has a 44px hit area on touch. The menu is a DropdownMenu radio group, so it opens with Enter, Space and the arrow keys.

To put it in a header, use the trailing slot of SiteHeader (the end of the bar), next to the primary call to action.

#501

Basic

Native names, a short code on narrow screens and an async onselect that keeps the switcher busy.

Pick a language: the switcher stays busy until onselect resolves.

Reference #501 · Pick in the gallery

Code
svelte
<script lang="ts">
  import { LocaleSwitcher } from '@codeatlasdev/ui';

  const options = [
    { value: 'pt-BR', label: 'Português', short: 'PT' },
    { value: 'en', label: 'English' },
    { value: 'es', label: 'Español' },
  ];

  let locale = $state('pt-BR');
  let saved = $state('');

  // Save the preference, set the cookie, then navigate to the localized URL.
  async function onselect(next: string) {
    await new Promise((resolve) => setTimeout(resolve, 600));
    saved = `Saved ${next}; the app would now go to /${next}/login.`;
  }
</script>

<div class="col">
  <LocaleSwitcher {options} bind:value={locale} {onselect} />
  <p class="note">{saved || 'Pick a language: the switcher stays busy until onselect resolves.'}</p>
</div>

<style>
  .col {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--ca-space-3);
  }
  .note {
    margin: 0;
    color: var(--ca-text-secondary);
    font-size: var(--ca-font-size-body2);
  }
</style>
#502

In header

The SiteHeader trailing slot holds the switcher and a Sign up button.

Reference #502 · Pick in the gallery

Code
svelte
<script lang="ts">
  import { Button, Card, LocaleSwitcher, SiteHeader } from '@codeatlasdev/ui';

  const options = [
    { value: 'pt-BR', label: 'Português', short: 'PT' },
    { value: 'en', label: 'English' },
  ];
  let locale = $state('pt-BR');
</script>

<Card full flush>
  <SiteHeader>
    {#snippet trailing()}
      <LocaleSwitcher {options} bind:value={locale} />
      <Button size="sm" variant="filled">Sign up</Button>
    {/snippet}
  </SiteHeader>
</Card>

Props

PropTypeDefaultDescription
options{ value: string; label: string; short?: string; lang?: string }[]–The languages on offer, each `label` in its own language. `short` is the narrow-screen code, `lang` the BCP 47 tag of the item (default `value`).
valuestring–Current locale, an option `value`. Bindable; changes only after `onselect` succeeds.
onselect(value: string) => void | Promise<void>–Called with the picked locale. Await your save, cookie and navigation here.
onerror(error: unknown) => void–Called when `onselect` throws or rejects; `value` keeps its previous value.
compactbooleanfalseAlways show the short code. Without it the name shows from 600px.
labelstring'Language'Accessible name prefix. Localized by default.
align'start' | 'center' | 'end''end'Menu alignment to the trigger.
classstring''Extra classes on the trigger.