# `PhoenixKit.Modules.Languages.DialectMapper`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.165/lib/modules/languages/dialect_mapper.ex#L1)

Handles mapping between base language codes (en, es) and full dialect codes (en-US, es-MX).

This module provides the core logic for PhoenixKit's simplified URL architecture where
URLs show base codes (/en/) but translations use full dialect codes (en-US).

## Architecture

PhoenixKit uses a two-tier locale system:

1. **Base Language Codes** - Used in URLs for simplicity
   - Format: 2-letter ISO 639-1 codes (en, es, fr, de, pt, zh, ja, etc.)
   - Examples: `/en/dashboard`, `/es/admin`, `/fr/users`
   - User-facing, SEO-friendly, easy to remember

2. **Full Dialect Codes** - Used internally for translations
   - Format: BCP 47 language tags (en-US, es-MX, pt-BR, zh-CN)
   - Examples: en-US, en-GB, es-ES, es-MX, pt-PT, pt-BR
   - Translation-aware, respects regional differences

## Data Flow

```
User visits: /en/dashboard
      ↓
Extract base: "en"
      ↓
Resolve dialect: "en" → "en-US" (default mapping for the base code)
      ↓
Set Gettext: "en-US"
      ↓
Generate URLs: Always use base code "en"
```

## Default Dialect Mapping

When no user preference exists, base codes map to most common regional variants:
- `en` → `en-US` (American English)
- `es` → `es-ES` (European Spanish)
- `pt` → `pt-BR` (Brazilian Portuguese)
- `zh` → `zh-CN` (Simplified Chinese)
- `de` → `de-DE` (German Germany)
- `fr` → `fr-FR` (French France)

## URL-Driven Resolution

Dialect resolution is purely URL-driven: a base code maps to its
default dialect and nothing else. A logged-in user's locale preference
is intentionally not consulted (see `PhoenixKitWeb.Users.Auth` for the
URL-is-authoritative rationale), so `/en/dashboard` always resolves to
the default `en-US` regardless of who is signed in.

## Examples

    # Extract base language from full dialect
    iex> DialectMapper.extract_base("en-US")
    "en"

    iex> DialectMapper.extract_base("es-MX")
    "es"

    # Convert base to default dialect
    iex> DialectMapper.base_to_dialect("en")
    "en-US"

    iex> DialectMapper.base_to_dialect("pt")
    "pt-BR"

    # Resolve a base code to its default dialect (URL-driven)
    iex> DialectMapper.resolve_dialect("en")
    "en-US"

## Validation

    iex> DialectMapper.valid_base_code?("en")
    true

    iex> DialectMapper.valid_base_code?("xx")
    false

## Getting Available Dialects

    iex> DialectMapper.dialects_for_base("en")
    ["en-US", "en-GB", "en-CA", "en-AU"]

    iex> DialectMapper.dialects_for_base("es")
    ["es-ES", "es-MX", "es-AR", "es-CO"]

# `base_to_dialect`

Converts base language code to default dialect.

Uses predefined mapping for most common regional variants.
Falls back to base code if no mapping exists.

## Examples

    iex> DialectMapper.base_to_dialect("en")
    "en-US"

    iex> DialectMapper.base_to_dialect("pt")
    "pt-BR"

    iex> DialectMapper.base_to_dialect("ja")
    "ja"

    iex> DialectMapper.base_to_dialect("xx")
    "xx"

# `default_dialects`

Gets the default dialects map.

Useful for debugging, testing, or documentation purposes.

## Examples

    iex> defaults = DialectMapper.default_dialects()
    iex> defaults["en"]
    "en-US"

    iex> defaults["pt"]
    "pt-BR"

# `dialects_for_base`

Gets all available dialect codes for a base language.

Searches the predefined language list for all dialects
matching the given base code.

## Examples

    iex> DialectMapper.dialects_for_base("en")
    ["en-US", "en-GB", "en-CA", "en-AU"]

    iex> DialectMapper.dialects_for_base("es")
    ["es-ES", "es-MX", "es-AR", "es-CO"]

    iex> DialectMapper.dialects_for_base("ja")
    ["ja"]

    iex> DialectMapper.dialects_for_base("xx")
    []

## Use Cases

- Populate user preference dropdown
- Admin analytics (dialects per base language)
- Migration tools (find affected users)

# `extract_base`

Extracts base language code from full dialect code.

Splits on hyphen and returns first part (lowercased).
Handles both dialect codes (en-US) and base codes (en).
Returns "en" as default fallback for nil and empty string values.

## Examples

    iex> DialectMapper.extract_base("en-US")
    "en"

    iex> DialectMapper.extract_base("es-MX")
    "es"

    iex> DialectMapper.extract_base("zh-Hans-CN")
    "zh"

    iex> DialectMapper.extract_base("ja")
    "ja"

    iex> DialectMapper.extract_base("EN-GB")
    "en"

    iex> DialectMapper.extract_base(nil)
    "en"

    iex> DialectMapper.extract_base("")
    "en"

# `group_dialects_by_base`

Counts how many entries share each base language code in a list of
language entries. Used by language switchers to decide whether to
show a country qualifier or just the bare language name.

Accepts both maps with atom-keyed `:code` (e.g. `%Language{}` structs)
and string-keyed `"code"` (e.g. JSON-decoded settings). Entries
without a recognizable code are skipped.

## Examples

    iex> DialectMapper.group_dialects_by_base([
    ...>   %{code: "en-US"},
    ...>   %{code: "en-GB"},
    ...>   %{code: "et-EE"}
    ...> ])
    %{"en" => 2, "et" => 1}

    iex> DialectMapper.group_dialects_by_base([])
    %{}

# `resolve_dialect`

Resolves the full dialect code for a base language URL.

The URL is authoritative: the base code maps straight to its default
dialect via `base_to_dialect/1`. User locale preferences are
deliberately NOT consulted here — locale resolution is URL-driven
across both the LiveView mount and the HTTP plug (see the rationale in
`PhoenixKitWeb.Users.Auth`). Resolving without a user keeps a logged-in
user's `custom_fields["preferred_locale"]` from silently upgrading e.g.
base `"en"` to `"en-GB"`.

## Examples

    iex> DialectMapper.resolve_dialect("en")
    "en-US"

    iex> DialectMapper.resolve_dialect("es")
    "es-ES"

    iex> DialectMapper.resolve_dialect("ja")
    "ja"

# `valid_base_code?`

Validates if a base language code is supported.

Checks if the default dialect for this base code exists in the
predefined language list.

## Examples

    iex> DialectMapper.valid_base_code?("en")
    true

    iex> DialectMapper.valid_base_code?("ja")
    true

    iex> DialectMapper.valid_base_code?("xx")
    false

    iex> DialectMapper.valid_base_code?("en-US")
    false  # Not a base code (contains hyphen)

## Notes

- Only validates base codes (2 letters)
- Full dialect codes will return false (use extract_base first)
- Checks against Languages.get_predefined_language/1

---

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