# `PhoenixKitWeb.Components.Core.LanguageSwitcher`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.32.1/lib/phoenix_kit_web/components/core/language_switcher.ex#L1)

Language switcher component for frontend and admin applications.

Provides reusable language selection UI that pulls available languages
from the unified Languages module. Three display variants are available:
dropdown, button group, and inline.

## Continent Grouping

When more than 7 languages are enabled (configurable via `continent_threshold`),
the dropdown automatically shows a two-step interface: first pick a continent,
then pick a language within it. Set `group_by_continent={false}` to always
show a flat list regardless of language count.

## Examples

    # Basic dropdown — auto-groups by continent when >7 languages
    <.language_switcher_dropdown current_locale={@current_locale} />

    # Force flat list (no continent step)
    <.language_switcher_dropdown current_locale={@current_locale} group_by_continent={false} />

    # Custom threshold for continent grouping
    <.language_switcher_dropdown current_locale={@current_locale} continent_threshold={5} />

    # Show current language in trigger button
    <.language_switcher_dropdown current_locale={@current_locale} show_current={true} />

    # Button group (for mobile)
    <.language_switcher_buttons current_locale={@current_locale} />

    # Inline text links (for footers)
    <.language_switcher_inline current_locale={@current_locale} />

## Locale routing contract

**The language of a page lives in its URL** — `/et/products`, `/ru/products`
— never in the session. The default language may be served without a
prefix when the site is configured that way; every other language carries
its segment. This is deliberate, and the switcher is built on it:

  * A shared link must show the same language to everyone who opens it. A
    session language makes the same URL show different content to different
    people.
  * Search engines need a distinct URL per language: `hreflang` and
    canonical links point at URLs, crawlers send no cookies, and a page that
    varies by session gets indexed in one language for everyone.
  * Caches and CDNs key on the URL; link previews (chat apps, social cards)
    fetch without cookies and would always show the default language.

PhoenixKit once had a session fallback and removed it after it overrode the
language of URLs people had been sent. The navigation hook sets the page
language from the URL on every navigation, so a session-locale page is
fighting the kit, and the switcher's links will not behave there. In
development the switcher logs a warning (once per boot) when it renders a
non-default language at a URL without a locale segment.

**Not supported, on purpose:** a "session locale mode", or a hook that lets
the switcher emit the same URL for every language. What a host can do:

  * Route its localized pages under the locale segment (the migration is
    usually a `scope "/:locale"` around the existing routes plus
    `Routes.path/2` for links).
  * Use a cookie or saved preference only to pick the language of a
    **bare** landing request (`/`), redirecting to `/<locale>/…`. Once a URL
    carries a locale, the URL wins.
  * Build its own switcher markup with `locale_path/2`, which returns
    exactly the links this component renders.

A subdomain per language satisfies the same rule — the invariant is "a
distinct, stable URL per language", not "a path segment".

# `dedupe_names`

Overrides each language entry's `:name` (or `"name"`) with the
bare base-language label when only one dialect of that base is
configured. Multi-dialect bases keep their full configured name so
users can tell them apart.

Accepts a list of language entries shaped as atom-keyed maps,
string-keyed maps, or `%PhoenixKit.Modules.Languages.Language{}`
structs. Returns the list in the same shape with the relevant
`:name`/`"name"` key replaced.

Used by the admin top-bar dropdown and the user dashboard nav to
inherit the frontend switcher's dedup rule without duplicating the
logic. Internal frontend-switcher code paths
(`build_dialect_list/1`, `langs_to_dialect_maps/2`) compute names
inline because they emit a new result map per entry; this helper
is the entry point for callers that already have a list of entries
and just need their names normalized.

# `extract_base_language_name`

Strips the country / region qualifier from a configured language
name. Used to render bare base-language labels when only one
dialect of the base is enabled.

Public because the admin top-bar dropdown
(`PhoenixKitWeb.Components.AdminNav`) and the user dashboard nav
(`PhoenixKitWeb.Components.UserDashboardNav`) both call into it via
`dedupe_names/1` so all language menus share one rule.

## Examples

    iex> extract_base_language_name("Spanish (Mexico)")
    "Spanish"

    iex> extract_base_language_name("Chinese (Simplified)")
    "Chinese"

    iex> extract_base_language_name("Japanese")
    "Japanese"

# `language_switcher_buttons`

Renders a button group language switcher.

Displays language buttons in a row. Good for mobile layouts and areas
where space allows for multiple buttons. Automatically fetches the configured
languages (or default top 12 if not configured).

## Examples

    <.language_switcher_buttons current_locale={@current_locale} />

## Attributes

* `current_locale` (`:string`) - Current active language code (auto-detected if not provided). Defaults to `nil`.
* `languages` (`:any`) - List of language maps. If nil, fetches from Language Module. Defaults to `nil`.
* `show_flags` (`:boolean`) - Show language flags. Defaults to `true`.
* `show_names` (`:boolean`) - Show language names. Defaults to `true`.
* `goto_home` (`:boolean`) - Send every language link to that language's home page instead of the current page in that language. Defaults to `false`.
* `hide_current` (`:boolean`) - Hide currently selected language from list. Defaults to `false`.
* `class` (`:string`) - Additional CSS classes. Defaults to `""`.
* `current_path` (`:string`) - Current path to preserve when switching languages. Defaults to `nil`.
* `per_translation_urls` (`:list`) - Optional per-translation URL overrides. See `language_switcher_dropdown/1` for details. Defaults to `nil`.

# `language_switcher_dropdown`

Renders a dropdown language switcher.

Displays a globe icon that opens a dropdown menu with available languages.
Automatically fetches the configured languages (or defaults when unconfigured).
Used in both frontend navigation bars and the admin panel header.

When more than `continent_threshold` languages are enabled, shows a two-step
continent → language navigation. Set `group_by_continent={false}` to disable.

## Examples

    <.language_switcher_dropdown current_locale={@current_locale} />

    <.language_switcher_dropdown
      current_locale={@current_locale}
      group_by_continent={false}
    />

## Attributes

* `current_locale` (`:string`) - Current active language code (auto-detected if not provided). Defaults to `nil`.
* `languages` (`:any`) - List of language maps. If nil, fetches from Language Module. Defaults to `nil`.
* `show_flags` (`:boolean`) - Show language flags. Defaults to `true`.
* `show_names` (`:boolean`) - Show language names. Defaults to `true`.
* `show_native_names` (`:boolean`) - Show native language names. Defaults to `false`.
* `goto_home` (`:boolean`) - Send every language link to that language's home page instead of the current page in that language. Defaults to `false`.
* `hide_current` (`:boolean`) - Hide currently selected language from list. Defaults to `false`.
* `class` (`:string`) - Additional CSS classes. Defaults to `""`.
* `current_path` (`:string`) - Current path to preserve when switching languages. Defaults to `nil`.
* `scroll_threshold` (`:integer`) - Number of languages after which to show scrollbar and search. Defaults to `10`.
* `show_current` (`:boolean`) - Show current language (flag + name) in dropdown trigger instead of globe icon. Defaults to `false`.
* `group_by_continent` (`:boolean`) - Enable continent grouping when language count exceeds continent_threshold. Defaults to `true`.
* `continent_threshold` (`:integer`) - Number of languages after which the continent grouping step is shown. Defaults to `7`.
* `_language_update_key` (`:any`) - Internal: forces re-render when languages change. Defaults to `nil`.
* `per_translation_urls` (`:list`) - Optional list of per-translation URLs that override the locale-rewrite
  default. Each entry is `%{code: <display_code>, url: <full_url>}`.
  Both atom-keyed (`%{code: ..., url: ...}`) and string-keyed
  (`%{"code" => ..., "url" => ...}`) entries are accepted — useful
  when the list comes from JSON/JSONB rather than Elixir code.

  Useful when a feature module (e.g. `phoenix_kit_publishing`) has
  computed canonical URLs for each available translation that the
  simple locale-rewrite default can't reproduce — for example when
  a post has per-language URL slugs. Pass
  `assigns[:phoenix_kit_publishing_translations]` from the layout;
  the switcher resolves each language's `base_code` against the list
  (via `DialectMapper.extract_base/1`) and falls back to the
  locale-rewrite URL when no entry matches or the matched entry
  has a `nil` `url` (e.g. an unpublished draft).

  Defaults to `nil`.
* `ai_translate` (`:map`) - Optional opt-in for the AI-translate affordance. When present and
  `:enabled` is true, missing-language items show a sparkle button
  that fires the host LV's `phx-click` event (and a bulk
  "translate all missing" CTA renders below the list when ≥2 languages
  are missing).

  Shape:

      %{
        enabled: true,
        event: "translate_lang",        # phx-click target on host LV
        missing: ["es", "de"],          # base codes lacking a translation
        in_flight: ["es"]               # show spinner, click disabled
      }

  Only `:missing` and `:in_flight` drive rendering. A "completed"
  language is signalled simply by the host dropping its code from
  `:missing` — the sparkle then disappears on the next render. (There
  is no separate `:completed` checkmark state; pass nothing for it.)

  The component emits the host's event; the host owns enqueuing the
  actual translation worker and broadcasting the resulting `:missing` /
  `:in_flight` state back via PubSub. Set `:enabled` to `false` (or pass
  `nil`) to fall back to today's behavior with no AI UI — convenient for
  hosts that gate on `PhoenixKitAI.Translations.available?/0`.

  ## Bulk action dispatch

  The "Translate all missing" CTA fires the same event with
  `phx-value-lang="*"` as a sentinel. Host handlers branch on the
  value:

      def handle_event("translate_lang", %{"lang" => "*"}, socket) do
        # enqueue one job per *actionable* language (missing minus
        # in_flight) — matches the count shown on the bulk button
      end

      def handle_event("translate_lang", %{"lang" => lang}, socket) do
        # enqueue a single-language job
      end

  Defaults to `nil`.

# `language_switcher_inline`

Renders an inline language switcher.

Displays languages as inline text links. Minimal design perfect for footers
or compact navigation areas. Automatically fetches the configured languages
(or default top 12 if not configured).

## Examples

    <.language_switcher_inline current_locale={@current_locale} />

## Attributes

* `current_locale` (`:string`) - Current active language code (auto-detected if not provided). Defaults to `nil`.
* `languages` (`:any`) - List of language maps. If nil, fetches from Language Module. Defaults to `nil`.
* `show_flags` (`:boolean`) - Show language flags. Defaults to `true`.
* `show_names` (`:boolean`) - Show language names. Defaults to `true`.
* `goto_home` (`:boolean`) - Send every language link to that language's home page instead of the current page in that language. Defaults to `false`.
* `hide_current` (`:boolean`) - Hide currently selected language from list. Defaults to `false`.
* `class` (`:string`) - Additional CSS classes. Defaults to `""`.
* `current_path` (`:string`) - Current path to preserve when switching languages. Defaults to `nil`.
* `per_translation_urls` (`:list`) - Optional per-translation URL overrides. See `language_switcher_dropdown/1` for details. Defaults to `nil`.

# `locale_path`

```elixir
@spec locale_path(String.t() | nil, String.t()) :: String.t()
```

The path of `current_path` in the language `base_code` — what the switcher
links to. The default language gets an unprefixed path when the site is
configured that way; every other language gets its locale segment.

Public so a host that needs its own switcher markup builds the same links
the kit does, instead of re-deriving the locale rules.

    iex> locale_path("/phoenix_kit/et/admin/users", "ru")
    "/phoenix_kit/ru/admin/users"

See "Locale routing contract" in the moduledoc: the language of a page lives
in its URL, so this always produces a URL that carries it.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
